npm package: @halecraft/verify
Description: Hierarchical verification runner with parallel execution and terse output
Author: Duane Johnson
Latest version: 1.3.0

# @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

```bash
pnpm add -D @halecraft/verify
# or
npm install -D @halecraft/verify
# or
yarn add -D @halecraft/verify
```

## Quick Start

### Initialize a Config

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.)
2. Present an interactive checkbox UI to select which tasks to include
3. Generate a `verify.config.ts` file with your selections

### Manual Configuration

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.

### Run Verification

```bash
# 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 --json
```

Or you can add `"verify": "verify"` to package.json scripts and run:

```bash
# Run all tasks
pnpm verify
```

## Configuration

### Task Definition

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<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;
}
```

### Dependency-Aware Failure Reporting 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 marking dependent failures as blocked.

```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 "blocked"
- Only the root cause failure shows detailed logs
- Blocked tasks show `⊘ blocked` instead of `✗ failed`

**Before (noisy):**

```
✗ format failed (syntax error at line 14)
✗ types failed (syntax error at line 14)
✗ logic failed (syntax error at line 14)
✗ build failed (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 failed (syntax error at line 14)
⊘ types blocked (by format, 120ms)
⊘ logic blocked (by format, 150ms)
⊘ build blocked (by format, 130ms)

==== 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:

```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
```

### Execution Strategies

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

### Command Timeouts

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 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):

```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 only

**Unsetting 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
```

### Passthrough Arguments

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 -- --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 `run` command)
- 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 `args` array

### Exit Codes

- `0` - All tasks passed
- `1` - Some tasks failed
- `2` - 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:

```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");
```

### VerifyResult

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
  blocked?: boolean; // True if blocked by a dependency failure
  blockedBy?: string; // Path of dependency that caused the block
  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:

```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"`

## License

MIT