From a376c9d90c7daa9807845bdb36dabc74db4975ae Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Mon, 6 Apr 2026 19:29:40 +0000 Subject: [PATCH] fix: repair CI failures and remove test:npx binary distribution setup - ci.yml: add file guard to version check loop so it no-ops on a single-package repo (was failing with jq exit 2 on missing packages/) - ci.yml: replace npm-pack E2E setup step with a direct bun build of the public API (src/index.ts -> dist/index.js CJS) - hook-runner.ts: run binary from repo source via bun instead of the planned native binary distribution; point dist path at repo root - src/index.ts: add public API entry point used by the bun build step Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 11 ++++----- __tests__/e2e/helpers/hook-runner.ts | 24 +++++++------------ .../e2e/hooks/builtin-policies.e2e.test.ts | 2 +- docs/testing.md | 13 ++++------ src/index.ts | 19 +++++++++++++++ 5 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 src/index.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 608013ca..71cd9e56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,7 @@ jobs: echo "Root version: $ROOT_VERSION" MISMATCH=0 for pkg in packages/*/package.json; do + [ -f "$pkg" ] || continue PKG_VERSION=$(jq -r .version "$pkg") if [ "$PKG_VERSION" != "$ROOT_VERSION" ]; then echo "::error file=$pkg::Version mismatch: $pkg has $PKG_VERSION, expected $ROOT_VERSION" @@ -46,7 +47,7 @@ jobs: fi done # Check optionalDependencies in wrapper - for dep_version in $(jq -r '.optionalDependencies // {} | values[]' packages/wrapper/package.json); do + for dep_version in $(jq -r '.optionalDependencies // {} | values[]' packages/wrapper/package.json 2>/dev/null || true); do if [ "$dep_version" != "$ROOT_VERSION" ]; then echo "::error file=packages/wrapper/package.json::Dependency version mismatch: $dep_version, expected $ROOT_VERSION" MISMATCH=1 @@ -163,12 +164,8 @@ jobs: timeout_minutes: 5 command: bun install --frozen-lockfile - - name: Build npm package (setup E2E env) - uses: nick-fields/retry@v4 - with: - max_attempts: 2 - timeout_minutes: 15 - command: bun run test:npx + - name: Build E2E fixtures + run: bun build src/index.ts --outdir dist --target node --format cjs - name: E2E Hook Tests uses: nick-fields/retry@v4 diff --git a/__tests__/e2e/helpers/hook-runner.ts b/__tests__/e2e/helpers/hook-runner.ts index b628b41f..5faf8c4b 100644 --- a/__tests__/e2e/helpers/hook-runner.ts +++ b/__tests__/e2e/helpers/hook-runner.ts @@ -1,30 +1,25 @@ /** * Runs the failproofai binary as a subprocess for E2E hook tests. * - * Invokes .test-npx/node_modules/@failproofai//bin/failproofai --hook - * exactly as Claude Code does — no Node.js bridge, no mocks. + * Invokes bin/failproofai.mjs --hook via bun, exactly as Claude Code does — + * no Node.js bridge, no mocks. * - * Run `bun run test:npx` once before running these tests. + * Run `bun build src/index.ts --outdir dist --target node --format cjs` once before + * running these tests (required for custom hook files that import from 'failproofai'). */ import { expect } from "vitest"; import { spawnSync } from "node:child_process"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { existsSync } from "node:fs"; -import { platform, arch } from "node:os"; - const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../.."); function getBinaryPath(): string { - const os = platform(); // linux | darwin | win32 - const cpu = arch(); // x64 | arm64 - const ext = os === "win32" ? ".exe" : ""; - const pkgName = `${os}-${cpu}`; - return resolve(REPO_ROOT, `.test-npx/node_modules/@failproofai/${pkgName}/bin/failproofai${ext}`); + return resolve(REPO_ROOT, "bin/failproofai.mjs"); } function getDistPath(): string { - return resolve(REPO_ROOT, ".test-npx/node_modules/failproofai/dist"); + return resolve(REPO_ROOT, "dist"); } export interface HookRunResult { @@ -50,10 +45,7 @@ export function runHook( const binaryPath = getBinaryPath(); if (!existsSync(binaryPath)) { - throw new Error( - `E2E binary not found: ${binaryPath}\n` + - `Run \`bun run test:npx\` first to build and install the npm package.`, - ); + throw new Error(`E2E binary not found: ${binaryPath}`); } const env: NodeJS.ProcessEnv = { @@ -63,7 +55,7 @@ export function runHook( ...(opts?.homeDir ? { HOME: opts.homeDir } : {}), }; - const result = spawnSync(binaryPath, ["--hook", event], { + const result = spawnSync("bun", [binaryPath, "--hook", event], { input: JSON.stringify(payload), env, encoding: "utf8", diff --git a/__tests__/e2e/hooks/builtin-policies.e2e.test.ts b/__tests__/e2e/hooks/builtin-policies.e2e.test.ts index 3e36a24a..3aeefb70 100644 --- a/__tests__/e2e/hooks/builtin-policies.e2e.test.ts +++ b/__tests__/e2e/hooks/builtin-policies.e2e.test.ts @@ -4,7 +4,7 @@ * Each test invokes the real failproofai binary as a subprocess with an isolated * fixture environment — no mocks, no Claude, just stdin/stdout. * - * Run `bun run test:npx` once before running these tests. + * Run `bun build src/index.ts --outdir dist --target node --format cjs` once before running these tests. */ import { describe, it } from "vitest"; import { runHook, assertAllow, assertPreToolUseDeny, assertPostToolUseDeny, assertInstruct } from "../helpers/hook-runner"; diff --git a/docs/testing.md b/docs/testing.md index 1a69320c..ad766c69 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -110,24 +110,19 @@ E2E tests invoke the real `failproofai` binary as a subprocess, pipe a JSON payl ### Setup -E2E tests require the npm package to be built and installed locally first: +E2E tests run the binary directly from the repo source. Before the first run, build the CJS bundle that custom hook files use when they import from `'failproofai'`: ```bash -bun run test:npx +bun build src/index.ts --outdir dist --target node --format cjs ``` -This script: -1. Builds the Next.js standalone -2. Compiles the native binary (`bun build --compile`) -3. Packs and installs both platform and wrapper packages into `.test-npx/` - -Once setup, run the tests: +Then run the tests: ```bash bun run test:e2e ``` -The built package persists in `.test-npx/` between runs, so you only need to run `test:npx` again after making changes to the hook handler or binary entry point. +Rebuild `dist/` whenever you change the public hook API (`src/hooks/custom-hooks-registry.ts`, `src/hooks/policy-helpers.ts`, or `src/hooks/policy-types.ts`). ### E2E test structure diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..aed5c66b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,19 @@ +/** + * Public API for failproofai custom hooks. + * + * Used as the bundle entry point for `dist/index.js` (CJS) and re-exported + * by the ESM shim that rewrites `from 'failproofai'` in user hook files. + */ +export { + customPolicies, + getCustomHooks, + clearCustomHooks, +} from "./hooks/custom-hooks-registry"; +export { allow, deny, instruct } from "./hooks/policy-helpers"; +export type { + PolicyContext, + PolicyResult, + CustomHook, + PolicyDecision, + PolicyFunction, +} from "./hooks/policy-types";