@halecraft/verify 1.0.0 → 1.1.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 +117 -6
- package/bin/verify.mjs +111 -135
- package/dist/index.d.ts +133 -7
- package/dist/index.js +373 -86
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,14 +1,72 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// src/config.ts
|
|
9
2
|
import { existsSync } from "fs";
|
|
10
3
|
import { join, resolve } from "path";
|
|
11
4
|
import { pathToFileURL } from "url";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var ConfigError = class extends Error {
|
|
7
|
+
constructor(message, configPath) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.configPath = configPath;
|
|
10
|
+
this.name = "ConfigError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var VerificationCommandSchema = z.object({
|
|
14
|
+
cmd: z.string(),
|
|
15
|
+
args: z.array(z.string()),
|
|
16
|
+
cwd: z.string().optional(),
|
|
17
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
18
|
+
timeout: z.number().positive().optional()
|
|
19
|
+
});
|
|
20
|
+
var VerificationNodeSchema = z.lazy(
|
|
21
|
+
() => z.object({
|
|
22
|
+
key: z.string().min(1, "Task key cannot be empty").refine((key) => !key.includes(":"), {
|
|
23
|
+
message: "Task key cannot contain ':' (reserved for paths)"
|
|
24
|
+
}),
|
|
25
|
+
name: z.string().optional(),
|
|
26
|
+
run: z.union([z.string(), VerificationCommandSchema]).optional(),
|
|
27
|
+
children: z.array(VerificationNodeSchema).optional(),
|
|
28
|
+
strategy: z.enum(["parallel", "sequential", "fail-fast"]).optional(),
|
|
29
|
+
parser: z.string().optional(),
|
|
30
|
+
successLabel: z.string().optional(),
|
|
31
|
+
failureLabel: z.string().optional(),
|
|
32
|
+
reportingDependsOn: z.array(z.string()).optional(),
|
|
33
|
+
timeout: z.number().positive().optional()
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
var VerifyOptionsSchema = z.object({
|
|
37
|
+
logs: z.enum(["all", "failed", "none"]).optional(),
|
|
38
|
+
format: z.enum(["human", "json"]).optional(),
|
|
39
|
+
filter: z.array(z.string()).optional(),
|
|
40
|
+
cwd: z.string().optional(),
|
|
41
|
+
noColor: z.boolean().optional(),
|
|
42
|
+
topLevelOnly: z.boolean().optional(),
|
|
43
|
+
noTty: z.boolean().optional()
|
|
44
|
+
});
|
|
45
|
+
var PackageDiscoveryOptionsSchema = z.object({
|
|
46
|
+
patterns: z.array(z.string()).optional(),
|
|
47
|
+
filter: z.array(z.string()).optional(),
|
|
48
|
+
changed: z.boolean().optional()
|
|
49
|
+
});
|
|
50
|
+
var VerifyConfigSchema = z.object({
|
|
51
|
+
tasks: z.array(VerificationNodeSchema),
|
|
52
|
+
packages: PackageDiscoveryOptionsSchema.optional(),
|
|
53
|
+
options: VerifyOptionsSchema.optional()
|
|
54
|
+
});
|
|
55
|
+
function validateConfig(value, configPath) {
|
|
56
|
+
const result = VerifyConfigSchema.safeParse(value);
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
const errors = result.error.issues.map((issue) => {
|
|
59
|
+
const path = issue.path.join(".");
|
|
60
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
61
|
+
});
|
|
62
|
+
throw new ConfigError(
|
|
63
|
+
`Invalid config:
|
|
64
|
+
- ${errors.join("\n - ")}`,
|
|
65
|
+
configPath
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return result.data;
|
|
69
|
+
}
|
|
12
70
|
function defineConfig(config) {
|
|
13
71
|
return config;
|
|
14
72
|
}
|
|
@@ -38,9 +96,9 @@ async function loadConfig(configPath) {
|
|
|
38
96
|
const fileUrl = pathToFileURL(absolutePath).href;
|
|
39
97
|
const module = await import(fileUrl);
|
|
40
98
|
if (!module.default) {
|
|
41
|
-
throw new
|
|
99
|
+
throw new ConfigError(`Config file must have a default export`, configPath);
|
|
42
100
|
}
|
|
43
|
-
return module.default;
|
|
101
|
+
return validateConfig(module.default, configPath);
|
|
44
102
|
}
|
|
45
103
|
async function loadConfigFromCwd(cwd, configPath) {
|
|
46
104
|
if (configPath) {
|
|
@@ -65,8 +123,26 @@ function mergeOptions(configOptions, cliOptions) {
|
|
|
65
123
|
}
|
|
66
124
|
|
|
67
125
|
// src/discovery.ts
|
|
68
|
-
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
126
|
+
import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
|
|
69
127
|
import { join as join2, relative } from "path";
|
|
128
|
+
|
|
129
|
+
// src/schemas/package-json.ts
|
|
130
|
+
import { z as z2 } from "zod";
|
|
131
|
+
var PackageJsonSchema = z2.object({
|
|
132
|
+
name: z2.string().optional(),
|
|
133
|
+
scripts: z2.record(z2.string(), z2.string()).optional()
|
|
134
|
+
}).passthrough();
|
|
135
|
+
function parsePackageJson(content) {
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(content);
|
|
138
|
+
const result = PackageJsonSchema.safeParse(parsed);
|
|
139
|
+
return result.success ? result.data : null;
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/discovery.ts
|
|
70
146
|
var DEFAULT_PATTERNS = ["packages/*", "apps/*"];
|
|
71
147
|
function findMatchingDirs(rootDir, patterns) {
|
|
72
148
|
const results = [];
|
|
@@ -98,8 +174,9 @@ function getPackageName(packageDir) {
|
|
|
98
174
|
return null;
|
|
99
175
|
}
|
|
100
176
|
try {
|
|
101
|
-
const content =
|
|
102
|
-
|
|
177
|
+
const content = readFileSync(packageJsonPath, "utf-8");
|
|
178
|
+
const parsed = parsePackageJson(content);
|
|
179
|
+
return parsed?.name ?? null;
|
|
103
180
|
} catch {
|
|
104
181
|
return null;
|
|
105
182
|
}
|
|
@@ -132,8 +209,133 @@ async function hasPackageChanged(_packagePath, _baseBranch = "main") {
|
|
|
132
209
|
return true;
|
|
133
210
|
}
|
|
134
211
|
|
|
212
|
+
// src/filter.ts
|
|
213
|
+
import leven from "leven";
|
|
214
|
+
|
|
215
|
+
// src/tree.ts
|
|
216
|
+
var PATH_SEPARATOR = ":";
|
|
217
|
+
function buildTaskPath(parentPath, key) {
|
|
218
|
+
return parentPath ? `${parentPath}${PATH_SEPARATOR}${key}` : key;
|
|
219
|
+
}
|
|
220
|
+
function walkNodes(nodes, visitor, parentPath = "", depth = 0) {
|
|
221
|
+
for (const node of nodes) {
|
|
222
|
+
const path = buildTaskPath(parentPath, node.key);
|
|
223
|
+
visitor(node, path, depth);
|
|
224
|
+
if (node.children) {
|
|
225
|
+
walkNodes(node.children, visitor, path, depth + 1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function collectPaths(nodes) {
|
|
230
|
+
const paths = [];
|
|
231
|
+
walkNodes(nodes, (_node, path) => {
|
|
232
|
+
paths.push(path);
|
|
233
|
+
});
|
|
234
|
+
return paths;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/filter.ts
|
|
238
|
+
var TaskNotFoundError = class extends Error {
|
|
239
|
+
constructor(filter, suggestion, availableTasks) {
|
|
240
|
+
super(buildErrorMessage(filter, suggestion, availableTasks));
|
|
241
|
+
this.filter = filter;
|
|
242
|
+
this.suggestion = suggestion;
|
|
243
|
+
this.availableTasks = availableTasks;
|
|
244
|
+
this.name = "TaskNotFoundError";
|
|
245
|
+
}
|
|
246
|
+
exitCode = 2;
|
|
247
|
+
};
|
|
248
|
+
var AmbiguousTaskError = class extends Error {
|
|
249
|
+
constructor(filter, matches) {
|
|
250
|
+
super(buildAmbiguousErrorMessage(filter, matches));
|
|
251
|
+
this.filter = filter;
|
|
252
|
+
this.matches = matches;
|
|
253
|
+
this.name = "AmbiguousTaskError";
|
|
254
|
+
}
|
|
255
|
+
exitCode = 2;
|
|
256
|
+
};
|
|
257
|
+
function buildErrorMessage(filter, suggestion, availableTasks) {
|
|
258
|
+
let message = `Task "${filter}" not found.`;
|
|
259
|
+
if (suggestion) {
|
|
260
|
+
message += `
|
|
261
|
+
|
|
262
|
+
Did you mean "${suggestion}"?`;
|
|
263
|
+
}
|
|
264
|
+
message += "\n\nAvailable tasks:";
|
|
265
|
+
for (const task of availableTasks) {
|
|
266
|
+
message += `
|
|
267
|
+
${task}`;
|
|
268
|
+
}
|
|
269
|
+
return message;
|
|
270
|
+
}
|
|
271
|
+
function buildAmbiguousErrorMessage(filter, matches) {
|
|
272
|
+
let message = `Task "${filter}" is ambiguous.`;
|
|
273
|
+
message += "\n\nMatches multiple tasks:";
|
|
274
|
+
for (const match of matches) {
|
|
275
|
+
message += `
|
|
276
|
+
${match}`;
|
|
277
|
+
}
|
|
278
|
+
return message;
|
|
279
|
+
}
|
|
280
|
+
function findBestSuggestion(availablePaths, invalidFilter) {
|
|
281
|
+
let bestPath;
|
|
282
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
283
|
+
const threshold = Math.max(2, Math.floor(invalidFilter.length / 3));
|
|
284
|
+
for (const path of availablePaths) {
|
|
285
|
+
const distance = leven(invalidFilter, path);
|
|
286
|
+
if (distance < bestDistance && distance <= threshold) {
|
|
287
|
+
bestDistance = distance;
|
|
288
|
+
bestPath = path;
|
|
289
|
+
}
|
|
290
|
+
const lastSegment = path.split(PATH_SEPARATOR).pop();
|
|
291
|
+
if (lastSegment && lastSegment !== path) {
|
|
292
|
+
const segmentDistance = leven(invalidFilter, lastSegment);
|
|
293
|
+
if (segmentDistance < bestDistance && segmentDistance <= threshold) {
|
|
294
|
+
bestDistance = segmentDistance;
|
|
295
|
+
bestPath = path;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return bestPath;
|
|
300
|
+
}
|
|
301
|
+
function resolveFilter(filter, availablePaths) {
|
|
302
|
+
if (availablePaths.includes(filter)) {
|
|
303
|
+
return { original: filter, resolved: filter, wasShortcut: false };
|
|
304
|
+
}
|
|
305
|
+
const prefixMatches = availablePaths.filter(
|
|
306
|
+
(path) => path === filter || path.startsWith(`${filter}${PATH_SEPARATOR}`)
|
|
307
|
+
);
|
|
308
|
+
if (prefixMatches.length > 0) {
|
|
309
|
+
return { original: filter, resolved: filter, wasShortcut: false };
|
|
310
|
+
}
|
|
311
|
+
const childMatches = availablePaths.filter((path) => {
|
|
312
|
+
const lastSegment = path.split(PATH_SEPARATOR).pop();
|
|
313
|
+
return lastSegment === filter;
|
|
314
|
+
});
|
|
315
|
+
if (childMatches.length === 1) {
|
|
316
|
+
return {
|
|
317
|
+
original: filter,
|
|
318
|
+
resolved: childMatches[0],
|
|
319
|
+
wasShortcut: true
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (childMatches.length > 1) {
|
|
323
|
+
throw new AmbiguousTaskError(filter, childMatches);
|
|
324
|
+
}
|
|
325
|
+
const suggestion = findBestSuggestion(availablePaths, filter);
|
|
326
|
+
throw new TaskNotFoundError(filter, suggestion, availablePaths);
|
|
327
|
+
}
|
|
328
|
+
function resolveFilters(nodes, filters) {
|
|
329
|
+
const availablePaths = collectPaths(nodes);
|
|
330
|
+
const resolved = [];
|
|
331
|
+
for (const filter of filters) {
|
|
332
|
+
resolved.push(resolveFilter(filter, availablePaths));
|
|
333
|
+
}
|
|
334
|
+
return resolved;
|
|
335
|
+
}
|
|
336
|
+
|
|
135
337
|
// src/init/detect.ts
|
|
136
|
-
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
338
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
137
339
|
import { join as join3 } from "path";
|
|
138
340
|
var TOOL_PATTERNS = [
|
|
139
341
|
// Biome
|
|
@@ -292,8 +494,8 @@ function readPackageJson(cwd) {
|
|
|
292
494
|
return null;
|
|
293
495
|
}
|
|
294
496
|
try {
|
|
295
|
-
const content =
|
|
296
|
-
return
|
|
497
|
+
const content = readFileSync2(packageJsonPath, "utf-8");
|
|
498
|
+
return parsePackageJson(content);
|
|
297
499
|
} catch {
|
|
298
500
|
return null;
|
|
299
501
|
}
|
|
@@ -764,6 +966,18 @@ var vitestParser = {
|
|
|
764
966
|
};
|
|
765
967
|
|
|
766
968
|
// src/parsers/index.ts
|
|
969
|
+
var parsers = {
|
|
970
|
+
/** Vitest/Jest test runner output parser */
|
|
971
|
+
vitest: "vitest",
|
|
972
|
+
/** TypeScript compiler (tsc/tsgo) output parser */
|
|
973
|
+
tsc: "tsc",
|
|
974
|
+
/** Biome/ESLint linter output parser */
|
|
975
|
+
biome: "biome",
|
|
976
|
+
/** Go test runner output parser */
|
|
977
|
+
gotest: "gotest",
|
|
978
|
+
/** Generic fallback parser */
|
|
979
|
+
generic: "generic"
|
|
980
|
+
};
|
|
767
981
|
var ParserRegistry = class {
|
|
768
982
|
parsers = /* @__PURE__ */ new Map();
|
|
769
983
|
constructor() {
|
|
@@ -791,24 +1005,24 @@ var ParserRegistry = class {
|
|
|
791
1005
|
detectParser(cmd) {
|
|
792
1006
|
const cmdLower = cmd.toLowerCase();
|
|
793
1007
|
if (cmdLower.includes("vitest") || cmdLower.includes("jest")) {
|
|
794
|
-
return
|
|
1008
|
+
return parsers.vitest;
|
|
795
1009
|
}
|
|
796
1010
|
if (cmdLower.includes("tsc") || cmdLower.includes("tsgo")) {
|
|
797
|
-
return
|
|
1011
|
+
return parsers.tsc;
|
|
798
1012
|
}
|
|
799
1013
|
if (cmdLower.includes("biome") || cmdLower.includes("eslint")) {
|
|
800
|
-
return
|
|
1014
|
+
return parsers.biome;
|
|
801
1015
|
}
|
|
802
1016
|
if (cmdLower.includes("go test") || cmdLower.includes("go") && cmdLower.includes("test")) {
|
|
803
|
-
return
|
|
1017
|
+
return parsers.gotest;
|
|
804
1018
|
}
|
|
805
|
-
return
|
|
1019
|
+
return parsers.generic;
|
|
806
1020
|
}
|
|
807
1021
|
/**
|
|
808
1022
|
* Parse output using the specified or auto-detected parser
|
|
809
1023
|
*/
|
|
810
1024
|
parse(output, exitCode, parserId, cmd) {
|
|
811
|
-
const id = parserId ?? (cmd ? this.detectParser(cmd) :
|
|
1025
|
+
const id = parserId ?? (cmd ? this.detectParser(cmd) : parsers.generic);
|
|
812
1026
|
const parser = this.parsers.get(id) ?? genericParser;
|
|
813
1027
|
const result = parser.parse(output, exitCode);
|
|
814
1028
|
if (result) {
|
|
@@ -940,16 +1154,12 @@ var BaseReporter = class {
|
|
|
940
1154
|
return this.taskDepths.get(path) ?? 0;
|
|
941
1155
|
}
|
|
942
1156
|
/**
|
|
943
|
-
*
|
|
1157
|
+
* Collect task depths from verification tree using walkNodes
|
|
944
1158
|
*/
|
|
945
|
-
collectTaskDepths(nodes
|
|
946
|
-
|
|
947
|
-
const path = parentPath ? `${parentPath}:${node.key}` : node.key;
|
|
1159
|
+
collectTaskDepths(nodes) {
|
|
1160
|
+
walkNodes(nodes, (_node, path, depth) => {
|
|
948
1161
|
this.taskDepths.set(path, depth);
|
|
949
|
-
|
|
950
|
-
this.collectTaskDepths(node.children, path, depth + 1);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
1162
|
+
});
|
|
953
1163
|
}
|
|
954
1164
|
/**
|
|
955
1165
|
* Extract summary from task result
|
|
@@ -1027,16 +1237,15 @@ var LiveDashboardReporter = class extends BaseReporter {
|
|
|
1027
1237
|
* Initialize task list from verification nodes
|
|
1028
1238
|
*/
|
|
1029
1239
|
onStart(tasks) {
|
|
1030
|
-
this.collectTasks(tasks
|
|
1240
|
+
this.collectTasks(tasks);
|
|
1031
1241
|
this.stream.write(cursor.hide);
|
|
1032
1242
|
this.spinner.start(() => this.redraw());
|
|
1033
1243
|
}
|
|
1034
1244
|
/**
|
|
1035
|
-
*
|
|
1245
|
+
* Collect tasks from verification tree using walkNodes
|
|
1036
1246
|
*/
|
|
1037
|
-
collectTasks(nodes
|
|
1038
|
-
|
|
1039
|
-
const path = parentPath ? `${parentPath}:${node.key}` : node.key;
|
|
1247
|
+
collectTasks(nodes) {
|
|
1248
|
+
walkNodes(nodes, (node, path, depth) => {
|
|
1040
1249
|
this.tasks.set(path, {
|
|
1041
1250
|
key: node.key,
|
|
1042
1251
|
path,
|
|
@@ -1045,10 +1254,7 @@ var LiveDashboardReporter = class extends BaseReporter {
|
|
|
1045
1254
|
});
|
|
1046
1255
|
this.taskOrder.push(path);
|
|
1047
1256
|
this.taskDepths.set(path, depth);
|
|
1048
|
-
|
|
1049
|
-
this.collectTasks(node.children, path, depth + 1);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1257
|
+
});
|
|
1052
1258
|
}
|
|
1053
1259
|
/**
|
|
1054
1260
|
* Get display key - shows :key for nested, key for root
|
|
@@ -1084,9 +1290,9 @@ var LiveDashboardReporter = class extends BaseReporter {
|
|
|
1084
1290
|
}
|
|
1085
1291
|
const summary = this.extractSummary(task.result);
|
|
1086
1292
|
if (task.result.ok) {
|
|
1087
|
-
return
|
|
1293
|
+
return `${indent}${this.okMark()} verified ${this.c(ansi.bold, displayKey)} ${this.c(ansi.dim, `(${summary}, ${duration})`)}`;
|
|
1088
1294
|
}
|
|
1089
|
-
return `${indent}${this.failMark()}
|
|
1295
|
+
return `${indent}${this.failMark()} failed ${this.c(ansi.bold, displayKey)} ${this.c(ansi.dim, `(${summary}, ${duration})`)}`;
|
|
1090
1296
|
}
|
|
1091
1297
|
return "";
|
|
1092
1298
|
}
|
|
@@ -1141,7 +1347,7 @@ var SequentialReporter = class extends BaseReporter {
|
|
|
1141
1347
|
this.topLevelOnly = options.topLevelOnly ?? false;
|
|
1142
1348
|
}
|
|
1143
1349
|
onStart(tasks) {
|
|
1144
|
-
this.collectTaskDepths(tasks
|
|
1350
|
+
this.collectTaskDepths(tasks);
|
|
1145
1351
|
}
|
|
1146
1352
|
/**
|
|
1147
1353
|
* Check if task should be displayed based on topLevelOnly flag
|
|
@@ -1166,13 +1372,18 @@ var SequentialReporter = class extends BaseReporter {
|
|
|
1166
1372
|
);
|
|
1167
1373
|
return;
|
|
1168
1374
|
}
|
|
1169
|
-
const mark = result.ok ? this.okMark() : this.failMark();
|
|
1170
|
-
const verb = result.ok ? "verified" : "failed";
|
|
1171
1375
|
const summary = this.extractSummary(result);
|
|
1172
|
-
|
|
1173
|
-
|
|
1376
|
+
if (result.ok) {
|
|
1377
|
+
this.stream.write(
|
|
1378
|
+
`${this.okMark()} verified ${this.c(ansi.bold, result.path)} ${this.c(ansi.dim, `(${summary}, ${duration})`)}
|
|
1174
1379
|
`
|
|
1175
|
-
|
|
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
|
+
}
|
|
1176
1387
|
}
|
|
1177
1388
|
onFinish() {
|
|
1178
1389
|
}
|
|
@@ -1242,23 +1453,10 @@ function createReporter(options) {
|
|
|
1242
1453
|
|
|
1243
1454
|
// src/runner.ts
|
|
1244
1455
|
import { spawn } from "child_process";
|
|
1456
|
+
import treeKill2 from "tree-kill";
|
|
1457
|
+
|
|
1458
|
+
// src/dependency-tracker.ts
|
|
1245
1459
|
import treeKill from "tree-kill";
|
|
1246
|
-
function normalizeCommand(run) {
|
|
1247
|
-
if (typeof run === "string") {
|
|
1248
|
-
const parts = run.split(/\s+/);
|
|
1249
|
-
return {
|
|
1250
|
-
cmd: parts[0],
|
|
1251
|
-
args: parts.slice(1)
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
if (Array.isArray(run)) {
|
|
1255
|
-
return {
|
|
1256
|
-
cmd: run[0],
|
|
1257
|
-
args: run[1]
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
return run;
|
|
1261
|
-
}
|
|
1262
1460
|
var ReportingDependencyTracker = class {
|
|
1263
1461
|
/** Map of task path/key to their results */
|
|
1264
1462
|
results = /* @__PURE__ */ new Map();
|
|
@@ -1276,26 +1474,22 @@ var ReportingDependencyTracker = class {
|
|
|
1276
1474
|
processes = /* @__PURE__ */ new Map();
|
|
1277
1475
|
/** Set of task paths that have been killed */
|
|
1278
1476
|
killedPaths = /* @__PURE__ */ new Set();
|
|
1477
|
+
/** Set of task paths that will actually run (based on filter) */
|
|
1478
|
+
activePaths = /* @__PURE__ */ new Set();
|
|
1279
1479
|
/**
|
|
1280
1480
|
* Initialize the tracker with all tasks from the verification tree.
|
|
1281
1481
|
* Also validates for circular dependencies and builds reverse dependency map.
|
|
1282
1482
|
*/
|
|
1283
|
-
initialize(nodes
|
|
1284
|
-
|
|
1285
|
-
const path = parentPath ? `${parentPath}:${node.key}` : node.key;
|
|
1483
|
+
initialize(nodes) {
|
|
1484
|
+
walkNodes(nodes, (node, path) => {
|
|
1286
1485
|
this.pathToKey.set(path, node.key);
|
|
1287
1486
|
this.keyToPath.set(node.key, path);
|
|
1288
1487
|
if (node.reportingDependsOn && node.reportingDependsOn.length > 0) {
|
|
1289
1488
|
this.dependencies.set(path, node.reportingDependsOn);
|
|
1290
1489
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
}
|
|
1295
|
-
if (parentPath === "") {
|
|
1296
|
-
this.validateNoCycles();
|
|
1297
|
-
this.buildReverseDeps();
|
|
1298
|
-
}
|
|
1490
|
+
});
|
|
1491
|
+
this.validateNoCycles();
|
|
1492
|
+
this.buildReverseDeps();
|
|
1299
1493
|
}
|
|
1300
1494
|
/**
|
|
1301
1495
|
* Build reverse dependency map (task → tasks that depend on it)
|
|
@@ -1398,15 +1592,35 @@ var ReportingDependencyTracker = class {
|
|
|
1398
1592
|
this.waiters.delete(key);
|
|
1399
1593
|
}
|
|
1400
1594
|
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Mark a task as active (will actually run).
|
|
1597
|
+
* Called before running each task to track which tasks are in the execution set.
|
|
1598
|
+
*/
|
|
1599
|
+
markActive(path) {
|
|
1600
|
+
this.activePaths.add(path);
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Check if a dependency is active (will run).
|
|
1604
|
+
* If not active, we shouldn't wait for it.
|
|
1605
|
+
*/
|
|
1606
|
+
isDependencyActive(dep) {
|
|
1607
|
+
const resolvedPath = this.resolveDependency(dep);
|
|
1608
|
+
if (!resolvedPath) {
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
return this.activePaths.has(resolvedPath);
|
|
1612
|
+
}
|
|
1401
1613
|
/**
|
|
1402
1614
|
* Wait for all dependencies of a task to complete.
|
|
1615
|
+
* Only waits for dependencies that are actually active (will run).
|
|
1403
1616
|
*/
|
|
1404
1617
|
async waitForDependencies(path) {
|
|
1405
1618
|
const deps = this.dependencies.get(path);
|
|
1406
1619
|
if (!deps || deps.length === 0) {
|
|
1407
1620
|
return;
|
|
1408
1621
|
}
|
|
1409
|
-
const
|
|
1622
|
+
const activeDeps = deps.filter((dep) => this.isDependencyActive(dep));
|
|
1623
|
+
const waitPromises = activeDeps.map((dep) => this.waitForResult(dep));
|
|
1410
1624
|
await Promise.all(waitPromises);
|
|
1411
1625
|
}
|
|
1412
1626
|
/**
|
|
@@ -1488,11 +1702,30 @@ var ReportingDependencyTracker = class {
|
|
|
1488
1702
|
}
|
|
1489
1703
|
}
|
|
1490
1704
|
};
|
|
1705
|
+
|
|
1706
|
+
// src/runner.ts
|
|
1707
|
+
function normalizeCommand(run, nodeTimeout) {
|
|
1708
|
+
if (typeof run === "string") {
|
|
1709
|
+
return {
|
|
1710
|
+
cmd: run,
|
|
1711
|
+
args: [],
|
|
1712
|
+
shell: true,
|
|
1713
|
+
timeout: nodeTimeout
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
return {
|
|
1717
|
+
...run,
|
|
1718
|
+
shell: false,
|
|
1719
|
+
// Command-level timeout takes precedence over node-level timeout
|
|
1720
|
+
timeout: run.timeout ?? nodeTimeout
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1491
1723
|
async function executeCommand(command, cwd, tracker, path) {
|
|
1492
1724
|
const start = Date.now();
|
|
1493
1725
|
return new Promise((resolve3) => {
|
|
1726
|
+
const useShell = command.shell || process.platform === "win32";
|
|
1494
1727
|
const proc = spawn(command.cmd, command.args, {
|
|
1495
|
-
shell:
|
|
1728
|
+
shell: useShell,
|
|
1496
1729
|
cwd: command.cwd ?? cwd,
|
|
1497
1730
|
env: { ...process.env, NO_COLOR: "1", ...command.env }
|
|
1498
1731
|
});
|
|
@@ -1500,6 +1733,18 @@ async function executeCommand(command, cwd, tracker, path) {
|
|
|
1500
1733
|
tracker.registerProcess(path, proc);
|
|
1501
1734
|
}
|
|
1502
1735
|
let output = "";
|
|
1736
|
+
let timedOut = false;
|
|
1737
|
+
let timeoutId;
|
|
1738
|
+
if (command.timeout && proc.pid) {
|
|
1739
|
+
const pid = proc.pid;
|
|
1740
|
+
timeoutId = setTimeout(() => {
|
|
1741
|
+
timedOut = true;
|
|
1742
|
+
treeKill2(pid, "SIGTERM", (err) => {
|
|
1743
|
+
if (err) {
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
}, command.timeout);
|
|
1747
|
+
}
|
|
1503
1748
|
proc.stdout.on("data", (data) => {
|
|
1504
1749
|
output += data.toString();
|
|
1505
1750
|
});
|
|
@@ -1507,6 +1752,9 @@ async function executeCommand(command, cwd, tracker, path) {
|
|
|
1507
1752
|
output += data.toString();
|
|
1508
1753
|
});
|
|
1509
1754
|
proc.on("close", (code, signal) => {
|
|
1755
|
+
if (timeoutId) {
|
|
1756
|
+
clearTimeout(timeoutId);
|
|
1757
|
+
}
|
|
1510
1758
|
if (tracker && path) {
|
|
1511
1759
|
tracker.unregisterProcess(path);
|
|
1512
1760
|
}
|
|
@@ -1516,10 +1764,15 @@ async function executeCommand(command, cwd, tracker, path) {
|
|
|
1516
1764
|
code: code ?? 1,
|
|
1517
1765
|
output,
|
|
1518
1766
|
durationMs,
|
|
1519
|
-
killed
|
|
1767
|
+
killed: killed && !timedOut,
|
|
1768
|
+
// Don't mark as killed if it was a timeout
|
|
1769
|
+
timedOut
|
|
1520
1770
|
});
|
|
1521
1771
|
});
|
|
1522
1772
|
proc.on("error", (err) => {
|
|
1773
|
+
if (timeoutId) {
|
|
1774
|
+
clearTimeout(timeoutId);
|
|
1775
|
+
}
|
|
1523
1776
|
if (tracker && path) {
|
|
1524
1777
|
tracker.unregisterProcess(path);
|
|
1525
1778
|
}
|
|
@@ -1528,14 +1781,12 @@ async function executeCommand(command, cwd, tracker, path) {
|
|
|
1528
1781
|
code: 1,
|
|
1529
1782
|
output: `Failed to execute command: ${err.message}`,
|
|
1530
1783
|
durationMs,
|
|
1531
|
-
killed: false
|
|
1784
|
+
killed: false,
|
|
1785
|
+
timedOut: false
|
|
1532
1786
|
});
|
|
1533
1787
|
});
|
|
1534
1788
|
});
|
|
1535
1789
|
}
|
|
1536
|
-
function buildPath(parentPath, key) {
|
|
1537
|
-
return parentPath ? `${parentPath}:${key}` : key;
|
|
1538
|
-
}
|
|
1539
1790
|
function matchesFilter(path, filters) {
|
|
1540
1791
|
if (!filters || filters.length === 0) {
|
|
1541
1792
|
return true;
|
|
@@ -1545,7 +1796,7 @@ function matchesFilter(path, filters) {
|
|
|
1545
1796
|
});
|
|
1546
1797
|
}
|
|
1547
1798
|
function hasMatchingDescendant(node, parentPath, filters) {
|
|
1548
|
-
const path =
|
|
1799
|
+
const path = buildTaskPath(parentPath, node.key);
|
|
1549
1800
|
if (matchesFilter(path, filters)) {
|
|
1550
1801
|
return true;
|
|
1551
1802
|
}
|
|
@@ -1625,7 +1876,8 @@ var VerificationRunner = class {
|
|
|
1625
1876
|
* Run a single node (leaf or group)
|
|
1626
1877
|
*/
|
|
1627
1878
|
async runNode(node, parentPath) {
|
|
1628
|
-
const path =
|
|
1879
|
+
const path = buildTaskPath(parentPath, node.key);
|
|
1880
|
+
this.dependencyTracker.markActive(path);
|
|
1629
1881
|
this.callbacks.onTaskStart?.(path, node.key);
|
|
1630
1882
|
if (node.children && node.children.length > 0) {
|
|
1631
1883
|
const start = Date.now();
|
|
@@ -1671,15 +1923,30 @@ var VerificationRunner = class {
|
|
|
1671
1923
|
this.callbacks.onTaskComplete?.(result2);
|
|
1672
1924
|
return result2;
|
|
1673
1925
|
}
|
|
1674
|
-
const command = normalizeCommand(node.run);
|
|
1926
|
+
const command = normalizeCommand(node.run, node.timeout);
|
|
1675
1927
|
const cwd = this.options.cwd ?? process.cwd();
|
|
1676
|
-
const { code, output, durationMs, killed } = await executeCommand(
|
|
1928
|
+
const { code, output, durationMs, killed, timedOut } = await executeCommand(
|
|
1677
1929
|
command,
|
|
1678
1930
|
cwd,
|
|
1679
1931
|
this.dependencyTracker,
|
|
1680
1932
|
path
|
|
1681
1933
|
);
|
|
1682
1934
|
const ok = code === 0;
|
|
1935
|
+
if (timedOut) {
|
|
1936
|
+
const result2 = {
|
|
1937
|
+
key: node.key,
|
|
1938
|
+
path,
|
|
1939
|
+
ok: false,
|
|
1940
|
+
code,
|
|
1941
|
+
durationMs,
|
|
1942
|
+
output,
|
|
1943
|
+
summaryLine: `${node.key}: timed out after ${command.timeout}ms`,
|
|
1944
|
+
timedOut: true
|
|
1945
|
+
};
|
|
1946
|
+
this.dependencyTracker.recordResult(result2);
|
|
1947
|
+
this.callbacks.onTaskComplete?.(result2);
|
|
1948
|
+
return result2;
|
|
1949
|
+
}
|
|
1683
1950
|
if (killed) {
|
|
1684
1951
|
await this.dependencyTracker.waitForDependencies(path);
|
|
1685
1952
|
const failedDep = this.dependencyTracker.getFailedDependency(path);
|
|
@@ -1698,7 +1965,7 @@ var VerificationRunner = class {
|
|
|
1698
1965
|
this.callbacks.onTaskComplete?.(result2);
|
|
1699
1966
|
return result2;
|
|
1700
1967
|
}
|
|
1701
|
-
const cmdString = `${command.cmd} ${command.args.join(" ")}`;
|
|
1968
|
+
const cmdString = command.shell ? command.cmd : `${command.cmd} ${command.args.join(" ")}`;
|
|
1702
1969
|
const parsed = this.registry.parse(
|
|
1703
1970
|
output,
|
|
1704
1971
|
code,
|
|
@@ -1743,6 +2010,15 @@ var VerificationRunner = class {
|
|
|
1743
2010
|
// src/index.ts
|
|
1744
2011
|
async function verify(config, cliOptions) {
|
|
1745
2012
|
const options = mergeOptions(config.options, cliOptions);
|
|
2013
|
+
if (options.filter && options.filter.length > 0) {
|
|
2014
|
+
const resolved = resolveFilters(config.tasks, options.filter);
|
|
2015
|
+
for (const r of resolved) {
|
|
2016
|
+
if (r.wasShortcut) {
|
|
2017
|
+
console.error(`\u2192 Resolving "${r.original}" to "${r.resolved}"`);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
options.filter = resolved.map((r) => r.resolved);
|
|
2021
|
+
}
|
|
1746
2022
|
const reporter = createReporter(options);
|
|
1747
2023
|
reporter.onStart?.(config.tasks);
|
|
1748
2024
|
const runner = new VerificationRunner(options, void 0, {
|
|
@@ -1765,20 +2041,27 @@ async function verifyFromConfig(cwd = process.cwd(), cliOptions) {
|
|
|
1765
2041
|
return verify(config, { ...cliOptions, cwd });
|
|
1766
2042
|
}
|
|
1767
2043
|
export {
|
|
2044
|
+
AmbiguousTaskError,
|
|
2045
|
+
ConfigError,
|
|
1768
2046
|
JSONReporter,
|
|
1769
2047
|
LiveDashboardReporter,
|
|
2048
|
+
PATH_SEPARATOR,
|
|
1770
2049
|
ParserRegistry,
|
|
1771
2050
|
QuietReporter,
|
|
1772
2051
|
SequentialReporter,
|
|
1773
2052
|
SequentialReporter as TTYReporter,
|
|
2053
|
+
TaskNotFoundError,
|
|
1774
2054
|
VerificationRunner,
|
|
1775
2055
|
biomeParser,
|
|
2056
|
+
buildTaskPath,
|
|
2057
|
+
collectPaths,
|
|
1776
2058
|
createReporter,
|
|
1777
2059
|
defaultRegistry,
|
|
1778
2060
|
defineConfig,
|
|
1779
2061
|
defineTask,
|
|
1780
2062
|
detectTasks,
|
|
1781
2063
|
discoverPackages,
|
|
2064
|
+
findBestSuggestion,
|
|
1782
2065
|
findConfigFile,
|
|
1783
2066
|
generateConfigContent,
|
|
1784
2067
|
genericParser,
|
|
@@ -1787,10 +2070,14 @@ export {
|
|
|
1787
2070
|
loadConfig,
|
|
1788
2071
|
loadConfigFromCwd,
|
|
1789
2072
|
mergeOptions,
|
|
2073
|
+
parsers,
|
|
2074
|
+
resolveFilters,
|
|
1790
2075
|
runInit,
|
|
1791
2076
|
tscParser,
|
|
2077
|
+
validateConfig,
|
|
1792
2078
|
verify,
|
|
1793
2079
|
verifyFromConfig,
|
|
1794
|
-
vitestParser
|
|
2080
|
+
vitestParser,
|
|
2081
|
+
walkNodes
|
|
1795
2082
|
};
|
|
1796
2083
|
//# sourceMappingURL=index.js.map
|