Hierarchical verification runner with parallel execution and terse output
npm install @halecraft/verifyQuickly check if your nodejs project is in an OK state.
Or, more technically--verify is a hierarchical verification runner with parallel execution and terse output.
``bash`
pnpm add -D @halecraft/verifyor
npm install -D @halecraft/verifyor
yarn add -D @halecraft/verify
The easiest way to get started is to use the --init flag:
`bash`Interactive mode - select which tasks to include
npx @halecraft/verify --init
The init command will:
1. Scan your package.json for verification-related scripts (lint, test, typecheck, build, etc.)verify.config.ts
2. Present an interactive checkbox UI to select which tasks to include
3. Generate a file with your selections
Create a verify.config.ts file in your project root:
`typescript
import { defineConfig } from "@halecraft/verify";
export default defineConfig({
tasks: [
{ key: "format", run: "biome check ." },
{ key: "types", run: "tsc --noEmit" },
{ key: "test", run: "vitest run" },
],
});
`
Note: Commands automatically have access to binaries in node_modules/.bin directories. You can write run: "biome check ." instead of run: "./node_modules/.bin/biome check .". This works in monorepos too—verify walks up the directory tree to find all node_modules/.bin directories, just like npm/pnpm/yarn do when running package.json scripts.
`bashRun all tasks
pnpm exec verify
Or you can add
"verify": "verify" to package.json scripts and run:`bash
Run all tasks
pnpm verify
`Configuration
$3
Each task in verify.config.ts can have the following properties:
`typescript
interface VerificationNode {
// Unique key for this task (used in CLI filtering)
key: string; // Human-readable name (optional)
name?: string;
// Command to run (leaf nodes only)
// Supports: string, object with cmd/args/cwd/env/timeout
run?:
| string
| {
cmd: string;
args: string[];
cwd?: string;
env?: Record;
timeout?: number;
};
// Child tasks (for grouping)
children?: VerificationNode[];
// Execution strategy for children: 'parallel' | 'sequential' | 'fail-fast'
strategy?: ExecutionStrategy;
// Parser ID for output parsing (auto-detected if not specified)
parser?: string;
// Tasks that must pass for this task's failure to be reported
reportingDependsOn?: string[];
// Timeout in milliseconds (for string commands)
timeout?: number;
// Environment variables for this task and its children
// Set to null to unset an inherited variable
env?: Record;
// Custom success message template (optional)
successLabel?: string;
// Custom failure message template (optional)
failureLabel?: string;
}
`$3
When a syntax error occurs, multiple tools often report the same underlying issue (Biome, tsc, esbuild all complaining about the same missing comma). The
reportingDependsOn option reduces this noise by suppressing redundant failure output.`typescript
import { defineConfig } from "@halecraft/verify";export default defineConfig({
tasks: [
{ key: "format", run: "biome check ." },
{ key: "types", run: "tsc --noEmit", reportingDependsOn: ["format"] },
{ key: "logic", run: "vitest run", reportingDependsOn: ["format"] },
{ key: "build", run: "tsup", reportingDependsOn: ["format"] },
],
});
`How it works:
- All tasks still execute in parallel (no speed regression)
- When a dependency fails (e.g.,
format), dependent tasks are terminated early for faster feedback
- Dependent tasks that also fail are marked as "suppressed"
- Only the root cause failure shows detailed logs
- Suppressed tasks show ⊘ suppressed instead of ✗ failedBefore (noisy):
`
✗ format (syntax error at line 14)
✗ types (syntax error at line 14)
✗ logic (syntax error at line 14)
✗ build (syntax error at line 14)==== FORMAT FAIL ====
[50 lines of biome output]
==== TYPES FAIL ====
[20 lines of tsc output]
==== LOGIC FAIL ====
[30 lines of vitest output]
==== BUILD FAIL ====
[30 lines of tsup output]
`After (clean):
`
✗ format (syntax error at line 14)
⊘ types (suppressed - format failed)
⊘ logic (suppressed - format failed)
⊘ build (suppressed - format failed)==== FORMAT FAIL ====
[50 lines of biome output]
== verification: Failed ==
`Note: When using
verify --init, the generated config automatically adds reportingDependsOn: ["format"] to types, logic, and build tasks when a format task is detected.$3
Group related tasks together:
`typescript
import { defineConfig } from "@halecraft/verify";export default defineConfig({
tasks: [
{ key: "format", run: "pnpm lint" },
{ key: "types", run: "pnpm typecheck" },
{
key: "logic",
children: [
{ key: "unit", run: "vitest run" },
{ key: "e2e", run: "playwright test" },
],
},
],
});
`Run nested tasks with colon notation:
`bash
npx verify logic:unit
`$3
Control how child tasks are executed:
`typescript
{
key: 'tests',
strategy: 'fail-fast', // Stop on first failure
children: [
{ key: 'unit', run: 'vitest run' },
{ key: 'integration', run: 'pnpm test:integration' },
],
}
`-
parallel (default): Run all tasks simultaneously
- sequential: Run tasks one after another
- fail-fast: Run sequentially, stop on first failure$3
Prevent hung processes by setting a timeout (in milliseconds):
`typescript
import { defineConfig } from "@halecraft/verify";export default defineConfig({
tasks: [
// String command with timeout
{ key: "test", run: "vitest run", timeout: 60000 }, // 60 seconds
// Object command with timeout
{
key: "build",
run: {
cmd: "tsup",
args: [],
timeout: 120000, // 2 minutes
},
},
],
});
`When a command exceeds its timeout:
- The process is killed with
SIGTERM (including child processes)
- The task is marked as failed with timedOut: true
- The summary shows: task: timed out after 60000msNote: For object commands, the
timeout on the command takes precedence over the node-level timeout.$3
Set environment variables at the config level (applies to all tasks) or at the task level (inherits to children):
`typescript
import { defineConfig } from "@halecraft/verify";export default defineConfig({
// Global env vars - applied to all tasks
env: {
NO_COLOR: "1", // Recommended: disable colors for cleaner output parsing
CI: "true",
},
tasks: [
{ key: "format", run: "biome check ." },
{
key: "test",
// Enable colors for test output by unsetting NO_COLOR
env: { NO_COLOR: null },
children: [
{ key: "unit", run: "vitest run" },
{
key: "e2e",
run: "playwright test",
// E2E-specific env (still inherits CI: "true")
env: { PLAYWRIGHT_BROWSERS_PATH: "0" },
},
],
},
],
});
`Environment merge order (most specific wins):
1.
process.env - System environment
2. config.env - Global config-level
3. Parent task env - Inherited from parent tasks
4. Node env - Current task
5. Command env - VerificationCommand object onlyUnsetting variables: Set a value to
null to explicitly unset an inherited variable:`typescript
{
key: "test",
env: { NO_COLOR: null }, // Re-enables colors for this task
run: "vitest run",
}
`Note: Generated configs (via
--init) include env: { NO_COLOR: "1" } by default to ensure consistent output parsing.CLI Options
`
Usage:
verify [flags...] [task] [--] [passthrough...]Flags:
--json Output results as JSON
--verbose, -v Show all task output
--quiet, -q Show only final result
--top-level, -t Show only top-level tasks (hide descendants)
--no-tty Force sequential output (disable live dashboard)
--logs=MODE Log verbosity: all, failed, none (default: failed)
--config, -c PATH Path to config file (or output path for --init)
--init Initialize a new verify.config.ts file
--force Overwrite existing config file (with --init)
--yes, -y Skip interactive prompts, auto-accept detected tasks
--help, -h Show this help message
`$3
You can pass arguments directly to the underlying command using
-- (double-dash):`bash
Run a specific vitest test
verify logic -- -t "should handle edge case"Run with coverage
verify logic -- --coverageMultiple passthrough args
verify logic -- -t "foo" --reporter=verboseCombine with verify flags
verify logic --verbose -- -t "foo"
`Requirements:
- Passthrough arguments require exactly one task filter
- The task must be a leaf node (has a
run command)
- Arguments are appended to the command stringHow it works:
- For string commands: args are shell-escaped and appended to the command
- For object commands: args are appended to the
args array$3
-
0 - All tasks passed
- 1 - Some tasks failed
- 2 - Configuration/usage error (invalid task name, missing config)$3
Verify validates task names before running any tasks. If you specify a task that doesn't exist, you'll get a helpful error:
`bash
$ verify test
Error: Task "test" not found.Did you mean "types:tsc"?
Available tasks:
format
logic
types
types:tsc
types:tsgo
build
`Child task shortcuts: You can use just the child key if it's unambiguous:
`bash
$ verify tsc
→ Resolving "tsc" to "types:tsc"
✓ verified types:tsc
`If the shortcut is ambiguous (multiple tasks have the same child key), you'll get an error listing the options:
`bash
$ verify test # if both unit:test and e2e:test exist
Error: Task "test" is ambiguous.Matches multiple tasks:
unit:test
e2e:test
`Programmatic API
`typescript
import { verify, defineConfig } from "@halecraft/verify";const config = defineConfig({
tasks: [{ key: "test", run: "vitest run" }],
// Optional: set default options for this config
options: {
logs: "failed",
},
});
const result = await verify(config, {
// All options (CLI options can override config defaults)
logs: "failed", // "all" | "failed" | "none"
format: "human", // "human" | "json"
filter: ["test"], // Filter to specific task paths
cwd: process.cwd(), // Working directory
noColor: false, // Disable colors
topLevelOnly: false, // Show only top-level tasks
noTty: false, // Force sequential output
});
console.log(result.ok ? "All passed!" : "Some failed");
`$3
The
verify() function returns a VerifyResult object:`typescript
interface VerifyResult {
ok: boolean; // Whether all tasks passed
startedAt: string; // ISO timestamp when run started
finishedAt: string; // ISO timestamp when run finished
durationMs: number; // Total duration in milliseconds
tasks: TaskResult[]; // Individual task results
}interface TaskResult {
key: string; // Task key
path: string; // Full path (e.g., "logic:unit")
ok: boolean; // Whether the task passed
code: number; // Exit code
durationMs: number; // Duration in milliseconds
output: string; // Raw output
summaryLine: string; // Parsed summary
suppressed?: boolean; // True if output was suppressed
suppressedBy?: string; // Path of dependency that caused suppression
timedOut?: boolean; // True if task exceeded its timeout
children?: TaskResult[]; // Child results (for group nodes)
}
`Output Parsers
Built-in parsers for common tools:
- vitest - Vitest/Jest test runner
- tsc - TypeScript compiler (tsc/tsgo)
- biome - Biome/ESLint linter/formatter
- gotest - Go test runner
- generic - Fallback for unknown tools
Parsers automatically extract metrics (passed/failed counts, duration) and provide concise summaries.
$3
Use
parsers for type-safe parser references instead of magic strings:`typescript
import { defineConfig, parsers } from "@halecraft/verify";export default defineConfig({
tasks: [
{ key: "test", run: "vitest run", parser: parsers.vitest },
{ key: "types", run: "tsc --noEmit", parser: parsers.tsc },
{ key: "lint", run: "biome check .", parser: parsers.biome },
],
});
`Available constants:
-
parsers.vitest - "vitest"
- parsers.tsc - "tsc"
- parsers.biome - "biome"
- parsers.gotest - "gotest"
- parsers.generic - "generic"`MIT