@halecraft/verify 1.1.0 → 1.2.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 +101 -9
- package/bin/verify.mjs +19 -9
- package/dist/index.d.ts +24 -4
- package/dist/index.js +131 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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: "
|
|
44
|
-
{ key: "types", run: "
|
|
45
|
-
{ key: "test", run: "
|
|
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
|
|
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
|
-
| {
|
|
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
|
|
|
@@ -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 [
|
|
337
|
+
verify [flags...] [task] [--] [passthrough...]
|
|
272
338
|
|
|
273
|
-
|
|
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
|
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,27 @@ async function main() {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
//
|
|
121
|
-
const
|
|
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:
|
|
137
|
+
filter: task ? [task] : undefined,
|
|
129
138
|
cwd: flags.config,
|
|
130
139
|
topLevelOnly: flags.topLevel,
|
|
131
140
|
noTty: flags.noTty,
|
|
141
|
+
passthrough: passthrough && passthrough.length > 0 ? passthrough : undefined,
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
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
|
-
/**
|
|
12
|
-
|
|
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
|
}
|
|
@@ -74,6 +77,14 @@ interface VerificationNode {
|
|
|
74
77
|
* For VerificationCommand objects, use the timeout field on the command itself.
|
|
75
78
|
*/
|
|
76
79
|
timeout?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Environment variables for this task and its children.
|
|
82
|
+
* Inherits from parent tasks and config-level env.
|
|
83
|
+
* Child tasks can override parent values.
|
|
84
|
+
* Set to null to explicitly unset an inherited variable.
|
|
85
|
+
* For VerificationCommand objects, command-level env takes precedence.
|
|
86
|
+
*/
|
|
87
|
+
env?: Record<string, string | null>;
|
|
77
88
|
}
|
|
78
89
|
/**
|
|
79
90
|
* Options for the verification runner
|
|
@@ -93,6 +104,8 @@ interface VerifyOptions {
|
|
|
93
104
|
topLevelOnly?: boolean;
|
|
94
105
|
/** Force sequential output (disable live dashboard) */
|
|
95
106
|
noTty?: boolean;
|
|
107
|
+
/** Arguments to pass through to the underlying command (requires single task filter) */
|
|
108
|
+
passthrough?: string[];
|
|
96
109
|
}
|
|
97
110
|
/**
|
|
98
111
|
* Package discovery options for monorepos
|
|
@@ -115,6 +128,12 @@ interface VerifyConfig {
|
|
|
115
128
|
packages?: PackageDiscoveryOptions;
|
|
116
129
|
/** Default options */
|
|
117
130
|
options?: VerifyOptions;
|
|
131
|
+
/**
|
|
132
|
+
* Global environment variables applied to all tasks.
|
|
133
|
+
* Set to null to explicitly unset an inherited variable.
|
|
134
|
+
* Recommend setting NO_COLOR: "1" here to disable colors in output.
|
|
135
|
+
*/
|
|
136
|
+
env?: Record<string, string | null>;
|
|
118
137
|
}
|
|
119
138
|
/**
|
|
120
139
|
* Result of a single verification task
|
|
@@ -304,7 +323,7 @@ interface DetectedTask {
|
|
|
304
323
|
}
|
|
305
324
|
/**
|
|
306
325
|
* Detect tasks with proper package manager commands
|
|
307
|
-
* Uses optimized direct
|
|
326
|
+
* Uses optimized direct binary names when possible, falls back to package manager
|
|
308
327
|
*/
|
|
309
328
|
declare function detectTasks(cwd: string): DetectedTask[];
|
|
310
329
|
|
|
@@ -605,7 +624,8 @@ declare class VerificationRunner {
|
|
|
605
624
|
private options;
|
|
606
625
|
private callbacks;
|
|
607
626
|
private dependencyTracker;
|
|
608
|
-
|
|
627
|
+
private configEnv;
|
|
628
|
+
constructor(options?: VerifyOptions, registry?: ParserRegistry, callbacks?: RunnerCallbacks, configEnv?: Record<string, string | null>);
|
|
609
629
|
/**
|
|
610
630
|
* Run all verification tasks
|
|
611
631
|
*/
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,12 @@ var ConfigError = class extends Error {
|
|
|
10
10
|
this.name = "ConfigError";
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
|
+
var EnvSchema = z.record(z.string(), z.string().nullable()).optional();
|
|
13
14
|
var VerificationCommandSchema = z.object({
|
|
14
15
|
cmd: z.string(),
|
|
15
16
|
args: z.array(z.string()),
|
|
16
17
|
cwd: z.string().optional(),
|
|
17
|
-
env:
|
|
18
|
+
env: EnvSchema,
|
|
18
19
|
timeout: z.number().positive().optional()
|
|
19
20
|
});
|
|
20
21
|
var VerificationNodeSchema = z.lazy(
|
|
@@ -30,7 +31,8 @@ var VerificationNodeSchema = z.lazy(
|
|
|
30
31
|
successLabel: z.string().optional(),
|
|
31
32
|
failureLabel: z.string().optional(),
|
|
32
33
|
reportingDependsOn: z.array(z.string()).optional(),
|
|
33
|
-
timeout: z.number().positive().optional()
|
|
34
|
+
timeout: z.number().positive().optional(),
|
|
35
|
+
env: EnvSchema
|
|
34
36
|
})
|
|
35
37
|
);
|
|
36
38
|
var VerifyOptionsSchema = z.object({
|
|
@@ -40,7 +42,8 @@ var VerifyOptionsSchema = z.object({
|
|
|
40
42
|
cwd: z.string().optional(),
|
|
41
43
|
noColor: z.boolean().optional(),
|
|
42
44
|
topLevelOnly: z.boolean().optional(),
|
|
43
|
-
noTty: z.boolean().optional()
|
|
45
|
+
noTty: z.boolean().optional(),
|
|
46
|
+
passthrough: z.array(z.string()).optional()
|
|
44
47
|
});
|
|
45
48
|
var PackageDiscoveryOptionsSchema = z.object({
|
|
46
49
|
patterns: z.array(z.string()).optional(),
|
|
@@ -50,7 +53,8 @@ var PackageDiscoveryOptionsSchema = z.object({
|
|
|
50
53
|
var VerifyConfigSchema = z.object({
|
|
51
54
|
tasks: z.array(VerificationNodeSchema),
|
|
52
55
|
packages: PackageDiscoveryOptionsSchema.optional(),
|
|
53
|
-
options: VerifyOptionsSchema.optional()
|
|
56
|
+
options: VerifyOptionsSchema.optional(),
|
|
57
|
+
env: EnvSchema
|
|
54
58
|
});
|
|
55
59
|
function validateConfig(value, configPath) {
|
|
56
60
|
const result = VerifyConfigSchema.safeParse(value);
|
|
@@ -118,7 +122,8 @@ function mergeOptions(configOptions, cliOptions) {
|
|
|
118
122
|
cwd: cliOptions?.cwd ?? configOptions?.cwd ?? process.cwd(),
|
|
119
123
|
noColor: cliOptions?.noColor ?? configOptions?.noColor ?? false,
|
|
120
124
|
topLevelOnly: cliOptions?.topLevelOnly ?? configOptions?.topLevelOnly ?? false,
|
|
121
|
-
noTty: cliOptions?.noTty ?? configOptions?.noTty ?? false
|
|
125
|
+
noTty: cliOptions?.noTty ?? configOptions?.noTty ?? false,
|
|
126
|
+
passthrough: cliOptions?.passthrough ?? configOptions?.passthrough
|
|
122
127
|
};
|
|
123
128
|
}
|
|
124
129
|
|
|
@@ -508,7 +513,7 @@ function extractOptimizedCommand(cwd, scriptContent) {
|
|
|
508
513
|
const match = scriptContent.match(tool.pattern);
|
|
509
514
|
if (match && binaryExists(cwd, tool.binary)) {
|
|
510
515
|
const args = tool.getArgs(match, scriptContent);
|
|
511
|
-
const command = args ?
|
|
516
|
+
const command = args ? `${tool.binary} ${args}` : tool.binary;
|
|
512
517
|
return { command, parser: tool.parser };
|
|
513
518
|
}
|
|
514
519
|
}
|
|
@@ -585,7 +590,7 @@ function detectTasks(cwd) {
|
|
|
585
590
|
const packageManager = detectPackageManager(cwd);
|
|
586
591
|
const tasks = detectFromPackageJson(cwd);
|
|
587
592
|
return tasks.map((task) => {
|
|
588
|
-
if (task.command.startsWith("
|
|
593
|
+
if (!task.command.startsWith("npm run ")) {
|
|
589
594
|
return task;
|
|
590
595
|
}
|
|
591
596
|
return {
|
|
@@ -621,12 +626,15 @@ function generateSkeleton(format) {
|
|
|
621
626
|
return `${importStatement}
|
|
622
627
|
|
|
623
628
|
export default defineConfig({
|
|
629
|
+
env: {
|
|
630
|
+
NO_COLOR: "1",
|
|
631
|
+
},
|
|
624
632
|
tasks: [
|
|
625
633
|
// Add your verification tasks here
|
|
626
634
|
// Example:
|
|
627
|
-
// { key: "format", run: "
|
|
628
|
-
// { key: "types", run: "
|
|
629
|
-
// { key: "test", run: "
|
|
635
|
+
// { key: "format", run: "biome check ." },
|
|
636
|
+
// { key: "types", run: "tsc --noEmit" },
|
|
637
|
+
// { key: "test", run: "vitest run" },
|
|
630
638
|
],
|
|
631
639
|
})
|
|
632
640
|
`;
|
|
@@ -641,6 +649,9 @@ function generateConfigContent(tasks, format) {
|
|
|
641
649
|
return `${importStatement}
|
|
642
650
|
|
|
643
651
|
export default defineConfig({
|
|
652
|
+
env: {
|
|
653
|
+
NO_COLOR: "1",
|
|
654
|
+
},
|
|
644
655
|
tasks: [
|
|
645
656
|
${taskLines.join(",\n")},
|
|
646
657
|
],
|
|
@@ -1453,6 +1464,7 @@ function createReporter(options) {
|
|
|
1453
1464
|
|
|
1454
1465
|
// src/runner.ts
|
|
1455
1466
|
import { spawn } from "child_process";
|
|
1467
|
+
import { dirname, join as join4, resolve as resolve3 } from "path";
|
|
1456
1468
|
import treeKill2 from "tree-kill";
|
|
1457
1469
|
|
|
1458
1470
|
// src/dependency-tracker.ts
|
|
@@ -1634,9 +1646,9 @@ var ReportingDependencyTracker = class {
|
|
|
1634
1646
|
if (this.results.has(pathOrKey)) {
|
|
1635
1647
|
return Promise.resolve();
|
|
1636
1648
|
}
|
|
1637
|
-
return new Promise((
|
|
1649
|
+
return new Promise((resolve4) => {
|
|
1638
1650
|
const waiters = this.waiters.get(pathOrKey) ?? [];
|
|
1639
|
-
waiters.push(
|
|
1651
|
+
waiters.push(resolve4);
|
|
1640
1652
|
this.waiters.set(pathOrKey, waiters);
|
|
1641
1653
|
});
|
|
1642
1654
|
}
|
|
@@ -1704,17 +1716,78 @@ var ReportingDependencyTracker = class {
|
|
|
1704
1716
|
};
|
|
1705
1717
|
|
|
1706
1718
|
// src/runner.ts
|
|
1707
|
-
function
|
|
1719
|
+
function mergeEnv(base, overlay) {
|
|
1720
|
+
if (!overlay) return { ...base };
|
|
1721
|
+
const result = { ...base };
|
|
1722
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
1723
|
+
if (value === null) {
|
|
1724
|
+
result[key] = null;
|
|
1725
|
+
} else {
|
|
1726
|
+
result[key] = value;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return result;
|
|
1730
|
+
}
|
|
1731
|
+
function buildFinalEnv(processEnv, path, overlay) {
|
|
1732
|
+
const result = {};
|
|
1733
|
+
for (const [key, value] of Object.entries(processEnv)) {
|
|
1734
|
+
if (value !== void 0) {
|
|
1735
|
+
result[key] = value;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
result.PATH = path;
|
|
1739
|
+
if (overlay) {
|
|
1740
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
1741
|
+
if (value === null) {
|
|
1742
|
+
delete result[key];
|
|
1743
|
+
} else {
|
|
1744
|
+
result[key] = value;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
return result;
|
|
1749
|
+
}
|
|
1750
|
+
function shellEscape(arg) {
|
|
1751
|
+
if (/[\s"'`$\\!&|;<>(){}[\]*?#~]/.test(arg)) {
|
|
1752
|
+
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
1753
|
+
}
|
|
1754
|
+
return arg;
|
|
1755
|
+
}
|
|
1756
|
+
function buildNodeModulesPath(cwd) {
|
|
1757
|
+
const pathSeparator = process.platform === "win32" ? ";" : ":";
|
|
1758
|
+
const existingPath = process.env.PATH ?? "";
|
|
1759
|
+
const binPaths = [];
|
|
1760
|
+
let current = resolve3(cwd);
|
|
1761
|
+
while (true) {
|
|
1762
|
+
binPaths.push(join4(current, "node_modules", ".bin"));
|
|
1763
|
+
const parent = dirname(current);
|
|
1764
|
+
if (parent === current) break;
|
|
1765
|
+
current = parent;
|
|
1766
|
+
}
|
|
1767
|
+
return [...binPaths, existingPath].join(pathSeparator);
|
|
1768
|
+
}
|
|
1769
|
+
function normalizeCommand(run, nodeTimeout, passthrough, inheritedEnv) {
|
|
1708
1770
|
if (typeof run === "string") {
|
|
1771
|
+
let cmd = run;
|
|
1772
|
+
if (passthrough && passthrough.length > 0) {
|
|
1773
|
+
const escapedArgs = passthrough.map(shellEscape).join(" ");
|
|
1774
|
+
cmd = `${run} ${escapedArgs}`;
|
|
1775
|
+
}
|
|
1709
1776
|
return {
|
|
1710
|
-
cmd
|
|
1777
|
+
cmd,
|
|
1711
1778
|
args: [],
|
|
1712
1779
|
shell: true,
|
|
1713
|
-
timeout: nodeTimeout
|
|
1780
|
+
timeout: nodeTimeout,
|
|
1781
|
+
env: inheritedEnv
|
|
1714
1782
|
};
|
|
1715
1783
|
}
|
|
1784
|
+
const args = passthrough ? [...run.args, ...passthrough] : run.args;
|
|
1785
|
+
const env = mergeEnv(inheritedEnv ?? {}, run.env);
|
|
1716
1786
|
return {
|
|
1717
|
-
|
|
1787
|
+
cmd: run.cmd,
|
|
1788
|
+
args,
|
|
1789
|
+
cwd: run.cwd,
|
|
1790
|
+
env: Object.keys(env).length > 0 ? env : void 0,
|
|
1718
1791
|
shell: false,
|
|
1719
1792
|
// Command-level timeout takes precedence over node-level timeout
|
|
1720
1793
|
timeout: run.timeout ?? nodeTimeout
|
|
@@ -1722,12 +1795,18 @@ function normalizeCommand(run, nodeTimeout) {
|
|
|
1722
1795
|
}
|
|
1723
1796
|
async function executeCommand(command, cwd, tracker, path) {
|
|
1724
1797
|
const start = Date.now();
|
|
1725
|
-
return new Promise((
|
|
1798
|
+
return new Promise((resolve4) => {
|
|
1726
1799
|
const useShell = command.shell || process.platform === "win32";
|
|
1800
|
+
const effectiveCwd = command.cwd ?? cwd;
|
|
1801
|
+
const finalEnv = buildFinalEnv(
|
|
1802
|
+
process.env,
|
|
1803
|
+
buildNodeModulesPath(effectiveCwd),
|
|
1804
|
+
command.env
|
|
1805
|
+
);
|
|
1727
1806
|
const proc = spawn(command.cmd, command.args, {
|
|
1728
1807
|
shell: useShell,
|
|
1729
|
-
cwd:
|
|
1730
|
-
env:
|
|
1808
|
+
cwd: effectiveCwd,
|
|
1809
|
+
env: finalEnv
|
|
1731
1810
|
});
|
|
1732
1811
|
if (tracker && path) {
|
|
1733
1812
|
tracker.registerProcess(path, proc);
|
|
@@ -1760,7 +1839,7 @@ async function executeCommand(command, cwd, tracker, path) {
|
|
|
1760
1839
|
}
|
|
1761
1840
|
const durationMs = Date.now() - start;
|
|
1762
1841
|
const killed = signal === "SIGTERM" || code === 143 || (tracker?.wasKilled(path ?? "") ?? false);
|
|
1763
|
-
|
|
1842
|
+
resolve4({
|
|
1764
1843
|
code: code ?? 1,
|
|
1765
1844
|
output,
|
|
1766
1845
|
durationMs,
|
|
@@ -1777,7 +1856,7 @@ async function executeCommand(command, cwd, tracker, path) {
|
|
|
1777
1856
|
tracker.unregisterProcess(path);
|
|
1778
1857
|
}
|
|
1779
1858
|
const durationMs = Date.now() - start;
|
|
1780
|
-
|
|
1859
|
+
resolve4({
|
|
1781
1860
|
code: 1,
|
|
1782
1861
|
output: `Failed to execute command: ${err.message}`,
|
|
1783
1862
|
durationMs,
|
|
@@ -1812,11 +1891,13 @@ var VerificationRunner = class {
|
|
|
1812
1891
|
options;
|
|
1813
1892
|
callbacks;
|
|
1814
1893
|
dependencyTracker;
|
|
1815
|
-
|
|
1894
|
+
configEnv;
|
|
1895
|
+
constructor(options = {}, registry = defaultRegistry, callbacks = {}, configEnv = {}) {
|
|
1816
1896
|
this.options = options;
|
|
1817
1897
|
this.registry = registry;
|
|
1818
1898
|
this.callbacks = callbacks;
|
|
1819
1899
|
this.dependencyTracker = new ReportingDependencyTracker();
|
|
1900
|
+
this.configEnv = configEnv;
|
|
1820
1901
|
}
|
|
1821
1902
|
/**
|
|
1822
1903
|
* Run all verification tasks
|
|
@@ -1825,7 +1906,8 @@ var VerificationRunner = class {
|
|
|
1825
1906
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1826
1907
|
const wallStart = Date.now();
|
|
1827
1908
|
this.dependencyTracker.initialize(tasks);
|
|
1828
|
-
const
|
|
1909
|
+
const baseEnv = mergeEnv({}, this.configEnv);
|
|
1910
|
+
const results = await this.runNodes(tasks, "", "parallel", baseEnv);
|
|
1829
1911
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1830
1912
|
const durationMs = Date.now() - wallStart;
|
|
1831
1913
|
const allOk = results.every((r) => r.ok);
|
|
@@ -1840,7 +1922,7 @@ var VerificationRunner = class {
|
|
|
1840
1922
|
/**
|
|
1841
1923
|
* Run a list of nodes with the appropriate strategy
|
|
1842
1924
|
*/
|
|
1843
|
-
async runNodes(nodes, parentPath, strategy = "parallel") {
|
|
1925
|
+
async runNodes(nodes, parentPath, strategy = "parallel", inheritedEnv = {}) {
|
|
1844
1926
|
const filteredNodes = nodes.filter(
|
|
1845
1927
|
(node) => hasMatchingDescendant(node, parentPath, this.options.filter)
|
|
1846
1928
|
);
|
|
@@ -1850,19 +1932,21 @@ var VerificationRunner = class {
|
|
|
1850
1932
|
switch (strategy) {
|
|
1851
1933
|
case "parallel":
|
|
1852
1934
|
return Promise.all(
|
|
1853
|
-
filteredNodes.map(
|
|
1935
|
+
filteredNodes.map(
|
|
1936
|
+
(node) => this.runNode(node, parentPath, inheritedEnv)
|
|
1937
|
+
)
|
|
1854
1938
|
);
|
|
1855
1939
|
case "sequential": {
|
|
1856
1940
|
const results = [];
|
|
1857
1941
|
for (const node of filteredNodes) {
|
|
1858
|
-
results.push(await this.runNode(node, parentPath));
|
|
1942
|
+
results.push(await this.runNode(node, parentPath, inheritedEnv));
|
|
1859
1943
|
}
|
|
1860
1944
|
return results;
|
|
1861
1945
|
}
|
|
1862
1946
|
case "fail-fast": {
|
|
1863
1947
|
const results = [];
|
|
1864
1948
|
for (const node of filteredNodes) {
|
|
1865
|
-
const result = await this.runNode(node, parentPath);
|
|
1949
|
+
const result = await this.runNode(node, parentPath, inheritedEnv);
|
|
1866
1950
|
results.push(result);
|
|
1867
1951
|
if (!result.ok) {
|
|
1868
1952
|
break;
|
|
@@ -1875,8 +1959,9 @@ var VerificationRunner = class {
|
|
|
1875
1959
|
/**
|
|
1876
1960
|
* Run a single node (leaf or group)
|
|
1877
1961
|
*/
|
|
1878
|
-
async runNode(node, parentPath) {
|
|
1962
|
+
async runNode(node, parentPath, inheritedEnv = {}) {
|
|
1879
1963
|
const path = buildTaskPath(parentPath, node.key);
|
|
1964
|
+
const nodeEnv = mergeEnv(inheritedEnv, node.env);
|
|
1880
1965
|
this.dependencyTracker.markActive(path);
|
|
1881
1966
|
this.callbacks.onTaskStart?.(path, node.key);
|
|
1882
1967
|
if (node.children && node.children.length > 0) {
|
|
@@ -1884,7 +1969,8 @@ var VerificationRunner = class {
|
|
|
1884
1969
|
const childResults = await this.runNodes(
|
|
1885
1970
|
node.children,
|
|
1886
1971
|
path,
|
|
1887
|
-
node.strategy ?? "parallel"
|
|
1972
|
+
node.strategy ?? "parallel",
|
|
1973
|
+
nodeEnv
|
|
1888
1974
|
);
|
|
1889
1975
|
const durationMs2 = Date.now() - start;
|
|
1890
1976
|
const allOk = childResults.every((r) => r.ok || r.suppressed);
|
|
@@ -1923,7 +2009,13 @@ var VerificationRunner = class {
|
|
|
1923
2009
|
this.callbacks.onTaskComplete?.(result2);
|
|
1924
2010
|
return result2;
|
|
1925
2011
|
}
|
|
1926
|
-
const
|
|
2012
|
+
const passthrough = this.options.passthrough;
|
|
2013
|
+
const command = normalizeCommand(
|
|
2014
|
+
node.run,
|
|
2015
|
+
node.timeout,
|
|
2016
|
+
passthrough,
|
|
2017
|
+
nodeEnv
|
|
2018
|
+
);
|
|
1927
2019
|
const cwd = this.options.cwd ?? process.cwd();
|
|
1928
2020
|
const { code, output, durationMs, killed, timedOut } = await executeCommand(
|
|
1929
2021
|
command,
|
|
@@ -2021,10 +2113,15 @@ async function verify(config, cliOptions) {
|
|
|
2021
2113
|
}
|
|
2022
2114
|
const reporter = createReporter(options);
|
|
2023
2115
|
reporter.onStart?.(config.tasks);
|
|
2024
|
-
const runner = new VerificationRunner(
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2116
|
+
const runner = new VerificationRunner(
|
|
2117
|
+
options,
|
|
2118
|
+
void 0,
|
|
2119
|
+
{
|
|
2120
|
+
onTaskStart: (path, key) => reporter.onTaskStart(path, key),
|
|
2121
|
+
onTaskComplete: (result2) => reporter.onTaskComplete(result2)
|
|
2122
|
+
},
|
|
2123
|
+
config.env
|
|
2124
|
+
);
|
|
2028
2125
|
const result = await runner.run(config.tasks);
|
|
2029
2126
|
reporter.onFinish?.();
|
|
2030
2127
|
reporter.outputLogs(result.tasks, options.logs ?? "failed");
|