From 50dca8a8c9d681df7efbabfd871012108550dd71 Mon Sep 17 00:00:00 2001 From: hivemoot-nurse Date: Thu, 9 Apr 2026 01:53:30 +0000 Subject: [PATCH] chore: harden governance health CLI args Add explicit argument parsing to check-governance-health so --help is supported and mistyped flags fail loudly instead of being ignored. This keeps the script aligned with the repo's newer CLI conventions and reduces onboarding friction for operators. --- .../__tests__/check-governance-health.test.ts | 49 ++++++++++++++++++- web/scripts/check-governance-health.ts | 35 ++++++++++++- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/web/scripts/__tests__/check-governance-health.test.ts b/web/scripts/__tests__/check-governance-health.test.ts index 09cbaf55..7129b1f9 100644 --- a/web/scripts/__tests__/check-governance-health.test.ts +++ b/web/scripts/__tests__/check-governance-health.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import type { ActivityData, Comment, @@ -20,6 +20,7 @@ import { extractRole, hadQuorumFailure, inferEligibleVoterCount, + parseArgs, percentile, resolveActivityFile, } from '../check-governance-health'; @@ -87,6 +88,52 @@ function minimalData(overrides: Partial = {}): ActivityData { }; } +afterEach(() => { + vi.restoreAllMocks(); +}); + +function stubProcessExit(): ReturnType { + return vi.spyOn(process, 'exit').mockImplementation(((code?: number) => { + throw new Error(`process.exit:${code ?? 0}`); + }) as typeof process.exit); +} + +// ────────────────────────────────────────────── +// parseArgs +// ────────────────────────────────────────────── + +describe('parseArgs', () => { + it('defaults to text output', () => { + expect(parseArgs([])).toEqual({ json: false }); + }); + + it('enables json output with --json', () => { + expect(parseArgs(['--json'])).toEqual({ json: true }); + }); + + it('prints help and exits 0 for --help', () => { + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + const exitSpy = stubProcessExit(); + + expect(() => parseArgs(['--help'])).toThrow('process.exit:0'); + expect(logSpy).toHaveBeenCalledWith( + 'Usage: npm run check-governance-health -- [--json]' + ); + expect(exitSpy).toHaveBeenCalledWith(0); + }); + + it('rejects unknown flags', () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const exitSpy = stubProcessExit(); + + expect(() => parseArgs(['--jsom'])).toThrow('process.exit:1'); + expect(errorSpy).toHaveBeenCalledWith( + 'Unknown argument "--jsom". Expected --json or --help.' + ); + expect(exitSpy).toHaveBeenCalledWith(1); + }); +}); + // ────────────────────────────────────────────── // extractRole // ────────────────────────────────────────────── diff --git a/web/scripts/check-governance-health.ts b/web/scripts/check-governance-health.ts index 91a32932..e5f72e7b 100644 --- a/web/scripts/check-governance-health.ts +++ b/web/scripts/check-governance-health.ts @@ -37,6 +37,10 @@ const DEFAULT_ACTIVITY_FILE = join( 'activity.json' ); +interface CliOptions { + json: boolean; +} + // ────────────────────────────────────────────── // Types // ────────────────────────────────────────────── @@ -708,6 +712,33 @@ export function resolveActivityFile( return env.ACTIVITY_FILE ?? DEFAULT_ACTIVITY_FILE; } +function printHelp(): void { + console.log('Usage: npm run check-governance-health -- [--json]'); +} + +export function parseArgs(argv: string[]): CliOptions { + const options: CliOptions = { + json: false, + }; + + for (const arg of argv) { + if (arg === '--json') { + options.json = true; + continue; + } + + if (arg === '--help') { + printHelp(); + process.exit(0); + } + + console.error(`Unknown argument "${arg}". Expected --json or --help.`); + process.exit(1); + } + + return options; +} + function isDirectExecution(): boolean { if (!process.argv[1]) return false; return resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url)); @@ -715,7 +746,7 @@ function isDirectExecution(): boolean { async function main(): Promise { const activityFile = resolveActivityFile(); - const isJson = process.argv.includes('--json'); + const { json } = parseArgs(process.argv.slice(2)); if (!existsSync(activityFile)) { console.error(`Activity file not found: ${activityFile}`); @@ -728,7 +759,7 @@ async function main(): Promise { const data = JSON.parse(readFileSync(activityFile, 'utf-8')) as ActivityData; const report = buildHealthReport(data); - if (isJson) { + if (json) { console.log(JSON.stringify(report, null, 2)); } else { printReport(report);