@pablozaiden/terminatui 0.2.0 → 0.3.0-beta-1
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/AGENTS.md +14 -2
- package/CLAUDE.md +1 -0
- package/README.md +64 -43
- package/bun.lock +85 -0
- package/examples/tui-app/commands/config/app/get.ts +6 -10
- package/examples/tui-app/commands/config/app/index.ts +2 -6
- package/examples/tui-app/commands/config/app/set.ts +23 -13
- package/examples/tui-app/commands/config/index.ts +2 -6
- package/examples/tui-app/commands/config/user/get.ts +6 -10
- package/examples/tui-app/commands/config/user/index.ts +2 -6
- package/examples/tui-app/commands/config/user/set.ts +6 -10
- package/examples/tui-app/commands/greet.ts +13 -11
- package/examples/tui-app/commands/math.ts +5 -9
- package/examples/tui-app/commands/status.ts +21 -12
- package/examples/tui-app/index.ts +6 -3
- package/guides/01-hello-world.md +7 -2
- package/guides/02-adding-options.md +2 -2
- package/guides/03-multiple-commands.md +6 -8
- package/guides/04-subcommands.md +8 -8
- package/guides/05-interactive-tui.md +45 -30
- package/guides/06-config-validation.md +4 -12
- package/guides/07-async-cancellation.md +14 -16
- package/guides/08-complete-application.md +12 -42
- package/guides/README.md +7 -3
- package/package.json +4 -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__/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 +4 -4
- 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/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 +135 -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 +115 -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 +70 -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 +160 -0
- package/src/tui/screens/ErrorScreen.tsx +58 -0
- package/src/tui/screens/ResultsScreen.tsx +60 -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/examples/tui-app/commands/index.ts +0 -4
- 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/index.ts +0 -137
- 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
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScrollViewProps, ScrollViewRef } from "./types.ts";
|
|
2
|
+
import { useRenderer } from "../context/RendererContext.tsx";
|
|
3
|
+
|
|
4
|
+
export function ScrollView(props: ScrollViewProps) {
|
|
5
|
+
const renderer = useRenderer();
|
|
6
|
+
return renderer.components.ScrollView(props);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type { ScrollViewRef };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type Align = "flex-start" | "center" | "flex-end" | "stretch";
|
|
4
|
+
export type Justify = "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | "space-evenly";
|
|
5
|
+
export type FlexDirection = "row" | "column";
|
|
6
|
+
|
|
7
|
+
export interface Spacing {
|
|
8
|
+
top?: number;
|
|
9
|
+
right?: number;
|
|
10
|
+
bottom?: number;
|
|
11
|
+
left?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ThemeConfig {
|
|
15
|
+
colors: {
|
|
16
|
+
background: string;
|
|
17
|
+
panelBackground: string;
|
|
18
|
+
overlay: string;
|
|
19
|
+
|
|
20
|
+
text: string;
|
|
21
|
+
mutedText: string;
|
|
22
|
+
inverseText: string;
|
|
23
|
+
|
|
24
|
+
border: string;
|
|
25
|
+
focusBorder: string;
|
|
26
|
+
|
|
27
|
+
primary: string;
|
|
28
|
+
primaryText: string;
|
|
29
|
+
|
|
30
|
+
success: string;
|
|
31
|
+
warning: string;
|
|
32
|
+
error: string;
|
|
33
|
+
|
|
34
|
+
value: string;
|
|
35
|
+
code: string;
|
|
36
|
+
|
|
37
|
+
selectionBackground: string;
|
|
38
|
+
selectionText: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SemanticColor = keyof ThemeConfig["colors"];
|
|
43
|
+
|
|
44
|
+
export interface LayoutProps {
|
|
45
|
+
flex?: number;
|
|
46
|
+
width?: number | string;
|
|
47
|
+
height?: number | string;
|
|
48
|
+
|
|
49
|
+
flexDirection?: FlexDirection;
|
|
50
|
+
alignItems?: Align;
|
|
51
|
+
justifyContent?: Justify;
|
|
52
|
+
|
|
53
|
+
gap?: number;
|
|
54
|
+
padding?: number | Spacing;
|
|
55
|
+
|
|
56
|
+
/** When set, should prevent the node from shrinking in flex layouts. */
|
|
57
|
+
noShrink?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type PanelSurface = "panel" | "overlay";
|
|
61
|
+
|
|
62
|
+
export interface PanelProps extends LayoutProps {
|
|
63
|
+
title?: string;
|
|
64
|
+
focused?: boolean;
|
|
65
|
+
border?: boolean;
|
|
66
|
+
surface?: PanelSurface;
|
|
67
|
+
|
|
68
|
+
/** Renderer-level compact styling (e.g. no default padding). */
|
|
69
|
+
dense?: boolean;
|
|
70
|
+
|
|
71
|
+
children?: ReactNode;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ContainerProps extends LayoutProps {
|
|
75
|
+
children?: ReactNode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ScrollViewRef {
|
|
79
|
+
scrollToTop: () => void;
|
|
80
|
+
scrollToBottom: () => void;
|
|
81
|
+
scrollToIndex: (index: number) => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ScrollViewProps extends LayoutProps {
|
|
85
|
+
axis?: "vertical" | "horizontal" | "both";
|
|
86
|
+
stickyToEnd?: boolean;
|
|
87
|
+
focused?: boolean;
|
|
88
|
+
scrollRef?: (ref: ScrollViewRef | null) => void;
|
|
89
|
+
children?: ReactNode;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface OverlayProps {
|
|
93
|
+
zIndex?: number;
|
|
94
|
+
top?: number | string;
|
|
95
|
+
left?: number | string;
|
|
96
|
+
right?: number | string;
|
|
97
|
+
bottom?: number | string;
|
|
98
|
+
width?: number | string;
|
|
99
|
+
height?: number | string;
|
|
100
|
+
children?: ReactNode;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface SpacerProps {
|
|
104
|
+
size: number;
|
|
105
|
+
axis?: "horizontal" | "vertical";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface SpinnerProps {
|
|
109
|
+
active: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface LabelProps {
|
|
113
|
+
color?: SemanticColor;
|
|
114
|
+
bold?: boolean;
|
|
115
|
+
italic?: boolean;
|
|
116
|
+
wrap?: boolean;
|
|
117
|
+
children: ReactNode;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ValueProps {
|
|
121
|
+
color?: SemanticColor;
|
|
122
|
+
truncate?: boolean;
|
|
123
|
+
children: ReactNode;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface CodeProps {
|
|
127
|
+
color?: SemanticColor;
|
|
128
|
+
children: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type CodeTokenType =
|
|
132
|
+
| "punctuation"
|
|
133
|
+
| "string"
|
|
134
|
+
| "number"
|
|
135
|
+
| "boolean"
|
|
136
|
+
| "null"
|
|
137
|
+
| "key"
|
|
138
|
+
| "unknown";
|
|
139
|
+
|
|
140
|
+
export interface CodeToken {
|
|
141
|
+
type: CodeTokenType;
|
|
142
|
+
value: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface CodeHighlightProps {
|
|
146
|
+
tokens: CodeToken[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface FieldProps {
|
|
150
|
+
label: string;
|
|
151
|
+
value: ReactNode;
|
|
152
|
+
selected?: boolean;
|
|
153
|
+
onActivate?: () => void;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface TextInputProps {
|
|
157
|
+
value: string;
|
|
158
|
+
placeholder?: string;
|
|
159
|
+
focused?: boolean;
|
|
160
|
+
onChange: (value: string) => void;
|
|
161
|
+
onSubmit?: () => void;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface SelectOption {
|
|
165
|
+
label: string;
|
|
166
|
+
value: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface SelectProps {
|
|
170
|
+
options: SelectOption[];
|
|
171
|
+
value: string;
|
|
172
|
+
focused?: boolean;
|
|
173
|
+
onChange: (value: string) => void;
|
|
174
|
+
onSubmit?: () => void;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface ButtonProps {
|
|
178
|
+
label: string;
|
|
179
|
+
selected?: boolean;
|
|
180
|
+
onActivate?: () => void;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface MenuButtonProps {
|
|
184
|
+
label: string;
|
|
185
|
+
selected?: boolean;
|
|
186
|
+
onActivate?: () => void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface MenuItemProps {
|
|
190
|
+
label: string;
|
|
191
|
+
description?: string;
|
|
192
|
+
suffix?: string;
|
|
193
|
+
selected?: boolean;
|
|
194
|
+
onActivate?: () => void;
|
|
195
|
+
}
|
package/src/tui/theme.ts
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Default TUI theme
|
|
2
|
+
* Default TUI theme.
|
|
3
|
+
*
|
|
4
|
+
* This is intentionally semantic: UI code should reference meanings
|
|
5
|
+
* (e.g. "error", "mutedText", "selectionBackground") rather than hardcoding colors.
|
|
3
6
|
*/
|
|
4
|
-
export const
|
|
5
|
-
background: "#
|
|
7
|
+
export const SemanticColors = {
|
|
8
|
+
background: "#0f1117",
|
|
9
|
+
panelBackground: "#0f1117",
|
|
10
|
+
overlay: "#101218",
|
|
11
|
+
|
|
12
|
+
text: "#d6dde6",
|
|
13
|
+
mutedText: "#666666",
|
|
14
|
+
inverseText: "#0b0c10",
|
|
15
|
+
|
|
6
16
|
border: "#2c2f36",
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
header: "#a8b3c1",
|
|
13
|
-
statusText: "#d6dde6",
|
|
14
|
-
overlay: "#0e1117",
|
|
15
|
-
overlayTitle: "#e5c07b",
|
|
16
|
-
error: "#f78888",
|
|
17
|
+
focusBorder: "#5da9e9",
|
|
18
|
+
|
|
19
|
+
primary: "#61afef",
|
|
20
|
+
primaryText: "#0b0c10",
|
|
21
|
+
|
|
17
22
|
success: "#98c379",
|
|
18
23
|
warning: "#f5c542",
|
|
24
|
+
error: "#f78888",
|
|
25
|
+
|
|
26
|
+
value: "#98c379",
|
|
27
|
+
code: "#c0cad6",
|
|
28
|
+
|
|
29
|
+
selectionBackground: "#61afef",
|
|
30
|
+
selectionText: "#0b0c10",
|
|
19
31
|
} as const;
|
|
20
32
|
|
|
21
|
-
export type ThemeColors = typeof Theme;
|
package/src/types/command.ts
CHANGED
|
@@ -41,63 +41,3 @@ export type OptionValues<T extends OptionSchema> = {
|
|
|
41
41
|
: unknown;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
/**
|
|
45
|
-
* Context passed to command executors
|
|
46
|
-
*/
|
|
47
|
-
export interface CommandContext<T extends OptionSchema = OptionSchema> {
|
|
48
|
-
options: OptionValues<T>;
|
|
49
|
-
args: string[];
|
|
50
|
-
commandPath: string[];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Command executor function
|
|
55
|
-
*/
|
|
56
|
-
export type CommandExecutor<T extends OptionSchema = OptionSchema> = (
|
|
57
|
-
ctx: CommandContext<T>
|
|
58
|
-
) => void | Promise<void>;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Command definition
|
|
62
|
-
*/
|
|
63
|
-
export interface Command<
|
|
64
|
-
T extends OptionSchema = OptionSchema,
|
|
65
|
-
R = void,
|
|
66
|
-
> {
|
|
67
|
-
name: string;
|
|
68
|
-
description: string;
|
|
69
|
-
aliases?: string[];
|
|
70
|
-
hidden?: boolean;
|
|
71
|
-
options?: T;
|
|
72
|
-
subcommands?: Record<string, Command>;
|
|
73
|
-
examples?: Array<{ command: string; description: string }>;
|
|
74
|
-
execute: (ctx: CommandContext<T>) => R | Promise<R>;
|
|
75
|
-
beforeExecute?: (ctx: CommandContext<T>) => void | Promise<void>;
|
|
76
|
-
afterExecute?: (ctx: CommandContext<T>) => void | Promise<void>;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* TUI command with a render function
|
|
81
|
-
*/
|
|
82
|
-
export interface TuiCommand<T extends OptionSchema = OptionSchema>
|
|
83
|
-
extends Omit<Command<T>, "execute"> {
|
|
84
|
-
render: (ctx: CommandContext<T>) => React.ReactNode;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Define a CLI command
|
|
89
|
-
*/
|
|
90
|
-
export function defineCommand<T extends OptionSchema = OptionSchema>(
|
|
91
|
-
config: Command<T>
|
|
92
|
-
): Command<T> {
|
|
93
|
-
return config;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Define a TUI command
|
|
98
|
-
*/
|
|
99
|
-
export function defineTuiCommand<T extends OptionSchema = OptionSchema>(
|
|
100
|
-
config: TuiCommand<T>
|
|
101
|
-
): TuiCommand<T> {
|
|
102
|
-
return config;
|
|
103
|
-
}
|
|
@@ -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
|
-
});
|