verify-grid 0.2.2 → 0.3.1
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/README.md +262 -22
- package/dist/cli.js +60 -25
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +57 -23
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,48 +1,288 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# 🎯 verify-grid
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Run matrix of tasks across services with parallel execution and live status reporting**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/verify-grid)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
|
|
12
|
+
<img src="./example.gif" alt="Demo" width="800"/>
|
|
13
|
+
|
|
14
|
+
[Features](#-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [CLI](#-cli-commands) • [Configuration](#-configuration)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ✨ Features
|
|
21
|
+
|
|
22
|
+
- 🚀 **Parallel Execution** - Run tasks concurrently with configurable limits
|
|
23
|
+
- 🎬 **Live Updates** - Animated spinners and real-time status in your terminal
|
|
24
|
+
- 🎯 **Priority Control** - Execute tasks in specific order or randomize for testing
|
|
25
|
+
- ⚡ **Fail-Fast Mode** - Stop immediately on first error
|
|
26
|
+
- 📊 **Multiple Formats** - Table, minimal, or JSON output
|
|
27
|
+
- 🎨 **Horizontal Layout** - Optimized for terminal space (services × tasks)
|
|
28
|
+
- 🔧 **Task Overrides** - Customize tasks per service
|
|
29
|
+
- ⏱️ **Timeout Support** - Set time limits for tasks
|
|
30
|
+
- 🌍 **Environment Variables** - Per-task and per-service configuration
|
|
31
|
+
- 🎲 **Random Execution** - Test robustness with randomized order
|
|
32
|
+
- 🔄 **CI/CD Ready** - Proper exit codes (0=success, 1=errors, 2=config error)
|
|
33
|
+
|
|
34
|
+
## 📦 Installation
|
|
6
35
|
|
|
7
36
|
```bash
|
|
8
37
|
npm install verify-grid
|
|
9
38
|
```
|
|
10
39
|
|
|
11
|
-
## Quick Start
|
|
40
|
+
## 🚀 Quick Start
|
|
12
41
|
|
|
13
|
-
Create
|
|
42
|
+
**1. Create configuration file** `qa.config.js`:
|
|
14
43
|
|
|
15
44
|
```javascript
|
|
16
45
|
export default {
|
|
17
46
|
tasks: {
|
|
18
|
-
|
|
19
|
-
|
|
47
|
+
lint: { command: 'npm', args: ['run', 'lint'], priority: 10 },
|
|
48
|
+
test: { command: 'npm', args: ['test'], priority: 5 },
|
|
49
|
+
build: { command: 'npm', args: ['run', 'build'] },
|
|
20
50
|
},
|
|
21
51
|
services: [
|
|
22
|
-
{ name: '
|
|
23
|
-
{ name: '
|
|
52
|
+
{ name: 'api', cwd: './services/api' },
|
|
53
|
+
{ name: 'web', cwd: './services/web' },
|
|
24
54
|
],
|
|
25
55
|
};
|
|
26
56
|
```
|
|
27
57
|
|
|
28
|
-
Run tasks
|
|
58
|
+
**2. Run tasks:**
|
|
29
59
|
|
|
30
60
|
```bash
|
|
31
|
-
|
|
32
|
-
qa run
|
|
33
|
-
|
|
61
|
+
# Run all tasks on all services
|
|
62
|
+
qa run
|
|
63
|
+
|
|
64
|
+
# Run specific tasks
|
|
65
|
+
qa run --tasks lint,test
|
|
66
|
+
|
|
67
|
+
# Stop on first error
|
|
68
|
+
qa run --fail-fast
|
|
69
|
+
|
|
70
|
+
# Randomize execution order
|
|
71
|
+
qa run --random
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 🎮 CLI Commands
|
|
75
|
+
|
|
76
|
+
### `qa run`
|
|
77
|
+
|
|
78
|
+
Execute tasks across services with live status updates.
|
|
79
|
+
|
|
80
|
+
#### Options
|
|
81
|
+
|
|
82
|
+
| Option | Description | Default |
|
|
83
|
+
|--------|-------------|---------|
|
|
84
|
+
| `--tasks <tasks>` | Comma-separated tasks or "all" | `all` |
|
|
85
|
+
| `--services <services>` | Comma-separated services or "all" | `all` |
|
|
86
|
+
| `--concurrency <n>` | Maximum parallel jobs | CPU count |
|
|
87
|
+
| `--fail-fast` | Stop on first error | `false` |
|
|
88
|
+
| `--random` | Randomize execution order | `false` |
|
|
89
|
+
| `--no-watch` | Disable live updates | enabled in TTY |
|
|
90
|
+
| `--format <format>` | Output: table, minimal, json | `table` |
|
|
91
|
+
| `--output <path>` | Save JSON to file | - |
|
|
92
|
+
| `--timeout <ms>` | Timeout per task (milliseconds) | - |
|
|
93
|
+
| `--no-color` | Disable colors | enabled in TTY |
|
|
94
|
+
| `--config <path>` | Custom config file path | `qa.config.js` |
|
|
95
|
+
|
|
96
|
+
#### Examples
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Run all tasks
|
|
100
|
+
qa run
|
|
101
|
+
|
|
102
|
+
# Run specific tasks on specific services
|
|
103
|
+
qa run --tasks lint,test --services api,web
|
|
104
|
+
|
|
105
|
+
# Limit concurrency and stop on error
|
|
106
|
+
qa run --concurrency 2 --fail-fast
|
|
107
|
+
|
|
108
|
+
# Output as JSON
|
|
109
|
+
qa run --format json --output results.json
|
|
110
|
+
|
|
111
|
+
# Set timeout (30 seconds)
|
|
112
|
+
qa run --timeout 30000
|
|
113
|
+
|
|
114
|
+
# Randomize for testing
|
|
115
|
+
qa run --random
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## ⚙️ Configuration
|
|
119
|
+
|
|
120
|
+
### Task Definition
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
{
|
|
124
|
+
tasks: {
|
|
125
|
+
taskName: {
|
|
126
|
+
command: 'npm', // Command to execute (required)
|
|
127
|
+
args: ['test'], // Arguments (optional)
|
|
128
|
+
shell: false, // Run in shell (optional)
|
|
129
|
+
timeout: 30000, // Timeout in ms (optional)
|
|
130
|
+
priority: 10, // Higher = earlier (optional)
|
|
131
|
+
env: { // Environment variables (optional)
|
|
132
|
+
NODE_ENV: 'test'
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Task Properties:**
|
|
140
|
+
|
|
141
|
+
| Property | Type | Required | Description |
|
|
142
|
+
|----------|------|----------|-------------|
|
|
143
|
+
| `command` | `string` | ✅ | Command to execute |
|
|
144
|
+
| `args` | `string[]` | ❌ | Command arguments |
|
|
145
|
+
| `shell` | `boolean` | ❌ | Run in shell (default: false) |
|
|
146
|
+
| `timeout` | `number` | ❌ | Timeout in milliseconds |
|
|
147
|
+
| `priority` | `number` | ❌ | Execution priority (higher first, default: 0) |
|
|
148
|
+
| `env` | `Record<string, string>` | ❌ | Environment variables |
|
|
149
|
+
|
|
150
|
+
### Service Definition
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
{
|
|
154
|
+
services: [
|
|
155
|
+
{
|
|
156
|
+
name: 'api', // Service name (required)
|
|
157
|
+
cwd: './services/api', // Working directory (required)
|
|
158
|
+
tasks: { // Task overrides (optional)
|
|
159
|
+
test: {
|
|
160
|
+
args: ['test', '--coverage'],
|
|
161
|
+
priority: 5
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
env: { // Environment variables (optional)
|
|
165
|
+
PORT: '3000'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Service Properties:**
|
|
173
|
+
|
|
174
|
+
| Property | Type | Required | Description |
|
|
175
|
+
|----------|------|----------|-------------|
|
|
176
|
+
| `name` | `string` | ✅ | Service name |
|
|
177
|
+
| `cwd` | `string` | ✅ | Working directory path |
|
|
178
|
+
| `tasks` | `Record<string, Partial<TaskDef>>` | ❌ | Override task properties |
|
|
179
|
+
| `env` | `Record<string, string>` | ❌ | Environment variables |
|
|
180
|
+
|
|
181
|
+
### Full Example
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
export default {
|
|
185
|
+
tasks: {
|
|
186
|
+
lint: {
|
|
187
|
+
command: 'npm',
|
|
188
|
+
args: ['run', 'lint'],
|
|
189
|
+
timeout: 30000,
|
|
190
|
+
priority: 10,
|
|
191
|
+
},
|
|
192
|
+
test: {
|
|
193
|
+
command: 'npm',
|
|
194
|
+
args: ['test'],
|
|
195
|
+
priority: 5,
|
|
196
|
+
env: { NODE_ENV: 'test' },
|
|
197
|
+
},
|
|
198
|
+
build: {
|
|
199
|
+
command: 'npm',
|
|
200
|
+
args: ['run', 'build'],
|
|
201
|
+
timeout: 60000,
|
|
202
|
+
},
|
|
203
|
+
typecheck: {
|
|
204
|
+
command: 'tsc',
|
|
205
|
+
args: ['--noEmit'],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
services: [
|
|
209
|
+
{
|
|
210
|
+
name: 'api',
|
|
211
|
+
cwd: './services/api',
|
|
212
|
+
env: { PORT: '3000' },
|
|
213
|
+
tasks: {
|
|
214
|
+
test: {
|
|
215
|
+
args: ['test', '--coverage'],
|
|
216
|
+
timeout: 45000,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'web',
|
|
222
|
+
cwd: './services/web',
|
|
223
|
+
env: { PORT: '3001' },
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'shared',
|
|
227
|
+
cwd: './packages/shared',
|
|
228
|
+
tasks: {
|
|
229
|
+
build: { command: 'tsup' },
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 📊 Output Formats
|
|
237
|
+
|
|
238
|
+
### Table (default)
|
|
239
|
+
|
|
240
|
+
Horizontal matrix with live updates:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
Service | lint | test | build
|
|
244
|
+
---------|-----------|--------------|-------
|
|
245
|
+
api | ✓ done | ⠋ running | ⏳ queued
|
|
246
|
+
web | ✓ done | ✓ done | ⏳ queued
|
|
247
|
+
shared | ✓ done | ✗ error | - skipped
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Minimal
|
|
251
|
+
|
|
252
|
+
Simple text output:
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
lint api done
|
|
256
|
+
lint web done
|
|
257
|
+
test api running
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### JSON
|
|
261
|
+
|
|
262
|
+
Structured output for automation:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"startedAt": 1234567890,
|
|
267
|
+
"endedAt": 1234567900,
|
|
268
|
+
"durationMs": 10000,
|
|
269
|
+
"matrix": {
|
|
270
|
+
"lint": {
|
|
271
|
+
"api": { "status": "done", "exitCode": 0, "durationMs": 1234 }
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
"errors": []
|
|
275
|
+
}
|
|
34
276
|
```
|
|
35
277
|
|
|
36
|
-
##
|
|
278
|
+
## 🚦 Exit Codes
|
|
37
279
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- Task overrides per service
|
|
44
|
-
- Timeout support
|
|
280
|
+
| Code | Meaning |
|
|
281
|
+
|------|---------|
|
|
282
|
+
| `0` | All tasks completed successfully ✅ |
|
|
283
|
+
| `1` | One or more tasks failed ❌ |
|
|
284
|
+
| `2` | Configuration error or invalid arguments ⚠️ |
|
|
45
285
|
|
|
46
|
-
## License
|
|
286
|
+
## 📝 License
|
|
47
287
|
|
|
48
288
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ var TaskDefSchema = z.object({
|
|
|
17
17
|
args: z.array(z.string()).optional(),
|
|
18
18
|
shell: z.boolean().optional(),
|
|
19
19
|
timeout: z.number().positive().optional(),
|
|
20
|
+
priority: z.number().optional(),
|
|
20
21
|
env: z.record(z.string()).optional()
|
|
21
22
|
});
|
|
22
23
|
var ServiceDefSchema = z.object({
|
|
@@ -27,6 +28,7 @@ var ServiceDefSchema = z.object({
|
|
|
27
28
|
args: z.array(z.string()).optional(),
|
|
28
29
|
shell: z.boolean().optional(),
|
|
29
30
|
timeout: z.number().positive().optional(),
|
|
31
|
+
priority: z.number().optional(),
|
|
30
32
|
env: z.record(z.string()).optional()
|
|
31
33
|
})).optional(),
|
|
32
34
|
env: z.record(z.string()).optional()
|
|
@@ -148,6 +150,7 @@ function generateJobs(config, taskNames, serviceNames) {
|
|
|
148
150
|
args: taskOverride?.args ?? globalTask.args,
|
|
149
151
|
shell: taskOverride?.shell ?? globalTask.shell ?? true,
|
|
150
152
|
timeout: taskOverride?.timeout ?? globalTask.timeout,
|
|
153
|
+
priority: taskOverride?.priority ?? globalTask.priority,
|
|
151
154
|
env: {
|
|
152
155
|
...globalTask.env,
|
|
153
156
|
...service.env,
|
|
@@ -163,6 +166,7 @@ function generateJobs(config, taskNames, serviceNames) {
|
|
|
163
166
|
args: mergedTask.args,
|
|
164
167
|
shell: mergedTask.shell,
|
|
165
168
|
timeout: mergedTask.timeout,
|
|
169
|
+
priority: mergedTask.priority,
|
|
166
170
|
env: mergedTask.env,
|
|
167
171
|
status: "queued"
|
|
168
172
|
});
|
|
@@ -309,7 +313,12 @@ var Runner = class extends EventEmitter {
|
|
|
309
313
|
for (const job of jobs) {
|
|
310
314
|
this.state.jobs.set(job.id, job);
|
|
311
315
|
}
|
|
312
|
-
|
|
316
|
+
let executableJobs = jobs.filter((j) => j.status === "queued");
|
|
317
|
+
if (this.options.random) {
|
|
318
|
+
executableJobs = this.shuffleJobs(executableJobs);
|
|
319
|
+
} else {
|
|
320
|
+
executableJobs = this.sortJobsByPriority(executableJobs);
|
|
321
|
+
}
|
|
313
322
|
for (const job of executableJobs) {
|
|
314
323
|
if (this.shouldStop) {
|
|
315
324
|
job.status = "canceled";
|
|
@@ -356,6 +365,23 @@ var Runner = class extends EventEmitter {
|
|
|
356
365
|
getState() {
|
|
357
366
|
return this.state;
|
|
358
367
|
}
|
|
368
|
+
sortJobsByPriority(jobs) {
|
|
369
|
+
return jobs.slice().sort((a, b) => {
|
|
370
|
+
const priorityA = a.priority ?? 0;
|
|
371
|
+
const priorityB = b.priority ?? 0;
|
|
372
|
+
return priorityB - priorityA;
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
shuffleJobs(jobs) {
|
|
376
|
+
const shuffled = jobs.slice();
|
|
377
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
378
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
379
|
+
const temp = shuffled[i];
|
|
380
|
+
shuffled[i] = shuffled[j];
|
|
381
|
+
shuffled[j] = temp;
|
|
382
|
+
}
|
|
383
|
+
return shuffled;
|
|
384
|
+
}
|
|
359
385
|
};
|
|
360
386
|
|
|
361
387
|
// src/render/table.ts
|
|
@@ -419,32 +445,33 @@ var Spinner = class {
|
|
|
419
445
|
// src/render/table.ts
|
|
420
446
|
var spinner = new Spinner();
|
|
421
447
|
function renderTable(state) {
|
|
448
|
+
const taskNames = getUniqueTaskNames(state);
|
|
449
|
+
const serviceGroups = groupJobsByService(state);
|
|
422
450
|
const table = new Table({
|
|
423
|
-
head: ["
|
|
451
|
+
head: ["Service", ...taskNames],
|
|
424
452
|
style: {
|
|
425
453
|
head: [],
|
|
426
454
|
border: []
|
|
427
455
|
}
|
|
428
456
|
});
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
457
|
+
for (const [serviceName, jobs] of serviceGroups) {
|
|
458
|
+
const row = [serviceName];
|
|
459
|
+
for (const taskName of taskNames) {
|
|
460
|
+
const job = jobs.find((j) => j.taskName === taskName);
|
|
461
|
+
if (job) {
|
|
462
|
+
const statusColor = getStatusColor(job.status);
|
|
463
|
+
let statusText = job.status;
|
|
464
|
+
if (job.status === "running") {
|
|
465
|
+
statusText = `${spinner.next()} ${job.status}`;
|
|
466
|
+
} else if (job.status === "queued") {
|
|
467
|
+
statusText = `\u23F3 ${job.status}`;
|
|
468
|
+
}
|
|
469
|
+
row.push(statusColor(statusText));
|
|
470
|
+
} else {
|
|
471
|
+
row.push("-");
|
|
439
472
|
}
|
|
440
|
-
const coloredStatus = statusColor(statusText);
|
|
441
|
-
table.push([
|
|
442
|
-
isFirstRow ? taskName : "",
|
|
443
|
-
job.serviceName,
|
|
444
|
-
coloredStatus
|
|
445
|
-
]);
|
|
446
|
-
isFirstRow = false;
|
|
447
473
|
}
|
|
474
|
+
table.push(row);
|
|
448
475
|
}
|
|
449
476
|
return table.toString();
|
|
450
477
|
}
|
|
@@ -489,16 +516,23 @@ function renderJson(state) {
|
|
|
489
516
|
};
|
|
490
517
|
return JSON.stringify(output, null, 2);
|
|
491
518
|
}
|
|
492
|
-
function
|
|
519
|
+
function groupJobsByService(state) {
|
|
493
520
|
const groups = /* @__PURE__ */ new Map();
|
|
494
521
|
for (const [, job] of state.jobs) {
|
|
495
|
-
if (!groups.has(job.
|
|
496
|
-
groups.set(job.
|
|
522
|
+
if (!groups.has(job.serviceName)) {
|
|
523
|
+
groups.set(job.serviceName, []);
|
|
497
524
|
}
|
|
498
|
-
groups.get(job.
|
|
525
|
+
groups.get(job.serviceName).push(job);
|
|
499
526
|
}
|
|
500
527
|
return groups;
|
|
501
528
|
}
|
|
529
|
+
function getUniqueTaskNames(state) {
|
|
530
|
+
const taskNames = /* @__PURE__ */ new Set();
|
|
531
|
+
for (const [, job] of state.jobs) {
|
|
532
|
+
taskNames.add(job.taskName);
|
|
533
|
+
}
|
|
534
|
+
return Array.from(taskNames).sort();
|
|
535
|
+
}
|
|
502
536
|
|
|
503
537
|
// src/render/watch.ts
|
|
504
538
|
import logUpdate from "log-update";
|
|
@@ -591,7 +625,7 @@ function formatErrors(errors) {
|
|
|
591
625
|
// src/cli.ts
|
|
592
626
|
var cli = cac("qa");
|
|
593
627
|
cli.usage(`${green("qa")} ${cyan("<command>")} ${dim("[options]")}`);
|
|
594
|
-
cli.command("run", "Run tasks across services").option("--tasks <tasks>", 'Comma-separated list of tasks or "all"', { default: "all" }).option("--services <services>", 'Comma-separated list of services or "all"', { default: "all" }).option("--concurrency <n>", "Maximum parallel jobs", { default: cpus().length }).option("--parallel", "Alias for concurrency").option("--fail-fast", "Stop on first error", { default: false }).option("--
|
|
628
|
+
cli.command("run", "Run tasks across services").option("--tasks <tasks>", 'Comma-separated list of tasks or "all"', { default: "all" }).option("--services <services>", 'Comma-separated list of services or "all"', { default: "all" }).option("--concurrency <n>", "Maximum parallel jobs", { default: cpus().length }).option("--parallel", "Alias for concurrency").option("--fail-fast", "Stop on first error", { default: false }).option("--random", "Randomize job execution order", { default: false }).option("--no-watch", "Disable live updates").option("--format <format>", "Output format: table, minimal, json", { default: "table" }).option("--output <path>", "Save JSON output to file").option("--verbose", "Verbose output", { default: false }).option("--quiet", "Minimal output", { default: false }).option("--timeout <ms>", "Timeout per task in milliseconds").option("--no-color", "Disable colors").option("--config <path>", "Path to config file").action(async (options) => {
|
|
595
629
|
try {
|
|
596
630
|
const isTTY = process.stdout.isTTY;
|
|
597
631
|
const colorsEnabled2 = options.color !== false && isTTY;
|
|
@@ -618,7 +652,8 @@ cli.command("run", "Run tasks across services").option("--tasks <tasks>", 'Comma
|
|
|
618
652
|
verbose: options.verbose,
|
|
619
653
|
quiet: options.quiet,
|
|
620
654
|
timeout: options.timeout ? Number(options.timeout) : void 0,
|
|
621
|
-
noColor: !colorsEnabled2
|
|
655
|
+
noColor: !colorsEnabled2,
|
|
656
|
+
random: options.random
|
|
622
657
|
};
|
|
623
658
|
let jobs;
|
|
624
659
|
try {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/config/loader.ts","../src/config/schema.ts","../src/runner/generator.ts","../src/runner/runner.ts","../src/runner/executor.ts","../src/utils/log.ts","../src/render/table.ts","../src/utils/colors.ts","../src/utils/spinner.ts","../src/render/watch.ts","../src/report/aggregator.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { cac } from 'cac';\nimport { cpus } from 'node:os';\nimport { loadConfig } from './config/loader.js';\nimport { generateJobs } from './runner/generator.js';\nimport { Runner } from './runner/runner.js';\nimport { renderTable, renderMinimal, renderJson } from './render/table.js';\nimport { WatchRenderer } from './render/watch.js';\nimport { aggregateErrors, formatErrors } from './report/aggregator.js';\nimport { setColorsEnabled, green, yellow, red, cyan, dim } from './utils/colors.js';\nimport type { RunOptions, ServiceDef } from './model/types.js';\n\nconst cli = cac('qa');\n\ncli.usage(`${green('qa')} ${cyan('<command>')} ${dim('[options]')}`);\n\ncli\n .command('run', 'Run tasks across services')\n .option('--tasks <tasks>', 'Comma-separated list of tasks or \"all\"', { default: 'all' })\n .option('--services <services>', 'Comma-separated list of services or \"all\"', { default: 'all' })\n .option('--concurrency <n>', 'Maximum parallel jobs', { default: cpus().length })\n .option('--parallel', 'Alias for concurrency')\n .option('--fail-fast', 'Stop on first error', { default: false })\n .option('--watch', 'Watch mode with live updates', { default: false })\n .option('--format <format>', 'Output format: table, minimal, json', { default: 'table' })\n .option('--output <path>', 'Save JSON output to file')\n .option('--verbose', 'Verbose output', { default: false })\n .option('--quiet', 'Minimal output', { default: false })\n .option('--timeout <ms>', 'Timeout per task in milliseconds')\n .option('--no-color', 'Disable colors')\n .option('--config <path>', 'Path to config file')\n .action(async (options) => {\n try {\n const isTTY = process.stdout.isTTY;\n const colorsEnabled = options.color !== false && isTTY;\n setColorsEnabled(colorsEnabled);\n\n let config;\n try {\n config = await loadConfig(options.config);\n } catch (error) {\n console.error(red('✗ Config Error:'), error instanceof Error ? error.message : String(error));\n console.log(dim('\\nMake sure you have a qa.config.js file in your project root.'));\n console.log(dim('Example: https://github.com/your-repo/verify-grid#configuration'));\n process.exit(2);\n }\n\n const tasks = options.tasks === 'all' \n ? ['all'] \n : options.tasks.split(',').map((t: string) => t.trim());\n \n const services = options.services === 'all'\n ? ['all']\n : options.services.split(',').map((s: string) => s.trim());\n\n const runOptions: RunOptions = {\n tasks,\n services,\n concurrency: Number(options.concurrency),\n failFast: options.failFast,\n watch: (options.watch !== false && isTTY && options.format === 'table'),\n format: options.format,\n output: options.output,\n verbose: options.verbose,\n quiet: options.quiet,\n timeout: options.timeout ? Number(options.timeout) : undefined,\n noColor: !colorsEnabled,\n };\n\n let jobs;\n try {\n jobs = generateJobs(config, tasks, services);\n } catch (error) {\n console.error(red('✗ Job Generation Error:'), error instanceof Error ? error.message : String(error));\n process.exit(2);\n }\n\n if (jobs.length === 0) {\n console.error(yellow('⚠ No jobs to run'));\n console.log(dim('\\nCheck your --tasks and --services filters.'));\n console.log(dim(`Available tasks: ${Object.keys(config.tasks).join(', ')}`));\n console.log(dim(`Available services: ${config.services.map((s: ServiceDef) => s.name).join(', ')}`));\n process.exit(2);\n }\n\n const runner = new Runner(runOptions);\n let watchRenderer: WatchRenderer | null = null;\n\n if (runOptions.watch) {\n watchRenderer = new WatchRenderer();\n watchRenderer.start(runner.getState());\n\n runner.on('jobStart', () => {\n watchRenderer?.update(runner.getState());\n });\n\n runner.on('jobComplete', () => {\n watchRenderer?.update(runner.getState());\n });\n\n runner.on('jobError', () => {\n watchRenderer?.update(runner.getState());\n });\n }\n\n const state = await runner.run(jobs);\n\n if (watchRenderer) {\n watchRenderer.stop();\n }\n\n if (!runOptions.quiet) {\n let output: string;\n \n if (runOptions.format === 'json') {\n output = renderJson(state);\n } else if (runOptions.format === 'minimal') {\n output = renderMinimal(state);\n } else {\n output = renderTable(state);\n }\n\n console.log(output);\n\n const errors = aggregateErrors(state);\n if (errors.length > 0 && runOptions.format !== 'json') {\n console.log(formatErrors(errors));\n }\n }\n\n if (options.output) {\n const { writeFileSync } = await import('node:fs');\n writeFileSync(options.output, renderJson(state), 'utf-8');\n }\n\n const hasErrors = Array.from(state.jobs.values()).some(j => j.status === 'error');\n process.exit(hasErrors ? 1 : 0);\n\n } catch (error) {\n console.error(red('✗ Unexpected Error:'), error instanceof Error ? error.message : String(error));\n if (error instanceof Error && error.stack) {\n console.error(dim(error.stack));\n }\n process.exit(2);\n }\n });\n\ncli.help();\ncli.version('0.1.0');\n\nif (process.argv.length === 2) {\n console.log(green('🚀 QA Task Runner\\n'));\n console.log('Quick start:');\n console.log(` ${cyan('qa run')} ${dim('# Run all tasks on all services')}`);\n console.log(` ${cyan('qa run --tasks lint')} ${dim('# Run specific task')}`);\n console.log(` ${cyan('qa run --services api,web')} ${dim('# Run on specific services')}`);\n console.log(` ${cyan('qa run --fail-fast')} ${dim('# Stop on first error')}`);\n console.log(` ${cyan('qa run --concurrency 4')} ${dim('# Limit parallel jobs')}`);\n console.log('\\nOptions:');\n console.log(` ${cyan('--help')} ${dim('# Show all available options')}`);\n console.log(` ${cyan('--version')} ${dim('# Show version')}`);\n console.log('\\nConfiguration:');\n console.log(` Create ${yellow('qa.config.js')} in your project root.`);\n console.log(` See: ${dim('https://github.com/your-repo/verify-grid#configuration')}`);\n process.exit(0);\n}\n\ncli.parse();\n","import { pathToFileURL } from 'node:url';\nimport { existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { ConfigSchema } from './schema.js';\nimport type { Config } from '../model/types.js';\n\nconst CONFIG_FILES = ['qa.config.ts', 'qa.config.js', 'qa.config.mjs'];\n\nexport async function findConfig(startDir: string = process.cwd()): Promise<string | null> {\n let currentDir = resolve(startDir);\n const root = resolve('/');\n\n while (currentDir !== root) {\n for (const configFile of CONFIG_FILES) {\n const configPath = resolve(currentDir, configFile);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n currentDir = dirname(currentDir);\n }\n\n for (const configFile of CONFIG_FILES) {\n const configPath = resolve(root, configFile);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n\n return null;\n}\n\nexport async function loadConfig(configPath?: string): Promise<Config> {\n const path = configPath || await findConfig();\n \n if (!path) {\n throw new Error(\n 'No config file found. Create qa.config.ts or qa.config.js in your project root.'\n );\n }\n\n if (!existsSync(path)) {\n throw new Error(`Config file not found: ${path}`);\n }\n\n try {\n const fileUrl = pathToFileURL(path).href;\n const module = await import(fileUrl);\n const configData = module.default || module;\n\n const result = ConfigSchema.safeParse(configData);\n \n if (!result.success) {\n const errors = result.error.errors.map(err => \n ` - ${err.path.join('.')}: ${err.message}`\n ).join('\\n');\n throw new Error(`Invalid config:\\n${errors}`);\n }\n\n const serviceNames = new Set<string>();\n for (const service of result.data.services) {\n if (serviceNames.has(service.name)) {\n throw new Error(`Duplicate service name: ${service.name}`);\n }\n serviceNames.add(service.name);\n }\n\n const config: Config = {\n services: result.data.services,\n tasks: Object.entries(result.data.tasks).reduce((acc, [name, def]) => {\n acc[name] = { name, ...def };\n return acc;\n }, {} as Record<string, any>),\n };\n\n return config;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Failed to load config: ${String(error)}`);\n }\n}\n","import { z } from 'zod';\n\nexport const TaskDefSchema = z.object({\n name: z.string(),\n command: z.string(),\n args: z.array(z.string()).optional(),\n shell: z.boolean().optional(),\n timeout: z.number().positive().optional(),\n env: z.record(z.string()).optional(),\n});\n\nexport const ServiceDefSchema = z.object({\n name: z.string(),\n cwd: z.string(),\n tasks: z.record(z.object({\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n shell: z.boolean().optional(),\n timeout: z.number().positive().optional(),\n env: z.record(z.string()).optional(),\n })).optional(),\n env: z.record(z.string()).optional(),\n});\n\nexport const ConfigSchema = z.object({\n services: z.array(ServiceDefSchema),\n tasks: z.record(TaskDefSchema.omit({ name: true })),\n});\n\nexport type ConfigInput = z.input<typeof ConfigSchema>;\nexport type ConfigOutput = z.output<typeof ConfigSchema>;\n","import { existsSync } from 'node:fs';\nimport type { Config, Job, TaskDef } from '../model/types.js';\n\nexport function generateJobs(\n config: Config,\n taskNames: string[],\n serviceNames: string[]\n): Job[] {\n const jobs: Job[] = [];\n const resolvedServiceNames = serviceNames.includes('all') \n ? config.services.map(s => s.name)\n : serviceNames;\n\n const resolvedTaskNames = taskNames.includes('all')\n ? Object.keys(config.tasks)\n : taskNames;\n\n for (const taskName of resolvedTaskNames) {\n const globalTask = config.tasks[taskName];\n \n if (!globalTask) {\n throw new Error(`Unknown task: ${taskName}`);\n }\n\n for (const serviceName of resolvedServiceNames) {\n const service = config.services.find(s => s.name === serviceName);\n \n if (!service) {\n throw new Error(`Unknown service: ${serviceName}`);\n }\n\n if (!existsSync(service.cwd)) {\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: '',\n status: 'skipped',\n });\n continue;\n }\n\n const taskOverride = service.tasks?.[taskName];\n \n if (taskOverride && taskOverride.command === undefined && !globalTask.command) {\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: '',\n status: 'skipped',\n });\n continue;\n }\n\n const mergedTask: TaskDef = {\n name: taskName,\n command: taskOverride?.command ?? globalTask.command,\n args: taskOverride?.args ?? globalTask.args,\n shell: taskOverride?.shell ?? globalTask.shell ?? true,\n timeout: taskOverride?.timeout ?? globalTask.timeout,\n env: {\n ...globalTask.env,\n ...service.env,\n ...taskOverride?.env,\n },\n };\n\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: mergedTask.command,\n args: mergedTask.args,\n shell: mergedTask.shell,\n timeout: mergedTask.timeout,\n env: mergedTask.env,\n status: 'queued',\n });\n }\n }\n\n return jobs;\n}\n","import PQueue from 'p-queue';\nimport { EventEmitter } from 'node:events';\nimport type { Job, JobResult, MatrixState, RunOptions } from '../model/types.js';\nimport { executeJob } from './executor.js';\n\nexport interface RunnerEvents {\n jobStart: (job: Job) => void;\n jobComplete: (result: JobResult) => void;\n jobError: (result: JobResult) => void;\n runComplete: (state: MatrixState) => void;\n runCanceled: () => void;\n}\n\nexport class Runner extends EventEmitter {\n private queue: PQueue;\n private state: MatrixState;\n private shouldStop = false;\n private options: RunOptions;\n\n constructor(options: RunOptions) {\n super();\n this.options = options;\n this.queue = new PQueue({ concurrency: options.concurrency });\n this.state = {\n jobs: new Map(),\n results: new Map(),\n errors: [],\n startedAt: Date.now(),\n };\n\n process.on('SIGINT', () => this.handleInterrupt());\n process.on('SIGTERM', () => this.handleInterrupt());\n }\n\n private handleInterrupt(): void {\n if (this.shouldStop) {\n process.exit(130);\n }\n \n this.shouldStop = true;\n this.queue.clear();\n \n for (const [id, job] of this.state.jobs) {\n if (job.status === 'queued') {\n job.status = 'canceled';\n this.state.jobs.set(id, job);\n }\n }\n \n this.emit('runCanceled');\n }\n\n async run(jobs: Job[]): Promise<MatrixState> {\n for (const job of jobs) {\n this.state.jobs.set(job.id, job);\n }\n\n const executableJobs = jobs.filter(j => j.status === 'queued');\n \n for (const job of executableJobs) {\n if (this.shouldStop) {\n job.status = 'canceled';\n this.state.jobs.set(job.id, job);\n continue;\n }\n\n this.queue.add(async () => {\n if (this.shouldStop) {\n job.status = 'canceled';\n this.state.jobs.set(job.id, job);\n return;\n }\n\n job.status = 'running';\n job.startedAt = Date.now();\n this.state.jobs.set(job.id, job);\n this.emit('jobStart', job);\n\n const result = await executeJob(job);\n \n job.status = result.exitCode === 0 ? 'done' : 'error';\n job.endedAt = Date.now();\n this.state.jobs.set(job.id, job);\n this.state.results.set(job.id, result);\n\n if (result.exitCode !== 0) {\n this.emit('jobError', result);\n \n if (this.options.failFast) {\n this.shouldStop = true;\n this.queue.clear();\n \n for (const [id, j] of this.state.jobs) {\n if (j.status === 'queued') {\n j.status = 'canceled';\n this.state.jobs.set(id, j);\n }\n }\n }\n } else {\n this.emit('jobComplete', result);\n }\n });\n }\n\n await this.queue.onIdle();\n \n this.state.endedAt = Date.now();\n this.emit('runComplete', this.state);\n \n return this.state;\n }\n\n getState(): MatrixState {\n return this.state;\n }\n}\n","import { execa, type ExecaError } from 'execa';\nimport type { Job, JobResult } from '../model/types.js';\nimport { LogBuffer } from '../utils/log.js';\n\nexport async function executeJob(job: Job): Promise<JobResult> {\n const startTime = Date.now();\n const stdoutBuffer = new LogBuffer();\n const stderrBuffer = new LogBuffer();\n\n try {\n const args = job.args || [];\n const options: any = {\n cwd: job.cwd,\n env: { ...process.env, ...job.env },\n shell: job.shell,\n timeout: job.timeout,\n reject: false,\n all: true,\n };\n\n const result = await execa(job.command, args, options);\n\n stdoutBuffer.append(result.stdout || '');\n stderrBuffer.append(result.stderr || '');\n\n const durationMs = Date.now() - startTime;\n\n if (result.exitCode === 0) {\n return {\n job,\n exitCode: 0,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n };\n }\n\n return {\n job,\n exitCode: result.exitCode,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n errorType: result.signal ? 'signal' : 'exit',\n signal: result.signal || undefined,\n };\n } catch (error) {\n const durationMs = Date.now() - startTime;\n const execaError = error as ExecaError;\n\n if (execaError.stdout) stdoutBuffer.append(execaError.stdout);\n if (execaError.stderr) stderrBuffer.append(execaError.stderr);\n\n let errorType: 'exit' | 'timeout' | 'spawn' | 'signal' = 'exit';\n \n if (execaError.timedOut) {\n errorType = 'timeout';\n } else if (execaError.signal) {\n errorType = 'signal';\n } else if (execaError.exitCode === 127 || (error as any).code === 'ENOENT') {\n errorType = 'spawn';\n }\n\n return {\n job,\n exitCode: execaError.exitCode,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n errorType,\n signal: execaError.signal || undefined,\n };\n }\n}\n","const MAX_LOG_LINES = 200;\n\nexport function truncateLog(log: string, maxLines: number = MAX_LOG_LINES): string {\n const lines = log.split('\\n');\n if (lines.length <= maxLines) {\n return log;\n }\n return lines.slice(-maxLines).join('\\n');\n}\n\nexport function extractErrorSnippet(stderr: string, stdout: string, maxLines: number = 10): string {\n const errorLog = stderr || stdout;\n const lines = errorLog.split('\\n').filter(line => line.trim());\n \n if (lines.length === 0) {\n return 'No error output';\n }\n \n const relevantLines = lines.slice(-maxLines);\n return relevantLines.join('\\n');\n}\n\nexport class LogBuffer {\n private lines: string[] = [];\n private maxLines: number;\n\n constructor(maxLines: number = MAX_LOG_LINES) {\n this.maxLines = maxLines;\n }\n\n append(data: string): void {\n const newLines = data.split('\\n');\n this.lines.push(...newLines);\n \n if (this.lines.length > this.maxLines) {\n this.lines = this.lines.slice(-this.maxLines);\n }\n }\n\n getContent(): string {\n return this.lines.join('\\n');\n }\n\n clear(): void {\n this.lines = [];\n }\n}\n","import Table from 'cli-table3';\nimport type { MatrixState, Job } from '../model/types.js';\nimport { getStatusColor } from '../utils/colors.js';\nimport { Spinner } from '../utils/spinner.js';\n\nconst spinner = new Spinner();\n\nexport function renderTable(state: MatrixState): string {\n const table = new Table({\n head: ['Task', 'Service', 'Status'],\n style: {\n head: [],\n border: [],\n },\n });\n\n const taskGroups = groupJobsByTask(state);\n\n for (const [taskName, jobs] of taskGroups) {\n let isFirstRow = true;\n \n for (const job of jobs) {\n const statusColor = getStatusColor(job.status);\n let statusText: string = job.status;\n \n if (job.status === 'running') {\n statusText = `${spinner.next()} ${job.status}`;\n } else if (job.status === 'queued') {\n statusText = `⏳ ${job.status}`;\n }\n \n const coloredStatus = statusColor(statusText);\n \n table.push([\n isFirstRow ? taskName : '',\n job.serviceName,\n coloredStatus,\n ]);\n \n isFirstRow = false;\n }\n }\n\n return table.toString();\n}\n\nexport function renderMinimal(state: MatrixState): string {\n const lines: string[] = [];\n \n for (const [, job] of state.jobs) {\n const statusColor = getStatusColor(job.status);\n const statusText = statusColor(job.status);\n lines.push(`${job.taskName} ${job.serviceName} ${statusText}`);\n }\n \n return lines.join('\\n');\n}\n\nexport function renderJson(state: MatrixState): string {\n const matrix: Record<string, Record<string, any>> = {};\n \n for (const [, job] of state.jobs) {\n if (!matrix[job.taskName]) {\n matrix[job.taskName] = {};\n }\n \n const result = state.results.get(job.id);\n const taskMatrix = matrix[job.taskName];\n \n if (taskMatrix) {\n taskMatrix[job.serviceName] = {\n status: job.status,\n exitCode: result?.exitCode,\n durationMs: result?.durationMs,\n };\n }\n }\n\n const errors = Array.from(state.results.values())\n .filter(r => r.job.status === 'error')\n .map(r => ({\n taskName: r.job.taskName,\n serviceName: r.job.serviceName,\n exitCode: r.exitCode,\n durationMs: r.durationMs,\n errorType: r.errorType,\n }));\n\n const output = {\n startedAt: state.startedAt,\n endedAt: state.endedAt,\n durationMs: state.endedAt ? state.endedAt - state.startedAt : undefined,\n matrix,\n errors,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\nfunction groupJobsByTask(state: MatrixState): Map<string, Job[]> {\n const groups = new Map<string, Job[]>();\n \n for (const [, job] of state.jobs) {\n if (!groups.has(job.taskName)) {\n groups.set(job.taskName, []);\n }\n groups.get(job.taskName)!.push(job);\n }\n \n return groups;\n}\n","import kleur from 'kleur';\nimport type { JobStatus } from '../model/types.js';\n\nlet colorsEnabled = true;\n\nexport function setColorsEnabled(enabled: boolean): void {\n colorsEnabled = enabled;\n kleur.enabled = enabled;\n}\n\nexport function getStatusColor(status: JobStatus): (text: string) => string {\n if (!colorsEnabled) {\n return (text: string) => text;\n }\n\n switch (status) {\n case 'done':\n return kleur.green;\n case 'error':\n return kleur.red;\n case 'running':\n return kleur.blue;\n case 'queued':\n return kleur.yellow;\n case 'skipped':\n case 'canceled':\n return kleur.gray;\n default:\n return (text: string) => text;\n }\n}\n\nexport const colors = {\n error: kleur.red,\n success: kleur.green,\n warning: kleur.yellow,\n info: kleur.blue,\n dim: kleur.gray,\n bold: kleur.bold,\n};\n\nexport const green = kleur.green;\nexport const yellow = kleur.yellow;\nexport const red = kleur.red;\nexport const cyan = kleur.cyan;\nexport const dim = kleur.gray;\n","const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nexport class Spinner {\n private frame = 0;\n\n next(): string {\n const char = SPINNER_FRAMES[this.frame];\n this.frame = (this.frame + 1) % SPINNER_FRAMES.length;\n return char || '⠋';\n }\n\n reset(): void {\n this.frame = 0;\n }\n}\n","import logUpdate from 'log-update';\nimport type { MatrixState } from '../model/types.js';\nimport { renderTable } from './table.js';\n\nexport class WatchRenderer {\n private updateInterval: NodeJS.Timeout | null = null;\n private state: MatrixState | null = null;\n\n start(state: MatrixState): void {\n this.state = state;\n this.render();\n \n this.updateInterval = setInterval(() => {\n this.render();\n }, 80);\n }\n\n update(state: MatrixState): void {\n this.state = state;\n }\n\n stop(): void {\n if (this.updateInterval) {\n clearInterval(this.updateInterval);\n this.updateInterval = null;\n }\n logUpdate.done();\n }\n\n private render(): void {\n if (!this.state) return;\n \n const output = renderTable(this.state);\n logUpdate(output);\n }\n}\n","import type { MatrixState, ErrorEntry, JobResult } from '../model/types.js';\nimport { extractErrorSnippet } from '../utils/log.js';\n\nexport function aggregateErrors(state: MatrixState): ErrorEntry[] {\n const errors: ErrorEntry[] = [];\n\n for (const [, result] of state.results) {\n if (result.job.status === 'error') {\n const errorMessage = getErrorMessage(result);\n const logSnippet = extractErrorSnippet(result.stderr, result.stdout);\n\n errors.push({\n taskName: result.job.taskName,\n serviceName: result.job.serviceName,\n exitCode: result.exitCode,\n durationMs: result.durationMs,\n message: errorMessage,\n logSnippet,\n errorType: result.errorType,\n });\n }\n }\n\n errors.sort((a, b) => {\n if (a.taskName !== b.taskName) {\n return a.taskName.localeCompare(b.taskName);\n }\n return a.serviceName.localeCompare(b.serviceName);\n });\n\n return errors;\n}\n\nfunction getErrorMessage(result: JobResult): string {\n if (result.errorType === 'timeout') {\n return `Timeout after ${result.durationMs}ms`;\n }\n \n if (result.errorType === 'spawn') {\n return `Command not found: ${result.job.command}`;\n }\n \n if (result.errorType === 'signal') {\n return `Killed by signal: ${result.signal}`;\n }\n \n return `Exit code ${result.exitCode}`;\n}\n\nexport function formatErrors(errors: ErrorEntry[]): string {\n if (errors.length === 0) {\n return '';\n }\n\n const lines: string[] = ['', 'Errors:'];\n\n for (const error of errors) {\n lines.push(`- ${error.serviceName} has error in ${error.taskName} test:`);\n \n const snippet = error.logSnippet.trim();\n if (snippet) {\n const snippetLines = snippet.split('\\n').slice(0, 10);\n for (const line of snippetLines) {\n lines.push(` ${line}`);\n }\n } else {\n lines.push(` ${error.message}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["existsSync","colorsEnabled"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/config/loader.ts","../src/config/schema.ts","../src/runner/generator.ts","../src/runner/runner.ts","../src/runner/executor.ts","../src/utils/log.ts","../src/render/table.ts","../src/utils/colors.ts","../src/utils/spinner.ts","../src/render/watch.ts","../src/report/aggregator.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { cac } from 'cac';\nimport { cpus } from 'node:os';\nimport { loadConfig } from './config/loader.js';\nimport { generateJobs } from './runner/generator.js';\nimport { Runner } from './runner/runner.js';\nimport { renderTable, renderMinimal, renderJson } from './render/table.js';\nimport { WatchRenderer } from './render/watch.js';\nimport { aggregateErrors, formatErrors } from './report/aggregator.js';\nimport { setColorsEnabled, green, yellow, red, cyan, dim } from './utils/colors.js';\nimport type { RunOptions, ServiceDef } from './model/types.js';\n\nconst cli = cac('qa');\n\ncli.usage(`${green('qa')} ${cyan('<command>')} ${dim('[options]')}`);\n\ncli\n .command('run', 'Run tasks across services')\n .option('--tasks <tasks>', 'Comma-separated list of tasks or \"all\"', { default: 'all' })\n .option('--services <services>', 'Comma-separated list of services or \"all\"', { default: 'all' })\n .option('--concurrency <n>', 'Maximum parallel jobs', { default: cpus().length })\n .option('--parallel', 'Alias for concurrency')\n .option('--fail-fast', 'Stop on first error', { default: false })\n .option('--random', 'Randomize job execution order', { default: false })\n .option('--no-watch', 'Disable live updates')\n .option('--format <format>', 'Output format: table, minimal, json', { default: 'table' })\n .option('--output <path>', 'Save JSON output to file')\n .option('--verbose', 'Verbose output', { default: false })\n .option('--quiet', 'Minimal output', { default: false })\n .option('--timeout <ms>', 'Timeout per task in milliseconds')\n .option('--no-color', 'Disable colors')\n .option('--config <path>', 'Path to config file')\n .action(async (options) => {\n try {\n const isTTY = process.stdout.isTTY;\n const colorsEnabled = options.color !== false && isTTY;\n setColorsEnabled(colorsEnabled);\n\n let config;\n try {\n config = await loadConfig(options.config);\n } catch (error) {\n console.error(red('✗ Config Error:'), error instanceof Error ? error.message : String(error));\n console.log(dim('\\nMake sure you have a qa.config.js file in your project root.'));\n console.log(dim('Example: https://github.com/your-repo/verify-grid#configuration'));\n process.exit(2);\n }\n\n const tasks = options.tasks === 'all' \n ? ['all'] \n : options.tasks.split(',').map((t: string) => t.trim());\n \n const services = options.services === 'all'\n ? ['all']\n : options.services.split(',').map((s: string) => s.trim());\n\n const runOptions: RunOptions = {\n tasks,\n services,\n concurrency: Number(options.concurrency),\n failFast: options.failFast,\n watch: (options.watch !== false && isTTY && options.format === 'table'),\n format: options.format,\n output: options.output,\n verbose: options.verbose,\n quiet: options.quiet,\n timeout: options.timeout ? Number(options.timeout) : undefined,\n noColor: !colorsEnabled,\n random: options.random,\n };\n\n let jobs;\n try {\n jobs = generateJobs(config, tasks, services);\n } catch (error) {\n console.error(red('✗ Job Generation Error:'), error instanceof Error ? error.message : String(error));\n process.exit(2);\n }\n\n if (jobs.length === 0) {\n console.error(yellow('⚠ No jobs to run'));\n console.log(dim('\\nCheck your --tasks and --services filters.'));\n console.log(dim(`Available tasks: ${Object.keys(config.tasks).join(', ')}`));\n console.log(dim(`Available services: ${config.services.map((s: ServiceDef) => s.name).join(', ')}`));\n process.exit(2);\n }\n\n const runner = new Runner(runOptions);\n let watchRenderer: WatchRenderer | null = null;\n\n if (runOptions.watch) {\n watchRenderer = new WatchRenderer();\n watchRenderer.start(runner.getState());\n\n runner.on('jobStart', () => {\n watchRenderer?.update(runner.getState());\n });\n\n runner.on('jobComplete', () => {\n watchRenderer?.update(runner.getState());\n });\n\n runner.on('jobError', () => {\n watchRenderer?.update(runner.getState());\n });\n }\n\n const state = await runner.run(jobs);\n\n if (watchRenderer) {\n watchRenderer.stop();\n }\n\n if (!runOptions.quiet) {\n let output: string;\n \n if (runOptions.format === 'json') {\n output = renderJson(state);\n } else if (runOptions.format === 'minimal') {\n output = renderMinimal(state);\n } else {\n output = renderTable(state);\n }\n\n console.log(output);\n\n const errors = aggregateErrors(state);\n if (errors.length > 0 && runOptions.format !== 'json') {\n console.log(formatErrors(errors));\n }\n }\n\n if (options.output) {\n const { writeFileSync } = await import('node:fs');\n writeFileSync(options.output, renderJson(state), 'utf-8');\n }\n\n const hasErrors = Array.from(state.jobs.values()).some(j => j.status === 'error');\n process.exit(hasErrors ? 1 : 0);\n\n } catch (error) {\n console.error(red('✗ Unexpected Error:'), error instanceof Error ? error.message : String(error));\n if (error instanceof Error && error.stack) {\n console.error(dim(error.stack));\n }\n process.exit(2);\n }\n });\n\ncli.help();\ncli.version('0.1.0');\n\nif (process.argv.length === 2) {\n console.log(green('🚀 QA Task Runner\\n'));\n console.log('Quick start:');\n console.log(` ${cyan('qa run')} ${dim('# Run all tasks on all services')}`);\n console.log(` ${cyan('qa run --tasks lint')} ${dim('# Run specific task')}`);\n console.log(` ${cyan('qa run --services api,web')} ${dim('# Run on specific services')}`);\n console.log(` ${cyan('qa run --fail-fast')} ${dim('# Stop on first error')}`);\n console.log(` ${cyan('qa run --concurrency 4')} ${dim('# Limit parallel jobs')}`);\n console.log('\\nOptions:');\n console.log(` ${cyan('--help')} ${dim('# Show all available options')}`);\n console.log(` ${cyan('--version')} ${dim('# Show version')}`);\n console.log('\\nConfiguration:');\n console.log(` Create ${yellow('qa.config.js')} in your project root.`);\n console.log(` See: ${dim('https://github.com/your-repo/verify-grid#configuration')}`);\n process.exit(0);\n}\n\ncli.parse();\n","import { pathToFileURL } from 'node:url';\nimport { existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { ConfigSchema } from './schema.js';\nimport type { Config } from '../model/types.js';\n\nconst CONFIG_FILES = ['qa.config.ts', 'qa.config.js', 'qa.config.mjs'];\n\nexport async function findConfig(startDir: string = process.cwd()): Promise<string | null> {\n let currentDir = resolve(startDir);\n const root = resolve('/');\n\n while (currentDir !== root) {\n for (const configFile of CONFIG_FILES) {\n const configPath = resolve(currentDir, configFile);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n currentDir = dirname(currentDir);\n }\n\n for (const configFile of CONFIG_FILES) {\n const configPath = resolve(root, configFile);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n\n return null;\n}\n\nexport async function loadConfig(configPath?: string): Promise<Config> {\n const path = configPath || await findConfig();\n \n if (!path) {\n throw new Error(\n 'No config file found. Create qa.config.ts or qa.config.js in your project root.'\n );\n }\n\n if (!existsSync(path)) {\n throw new Error(`Config file not found: ${path}`);\n }\n\n try {\n const fileUrl = pathToFileURL(path).href;\n const module = await import(fileUrl);\n const configData = module.default || module;\n\n const result = ConfigSchema.safeParse(configData);\n \n if (!result.success) {\n const errors = result.error.errors.map(err => \n ` - ${err.path.join('.')}: ${err.message}`\n ).join('\\n');\n throw new Error(`Invalid config:\\n${errors}`);\n }\n\n const serviceNames = new Set<string>();\n for (const service of result.data.services) {\n if (serviceNames.has(service.name)) {\n throw new Error(`Duplicate service name: ${service.name}`);\n }\n serviceNames.add(service.name);\n }\n\n const config: Config = {\n services: result.data.services,\n tasks: Object.entries(result.data.tasks).reduce((acc, [name, def]) => {\n acc[name] = { name, ...def };\n return acc;\n }, {} as Record<string, any>),\n };\n\n return config;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Failed to load config: ${String(error)}`);\n }\n}\n","import { z } from 'zod';\n\nexport const TaskDefSchema = z.object({\n name: z.string(),\n command: z.string(),\n args: z.array(z.string()).optional(),\n shell: z.boolean().optional(),\n timeout: z.number().positive().optional(),\n priority: z.number().optional(),\n env: z.record(z.string()).optional(),\n});\n\nexport const ServiceDefSchema = z.object({\n name: z.string(),\n cwd: z.string(),\n tasks: z.record(z.object({\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n shell: z.boolean().optional(),\n timeout: z.number().positive().optional(),\n priority: z.number().optional(),\n env: z.record(z.string()).optional(),\n })).optional(),\n env: z.record(z.string()).optional(),\n});\n\nexport const ConfigSchema = z.object({\n services: z.array(ServiceDefSchema),\n tasks: z.record(TaskDefSchema.omit({ name: true })),\n});\n\nexport type ConfigInput = z.input<typeof ConfigSchema>;\nexport type ConfigOutput = z.output<typeof ConfigSchema>;\n","import { existsSync } from 'node:fs';\nimport type { Config, Job, TaskDef } from '../model/types.js';\n\nexport function generateJobs(\n config: Config,\n taskNames: string[],\n serviceNames: string[]\n): Job[] {\n const jobs: Job[] = [];\n const resolvedServiceNames = serviceNames.includes('all') \n ? config.services.map(s => s.name)\n : serviceNames;\n\n const resolvedTaskNames = taskNames.includes('all')\n ? Object.keys(config.tasks)\n : taskNames;\n\n for (const taskName of resolvedTaskNames) {\n const globalTask = config.tasks[taskName];\n \n if (!globalTask) {\n throw new Error(`Unknown task: ${taskName}`);\n }\n\n for (const serviceName of resolvedServiceNames) {\n const service = config.services.find(s => s.name === serviceName);\n \n if (!service) {\n throw new Error(`Unknown service: ${serviceName}`);\n }\n\n if (!existsSync(service.cwd)) {\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: '',\n status: 'skipped',\n });\n continue;\n }\n\n const taskOverride = service.tasks?.[taskName];\n \n if (taskOverride && taskOverride.command === undefined && !globalTask.command) {\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: '',\n status: 'skipped',\n });\n continue;\n }\n\n const mergedTask: TaskDef = {\n name: taskName,\n command: taskOverride?.command ?? globalTask.command,\n args: taskOverride?.args ?? globalTask.args,\n shell: taskOverride?.shell ?? globalTask.shell ?? true,\n timeout: taskOverride?.timeout ?? globalTask.timeout,\n priority: taskOverride?.priority ?? globalTask.priority,\n env: {\n ...globalTask.env,\n ...service.env,\n ...taskOverride?.env,\n },\n };\n\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: mergedTask.command,\n args: mergedTask.args,\n shell: mergedTask.shell,\n timeout: mergedTask.timeout,\n priority: mergedTask.priority,\n env: mergedTask.env,\n status: 'queued',\n });\n }\n }\n\n return jobs;\n}\n","import PQueue from 'p-queue';\nimport { EventEmitter } from 'node:events';\nimport type { Job, JobResult, MatrixState, RunOptions } from '../model/types.js';\nimport { executeJob } from './executor.js';\n\nexport interface RunnerEvents {\n jobStart: (job: Job) => void;\n jobComplete: (result: JobResult) => void;\n jobError: (result: JobResult) => void;\n runComplete: (state: MatrixState) => void;\n runCanceled: () => void;\n}\n\nexport class Runner extends EventEmitter {\n private queue: PQueue;\n private state: MatrixState;\n private shouldStop = false;\n private options: RunOptions;\n\n constructor(options: RunOptions) {\n super();\n this.options = options;\n this.queue = new PQueue({ concurrency: options.concurrency });\n this.state = {\n jobs: new Map(),\n results: new Map(),\n errors: [],\n startedAt: Date.now(),\n };\n\n process.on('SIGINT', () => this.handleInterrupt());\n process.on('SIGTERM', () => this.handleInterrupt());\n }\n\n private handleInterrupt(): void {\n if (this.shouldStop) {\n process.exit(130);\n }\n \n this.shouldStop = true;\n this.queue.clear();\n \n for (const [id, job] of this.state.jobs) {\n if (job.status === 'queued') {\n job.status = 'canceled';\n this.state.jobs.set(id, job);\n }\n }\n \n this.emit('runCanceled');\n }\n\n async run(jobs: Job[]): Promise<MatrixState> {\n for (const job of jobs) {\n this.state.jobs.set(job.id, job);\n }\n\n let executableJobs = jobs.filter(j => j.status === 'queued');\n \n if (this.options.random) {\n executableJobs = this.shuffleJobs(executableJobs);\n } else {\n executableJobs = this.sortJobsByPriority(executableJobs);\n }\n \n for (const job of executableJobs) {\n if (this.shouldStop) {\n job.status = 'canceled';\n this.state.jobs.set(job.id, job);\n continue;\n }\n\n this.queue.add(async () => {\n if (this.shouldStop) {\n job.status = 'canceled';\n this.state.jobs.set(job.id, job);\n return;\n }\n\n job.status = 'running';\n job.startedAt = Date.now();\n this.state.jobs.set(job.id, job);\n this.emit('jobStart', job);\n\n const result = await executeJob(job);\n \n job.status = result.exitCode === 0 ? 'done' : 'error';\n job.endedAt = Date.now();\n this.state.jobs.set(job.id, job);\n this.state.results.set(job.id, result);\n\n if (result.exitCode !== 0) {\n this.emit('jobError', result);\n \n if (this.options.failFast) {\n this.shouldStop = true;\n this.queue.clear();\n \n for (const [id, j] of this.state.jobs) {\n if (j.status === 'queued') {\n j.status = 'canceled';\n this.state.jobs.set(id, j);\n }\n }\n }\n } else {\n this.emit('jobComplete', result);\n }\n });\n }\n\n await this.queue.onIdle();\n \n this.state.endedAt = Date.now();\n this.emit('runComplete', this.state);\n \n return this.state;\n }\n\n getState(): MatrixState {\n return this.state;\n }\n\n private sortJobsByPriority(jobs: Job[]): Job[] {\n return jobs.slice().sort((a, b) => {\n const priorityA = a.priority ?? 0;\n const priorityB = b.priority ?? 0;\n return priorityB - priorityA;\n });\n }\n\n private shuffleJobs(jobs: Job[]): Job[] {\n const shuffled = jobs.slice();\n for (let i = shuffled.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const temp = shuffled[i];\n shuffled[i] = shuffled[j]!;\n shuffled[j] = temp!;\n }\n return shuffled;\n }\n}\n","import { execa, type ExecaError } from 'execa';\nimport type { Job, JobResult } from '../model/types.js';\nimport { LogBuffer } from '../utils/log.js';\n\nexport async function executeJob(job: Job): Promise<JobResult> {\n const startTime = Date.now();\n const stdoutBuffer = new LogBuffer();\n const stderrBuffer = new LogBuffer();\n\n try {\n const args = job.args || [];\n const options: any = {\n cwd: job.cwd,\n env: { ...process.env, ...job.env },\n shell: job.shell,\n timeout: job.timeout,\n reject: false,\n all: true,\n };\n\n const result = await execa(job.command, args, options);\n\n stdoutBuffer.append(result.stdout || '');\n stderrBuffer.append(result.stderr || '');\n\n const durationMs = Date.now() - startTime;\n\n if (result.exitCode === 0) {\n return {\n job,\n exitCode: 0,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n };\n }\n\n return {\n job,\n exitCode: result.exitCode,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n errorType: result.signal ? 'signal' : 'exit',\n signal: result.signal || undefined,\n };\n } catch (error) {\n const durationMs = Date.now() - startTime;\n const execaError = error as ExecaError;\n\n if (execaError.stdout) stdoutBuffer.append(execaError.stdout);\n if (execaError.stderr) stderrBuffer.append(execaError.stderr);\n\n let errorType: 'exit' | 'timeout' | 'spawn' | 'signal' = 'exit';\n \n if (execaError.timedOut) {\n errorType = 'timeout';\n } else if (execaError.signal) {\n errorType = 'signal';\n } else if (execaError.exitCode === 127 || (error as any).code === 'ENOENT') {\n errorType = 'spawn';\n }\n\n return {\n job,\n exitCode: execaError.exitCode,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n errorType,\n signal: execaError.signal || undefined,\n };\n }\n}\n","const MAX_LOG_LINES = 200;\n\nexport function truncateLog(log: string, maxLines: number = MAX_LOG_LINES): string {\n const lines = log.split('\\n');\n if (lines.length <= maxLines) {\n return log;\n }\n return lines.slice(-maxLines).join('\\n');\n}\n\nexport function extractErrorSnippet(stderr: string, stdout: string, maxLines: number = 10): string {\n const errorLog = stderr || stdout;\n const lines = errorLog.split('\\n').filter(line => line.trim());\n \n if (lines.length === 0) {\n return 'No error output';\n }\n \n const relevantLines = lines.slice(-maxLines);\n return relevantLines.join('\\n');\n}\n\nexport class LogBuffer {\n private lines: string[] = [];\n private maxLines: number;\n\n constructor(maxLines: number = MAX_LOG_LINES) {\n this.maxLines = maxLines;\n }\n\n append(data: string): void {\n const newLines = data.split('\\n');\n this.lines.push(...newLines);\n \n if (this.lines.length > this.maxLines) {\n this.lines = this.lines.slice(-this.maxLines);\n }\n }\n\n getContent(): string {\n return this.lines.join('\\n');\n }\n\n clear(): void {\n this.lines = [];\n }\n}\n","import Table from 'cli-table3';\nimport type { MatrixState, Job } from '../model/types.js';\nimport { getStatusColor } from '../utils/colors.js';\nimport { Spinner } from '../utils/spinner.js';\n\nconst spinner = new Spinner();\n\nexport function renderTable(state: MatrixState): string {\n const taskNames = getUniqueTaskNames(state);\n const serviceGroups = groupJobsByService(state);\n\n const table = new Table({\n head: ['Service', ...taskNames],\n style: {\n head: [],\n border: [],\n },\n });\n\n for (const [serviceName, jobs] of serviceGroups) {\n const row: string[] = [serviceName];\n \n for (const taskName of taskNames) {\n const job = jobs.find(j => j.taskName === taskName);\n \n if (job) {\n const statusColor = getStatusColor(job.status);\n let statusText: string = job.status;\n \n if (job.status === 'running') {\n statusText = `${spinner.next()} ${job.status}`;\n } else if (job.status === 'queued') {\n statusText = `⏳ ${job.status}`;\n }\n \n row.push(statusColor(statusText));\n } else {\n row.push('-');\n }\n }\n \n table.push(row);\n }\n\n return table.toString();\n}\n\nexport function renderMinimal(state: MatrixState): string {\n const lines: string[] = [];\n \n for (const [, job] of state.jobs) {\n const statusColor = getStatusColor(job.status);\n const statusText = statusColor(job.status);\n lines.push(`${job.taskName} ${job.serviceName} ${statusText}`);\n }\n \n return lines.join('\\n');\n}\n\nexport function renderJson(state: MatrixState): string {\n const matrix: Record<string, Record<string, any>> = {};\n \n for (const [, job] of state.jobs) {\n if (!matrix[job.taskName]) {\n matrix[job.taskName] = {};\n }\n \n const result = state.results.get(job.id);\n const taskMatrix = matrix[job.taskName];\n \n if (taskMatrix) {\n taskMatrix[job.serviceName] = {\n status: job.status,\n exitCode: result?.exitCode,\n durationMs: result?.durationMs,\n };\n }\n }\n\n const errors = Array.from(state.results.values())\n .filter(r => r.job.status === 'error')\n .map(r => ({\n taskName: r.job.taskName,\n serviceName: r.job.serviceName,\n exitCode: r.exitCode,\n durationMs: r.durationMs,\n errorType: r.errorType,\n }));\n\n const output = {\n startedAt: state.startedAt,\n endedAt: state.endedAt,\n durationMs: state.endedAt ? state.endedAt - state.startedAt : undefined,\n matrix,\n errors,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\nfunction groupJobsByService(state: MatrixState): Map<string, Job[]> {\n const groups = new Map<string, Job[]>();\n \n for (const [, job] of state.jobs) {\n if (!groups.has(job.serviceName)) {\n groups.set(job.serviceName, []);\n }\n groups.get(job.serviceName)!.push(job);\n }\n \n return groups;\n}\n\nfunction getUniqueTaskNames(state: MatrixState): string[] {\n const taskNames = new Set<string>();\n \n for (const [, job] of state.jobs) {\n taskNames.add(job.taskName);\n }\n \n return Array.from(taskNames).sort();\n}\n","import kleur from 'kleur';\nimport type { JobStatus } from '../model/types.js';\n\nlet colorsEnabled = true;\n\nexport function setColorsEnabled(enabled: boolean): void {\n colorsEnabled = enabled;\n kleur.enabled = enabled;\n}\n\nexport function getStatusColor(status: JobStatus): (text: string) => string {\n if (!colorsEnabled) {\n return (text: string) => text;\n }\n\n switch (status) {\n case 'done':\n return kleur.green;\n case 'error':\n return kleur.red;\n case 'running':\n return kleur.blue;\n case 'queued':\n return kleur.yellow;\n case 'skipped':\n case 'canceled':\n return kleur.gray;\n default:\n return (text: string) => text;\n }\n}\n\nexport const colors = {\n error: kleur.red,\n success: kleur.green,\n warning: kleur.yellow,\n info: kleur.blue,\n dim: kleur.gray,\n bold: kleur.bold,\n};\n\nexport const green = kleur.green;\nexport const yellow = kleur.yellow;\nexport const red = kleur.red;\nexport const cyan = kleur.cyan;\nexport const dim = kleur.gray;\n","const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nexport class Spinner {\n private frame = 0;\n\n next(): string {\n const char = SPINNER_FRAMES[this.frame];\n this.frame = (this.frame + 1) % SPINNER_FRAMES.length;\n return char || '⠋';\n }\n\n reset(): void {\n this.frame = 0;\n }\n}\n","import logUpdate from 'log-update';\nimport type { MatrixState } from '../model/types.js';\nimport { renderTable } from './table.js';\n\nexport class WatchRenderer {\n private updateInterval: NodeJS.Timeout | null = null;\n private state: MatrixState | null = null;\n\n start(state: MatrixState): void {\n this.state = state;\n this.render();\n \n this.updateInterval = setInterval(() => {\n this.render();\n }, 80);\n }\n\n update(state: MatrixState): void {\n this.state = state;\n }\n\n stop(): void {\n if (this.updateInterval) {\n clearInterval(this.updateInterval);\n this.updateInterval = null;\n }\n logUpdate.done();\n }\n\n private render(): void {\n if (!this.state) return;\n \n const output = renderTable(this.state);\n logUpdate(output);\n }\n}\n","import type { MatrixState, ErrorEntry, JobResult } from '../model/types.js';\nimport { extractErrorSnippet } from '../utils/log.js';\n\nexport function aggregateErrors(state: MatrixState): ErrorEntry[] {\n const errors: ErrorEntry[] = [];\n\n for (const [, result] of state.results) {\n if (result.job.status === 'error') {\n const errorMessage = getErrorMessage(result);\n const logSnippet = extractErrorSnippet(result.stderr, result.stdout);\n\n errors.push({\n taskName: result.job.taskName,\n serviceName: result.job.serviceName,\n exitCode: result.exitCode,\n durationMs: result.durationMs,\n message: errorMessage,\n logSnippet,\n errorType: result.errorType,\n });\n }\n }\n\n errors.sort((a, b) => {\n if (a.taskName !== b.taskName) {\n return a.taskName.localeCompare(b.taskName);\n }\n return a.serviceName.localeCompare(b.serviceName);\n });\n\n return errors;\n}\n\nfunction getErrorMessage(result: JobResult): string {\n if (result.errorType === 'timeout') {\n return `Timeout after ${result.durationMs}ms`;\n }\n \n if (result.errorType === 'spawn') {\n return `Command not found: ${result.job.command}`;\n }\n \n if (result.errorType === 'signal') {\n return `Killed by signal: ${result.signal}`;\n }\n \n return `Exit code ${result.exitCode}`;\n}\n\nexport function formatErrors(errors: ErrorEntry[]): string {\n if (errors.length === 0) {\n return '';\n }\n\n const lines: string[] = ['', 'Errors:'];\n\n for (const error of errors) {\n lines.push(`- ${error.serviceName} has error in ${error.taskName} test:`);\n \n const snippet = error.logSnippet.trim();\n if (snippet) {\n const snippetLines = snippet.split('\\n').slice(0, 10);\n for (const line of snippetLines) {\n lines.push(` ${line}`);\n }\n } else {\n lines.push(` ${error.message}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["existsSync","colorsEnabled"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ interface TaskDef {
|
|
|
8
8
|
shell?: boolean;
|
|
9
9
|
timeout?: number;
|
|
10
10
|
env?: Record<string, string>;
|
|
11
|
+
priority?: number;
|
|
11
12
|
}
|
|
12
13
|
interface ServiceDef {
|
|
13
14
|
name: string;
|
|
@@ -25,6 +26,7 @@ interface Job {
|
|
|
25
26
|
shell?: boolean;
|
|
26
27
|
timeout?: number;
|
|
27
28
|
env?: Record<string, string>;
|
|
29
|
+
priority?: number;
|
|
28
30
|
status: JobStatus;
|
|
29
31
|
startedAt?: number;
|
|
30
32
|
endedAt?: number;
|
|
@@ -66,6 +68,7 @@ interface RunOptions {
|
|
|
66
68
|
quiet: boolean;
|
|
67
69
|
timeout?: number;
|
|
68
70
|
noColor: boolean;
|
|
71
|
+
random?: boolean;
|
|
69
72
|
}
|
|
70
73
|
interface Config {
|
|
71
74
|
services: ServiceDef[];
|
|
@@ -86,6 +89,8 @@ declare class Runner extends EventEmitter {
|
|
|
86
89
|
private handleInterrupt;
|
|
87
90
|
run(jobs: Job[]): Promise<MatrixState>;
|
|
88
91
|
getState(): MatrixState;
|
|
92
|
+
private sortJobsByPriority;
|
|
93
|
+
private shuffleJobs;
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
declare function renderTable(state: MatrixState): string;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ var TaskDefSchema = z.object({
|
|
|
11
11
|
args: z.array(z.string()).optional(),
|
|
12
12
|
shell: z.boolean().optional(),
|
|
13
13
|
timeout: z.number().positive().optional(),
|
|
14
|
+
priority: z.number().optional(),
|
|
14
15
|
env: z.record(z.string()).optional()
|
|
15
16
|
});
|
|
16
17
|
var ServiceDefSchema = z.object({
|
|
@@ -21,6 +22,7 @@ var ServiceDefSchema = z.object({
|
|
|
21
22
|
args: z.array(z.string()).optional(),
|
|
22
23
|
shell: z.boolean().optional(),
|
|
23
24
|
timeout: z.number().positive().optional(),
|
|
25
|
+
priority: z.number().optional(),
|
|
24
26
|
env: z.record(z.string()).optional()
|
|
25
27
|
})).optional(),
|
|
26
28
|
env: z.record(z.string()).optional()
|
|
@@ -142,6 +144,7 @@ function generateJobs(config, taskNames, serviceNames) {
|
|
|
142
144
|
args: taskOverride?.args ?? globalTask.args,
|
|
143
145
|
shell: taskOverride?.shell ?? globalTask.shell ?? true,
|
|
144
146
|
timeout: taskOverride?.timeout ?? globalTask.timeout,
|
|
147
|
+
priority: taskOverride?.priority ?? globalTask.priority,
|
|
145
148
|
env: {
|
|
146
149
|
...globalTask.env,
|
|
147
150
|
...service.env,
|
|
@@ -157,6 +160,7 @@ function generateJobs(config, taskNames, serviceNames) {
|
|
|
157
160
|
args: mergedTask.args,
|
|
158
161
|
shell: mergedTask.shell,
|
|
159
162
|
timeout: mergedTask.timeout,
|
|
163
|
+
priority: mergedTask.priority,
|
|
160
164
|
env: mergedTask.env,
|
|
161
165
|
status: "queued"
|
|
162
166
|
});
|
|
@@ -303,7 +307,12 @@ var Runner = class extends EventEmitter {
|
|
|
303
307
|
for (const job of jobs) {
|
|
304
308
|
this.state.jobs.set(job.id, job);
|
|
305
309
|
}
|
|
306
|
-
|
|
310
|
+
let executableJobs = jobs.filter((j) => j.status === "queued");
|
|
311
|
+
if (this.options.random) {
|
|
312
|
+
executableJobs = this.shuffleJobs(executableJobs);
|
|
313
|
+
} else {
|
|
314
|
+
executableJobs = this.sortJobsByPriority(executableJobs);
|
|
315
|
+
}
|
|
307
316
|
for (const job of executableJobs) {
|
|
308
317
|
if (this.shouldStop) {
|
|
309
318
|
job.status = "canceled";
|
|
@@ -350,6 +359,23 @@ var Runner = class extends EventEmitter {
|
|
|
350
359
|
getState() {
|
|
351
360
|
return this.state;
|
|
352
361
|
}
|
|
362
|
+
sortJobsByPriority(jobs) {
|
|
363
|
+
return jobs.slice().sort((a, b) => {
|
|
364
|
+
const priorityA = a.priority ?? 0;
|
|
365
|
+
const priorityB = b.priority ?? 0;
|
|
366
|
+
return priorityB - priorityA;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
shuffleJobs(jobs) {
|
|
370
|
+
const shuffled = jobs.slice();
|
|
371
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
372
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
373
|
+
const temp = shuffled[i];
|
|
374
|
+
shuffled[i] = shuffled[j];
|
|
375
|
+
shuffled[j] = temp;
|
|
376
|
+
}
|
|
377
|
+
return shuffled;
|
|
378
|
+
}
|
|
353
379
|
};
|
|
354
380
|
|
|
355
381
|
// src/render/table.ts
|
|
@@ -409,32 +435,33 @@ var Spinner = class {
|
|
|
409
435
|
// src/render/table.ts
|
|
410
436
|
var spinner = new Spinner();
|
|
411
437
|
function renderTable(state) {
|
|
438
|
+
const taskNames = getUniqueTaskNames(state);
|
|
439
|
+
const serviceGroups = groupJobsByService(state);
|
|
412
440
|
const table = new Table({
|
|
413
|
-
head: ["
|
|
441
|
+
head: ["Service", ...taskNames],
|
|
414
442
|
style: {
|
|
415
443
|
head: [],
|
|
416
444
|
border: []
|
|
417
445
|
}
|
|
418
446
|
});
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
447
|
+
for (const [serviceName, jobs] of serviceGroups) {
|
|
448
|
+
const row = [serviceName];
|
|
449
|
+
for (const taskName of taskNames) {
|
|
450
|
+
const job = jobs.find((j) => j.taskName === taskName);
|
|
451
|
+
if (job) {
|
|
452
|
+
const statusColor = getStatusColor(job.status);
|
|
453
|
+
let statusText = job.status;
|
|
454
|
+
if (job.status === "running") {
|
|
455
|
+
statusText = `${spinner.next()} ${job.status}`;
|
|
456
|
+
} else if (job.status === "queued") {
|
|
457
|
+
statusText = `\u23F3 ${job.status}`;
|
|
458
|
+
}
|
|
459
|
+
row.push(statusColor(statusText));
|
|
460
|
+
} else {
|
|
461
|
+
row.push("-");
|
|
429
462
|
}
|
|
430
|
-
const coloredStatus = statusColor(statusText);
|
|
431
|
-
table.push([
|
|
432
|
-
isFirstRow ? taskName : "",
|
|
433
|
-
job.serviceName,
|
|
434
|
-
coloredStatus
|
|
435
|
-
]);
|
|
436
|
-
isFirstRow = false;
|
|
437
463
|
}
|
|
464
|
+
table.push(row);
|
|
438
465
|
}
|
|
439
466
|
return table.toString();
|
|
440
467
|
}
|
|
@@ -479,16 +506,23 @@ function renderJson(state) {
|
|
|
479
506
|
};
|
|
480
507
|
return JSON.stringify(output, null, 2);
|
|
481
508
|
}
|
|
482
|
-
function
|
|
509
|
+
function groupJobsByService(state) {
|
|
483
510
|
const groups = /* @__PURE__ */ new Map();
|
|
484
511
|
for (const [, job] of state.jobs) {
|
|
485
|
-
if (!groups.has(job.
|
|
486
|
-
groups.set(job.
|
|
512
|
+
if (!groups.has(job.serviceName)) {
|
|
513
|
+
groups.set(job.serviceName, []);
|
|
487
514
|
}
|
|
488
|
-
groups.get(job.
|
|
515
|
+
groups.get(job.serviceName).push(job);
|
|
489
516
|
}
|
|
490
517
|
return groups;
|
|
491
518
|
}
|
|
519
|
+
function getUniqueTaskNames(state) {
|
|
520
|
+
const taskNames = /* @__PURE__ */ new Set();
|
|
521
|
+
for (const [, job] of state.jobs) {
|
|
522
|
+
taskNames.add(job.taskName);
|
|
523
|
+
}
|
|
524
|
+
return Array.from(taskNames).sort();
|
|
525
|
+
}
|
|
492
526
|
|
|
493
527
|
// src/report/aggregator.ts
|
|
494
528
|
function aggregateErrors(state) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/loader.ts","../src/config/schema.ts","../src/runner/generator.ts","../src/runner/runner.ts","../src/runner/executor.ts","../src/utils/log.ts","../src/render/table.ts","../src/utils/colors.ts","../src/utils/spinner.ts","../src/report/aggregator.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url';\nimport { existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { ConfigSchema } from './schema.js';\nimport type { Config } from '../model/types.js';\n\nconst CONFIG_FILES = ['qa.config.ts', 'qa.config.js', 'qa.config.mjs'];\n\nexport async function findConfig(startDir: string = process.cwd()): Promise<string | null> {\n let currentDir = resolve(startDir);\n const root = resolve('/');\n\n while (currentDir !== root) {\n for (const configFile of CONFIG_FILES) {\n const configPath = resolve(currentDir, configFile);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n currentDir = dirname(currentDir);\n }\n\n for (const configFile of CONFIG_FILES) {\n const configPath = resolve(root, configFile);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n\n return null;\n}\n\nexport async function loadConfig(configPath?: string): Promise<Config> {\n const path = configPath || await findConfig();\n \n if (!path) {\n throw new Error(\n 'No config file found. Create qa.config.ts or qa.config.js in your project root.'\n );\n }\n\n if (!existsSync(path)) {\n throw new Error(`Config file not found: ${path}`);\n }\n\n try {\n const fileUrl = pathToFileURL(path).href;\n const module = await import(fileUrl);\n const configData = module.default || module;\n\n const result = ConfigSchema.safeParse(configData);\n \n if (!result.success) {\n const errors = result.error.errors.map(err => \n ` - ${err.path.join('.')}: ${err.message}`\n ).join('\\n');\n throw new Error(`Invalid config:\\n${errors}`);\n }\n\n const serviceNames = new Set<string>();\n for (const service of result.data.services) {\n if (serviceNames.has(service.name)) {\n throw new Error(`Duplicate service name: ${service.name}`);\n }\n serviceNames.add(service.name);\n }\n\n const config: Config = {\n services: result.data.services,\n tasks: Object.entries(result.data.tasks).reduce((acc, [name, def]) => {\n acc[name] = { name, ...def };\n return acc;\n }, {} as Record<string, any>),\n };\n\n return config;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Failed to load config: ${String(error)}`);\n }\n}\n","import { z } from 'zod';\n\nexport const TaskDefSchema = z.object({\n name: z.string(),\n command: z.string(),\n args: z.array(z.string()).optional(),\n shell: z.boolean().optional(),\n timeout: z.number().positive().optional(),\n env: z.record(z.string()).optional(),\n});\n\nexport const ServiceDefSchema = z.object({\n name: z.string(),\n cwd: z.string(),\n tasks: z.record(z.object({\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n shell: z.boolean().optional(),\n timeout: z.number().positive().optional(),\n env: z.record(z.string()).optional(),\n })).optional(),\n env: z.record(z.string()).optional(),\n});\n\nexport const ConfigSchema = z.object({\n services: z.array(ServiceDefSchema),\n tasks: z.record(TaskDefSchema.omit({ name: true })),\n});\n\nexport type ConfigInput = z.input<typeof ConfigSchema>;\nexport type ConfigOutput = z.output<typeof ConfigSchema>;\n","import { existsSync } from 'node:fs';\nimport type { Config, Job, TaskDef } from '../model/types.js';\n\nexport function generateJobs(\n config: Config,\n taskNames: string[],\n serviceNames: string[]\n): Job[] {\n const jobs: Job[] = [];\n const resolvedServiceNames = serviceNames.includes('all') \n ? config.services.map(s => s.name)\n : serviceNames;\n\n const resolvedTaskNames = taskNames.includes('all')\n ? Object.keys(config.tasks)\n : taskNames;\n\n for (const taskName of resolvedTaskNames) {\n const globalTask = config.tasks[taskName];\n \n if (!globalTask) {\n throw new Error(`Unknown task: ${taskName}`);\n }\n\n for (const serviceName of resolvedServiceNames) {\n const service = config.services.find(s => s.name === serviceName);\n \n if (!service) {\n throw new Error(`Unknown service: ${serviceName}`);\n }\n\n if (!existsSync(service.cwd)) {\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: '',\n status: 'skipped',\n });\n continue;\n }\n\n const taskOverride = service.tasks?.[taskName];\n \n if (taskOverride && taskOverride.command === undefined && !globalTask.command) {\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: '',\n status: 'skipped',\n });\n continue;\n }\n\n const mergedTask: TaskDef = {\n name: taskName,\n command: taskOverride?.command ?? globalTask.command,\n args: taskOverride?.args ?? globalTask.args,\n shell: taskOverride?.shell ?? globalTask.shell ?? true,\n timeout: taskOverride?.timeout ?? globalTask.timeout,\n env: {\n ...globalTask.env,\n ...service.env,\n ...taskOverride?.env,\n },\n };\n\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: mergedTask.command,\n args: mergedTask.args,\n shell: mergedTask.shell,\n timeout: mergedTask.timeout,\n env: mergedTask.env,\n status: 'queued',\n });\n }\n }\n\n return jobs;\n}\n","import PQueue from 'p-queue';\nimport { EventEmitter } from 'node:events';\nimport type { Job, JobResult, MatrixState, RunOptions } from '../model/types.js';\nimport { executeJob } from './executor.js';\n\nexport interface RunnerEvents {\n jobStart: (job: Job) => void;\n jobComplete: (result: JobResult) => void;\n jobError: (result: JobResult) => void;\n runComplete: (state: MatrixState) => void;\n runCanceled: () => void;\n}\n\nexport class Runner extends EventEmitter {\n private queue: PQueue;\n private state: MatrixState;\n private shouldStop = false;\n private options: RunOptions;\n\n constructor(options: RunOptions) {\n super();\n this.options = options;\n this.queue = new PQueue({ concurrency: options.concurrency });\n this.state = {\n jobs: new Map(),\n results: new Map(),\n errors: [],\n startedAt: Date.now(),\n };\n\n process.on('SIGINT', () => this.handleInterrupt());\n process.on('SIGTERM', () => this.handleInterrupt());\n }\n\n private handleInterrupt(): void {\n if (this.shouldStop) {\n process.exit(130);\n }\n \n this.shouldStop = true;\n this.queue.clear();\n \n for (const [id, job] of this.state.jobs) {\n if (job.status === 'queued') {\n job.status = 'canceled';\n this.state.jobs.set(id, job);\n }\n }\n \n this.emit('runCanceled');\n }\n\n async run(jobs: Job[]): Promise<MatrixState> {\n for (const job of jobs) {\n this.state.jobs.set(job.id, job);\n }\n\n const executableJobs = jobs.filter(j => j.status === 'queued');\n \n for (const job of executableJobs) {\n if (this.shouldStop) {\n job.status = 'canceled';\n this.state.jobs.set(job.id, job);\n continue;\n }\n\n this.queue.add(async () => {\n if (this.shouldStop) {\n job.status = 'canceled';\n this.state.jobs.set(job.id, job);\n return;\n }\n\n job.status = 'running';\n job.startedAt = Date.now();\n this.state.jobs.set(job.id, job);\n this.emit('jobStart', job);\n\n const result = await executeJob(job);\n \n job.status = result.exitCode === 0 ? 'done' : 'error';\n job.endedAt = Date.now();\n this.state.jobs.set(job.id, job);\n this.state.results.set(job.id, result);\n\n if (result.exitCode !== 0) {\n this.emit('jobError', result);\n \n if (this.options.failFast) {\n this.shouldStop = true;\n this.queue.clear();\n \n for (const [id, j] of this.state.jobs) {\n if (j.status === 'queued') {\n j.status = 'canceled';\n this.state.jobs.set(id, j);\n }\n }\n }\n } else {\n this.emit('jobComplete', result);\n }\n });\n }\n\n await this.queue.onIdle();\n \n this.state.endedAt = Date.now();\n this.emit('runComplete', this.state);\n \n return this.state;\n }\n\n getState(): MatrixState {\n return this.state;\n }\n}\n","import { execa, type ExecaError } from 'execa';\nimport type { Job, JobResult } from '../model/types.js';\nimport { LogBuffer } from '../utils/log.js';\n\nexport async function executeJob(job: Job): Promise<JobResult> {\n const startTime = Date.now();\n const stdoutBuffer = new LogBuffer();\n const stderrBuffer = new LogBuffer();\n\n try {\n const args = job.args || [];\n const options: any = {\n cwd: job.cwd,\n env: { ...process.env, ...job.env },\n shell: job.shell,\n timeout: job.timeout,\n reject: false,\n all: true,\n };\n\n const result = await execa(job.command, args, options);\n\n stdoutBuffer.append(result.stdout || '');\n stderrBuffer.append(result.stderr || '');\n\n const durationMs = Date.now() - startTime;\n\n if (result.exitCode === 0) {\n return {\n job,\n exitCode: 0,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n };\n }\n\n return {\n job,\n exitCode: result.exitCode,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n errorType: result.signal ? 'signal' : 'exit',\n signal: result.signal || undefined,\n };\n } catch (error) {\n const durationMs = Date.now() - startTime;\n const execaError = error as ExecaError;\n\n if (execaError.stdout) stdoutBuffer.append(execaError.stdout);\n if (execaError.stderr) stderrBuffer.append(execaError.stderr);\n\n let errorType: 'exit' | 'timeout' | 'spawn' | 'signal' = 'exit';\n \n if (execaError.timedOut) {\n errorType = 'timeout';\n } else if (execaError.signal) {\n errorType = 'signal';\n } else if (execaError.exitCode === 127 || (error as any).code === 'ENOENT') {\n errorType = 'spawn';\n }\n\n return {\n job,\n exitCode: execaError.exitCode,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n errorType,\n signal: execaError.signal || undefined,\n };\n }\n}\n","const MAX_LOG_LINES = 200;\n\nexport function truncateLog(log: string, maxLines: number = MAX_LOG_LINES): string {\n const lines = log.split('\\n');\n if (lines.length <= maxLines) {\n return log;\n }\n return lines.slice(-maxLines).join('\\n');\n}\n\nexport function extractErrorSnippet(stderr: string, stdout: string, maxLines: number = 10): string {\n const errorLog = stderr || stdout;\n const lines = errorLog.split('\\n').filter(line => line.trim());\n \n if (lines.length === 0) {\n return 'No error output';\n }\n \n const relevantLines = lines.slice(-maxLines);\n return relevantLines.join('\\n');\n}\n\nexport class LogBuffer {\n private lines: string[] = [];\n private maxLines: number;\n\n constructor(maxLines: number = MAX_LOG_LINES) {\n this.maxLines = maxLines;\n }\n\n append(data: string): void {\n const newLines = data.split('\\n');\n this.lines.push(...newLines);\n \n if (this.lines.length > this.maxLines) {\n this.lines = this.lines.slice(-this.maxLines);\n }\n }\n\n getContent(): string {\n return this.lines.join('\\n');\n }\n\n clear(): void {\n this.lines = [];\n }\n}\n","import Table from 'cli-table3';\nimport type { MatrixState, Job } from '../model/types.js';\nimport { getStatusColor } from '../utils/colors.js';\nimport { Spinner } from '../utils/spinner.js';\n\nconst spinner = new Spinner();\n\nexport function renderTable(state: MatrixState): string {\n const table = new Table({\n head: ['Task', 'Service', 'Status'],\n style: {\n head: [],\n border: [],\n },\n });\n\n const taskGroups = groupJobsByTask(state);\n\n for (const [taskName, jobs] of taskGroups) {\n let isFirstRow = true;\n \n for (const job of jobs) {\n const statusColor = getStatusColor(job.status);\n let statusText: string = job.status;\n \n if (job.status === 'running') {\n statusText = `${spinner.next()} ${job.status}`;\n } else if (job.status === 'queued') {\n statusText = `⏳ ${job.status}`;\n }\n \n const coloredStatus = statusColor(statusText);\n \n table.push([\n isFirstRow ? taskName : '',\n job.serviceName,\n coloredStatus,\n ]);\n \n isFirstRow = false;\n }\n }\n\n return table.toString();\n}\n\nexport function renderMinimal(state: MatrixState): string {\n const lines: string[] = [];\n \n for (const [, job] of state.jobs) {\n const statusColor = getStatusColor(job.status);\n const statusText = statusColor(job.status);\n lines.push(`${job.taskName} ${job.serviceName} ${statusText}`);\n }\n \n return lines.join('\\n');\n}\n\nexport function renderJson(state: MatrixState): string {\n const matrix: Record<string, Record<string, any>> = {};\n \n for (const [, job] of state.jobs) {\n if (!matrix[job.taskName]) {\n matrix[job.taskName] = {};\n }\n \n const result = state.results.get(job.id);\n const taskMatrix = matrix[job.taskName];\n \n if (taskMatrix) {\n taskMatrix[job.serviceName] = {\n status: job.status,\n exitCode: result?.exitCode,\n durationMs: result?.durationMs,\n };\n }\n }\n\n const errors = Array.from(state.results.values())\n .filter(r => r.job.status === 'error')\n .map(r => ({\n taskName: r.job.taskName,\n serviceName: r.job.serviceName,\n exitCode: r.exitCode,\n durationMs: r.durationMs,\n errorType: r.errorType,\n }));\n\n const output = {\n startedAt: state.startedAt,\n endedAt: state.endedAt,\n durationMs: state.endedAt ? state.endedAt - state.startedAt : undefined,\n matrix,\n errors,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\nfunction groupJobsByTask(state: MatrixState): Map<string, Job[]> {\n const groups = new Map<string, Job[]>();\n \n for (const [, job] of state.jobs) {\n if (!groups.has(job.taskName)) {\n groups.set(job.taskName, []);\n }\n groups.get(job.taskName)!.push(job);\n }\n \n return groups;\n}\n","import kleur from 'kleur';\nimport type { JobStatus } from '../model/types.js';\n\nlet colorsEnabled = true;\n\nexport function setColorsEnabled(enabled: boolean): void {\n colorsEnabled = enabled;\n kleur.enabled = enabled;\n}\n\nexport function getStatusColor(status: JobStatus): (text: string) => string {\n if (!colorsEnabled) {\n return (text: string) => text;\n }\n\n switch (status) {\n case 'done':\n return kleur.green;\n case 'error':\n return kleur.red;\n case 'running':\n return kleur.blue;\n case 'queued':\n return kleur.yellow;\n case 'skipped':\n case 'canceled':\n return kleur.gray;\n default:\n return (text: string) => text;\n }\n}\n\nexport const colors = {\n error: kleur.red,\n success: kleur.green,\n warning: kleur.yellow,\n info: kleur.blue,\n dim: kleur.gray,\n bold: kleur.bold,\n};\n\nexport const green = kleur.green;\nexport const yellow = kleur.yellow;\nexport const red = kleur.red;\nexport const cyan = kleur.cyan;\nexport const dim = kleur.gray;\n","const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nexport class Spinner {\n private frame = 0;\n\n next(): string {\n const char = SPINNER_FRAMES[this.frame];\n this.frame = (this.frame + 1) % SPINNER_FRAMES.length;\n return char || '⠋';\n }\n\n reset(): void {\n this.frame = 0;\n }\n}\n","import type { MatrixState, ErrorEntry, JobResult } from '../model/types.js';\nimport { extractErrorSnippet } from '../utils/log.js';\n\nexport function aggregateErrors(state: MatrixState): ErrorEntry[] {\n const errors: ErrorEntry[] = [];\n\n for (const [, result] of state.results) {\n if (result.job.status === 'error') {\n const errorMessage = getErrorMessage(result);\n const logSnippet = extractErrorSnippet(result.stderr, result.stdout);\n\n errors.push({\n taskName: result.job.taskName,\n serviceName: result.job.serviceName,\n exitCode: result.exitCode,\n durationMs: result.durationMs,\n message: errorMessage,\n logSnippet,\n errorType: result.errorType,\n });\n }\n }\n\n errors.sort((a, b) => {\n if (a.taskName !== b.taskName) {\n return a.taskName.localeCompare(b.taskName);\n }\n return a.serviceName.localeCompare(b.serviceName);\n });\n\n return errors;\n}\n\nfunction getErrorMessage(result: JobResult): string {\n if (result.errorType === 'timeout') {\n return `Timeout after ${result.durationMs}ms`;\n }\n \n if (result.errorType === 'spawn') {\n return `Command not found: ${result.job.command}`;\n }\n \n if (result.errorType === 'signal') {\n return `Killed by signal: ${result.signal}`;\n }\n \n return `Exit code ${result.exitCode}`;\n}\n\nexport function formatErrors(errors: ErrorEntry[]): string {\n if (errors.length === 0) {\n return '';\n }\n\n const lines: string[] = ['', 'Errors:'];\n\n for (const error of errors) {\n lines.push(`- ${error.serviceName} has error in ${error.taskName} test:`);\n \n const snippet = error.logSnippet.trim();\n if (snippet) {\n const snippetLines = snippet.split('\\n').slice(0, 10);\n for (const line of snippetLines) {\n lines.push(` ${line}`);\n }\n } else {\n lines.push(` ${error.message}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["existsSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/config/loader.ts","../src/config/schema.ts","../src/runner/generator.ts","../src/runner/runner.ts","../src/runner/executor.ts","../src/utils/log.ts","../src/render/table.ts","../src/utils/colors.ts","../src/utils/spinner.ts","../src/report/aggregator.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url';\nimport { existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { ConfigSchema } from './schema.js';\nimport type { Config } from '../model/types.js';\n\nconst CONFIG_FILES = ['qa.config.ts', 'qa.config.js', 'qa.config.mjs'];\n\nexport async function findConfig(startDir: string = process.cwd()): Promise<string | null> {\n let currentDir = resolve(startDir);\n const root = resolve('/');\n\n while (currentDir !== root) {\n for (const configFile of CONFIG_FILES) {\n const configPath = resolve(currentDir, configFile);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n currentDir = dirname(currentDir);\n }\n\n for (const configFile of CONFIG_FILES) {\n const configPath = resolve(root, configFile);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n\n return null;\n}\n\nexport async function loadConfig(configPath?: string): Promise<Config> {\n const path = configPath || await findConfig();\n \n if (!path) {\n throw new Error(\n 'No config file found. Create qa.config.ts or qa.config.js in your project root.'\n );\n }\n\n if (!existsSync(path)) {\n throw new Error(`Config file not found: ${path}`);\n }\n\n try {\n const fileUrl = pathToFileURL(path).href;\n const module = await import(fileUrl);\n const configData = module.default || module;\n\n const result = ConfigSchema.safeParse(configData);\n \n if (!result.success) {\n const errors = result.error.errors.map(err => \n ` - ${err.path.join('.')}: ${err.message}`\n ).join('\\n');\n throw new Error(`Invalid config:\\n${errors}`);\n }\n\n const serviceNames = new Set<string>();\n for (const service of result.data.services) {\n if (serviceNames.has(service.name)) {\n throw new Error(`Duplicate service name: ${service.name}`);\n }\n serviceNames.add(service.name);\n }\n\n const config: Config = {\n services: result.data.services,\n tasks: Object.entries(result.data.tasks).reduce((acc, [name, def]) => {\n acc[name] = { name, ...def };\n return acc;\n }, {} as Record<string, any>),\n };\n\n return config;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Failed to load config: ${String(error)}`);\n }\n}\n","import { z } from 'zod';\n\nexport const TaskDefSchema = z.object({\n name: z.string(),\n command: z.string(),\n args: z.array(z.string()).optional(),\n shell: z.boolean().optional(),\n timeout: z.number().positive().optional(),\n priority: z.number().optional(),\n env: z.record(z.string()).optional(),\n});\n\nexport const ServiceDefSchema = z.object({\n name: z.string(),\n cwd: z.string(),\n tasks: z.record(z.object({\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n shell: z.boolean().optional(),\n timeout: z.number().positive().optional(),\n priority: z.number().optional(),\n env: z.record(z.string()).optional(),\n })).optional(),\n env: z.record(z.string()).optional(),\n});\n\nexport const ConfigSchema = z.object({\n services: z.array(ServiceDefSchema),\n tasks: z.record(TaskDefSchema.omit({ name: true })),\n});\n\nexport type ConfigInput = z.input<typeof ConfigSchema>;\nexport type ConfigOutput = z.output<typeof ConfigSchema>;\n","import { existsSync } from 'node:fs';\nimport type { Config, Job, TaskDef } from '../model/types.js';\n\nexport function generateJobs(\n config: Config,\n taskNames: string[],\n serviceNames: string[]\n): Job[] {\n const jobs: Job[] = [];\n const resolvedServiceNames = serviceNames.includes('all') \n ? config.services.map(s => s.name)\n : serviceNames;\n\n const resolvedTaskNames = taskNames.includes('all')\n ? Object.keys(config.tasks)\n : taskNames;\n\n for (const taskName of resolvedTaskNames) {\n const globalTask = config.tasks[taskName];\n \n if (!globalTask) {\n throw new Error(`Unknown task: ${taskName}`);\n }\n\n for (const serviceName of resolvedServiceNames) {\n const service = config.services.find(s => s.name === serviceName);\n \n if (!service) {\n throw new Error(`Unknown service: ${serviceName}`);\n }\n\n if (!existsSync(service.cwd)) {\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: '',\n status: 'skipped',\n });\n continue;\n }\n\n const taskOverride = service.tasks?.[taskName];\n \n if (taskOverride && taskOverride.command === undefined && !globalTask.command) {\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: '',\n status: 'skipped',\n });\n continue;\n }\n\n const mergedTask: TaskDef = {\n name: taskName,\n command: taskOverride?.command ?? globalTask.command,\n args: taskOverride?.args ?? globalTask.args,\n shell: taskOverride?.shell ?? globalTask.shell ?? true,\n timeout: taskOverride?.timeout ?? globalTask.timeout,\n priority: taskOverride?.priority ?? globalTask.priority,\n env: {\n ...globalTask.env,\n ...service.env,\n ...taskOverride?.env,\n },\n };\n\n jobs.push({\n id: `${taskName}:${serviceName}`,\n taskName,\n serviceName,\n cwd: service.cwd,\n command: mergedTask.command,\n args: mergedTask.args,\n shell: mergedTask.shell,\n timeout: mergedTask.timeout,\n priority: mergedTask.priority,\n env: mergedTask.env,\n status: 'queued',\n });\n }\n }\n\n return jobs;\n}\n","import PQueue from 'p-queue';\nimport { EventEmitter } from 'node:events';\nimport type { Job, JobResult, MatrixState, RunOptions } from '../model/types.js';\nimport { executeJob } from './executor.js';\n\nexport interface RunnerEvents {\n jobStart: (job: Job) => void;\n jobComplete: (result: JobResult) => void;\n jobError: (result: JobResult) => void;\n runComplete: (state: MatrixState) => void;\n runCanceled: () => void;\n}\n\nexport class Runner extends EventEmitter {\n private queue: PQueue;\n private state: MatrixState;\n private shouldStop = false;\n private options: RunOptions;\n\n constructor(options: RunOptions) {\n super();\n this.options = options;\n this.queue = new PQueue({ concurrency: options.concurrency });\n this.state = {\n jobs: new Map(),\n results: new Map(),\n errors: [],\n startedAt: Date.now(),\n };\n\n process.on('SIGINT', () => this.handleInterrupt());\n process.on('SIGTERM', () => this.handleInterrupt());\n }\n\n private handleInterrupt(): void {\n if (this.shouldStop) {\n process.exit(130);\n }\n \n this.shouldStop = true;\n this.queue.clear();\n \n for (const [id, job] of this.state.jobs) {\n if (job.status === 'queued') {\n job.status = 'canceled';\n this.state.jobs.set(id, job);\n }\n }\n \n this.emit('runCanceled');\n }\n\n async run(jobs: Job[]): Promise<MatrixState> {\n for (const job of jobs) {\n this.state.jobs.set(job.id, job);\n }\n\n let executableJobs = jobs.filter(j => j.status === 'queued');\n \n if (this.options.random) {\n executableJobs = this.shuffleJobs(executableJobs);\n } else {\n executableJobs = this.sortJobsByPriority(executableJobs);\n }\n \n for (const job of executableJobs) {\n if (this.shouldStop) {\n job.status = 'canceled';\n this.state.jobs.set(job.id, job);\n continue;\n }\n\n this.queue.add(async () => {\n if (this.shouldStop) {\n job.status = 'canceled';\n this.state.jobs.set(job.id, job);\n return;\n }\n\n job.status = 'running';\n job.startedAt = Date.now();\n this.state.jobs.set(job.id, job);\n this.emit('jobStart', job);\n\n const result = await executeJob(job);\n \n job.status = result.exitCode === 0 ? 'done' : 'error';\n job.endedAt = Date.now();\n this.state.jobs.set(job.id, job);\n this.state.results.set(job.id, result);\n\n if (result.exitCode !== 0) {\n this.emit('jobError', result);\n \n if (this.options.failFast) {\n this.shouldStop = true;\n this.queue.clear();\n \n for (const [id, j] of this.state.jobs) {\n if (j.status === 'queued') {\n j.status = 'canceled';\n this.state.jobs.set(id, j);\n }\n }\n }\n } else {\n this.emit('jobComplete', result);\n }\n });\n }\n\n await this.queue.onIdle();\n \n this.state.endedAt = Date.now();\n this.emit('runComplete', this.state);\n \n return this.state;\n }\n\n getState(): MatrixState {\n return this.state;\n }\n\n private sortJobsByPriority(jobs: Job[]): Job[] {\n return jobs.slice().sort((a, b) => {\n const priorityA = a.priority ?? 0;\n const priorityB = b.priority ?? 0;\n return priorityB - priorityA;\n });\n }\n\n private shuffleJobs(jobs: Job[]): Job[] {\n const shuffled = jobs.slice();\n for (let i = shuffled.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const temp = shuffled[i];\n shuffled[i] = shuffled[j]!;\n shuffled[j] = temp!;\n }\n return shuffled;\n }\n}\n","import { execa, type ExecaError } from 'execa';\nimport type { Job, JobResult } from '../model/types.js';\nimport { LogBuffer } from '../utils/log.js';\n\nexport async function executeJob(job: Job): Promise<JobResult> {\n const startTime = Date.now();\n const stdoutBuffer = new LogBuffer();\n const stderrBuffer = new LogBuffer();\n\n try {\n const args = job.args || [];\n const options: any = {\n cwd: job.cwd,\n env: { ...process.env, ...job.env },\n shell: job.shell,\n timeout: job.timeout,\n reject: false,\n all: true,\n };\n\n const result = await execa(job.command, args, options);\n\n stdoutBuffer.append(result.stdout || '');\n stderrBuffer.append(result.stderr || '');\n\n const durationMs = Date.now() - startTime;\n\n if (result.exitCode === 0) {\n return {\n job,\n exitCode: 0,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n };\n }\n\n return {\n job,\n exitCode: result.exitCode,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n errorType: result.signal ? 'signal' : 'exit',\n signal: result.signal || undefined,\n };\n } catch (error) {\n const durationMs = Date.now() - startTime;\n const execaError = error as ExecaError;\n\n if (execaError.stdout) stdoutBuffer.append(execaError.stdout);\n if (execaError.stderr) stderrBuffer.append(execaError.stderr);\n\n let errorType: 'exit' | 'timeout' | 'spawn' | 'signal' = 'exit';\n \n if (execaError.timedOut) {\n errorType = 'timeout';\n } else if (execaError.signal) {\n errorType = 'signal';\n } else if (execaError.exitCode === 127 || (error as any).code === 'ENOENT') {\n errorType = 'spawn';\n }\n\n return {\n job,\n exitCode: execaError.exitCode,\n stdout: stdoutBuffer.getContent(),\n stderr: stderrBuffer.getContent(),\n durationMs,\n errorType,\n signal: execaError.signal || undefined,\n };\n }\n}\n","const MAX_LOG_LINES = 200;\n\nexport function truncateLog(log: string, maxLines: number = MAX_LOG_LINES): string {\n const lines = log.split('\\n');\n if (lines.length <= maxLines) {\n return log;\n }\n return lines.slice(-maxLines).join('\\n');\n}\n\nexport function extractErrorSnippet(stderr: string, stdout: string, maxLines: number = 10): string {\n const errorLog = stderr || stdout;\n const lines = errorLog.split('\\n').filter(line => line.trim());\n \n if (lines.length === 0) {\n return 'No error output';\n }\n \n const relevantLines = lines.slice(-maxLines);\n return relevantLines.join('\\n');\n}\n\nexport class LogBuffer {\n private lines: string[] = [];\n private maxLines: number;\n\n constructor(maxLines: number = MAX_LOG_LINES) {\n this.maxLines = maxLines;\n }\n\n append(data: string): void {\n const newLines = data.split('\\n');\n this.lines.push(...newLines);\n \n if (this.lines.length > this.maxLines) {\n this.lines = this.lines.slice(-this.maxLines);\n }\n }\n\n getContent(): string {\n return this.lines.join('\\n');\n }\n\n clear(): void {\n this.lines = [];\n }\n}\n","import Table from 'cli-table3';\nimport type { MatrixState, Job } from '../model/types.js';\nimport { getStatusColor } from '../utils/colors.js';\nimport { Spinner } from '../utils/spinner.js';\n\nconst spinner = new Spinner();\n\nexport function renderTable(state: MatrixState): string {\n const taskNames = getUniqueTaskNames(state);\n const serviceGroups = groupJobsByService(state);\n\n const table = new Table({\n head: ['Service', ...taskNames],\n style: {\n head: [],\n border: [],\n },\n });\n\n for (const [serviceName, jobs] of serviceGroups) {\n const row: string[] = [serviceName];\n \n for (const taskName of taskNames) {\n const job = jobs.find(j => j.taskName === taskName);\n \n if (job) {\n const statusColor = getStatusColor(job.status);\n let statusText: string = job.status;\n \n if (job.status === 'running') {\n statusText = `${spinner.next()} ${job.status}`;\n } else if (job.status === 'queued') {\n statusText = `⏳ ${job.status}`;\n }\n \n row.push(statusColor(statusText));\n } else {\n row.push('-');\n }\n }\n \n table.push(row);\n }\n\n return table.toString();\n}\n\nexport function renderMinimal(state: MatrixState): string {\n const lines: string[] = [];\n \n for (const [, job] of state.jobs) {\n const statusColor = getStatusColor(job.status);\n const statusText = statusColor(job.status);\n lines.push(`${job.taskName} ${job.serviceName} ${statusText}`);\n }\n \n return lines.join('\\n');\n}\n\nexport function renderJson(state: MatrixState): string {\n const matrix: Record<string, Record<string, any>> = {};\n \n for (const [, job] of state.jobs) {\n if (!matrix[job.taskName]) {\n matrix[job.taskName] = {};\n }\n \n const result = state.results.get(job.id);\n const taskMatrix = matrix[job.taskName];\n \n if (taskMatrix) {\n taskMatrix[job.serviceName] = {\n status: job.status,\n exitCode: result?.exitCode,\n durationMs: result?.durationMs,\n };\n }\n }\n\n const errors = Array.from(state.results.values())\n .filter(r => r.job.status === 'error')\n .map(r => ({\n taskName: r.job.taskName,\n serviceName: r.job.serviceName,\n exitCode: r.exitCode,\n durationMs: r.durationMs,\n errorType: r.errorType,\n }));\n\n const output = {\n startedAt: state.startedAt,\n endedAt: state.endedAt,\n durationMs: state.endedAt ? state.endedAt - state.startedAt : undefined,\n matrix,\n errors,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\nfunction groupJobsByService(state: MatrixState): Map<string, Job[]> {\n const groups = new Map<string, Job[]>();\n \n for (const [, job] of state.jobs) {\n if (!groups.has(job.serviceName)) {\n groups.set(job.serviceName, []);\n }\n groups.get(job.serviceName)!.push(job);\n }\n \n return groups;\n}\n\nfunction getUniqueTaskNames(state: MatrixState): string[] {\n const taskNames = new Set<string>();\n \n for (const [, job] of state.jobs) {\n taskNames.add(job.taskName);\n }\n \n return Array.from(taskNames).sort();\n}\n","import kleur from 'kleur';\nimport type { JobStatus } from '../model/types.js';\n\nlet colorsEnabled = true;\n\nexport function setColorsEnabled(enabled: boolean): void {\n colorsEnabled = enabled;\n kleur.enabled = enabled;\n}\n\nexport function getStatusColor(status: JobStatus): (text: string) => string {\n if (!colorsEnabled) {\n return (text: string) => text;\n }\n\n switch (status) {\n case 'done':\n return kleur.green;\n case 'error':\n return kleur.red;\n case 'running':\n return kleur.blue;\n case 'queued':\n return kleur.yellow;\n case 'skipped':\n case 'canceled':\n return kleur.gray;\n default:\n return (text: string) => text;\n }\n}\n\nexport const colors = {\n error: kleur.red,\n success: kleur.green,\n warning: kleur.yellow,\n info: kleur.blue,\n dim: kleur.gray,\n bold: kleur.bold,\n};\n\nexport const green = kleur.green;\nexport const yellow = kleur.yellow;\nexport const red = kleur.red;\nexport const cyan = kleur.cyan;\nexport const dim = kleur.gray;\n","const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nexport class Spinner {\n private frame = 0;\n\n next(): string {\n const char = SPINNER_FRAMES[this.frame];\n this.frame = (this.frame + 1) % SPINNER_FRAMES.length;\n return char || '⠋';\n }\n\n reset(): void {\n this.frame = 0;\n }\n}\n","import type { MatrixState, ErrorEntry, JobResult } from '../model/types.js';\nimport { extractErrorSnippet } from '../utils/log.js';\n\nexport function aggregateErrors(state: MatrixState): ErrorEntry[] {\n const errors: ErrorEntry[] = [];\n\n for (const [, result] of state.results) {\n if (result.job.status === 'error') {\n const errorMessage = getErrorMessage(result);\n const logSnippet = extractErrorSnippet(result.stderr, result.stdout);\n\n errors.push({\n taskName: result.job.taskName,\n serviceName: result.job.serviceName,\n exitCode: result.exitCode,\n durationMs: result.durationMs,\n message: errorMessage,\n logSnippet,\n errorType: result.errorType,\n });\n }\n }\n\n errors.sort((a, b) => {\n if (a.taskName !== b.taskName) {\n return a.taskName.localeCompare(b.taskName);\n }\n return a.serviceName.localeCompare(b.serviceName);\n });\n\n return errors;\n}\n\nfunction getErrorMessage(result: JobResult): string {\n if (result.errorType === 'timeout') {\n return `Timeout after ${result.durationMs}ms`;\n }\n \n if (result.errorType === 'spawn') {\n return `Command not found: ${result.job.command}`;\n }\n \n if (result.errorType === 'signal') {\n return `Killed by signal: ${result.signal}`;\n }\n \n return `Exit code ${result.exitCode}`;\n}\n\nexport function formatErrors(errors: ErrorEntry[]): string {\n if (errors.length === 0) {\n return '';\n }\n\n const lines: string[] = ['', 'Errors:'];\n\n for (const error of errors) {\n lines.push(`- ${error.serviceName} has error in ${error.taskName} test:`);\n \n const snippet = error.logSnippet.trim();\n if (snippet) {\n const snippetLines = snippet.split('\\n').slice(0, 10);\n for (const line of snippetLines) {\n lines.push(` ${line}`);\n }\n } else {\n lines.push(` ${error.message}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["existsSync"]}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "verify-grid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Run matrix of tasks across services with parallel execution and status reporting",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/
|
|
10
|
+
"url": "https://github.com/kirull1/verify-grid.git"
|
|
11
11
|
},
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/
|
|
13
|
+
"url": "https://github.com/kirull1/verify-grid/issues"
|
|
14
14
|
},
|
|
15
|
-
"homepage": "https://github.com/
|
|
15
|
+
"homepage": "https://github.com/kirull1/verify-grid#readme",
|
|
16
16
|
"bin": {
|
|
17
17
|
"qa": "./dist/cli.js"
|
|
18
18
|
},
|