@halecraft/verify 1.1.0 → 1.3.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
@@ -40,14 +40,14 @@ import { defineConfig } from "@halecraft/verify";
40
40
 
41
41
  export default defineConfig({
42
42
  tasks: [
43
- { key: "format", run: "pnpm lint" },
44
- { key: "types", run: "pnpm typecheck" },
45
- { key: "test", run: "pnpm test" },
43
+ { key: "format", run: "biome check ." },
44
+ { key: "types", run: "tsc --noEmit" },
45
+ { key: "test", run: "vitest run" },
46
46
  ],
47
47
  });
48
48
  ```
49
49
 
50
- Note that you can shave off ~150ms for each command if you skip your package manager (e.g. `./node_modules/.bin/eslint` instead of `pnpm lint`).
50
+ **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.
51
51
 
52
52
  ### Run Verification
53
53
 
@@ -65,6 +65,9 @@ pnpm exec verify types:tsc
65
65
  pnpm exec verify tsc
66
66
  # → Resolving "tsc" to "types:tsc"
67
67
 
68
+ # Pass arguments to underlying command
69
+ pnpm exec verify logic -- -t "specific test name"
70
+
68
71
  # Run with verbose output
69
72
  pnpm exec verify --verbose
70
73
 
@@ -94,10 +97,16 @@ interface VerificationNode {
94
97
  name?: string;
95
98
 
96
99
  // Command to run (leaf nodes only)
97
- // Supports: string, object with cmd/args/cwd/timeout
100
+ // Supports: string, object with cmd/args/cwd/env/timeout
98
101
  run?:
99
102
  | string
100
- | { cmd: string; args: string[]; cwd?: string; timeout?: number };
103
+ | {
104
+ cmd: string;
105
+ args: string[];
106
+ cwd?: string;
107
+ env?: Record<string, string | null>;
108
+ timeout?: number;
109
+ };
101
110
 
102
111
  // Child tasks (for grouping)
103
112
  children?: VerificationNode[];
@@ -114,6 +123,10 @@ interface VerificationNode {
114
123
  // Timeout in milliseconds (for string commands)
115
124
  timeout?: number;
116
125
 
126
+ // Environment variables for this task and its children
127
+ // Set to null to unset an inherited variable
128
+ env?: Record<string, string | null>;
129
+
117
130
  // Custom success message template (optional)
118
131
  successLabel?: string;
119
132
 
@@ -122,9 +135,9 @@ interface VerificationNode {
122
135
  }
123
136
  ```
124
137
 
125
- ### Smart Output Suppression with `reportingDependsOn`
138
+ ### Dependency-Aware Failure Reporting with `reportingDependsOn`
126
139
 
127
- 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.
140
+ 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.
128
141
 
129
142
  ```typescript
130
143
  import { defineConfig } from "@halecraft/verify";
@@ -143,17 +156,17 @@ export default defineConfig({
143
156
 
144
157
  - All tasks still execute in parallel (no speed regression)
145
158
  - When a dependency fails (e.g., `format`), dependent tasks are terminated early for faster feedback
146
- - Dependent tasks that also fail are marked as "suppressed"
159
+ - Dependent tasks that also fail are marked as "blocked"
147
160
  - Only the root cause failure shows detailed logs
148
- - Suppressed tasks show `⊘ suppressed` instead of `✗ failed`
161
+ - Blocked tasks show `⊘ blocked` instead of `✗ failed`
149
162
 
150
163
  **Before (noisy):**
151
164
 
152
165
  ```
153
- ✗ format (syntax error at line 14)
154
- ✗ types (syntax error at line 14)
155
- ✗ logic (syntax error at line 14)
156
- ✗ build (syntax error at line 14)
166
+ ✗ format failed (syntax error at line 14)
167
+ ✗ types failed (syntax error at line 14)
168
+ ✗ logic failed (syntax error at line 14)
169
+ ✗ build failed (syntax error at line 14)
157
170
 
158
171
  ==== FORMAT FAIL ====
159
172
  [50 lines of biome output]
@@ -171,10 +184,10 @@ export default defineConfig({
171
184
  **After (clean):**
172
185
 
173
186
  ```
174
- ✗ format (syntax error at line 14)
175
- ⊘ types (suppressed - format failed)
176
- ⊘ logic (suppressed - format failed)
177
- ⊘ build (suppressed - format failed)
187
+ ✗ format failed (syntax error at line 14)
188
+ ⊘ types blocked (by format, 120ms)
189
+ ⊘ logic blocked (by format, 150ms)
190
+ ⊘ build blocked (by format, 130ms)
178
191
 
179
192
  ==== FORMAT FAIL ====
180
193
  [50 lines of biome output]
@@ -264,13 +277,66 @@ When a command exceeds its timeout:
264
277
 
265
278
  **Note:** For object commands, the `timeout` on the command takes precedence over the node-level `timeout`.
266
279
 
280
+ ### Environment Variables
281
+
282
+ Set environment variables at the config level (applies to all tasks) or at the task level (inherits to children):
283
+
284
+ ```typescript
285
+ import { defineConfig } from "@halecraft/verify";
286
+
287
+ export default defineConfig({
288
+ // Global env vars - applied to all tasks
289
+ env: {
290
+ NO_COLOR: "1", // Recommended: disable colors for cleaner output parsing
291
+ CI: "true",
292
+ },
293
+ tasks: [
294
+ { key: "format", run: "biome check ." },
295
+ {
296
+ key: "test",
297
+ // Enable colors for test output by unsetting NO_COLOR
298
+ env: { NO_COLOR: null },
299
+ children: [
300
+ { key: "unit", run: "vitest run" },
301
+ {
302
+ key: "e2e",
303
+ run: "playwright test",
304
+ // E2E-specific env (still inherits CI: "true")
305
+ env: { PLAYWRIGHT_BROWSERS_PATH: "0" },
306
+ },
307
+ ],
308
+ },
309
+ ],
310
+ });
311
+ ```
312
+
313
+ **Environment merge order** (most specific wins):
314
+
315
+ 1. `process.env` - System environment
316
+ 2. `config.env` - Global config-level
317
+ 3. Parent task `env` - Inherited from parent tasks
318
+ 4. Node `env` - Current task
319
+ 5. Command `env` - VerificationCommand object only
320
+
321
+ **Unsetting variables:** Set a value to `null` to explicitly unset an inherited variable:
322
+
323
+ ```typescript
324
+ {
325
+ key: "test",
326
+ env: { NO_COLOR: null }, // Re-enables colors for this task
327
+ run: "vitest run",
328
+ }
329
+ ```
330
+
331
+ **Note:** Generated configs (via `--init`) include `env: { NO_COLOR: "1" }` by default to ensure consistent output parsing.
332
+
267
333
  ## CLI Options
268
334
 
269
335
  ```
270
336
  Usage:
271
- verify [options] [filter...]
337
+ verify [flags...] [task] [--] [passthrough...]
272
338
 
273
- Options:
339
+ Flags:
274
340
  --json Output results as JSON
275
341
  --verbose, -v Show all task output
276
342
  --quiet, -q Show only final result
@@ -278,13 +344,39 @@ Options:
278
344
  --no-tty Force sequential output (disable live dashboard)
279
345
  --logs=MODE Log verbosity: all, failed, none (default: failed)
280
346
  --config, -c PATH Path to config file (or output path for --init)
281
- --filter, -f PATH Filter to specific task paths
282
347
  --init Initialize a new verify.config.ts file
283
348
  --force Overwrite existing config file (with --init)
284
349
  --yes, -y Skip interactive prompts, auto-accept detected tasks
285
350
  --help, -h Show this help message
286
351
  ```
287
352
 
353
+ ### Passthrough Arguments
354
+
355
+ You can pass arguments directly to the underlying command using `--` (double-dash):
356
+
357
+ ```bash
358
+ # Run a specific vitest test
359
+ verify logic -- -t "should handle edge case"
360
+
361
+ # Run with coverage
362
+ verify logic -- --coverage
363
+
364
+ # Multiple passthrough args
365
+ verify logic -- -t "foo" --reporter=verbose
366
+
367
+ # Combine with verify flags
368
+ verify logic --verbose -- -t "foo"
369
+ ```
370
+
371
+ **Requirements:**
372
+ - Passthrough arguments require exactly one task filter
373
+ - The task must be a leaf node (has a `run` command)
374
+ - Arguments are appended to the command string
375
+
376
+ **How it works:**
377
+ - For string commands: args are shell-escaped and appended to the command
378
+ - For object commands: args are appended to the `args` array
379
+
288
380
  ### Exit Codes
289
381
 
290
382
  - `0` - All tasks passed
@@ -377,8 +469,8 @@ interface TaskResult {
377
469
  durationMs: number; // Duration in milliseconds
378
470
  output: string; // Raw output
379
471
  summaryLine: string; // Parsed summary
380
- suppressed?: boolean; // True if output was suppressed
381
- suppressedBy?: string; // Path of dependency that caused suppression
472
+ blocked?: boolean; // True if blocked by a dependency failure
473
+ blockedBy?: string; // Path of dependency that caused the block
382
474
  timedOut?: boolean; // True if task exceeded its timeout
383
475
  children?: TaskResult[]; // Child results (for group nodes)
384
476
  }
package/bin/verify.mjs CHANGED
@@ -14,6 +14,12 @@ const argv = cli(
14
14
  version: "1.0.0",
15
15
  description: "Hierarchical verification runner with parallel execution",
16
16
 
17
+ parameters: [
18
+ "[task]", // Optional single task filter
19
+ "--", // End-of-flags separator
20
+ "[passthrough...]", // Args to pass to underlying command
21
+ ],
22
+
17
23
  flags: {
18
24
  json: {
19
25
  type: Boolean,
@@ -52,12 +58,6 @@ const argv = cli(
52
58
  alias: "c",
53
59
  description: "Path to config file (or output path for --init)",
54
60
  },
55
- filter: {
56
- type: [String],
57
- alias: "f",
58
- description: "Filter to specific task paths",
59
- default: [],
60
- },
61
61
  init: {
62
62
  type: Boolean,
63
63
  description: "Initialize a new verify.config.ts file",
@@ -81,6 +81,7 @@ const argv = cli(
81
81
  "verify Run all verifications",
82
82
  "verify logic Run only 'logic' tasks",
83
83
  "verify logic:ts Run only 'logic:ts' task",
84
+ "verify logic -- -t foo Run 'logic' with passthrough args",
84
85
  "verify --top-level Show only top-level tasks",
85
86
  "verify --json Output JSON for CI",
86
87
  "verify --logs=all Show all output",
@@ -117,18 +118,28 @@ async function main() {
117
118
  }
118
119
  }
119
120
 
120
- // Combine --filter flags with positional arguments
121
- const allFilters = [...flags.filter, ..._]
121
+ // Get task filter from named parameter
122
+ const task = _.task
123
+ const passthrough = _.passthrough
124
+
125
+ // Validate: passthrough requires a single task filter
126
+ if (passthrough && passthrough.length > 0 && !task) {
127
+ console.error("Error: Passthrough arguments (after --) require a task filter")
128
+ console.error("Usage: verify <task> -- [args...]")
129
+ process.exit(2)
130
+ }
122
131
 
123
132
  // Build verify options
124
133
  const verifyOptions = {
125
134
  format: flags.json ? "json" : "human",
126
135
  logs:
127
136
  flags.logs ?? (flags.verbose ? "all" : flags.quiet ? "none" : "failed"),
128
- filter: allFilters.length > 0 ? allFilters : undefined,
137
+ filter: task ? [task] : undefined,
129
138
  cwd: flags.config,
130
139
  topLevelOnly: flags.topLevel,
131
140
  noTty: flags.noTty,
141
+ quiet: flags.quiet,
142
+ passthrough: passthrough && passthrough.length > 0 ? passthrough : undefined,
132
143
  }
133
144
 
134
145
  try {
package/dist/index.d.ts CHANGED
@@ -8,8 +8,11 @@ interface VerificationCommand {
8
8
  args: string[];
9
9
  /** Working directory (defaults to cwd) */
10
10
  cwd?: string;
11
- /** Environment variables to set */
12
- env?: Record<string, string>;
11
+ /**
12
+ * Environment variables to set.
13
+ * Set to null to explicitly unset an inherited variable.
14
+ */
15
+ env?: Record<string, string | null>;
13
16
  /** Timeout in milliseconds (process killed with SIGTERM if exceeded) */
14
17
  timeout?: number;
15
18
  }
@@ -23,6 +26,7 @@ interface ParsedResult {
23
26
  metrics?: {
24
27
  passed?: number;
25
28
  failed?: number;
29
+ skipped?: number;
26
30
  total?: number;
27
31
  duration?: string;
28
32
  errors?: number;
@@ -64,7 +68,7 @@ interface VerificationNode {
64
68
  failureLabel?: string;
65
69
  /**
66
70
  * Tasks that must pass for this task's failure to be reported.
67
- * If any dependency fails, this task's failure output is suppressed.
71
+ * If any dependency fails, this task's failure is marked as blocked.
68
72
  * Can specify task keys (e.g., "format") or full paths (e.g., "types:tsc").
69
73
  */
70
74
  reportingDependsOn?: string[];
@@ -74,6 +78,14 @@ interface VerificationNode {
74
78
  * For VerificationCommand objects, use the timeout field on the command itself.
75
79
  */
76
80
  timeout?: number;
81
+ /**
82
+ * Environment variables for this task and its children.
83
+ * Inherits from parent tasks and config-level env.
84
+ * Child tasks can override parent values.
85
+ * Set to null to explicitly unset an inherited variable.
86
+ * For VerificationCommand objects, command-level env takes precedence.
87
+ */
88
+ env?: Record<string, string | null>;
77
89
  }
78
90
  /**
79
91
  * Options for the verification runner
@@ -93,6 +105,10 @@ interface VerifyOptions {
93
105
  topLevelOnly?: boolean;
94
106
  /** Force sequential output (disable live dashboard) */
95
107
  noTty?: boolean;
108
+ /** Suppress in-progress output, show only final summary */
109
+ quiet?: boolean;
110
+ /** Arguments to pass through to the underlying command (requires single task filter) */
111
+ passthrough?: string[];
96
112
  }
97
113
  /**
98
114
  * Package discovery options for monorepos
@@ -115,6 +131,12 @@ interface VerifyConfig {
115
131
  packages?: PackageDiscoveryOptions;
116
132
  /** Default options */
117
133
  options?: VerifyOptions;
134
+ /**
135
+ * Global environment variables applied to all tasks.
136
+ * Set to null to explicitly unset an inherited variable.
137
+ * Recommend setting NO_COLOR: "1" here to disable colors in output.
138
+ */
139
+ env?: Record<string, string | null>;
118
140
  }
119
141
  /**
120
142
  * Result of a single verification task
@@ -139,15 +161,15 @@ interface TaskResult {
139
161
  /** Child results (for group nodes) */
140
162
  children?: TaskResult[];
141
163
  /**
142
- * Whether this task's failure output was suppressed due to a dependency failure.
143
- * The task still ran and failed, but its output is hidden to reduce noise.
164
+ * Whether this task's failure was blocked by a dependency failure.
165
+ * The task ran but its failure output is hidden because the root cause is the dependency.
144
166
  */
145
- suppressed?: boolean;
167
+ blocked?: boolean;
146
168
  /**
147
- * The path of the dependency task that caused this task to be suppressed.
148
- * Only set when suppressed is true.
169
+ * The path of the dependency task that caused this task to be blocked.
170
+ * Only set when blocked is true.
149
171
  */
150
- suppressedBy?: string;
172
+ blockedBy?: string;
151
173
  /**
152
174
  * Whether this task was terminated due to timeout.
153
175
  * Only set when the task exceeded its configured timeout.
@@ -304,7 +326,7 @@ interface DetectedTask {
304
326
  }
305
327
  /**
306
328
  * Detect tasks with proper package manager commands
307
- * Uses optimized direct paths when possible, falls back to package manager
329
+ * Uses optimized direct binary names when possible, falls back to package manager
308
330
  */
309
331
  declare function detectTasks(cwd: string): DetectedTask[];
310
332
 
@@ -450,6 +472,14 @@ interface Reporter {
450
472
  /** Called to output final summary */
451
473
  outputSummary(result: VerifyResult): void;
452
474
  }
475
+ /**
476
+ * Terminal capability context — the single point where globals are read.
477
+ * Passed to pure decision functions for testability.
478
+ */
479
+ interface TerminalContext {
480
+ isTTY: boolean;
481
+ env: Record<string, string | undefined>;
482
+ }
453
483
  /**
454
484
  * Base Reporter - common functionality for all reporters
455
485
  */
@@ -457,7 +487,7 @@ declare abstract class BaseReporter implements Reporter {
457
487
  protected colorEnabled: boolean;
458
488
  protected stream: NodeJS.WriteStream;
459
489
  protected taskDepths: Map<string, number>;
460
- constructor(options?: VerifyOptions);
490
+ constructor(options?: VerifyOptions, ctx?: TerminalContext);
461
491
  /**
462
492
  * Apply ANSI color code to string (if colors enabled)
463
493
  */
@@ -471,9 +501,9 @@ declare abstract class BaseReporter implements Reporter {
471
501
  */
472
502
  protected failMark(): string;
473
503
  /**
474
- * Get suppressed mark (⊘ or SUPPRESSED)
504
+ * Get blocked mark (⊘ or BLOCK)
475
505
  */
476
- protected suppressedMark(): string;
506
+ protected blockedMark(): string;
477
507
  /**
478
508
  * Get arrow symbol (→ or ->)
479
509
  */
@@ -490,6 +520,11 @@ declare abstract class BaseReporter implements Reporter {
490
520
  * Collect task depths from verification tree using walkNodes
491
521
  */
492
522
  protected collectTaskDepths(nodes: VerificationNode[]): void;
523
+ /**
524
+ * Format a completed task result line in name-first format.
525
+ * Shared by LiveDashboardReporter and SequentialReporter.
526
+ */
527
+ protected formatResultLine(name: string, result: TaskResult): string;
493
528
  /**
494
529
  * Extract summary from task result
495
530
  */
@@ -520,7 +555,7 @@ declare class LiveDashboardReporter extends BaseReporter {
520
555
  private taskOrder;
521
556
  private spinner;
522
557
  private lineCount;
523
- constructor(options?: VerifyOptions);
558
+ constructor(options?: VerifyOptions, ctx?: TerminalContext);
524
559
  /**
525
560
  * Initialize task list from verification nodes
526
561
  */
@@ -555,7 +590,7 @@ declare class LiveDashboardReporter extends BaseReporter {
555
590
  */
556
591
  declare class SequentialReporter extends BaseReporter {
557
592
  private topLevelOnly;
558
- constructor(options?: VerifyOptions);
593
+ constructor(options?: VerifyOptions, ctx?: TerminalContext);
559
594
  onStart(tasks: VerificationNode[]): void;
560
595
  /**
561
596
  * Check if task should be displayed based on topLevelOnly flag
@@ -589,7 +624,9 @@ declare class QuietReporter extends BaseReporter {
589
624
  outputSummary(result: VerifyResult): void;
590
625
  }
591
626
  /**
592
- * Create appropriate reporter based on options
627
+ * Create appropriate reporter based on options.
628
+ *
629
+ * Decision tree: JSON → Quiet → LiveDashboard → Sequential
593
630
  */
594
631
  declare function createReporter(options: VerifyOptions): Reporter;
595
632
 
@@ -605,7 +642,8 @@ declare class VerificationRunner {
605
642
  private options;
606
643
  private callbacks;
607
644
  private dependencyTracker;
608
- constructor(options?: VerifyOptions, registry?: ParserRegistry, callbacks?: RunnerCallbacks);
645
+ private configEnv;
646
+ constructor(options?: VerifyOptions, registry?: ParserRegistry, callbacks?: RunnerCallbacks, configEnv?: Record<string, string | null>);
609
647
  /**
610
648
  * Run all verification tasks
611
649
  */
@@ -655,4 +693,4 @@ declare function verify(config: VerifyConfig, cliOptions?: Partial<VerifyOptions
655
693
  */
656
694
  declare function verifyFromConfig(cwd?: string, cliOptions?: Partial<VerifyOptions>): Promise<VerifyResult>;
657
695
 
658
- export { AmbiguousTaskError, ConfigError, type DetectedTask, type DiscoveredPackage, type ExecutionStrategy, type InitOptions, type InitResult, JSONReporter, LiveDashboardReporter, type NodeVisitor, type OutputFormat, type OutputParser, PATH_SEPARATOR, type PackageDiscoveryOptions, type ParsedResult, type ParserId, ParserRegistry, QuietReporter, type Reporter, type ResolvedFilter, type RunnerCallbacks, SequentialReporter, SequentialReporter as TTYReporter, TaskNotFoundError, type TaskResult, type VerificationCommand, type VerificationNode, VerificationRunner, type VerifyConfig, type VerifyOptions, type VerifyResult, biomeParser, buildTaskPath, collectPaths, createReporter, defaultRegistry, defineConfig, defineTask, detectTasks, discoverPackages, findBestSuggestion, findConfigFile, generateConfigContent, genericParser, gotestParser, hasPackageChanged, loadConfig, loadConfigFromCwd, mergeOptions, parsers, resolveFilters, runInit, tscParser, validateConfig, verify, verifyFromConfig, vitestParser, walkNodes };
696
+ export { AmbiguousTaskError, ConfigError, type DetectedTask, type DiscoveredPackage, type ExecutionStrategy, type InitOptions, type InitResult, JSONReporter, LiveDashboardReporter, type NodeVisitor, type OutputFormat, type OutputParser, PATH_SEPARATOR, type PackageDiscoveryOptions, type ParsedResult, type ParserId, ParserRegistry, QuietReporter, type Reporter, type ResolvedFilter, type RunnerCallbacks, SequentialReporter, SequentialReporter as TTYReporter, TaskNotFoundError, type TaskResult, type TerminalContext, type VerificationCommand, type VerificationNode, VerificationRunner, type VerifyConfig, type VerifyOptions, type VerifyResult, biomeParser, buildTaskPath, collectPaths, createReporter, defaultRegistry, defineConfig, defineTask, detectTasks, discoverPackages, findBestSuggestion, findConfigFile, generateConfigContent, genericParser, gotestParser, hasPackageChanged, loadConfig, loadConfigFromCwd, mergeOptions, parsers, resolveFilters, runInit, tscParser, validateConfig, verify, verifyFromConfig, vitestParser, walkNodes };