@h-rig/validator-kit 0.0.6-alpha.16 → 0.0.6-alpha.161

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.
@@ -0,0 +1,17 @@
1
+ import { Checker } from "./result";
2
+ import { type RunCommand } from "./exec";
3
+ /** Check a file exists. */
4
+ export declare function requireFileCheck(checker: Checker, path: string, checkName: string): void;
5
+ /** Check a directory exists. */
6
+ export declare function requireDirCheck(checker: Checker, path: string, checkName: string): void;
7
+ /** Validate standard package structure: package.json + tsconfig.json + src/. */
8
+ export declare function requirePackageStructure(checker: Checker, serviceDir: string, prefix: string): void;
9
+ /** Check test files exist in a project directory. */
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,7 @@
1
+ import { Checker } from "./result";
2
+ /** Check markdown file has section headings matching each term. */
3
+ export declare function requireMarkdownSections(checker: Checker, filePath: string, sections: string[]): void;
4
+ /** Check document contains terms (case-insensitive). Pattern can include | for alternation. */
5
+ export declare function requireTerms(checker: Checker, filePath: string, terms: Record<string, string>): void;
6
+ /** Parse JSON and check key paths exist (dot-separated, e.g. ".summary.total"). */
7
+ export declare function requireJsonKeys(checker: Checker, filePath: string, keys: string[]): void;
@@ -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
+ };
@@ -0,0 +1,19 @@
1
+ /** Return the first existing path from candidates, or null. */
2
+ export declare function findFirstFile(candidates: string[]): string | null;
3
+ /** Read a file as UTF-8 or return null if missing. */
4
+ export declare function readFileSafe(path: string): string | null;
5
+ export declare function requireFile(path: string, id: string, description: string): string;
6
+ /** Check if file content matches a pattern. */
7
+ export declare function fileContains(path: string, pattern: RegExp): boolean;
8
+ /** Walk a directory tree, calling fn for each file. Skips node_modules/.git. */
9
+ export declare function walkDir(dir: string, fn: (filePath: string) => void): void;
10
+ /** List subdirectories of a directory. */
11
+ export declare function listSubdirs(dir: string): string[];
12
+ /** Count .ts files in a directory (excluding tests). */
13
+ export declare function countTsFiles(dir: string): number;
14
+ /** Count test files (.test.ts / .spec.ts) in a directory. */
15
+ export declare function countTestFiles(dir: string): number;
16
+ /** Find first directory matching candidates. */
17
+ export declare function findFirstDir(candidates: string[]): string | null;
18
+ /** Find files by name pattern (glob-free, recursive). */
19
+ export declare function findFilesByName(dir: string, namePattern: RegExp): string[];
@@ -0,0 +1,6 @@
1
+ /** Find files whose content matches pattern. Optionally filter by extension. */
2
+ export declare function grepFiles(dir: string, pattern: RegExp, extensions?: string[]): string[];
3
+ /** Count files matching pattern. */
4
+ export declare function grepCount(dir: string, pattern: RegExp, extensions?: string[]): number;
5
+ /** Get matching lines from files in a directory. */
6
+ export declare function grepLines(dir: string, pattern: RegExp, extensions?: string[], maxLines?: number): string[];
@@ -0,0 +1,10 @@
1
+ export type { ValidatorOutput } from "./result";
2
+ export { pass, fail, error, Checker } from "./result";
3
+ export { findFirstFile, readFileSafe, requireFile, fileContains, walkDir, listSubdirs, countTsFiles, countTestFiles, findFirstDir, findFilesByName, } from "./fs";
4
+ export { grepFiles, grepCount, grepLines } from "./grep";
5
+ export { requireMarkdownSections, requireTerms, requireJsonKeys, } from "./content";
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
+ /**
2
+ * Validator output contract and result helpers.
3
+ *
4
+ * Provides the `ValidatorOutput` shape, single-shot exit helpers (`pass`,
5
+ * `fail`, `error`), and the multi-check `Checker` accumulator.
6
+ *
7
+ * Moved from packages/runtime/src/control-plane/validators/shared.ts
8
+ * in Phase 3 Task 3.3 of the Rig extraction.
9
+ */
10
+ export type ValidatorOutput = {
11
+ id: string;
12
+ passed: boolean;
13
+ summary: string;
14
+ details?: string;
15
+ };
16
+ export declare function pass(id: string, summary: string): never;
17
+ export declare function fail(id: string, summary: string, details?: string): never;
18
+ export declare function error(id: string, message: string): never;
19
+ export declare class Checker {
20
+ private results;
21
+ pass(name: string): void;
22
+ fail(name: string, reason: string): void;
23
+ /** Fail and immediately emit — for fatal precondition failures. */
24
+ fatal(id: string, reason: string): never;
25
+ /** Print JSON result and exit. */
26
+ emit(id: string): never;
27
+ }
@@ -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,8 +1,8 @@
1
1
  {
2
2
  "name": "@h-rig/validator-kit",
3
- "version": "0.0.6-alpha.16",
3
+ "version": "0.0.6-alpha.161",
4
4
  "type": "module",
5
- "description": "Rig package",
5
+ "description": "Validator schema helpers for Rig plugin contributions; not a validation runtime.",
6
6
  "license": "UNLICENSED",
7
7
  "files": [
8
8
  "dist",
@@ -10,6 +10,7 @@
10
10
  ],
11
11
  "exports": {
12
12
  ".": {
13
+ "types": "./dist/src/index.d.ts",
13
14
  "import": "./dist/src/index.js"
14
15
  }
15
16
  },
@@ -18,8 +19,9 @@
18
19
  },
19
20
  "main": "./dist/src/index.js",
20
21
  "module": "./dist/src/index.js",
22
+ "types": "./dist/src/index.d.ts",
21
23
  "dependencies": {
22
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.16",
23
- "effect": "4.0.0-beta.78"
24
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.161",
25
+ "effect": "4.0.0-beta.90"
24
26
  }
25
27
  }