From 96e3c875fc629e8d43d456ff9dd76cdde5965764 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Wed, 13 May 2026 19:57:39 +0200 Subject: [PATCH] fix(cli): omit hidden commands from docs command tree --- packages/cli/src/command-tree.test.ts | 18 ++++++++++++++++++ packages/cli/src/command-tree.ts | 6 +++++- packages/cli/src/print-command-tree.test.ts | 6 +++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/command-tree.test.ts b/packages/cli/src/command-tree.test.ts index 85fa0e84..a23ebd1b 100644 --- a/packages/cli/src/command-tree.test.ts +++ b/packages/cli/src/command-tree.test.ts @@ -52,6 +52,24 @@ describe('walkCommandTree', () => { expect(walkCommandTree(command).arguments).toEqual(['', '[schemas...]']); }); + + it('omits Commander hidden commands from the public tree', () => { + const root = new Command('ktx'); + root.command('scan', { hidden: true }).description('Run a standalone connection scan'); + const ingest = root.command('ingest').description('Build or inspect KTX context'); + ingest.command('run', { hidden: true }).description('Run local ingest by adapter'); + ingest.command('watch', { hidden: true }).description('Open a stored visual report'); + ingest.command('status').description('Print status'); + root.command('status').description('Check readiness'); + + const tree = walkCommandTree(root); + + expect(tree.children.map((child) => child.name)).toEqual(['ingest', 'status']); + expect(tree.children[0]).toMatchObject({ + name: 'ingest', + children: [{ name: 'status', description: 'Print status', aliases: [], arguments: [], children: [] }], + }); + }); }); describe('formatCommandTree', () => { diff --git a/packages/cli/src/command-tree.ts b/packages/cli/src/command-tree.ts index 2eeb24e8..1ef9ba26 100644 --- a/packages/cli/src/command-tree.ts +++ b/packages/cli/src/command-tree.ts @@ -10,13 +10,17 @@ export interface CommandTreeNode { children: CommandTreeNode[]; } +function isHiddenCommand(command: CommandUnknownOpts): boolean { + return (command as CommandUnknownOpts & { _hidden?: boolean })._hidden === true; +} + export function walkCommandTree(command: CommandUnknownOpts): CommandTreeNode { return { name: command.name(), description: command.description(), aliases: command.aliases(), arguments: command.registeredArguments.map(formatArgumentDeclaration), - children: command.commands.map((child) => walkCommandTree(child)), + children: command.commands.filter((child) => !isHiddenCommand(child)).map((child) => walkCommandTree(child)), }; } diff --git a/packages/cli/src/print-command-tree.test.ts b/packages/cli/src/print-command-tree.test.ts index 1385d37d..67ca7aeb 100644 --- a/packages/cli/src/print-command-tree.test.ts +++ b/packages/cli/src/print-command-tree.test.ts @@ -12,7 +12,7 @@ describe('renderKtxCommandTree', () => { .filter((line) => /^ {2}[├└]── \S/.test(line)) .map((line) => line.replace(/^ {2}[├└]── /, '').trim().split(' ')[0]); - for (const expected of ['setup', 'connection', 'ingest', 'sl', 'dev']) { + for (const expected of ['setup', 'connection', 'ingest', 'sl']) { expect(topLevel).toContain(expected); } @@ -23,6 +23,10 @@ describe('renderKtxCommandTree', () => { expect(output).not.toContain('│ ├── mapping'); expect(output).not.toContain('│ ├── metabase'); expect(output).not.toContain('│ ├── notion'); + expect(output).not.toContain('scan '); + expect(output).not.toContain('│ ├── run'); + expect(output).not.toContain('│ ├── watch'); + expect(output).not.toContain('│ └── watch'); }); it('ends with a single trailing newline', () => {