verify-grid 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +704 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +562 -0
- package/dist/index.js.map +1 -0
- package/package.json +13 -2
package/dist/index.js
ADDED
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
// src/config/loader.ts
|
|
2
|
+
import { pathToFileURL } from "url";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { resolve, dirname } from "path";
|
|
5
|
+
|
|
6
|
+
// src/config/schema.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var TaskDefSchema = z.object({
|
|
9
|
+
name: z.string(),
|
|
10
|
+
command: z.string(),
|
|
11
|
+
args: z.array(z.string()).optional(),
|
|
12
|
+
shell: z.boolean().optional(),
|
|
13
|
+
timeout: z.number().positive().optional(),
|
|
14
|
+
env: z.record(z.string()).optional()
|
|
15
|
+
});
|
|
16
|
+
var ServiceDefSchema = z.object({
|
|
17
|
+
name: z.string(),
|
|
18
|
+
cwd: z.string(),
|
|
19
|
+
tasks: z.record(z.object({
|
|
20
|
+
command: z.string().optional(),
|
|
21
|
+
args: z.array(z.string()).optional(),
|
|
22
|
+
shell: z.boolean().optional(),
|
|
23
|
+
timeout: z.number().positive().optional(),
|
|
24
|
+
env: z.record(z.string()).optional()
|
|
25
|
+
})).optional(),
|
|
26
|
+
env: z.record(z.string()).optional()
|
|
27
|
+
});
|
|
28
|
+
var ConfigSchema = z.object({
|
|
29
|
+
services: z.array(ServiceDefSchema),
|
|
30
|
+
tasks: z.record(TaskDefSchema.omit({ name: true }))
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// src/config/loader.ts
|
|
34
|
+
var CONFIG_FILES = ["qa.config.ts", "qa.config.js", "qa.config.mjs"];
|
|
35
|
+
async function findConfig(startDir = process.cwd()) {
|
|
36
|
+
let currentDir = resolve(startDir);
|
|
37
|
+
const root = resolve("/");
|
|
38
|
+
while (currentDir !== root) {
|
|
39
|
+
for (const configFile of CONFIG_FILES) {
|
|
40
|
+
const configPath = resolve(currentDir, configFile);
|
|
41
|
+
if (existsSync(configPath)) {
|
|
42
|
+
return configPath;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
currentDir = dirname(currentDir);
|
|
46
|
+
}
|
|
47
|
+
for (const configFile of CONFIG_FILES) {
|
|
48
|
+
const configPath = resolve(root, configFile);
|
|
49
|
+
if (existsSync(configPath)) {
|
|
50
|
+
return configPath;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
async function loadConfig(configPath) {
|
|
56
|
+
const path = configPath || await findConfig();
|
|
57
|
+
if (!path) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"No config file found. Create qa.config.ts or qa.config.js in your project root."
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (!existsSync(path)) {
|
|
63
|
+
throw new Error(`Config file not found: ${path}`);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const fileUrl = pathToFileURL(path).href;
|
|
67
|
+
const module = await import(fileUrl);
|
|
68
|
+
const configData = module.default || module;
|
|
69
|
+
const result = ConfigSchema.safeParse(configData);
|
|
70
|
+
if (!result.success) {
|
|
71
|
+
const errors = result.error.errors.map(
|
|
72
|
+
(err) => ` - ${err.path.join(".")}: ${err.message}`
|
|
73
|
+
).join("\n");
|
|
74
|
+
throw new Error(`Invalid config:
|
|
75
|
+
${errors}`);
|
|
76
|
+
}
|
|
77
|
+
const serviceNames = /* @__PURE__ */ new Set();
|
|
78
|
+
for (const service of result.data.services) {
|
|
79
|
+
if (serviceNames.has(service.name)) {
|
|
80
|
+
throw new Error(`Duplicate service name: ${service.name}`);
|
|
81
|
+
}
|
|
82
|
+
serviceNames.add(service.name);
|
|
83
|
+
}
|
|
84
|
+
const config = {
|
|
85
|
+
services: result.data.services,
|
|
86
|
+
tasks: Object.entries(result.data.tasks).reduce((acc, [name, def]) => {
|
|
87
|
+
acc[name] = { name, ...def };
|
|
88
|
+
return acc;
|
|
89
|
+
}, {})
|
|
90
|
+
};
|
|
91
|
+
return config;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof Error) {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Failed to load config: ${String(error)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/runner/generator.ts
|
|
101
|
+
import { existsSync as existsSync2 } from "fs";
|
|
102
|
+
function generateJobs(config, taskNames, serviceNames) {
|
|
103
|
+
const jobs = [];
|
|
104
|
+
const resolvedServiceNames = serviceNames.includes("all") ? config.services.map((s) => s.name) : serviceNames;
|
|
105
|
+
const resolvedTaskNames = taskNames.includes("all") ? Object.keys(config.tasks) : taskNames;
|
|
106
|
+
for (const taskName of resolvedTaskNames) {
|
|
107
|
+
const globalTask = config.tasks[taskName];
|
|
108
|
+
if (!globalTask) {
|
|
109
|
+
throw new Error(`Unknown task: ${taskName}`);
|
|
110
|
+
}
|
|
111
|
+
for (const serviceName of resolvedServiceNames) {
|
|
112
|
+
const service = config.services.find((s) => s.name === serviceName);
|
|
113
|
+
if (!service) {
|
|
114
|
+
throw new Error(`Unknown service: ${serviceName}`);
|
|
115
|
+
}
|
|
116
|
+
if (!existsSync2(service.cwd)) {
|
|
117
|
+
jobs.push({
|
|
118
|
+
id: `${taskName}:${serviceName}`,
|
|
119
|
+
taskName,
|
|
120
|
+
serviceName,
|
|
121
|
+
cwd: service.cwd,
|
|
122
|
+
command: "",
|
|
123
|
+
status: "skipped"
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const taskOverride = service.tasks?.[taskName];
|
|
128
|
+
if (taskOverride && taskOverride.command === void 0 && !globalTask.command) {
|
|
129
|
+
jobs.push({
|
|
130
|
+
id: `${taskName}:${serviceName}`,
|
|
131
|
+
taskName,
|
|
132
|
+
serviceName,
|
|
133
|
+
cwd: service.cwd,
|
|
134
|
+
command: "",
|
|
135
|
+
status: "skipped"
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const mergedTask = {
|
|
140
|
+
name: taskName,
|
|
141
|
+
command: taskOverride?.command ?? globalTask.command,
|
|
142
|
+
args: taskOverride?.args ?? globalTask.args,
|
|
143
|
+
shell: taskOverride?.shell ?? globalTask.shell ?? true,
|
|
144
|
+
timeout: taskOverride?.timeout ?? globalTask.timeout,
|
|
145
|
+
env: {
|
|
146
|
+
...globalTask.env,
|
|
147
|
+
...service.env,
|
|
148
|
+
...taskOverride?.env
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
jobs.push({
|
|
152
|
+
id: `${taskName}:${serviceName}`,
|
|
153
|
+
taskName,
|
|
154
|
+
serviceName,
|
|
155
|
+
cwd: service.cwd,
|
|
156
|
+
command: mergedTask.command,
|
|
157
|
+
args: mergedTask.args,
|
|
158
|
+
shell: mergedTask.shell,
|
|
159
|
+
timeout: mergedTask.timeout,
|
|
160
|
+
env: mergedTask.env,
|
|
161
|
+
status: "queued"
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return jobs;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/runner/runner.ts
|
|
169
|
+
import PQueue from "p-queue";
|
|
170
|
+
import { EventEmitter } from "events";
|
|
171
|
+
|
|
172
|
+
// src/runner/executor.ts
|
|
173
|
+
import { execa } from "execa";
|
|
174
|
+
|
|
175
|
+
// src/utils/log.ts
|
|
176
|
+
var MAX_LOG_LINES = 200;
|
|
177
|
+
function extractErrorSnippet(stderr, stdout, maxLines = 10) {
|
|
178
|
+
const errorLog = stderr || stdout;
|
|
179
|
+
const lines = errorLog.split("\n").filter((line) => line.trim());
|
|
180
|
+
if (lines.length === 0) {
|
|
181
|
+
return "No error output";
|
|
182
|
+
}
|
|
183
|
+
const relevantLines = lines.slice(-maxLines);
|
|
184
|
+
return relevantLines.join("\n");
|
|
185
|
+
}
|
|
186
|
+
var LogBuffer = class {
|
|
187
|
+
lines = [];
|
|
188
|
+
maxLines;
|
|
189
|
+
constructor(maxLines = MAX_LOG_LINES) {
|
|
190
|
+
this.maxLines = maxLines;
|
|
191
|
+
}
|
|
192
|
+
append(data) {
|
|
193
|
+
const newLines = data.split("\n");
|
|
194
|
+
this.lines.push(...newLines);
|
|
195
|
+
if (this.lines.length > this.maxLines) {
|
|
196
|
+
this.lines = this.lines.slice(-this.maxLines);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
getContent() {
|
|
200
|
+
return this.lines.join("\n");
|
|
201
|
+
}
|
|
202
|
+
clear() {
|
|
203
|
+
this.lines = [];
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/runner/executor.ts
|
|
208
|
+
async function executeJob(job) {
|
|
209
|
+
const startTime = Date.now();
|
|
210
|
+
const stdoutBuffer = new LogBuffer();
|
|
211
|
+
const stderrBuffer = new LogBuffer();
|
|
212
|
+
try {
|
|
213
|
+
const args = job.args || [];
|
|
214
|
+
const options = {
|
|
215
|
+
cwd: job.cwd,
|
|
216
|
+
env: { ...process.env, ...job.env },
|
|
217
|
+
shell: job.shell,
|
|
218
|
+
timeout: job.timeout,
|
|
219
|
+
reject: false,
|
|
220
|
+
all: true
|
|
221
|
+
};
|
|
222
|
+
const result = await execa(job.command, args, options);
|
|
223
|
+
stdoutBuffer.append(result.stdout || "");
|
|
224
|
+
stderrBuffer.append(result.stderr || "");
|
|
225
|
+
const durationMs = Date.now() - startTime;
|
|
226
|
+
if (result.exitCode === 0) {
|
|
227
|
+
return {
|
|
228
|
+
job,
|
|
229
|
+
exitCode: 0,
|
|
230
|
+
stdout: stdoutBuffer.getContent(),
|
|
231
|
+
stderr: stderrBuffer.getContent(),
|
|
232
|
+
durationMs
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
job,
|
|
237
|
+
exitCode: result.exitCode,
|
|
238
|
+
stdout: stdoutBuffer.getContent(),
|
|
239
|
+
stderr: stderrBuffer.getContent(),
|
|
240
|
+
durationMs,
|
|
241
|
+
errorType: result.signal ? "signal" : "exit",
|
|
242
|
+
signal: result.signal || void 0
|
|
243
|
+
};
|
|
244
|
+
} catch (error) {
|
|
245
|
+
const durationMs = Date.now() - startTime;
|
|
246
|
+
const execaError = error;
|
|
247
|
+
if (execaError.stdout) stdoutBuffer.append(execaError.stdout);
|
|
248
|
+
if (execaError.stderr) stderrBuffer.append(execaError.stderr);
|
|
249
|
+
let errorType = "exit";
|
|
250
|
+
if (execaError.timedOut) {
|
|
251
|
+
errorType = "timeout";
|
|
252
|
+
} else if (execaError.signal) {
|
|
253
|
+
errorType = "signal";
|
|
254
|
+
} else if (execaError.exitCode === 127 || error.code === "ENOENT") {
|
|
255
|
+
errorType = "spawn";
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
job,
|
|
259
|
+
exitCode: execaError.exitCode,
|
|
260
|
+
stdout: stdoutBuffer.getContent(),
|
|
261
|
+
stderr: stderrBuffer.getContent(),
|
|
262
|
+
durationMs,
|
|
263
|
+
errorType,
|
|
264
|
+
signal: execaError.signal || void 0
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/runner/runner.ts
|
|
270
|
+
var Runner = class extends EventEmitter {
|
|
271
|
+
queue;
|
|
272
|
+
state;
|
|
273
|
+
shouldStop = false;
|
|
274
|
+
options;
|
|
275
|
+
constructor(options) {
|
|
276
|
+
super();
|
|
277
|
+
this.options = options;
|
|
278
|
+
this.queue = new PQueue({ concurrency: options.concurrency });
|
|
279
|
+
this.state = {
|
|
280
|
+
jobs: /* @__PURE__ */ new Map(),
|
|
281
|
+
results: /* @__PURE__ */ new Map(),
|
|
282
|
+
errors: [],
|
|
283
|
+
startedAt: Date.now()
|
|
284
|
+
};
|
|
285
|
+
process.on("SIGINT", () => this.handleInterrupt());
|
|
286
|
+
process.on("SIGTERM", () => this.handleInterrupt());
|
|
287
|
+
}
|
|
288
|
+
handleInterrupt() {
|
|
289
|
+
if (this.shouldStop) {
|
|
290
|
+
process.exit(130);
|
|
291
|
+
}
|
|
292
|
+
this.shouldStop = true;
|
|
293
|
+
this.queue.clear();
|
|
294
|
+
for (const [id, job] of this.state.jobs) {
|
|
295
|
+
if (job.status === "queued") {
|
|
296
|
+
job.status = "canceled";
|
|
297
|
+
this.state.jobs.set(id, job);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
this.emit("runCanceled");
|
|
301
|
+
}
|
|
302
|
+
async run(jobs) {
|
|
303
|
+
for (const job of jobs) {
|
|
304
|
+
this.state.jobs.set(job.id, job);
|
|
305
|
+
}
|
|
306
|
+
const executableJobs = jobs.filter((j) => j.status === "queued");
|
|
307
|
+
for (const job of executableJobs) {
|
|
308
|
+
if (this.shouldStop) {
|
|
309
|
+
job.status = "canceled";
|
|
310
|
+
this.state.jobs.set(job.id, job);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
this.queue.add(async () => {
|
|
314
|
+
if (this.shouldStop) {
|
|
315
|
+
job.status = "canceled";
|
|
316
|
+
this.state.jobs.set(job.id, job);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
job.status = "running";
|
|
320
|
+
job.startedAt = Date.now();
|
|
321
|
+
this.state.jobs.set(job.id, job);
|
|
322
|
+
this.emit("jobStart", job);
|
|
323
|
+
const result = await executeJob(job);
|
|
324
|
+
job.status = result.exitCode === 0 ? "done" : "error";
|
|
325
|
+
job.endedAt = Date.now();
|
|
326
|
+
this.state.jobs.set(job.id, job);
|
|
327
|
+
this.state.results.set(job.id, result);
|
|
328
|
+
if (result.exitCode !== 0) {
|
|
329
|
+
this.emit("jobError", result);
|
|
330
|
+
if (this.options.failFast) {
|
|
331
|
+
this.shouldStop = true;
|
|
332
|
+
this.queue.clear();
|
|
333
|
+
for (const [id, j] of this.state.jobs) {
|
|
334
|
+
if (j.status === "queued") {
|
|
335
|
+
j.status = "canceled";
|
|
336
|
+
this.state.jobs.set(id, j);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
this.emit("jobComplete", result);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
await this.queue.onIdle();
|
|
346
|
+
this.state.endedAt = Date.now();
|
|
347
|
+
this.emit("runComplete", this.state);
|
|
348
|
+
return this.state;
|
|
349
|
+
}
|
|
350
|
+
getState() {
|
|
351
|
+
return this.state;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// src/render/table.ts
|
|
356
|
+
import Table from "cli-table3";
|
|
357
|
+
|
|
358
|
+
// src/utils/colors.ts
|
|
359
|
+
import kleur from "kleur";
|
|
360
|
+
var colorsEnabled = true;
|
|
361
|
+
function getStatusColor(status) {
|
|
362
|
+
if (!colorsEnabled) {
|
|
363
|
+
return (text) => text;
|
|
364
|
+
}
|
|
365
|
+
switch (status) {
|
|
366
|
+
case "done":
|
|
367
|
+
return kleur.green;
|
|
368
|
+
case "error":
|
|
369
|
+
return kleur.red;
|
|
370
|
+
case "running":
|
|
371
|
+
return kleur.blue;
|
|
372
|
+
case "queued":
|
|
373
|
+
return kleur.yellow;
|
|
374
|
+
case "skipped":
|
|
375
|
+
case "canceled":
|
|
376
|
+
return kleur.gray;
|
|
377
|
+
default:
|
|
378
|
+
return (text) => text;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
var colors = {
|
|
382
|
+
error: kleur.red,
|
|
383
|
+
success: kleur.green,
|
|
384
|
+
warning: kleur.yellow,
|
|
385
|
+
info: kleur.blue,
|
|
386
|
+
dim: kleur.gray,
|
|
387
|
+
bold: kleur.bold
|
|
388
|
+
};
|
|
389
|
+
var green = kleur.green;
|
|
390
|
+
var yellow = kleur.yellow;
|
|
391
|
+
var red = kleur.red;
|
|
392
|
+
var cyan = kleur.cyan;
|
|
393
|
+
var dim = kleur.gray;
|
|
394
|
+
|
|
395
|
+
// src/utils/spinner.ts
|
|
396
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
397
|
+
var Spinner = class {
|
|
398
|
+
frame = 0;
|
|
399
|
+
next() {
|
|
400
|
+
const char = SPINNER_FRAMES[this.frame];
|
|
401
|
+
this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
|
|
402
|
+
return char || "\u280B";
|
|
403
|
+
}
|
|
404
|
+
reset() {
|
|
405
|
+
this.frame = 0;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// src/render/table.ts
|
|
410
|
+
var spinner = new Spinner();
|
|
411
|
+
function renderTable(state) {
|
|
412
|
+
const table = new Table({
|
|
413
|
+
head: ["Task", "Service", "Status"],
|
|
414
|
+
style: {
|
|
415
|
+
head: [],
|
|
416
|
+
border: []
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
const taskGroups = groupJobsByTask(state);
|
|
420
|
+
for (const [taskName, jobs] of taskGroups) {
|
|
421
|
+
let isFirstRow = true;
|
|
422
|
+
for (const job of jobs) {
|
|
423
|
+
const statusColor = getStatusColor(job.status);
|
|
424
|
+
let statusText = job.status;
|
|
425
|
+
if (job.status === "running") {
|
|
426
|
+
statusText = `${spinner.next()} ${job.status}`;
|
|
427
|
+
} else if (job.status === "queued") {
|
|
428
|
+
statusText = `\u23F3 ${job.status}`;
|
|
429
|
+
}
|
|
430
|
+
const coloredStatus = statusColor(statusText);
|
|
431
|
+
table.push([
|
|
432
|
+
isFirstRow ? taskName : "",
|
|
433
|
+
job.serviceName,
|
|
434
|
+
coloredStatus
|
|
435
|
+
]);
|
|
436
|
+
isFirstRow = false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return table.toString();
|
|
440
|
+
}
|
|
441
|
+
function renderMinimal(state) {
|
|
442
|
+
const lines = [];
|
|
443
|
+
for (const [, job] of state.jobs) {
|
|
444
|
+
const statusColor = getStatusColor(job.status);
|
|
445
|
+
const statusText = statusColor(job.status);
|
|
446
|
+
lines.push(`${job.taskName} ${job.serviceName} ${statusText}`);
|
|
447
|
+
}
|
|
448
|
+
return lines.join("\n");
|
|
449
|
+
}
|
|
450
|
+
function renderJson(state) {
|
|
451
|
+
const matrix = {};
|
|
452
|
+
for (const [, job] of state.jobs) {
|
|
453
|
+
if (!matrix[job.taskName]) {
|
|
454
|
+
matrix[job.taskName] = {};
|
|
455
|
+
}
|
|
456
|
+
const result = state.results.get(job.id);
|
|
457
|
+
const taskMatrix = matrix[job.taskName];
|
|
458
|
+
if (taskMatrix) {
|
|
459
|
+
taskMatrix[job.serviceName] = {
|
|
460
|
+
status: job.status,
|
|
461
|
+
exitCode: result?.exitCode,
|
|
462
|
+
durationMs: result?.durationMs
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const errors = Array.from(state.results.values()).filter((r) => r.job.status === "error").map((r) => ({
|
|
467
|
+
taskName: r.job.taskName,
|
|
468
|
+
serviceName: r.job.serviceName,
|
|
469
|
+
exitCode: r.exitCode,
|
|
470
|
+
durationMs: r.durationMs,
|
|
471
|
+
errorType: r.errorType
|
|
472
|
+
}));
|
|
473
|
+
const output = {
|
|
474
|
+
startedAt: state.startedAt,
|
|
475
|
+
endedAt: state.endedAt,
|
|
476
|
+
durationMs: state.endedAt ? state.endedAt - state.startedAt : void 0,
|
|
477
|
+
matrix,
|
|
478
|
+
errors
|
|
479
|
+
};
|
|
480
|
+
return JSON.stringify(output, null, 2);
|
|
481
|
+
}
|
|
482
|
+
function groupJobsByTask(state) {
|
|
483
|
+
const groups = /* @__PURE__ */ new Map();
|
|
484
|
+
for (const [, job] of state.jobs) {
|
|
485
|
+
if (!groups.has(job.taskName)) {
|
|
486
|
+
groups.set(job.taskName, []);
|
|
487
|
+
}
|
|
488
|
+
groups.get(job.taskName).push(job);
|
|
489
|
+
}
|
|
490
|
+
return groups;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/report/aggregator.ts
|
|
494
|
+
function aggregateErrors(state) {
|
|
495
|
+
const errors = [];
|
|
496
|
+
for (const [, result] of state.results) {
|
|
497
|
+
if (result.job.status === "error") {
|
|
498
|
+
const errorMessage = getErrorMessage(result);
|
|
499
|
+
const logSnippet = extractErrorSnippet(result.stderr, result.stdout);
|
|
500
|
+
errors.push({
|
|
501
|
+
taskName: result.job.taskName,
|
|
502
|
+
serviceName: result.job.serviceName,
|
|
503
|
+
exitCode: result.exitCode,
|
|
504
|
+
durationMs: result.durationMs,
|
|
505
|
+
message: errorMessage,
|
|
506
|
+
logSnippet,
|
|
507
|
+
errorType: result.errorType
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
errors.sort((a, b) => {
|
|
512
|
+
if (a.taskName !== b.taskName) {
|
|
513
|
+
return a.taskName.localeCompare(b.taskName);
|
|
514
|
+
}
|
|
515
|
+
return a.serviceName.localeCompare(b.serviceName);
|
|
516
|
+
});
|
|
517
|
+
return errors;
|
|
518
|
+
}
|
|
519
|
+
function getErrorMessage(result) {
|
|
520
|
+
if (result.errorType === "timeout") {
|
|
521
|
+
return `Timeout after ${result.durationMs}ms`;
|
|
522
|
+
}
|
|
523
|
+
if (result.errorType === "spawn") {
|
|
524
|
+
return `Command not found: ${result.job.command}`;
|
|
525
|
+
}
|
|
526
|
+
if (result.errorType === "signal") {
|
|
527
|
+
return `Killed by signal: ${result.signal}`;
|
|
528
|
+
}
|
|
529
|
+
return `Exit code ${result.exitCode}`;
|
|
530
|
+
}
|
|
531
|
+
function formatErrors(errors) {
|
|
532
|
+
if (errors.length === 0) {
|
|
533
|
+
return "";
|
|
534
|
+
}
|
|
535
|
+
const lines = ["", "Errors:"];
|
|
536
|
+
for (const error of errors) {
|
|
537
|
+
lines.push(`- ${error.serviceName} has error in ${error.taskName} test:`);
|
|
538
|
+
const snippet = error.logSnippet.trim();
|
|
539
|
+
if (snippet) {
|
|
540
|
+
const snippetLines = snippet.split("\n").slice(0, 10);
|
|
541
|
+
for (const line of snippetLines) {
|
|
542
|
+
lines.push(` ${line}`);
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
lines.push(` ${error.message}`);
|
|
546
|
+
}
|
|
547
|
+
lines.push("");
|
|
548
|
+
}
|
|
549
|
+
return lines.join("\n");
|
|
550
|
+
}
|
|
551
|
+
export {
|
|
552
|
+
Runner,
|
|
553
|
+
aggregateErrors,
|
|
554
|
+
findConfig,
|
|
555
|
+
formatErrors,
|
|
556
|
+
generateJobs,
|
|
557
|
+
loadConfig,
|
|
558
|
+
renderJson,
|
|
559
|
+
renderMinimal,
|
|
560
|
+
renderTable
|
|
561
|
+
};
|
|
562
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "verify-grid",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Run matrix of tasks across services with parallel execution and status reporting",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/your-username/verify-grid.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/your-username/verify-grid/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/your-username/verify-grid#readme",
|
|
6
16
|
"bin": {
|
|
7
17
|
"qa": "./dist/cli.js"
|
|
8
18
|
},
|
|
@@ -13,7 +23,8 @@
|
|
|
13
23
|
}
|
|
14
24
|
},
|
|
15
25
|
"files": [
|
|
16
|
-
"dist"
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
17
28
|
],
|
|
18
29
|
"scripts": {
|
|
19
30
|
"build": "tsup",
|