@halecraft/verify 1.0.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/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 duane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,314 @@
1
+ # @halecraft/verify
2
+
3
+ Quickly check if your nodejs project is in an OK state.
4
+
5
+ Or, more technically--`verify` is a hierarchical verification runner with parallel execution and terse output.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add -D @halecraft/verify
11
+ # or
12
+ npm install -D @halecraft/verify
13
+ # or
14
+ yarn add -D @halecraft/verify
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ### Initialize a Config
20
+
21
+ The easiest way to get started is to use the `--init` flag:
22
+
23
+ ```bash
24
+ # Interactive mode - select which tasks to include
25
+ npx @halecraft/verify --init
26
+ ```
27
+
28
+ The init command will:
29
+
30
+ 1. Scan your `package.json` for verification-related scripts (lint, test, typecheck, build, etc.)
31
+ 2. Present an interactive checkbox UI to select which tasks to include
32
+ 3. Generate a `verify.config.ts` file with your selections
33
+
34
+ ### Manual Configuration
35
+
36
+ Create a `verify.config.ts` file in your project root:
37
+
38
+ ```typescript
39
+ import { defineConfig } from "@halecraft/verify";
40
+
41
+ export default defineConfig({
42
+ tasks: [
43
+ { key: "format", run: "pnpm lint" },
44
+ { key: "types", run: "pnpm typecheck" },
45
+ { key: "test", run: "pnpm test" },
46
+ ],
47
+ });
48
+ ```
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`).
51
+
52
+ ### Run Verification
53
+
54
+ ```bash
55
+ # Run all tasks
56
+ pnpm exec verify
57
+
58
+ # Run specific task
59
+ pnpm exec verify format
60
+
61
+ # Run with verbose output
62
+ pnpm exec verify --verbose
63
+
64
+ # Output JSON (for CI)
65
+ pnpm exec verify --json
66
+ ```
67
+
68
+ Or you can add `"verify": "verify"` to package.json scripts and run:
69
+
70
+ ```bash
71
+ # Run all tasks
72
+ pnpm verify
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ ### Task Definition
78
+
79
+ Each task in verify.config.ts can have the following properties:
80
+
81
+ ```typescript
82
+ interface VerificationNode {
83
+ // Unique key for this task (used in CLI filtering)
84
+ key: string;
85
+
86
+ // Human-readable name (optional)
87
+ name?: string;
88
+
89
+ // Command to run (leaf nodes only)
90
+ // Supports: string, object with cmd/args/cwd, or [cmd, args] tuple
91
+ run?:
92
+ | string
93
+ | { cmd: string; args: string[]; cwd?: string }
94
+ | [string, string[]];
95
+
96
+ // Child tasks (for grouping)
97
+ children?: VerificationNode[];
98
+
99
+ // Execution strategy for children: 'parallel' | 'sequential' | 'fail-fast'
100
+ strategy?: ExecutionStrategy;
101
+
102
+ // Parser ID for output parsing (auto-detected if not specified)
103
+ parser?: string;
104
+
105
+ // Tasks that must pass for this task's failure to be reported
106
+ reportingDependsOn?: string[];
107
+
108
+ // Custom success message template (optional)
109
+ successLabel?: string;
110
+
111
+ // Custom failure message template (optional)
112
+ failureLabel?: string;
113
+ }
114
+ ```
115
+
116
+ ### Smart Output Suppression with `reportingDependsOn`
117
+
118
+ 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.
119
+
120
+ ```typescript
121
+ import { defineConfig } from "@halecraft/verify";
122
+
123
+ export default defineConfig({
124
+ tasks: [
125
+ { key: "format", run: "biome check ." },
126
+ { key: "types", run: "tsc --noEmit", reportingDependsOn: ["format"] },
127
+ { key: "logic", run: "vitest run", reportingDependsOn: ["format"] },
128
+ { key: "build", run: "tsup", reportingDependsOn: ["format"] },
129
+ ],
130
+ });
131
+ ```
132
+
133
+ **How it works:**
134
+
135
+ - All tasks still execute in parallel (no speed regression)
136
+ - When a dependency fails (e.g., `format`), dependent tasks are terminated early for faster feedback
137
+ - Dependent tasks that also fail are marked as "suppressed"
138
+ - Only the root cause failure shows detailed logs
139
+ - Suppressed tasks show `⊘ suppressed` instead of `✗ failed`
140
+
141
+ **Before (noisy):**
142
+
143
+ ```
144
+ ✗ format (syntax error at line 14)
145
+ ✗ types (syntax error at line 14)
146
+ ✗ logic (syntax error at line 14)
147
+ ✗ build (syntax error at line 14)
148
+
149
+ ==== FORMAT FAIL ====
150
+ [50 lines of biome output]
151
+
152
+ ==== TYPES FAIL ====
153
+ [20 lines of tsc output]
154
+
155
+ ==== LOGIC FAIL ====
156
+ [30 lines of vitest output]
157
+
158
+ ==== BUILD FAIL ====
159
+ [30 lines of tsup output]
160
+ ```
161
+
162
+ **After (clean):**
163
+
164
+ ```
165
+ ✗ format (syntax error at line 14)
166
+ ⊘ types (suppressed - format failed)
167
+ ⊘ logic (suppressed - format failed)
168
+ ⊘ build (suppressed - format failed)
169
+
170
+ ==== FORMAT FAIL ====
171
+ [50 lines of biome output]
172
+
173
+ == verification: Failed ==
174
+ ```
175
+
176
+ **Note:** When using `verify --init`, the generated config automatically adds `reportingDependsOn: ["format"]` to types, logic, and build tasks when a format task is detected.
177
+
178
+ ### Nested Tasks
179
+
180
+ Group related tasks together:
181
+
182
+ ```typescript
183
+ import { defineConfig } from "@halecraft/verify";
184
+
185
+ export default defineConfig({
186
+ tasks: [
187
+ { key: "format", run: "pnpm lint" },
188
+ { key: "types", run: "pnpm typecheck" },
189
+ {
190
+ key: "logic",
191
+ children: [
192
+ { key: "unit", run: "vitest run" },
193
+ { key: "e2e", run: "playwright test" },
194
+ ],
195
+ },
196
+ ],
197
+ });
198
+ ```
199
+
200
+ Run nested tasks with colon notation:
201
+
202
+ ```bash
203
+ npx verify logic:unit
204
+ ```
205
+
206
+ ### Execution Strategies
207
+
208
+ Control how child tasks are executed:
209
+
210
+ ```typescript
211
+ {
212
+ key: 'tests',
213
+ strategy: 'fail-fast', // Stop on first failure
214
+ children: [
215
+ { key: 'unit', run: 'vitest run' },
216
+ { key: 'integration', run: 'pnpm test:integration' },
217
+ ],
218
+ }
219
+ ```
220
+
221
+ - `parallel` (default): Run all tasks simultaneously
222
+ - `sequential`: Run tasks one after another
223
+ - `fail-fast`: Run sequentially, stop on first failure
224
+
225
+ ## CLI Options
226
+
227
+ ```
228
+ Usage:
229
+ verify [options] [filter...]
230
+
231
+ Options:
232
+ --json Output results as JSON
233
+ --verbose, -v Show all task output
234
+ --quiet, -q Show only final result
235
+ --top-level, -t Show only top-level tasks (hide descendants)
236
+ --no-tty Force sequential output (disable live dashboard)
237
+ --logs=MODE Log verbosity: all, failed, none (default: failed)
238
+ --config, -c PATH Path to config file (or output path for --init)
239
+ --filter, -f PATH Filter to specific task paths
240
+ --init Initialize a new verify.config.ts file
241
+ --force Overwrite existing config file (with --init)
242
+ --yes, -y Skip interactive prompts, auto-accept detected tasks
243
+ --help, -h Show this help message
244
+ ```
245
+
246
+ ## Programmatic API
247
+
248
+ ```typescript
249
+ import { verify, defineConfig } from "@halecraft/verify";
250
+
251
+ const config = defineConfig({
252
+ tasks: [{ key: "test", run: "vitest run" }],
253
+ // Optional: set default options for this config
254
+ options: {
255
+ logs: "failed",
256
+ },
257
+ });
258
+
259
+ const result = await verify(config, {
260
+ // All options (CLI options can override config defaults)
261
+ logs: "failed", // "all" | "failed" | "none"
262
+ format: "human", // "human" | "json"
263
+ filter: ["test"], // Filter to specific task paths
264
+ cwd: process.cwd(), // Working directory
265
+ noColor: false, // Disable colors
266
+ topLevelOnly: false, // Show only top-level tasks
267
+ noTty: false, // Force sequential output
268
+ });
269
+
270
+ console.log(result.ok ? "All passed!" : "Some failed");
271
+ ```
272
+
273
+ ### VerifyResult
274
+
275
+ The `verify()` function returns a `VerifyResult` object:
276
+
277
+ ```typescript
278
+ interface VerifyResult {
279
+ ok: boolean; // Whether all tasks passed
280
+ startedAt: string; // ISO timestamp when run started
281
+ finishedAt: string; // ISO timestamp when run finished
282
+ durationMs: number; // Total duration in milliseconds
283
+ tasks: TaskResult[]; // Individual task results
284
+ }
285
+
286
+ interface TaskResult {
287
+ key: string; // Task key
288
+ path: string; // Full path (e.g., "logic:unit")
289
+ ok: boolean; // Whether the task passed
290
+ code: number; // Exit code
291
+ durationMs: number; // Duration in milliseconds
292
+ output: string; // Raw output
293
+ summaryLine: string; // Parsed summary
294
+ suppressed?: boolean; // True if output was suppressed
295
+ suppressedBy?: string; // Path of dependency that caused suppression
296
+ children?: TaskResult[]; // Child results (for group nodes)
297
+ }
298
+ ```
299
+
300
+ ## Output Parsers
301
+
302
+ Built-in parsers for common tools:
303
+
304
+ - **vitest** - Vitest test runner
305
+ - **tsc** - TypeScript compiler
306
+ - **biome** - Biome linter/formatter
307
+ - **gotest** - Go test runner
308
+ - **generic** - Fallback for unknown tools
309
+
310
+ Parsers automatically extract metrics (passed/failed counts, duration) and provide concise summaries.
311
+
312
+ ## License
313
+
314
+ MIT
package/bin/verify.mjs ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runInit, verifyFromConfig } from "../dist/index.js"
4
+
5
+ /**
6
+ * Parse CLI arguments
7
+ */
8
+ function parseArgs(args) {
9
+ const options = {
10
+ json: false,
11
+ verbose: false,
12
+ quiet: false,
13
+ logs: undefined,
14
+ filter: [],
15
+ config: undefined,
16
+ help: false,
17
+ init: false,
18
+ force: false,
19
+ yes: false,
20
+ topLevelOnly: false,
21
+ noTty: false,
22
+ }
23
+
24
+ for (let i = 0; i < args.length; i++) {
25
+ const arg = args[i]
26
+
27
+ if (arg === "--json") {
28
+ options.json = true
29
+ } else if (arg === "--verbose" || arg === "-v") {
30
+ options.verbose = true
31
+ } else if (arg === "--quiet" || arg === "-q") {
32
+ options.quiet = true
33
+ } else if (arg === "--help" || arg === "-h") {
34
+ options.help = true
35
+ } else if (arg === "--init") {
36
+ options.init = true
37
+ } else if (arg === "--force") {
38
+ options.force = true
39
+ } else if (arg === "--yes" || arg === "-y") {
40
+ options.yes = true
41
+ } else if (arg === "--top-level" || arg === "-t") {
42
+ options.topLevelOnly = true
43
+ } else if (arg === "--no-tty") {
44
+ options.noTty = true
45
+ } else if (arg.startsWith("--logs=")) {
46
+ options.logs = arg.slice(7)
47
+ } else if (arg === "--logs") {
48
+ options.logs = args[++i]
49
+ } else if (arg.startsWith("--config=")) {
50
+ options.config = arg.slice(9)
51
+ } else if (arg === "--config" || arg === "-c") {
52
+ options.config = args[++i]
53
+ } else if (arg.startsWith("--filter=")) {
54
+ options.filter.push(arg.slice(9))
55
+ } else if (arg === "--filter" || arg === "-f") {
56
+ options.filter.push(args[++i])
57
+ } else if (!arg.startsWith("-")) {
58
+ // Positional argument treated as filter
59
+ options.filter.push(arg)
60
+ }
61
+ }
62
+
63
+ return options
64
+ }
65
+
66
+ /**
67
+ * Print help message
68
+ */
69
+ function printHelp() {
70
+ console.log(`
71
+ @halecraft/verify - Hierarchical verification runner
72
+
73
+ Usage:
74
+ verify [options] [filter...]
75
+
76
+ Options:
77
+ --json Output results as JSON
78
+ --verbose, -v Show all task output
79
+ --quiet, -q Show only final result
80
+ --top-level, -t Show only top-level tasks (hide descendants)
81
+ --no-tty Force sequential output (disable live dashboard)
82
+ --logs=MODE Log verbosity: all, failed, none (default: failed)
83
+ --config, -c PATH Path to config file (or output path for --init)
84
+ --filter, -f PATH Filter to specific task paths
85
+ --init Initialize a new verify.config.ts file
86
+ --force Overwrite existing config file (with --init)
87
+ --yes, -y Skip interactive prompts, auto-accept detected tasks
88
+ --help, -h Show this help message
89
+
90
+ Examples:
91
+ verify Run all verifications
92
+ verify logic Run only 'logic' tasks
93
+ verify logic:ts Run only 'logic:ts' task
94
+ verify --top-level Show only top-level tasks
95
+ verify --json Output JSON for CI
96
+ verify --logs=all Show all output
97
+ verify --init Create config interactively
98
+ verify --init -y Create config with all detected tasks
99
+ verify --init --force Overwrite existing config
100
+
101
+ Config:
102
+ Create a verify.config.ts file in your project root:
103
+
104
+ import { defineConfig } from '@halecraft/verify'
105
+
106
+ export default defineConfig({
107
+ tasks: [
108
+ { key: 'format', run: 'pnpm verify:format' },
109
+ { key: 'types', run: 'pnpm verify:types' },
110
+ {
111
+ key: 'logic',
112
+ children: [
113
+ { key: 'ts', run: 'vitest run' },
114
+ { key: 'go', run: 'go test ./...' },
115
+ ],
116
+ },
117
+ ],
118
+ })
119
+ `)
120
+ }
121
+
122
+ async function main() {
123
+ const args = process.argv.slice(2)
124
+ const options = parseArgs(args)
125
+
126
+ if (options.help) {
127
+ printHelp()
128
+ process.exit(0)
129
+ }
130
+
131
+ // Handle --init command
132
+ if (options.init) {
133
+ try {
134
+ const result = await runInit({
135
+ config: options.config,
136
+ force: options.force,
137
+ yes: options.yes,
138
+ cwd: process.cwd(),
139
+ })
140
+ process.exit(result.success ? 0 : 1)
141
+ } catch (error) {
142
+ if (error instanceof Error) {
143
+ console.error(`Error: ${error.message}`)
144
+ } else {
145
+ console.error("Unknown error occurred")
146
+ }
147
+ process.exit(1)
148
+ }
149
+ }
150
+
151
+ // Build verify options
152
+ const verifyOptions = {
153
+ format: options.json ? "json" : "human",
154
+ logs:
155
+ options.logs ??
156
+ (options.verbose ? "all" : options.quiet ? "none" : "failed"),
157
+ filter: options.filter.length > 0 ? options.filter : undefined,
158
+ cwd: options.config,
159
+ topLevelOnly: options.topLevelOnly,
160
+ noTty: options.noTty,
161
+ }
162
+
163
+ try {
164
+ const result = await verifyFromConfig(process.cwd(), verifyOptions)
165
+ process.exit(result.ok ? 0 : 1)
166
+ } catch (error) {
167
+ if (error instanceof Error) {
168
+ console.error(`Error: ${error.message}`)
169
+ } else {
170
+ console.error("Unknown error occurred")
171
+ }
172
+ process.exit(1)
173
+ }
174
+ }
175
+
176
+ main()