@halecraft/verify
v1.2.0
Published
Hierarchical verification runner with parallel execution and terse output
Maintainers
Readme
@halecraft/verify
Quickly 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.
Installation
pnpm add -D @halecraft/verify
# or
npm install -D @halecraft/verify
# or
yarn add -D @halecraft/verifyQuick Start
Initialize a Config
The easiest way to get started is to use the --init flag:
# Interactive mode - select which tasks to include
npx @halecraft/verify --initThe init command will:
- Scan your
package.jsonfor verification-related scripts (lint, test, typecheck, build, etc.) - Present an interactive checkbox UI to select which tasks to include
- Generate a
verify.config.tsfile with your selections
Manual Configuration
Create a verify.config.ts file in your project root:
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.
Run Verification
# Run all tasks
pnpm exec verify
# Run specific task
pnpm exec verify format
# Run nested task with full path
pnpm exec verify types:tsc
# Run nested task with shortcut (if unambiguous)
pnpm exec verify tsc
# → Resolving "tsc" to "types:tsc"
# Pass arguments to underlying command
pnpm exec verify logic -- -t "specific test name"
# Run with verbose output
pnpm exec verify --verbose
# Output JSON (for CI)
pnpm exec verify --jsonOr you can add "verify": "verify" to package.json scripts and run:
# Run all tasks
pnpm verifyConfiguration
Task Definition
Each task in verify.config.ts can have the following properties:
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<string, string | null>;
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<string, string | null>;
// Custom success message template (optional)
successLabel?: string;
// Custom failure message template (optional)
failureLabel?: string;
}Smart Output Suppression with reportingDependsOn
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.
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
⊘ suppressedinstead of✗ failed
Before (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.
Nested Tasks
Group related tasks together:
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:
npx verify logic:unitExecution Strategies
Control how child tasks are executed:
{
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 simultaneouslysequential: Run tasks one after anotherfail-fast: Run sequentially, stop on first failure
Command Timeouts
Prevent hung processes by setting a timeout (in milliseconds):
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 60000ms
Note: For object commands, the timeout on the command takes precedence over the node-level timeout.
Environment Variables
Set environment variables at the config level (applies to all tasks) or at the task level (inherits to children):
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):
process.env- System environmentconfig.env- Global config-level- Parent task
env- Inherited from parent tasks - Node
env- Current task - Command
env- VerificationCommand object only
Unsetting variables: Set a value to null to explicitly unset an inherited variable:
{
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 messagePassthrough Arguments
You can pass arguments directly to the underlying command using -- (double-dash):
# Run a specific vitest test
verify logic -- -t "should handle edge case"
# Run with coverage
verify logic -- --coverage
# Multiple passthrough args
verify logic -- -t "foo" --reporter=verbose
# Combine 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
runcommand) - Arguments are appended to the command string
How it works:
- For string commands: args are shell-escaped and appended to the command
- For object commands: args are appended to the
argsarray
Exit Codes
0- All tasks passed1- Some tasks failed2- Configuration/usage error (invalid task name, missing config)
Task Name Validation
Verify validates task names before running any tasks. If you specify a task that doesn't exist, you'll get a helpful error:
$ verify test
Error: Task "test" not found.
Did you mean "types:tsc"?
Available tasks:
format
logic
types
types:tsc
types:tsgo
buildChild task shortcuts: You can use just the child key if it's unambiguous:
$ verify tsc
→ Resolving "tsc" to "types:tsc"
✓ verified types:tscIf the shortcut is ambiguous (multiple tasks have the same child key), you'll get an error listing the options:
$ verify test # if both unit:test and e2e:test exist
Error: Task "test" is ambiguous.
Matches multiple tasks:
unit:test
e2e:testProgrammatic API
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");VerifyResult
The verify() function returns a VerifyResult object:
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.
Parser ID Constants
Use parsers for type-safe parser references instead of magic strings:
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"
License
MIT
