@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/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: z.record(z.string(), z.string()).optional(),
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,9 @@ 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
+ quiet: cliOptions?.quiet ?? configOptions?.quiet ?? false,
127
+ passthrough: cliOptions?.passthrough ?? configOptions?.passthrough
122
128
  };
123
129
  }
124
130
 
@@ -508,7 +514,7 @@ function extractOptimizedCommand(cwd, scriptContent) {
508
514
  const match = scriptContent.match(tool.pattern);
509
515
  if (match && binaryExists(cwd, tool.binary)) {
510
516
  const args = tool.getArgs(match, scriptContent);
511
- const command = args ? `./node_modules/.bin/${tool.binary} ${args}` : `./node_modules/.bin/${tool.binary}`;
517
+ const command = args ? `${tool.binary} ${args}` : tool.binary;
512
518
  return { command, parser: tool.parser };
513
519
  }
514
520
  }
@@ -585,7 +591,7 @@ function detectTasks(cwd) {
585
591
  const packageManager = detectPackageManager(cwd);
586
592
  const tasks = detectFromPackageJson(cwd);
587
593
  return tasks.map((task) => {
588
- if (task.command.startsWith("./")) {
594
+ if (!task.command.startsWith("npm run ")) {
589
595
  return task;
590
596
  }
591
597
  return {
@@ -621,12 +627,15 @@ function generateSkeleton(format) {
621
627
  return `${importStatement}
622
628
 
623
629
  export default defineConfig({
630
+ env: {
631
+ NO_COLOR: "1",
632
+ },
624
633
  tasks: [
625
634
  // Add your verification tasks here
626
635
  // Example:
627
- // { key: "format", run: "pnpm lint" },
628
- // { key: "types", run: "pnpm typecheck" },
629
- // { key: "test", run: "pnpm test" },
636
+ // { key: "format", run: "biome check ." },
637
+ // { key: "types", run: "tsc --noEmit" },
638
+ // { key: "test", run: "vitest run" },
630
639
  ],
631
640
  })
632
641
  `;
@@ -641,6 +650,9 @@ function generateConfigContent(tasks, format) {
641
650
  return `${importStatement}
642
651
 
643
652
  export default defineConfig({
653
+ env: {
654
+ NO_COLOR: "1",
655
+ },
644
656
  tasks: [
645
657
  ${taskLines.join(",\n")},
646
658
  ],
@@ -945,20 +957,36 @@ var vitestParser = {
945
957
  id: "vitest",
946
958
  parse(output, exitCode) {
947
959
  const cleanOutput = stripAnsi(output);
948
- const testsMatch = cleanOutput.match(/Tests\s+(\d+)\s+passed\s*\((\d+)\)/m);
960
+ const testsLineMatch = cleanOutput.match(/Tests\s+(.+?)\s*\((\d+)\)/m);
949
961
  const durationMatch = cleanOutput.match(/Duration\s+([\d.]+(?:ms|s))\b/m);
950
- if (!testsMatch) {
962
+ if (!testsLineMatch) {
951
963
  return null;
952
964
  }
953
- const passed = Number.parseInt(testsMatch[1], 10);
954
- const total = Number.parseInt(testsMatch[2], 10);
965
+ const statsStr = testsLineMatch[1];
966
+ const total = Number.parseInt(testsLineMatch[2], 10);
967
+ const passedMatch = statsStr.match(/(\d+)\s+passed/);
968
+ const failedMatch = statsStr.match(/(\d+)\s+failed/);
969
+ const skippedMatch = statsStr.match(/(\d+)\s+skipped/);
970
+ const passed = passedMatch ? Number.parseInt(passedMatch[1], 10) : 0;
971
+ const skipped = skippedMatch ? Number.parseInt(skippedMatch[1], 10) : 0;
955
972
  const duration = durationMatch ? durationMatch[1] : void 0;
973
+ const failed = failedMatch ? Number.parseInt(failedMatch[1], 10) : total - passed - skipped;
974
+ const ran = passed + failed;
975
+ let summary;
976
+ if (ran === 0 && skipped > 0) {
977
+ summary = `${skipped} tests skipped (no matches)`;
978
+ } else if (skipped > 0) {
979
+ summary = exitCode === 0 ? `passed ${passed}/${ran} tests (${skipped} skipped)` : `passed ${passed}/${ran} tests (${skipped} skipped, some failed)`;
980
+ } else {
981
+ summary = exitCode === 0 ? `passed ${passed}/${total} tests` : `passed ${passed}/${total} tests (some failed)`;
982
+ }
956
983
  return {
957
- summary: exitCode === 0 ? `passed ${passed}/${total} tests` : `passed ${passed}/${total} tests (some failed)`,
984
+ summary,
958
985
  metrics: {
959
986
  passed,
960
987
  total,
961
- failed: total - passed,
988
+ failed,
989
+ skipped,
962
990
  duration
963
991
  }
964
992
  };
@@ -1095,20 +1123,31 @@ var cursor = {
1095
1123
  moveToStart: "\x1B[0G",
1096
1124
  clearLine: "\x1B[2K"
1097
1125
  };
1098
- function shouldUseColors(options) {
1126
+ function defaultTerminalContext() {
1127
+ return { isTTY: !!process.stdout.isTTY, env: process.env };
1128
+ }
1129
+ function shouldUseColors(options, ctx) {
1099
1130
  if (options.noColor) return false;
1100
1131
  if (options.format === "json") return false;
1101
- if (!process.stdout.isTTY) return false;
1102
- if ("NO_COLOR" in process.env) return false;
1103
- if (process.env.TERM === "dumb") return false;
1132
+ if (!ctx.isTTY) return false;
1133
+ if ("NO_COLOR" in ctx.env) return false;
1134
+ if (ctx.env.TERM === "dumb") return false;
1135
+ return true;
1136
+ }
1137
+ function shouldUseLiveDashboard(options, ctx) {
1138
+ if (options.noTty) return false;
1139
+ if (ctx.env.TURBO_IS_TUI === "true") return false;
1140
+ if (ctx.isTTY && ctx.env.TURBO_HASH != null) return false;
1141
+ if (!ctx.isTTY) return false;
1104
1142
  return true;
1105
1143
  }
1106
1144
  var BaseReporter = class {
1107
1145
  colorEnabled;
1108
1146
  stream;
1109
1147
  taskDepths = /* @__PURE__ */ new Map();
1110
- constructor(options = {}) {
1111
- this.colorEnabled = shouldUseColors(options);
1148
+ constructor(options = {}, ctx) {
1149
+ const termCtx = ctx ?? defaultTerminalContext();
1150
+ this.colorEnabled = shouldUseColors(options, termCtx);
1112
1151
  this.stream = options.format === "json" ? process.stderr : process.stdout;
1113
1152
  }
1114
1153
  /**
@@ -1130,10 +1169,10 @@ var BaseReporter = class {
1130
1169
  return this.colorEnabled ? this.c(ansi.red, "\u2717") : "FAIL";
1131
1170
  }
1132
1171
  /**
1133
- * Get suppressed mark (⊘ or SUPPRESSED)
1172
+ * Get blocked mark (⊘ or BLOCK)
1134
1173
  */
1135
- suppressedMark() {
1136
- return this.colorEnabled ? this.c(ansi.yellow, "\u2298") : "SUPPRESSED";
1174
+ blockedMark() {
1175
+ return this.colorEnabled ? this.c(ansi.yellow, "\u2298") : "BLOCK";
1137
1176
  }
1138
1177
  /**
1139
1178
  * Get arrow symbol (→ or ->)
@@ -1161,6 +1200,22 @@ var BaseReporter = class {
1161
1200
  this.taskDepths.set(path, depth);
1162
1201
  });
1163
1202
  }
1203
+ /**
1204
+ * Format a completed task result line in name-first format.
1205
+ * Shared by LiveDashboardReporter and SequentialReporter.
1206
+ */
1207
+ formatResultLine(name, result) {
1208
+ const duration = this.c(ansi.dim, `${result.durationMs}ms`);
1209
+ if (result.blocked) {
1210
+ const reason = result.blockedBy ? `by ${result.blockedBy}` : "by dependency";
1211
+ return `${this.blockedMark()} ${this.c(ansi.bold, name)} blocked ${this.c(ansi.dim, `(${reason}, ${duration})`)}`;
1212
+ }
1213
+ const summary = this.extractSummary(result);
1214
+ if (result.ok) {
1215
+ return `${this.okMark()} ${this.c(ansi.bold, name)} verified ${this.c(ansi.dim, `(${summary}, ${duration})`)}`;
1216
+ }
1217
+ return `${this.failMark()} ${this.c(ansi.bold, name)} failed ${this.c(ansi.dim, `(${summary}, ${duration})`)}`;
1218
+ }
1164
1219
  /**
1165
1220
  * Extract summary from task result
1166
1221
  */
@@ -1196,7 +1251,7 @@ var BaseReporter = class {
1196
1251
  for (const r of flatResults) {
1197
1252
  if (r.children) continue;
1198
1253
  if (logsMode === "failed" && r.ok) continue;
1199
- if (r.suppressed) continue;
1254
+ if (r.blocked) continue;
1200
1255
  const status = r.ok ? this.c(ansi.green, "OK") : this.c(ansi.red, "FAIL");
1201
1256
  this.stream.write(
1202
1257
  `
@@ -1221,8 +1276,8 @@ var LiveDashboardReporter = class extends BaseReporter {
1221
1276
  taskOrder = [];
1222
1277
  spinner;
1223
1278
  lineCount = 0;
1224
- constructor(options = {}) {
1225
- super(options);
1279
+ constructor(options = {}, ctx) {
1280
+ super(options, ctx);
1226
1281
  this.topLevelOnly = options.topLevelOnly ?? false;
1227
1282
  this.spinner = new SpinnerManager();
1228
1283
  const cleanup = () => {
@@ -1283,16 +1338,7 @@ var LiveDashboardReporter = class extends BaseReporter {
1283
1338
  return `${indent}${this.arrow()} verifying ${this.c(ansi.bold, displayKey)} ${spinnerChar}`;
1284
1339
  }
1285
1340
  if (task.status === "completed" && task.result) {
1286
- const duration = this.c(ansi.dim, `${task.result.durationMs}ms`);
1287
- if (task.result.suppressed) {
1288
- const reason = task.result.suppressedBy ? `${task.result.suppressedBy} failed` : "dependency failed";
1289
- return `${indent}${this.suppressedMark()} suppressed ${this.c(ansi.bold, displayKey)} ${this.c(ansi.dim, `(${reason}, ${duration})`)}`;
1290
- }
1291
- const summary = this.extractSummary(task.result);
1292
- if (task.result.ok) {
1293
- return `${indent}${this.okMark()} verified ${this.c(ansi.bold, displayKey)} ${this.c(ansi.dim, `(${summary}, ${duration})`)}`;
1294
- }
1295
- return `${indent}${this.failMark()} failed ${this.c(ansi.bold, displayKey)} ${this.c(ansi.dim, `(${summary}, ${duration})`)}`;
1341
+ return `${indent}${this.formatResultLine(displayKey, task.result)}`;
1296
1342
  }
1297
1343
  return "";
1298
1344
  }
@@ -1342,8 +1388,8 @@ var LiveDashboardReporter = class extends BaseReporter {
1342
1388
  };
1343
1389
  var SequentialReporter = class extends BaseReporter {
1344
1390
  topLevelOnly;
1345
- constructor(options = {}) {
1346
- super(options);
1391
+ constructor(options = {}, ctx) {
1392
+ super(options, ctx);
1347
1393
  this.topLevelOnly = options.topLevelOnly ?? false;
1348
1394
  }
1349
1395
  onStart(tasks) {
@@ -1363,27 +1409,8 @@ var SequentialReporter = class extends BaseReporter {
1363
1409
  }
1364
1410
  onTaskComplete(result) {
1365
1411
  if (!this.shouldDisplay(result.path)) return;
1366
- const duration = this.c(ansi.dim, `${result.durationMs}ms`);
1367
- if (result.suppressed) {
1368
- const reason = result.suppressedBy ? `${result.suppressedBy} failed` : "dependency failed";
1369
- this.stream.write(
1370
- `${this.suppressedMark()} suppressed ${this.c(ansi.bold, result.path)} ${this.c(ansi.dim, `(${reason}, ${duration})`)}
1371
- `
1372
- );
1373
- return;
1374
- }
1375
- const summary = this.extractSummary(result);
1376
- if (result.ok) {
1377
- this.stream.write(
1378
- `${this.okMark()} verified ${this.c(ansi.bold, result.path)} ${this.c(ansi.dim, `(${summary}, ${duration})`)}
1379
- `
1380
- );
1381
- } else {
1382
- this.stream.write(
1383
- `${this.failMark()} failed ${this.c(ansi.bold, result.path)} ${this.c(ansi.dim, `(${summary}, ${duration})`)}
1384
- `
1385
- );
1386
- }
1412
+ this.stream.write(`${this.formatResultLine(result.path, result)}
1413
+ `);
1387
1414
  }
1388
1415
  onFinish() {
1389
1416
  }
@@ -1418,8 +1445,8 @@ var JSONReporter = class {
1418
1445
  code: t.code,
1419
1446
  durationMs: t.durationMs,
1420
1447
  summaryLine: t.summaryLine,
1421
- ...t.suppressed ? { suppressed: t.suppressed } : {},
1422
- ...t.suppressedBy ? { suppressedBy: t.suppressedBy } : {},
1448
+ ...t.blocked ? { blocked: t.blocked } : {},
1449
+ ...t.blockedBy ? { blockedBy: t.blockedBy } : {},
1423
1450
  ...t.children ? { children: this.serializeTasks(t.children) } : {}
1424
1451
  }));
1425
1452
  }
@@ -1442,17 +1469,22 @@ var QuietReporter = class extends BaseReporter {
1442
1469
  }
1443
1470
  };
1444
1471
  function createReporter(options) {
1472
+ const ctx = defaultTerminalContext();
1445
1473
  if (options.format === "json") {
1446
1474
  return new JSONReporter();
1447
1475
  }
1448
- if (process.stdout.isTTY && !options.noTty) {
1449
- return new LiveDashboardReporter(options);
1476
+ if (options.quiet) {
1477
+ return new QuietReporter(options, ctx);
1450
1478
  }
1451
- return new SequentialReporter(options);
1479
+ if (shouldUseLiveDashboard(options, ctx)) {
1480
+ return new LiveDashboardReporter(options, ctx);
1481
+ }
1482
+ return new SequentialReporter(options, ctx);
1452
1483
  }
1453
1484
 
1454
1485
  // src/runner.ts
1455
1486
  import { spawn } from "child_process";
1487
+ import { dirname, join as join4, resolve as resolve3 } from "path";
1456
1488
  import treeKill2 from "tree-kill";
1457
1489
 
1458
1490
  // src/dependency-tracker.ts
@@ -1634,9 +1666,9 @@ var ReportingDependencyTracker = class {
1634
1666
  if (this.results.has(pathOrKey)) {
1635
1667
  return Promise.resolve();
1636
1668
  }
1637
- return new Promise((resolve3) => {
1669
+ return new Promise((resolve4) => {
1638
1670
  const waiters = this.waiters.get(pathOrKey) ?? [];
1639
- waiters.push(resolve3);
1671
+ waiters.push(resolve4);
1640
1672
  this.waiters.set(pathOrKey, waiters);
1641
1673
  });
1642
1674
  }
@@ -1704,17 +1736,78 @@ var ReportingDependencyTracker = class {
1704
1736
  };
1705
1737
 
1706
1738
  // src/runner.ts
1707
- function normalizeCommand(run, nodeTimeout) {
1739
+ function mergeEnv(base, overlay) {
1740
+ if (!overlay) return { ...base };
1741
+ const result = { ...base };
1742
+ for (const [key, value] of Object.entries(overlay)) {
1743
+ if (value === null) {
1744
+ result[key] = null;
1745
+ } else {
1746
+ result[key] = value;
1747
+ }
1748
+ }
1749
+ return result;
1750
+ }
1751
+ function buildFinalEnv(processEnv, path, overlay) {
1752
+ const result = {};
1753
+ for (const [key, value] of Object.entries(processEnv)) {
1754
+ if (value !== void 0) {
1755
+ result[key] = value;
1756
+ }
1757
+ }
1758
+ result.PATH = path;
1759
+ if (overlay) {
1760
+ for (const [key, value] of Object.entries(overlay)) {
1761
+ if (value === null) {
1762
+ delete result[key];
1763
+ } else {
1764
+ result[key] = value;
1765
+ }
1766
+ }
1767
+ }
1768
+ return result;
1769
+ }
1770
+ function shellEscape(arg) {
1771
+ if (/[\s"'`$\\!&|;<>(){}[\]*?#~]/.test(arg)) {
1772
+ return `"${arg.replace(/"/g, '\\"')}"`;
1773
+ }
1774
+ return arg;
1775
+ }
1776
+ function buildNodeModulesPath(cwd) {
1777
+ const pathSeparator = process.platform === "win32" ? ";" : ":";
1778
+ const existingPath = process.env.PATH ?? "";
1779
+ const binPaths = [];
1780
+ let current = resolve3(cwd);
1781
+ while (true) {
1782
+ binPaths.push(join4(current, "node_modules", ".bin"));
1783
+ const parent = dirname(current);
1784
+ if (parent === current) break;
1785
+ current = parent;
1786
+ }
1787
+ return [...binPaths, existingPath].join(pathSeparator);
1788
+ }
1789
+ function normalizeCommand(run, nodeTimeout, passthrough, inheritedEnv) {
1708
1790
  if (typeof run === "string") {
1791
+ let cmd = run;
1792
+ if (passthrough && passthrough.length > 0) {
1793
+ const escapedArgs = passthrough.map(shellEscape).join(" ");
1794
+ cmd = `${run} ${escapedArgs}`;
1795
+ }
1709
1796
  return {
1710
- cmd: run,
1797
+ cmd,
1711
1798
  args: [],
1712
1799
  shell: true,
1713
- timeout: nodeTimeout
1800
+ timeout: nodeTimeout,
1801
+ env: inheritedEnv
1714
1802
  };
1715
1803
  }
1804
+ const args = passthrough ? [...run.args, ...passthrough] : run.args;
1805
+ const env = mergeEnv(inheritedEnv ?? {}, run.env);
1716
1806
  return {
1717
- ...run,
1807
+ cmd: run.cmd,
1808
+ args,
1809
+ cwd: run.cwd,
1810
+ env: Object.keys(env).length > 0 ? env : void 0,
1718
1811
  shell: false,
1719
1812
  // Command-level timeout takes precedence over node-level timeout
1720
1813
  timeout: run.timeout ?? nodeTimeout
@@ -1722,12 +1815,18 @@ function normalizeCommand(run, nodeTimeout) {
1722
1815
  }
1723
1816
  async function executeCommand(command, cwd, tracker, path) {
1724
1817
  const start = Date.now();
1725
- return new Promise((resolve3) => {
1818
+ return new Promise((resolve4) => {
1726
1819
  const useShell = command.shell || process.platform === "win32";
1820
+ const effectiveCwd = command.cwd ?? cwd;
1821
+ const finalEnv = buildFinalEnv(
1822
+ process.env,
1823
+ buildNodeModulesPath(effectiveCwd),
1824
+ command.env
1825
+ );
1727
1826
  const proc = spawn(command.cmd, command.args, {
1728
1827
  shell: useShell,
1729
- cwd: command.cwd ?? cwd,
1730
- env: { ...process.env, NO_COLOR: "1", ...command.env }
1828
+ cwd: effectiveCwd,
1829
+ env: finalEnv
1731
1830
  });
1732
1831
  if (tracker && path) {
1733
1832
  tracker.registerProcess(path, proc);
@@ -1760,7 +1859,7 @@ async function executeCommand(command, cwd, tracker, path) {
1760
1859
  }
1761
1860
  const durationMs = Date.now() - start;
1762
1861
  const killed = signal === "SIGTERM" || code === 143 || (tracker?.wasKilled(path ?? "") ?? false);
1763
- resolve3({
1862
+ resolve4({
1764
1863
  code: code ?? 1,
1765
1864
  output,
1766
1865
  durationMs,
@@ -1777,7 +1876,7 @@ async function executeCommand(command, cwd, tracker, path) {
1777
1876
  tracker.unregisterProcess(path);
1778
1877
  }
1779
1878
  const durationMs = Date.now() - start;
1780
- resolve3({
1879
+ resolve4({
1781
1880
  code: 1,
1782
1881
  output: `Failed to execute command: ${err.message}`,
1783
1882
  durationMs,
@@ -1812,11 +1911,13 @@ var VerificationRunner = class {
1812
1911
  options;
1813
1912
  callbacks;
1814
1913
  dependencyTracker;
1815
- constructor(options = {}, registry = defaultRegistry, callbacks = {}) {
1914
+ configEnv;
1915
+ constructor(options = {}, registry = defaultRegistry, callbacks = {}, configEnv = {}) {
1816
1916
  this.options = options;
1817
1917
  this.registry = registry;
1818
1918
  this.callbacks = callbacks;
1819
1919
  this.dependencyTracker = new ReportingDependencyTracker();
1920
+ this.configEnv = configEnv;
1820
1921
  }
1821
1922
  /**
1822
1923
  * Run all verification tasks
@@ -1825,7 +1926,8 @@ var VerificationRunner = class {
1825
1926
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1826
1927
  const wallStart = Date.now();
1827
1928
  this.dependencyTracker.initialize(tasks);
1828
- const results = await this.runNodes(tasks, "");
1929
+ const baseEnv = mergeEnv({}, this.configEnv);
1930
+ const results = await this.runNodes(tasks, "", "parallel", baseEnv);
1829
1931
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
1830
1932
  const durationMs = Date.now() - wallStart;
1831
1933
  const allOk = results.every((r) => r.ok);
@@ -1840,7 +1942,7 @@ var VerificationRunner = class {
1840
1942
  /**
1841
1943
  * Run a list of nodes with the appropriate strategy
1842
1944
  */
1843
- async runNodes(nodes, parentPath, strategy = "parallel") {
1945
+ async runNodes(nodes, parentPath, strategy = "parallel", inheritedEnv = {}) {
1844
1946
  const filteredNodes = nodes.filter(
1845
1947
  (node) => hasMatchingDescendant(node, parentPath, this.options.filter)
1846
1948
  );
@@ -1850,19 +1952,21 @@ var VerificationRunner = class {
1850
1952
  switch (strategy) {
1851
1953
  case "parallel":
1852
1954
  return Promise.all(
1853
- filteredNodes.map((node) => this.runNode(node, parentPath))
1955
+ filteredNodes.map(
1956
+ (node) => this.runNode(node, parentPath, inheritedEnv)
1957
+ )
1854
1958
  );
1855
1959
  case "sequential": {
1856
1960
  const results = [];
1857
1961
  for (const node of filteredNodes) {
1858
- results.push(await this.runNode(node, parentPath));
1962
+ results.push(await this.runNode(node, parentPath, inheritedEnv));
1859
1963
  }
1860
1964
  return results;
1861
1965
  }
1862
1966
  case "fail-fast": {
1863
1967
  const results = [];
1864
1968
  for (const node of filteredNodes) {
1865
- const result = await this.runNode(node, parentPath);
1969
+ const result = await this.runNode(node, parentPath, inheritedEnv);
1866
1970
  results.push(result);
1867
1971
  if (!result.ok) {
1868
1972
  break;
@@ -1875,8 +1979,9 @@ var VerificationRunner = class {
1875
1979
  /**
1876
1980
  * Run a single node (leaf or group)
1877
1981
  */
1878
- async runNode(node, parentPath) {
1982
+ async runNode(node, parentPath, inheritedEnv = {}) {
1879
1983
  const path = buildTaskPath(parentPath, node.key);
1984
+ const nodeEnv = mergeEnv(inheritedEnv, node.env);
1880
1985
  this.dependencyTracker.markActive(path);
1881
1986
  this.callbacks.onTaskStart?.(path, node.key);
1882
1987
  if (node.children && node.children.length > 0) {
@@ -1884,12 +1989,13 @@ var VerificationRunner = class {
1884
1989
  const childResults = await this.runNodes(
1885
1990
  node.children,
1886
1991
  path,
1887
- node.strategy ?? "parallel"
1992
+ node.strategy ?? "parallel",
1993
+ nodeEnv
1888
1994
  );
1889
1995
  const durationMs2 = Date.now() - start;
1890
- const allOk = childResults.every((r) => r.ok || r.suppressed);
1891
- const allSuppressed = childResults.length > 0 && childResults.every((r) => r.suppressed);
1892
- const anySuppressed = childResults.some((r) => r.suppressed);
1996
+ const allOk = childResults.every((r) => r.ok || r.blocked);
1997
+ const allBlocked = childResults.length > 0 && childResults.every((r) => r.blocked);
1998
+ const anyBlocked = childResults.some((r) => r.blocked);
1893
1999
  const result2 = {
1894
2000
  key: node.key,
1895
2001
  path,
@@ -1900,10 +2006,10 @@ var VerificationRunner = class {
1900
2006
  summaryLine: allOk ? node.successLabel ?? `${node.key}: all passed` : node.failureLabel ?? `${node.key}: some failed`,
1901
2007
  children: childResults
1902
2008
  };
1903
- if (allSuppressed) {
1904
- result2.suppressed = true;
1905
- result2.suppressedBy = childResults[0].suppressedBy;
1906
- } else if (anySuppressed && !allOk) {
2009
+ if (allBlocked) {
2010
+ result2.blocked = true;
2011
+ result2.blockedBy = childResults[0].blockedBy;
2012
+ } else if (anyBlocked && !allOk) {
1907
2013
  }
1908
2014
  this.dependencyTracker.recordResult(result2);
1909
2015
  this.callbacks.onTaskComplete?.(result2);
@@ -1923,7 +2029,13 @@ var VerificationRunner = class {
1923
2029
  this.callbacks.onTaskComplete?.(result2);
1924
2030
  return result2;
1925
2031
  }
1926
- const command = normalizeCommand(node.run, node.timeout);
2032
+ const passthrough = this.options.passthrough;
2033
+ const command = normalizeCommand(
2034
+ node.run,
2035
+ node.timeout,
2036
+ passthrough,
2037
+ nodeEnv
2038
+ );
1927
2039
  const cwd = this.options.cwd ?? process.cwd();
1928
2040
  const { code, output, durationMs, killed, timedOut } = await executeCommand(
1929
2041
  command,
@@ -1958,8 +2070,8 @@ var VerificationRunner = class {
1958
2070
  durationMs,
1959
2071
  output,
1960
2072
  summaryLine: `${node.key}: terminated`,
1961
- suppressed: true,
1962
- suppressedBy: failedDep ?? "unknown"
2073
+ blocked: true,
2074
+ blockedBy: failedDep ?? "unknown"
1963
2075
  };
1964
2076
  this.dependencyTracker.recordResult(result2);
1965
2077
  this.callbacks.onTaskComplete?.(result2);
@@ -1995,8 +2107,8 @@ var VerificationRunner = class {
1995
2107
  if (failedDep) {
1996
2108
  result = {
1997
2109
  ...result,
1998
- suppressed: true,
1999
- suppressedBy: failedDep
2110
+ blocked: true,
2111
+ blockedBy: failedDep
2000
2112
  };
2001
2113
  }
2002
2114
  }
@@ -2021,10 +2133,15 @@ async function verify(config, cliOptions) {
2021
2133
  }
2022
2134
  const reporter = createReporter(options);
2023
2135
  reporter.onStart?.(config.tasks);
2024
- const runner = new VerificationRunner(options, void 0, {
2025
- onTaskStart: (path, key) => reporter.onTaskStart(path, key),
2026
- onTaskComplete: (result2) => reporter.onTaskComplete(result2)
2027
- });
2136
+ const runner = new VerificationRunner(
2137
+ options,
2138
+ void 0,
2139
+ {
2140
+ onTaskStart: (path, key) => reporter.onTaskStart(path, key),
2141
+ onTaskComplete: (result2) => reporter.onTaskComplete(result2)
2142
+ },
2143
+ config.env
2144
+ );
2028
2145
  const result = await runner.run(config.tasks);
2029
2146
  reporter.onFinish?.();
2030
2147
  reporter.outputLogs(result.tasks, options.logs ?? "failed");