qa360 1.0.4 ā 1.1.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/dist/commands/history.js +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/commands/verify.js +1 -1
- package/dist/core/adapters/gitleaks-secrets.d.ts +115 -0
- package/dist/core/adapters/gitleaks-secrets.d.ts.map +1 -0
- package/dist/core/adapters/gitleaks-secrets.js +410 -0
- package/dist/core/adapters/k6-perf.d.ts +86 -0
- package/dist/core/adapters/k6-perf.d.ts.map +1 -0
- package/dist/core/adapters/k6-perf.js +398 -0
- package/dist/core/adapters/osv-deps.d.ts +124 -0
- package/dist/core/adapters/osv-deps.d.ts.map +1 -0
- package/dist/core/adapters/osv-deps.js +372 -0
- package/dist/core/adapters/playwright-api.d.ts +82 -0
- package/dist/core/adapters/playwright-api.d.ts.map +1 -0
- package/dist/core/adapters/playwright-api.js +252 -0
- package/dist/core/adapters/playwright-ui.d.ts +115 -0
- package/dist/core/adapters/playwright-ui.d.ts.map +1 -0
- package/dist/core/adapters/playwright-ui.js +346 -0
- package/dist/core/adapters/semgrep-sast.d.ts +100 -0
- package/dist/core/adapters/semgrep-sast.d.ts.map +1 -0
- package/dist/core/adapters/semgrep-sast.js +322 -0
- package/dist/core/adapters/zap-dast.d.ts +134 -0
- package/dist/core/adapters/zap-dast.d.ts.map +1 -0
- package/dist/core/adapters/zap-dast.js +424 -0
- package/dist/core/hooks/compose.d.ts +62 -0
- package/dist/core/hooks/compose.d.ts.map +1 -0
- package/dist/core/hooks/compose.js +225 -0
- package/dist/core/hooks/runner.d.ts +69 -0
- package/dist/core/hooks/runner.d.ts.map +1 -0
- package/dist/core/hooks/runner.js +303 -0
- package/dist/core/index.d.ts +74 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +39 -0
- package/dist/core/pack/migrator.d.ts +52 -0
- package/dist/core/pack/migrator.d.ts.map +1 -0
- package/dist/core/pack/migrator.js +304 -0
- package/dist/core/pack/validator.d.ts +43 -0
- package/dist/core/pack/validator.d.ts.map +1 -0
- package/dist/core/pack/validator.js +292 -0
- package/dist/core/proof/bundle.d.ts +138 -0
- package/dist/core/proof/bundle.d.ts.map +1 -0
- package/dist/core/proof/bundle.js +160 -0
- package/dist/core/proof/canonicalize.d.ts +48 -0
- package/dist/core/proof/canonicalize.d.ts.map +1 -0
- package/dist/core/proof/canonicalize.js +105 -0
- package/dist/core/proof/index.d.ts +14 -0
- package/dist/core/proof/index.d.ts.map +1 -0
- package/dist/core/proof/index.js +18 -0
- package/dist/core/proof/schema.d.ts +218 -0
- package/dist/core/proof/schema.d.ts.map +1 -0
- package/dist/core/proof/schema.js +263 -0
- package/dist/core/proof/signer.d.ts +112 -0
- package/dist/core/proof/signer.d.ts.map +1 -0
- package/dist/core/proof/signer.js +226 -0
- package/dist/core/proof/verifier.d.ts +98 -0
- package/dist/core/proof/verifier.d.ts.map +1 -0
- package/dist/core/proof/verifier.js +302 -0
- package/dist/core/runner/phase3-runner.d.ts +102 -0
- package/dist/core/runner/phase3-runner.d.ts.map +1 -0
- package/dist/core/runner/phase3-runner.js +471 -0
- package/dist/core/secrets/crypto.d.ts +76 -0
- package/dist/core/secrets/crypto.d.ts.map +1 -0
- package/dist/core/secrets/crypto.js +225 -0
- package/dist/core/secrets/manager.d.ts +77 -0
- package/dist/core/secrets/manager.d.ts.map +1 -0
- package/dist/core/secrets/manager.js +219 -0
- package/dist/core/security/redaction-patterns-extended.d.ts +28 -0
- package/dist/core/security/redaction-patterns-extended.d.ts.map +1 -0
- package/dist/core/security/redaction-patterns-extended.js +247 -0
- package/dist/core/security/redactor.d.ts +72 -0
- package/dist/core/security/redactor.d.ts.map +1 -0
- package/dist/core/security/redactor.js +279 -0
- package/dist/core/serve/diagnostics-collector.d.ts +33 -0
- package/dist/core/serve/diagnostics-collector.d.ts.map +1 -0
- package/dist/core/serve/diagnostics-collector.js +149 -0
- package/dist/core/serve/health-checker.d.ts +45 -0
- package/dist/core/serve/health-checker.d.ts.map +1 -0
- package/dist/core/serve/health-checker.js +219 -0
- package/dist/core/serve/index.d.ts +9 -0
- package/dist/core/serve/index.d.ts.map +1 -0
- package/dist/core/serve/index.js +8 -0
- package/dist/core/serve/metrics-collector.d.ts +25 -0
- package/dist/core/serve/metrics-collector.d.ts.map +1 -0
- package/dist/core/serve/metrics-collector.js +322 -0
- package/dist/core/serve/process-manager.d.ts +37 -0
- package/dist/core/serve/process-manager.d.ts.map +1 -0
- package/dist/core/serve/process-manager.js +213 -0
- package/dist/core/serve/server.d.ts +37 -0
- package/dist/core/serve/server.d.ts.map +1 -0
- package/dist/core/serve/server.js +191 -0
- package/dist/core/types/pack-v1.d.ts +162 -0
- package/dist/core/types/pack-v1.d.ts.map +1 -0
- package/dist/core/types/pack-v1.js +5 -0
- package/dist/core/types/trust-score.d.ts +70 -0
- package/dist/core/types/trust-score.d.ts.map +1 -0
- package/dist/core/types/trust-score.js +191 -0
- package/dist/core/vault/cas.d.ts +87 -0
- package/dist/core/vault/cas.d.ts.map +1 -0
- package/dist/core/vault/cas.js +255 -0
- package/dist/core/vault/index.d.ts +205 -0
- package/dist/core/vault/index.d.ts.map +1 -0
- package/dist/core/vault/index.js +631 -0
- package/package.json +12 -6
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Docker Compose Helper
|
|
3
|
+
* Safe Docker Compose operations with health checks and error handling
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
export class ComposeHelper {
|
|
10
|
+
workingDir;
|
|
11
|
+
composeFile;
|
|
12
|
+
isUp = false;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.workingDir = options.workingDir;
|
|
15
|
+
this.composeFile = options.composeFile || 'services.yml';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Start services with Docker Compose
|
|
19
|
+
*/
|
|
20
|
+
async up(timeout = 120000) {
|
|
21
|
+
// Idempotence check
|
|
22
|
+
if (this.isUp) {
|
|
23
|
+
console.log(chalk.yellow(' ā ļø Services already up, skipping'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const composeFilePath = join(this.workingDir, this.composeFile);
|
|
27
|
+
// Validate compose file exists
|
|
28
|
+
if (!existsSync(composeFilePath)) {
|
|
29
|
+
const error = new Error(`Compose file not found: ${composeFilePath}`);
|
|
30
|
+
error.code = 'QH001';
|
|
31
|
+
error.suggestion = `Create ${this.composeFile} in project root or set hooks.compose_file in pack.yml`;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
// Check if Docker is available
|
|
36
|
+
await this.checkDockerAvailable();
|
|
37
|
+
// Execute compose up
|
|
38
|
+
const result = await this.executeCompose(['up', '-d'], timeout);
|
|
39
|
+
if (result.success) {
|
|
40
|
+
this.isUp = true;
|
|
41
|
+
console.log(chalk.green(' š³ Services started successfully'));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const error = new Error(`Compose up failed: ${result.error}`);
|
|
45
|
+
error.code = 'QH002';
|
|
46
|
+
error.suggestion = 'Check Docker daemon is running and compose file syntax is valid';
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof Error && 'code' in error) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
const composeError = new Error(`Compose operation failed: ${error}`);
|
|
55
|
+
composeError.code = 'QH003';
|
|
56
|
+
composeError.suggestion = 'Verify Docker installation and compose file configuration';
|
|
57
|
+
throw composeError;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Stop and remove services
|
|
62
|
+
*/
|
|
63
|
+
async down(timeout = 60000) {
|
|
64
|
+
try {
|
|
65
|
+
const result = await this.executeCompose(['down', '-v'], timeout);
|
|
66
|
+
if (result.success) {
|
|
67
|
+
this.isUp = false;
|
|
68
|
+
console.log(chalk.green(' š³ Services stopped and cleaned up'));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(chalk.yellow(` ā ļø Compose down completed with warnings: ${result.error}`));
|
|
72
|
+
this.isUp = false; // Consider it down even if there were warnings
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Don't throw on down errors - just log and continue
|
|
77
|
+
console.log(chalk.yellow(` ā ļø Compose down error (continuing): ${error}`));
|
|
78
|
+
this.isUp = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get service status
|
|
83
|
+
*/
|
|
84
|
+
async getStatus() {
|
|
85
|
+
try {
|
|
86
|
+
const result = await this.executeCompose(['ps', '--format', 'json'], 10000);
|
|
87
|
+
if (result.success && result.output) {
|
|
88
|
+
const services = result.output
|
|
89
|
+
.split('\n')
|
|
90
|
+
.filter(line => line.trim())
|
|
91
|
+
.map(line => {
|
|
92
|
+
try {
|
|
93
|
+
const service = JSON.parse(line);
|
|
94
|
+
return {
|
|
95
|
+
service: service.Name || service.Service || 'unknown',
|
|
96
|
+
status: service.State || service.Status || 'unknown',
|
|
97
|
+
ports: service.Ports ? service.Ports.split(',').map((p) => p.trim()) : []
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
.filter((service) => service !== null);
|
|
105
|
+
return services;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.log(chalk.yellow(` ā ļø Could not get service status: ${error}`));
|
|
110
|
+
}
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if Docker is available
|
|
115
|
+
*/
|
|
116
|
+
async checkDockerAvailable() {
|
|
117
|
+
try {
|
|
118
|
+
const result = await this.executeCommand('docker --version', 5000);
|
|
119
|
+
if (!result.success) {
|
|
120
|
+
const error = new Error('Docker not available');
|
|
121
|
+
error.code = 'QH004';
|
|
122
|
+
error.suggestion = 'Install Docker Desktop or Docker Engine';
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const dockerError = new Error('Docker daemon not running');
|
|
128
|
+
dockerError.code = 'QH005';
|
|
129
|
+
dockerError.suggestion = 'Start Docker Desktop or Docker daemon service';
|
|
130
|
+
throw dockerError;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Execute Docker Compose command
|
|
135
|
+
*/
|
|
136
|
+
async executeCompose(args, timeout) {
|
|
137
|
+
const composeFilePath = join(this.workingDir, this.composeFile);
|
|
138
|
+
const command = `docker compose -f "${composeFilePath}" ${args.join(' ')}`;
|
|
139
|
+
return this.executeCommand(command, timeout);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Execute shell command with timeout
|
|
143
|
+
*/
|
|
144
|
+
async executeCommand(command, timeout) {
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
let output = '';
|
|
147
|
+
let error = '';
|
|
148
|
+
const child = spawn(command, [], {
|
|
149
|
+
cwd: this.workingDir,
|
|
150
|
+
shell: true,
|
|
151
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
152
|
+
});
|
|
153
|
+
const timer = setTimeout(() => {
|
|
154
|
+
child.kill('SIGKILL');
|
|
155
|
+
resolve({
|
|
156
|
+
success: false,
|
|
157
|
+
output,
|
|
158
|
+
error: `Command timed out after ${timeout}ms`
|
|
159
|
+
});
|
|
160
|
+
}, timeout);
|
|
161
|
+
if (child.stdout) {
|
|
162
|
+
child.stdout.on('data', (data) => {
|
|
163
|
+
output += data.toString();
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (child.stderr) {
|
|
167
|
+
child.stderr.on('data', (data) => {
|
|
168
|
+
error += data.toString();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
child.on('close', (code) => {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
resolve({
|
|
174
|
+
success: code === 0,
|
|
175
|
+
output,
|
|
176
|
+
error: error || undefined
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
child.on('error', (err) => {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
resolve({
|
|
182
|
+
success: false,
|
|
183
|
+
output,
|
|
184
|
+
error: err.message
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Validate compose file syntax
|
|
191
|
+
*/
|
|
192
|
+
async validateComposeFile() {
|
|
193
|
+
const errors = [];
|
|
194
|
+
const composeFilePath = join(this.workingDir, this.composeFile);
|
|
195
|
+
if (!existsSync(composeFilePath)) {
|
|
196
|
+
errors.push(`Compose file not found: ${composeFilePath}`);
|
|
197
|
+
return { valid: false, errors };
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const result = await this.executeCompose(['config', '--quiet'], 10000);
|
|
201
|
+
if (!result.success) {
|
|
202
|
+
errors.push(`Compose file validation failed: ${result.error}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
errors.push(`Could not validate compose file: ${error}`);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
valid: errors.length === 0,
|
|
210
|
+
errors
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get compose file path
|
|
215
|
+
*/
|
|
216
|
+
getComposeFilePath() {
|
|
217
|
+
return join(this.workingDir, this.composeFile);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Check if services are currently up
|
|
221
|
+
*/
|
|
222
|
+
isServicesUp() {
|
|
223
|
+
return this.isUp;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Hooks Runner
|
|
3
|
+
* Executes declarative hooks with timeouts, failure handling, and security
|
|
4
|
+
*/
|
|
5
|
+
import { PackHooks, PackExecution } from '../types/pack-v1.js';
|
|
6
|
+
import { SecurityRedactor } from '../security/redactor.js';
|
|
7
|
+
export interface HookExecutionResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
duration: number;
|
|
10
|
+
output: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
code?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface HookRunnerOptions {
|
|
15
|
+
workingDir: string;
|
|
16
|
+
hooks: PackHooks;
|
|
17
|
+
execution?: PackExecution;
|
|
18
|
+
redactor?: SecurityRedactor;
|
|
19
|
+
}
|
|
20
|
+
export declare class HooksRunner {
|
|
21
|
+
private workingDir;
|
|
22
|
+
private hooks;
|
|
23
|
+
private execution;
|
|
24
|
+
private redactor;
|
|
25
|
+
private composeHelper;
|
|
26
|
+
constructor(options: HookRunnerOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Execute hooks for a specific lifecycle phase
|
|
29
|
+
*/
|
|
30
|
+
executeHooks(phase: keyof PackHooks): Promise<HookExecutionResult[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Execute a single hook action
|
|
33
|
+
*/
|
|
34
|
+
private executeHook;
|
|
35
|
+
/**
|
|
36
|
+
* Execute a shell command
|
|
37
|
+
*/
|
|
38
|
+
private executeCommand;
|
|
39
|
+
/**
|
|
40
|
+
* Execute Docker Compose command
|
|
41
|
+
*/
|
|
42
|
+
private executeCompose;
|
|
43
|
+
/**
|
|
44
|
+
* Execute wait-on for service readiness
|
|
45
|
+
*/
|
|
46
|
+
private executeWaitOn;
|
|
47
|
+
/**
|
|
48
|
+
* Wait for HTTP endpoint to be ready
|
|
49
|
+
*/
|
|
50
|
+
private waitForHttp;
|
|
51
|
+
/**
|
|
52
|
+
* Wait for TCP port to be ready
|
|
53
|
+
*/
|
|
54
|
+
private waitForTcp;
|
|
55
|
+
/**
|
|
56
|
+
* Get human-readable description of hook action
|
|
57
|
+
*/
|
|
58
|
+
private getHookDescription;
|
|
59
|
+
/**
|
|
60
|
+
* Execute all lifecycle hooks in order
|
|
61
|
+
*/
|
|
62
|
+
executeLifecycle(): Promise<{
|
|
63
|
+
beforeAll: HookExecutionResult[];
|
|
64
|
+
beforeEach: HookExecutionResult[];
|
|
65
|
+
afterEach: HookExecutionResult[];
|
|
66
|
+
afterAll: HookExecutionResult[];
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/runner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAc,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,EAAE,iBAAiB;IAWtC;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,SAAS,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAiE1E;;OAEG;YACW,WAAW;IAczB;;OAEG;YACW,cAAc;IAgE5B;;OAEG;YACW,cAAc;IAyB5B;;OAEG;YACW,aAAa;IAyB3B;;OAEG;YACW,WAAW;IAwBzB;;OAEG;YACW,UAAU;IAyCxB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC;QAChC,SAAS,EAAE,mBAAmB,EAAE,CAAC;QACjC,UAAU,EAAE,mBAAmB,EAAE,CAAC;QAClC,SAAS,EAAE,mBAAmB,EAAE,CAAC;QACjC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;KACjC,CAAC;CAgBH"}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Hooks Runner
|
|
3
|
+
* Executes declarative hooks with timeouts, failure handling, and security
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { SecurityRedactor } from '../security/redactor.js';
|
|
8
|
+
import { ComposeHelper } from './compose.js';
|
|
9
|
+
export class HooksRunner {
|
|
10
|
+
workingDir;
|
|
11
|
+
hooks;
|
|
12
|
+
execution;
|
|
13
|
+
redactor;
|
|
14
|
+
composeHelper;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.workingDir = options.workingDir;
|
|
17
|
+
this.hooks = options.hooks;
|
|
18
|
+
this.execution = options.execution || {};
|
|
19
|
+
this.redactor = options.redactor || SecurityRedactor.forLogs();
|
|
20
|
+
this.composeHelper = new ComposeHelper({
|
|
21
|
+
workingDir: this.workingDir,
|
|
22
|
+
composeFile: this.execution.compose_file || 'services.yml'
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Execute hooks for a specific lifecycle phase
|
|
27
|
+
*/
|
|
28
|
+
async executeHooks(phase) {
|
|
29
|
+
const hooks = this.hooks[phase];
|
|
30
|
+
if (!hooks || hooks.length === 0) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
console.log(chalk.blue(`š Executing ${phase} hooks (${hooks.length} actions)`));
|
|
34
|
+
const results = [];
|
|
35
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
36
|
+
const hook = hooks[i];
|
|
37
|
+
const startTime = Date.now();
|
|
38
|
+
try {
|
|
39
|
+
console.log(chalk.gray(` ${i + 1}/${hooks.length} ${this.getHookDescription(hook)}`));
|
|
40
|
+
const result = await this.executeHook(hook);
|
|
41
|
+
const duration = Date.now() - startTime;
|
|
42
|
+
const hookResult = {
|
|
43
|
+
success: result.success,
|
|
44
|
+
duration,
|
|
45
|
+
output: this.redactor.redact(result.output),
|
|
46
|
+
error: result.error ? this.redactor.redact(result.error) : undefined,
|
|
47
|
+
code: result.code
|
|
48
|
+
};
|
|
49
|
+
results.push(hookResult);
|
|
50
|
+
if (result.success) {
|
|
51
|
+
console.log(chalk.green(` ā
Completed in ${duration}ms`));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log(chalk.red(` ā Failed after ${duration}ms (exit ${result.code})`));
|
|
55
|
+
if (this.execution.on_failure === 'stop') {
|
|
56
|
+
console.log(chalk.yellow(` š Stopping execution (on_failure: stop)`));
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log(chalk.yellow(` ā ļø Continuing execution (on_failure: continue)`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const duration = Date.now() - startTime;
|
|
66
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
67
|
+
console.log(chalk.red(` š„ Hook failed: ${errorMessage}`));
|
|
68
|
+
results.push({
|
|
69
|
+
success: false,
|
|
70
|
+
duration,
|
|
71
|
+
output: '',
|
|
72
|
+
error: this.redactor.redact(errorMessage)
|
|
73
|
+
});
|
|
74
|
+
if (this.execution.on_failure === 'stop') {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Execute a single hook action
|
|
83
|
+
*/
|
|
84
|
+
async executeHook(hook) {
|
|
85
|
+
const timeout = hook.timeout || this.execution.hook_timeout_ms || 180000; // 3 minutes default
|
|
86
|
+
if ('run' in hook) {
|
|
87
|
+
return this.executeCommand(hook.run, timeout);
|
|
88
|
+
}
|
|
89
|
+
else if ('compose' in hook) {
|
|
90
|
+
return this.executeCompose(hook.compose, timeout);
|
|
91
|
+
}
|
|
92
|
+
else if ('wait_on' in hook) {
|
|
93
|
+
return this.executeWaitOn(hook.wait_on, timeout, hook.method || 'http');
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
throw new Error('Unknown hook action type');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Execute a shell command
|
|
101
|
+
*/
|
|
102
|
+
async executeCommand(command, timeout) {
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
const startTime = Date.now();
|
|
105
|
+
let output = '';
|
|
106
|
+
let error = '';
|
|
107
|
+
const options = {
|
|
108
|
+
cwd: this.workingDir,
|
|
109
|
+
shell: true,
|
|
110
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
111
|
+
};
|
|
112
|
+
const child = spawn(command, [], options);
|
|
113
|
+
// Timeout handler
|
|
114
|
+
const timer = setTimeout(() => {
|
|
115
|
+
child.kill('SIGKILL');
|
|
116
|
+
resolve({
|
|
117
|
+
success: false,
|
|
118
|
+
duration: Date.now() - startTime,
|
|
119
|
+
output,
|
|
120
|
+
error: `Command timed out after ${timeout}ms`,
|
|
121
|
+
code: -1
|
|
122
|
+
});
|
|
123
|
+
}, timeout);
|
|
124
|
+
// Collect output
|
|
125
|
+
if (child.stdout) {
|
|
126
|
+
child.stdout.on('data', (data) => {
|
|
127
|
+
output += data.toString();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (child.stderr) {
|
|
131
|
+
child.stderr.on('data', (data) => {
|
|
132
|
+
error += data.toString();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Handle completion
|
|
136
|
+
child.on('close', (code) => {
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
resolve({
|
|
139
|
+
success: code === 0,
|
|
140
|
+
duration: Date.now() - startTime,
|
|
141
|
+
output,
|
|
142
|
+
error: error || undefined,
|
|
143
|
+
code: code || 0
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
child.on('error', (err) => {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
resolve({
|
|
149
|
+
success: false,
|
|
150
|
+
duration: Date.now() - startTime,
|
|
151
|
+
output,
|
|
152
|
+
error: err.message,
|
|
153
|
+
code: -1
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Execute Docker Compose command
|
|
160
|
+
*/
|
|
161
|
+
async executeCompose(action, timeout) {
|
|
162
|
+
const startTime = Date.now();
|
|
163
|
+
try {
|
|
164
|
+
if (action === 'up') {
|
|
165
|
+
await this.composeHelper.up(timeout);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
await this.composeHelper.down(timeout);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
success: true,
|
|
172
|
+
duration: Date.now() - startTime,
|
|
173
|
+
output: `Docker Compose ${action} completed successfully`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
duration: Date.now() - startTime,
|
|
180
|
+
output: '',
|
|
181
|
+
error: error instanceof Error ? error.message : 'Compose command failed'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Execute wait-on for service readiness
|
|
187
|
+
*/
|
|
188
|
+
async executeWaitOn(target, timeout, method) {
|
|
189
|
+
const startTime = Date.now();
|
|
190
|
+
try {
|
|
191
|
+
if (method === 'http') {
|
|
192
|
+
await this.waitForHttp(target, timeout);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
await this.waitForTcp(target, timeout);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
success: true,
|
|
199
|
+
duration: Date.now() - startTime,
|
|
200
|
+
output: `Service ${target} is ready (${method})`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
return {
|
|
205
|
+
success: false,
|
|
206
|
+
duration: Date.now() - startTime,
|
|
207
|
+
output: '',
|
|
208
|
+
error: error instanceof Error ? error.message : 'Wait-on failed'
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Wait for HTTP endpoint to be ready
|
|
214
|
+
*/
|
|
215
|
+
async waitForHttp(url, timeout) {
|
|
216
|
+
const startTime = Date.now();
|
|
217
|
+
const interval = 1000; // Check every second
|
|
218
|
+
while (Date.now() - startTime < timeout) {
|
|
219
|
+
try {
|
|
220
|
+
const response = await fetch(url, {
|
|
221
|
+
method: 'GET',
|
|
222
|
+
signal: AbortSignal.timeout(5000) // 5s per request
|
|
223
|
+
});
|
|
224
|
+
if (response.ok) {
|
|
225
|
+
return; // Success
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
// Continue waiting
|
|
230
|
+
}
|
|
231
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
232
|
+
}
|
|
233
|
+
throw new Error(`HTTP endpoint ${url} not ready after ${timeout}ms`);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Wait for TCP port to be ready
|
|
237
|
+
*/
|
|
238
|
+
async waitForTcp(target, timeout) {
|
|
239
|
+
const [host, portStr] = target.split(':');
|
|
240
|
+
const port = parseInt(portStr, 10);
|
|
241
|
+
if (!host || !port) {
|
|
242
|
+
throw new Error(`Invalid TCP target format: ${target} (expected host:port)`);
|
|
243
|
+
}
|
|
244
|
+
const startTime = Date.now();
|
|
245
|
+
const interval = 1000;
|
|
246
|
+
while (Date.now() - startTime < timeout) {
|
|
247
|
+
try {
|
|
248
|
+
const net = await import('net');
|
|
249
|
+
const socket = new net.Socket();
|
|
250
|
+
await new Promise((resolve, reject) => {
|
|
251
|
+
socket.setTimeout(2000);
|
|
252
|
+
socket.on('connect', () => {
|
|
253
|
+
socket.destroy();
|
|
254
|
+
resolve();
|
|
255
|
+
});
|
|
256
|
+
socket.on('timeout', () => {
|
|
257
|
+
socket.destroy();
|
|
258
|
+
reject(new Error('Connection timeout'));
|
|
259
|
+
});
|
|
260
|
+
socket.on('error', reject);
|
|
261
|
+
socket.connect(port, host);
|
|
262
|
+
});
|
|
263
|
+
return; // Success
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
// Continue waiting
|
|
267
|
+
}
|
|
268
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
269
|
+
}
|
|
270
|
+
throw new Error(`TCP port ${target} not ready after ${timeout}ms`);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get human-readable description of hook action
|
|
274
|
+
*/
|
|
275
|
+
getHookDescription(hook) {
|
|
276
|
+
if ('run' in hook) {
|
|
277
|
+
return `Run: ${hook.run.substring(0, 50)}${hook.run.length > 50 ? '...' : ''}`;
|
|
278
|
+
}
|
|
279
|
+
else if ('compose' in hook) {
|
|
280
|
+
return `Compose: ${hook.compose}`;
|
|
281
|
+
}
|
|
282
|
+
else if ('wait_on' in hook) {
|
|
283
|
+
return `Wait-on: ${hook.wait_on} (${hook.method || 'http'})`;
|
|
284
|
+
}
|
|
285
|
+
return 'Unknown action';
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Execute all lifecycle hooks in order
|
|
289
|
+
*/
|
|
290
|
+
async executeLifecycle() {
|
|
291
|
+
const results = {
|
|
292
|
+
beforeAll: await this.executeHooks('beforeAll'),
|
|
293
|
+
beforeEach: await this.executeHooks('beforeEach'),
|
|
294
|
+
afterEach: await this.executeHooks('afterEach'),
|
|
295
|
+
afterAll: await this.executeHooks('afterAll')
|
|
296
|
+
};
|
|
297
|
+
// Summary
|
|
298
|
+
const totalHooks = Object.values(results).flat().length;
|
|
299
|
+
const successfulHooks = Object.values(results).flat().filter(r => r.success).length;
|
|
300
|
+
console.log(chalk.blue(`\nš Hooks Summary: ${successfulHooks}/${totalHooks} successful`));
|
|
301
|
+
return results;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Core Engine
|
|
3
|
+
*
|
|
4
|
+
* The heart of QA360 - handles proof generation, cryptographic signatures,
|
|
5
|
+
* and evidence vault for verifiable quality assurance.
|
|
6
|
+
*/
|
|
7
|
+
export interface Pack {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
adapters: string[];
|
|
12
|
+
tests: TestSpec[];
|
|
13
|
+
hooks?: PackHooks;
|
|
14
|
+
}
|
|
15
|
+
export interface TestSpec {
|
|
16
|
+
name: string;
|
|
17
|
+
adapter: string;
|
|
18
|
+
config: Record<string, any>;
|
|
19
|
+
}
|
|
20
|
+
export interface PackHooks {
|
|
21
|
+
beforeAll?: string[];
|
|
22
|
+
afterAll?: string[];
|
|
23
|
+
beforeEach?: string[];
|
|
24
|
+
afterEach?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface Proof {
|
|
27
|
+
id: string;
|
|
28
|
+
pack: Pack;
|
|
29
|
+
results: TestResult[];
|
|
30
|
+
signature: string;
|
|
31
|
+
trustScore: number;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
metadata: ProofMetadata;
|
|
34
|
+
}
|
|
35
|
+
export interface TestResult {
|
|
36
|
+
testName: string;
|
|
37
|
+
adapter: string;
|
|
38
|
+
status: 'passed' | 'failed' | 'skipped';
|
|
39
|
+
duration: number;
|
|
40
|
+
evidence: Evidence[];
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface Evidence {
|
|
44
|
+
type: 'screenshot' | 'log' | 'metric' | 'artifact';
|
|
45
|
+
data: string | Buffer;
|
|
46
|
+
metadata: Record<string, any>;
|
|
47
|
+
}
|
|
48
|
+
export interface ProofMetadata {
|
|
49
|
+
environment: string;
|
|
50
|
+
platform: string;
|
|
51
|
+
qa360Version: string;
|
|
52
|
+
generatedBy: string;
|
|
53
|
+
}
|
|
54
|
+
export declare class QA360Core {
|
|
55
|
+
constructor();
|
|
56
|
+
generateProof(pack: Pack, results: TestResult[]): Promise<Proof>;
|
|
57
|
+
verifyProof(proof: Proof): Promise<boolean>;
|
|
58
|
+
calculateTrustScore(results: TestResult[]): Promise<number>;
|
|
59
|
+
}
|
|
60
|
+
export declare const VERSION = "0.9.0-core";
|
|
61
|
+
export * from './proof/index.js';
|
|
62
|
+
export { EvidenceVault } from './vault/index.js';
|
|
63
|
+
export type { RunRecord, GateRecord, FindingRecord } from './vault/index.js';
|
|
64
|
+
export { SecurityRedactor } from './security/redactor.js';
|
|
65
|
+
export { SecretsManager } from './secrets/manager.js';
|
|
66
|
+
export { SecretsCrypto } from './secrets/crypto.js';
|
|
67
|
+
export { PackValidator } from './pack/validator.js';
|
|
68
|
+
export { PackMigrator } from './pack/migrator.js';
|
|
69
|
+
export type { PackConfigV1 } from './types/pack-v1.js';
|
|
70
|
+
export { QA360Server } from './serve/server.js';
|
|
71
|
+
export type { ServeConfig } from './serve/server.js';
|
|
72
|
+
export { Phase3Runner } from './runner/phase3-runner.js';
|
|
73
|
+
export type { Phase3RunnerOptions, Phase3RunResult, GateResult } from './runner/phase3-runner.js';
|
|
74
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"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"}
|