verify-grid 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,6 +26,7 @@
26
26
  - 📊 **Multiple Formats** - Table, minimal, or JSON output
27
27
  - 🎨 **Horizontal Layout** - Optimized for terminal space (services × tasks)
28
28
  - 🔧 **Task Overrides** - Customize tasks per service
29
+ - ✨ **Optional Tasks** - Skip tasks that don't apply to certain services
29
30
  - ⏱️ **Timeout Support** - Set time limits for tasks
30
31
  - 🌍 **Environment Variables** - Per-task and per-service configuration
31
32
  - 🎲 **Random Execution** - Test robustness with randomized order
@@ -128,6 +129,7 @@ qa run --random
128
129
  shell: false, // Run in shell (optional)
129
130
  timeout: 30000, // Timeout in ms (optional)
130
131
  priority: 10, // Higher = earlier (optional)
132
+ optional: false, // Skip if not applicable (optional)
131
133
  env: { // Environment variables (optional)
132
134
  NODE_ENV: 'test'
133
135
  }
@@ -145,6 +147,7 @@ qa run --random
145
147
  | `shell` | `boolean` | ❌ | Run in shell (default: false) |
146
148
  | `timeout` | `number` | ❌ | Timeout in milliseconds |
147
149
  | `priority` | `number` | ❌ | Execution priority (higher first, default: 0) |
150
+ | `optional` | `boolean` | ❌ | Skip task if command not defined (default: false) |
148
151
  | `env` | `Record<string, string>` | ❌ | Environment variables |
149
152
 
150
153
  ### Service Definition
@@ -204,6 +207,10 @@ export default {
204
207
  command: 'tsc',
205
208
  args: ['--noEmit'],
206
209
  },
210
+ screenshot: {
211
+ command: '',
212
+ optional: true, // Only runs if service defines it
213
+ },
207
214
  },
208
215
  services: [
209
216
  {
@@ -221,6 +228,12 @@ export default {
221
228
  name: 'web',
222
229
  cwd: './services/web',
223
230
  env: { PORT: '3001' },
231
+ tasks: {
232
+ screenshot: {
233
+ command: 'npm',
234
+ args: ['run', 'screenshot'],
235
+ },
236
+ },
224
237
  },
225
238
  {
226
239
  name: 'shared',
package/dist/cli.js CHANGED
@@ -18,6 +18,7 @@ var TaskDefSchema = z.object({
18
18
  shell: z.boolean().optional(),
19
19
  timeout: z.number().positive().optional(),
20
20
  priority: z.number().optional(),
21
+ optional: z.boolean().optional(),
21
22
  env: z.record(z.string()).optional()
22
23
  });
23
24
  var ServiceDefSchema = z.object({
@@ -29,6 +30,7 @@ var ServiceDefSchema = z.object({
29
30
  shell: z.boolean().optional(),
30
31
  timeout: z.number().positive().optional(),
31
32
  priority: z.number().optional(),
33
+ optional: z.boolean().optional(),
32
34
  env: z.record(z.string()).optional()
33
35
  })).optional(),
34
36
  env: z.record(z.string()).optional()
@@ -133,7 +135,12 @@ function generateJobs(config, taskNames, serviceNames) {
133
135
  continue;
134
136
  }
135
137
  const taskOverride = service.tasks?.[taskName];
136
- if (taskOverride && taskOverride.command === void 0 && !globalTask.command) {
138
+ const isOptional = taskOverride?.optional ?? globalTask.optional ?? false;
139
+ const finalCommand = taskOverride?.command ?? globalTask.command;
140
+ if (!finalCommand) {
141
+ if (isOptional) {
142
+ continue;
143
+ }
137
144
  jobs.push({
138
145
  id: `${taskName}:${serviceName}`,
139
146
  taskName,
@@ -146,11 +153,12 @@ function generateJobs(config, taskNames, serviceNames) {
146
153
  }
147
154
  const mergedTask = {
148
155
  name: taskName,
149
- command: taskOverride?.command ?? globalTask.command,
156
+ command: finalCommand,
150
157
  args: taskOverride?.args ?? globalTask.args,
151
158
  shell: taskOverride?.shell ?? globalTask.shell ?? true,
152
159
  timeout: taskOverride?.timeout ?? globalTask.timeout,
153
160
  priority: taskOverride?.priority ?? globalTask.priority,
161
+ optional: isOptional,
154
162
  env: {
155
163
  ...globalTask.env,
156
164
  ...service.env,
@@ -167,6 +175,7 @@ function generateJobs(config, taskNames, serviceNames) {
167
175
  shell: mergedTask.shell,
168
176
  timeout: mergedTask.timeout,
169
177
  priority: mergedTask.priority,
178
+ optional: mergedTask.optional,
170
179
  env: mergedTask.env,
171
180
  status: "queued"
172
181
  });
@@ -554,6 +563,10 @@ var WatchRenderer = class {
554
563
  clearInterval(this.updateInterval);
555
564
  this.updateInterval = null;
556
565
  }
566
+ if (this.state) {
567
+ const output = renderTable(this.state);
568
+ logUpdate(output);
569
+ }
557
570
  logUpdate.done();
558
571
  }
559
572
  render() {
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('--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 && !runOptions.watch) {\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 (runOptions.watch && !runOptions.quiet) {\n const errors = aggregateErrors(state);\n if (errors.length > 0) {\n console.log('\\n' + 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"]}
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 && !runOptions.watch) {\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 (runOptions.watch && !runOptions.quiet) {\n const errors = aggregateErrors(state);\n if (errors.length > 0) {\n console.log('\\n' + 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 optional: z.boolean().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 optional: z.boolean().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 const isOptional = taskOverride?.optional ?? globalTask.optional ?? false;\n \n const finalCommand = taskOverride?.command ?? globalTask.command;\n \n if (!finalCommand) {\n if (isOptional) {\n continue;\n }\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: finalCommand,\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 optional: isOptional,\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 optional: mergedTask.optional,\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 \n if (this.state) {\n const output = renderTable(this.state);\n logUpdate(output);\n }\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
@@ -9,6 +9,7 @@ interface TaskDef {
9
9
  timeout?: number;
10
10
  env?: Record<string, string>;
11
11
  priority?: number;
12
+ optional?: boolean;
12
13
  }
13
14
  interface ServiceDef {
14
15
  name: string;
@@ -27,6 +28,7 @@ interface Job {
27
28
  timeout?: number;
28
29
  env?: Record<string, string>;
29
30
  priority?: number;
31
+ optional?: boolean;
30
32
  status: JobStatus;
31
33
  startedAt?: number;
32
34
  endedAt?: number;
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ var TaskDefSchema = z.object({
12
12
  shell: z.boolean().optional(),
13
13
  timeout: z.number().positive().optional(),
14
14
  priority: z.number().optional(),
15
+ optional: z.boolean().optional(),
15
16
  env: z.record(z.string()).optional()
16
17
  });
17
18
  var ServiceDefSchema = z.object({
@@ -23,6 +24,7 @@ var ServiceDefSchema = z.object({
23
24
  shell: z.boolean().optional(),
24
25
  timeout: z.number().positive().optional(),
25
26
  priority: z.number().optional(),
27
+ optional: z.boolean().optional(),
26
28
  env: z.record(z.string()).optional()
27
29
  })).optional(),
28
30
  env: z.record(z.string()).optional()
@@ -127,7 +129,12 @@ function generateJobs(config, taskNames, serviceNames) {
127
129
  continue;
128
130
  }
129
131
  const taskOverride = service.tasks?.[taskName];
130
- if (taskOverride && taskOverride.command === void 0 && !globalTask.command) {
132
+ const isOptional = taskOverride?.optional ?? globalTask.optional ?? false;
133
+ const finalCommand = taskOverride?.command ?? globalTask.command;
134
+ if (!finalCommand) {
135
+ if (isOptional) {
136
+ continue;
137
+ }
131
138
  jobs.push({
132
139
  id: `${taskName}:${serviceName}`,
133
140
  taskName,
@@ -140,11 +147,12 @@ function generateJobs(config, taskNames, serviceNames) {
140
147
  }
141
148
  const mergedTask = {
142
149
  name: taskName,
143
- command: taskOverride?.command ?? globalTask.command,
150
+ command: finalCommand,
144
151
  args: taskOverride?.args ?? globalTask.args,
145
152
  shell: taskOverride?.shell ?? globalTask.shell ?? true,
146
153
  timeout: taskOverride?.timeout ?? globalTask.timeout,
147
154
  priority: taskOverride?.priority ?? globalTask.priority,
155
+ optional: isOptional,
148
156
  env: {
149
157
  ...globalTask.env,
150
158
  ...service.env,
@@ -161,6 +169,7 @@ function generateJobs(config, taskNames, serviceNames) {
161
169
  shell: mergedTask.shell,
162
170
  timeout: mergedTask.timeout,
163
171
  priority: mergedTask.priority,
172
+ optional: mergedTask.optional,
164
173
  env: mergedTask.env,
165
174
  status: "queued"
166
175
  });
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 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"]}
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 optional: z.boolean().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 optional: z.boolean().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 const isOptional = taskOverride?.optional ?? globalTask.optional ?? false;\n \n const finalCommand = taskOverride?.command ?? globalTask.command;\n \n if (!finalCommand) {\n if (isOptional) {\n continue;\n }\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: finalCommand,\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 optional: isOptional,\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 optional: mergedTask.optional,\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,6 +1,6 @@
1
1
  {
2
2
  "name": "verify-grid",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Run matrix of tasks across services with parallel execution and status reporting",
5
5
  "type": "module",
6
6
  "author": "",