@pablozaiden/terminatui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -43
- package/package.json +11 -8
- package/src/__tests__/application.test.ts +87 -68
- package/src/__tests__/buildCliCommand.test.ts +99 -119
- package/src/__tests__/builtins.test.ts +27 -75
- package/src/__tests__/command.test.ts +100 -131
- package/src/__tests__/configOnChange.test.ts +63 -0
- package/src/__tests__/context.test.ts +1 -26
- package/src/__tests__/helpCore.test.ts +227 -0
- package/src/__tests__/parser.test.ts +98 -244
- package/src/__tests__/registry.test.ts +33 -160
- package/src/__tests__/schemaToFields.test.ts +75 -158
- package/src/builtins/help.ts +12 -4
- package/src/builtins/settings.ts +18 -32
- package/src/builtins/version.ts +3 -3
- package/src/cli/output/colors.ts +1 -1
- package/src/cli/parser.ts +26 -95
- package/src/core/application.ts +192 -110
- package/src/core/command.ts +26 -9
- package/src/core/context.ts +31 -20
- package/src/core/help.ts +24 -18
- package/src/core/knownCommands.ts +13 -0
- package/src/core/logger.ts +39 -42
- package/src/core/registry.ts +5 -12
- package/src/index.ts +22 -137
- package/src/tui/TuiApplication.tsx +63 -120
- package/src/tui/TuiRoot.tsx +135 -0
- package/src/tui/adapters/factory.ts +19 -0
- package/src/tui/adapters/ink/InkRenderer.tsx +139 -0
- package/src/tui/adapters/ink/components/Button.tsx +12 -0
- package/src/tui/adapters/ink/components/Code.tsx +6 -0
- package/src/tui/adapters/ink/components/CodeHighlight.tsx +6 -0
- package/src/tui/adapters/ink/components/Container.tsx +5 -0
- package/src/tui/adapters/ink/components/Field.tsx +12 -0
- package/src/tui/adapters/ink/components/Label.tsx +24 -0
- package/src/tui/adapters/ink/components/MenuButton.tsx +12 -0
- package/src/tui/adapters/ink/components/MenuItem.tsx +17 -0
- package/src/tui/adapters/ink/components/Overlay.tsx +5 -0
- package/src/tui/adapters/ink/components/Panel.tsx +15 -0
- package/src/tui/adapters/ink/components/ScrollView.tsx +5 -0
- package/src/tui/adapters/ink/components/Select.tsx +44 -0
- package/src/tui/adapters/ink/components/Spacer.tsx +15 -0
- package/src/tui/adapters/ink/components/Spinner.tsx +5 -0
- package/src/tui/adapters/ink/components/TextInput.tsx +22 -0
- package/src/tui/adapters/ink/components/Value.tsx +7 -0
- package/src/tui/adapters/ink/keyboard.ts +97 -0
- package/src/tui/adapters/ink/utils.ts +16 -0
- package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +119 -0
- package/src/tui/adapters/opentui/components/Button.tsx +13 -0
- package/src/tui/adapters/opentui/components/Code.tsx +12 -0
- package/src/tui/adapters/opentui/components/CodeHighlight.tsx +24 -0
- package/src/tui/adapters/opentui/components/Container.tsx +56 -0
- package/src/tui/adapters/opentui/components/Field.tsx +18 -0
- package/src/tui/adapters/opentui/components/Label.tsx +15 -0
- package/src/tui/adapters/opentui/components/MenuButton.tsx +14 -0
- package/src/tui/adapters/opentui/components/MenuItem.tsx +29 -0
- package/src/tui/adapters/opentui/components/Overlay.tsx +21 -0
- package/src/tui/adapters/opentui/components/Panel.tsx +78 -0
- package/src/tui/adapters/opentui/components/ScrollView.tsx +85 -0
- package/src/tui/adapters/opentui/components/Select.tsx +59 -0
- package/src/tui/adapters/opentui/components/Spacer.tsx +5 -0
- package/src/tui/adapters/opentui/components/Spinner.tsx +12 -0
- package/src/tui/adapters/opentui/components/TextInput.tsx +13 -0
- package/src/tui/adapters/opentui/components/Value.tsx +13 -0
- package/src/tui/{hooks → adapters/opentui/hooks}/useSpinner.ts +2 -11
- package/src/tui/adapters/opentui/keyboard.ts +61 -0
- package/src/tui/adapters/types.ts +71 -0
- package/src/tui/components/ActionButton.tsx +0 -36
- package/src/tui/components/CommandSelector.tsx +45 -92
- package/src/tui/components/ConfigForm.tsx +68 -42
- package/src/tui/components/FieldRow.tsx +0 -30
- package/src/tui/components/Header.tsx +14 -13
- package/src/tui/components/JsonHighlight.tsx +10 -17
- package/src/tui/components/ModalBase.tsx +38 -0
- package/src/tui/components/ResultsPanel.tsx +27 -36
- package/src/tui/components/StatusBar.tsx +24 -39
- package/src/tui/components/logColors.ts +12 -0
- package/src/tui/context/ClipboardContext.tsx +87 -0
- package/src/tui/context/ExecutorContext.tsx +139 -0
- package/src/tui/context/KeyboardContext.tsx +85 -71
- package/src/tui/context/LogsContext.tsx +35 -0
- package/src/tui/context/NavigationContext.tsx +194 -0
- package/src/tui/context/RendererContext.tsx +20 -0
- package/src/tui/context/TuiAppContext.tsx +58 -0
- package/src/tui/hooks/useActiveKeyHandler.ts +75 -0
- package/src/tui/hooks/useBackHandler.ts +34 -0
- package/src/tui/hooks/useClipboard.ts +40 -25
- package/src/tui/hooks/useClipboardProvider.ts +42 -0
- package/src/tui/hooks/useGlobalKeyHandler.ts +54 -0
- package/src/tui/modals/CliModal.tsx +82 -0
- package/src/tui/modals/EditorModal.tsx +207 -0
- package/src/tui/modals/LogsModal.tsx +98 -0
- package/src/tui/registry.ts +102 -0
- package/src/tui/screens/CommandSelectScreen.tsx +162 -0
- package/src/tui/screens/ConfigScreen.tsx +165 -0
- package/src/tui/screens/ErrorScreen.tsx +58 -0
- package/src/tui/screens/ResultsScreen.tsx +68 -0
- package/src/tui/screens/RunningScreen.tsx +72 -0
- package/src/tui/screens/ScreenBase.ts +6 -0
- package/src/tui/semantic/Button.tsx +7 -0
- package/src/tui/semantic/Code.tsx +7 -0
- package/src/tui/semantic/CodeHighlight.tsx +7 -0
- package/src/tui/semantic/Container.tsx +7 -0
- package/src/tui/semantic/Field.tsx +7 -0
- package/src/tui/semantic/Label.tsx +7 -0
- package/src/tui/semantic/MenuButton.tsx +7 -0
- package/src/tui/semantic/MenuItem.tsx +7 -0
- package/src/tui/semantic/Overlay.tsx +7 -0
- package/src/tui/semantic/Panel.tsx +7 -0
- package/src/tui/semantic/ScrollView.tsx +9 -0
- package/src/tui/semantic/Select.tsx +7 -0
- package/src/tui/semantic/Spacer.tsx +7 -0
- package/src/tui/semantic/Spinner.tsx +7 -0
- package/src/tui/semantic/TextInput.tsx +7 -0
- package/src/tui/semantic/Value.tsx +7 -0
- package/src/tui/semantic/types.ts +195 -0
- package/src/tui/theme.ts +25 -14
- package/src/tui/utils/buildCliCommand.ts +1 -0
- package/src/tui/utils/getEnumKeys.ts +3 -0
- package/src/tui/utils/parameterPersistence.ts +1 -0
- package/src/types/command.ts +0 -60
- package/.devcontainer/devcontainer.json +0 -19
- package/.devcontainer/install-prerequisites.sh +0 -49
- package/.github/workflows/copilot-setup-steps.yml +0 -32
- package/.github/workflows/pull-request.yml +0 -27
- package/.github/workflows/release-npm-package.yml +0 -81
- package/AGENTS.md +0 -31
- package/bun.lock +0 -236
- package/examples/tui-app/commands/config/app/get.ts +0 -66
- package/examples/tui-app/commands/config/app/index.ts +0 -27
- package/examples/tui-app/commands/config/app/set.ts +0 -86
- package/examples/tui-app/commands/config/index.ts +0 -32
- package/examples/tui-app/commands/config/user/get.ts +0 -65
- package/examples/tui-app/commands/config/user/index.ts +0 -27
- package/examples/tui-app/commands/config/user/set.ts +0 -61
- package/examples/tui-app/commands/greet.ts +0 -76
- package/examples/tui-app/commands/index.ts +0 -4
- package/examples/tui-app/commands/math.ts +0 -115
- package/examples/tui-app/commands/status.ts +0 -77
- package/examples/tui-app/index.ts +0 -35
- package/guides/01-hello-world.md +0 -96
- package/guides/02-adding-options.md +0 -103
- package/guides/03-multiple-commands.md +0 -163
- package/guides/04-subcommands.md +0 -206
- package/guides/05-interactive-tui.md +0 -194
- package/guides/06-config-validation.md +0 -264
- package/guides/07-async-cancellation.md +0 -336
- package/guides/08-complete-application.md +0 -537
- package/guides/README.md +0 -74
- package/src/__tests__/colors.test.ts +0 -127
- package/src/__tests__/commandClass.test.ts +0 -130
- package/src/__tests__/help.test.ts +0 -412
- package/src/__tests__/registryNew.test.ts +0 -160
- package/src/__tests__/table.test.ts +0 -146
- package/src/__tests__/tui.test.ts +0 -26
- package/src/builtins/index.ts +0 -4
- package/src/cli/help.ts +0 -174
- package/src/cli/index.ts +0 -3
- package/src/cli/output/index.ts +0 -2
- package/src/cli/output/table.ts +0 -141
- package/src/commands/help.ts +0 -50
- package/src/commands/index.ts +0 -1
- package/src/components/index.ts +0 -147
- package/src/core/index.ts +0 -15
- package/src/hooks/index.ts +0 -131
- package/src/registry/commandRegistry.ts +0 -77
- package/src/registry/index.ts +0 -1
- package/src/tui/TuiApp.tsx +0 -619
- package/src/tui/app.ts +0 -29
- package/src/tui/components/CliModal.tsx +0 -81
- package/src/tui/components/EditorModal.tsx +0 -177
- package/src/tui/components/LogsPanel.tsx +0 -86
- package/src/tui/components/index.ts +0 -13
- package/src/tui/context/index.ts +0 -7
- package/src/tui/hooks/index.ts +0 -35
- package/src/tui/hooks/useKeyboardHandler.ts +0 -91
- package/src/tui/hooks/useLogStream.ts +0 -96
- package/src/tui/index.ts +0 -65
- package/src/tui/utils/index.ts +0 -13
- package/src/types/index.ts +0 -1
- package/tsconfig.json +0 -25
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe } from "bun:test";
|
|
2
|
-
import { colors, supportsColors } from "../cli/output/colors.ts";
|
|
3
|
-
|
|
4
|
-
describe("colors", () => {
|
|
5
|
-
describe("basic colors", () => {
|
|
6
|
-
test("red wraps text", () => {
|
|
7
|
-
const result = colors.red("test");
|
|
8
|
-
expect(result).toContain("test");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("green wraps text", () => {
|
|
12
|
-
const result = colors.green("test");
|
|
13
|
-
expect(result).toContain("test");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("blue wraps text", () => {
|
|
17
|
-
const result = colors.blue("test");
|
|
18
|
-
expect(result).toContain("test");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("yellow wraps text", () => {
|
|
22
|
-
const result = colors.yellow("test");
|
|
23
|
-
expect(result).toContain("test");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("cyan wraps text", () => {
|
|
27
|
-
const result = colors.cyan("test");
|
|
28
|
-
expect(result).toContain("test");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("gray wraps text", () => {
|
|
32
|
-
const result = colors.gray("test");
|
|
33
|
-
expect(result).toContain("test");
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe("styles", () => {
|
|
38
|
-
test("bold wraps text", () => {
|
|
39
|
-
const result = colors.bold("test");
|
|
40
|
-
expect(result).toContain("test");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("dim wraps text", () => {
|
|
44
|
-
const result = colors.dim("test");
|
|
45
|
-
expect(result).toContain("test");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("italic wraps text", () => {
|
|
49
|
-
const result = colors.italic("test");
|
|
50
|
-
expect(result).toContain("test");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("underline wraps text", () => {
|
|
54
|
-
const result = colors.underline("test");
|
|
55
|
-
expect(result).toContain("test");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("strikethrough wraps text", () => {
|
|
59
|
-
const result = colors.strikethrough("test");
|
|
60
|
-
expect(result).toContain("test");
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe("semantic colors", () => {
|
|
65
|
-
test("success includes checkmark and message", () => {
|
|
66
|
-
const result = colors.success("done");
|
|
67
|
-
expect(result).toContain("done");
|
|
68
|
-
expect(result).toContain("✓");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("error includes message", () => {
|
|
72
|
-
const result = colors.error("failed");
|
|
73
|
-
expect(result).toContain("failed");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("warning includes message", () => {
|
|
77
|
-
const result = colors.warning("caution");
|
|
78
|
-
expect(result).toContain("caution");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("info includes message", () => {
|
|
82
|
-
const result = colors.info("note");
|
|
83
|
-
expect(result).toContain("note");
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe("chaining", () => {
|
|
88
|
-
test("can combine bold and red", () => {
|
|
89
|
-
const result = colors.bold(colors.red("test"));
|
|
90
|
-
expect(result).toContain("test");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("can combine dim and italic", () => {
|
|
94
|
-
const result = colors.dim(colors.italic("test"));
|
|
95
|
-
expect(result).toContain("test");
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("edge cases", () => {
|
|
100
|
-
test("handles empty string", () => {
|
|
101
|
-
const result = colors.red("");
|
|
102
|
-
expect(typeof result).toBe("string");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("handles string with newlines", () => {
|
|
106
|
-
const result = colors.blue("line1\nline2");
|
|
107
|
-
expect(result).toContain("line1");
|
|
108
|
-
expect(result).toContain("line2");
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("handles string with special characters", () => {
|
|
112
|
-
const result = colors.green("test © ® ™");
|
|
113
|
-
expect(result).toContain("©");
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe("supportsColors", () => {
|
|
119
|
-
test("is a function", () => {
|
|
120
|
-
expect(typeof supportsColors).toBe("function");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("returns a boolean", () => {
|
|
124
|
-
const result = supportsColors();
|
|
125
|
-
expect(typeof result).toBe("boolean");
|
|
126
|
-
});
|
|
127
|
-
});
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import { Command, type CommandResult } from "../core/command.ts";
|
|
3
|
-
import type { AppContext } from "../core/context.ts";
|
|
4
|
-
import type { OptionSchema, OptionValues } from "../types/command.ts";
|
|
5
|
-
|
|
6
|
-
// Test command with options
|
|
7
|
-
class TestCommand extends Command<{ name: { type: "string"; description: string } }> {
|
|
8
|
-
readonly name = "test";
|
|
9
|
-
readonly description = "A test command";
|
|
10
|
-
readonly options = {
|
|
11
|
-
name: { type: "string" as const, description: "Name option" },
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
executedWith: OptionValues<typeof this.options> | null = null;
|
|
15
|
-
|
|
16
|
-
override async execute(
|
|
17
|
-
_ctx: AppContext,
|
|
18
|
-
opts: OptionValues<typeof this.options>
|
|
19
|
-
): Promise<CommandResult> {
|
|
20
|
-
this.executedWith = opts;
|
|
21
|
-
return { success: true, message: "Executed" };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Simple command without options
|
|
26
|
-
class SimpleCommand extends Command<OptionSchema> {
|
|
27
|
-
readonly name = "simple";
|
|
28
|
-
readonly description = "A simple command";
|
|
29
|
-
readonly options = {};
|
|
30
|
-
|
|
31
|
-
executed = false;
|
|
32
|
-
|
|
33
|
-
override async execute(_ctx: AppContext): Promise<CommandResult> {
|
|
34
|
-
this.executed = true;
|
|
35
|
-
return { success: true, message: "Done" };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe("Command", () => {
|
|
40
|
-
describe("core properties", () => {
|
|
41
|
-
test("has name", () => {
|
|
42
|
-
const cmd = new TestCommand();
|
|
43
|
-
expect(cmd.name).toBe("test");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("has description", () => {
|
|
47
|
-
const cmd = new TestCommand();
|
|
48
|
-
expect(cmd.description).toBe("A test command");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("has options", () => {
|
|
52
|
-
const cmd = new TestCommand();
|
|
53
|
-
expect(cmd.options).toEqual({
|
|
54
|
-
name: { type: "string", description: "Name option" },
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("optional metadata", () => {
|
|
60
|
-
test("subCommands defaults to undefined", () => {
|
|
61
|
-
const cmd = new TestCommand();
|
|
62
|
-
expect(cmd.subCommands).toBeUndefined();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("examples defaults to undefined", () => {
|
|
66
|
-
const cmd = new TestCommand();
|
|
67
|
-
expect(cmd.examples).toBeUndefined();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("longDescription defaults to undefined", () => {
|
|
71
|
-
const cmd = new TestCommand();
|
|
72
|
-
expect(cmd.longDescription).toBeUndefined();
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("supportsCli", () => {
|
|
77
|
-
test("returns true for command with execute", () => {
|
|
78
|
-
const cmd = new TestCommand();
|
|
79
|
-
expect(cmd.supportsCli()).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe("supportsTui", () => {
|
|
84
|
-
test("returns true for command with execute", () => {
|
|
85
|
-
const cmd = new TestCommand();
|
|
86
|
-
expect(cmd.supportsTui()).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe("both modes", () => {
|
|
91
|
-
test("command supports both CLI and TUI", () => {
|
|
92
|
-
const cmd = new TestCommand();
|
|
93
|
-
expect(cmd.supportsCli()).toBe(true);
|
|
94
|
-
expect(cmd.supportsTui()).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe("validate", () => {
|
|
99
|
-
test("passes for command with execute", () => {
|
|
100
|
-
const cmd = new TestCommand();
|
|
101
|
-
expect(() => cmd.validate()).not.toThrow();
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe("subcommands", () => {
|
|
106
|
-
test("hasSubCommands returns false when no subcommands", () => {
|
|
107
|
-
const cmd = new TestCommand();
|
|
108
|
-
expect(cmd.hasSubCommands()).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("hasSubCommands returns true when subcommands exist", () => {
|
|
112
|
-
const cmd = new TestCommand();
|
|
113
|
-
cmd.subCommands = [new SimpleCommand()];
|
|
114
|
-
expect(cmd.hasSubCommands()).toBe(true);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("getSubCommand finds subcommand by name", () => {
|
|
118
|
-
const cmd = new TestCommand();
|
|
119
|
-
const subCmd = new SimpleCommand();
|
|
120
|
-
cmd.subCommands = [subCmd];
|
|
121
|
-
expect(cmd.getSubCommand("simple")).toBe(subCmd);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("getSubCommand returns undefined for unknown name", () => {
|
|
125
|
-
const cmd = new TestCommand();
|
|
126
|
-
cmd.subCommands = [new SimpleCommand()];
|
|
127
|
-
expect(cmd.getSubCommand("unknown")).toBeUndefined();
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
});
|
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
generateHelp,
|
|
4
|
-
formatUsage,
|
|
5
|
-
formatCommands,
|
|
6
|
-
formatOptions,
|
|
7
|
-
formatExamples,
|
|
8
|
-
getCommandSummary,
|
|
9
|
-
formatGlobalOptions,
|
|
10
|
-
generateCommandHelp,
|
|
11
|
-
} from "../cli/help.ts";
|
|
12
|
-
import { defineCommand } from "../types/command.ts";
|
|
13
|
-
|
|
14
|
-
describe("Help Generation", () => {
|
|
15
|
-
describe("formatUsage", () => {
|
|
16
|
-
test("formats basic usage", () => {
|
|
17
|
-
const cmd = defineCommand({
|
|
18
|
-
name: "test",
|
|
19
|
-
description: "Test command",
|
|
20
|
-
execute: () => {},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const usage = formatUsage(cmd, "myapp");
|
|
24
|
-
expect(usage).toContain("myapp");
|
|
25
|
-
expect(usage).toContain("test");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("includes [command] for commands with subcommands", () => {
|
|
29
|
-
const cmd = defineCommand({
|
|
30
|
-
name: "parent",
|
|
31
|
-
description: "Parent command",
|
|
32
|
-
subcommands: {
|
|
33
|
-
child: defineCommand({
|
|
34
|
-
name: "child",
|
|
35
|
-
description: "Child",
|
|
36
|
-
execute: () => {},
|
|
37
|
-
}),
|
|
38
|
-
},
|
|
39
|
-
execute: () => {},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const usage = formatUsage(cmd);
|
|
43
|
-
expect(usage).toContain("[command]");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("includes [options] for commands with options", () => {
|
|
47
|
-
const cmd = defineCommand({
|
|
48
|
-
name: "test",
|
|
49
|
-
description: "Test command",
|
|
50
|
-
options: {
|
|
51
|
-
verbose: { type: "boolean", description: "Verbose" },
|
|
52
|
-
},
|
|
53
|
-
execute: () => {},
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const usage = formatUsage(cmd);
|
|
57
|
-
expect(usage).toContain("[options]");
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe("formatCommands", () => {
|
|
62
|
-
test("formats subcommands list", () => {
|
|
63
|
-
const cmd = defineCommand({
|
|
64
|
-
name: "parent",
|
|
65
|
-
description: "Parent",
|
|
66
|
-
subcommands: {
|
|
67
|
-
child: defineCommand({
|
|
68
|
-
name: "child",
|
|
69
|
-
description: "Child command",
|
|
70
|
-
execute: () => {},
|
|
71
|
-
}),
|
|
72
|
-
},
|
|
73
|
-
execute: () => {},
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const commands = formatCommands(cmd);
|
|
77
|
-
expect(commands).toContain("child");
|
|
78
|
-
expect(commands).toContain("Child command");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("excludes hidden commands", () => {
|
|
82
|
-
const cmd = defineCommand({
|
|
83
|
-
name: "parent",
|
|
84
|
-
description: "Parent",
|
|
85
|
-
subcommands: {
|
|
86
|
-
visible: defineCommand({
|
|
87
|
-
name: "visible",
|
|
88
|
-
description: "Visible",
|
|
89
|
-
execute: () => {},
|
|
90
|
-
}),
|
|
91
|
-
hidden: defineCommand({
|
|
92
|
-
name: "hidden",
|
|
93
|
-
description: "Hidden",
|
|
94
|
-
hidden: true,
|
|
95
|
-
execute: () => {},
|
|
96
|
-
}),
|
|
97
|
-
},
|
|
98
|
-
execute: () => {},
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const commands = formatCommands(cmd);
|
|
102
|
-
expect(commands).toContain("visible");
|
|
103
|
-
expect(commands).not.toMatch(/\bhidden\b/);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("shows command aliases", () => {
|
|
107
|
-
const cmd = defineCommand({
|
|
108
|
-
name: "parent",
|
|
109
|
-
description: "Parent",
|
|
110
|
-
subcommands: {
|
|
111
|
-
list: defineCommand({
|
|
112
|
-
name: "list",
|
|
113
|
-
description: "List items",
|
|
114
|
-
aliases: ["ls", "l"],
|
|
115
|
-
execute: () => {},
|
|
116
|
-
}),
|
|
117
|
-
},
|
|
118
|
-
execute: () => {},
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const commands = formatCommands(cmd);
|
|
122
|
-
expect(commands).toContain("ls");
|
|
123
|
-
expect(commands).toContain("l");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("returns empty for no subcommands", () => {
|
|
127
|
-
const cmd = defineCommand({
|
|
128
|
-
name: "test",
|
|
129
|
-
description: "Test",
|
|
130
|
-
execute: () => {},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const commands = formatCommands(cmd);
|
|
134
|
-
expect(commands).toBe("");
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe("formatOptions", () => {
|
|
139
|
-
test("formats options with descriptions", () => {
|
|
140
|
-
const cmd = defineCommand({
|
|
141
|
-
name: "test",
|
|
142
|
-
description: "Test",
|
|
143
|
-
options: {
|
|
144
|
-
verbose: { type: "boolean", description: "Enable verbose" },
|
|
145
|
-
},
|
|
146
|
-
execute: () => {},
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const options = formatOptions(cmd);
|
|
150
|
-
expect(options).toContain("--verbose");
|
|
151
|
-
expect(options).toContain("Enable verbose");
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("shows option aliases", () => {
|
|
155
|
-
const cmd = defineCommand({
|
|
156
|
-
name: "test",
|
|
157
|
-
description: "Test",
|
|
158
|
-
options: {
|
|
159
|
-
verbose: { type: "boolean", alias: "v", description: "Verbose" },
|
|
160
|
-
},
|
|
161
|
-
execute: () => {},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const options = formatOptions(cmd);
|
|
165
|
-
expect(options).toContain("-v");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("shows default values", () => {
|
|
169
|
-
const cmd = defineCommand({
|
|
170
|
-
name: "test",
|
|
171
|
-
description: "Test",
|
|
172
|
-
options: {
|
|
173
|
-
count: { type: "number", default: 10, description: "Count" },
|
|
174
|
-
},
|
|
175
|
-
execute: () => {},
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const options = formatOptions(cmd);
|
|
179
|
-
expect(options).toContain("10");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("shows required marker", () => {
|
|
183
|
-
const cmd = defineCommand({
|
|
184
|
-
name: "test",
|
|
185
|
-
description: "Test",
|
|
186
|
-
options: {
|
|
187
|
-
name: { type: "string", required: true, description: "Name" },
|
|
188
|
-
},
|
|
189
|
-
execute: () => {},
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const options = formatOptions(cmd);
|
|
193
|
-
expect(options).toContain("*");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test("shows enum values", () => {
|
|
197
|
-
const cmd = defineCommand({
|
|
198
|
-
name: "test",
|
|
199
|
-
description: "Test",
|
|
200
|
-
options: {
|
|
201
|
-
level: {
|
|
202
|
-
type: "string",
|
|
203
|
-
enum: ["low", "high"],
|
|
204
|
-
description: "Level",
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
execute: () => {},
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const options = formatOptions(cmd);
|
|
211
|
-
expect(options).toContain("low");
|
|
212
|
-
expect(options).toContain("high");
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("returns empty for no options", () => {
|
|
216
|
-
const cmd = defineCommand({
|
|
217
|
-
name: "test",
|
|
218
|
-
description: "Test",
|
|
219
|
-
execute: () => {},
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const options = formatOptions(cmd);
|
|
223
|
-
expect(options).toBe("");
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe("formatExamples", () => {
|
|
228
|
-
test("formats examples list", () => {
|
|
229
|
-
const cmd = defineCommand({
|
|
230
|
-
name: "test",
|
|
231
|
-
description: "Test",
|
|
232
|
-
examples: [
|
|
233
|
-
{ command: "test --verbose", description: "Run with verbose" },
|
|
234
|
-
],
|
|
235
|
-
execute: () => {},
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const examples = formatExamples(cmd);
|
|
239
|
-
expect(examples).toContain("test --verbose");
|
|
240
|
-
expect(examples).toContain("Run with verbose");
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("returns empty for no examples", () => {
|
|
244
|
-
const cmd = defineCommand({
|
|
245
|
-
name: "test",
|
|
246
|
-
description: "Test",
|
|
247
|
-
execute: () => {},
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
const examples = formatExamples(cmd);
|
|
251
|
-
expect(examples).toBe("");
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe("generateHelp", () => {
|
|
256
|
-
test("generates help with app name and version", () => {
|
|
257
|
-
const cmd = defineCommand({
|
|
258
|
-
name: "root",
|
|
259
|
-
description: "Root command",
|
|
260
|
-
execute: () => {},
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const help = generateHelp(cmd, { appName: "myapp", version: "1.0.0" });
|
|
264
|
-
expect(help).toContain("myapp");
|
|
265
|
-
expect(help).toContain("1.0.0");
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test("includes usage section", () => {
|
|
269
|
-
const cmd = defineCommand({
|
|
270
|
-
name: "test",
|
|
271
|
-
description: "Test",
|
|
272
|
-
execute: () => {},
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
const help = generateHelp(cmd);
|
|
276
|
-
expect(help).toContain("Usage:");
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test("includes command description", () => {
|
|
280
|
-
const cmd = defineCommand({
|
|
281
|
-
name: "test",
|
|
282
|
-
description: "A test command for testing",
|
|
283
|
-
execute: () => {},
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
const help = generateHelp(cmd);
|
|
287
|
-
expect(help).toContain("A test command for testing");
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
test("includes options section", () => {
|
|
291
|
-
const cmd = defineCommand({
|
|
292
|
-
name: "test",
|
|
293
|
-
description: "Test",
|
|
294
|
-
options: {
|
|
295
|
-
verbose: { type: "boolean", description: "Verbose mode" },
|
|
296
|
-
},
|
|
297
|
-
execute: () => {},
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const help = generateHelp(cmd);
|
|
301
|
-
expect(help).toContain("Options:");
|
|
302
|
-
expect(help).toContain("--verbose");
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test("generates root help with commands", () => {
|
|
306
|
-
const cmd = defineCommand({
|
|
307
|
-
name: "root",
|
|
308
|
-
description: "Root",
|
|
309
|
-
subcommands: {
|
|
310
|
-
run: defineCommand({
|
|
311
|
-
name: "run",
|
|
312
|
-
description: "Run something",
|
|
313
|
-
execute: () => {},
|
|
314
|
-
}),
|
|
315
|
-
},
|
|
316
|
-
execute: () => {},
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const help = generateHelp(cmd);
|
|
320
|
-
expect(help).toContain("Commands:");
|
|
321
|
-
expect(help).toContain("run");
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
describe("getCommandSummary", () => {
|
|
326
|
-
test("returns command summary", () => {
|
|
327
|
-
const cmd = defineCommand({
|
|
328
|
-
name: "test",
|
|
329
|
-
description: "A test command",
|
|
330
|
-
execute: () => {},
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
const summary = getCommandSummary(cmd);
|
|
334
|
-
expect(summary).toContain("test");
|
|
335
|
-
expect(summary).toContain("A test command");
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("includes aliases in summary", () => {
|
|
339
|
-
const cmd = defineCommand({
|
|
340
|
-
name: "list",
|
|
341
|
-
description: "List items",
|
|
342
|
-
aliases: ["ls"],
|
|
343
|
-
execute: () => {},
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
const summary = getCommandSummary(cmd);
|
|
347
|
-
expect(summary).toContain("ls");
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
describe("formatGlobalOptions", () => {
|
|
352
|
-
test("includes --log-level option", () => {
|
|
353
|
-
const result = formatGlobalOptions();
|
|
354
|
-
expect(result).toContain("--log-level");
|
|
355
|
-
expect(result).toContain("Set log level");
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
test("includes log level choices", () => {
|
|
359
|
-
const result = formatGlobalOptions();
|
|
360
|
-
expect(result).toContain("silly");
|
|
361
|
-
expect(result).toContain("trace");
|
|
362
|
-
expect(result).toContain("debug");
|
|
363
|
-
expect(result).toContain("info");
|
|
364
|
-
expect(result).toContain("warn");
|
|
365
|
-
expect(result).toContain("error");
|
|
366
|
-
expect(result).toContain("fatal");
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
test("includes --detailed-logs option", () => {
|
|
370
|
-
const result = formatGlobalOptions();
|
|
371
|
-
expect(result).toContain("--detailed-logs");
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
test("includes --no-detailed-logs option", () => {
|
|
375
|
-
const result = formatGlobalOptions();
|
|
376
|
-
expect(result).toContain("--no-detailed-logs");
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
describe("generateCommandHelp with global options", () => {
|
|
381
|
-
test("includes Global Options section", () => {
|
|
382
|
-
const cmd = defineCommand({
|
|
383
|
-
name: "test",
|
|
384
|
-
description: "Test command",
|
|
385
|
-
execute: () => {},
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
const help = generateCommandHelp(cmd, "myapp");
|
|
389
|
-
expect(help).toContain("Global Options:");
|
|
390
|
-
expect(help).toContain("--log-level");
|
|
391
|
-
expect(help).toContain("--detailed-logs");
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
test("global options appear after command options", () => {
|
|
395
|
-
const cmd = defineCommand({
|
|
396
|
-
name: "test",
|
|
397
|
-
description: "Test command",
|
|
398
|
-
options: {
|
|
399
|
-
verbose: { type: "boolean", description: "Verbose output" },
|
|
400
|
-
},
|
|
401
|
-
execute: () => {},
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
const help = generateCommandHelp(cmd, "myapp");
|
|
405
|
-
const optionsIndex = help.indexOf("Options:");
|
|
406
|
-
const globalOptionsIndex = help.indexOf("Global Options:");
|
|
407
|
-
|
|
408
|
-
expect(optionsIndex).toBeGreaterThan(-1);
|
|
409
|
-
expect(globalOptionsIndex).toBeGreaterThan(optionsIndex);
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
});
|