From 7b5207648cc9974a978717cd3f081153c1038726 Mon Sep 17 00:00:00 2001 From: ROHAN <123131rkorohan@gmail.com> Date: Sun, 16 Aug 2026 21:01:58 +0530 Subject: [PATCH] feat(cli): add -f/--format to webcmd validate `webcmd validate` had a stable, already-typed `ValidationReport` result but only ever printed it as hand-written text, unlike `list` and `convention-audit` which support `-f json|yaml|csv|md|table`. Agents scripting around validation output had no structured path (#175). Add `-f, --format` mirroring the existing `convention-audit` pattern: table format keeps the current human-readable report text unchanged, other formats render the report object directly through the shared output path. Scope note: #175 lists many built-in commands (verify, doctor, skills, profile list, daemon status, and a dozen browser reads); this PR only covers `validate` as one complete, tested slice rather than a partial pass across all of them. The rest are left for follow-up PRs. Co-Authored-By: Claude Sonnet 5 --- src/cli.ts | 15 ++++++++++----- tests/e2e/management.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index c7b2a09b..26423b77 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -667,14 +667,19 @@ export function createProgram(BUILTIN_CLIS: string, USER_CLIS: string, pluginsDi // ── Built-in: validate / verify ─────────────────────────────────────────── - program + const validateCmd = program .command('validate') .description('Validate CLI definitions') .argument('[target]', 'site or site/name') - .action(async (target) => { - const { validateClisWithTarget, renderValidationReport } = await import('./validate.js'); - console.log(renderValidationReport(validateClisWithTarget([BUILTIN_CLIS, USER_CLIS], target))); - }); + .option('-f, --format ', OUTPUT_FORMAT_HELP, 'table'); + validateCmd.action(async (target, opts) => { + const fmt = resolveOutputFormat(opts.format); + if (fmt === null) return; + const { validateClisWithTarget, renderValidationReport } = await import('./validate.js'); + const report = validateClisWithTarget([BUILTIN_CLIS, USER_CLIS], target); + if (fmt === 'table') console.log(renderValidationReport(report)); + else renderOutput(report, { fmt }); + }); program .command('verify') diff --git a/tests/e2e/management.test.ts b/tests/e2e/management.test.ts index a47e731f..04eb9dca 100644 --- a/tests/e2e/management.test.ts +++ b/tests/e2e/management.test.ts @@ -96,6 +96,29 @@ describe('management commands E2E', () => { expect(stdout).toContain('PASS'); }); + it('validate -f json produces a structured report', async () => { + const { stdout, code } = await runManagementCli(['validate', '-f', 'json']); + expect(code).toBe(0); + const data = parseJsonOutput(stdout); + expect(data).toMatchObject({ ok: true, errors: 0 }); + expect(Array.isArray(data.results)).toBe(true); + expect(typeof data.commands).toBe('number'); + }); + + it('validate -f yaml produces a structured report', async () => { + const { stdout, code } = await runManagementCli(['validate', '-f', 'yaml']); + expect(code).toBe(0); + expect(stdout).toContain('ok: true'); + expect(stdout).toContain('results:'); + }); + + it('validate without -f still prints the human-readable report', async () => { + const { stdout, code } = await runManagementCli(['validate']); + expect(code).toBe(0); + expect(stdout).toContain(`validate: PASS`); + expect(stdout).toContain('Checked'); + }); + // ── verify ── it('verify runs validation without smoke tests', async () => { const { stdout, code } = await runManagementCli(['verify']);