mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
feat(cli): add walkCommandTree and formatCommandTree helpers
This commit is contained in:
parent
cdcfd21e95
commit
f205bec1f6
2 changed files with 116 additions and 0 deletions
81
packages/cli/src/command-tree.test.ts
Normal file
81
packages/cli/src/command-tree.test.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { Command } from '@commander-js/extra-typings';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatCommandTree, walkCommandTree } from './command-tree.js';
|
||||
|
||||
describe('walkCommandTree', () => {
|
||||
it('captures name, description, aliases, and nested children', () => {
|
||||
const root = new Command('root').description('the root');
|
||||
const child = new Command('child').description('a child').alias('c').alias('ch');
|
||||
const grandchild = new Command('grand').description('a grandchild');
|
||||
child.addCommand(grandchild);
|
||||
root.addCommand(child);
|
||||
|
||||
const tree = walkCommandTree(root);
|
||||
|
||||
expect(tree).toEqual({
|
||||
name: 'root',
|
||||
description: 'the root',
|
||||
aliases: [],
|
||||
children: [
|
||||
{
|
||||
name: 'child',
|
||||
description: 'a child',
|
||||
aliases: ['c', 'ch'],
|
||||
children: [{ name: 'grand', description: 'a grandchild', aliases: [], children: [] }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an empty children array when there are no subcommands', () => {
|
||||
const leaf = new Command('leaf').description('alone');
|
||||
expect(walkCommandTree(leaf)).toEqual({
|
||||
name: 'leaf',
|
||||
description: 'alone',
|
||||
aliases: [],
|
||||
children: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('uses an empty string when description is unset', () => {
|
||||
const command = new Command('bare');
|
||||
expect(walkCommandTree(command).description).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatCommandTree', () => {
|
||||
it('renders a single node with no children', () => {
|
||||
const node = { name: 'solo', description: 'just me', aliases: [], children: [] };
|
||||
expect(formatCommandTree(node)).toBe('solo - just me\n');
|
||||
});
|
||||
|
||||
it('renders aliases in parentheses before the description', () => {
|
||||
const node = { name: 'cmd', description: 'does things', aliases: ['c', 'co'], children: [] };
|
||||
expect(formatCommandTree(node)).toBe('cmd (c, co) - does things\n');
|
||||
});
|
||||
|
||||
it('omits the dash when description is empty', () => {
|
||||
const node = { name: 'bare', description: '', aliases: [], children: [] };
|
||||
expect(formatCommandTree(node)).toBe('bare\n');
|
||||
});
|
||||
|
||||
it('indents children by two spaces per depth level and sorts siblings alphabetically', () => {
|
||||
const tree = {
|
||||
name: 'root',
|
||||
description: 'top',
|
||||
aliases: [],
|
||||
children: [
|
||||
{ name: 'beta', description: 'b', aliases: [], children: [] },
|
||||
{
|
||||
name: 'alpha',
|
||||
description: 'a',
|
||||
aliases: ['al'],
|
||||
children: [{ name: 'inner', description: 'i', aliases: [], children: [] }],
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(formatCommandTree(tree)).toBe(
|
||||
'root - top\n' + ' alpha (al) - a\n' + ' inner - i\n' + ' beta - b\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
35
packages/cli/src/command-tree.ts
Normal file
35
packages/cli/src/command-tree.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import type { Command } from '@commander-js/extra-typings';
|
||||
|
||||
export interface CommandTreeNode {
|
||||
name: string;
|
||||
description: string;
|
||||
aliases: string[];
|
||||
children: CommandTreeNode[];
|
||||
}
|
||||
|
||||
export function walkCommandTree(command: Command): CommandTreeNode {
|
||||
return {
|
||||
name: command.name(),
|
||||
description: command.description(),
|
||||
aliases: command.aliases(),
|
||||
children: command.commands.map((child) => walkCommandTree(child as Command)),
|
||||
};
|
||||
}
|
||||
|
||||
export function formatCommandTree(node: CommandTreeNode): string {
|
||||
const lines: string[] = [];
|
||||
appendNode(node, 0, lines);
|
||||
return `${lines.join('\n')}\n`;
|
||||
}
|
||||
|
||||
function appendNode(node: CommandTreeNode, depth: number, lines: string[]): void {
|
||||
const indent = ' '.repeat(depth);
|
||||
const aliasPart = node.aliases.length > 0 ? ` (${node.aliases.join(', ')})` : '';
|
||||
const descriptionPart = node.description.length > 0 ? ` - ${node.description}` : '';
|
||||
lines.push(`${indent}${node.name}${aliasPart}${descriptionPart}`);
|
||||
|
||||
const sortedChildren = [...node.children].sort((a, b) => a.name.localeCompare(b.name));
|
||||
for (const child of sortedChildren) {
|
||||
appendNode(child, depth + 1, lines);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue