@h-rig/validator-kit 0.0.6-alpha.157 → 0.0.6-alpha.158

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.
@@ -1,4 +1,5 @@
1
1
  import { Checker } from "./result";
2
+ import { type RunCommand } from "./exec";
2
3
  /** Check a file exists. */
3
4
  export declare function requireFileCheck(checker: Checker, path: string, checkName: string): void;
4
5
  /** Check a directory exists. */
@@ -7,3 +8,10 @@ export declare function requireDirCheck(checker: Checker, path: string, checkNam
7
8
  export declare function requirePackageStructure(checker: Checker, serviceDir: string, prefix: string): void;
8
9
  /** Check test files exist in a project directory. */
9
10
  export declare function checkTestsExist(checker: Checker, projectDir: string, checkName: string): void;
11
+ /**
12
+ * Check that a TypeScript project compiles cleanly (`tsc --noEmit`). Runs the
13
+ * compile through the injectable {@link RunCommand} primitive — the default is
14
+ * exec.ts's minimal Bun.spawn runner; callers may inject a richer runtime
15
+ * runner. Records a pass/fail on the {@link Checker}; never throws.
16
+ */
17
+ export declare function checkTypescriptCompiles(checker: Checker, projectDir: string, checkName: string, run?: RunCommand): Promise<void>;
@@ -30,6 +30,22 @@ function countTestFiles(dir) {
30
30
  return count;
31
31
  }
32
32
 
33
+ // packages/validator-kit/src/exec.ts
34
+ var runCommand = async (cmd, cwd) => {
35
+ const proc = Bun.spawn([...cmd], {
36
+ cwd,
37
+ stdout: "pipe",
38
+ stderr: "pipe",
39
+ env: { ...process.env, PWD: cwd, INIT_CWD: cwd }
40
+ });
41
+ const exitCode = await proc.exited;
42
+ return {
43
+ exitCode,
44
+ stdout: await new Response(proc.stdout).text(),
45
+ stderr: await new Response(proc.stderr).text()
46
+ };
47
+ };
48
+
33
49
  // packages/validator-kit/src/checks.ts
34
50
  function requireFileCheck(checker, path, checkName) {
35
51
  if (existsSync2(path)) {
@@ -58,9 +74,42 @@ function checkTestsExist(checker, projectDir, checkName) {
58
74
  checker.fail(checkName, "no test files found");
59
75
  }
60
76
  }
77
+ function resolveTypescriptCommand(projectDir) {
78
+ const tsconfigPath = resolve2(projectDir, "tsconfig.json");
79
+ const candidates = [
80
+ resolve2(projectDir, "node_modules", "typescript", "lib", "tsc.js"),
81
+ resolve2(projectDir, "..", "node_modules", "typescript", "lib", "tsc.js"),
82
+ resolve2(projectDir, "..", "..", "node_modules", "typescript", "lib", "tsc.js"),
83
+ resolve2(projectDir, "..", "..", "..", "node_modules", "typescript", "lib", "tsc.js")
84
+ ];
85
+ for (const candidate of candidates) {
86
+ if (existsSync2(candidate)) {
87
+ return [process.execPath, candidate, "--noEmit", "--project", tsconfigPath];
88
+ }
89
+ }
90
+ return ["bunx", "tsc", "--noEmit", "--project", tsconfigPath];
91
+ }
92
+ async function checkTypescriptCompiles(checker, projectDir, checkName, run = runCommand) {
93
+ if (!existsSync2(resolve2(projectDir, "tsconfig.json"))) {
94
+ checker.fail(checkName, "no tsconfig.json found");
95
+ return;
96
+ }
97
+ const command = resolveTypescriptCommand(projectDir);
98
+ const result = await run(command, projectDir);
99
+ if (result.exitCode === 0) {
100
+ checker.pass(checkName);
101
+ return;
102
+ }
103
+ const combined = result.stdout + result.stderr;
104
+ const errorCount = combined.split(`
105
+ `).filter((line) => line.includes("error TS")).length;
106
+ const detail = errorCount > 0 ? `${errorCount} TypeScript errors` : `TypeScript command exited ${result.exitCode} without TS diagnostics`;
107
+ checker.fail(checkName, detail);
108
+ }
61
109
  export {
62
110
  requirePackageStructure,
63
111
  requireFileCheck,
64
112
  requireDirCheck,
113
+ checkTypescriptCompiles,
65
114
  checkTestsExist
66
115
  };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Minimal honest command-runner for validator checks.
3
+ *
4
+ * @rig/validator-kit is floor-neutral (contracts-only deps), so it does NOT
5
+ * depend on @rig/runtime's `resolveBunCli`/build-macro machinery. There is no
6
+ * shared `runCommand` PRIMITIVE for this floor to wire to yet, so this is the
7
+ * minimal honest implementation: a thin Bun.spawn wrapper that captures exit
8
+ * code + stdout + stderr. It is INJECTABLE — every check that needs to run a
9
+ * command accepts a `RunCommand` so a caller (or test) can substitute the
10
+ * runtime's richer runner. When a real shared primitive lands, swap the default.
11
+ */
12
+ export type RunCommandResult = {
13
+ readonly exitCode: number;
14
+ readonly stdout: string;
15
+ readonly stderr: string;
16
+ };
17
+ export type RunCommand = (cmd: readonly string[], cwd: string) => Promise<RunCommandResult>;
18
+ /**
19
+ * Default runner: spawn the command verbatim and capture output. Honest about
20
+ * its limits — it does not resolve a baked bun path or inject build env; it runs
21
+ * exactly what it is handed. Callers needing that resolution inject their own.
22
+ */
23
+ export declare const runCommand: RunCommand;
@@ -0,0 +1,19 @@
1
+ // @bun
2
+ // packages/validator-kit/src/exec.ts
3
+ var runCommand = async (cmd, cwd) => {
4
+ const proc = Bun.spawn([...cmd], {
5
+ cwd,
6
+ stdout: "pipe",
7
+ stderr: "pipe",
8
+ env: { ...process.env, PWD: cwd, INIT_CWD: cwd }
9
+ });
10
+ const exitCode = await proc.exited;
11
+ return {
12
+ exitCode,
13
+ stdout: await new Response(proc.stdout).text(),
14
+ stderr: await new Response(proc.stderr).text()
15
+ };
16
+ };
17
+ export {
18
+ runCommand
19
+ };
@@ -3,4 +3,8 @@ export { pass, fail, error, Checker } from "./result";
3
3
  export { findFirstFile, readFileSafe, requireFile, fileContains, walkDir, listSubdirs, countTsFiles, countTestFiles, findFirstDir, findFilesByName, } from "./fs";
4
4
  export { grepFiles, grepCount, grepLines } from "./grep";
5
5
  export { requireMarkdownSections, requireTerms, requireJsonKeys, } from "./content";
6
- export { requireFileCheck, requireDirCheck, requirePackageStructure, checkTestsExist, } from "./checks";
6
+ export { requireFileCheck, requireDirCheck, requirePackageStructure, checkTestsExist, checkTypescriptCompiles, } from "./checks";
7
+ export { runCommand } from "./exec";
8
+ export type { RunCommand, RunCommandResult } from "./exec";
9
+ export { createValidatorRegistry } from "./validator-registry";
10
+ export type { ValidatorResult, ValidatorContext, RegisteredValidator, ValidatorRegistry, } from "./validator-registry";
package/dist/src/index.js CHANGED
@@ -233,6 +233,24 @@ function requireJsonKeys(checker, filePath, keys) {
233
233
  // packages/validator-kit/src/checks.ts
234
234
  import { existsSync as existsSync2, statSync as statSync2 } from "fs";
235
235
  import { resolve as resolve2 } from "path";
236
+
237
+ // packages/validator-kit/src/exec.ts
238
+ var runCommand = async (cmd, cwd) => {
239
+ const proc = Bun.spawn([...cmd], {
240
+ cwd,
241
+ stdout: "pipe",
242
+ stderr: "pipe",
243
+ env: { ...process.env, PWD: cwd, INIT_CWD: cwd }
244
+ });
245
+ const exitCode = await proc.exited;
246
+ return {
247
+ exitCode,
248
+ stdout: await new Response(proc.stdout).text(),
249
+ stderr: await new Response(proc.stderr).text()
250
+ };
251
+ };
252
+
253
+ // packages/validator-kit/src/checks.ts
236
254
  function requireFileCheck(checker, path, checkName) {
237
255
  if (existsSync2(path)) {
238
256
  checker.pass(checkName);
@@ -260,8 +278,101 @@ function checkTestsExist(checker, projectDir, checkName) {
260
278
  checker.fail(checkName, "no test files found");
261
279
  }
262
280
  }
281
+ function resolveTypescriptCommand(projectDir) {
282
+ const tsconfigPath = resolve2(projectDir, "tsconfig.json");
283
+ const candidates = [
284
+ resolve2(projectDir, "node_modules", "typescript", "lib", "tsc.js"),
285
+ resolve2(projectDir, "..", "node_modules", "typescript", "lib", "tsc.js"),
286
+ resolve2(projectDir, "..", "..", "node_modules", "typescript", "lib", "tsc.js"),
287
+ resolve2(projectDir, "..", "..", "..", "node_modules", "typescript", "lib", "tsc.js")
288
+ ];
289
+ for (const candidate of candidates) {
290
+ if (existsSync2(candidate)) {
291
+ return [process.execPath, candidate, "--noEmit", "--project", tsconfigPath];
292
+ }
293
+ }
294
+ return ["bunx", "tsc", "--noEmit", "--project", tsconfigPath];
295
+ }
296
+ async function checkTypescriptCompiles(checker, projectDir, checkName, run = runCommand) {
297
+ if (!existsSync2(resolve2(projectDir, "tsconfig.json"))) {
298
+ checker.fail(checkName, "no tsconfig.json found");
299
+ return;
300
+ }
301
+ const command = resolveTypescriptCommand(projectDir);
302
+ const result = await run(command, projectDir);
303
+ if (result.exitCode === 0) {
304
+ checker.pass(checkName);
305
+ return;
306
+ }
307
+ const combined = result.stdout + result.stderr;
308
+ const errorCount = combined.split(`
309
+ `).filter((line) => line.includes("error TS")).length;
310
+ const detail = errorCount > 0 ? `${errorCount} TypeScript errors` : `TypeScript command exited ${result.exitCode} without TS diagnostics`;
311
+ checker.fail(checkName, detail);
312
+ }
313
+ // packages/validator-kit/src/validator-registry.ts
314
+ import { existsSync as existsSync3 } from "fs";
315
+ import { join } from "path";
316
+ function createValidatorRegistry() {
317
+ const map = new Map;
318
+ const order = [];
319
+ const registry = {
320
+ register(v) {
321
+ if (map.has(v.id))
322
+ throw new Error(`validator already registered: ${v.id}`);
323
+ map.set(v.id, v);
324
+ order.push(v);
325
+ },
326
+ resolve(id) {
327
+ const v = map.get(id);
328
+ if (!v)
329
+ throw new Error(`validator not registered: ${id}`);
330
+ return v;
331
+ },
332
+ list: () => order
333
+ };
334
+ registerBuiltInValidators(registry);
335
+ return registry;
336
+ }
337
+ function registerBuiltInValidators(registry) {
338
+ registry.register({
339
+ id: "std:typecheck",
340
+ category: "custom",
341
+ description: "Runs the package typecheck script when present.",
342
+ run: async (ctx) => runStdTypecheck(ctx)
343
+ });
344
+ }
345
+ async function runStdTypecheck(ctx) {
346
+ const packageJsonPath = join(ctx.workspaceRoot, "package.json");
347
+ if (!existsSync3(packageJsonPath)) {
348
+ return {
349
+ id: "std:typecheck",
350
+ passed: false,
351
+ summary: `package.json not found at ${packageJsonPath}`
352
+ };
353
+ }
354
+ const proc = Bun.spawn(["bun", "run", "typecheck"], {
355
+ cwd: ctx.workspaceRoot,
356
+ env: process.env,
357
+ stdout: "pipe",
358
+ stderr: "pipe"
359
+ });
360
+ const [exitCode, stdout, stderr] = await Promise.all([
361
+ proc.exited,
362
+ new Response(proc.stdout).text(),
363
+ new Response(proc.stderr).text()
364
+ ]);
365
+ const output = `${stdout}${stderr}`.trim();
366
+ return {
367
+ id: "std:typecheck",
368
+ passed: exitCode === 0,
369
+ summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
370
+ ...output ? { details: output.slice(0, 4000) } : {}
371
+ };
372
+ }
263
373
  export {
264
374
  walkDir,
375
+ runCommand,
265
376
  requireTerms,
266
377
  requirePackageStructure,
267
378
  requireMarkdownSections,
@@ -281,8 +392,10 @@ export {
281
392
  fileContains,
282
393
  fail,
283
394
  error,
395
+ createValidatorRegistry,
284
396
  countTsFiles,
285
397
  countTestFiles,
398
+ checkTypescriptCompiles,
286
399
  checkTestsExist,
287
400
  Checker
288
401
  };
@@ -0,0 +1,27 @@
1
+ import type { ValidatorRegistration } from "@rig/contracts";
2
+ export interface ValidatorResult {
3
+ id: string;
4
+ passed: boolean;
5
+ summary: string;
6
+ details?: string;
7
+ }
8
+ export interface ValidatorContext {
9
+ taskId: string;
10
+ workspaceRoot: string;
11
+ scope: readonly string[];
12
+ /** Absolute path to the monorepo checkout root (MONOREPO_ROOT / MONOREPO_MAIN_ROOT env equiv). */
13
+ monorepoRoot?: string;
14
+ /** Absolute path to the task's artifacts output directory (ARTIFACTS_DIR env equiv). */
15
+ artifactsDir?: string;
16
+ /** The full task config entry for this task — opaque to the registry, but available for advanced validators. */
17
+ taskConfig?: unknown;
18
+ }
19
+ export interface RegisteredValidator extends ValidatorRegistration {
20
+ run(ctx: ValidatorContext): Promise<ValidatorResult>;
21
+ }
22
+ export interface ValidatorRegistry {
23
+ register(v: RegisteredValidator): void;
24
+ resolve(id: string): RegisteredValidator;
25
+ list(): readonly RegisteredValidator[];
26
+ }
27
+ export declare function createValidatorRegistry(): ValidatorRegistry;
@@ -0,0 +1,64 @@
1
+ // @bun
2
+ // packages/validator-kit/src/validator-registry.ts
3
+ import { existsSync } from "fs";
4
+ import { join } from "path";
5
+ function createValidatorRegistry() {
6
+ const map = new Map;
7
+ const order = [];
8
+ const registry = {
9
+ register(v) {
10
+ if (map.has(v.id))
11
+ throw new Error(`validator already registered: ${v.id}`);
12
+ map.set(v.id, v);
13
+ order.push(v);
14
+ },
15
+ resolve(id) {
16
+ const v = map.get(id);
17
+ if (!v)
18
+ throw new Error(`validator not registered: ${id}`);
19
+ return v;
20
+ },
21
+ list: () => order
22
+ };
23
+ registerBuiltInValidators(registry);
24
+ return registry;
25
+ }
26
+ function registerBuiltInValidators(registry) {
27
+ registry.register({
28
+ id: "std:typecheck",
29
+ category: "custom",
30
+ description: "Runs the package typecheck script when present.",
31
+ run: async (ctx) => runStdTypecheck(ctx)
32
+ });
33
+ }
34
+ async function runStdTypecheck(ctx) {
35
+ const packageJsonPath = join(ctx.workspaceRoot, "package.json");
36
+ if (!existsSync(packageJsonPath)) {
37
+ return {
38
+ id: "std:typecheck",
39
+ passed: false,
40
+ summary: `package.json not found at ${packageJsonPath}`
41
+ };
42
+ }
43
+ const proc = Bun.spawn(["bun", "run", "typecheck"], {
44
+ cwd: ctx.workspaceRoot,
45
+ env: process.env,
46
+ stdout: "pipe",
47
+ stderr: "pipe"
48
+ });
49
+ const [exitCode, stdout, stderr] = await Promise.all([
50
+ proc.exited,
51
+ new Response(proc.stdout).text(),
52
+ new Response(proc.stderr).text()
53
+ ]);
54
+ const output = `${stdout}${stderr}`.trim();
55
+ return {
56
+ id: "std:typecheck",
57
+ passed: exitCode === 0,
58
+ summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
59
+ ...output ? { details: output.slice(0, 4000) } : {}
60
+ };
61
+ }
62
+ export {
63
+ createValidatorRegistry
64
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/validator-kit",
3
- "version": "0.0.6-alpha.157",
3
+ "version": "0.0.6-alpha.158",
4
4
  "type": "module",
5
5
  "description": "Validator schema helpers for Rig plugin contributions; not a validation runtime.",
6
6
  "license": "UNLICENSED",
@@ -21,7 +21,7 @@
21
21
  "module": "./dist/src/index.js",
22
22
  "types": "./dist/src/index.d.ts",
23
23
  "dependencies": {
24
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.157",
24
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.158",
25
25
  "effect": "4.0.0-beta.90"
26
26
  }
27
27
  }