@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
package/guides/04-subcommands.md
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
# Guide 4: Subcommands (Basic)
|
|
2
|
-
|
|
3
|
-
Organize related commands under a parent command for cleaner CLI structure.
|
|
4
|
-
|
|
5
|
-
## What You'll Build
|
|
6
|
-
|
|
7
|
-
A database CLI with nested subcommands:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
dbctl db migrate --target latest
|
|
11
|
-
dbctl db seed --file data.json
|
|
12
|
-
dbctl db status
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Step 1: Create the Subcommands
|
|
16
|
-
|
|
17
|
-
Create `src/commands/db/migrate.ts`:
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
|
|
21
|
-
|
|
22
|
-
const options = {
|
|
23
|
-
target: {
|
|
24
|
-
type: "string",
|
|
25
|
-
description: "Migration target version",
|
|
26
|
-
default: "latest",
|
|
27
|
-
},
|
|
28
|
-
dry: {
|
|
29
|
-
type: "boolean",
|
|
30
|
-
description: "Dry run without applying",
|
|
31
|
-
default: false,
|
|
32
|
-
},
|
|
33
|
-
} satisfies OptionSchema;
|
|
34
|
-
|
|
35
|
-
export class MigrateCommand extends Command<typeof options> {
|
|
36
|
-
readonly name = "migrate";
|
|
37
|
-
readonly description = "Run database migrations";
|
|
38
|
-
readonly options = options;
|
|
39
|
-
|
|
40
|
-
execute(ctx: AppContext, config: { target: string; dry: boolean }): CommandResult {
|
|
41
|
-
ctx.logger.info(`Migrating to: ${config.target}`);
|
|
42
|
-
|
|
43
|
-
if (config.dry) {
|
|
44
|
-
console.log("DRY RUN: Would migrate to", config.target);
|
|
45
|
-
} else {
|
|
46
|
-
console.log("Migrating to", config.target, "...");
|
|
47
|
-
console.log("Migration complete!");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return { success: true };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Create `src/commands/db/seed.ts`:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
|
|
59
|
-
|
|
60
|
-
const options = {
|
|
61
|
-
file: {
|
|
62
|
-
type: "string",
|
|
63
|
-
description: "Seed data file",
|
|
64
|
-
required: true,
|
|
65
|
-
alias: "f",
|
|
66
|
-
},
|
|
67
|
-
} satisfies OptionSchema;
|
|
68
|
-
|
|
69
|
-
export class SeedCommand extends Command<typeof options> {
|
|
70
|
-
readonly name = "seed";
|
|
71
|
-
readonly description = "Seed the database with data";
|
|
72
|
-
readonly options = options;
|
|
73
|
-
|
|
74
|
-
execute(ctx: AppContext, config: { file: string }): CommandResult {
|
|
75
|
-
ctx.logger.info(`Seeding from: ${config.file}`);
|
|
76
|
-
console.log(`Loading seed data from ${config.file}...`);
|
|
77
|
-
console.log("Database seeded successfully!");
|
|
78
|
-
return { success: true };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Create `src/commands/db/status.ts`:
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
import { Command, type AppContext, type CommandResult } from "@pablozaiden/terminatui";
|
|
87
|
-
|
|
88
|
-
export class StatusCommand extends Command {
|
|
89
|
-
readonly name = "status";
|
|
90
|
-
readonly description = "Show database status";
|
|
91
|
-
readonly options = {};
|
|
92
|
-
|
|
93
|
-
execute(_ctx: AppContext): CommandResult {
|
|
94
|
-
console.log("Database Status:");
|
|
95
|
-
console.log(" Connected: Yes");
|
|
96
|
-
console.log(" Version: 1.2.3");
|
|
97
|
-
console.log(" Migrations: 5 applied");
|
|
98
|
-
return { success: true };
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Step 2: Create the Parent Command
|
|
104
|
-
|
|
105
|
-
Create `src/commands/db/index.ts`:
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
import { Command, type AppContext, type CommandResult } from "@pablozaiden/terminatui";
|
|
109
|
-
import { MigrateCommand } from "./migrate";
|
|
110
|
-
import { SeedCommand } from "./seed";
|
|
111
|
-
import { StatusCommand } from "./status";
|
|
112
|
-
|
|
113
|
-
export class DbCommand extends Command {
|
|
114
|
-
readonly name = "db";
|
|
115
|
-
readonly description = "Database operations";
|
|
116
|
-
readonly options = {};
|
|
117
|
-
|
|
118
|
-
// Subcommands are nested here
|
|
119
|
-
override readonly subCommands = [
|
|
120
|
-
new MigrateCommand(),
|
|
121
|
-
new SeedCommand(),
|
|
122
|
-
new StatusCommand(),
|
|
123
|
-
];
|
|
124
|
-
|
|
125
|
-
// Parent command can have its own execute (optional)
|
|
126
|
-
execute(_ctx: AppContext): CommandResult {
|
|
127
|
-
console.log("Use 'dbctl db <command>' for database operations.");
|
|
128
|
-
console.log("Available: migrate, seed, status");
|
|
129
|
-
return { success: true };
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Step 3: Create the Application
|
|
135
|
-
|
|
136
|
-
Create `src/index.ts`:
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
import { Application } from "@pablozaiden/terminatui";
|
|
140
|
-
import { DbCommand } from "./commands/db";
|
|
141
|
-
|
|
142
|
-
class DbCtlApp extends Application {
|
|
143
|
-
constructor() {
|
|
144
|
-
super({
|
|
145
|
-
name: "dbctl",
|
|
146
|
-
version: "1.0.0",
|
|
147
|
-
description: "Database control CLI",
|
|
148
|
-
commands: [new DbCommand()],
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
await new DbCtlApp().run();
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## Step 4: Test It
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# Show db command help
|
|
160
|
-
bun src/index.ts db help
|
|
161
|
-
# Shows: migrate, seed, status
|
|
162
|
-
|
|
163
|
-
# Run migration
|
|
164
|
-
bun src/index.ts db migrate
|
|
165
|
-
# Migrating to latest...
|
|
166
|
-
|
|
167
|
-
# Dry run migration
|
|
168
|
-
bun src/index.ts db migrate --target v2 --dry
|
|
169
|
-
# DRY RUN: Would migrate to v2
|
|
170
|
-
|
|
171
|
-
# Seed database
|
|
172
|
-
bun src/index.ts db seed -f data.json
|
|
173
|
-
# Loading seed data from data.json...
|
|
174
|
-
|
|
175
|
-
# Check status
|
|
176
|
-
bun src/index.ts db status
|
|
177
|
-
# Database Status: ...
|
|
178
|
-
|
|
179
|
-
# Get help for subcommand
|
|
180
|
-
bun src/index.ts db migrate help
|
|
181
|
-
# Shows migrate options
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## Project Structure
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
src/
|
|
188
|
-
├── index.ts
|
|
189
|
-
└── commands/
|
|
190
|
-
└── db/
|
|
191
|
-
├── index.ts # Parent command
|
|
192
|
-
├── migrate.ts # Subcommand
|
|
193
|
-
├── seed.ts # Subcommand
|
|
194
|
-
└── status.ts # Subcommand
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## What You Learned
|
|
198
|
-
|
|
199
|
-
- Group related commands under a parent
|
|
200
|
-
- Define subcommands with `subCommands` property
|
|
201
|
-
- Each subcommand has its own options
|
|
202
|
-
- Help is automatically nested (`db help`, `db migrate help`)
|
|
203
|
-
|
|
204
|
-
## Next Steps
|
|
205
|
-
|
|
206
|
-
→ [Guide 5: Interactive TUI](05-interactive-tui.md)
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# Guide 5: Interactive TUI (Normal)
|
|
2
|
-
|
|
3
|
-
Add an auto-generated Terminal User Interface to your CLI.
|
|
4
|
-
|
|
5
|
-
## What You'll Build
|
|
6
|
-
|
|
7
|
-
A task runner with both CLI and interactive TUI modes:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# CLI mode
|
|
11
|
-
taskr run --task build --env production
|
|
12
|
-
|
|
13
|
-
# TUI mode (interactive)
|
|
14
|
-
taskr
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
When you run without arguments, an interactive form appears!
|
|
18
|
-
|
|
19
|
-
## Step 1: Create the Command with TUI Metadata
|
|
20
|
-
|
|
21
|
-
Create `src/commands/run.ts`:
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
|
|
25
|
-
|
|
26
|
-
const options = {
|
|
27
|
-
task: {
|
|
28
|
-
type: "string",
|
|
29
|
-
description: "Task to run",
|
|
30
|
-
required: true,
|
|
31
|
-
enum: ["build", "test", "lint", "deploy"],
|
|
32
|
-
// TUI metadata
|
|
33
|
-
label: "Task",
|
|
34
|
-
order: 1,
|
|
35
|
-
group: "Required",
|
|
36
|
-
},
|
|
37
|
-
env: {
|
|
38
|
-
type: "string",
|
|
39
|
-
description: "Environment",
|
|
40
|
-
default: "development",
|
|
41
|
-
enum: ["development", "staging", "production"],
|
|
42
|
-
// TUI metadata
|
|
43
|
-
label: "Environment",
|
|
44
|
-
order: 2,
|
|
45
|
-
group: "Configuration",
|
|
46
|
-
},
|
|
47
|
-
verbose: {
|
|
48
|
-
type: "boolean",
|
|
49
|
-
description: "Verbose output",
|
|
50
|
-
default: false,
|
|
51
|
-
// TUI metadata
|
|
52
|
-
label: "Verbose Mode",
|
|
53
|
-
order: 10,
|
|
54
|
-
group: "Options",
|
|
55
|
-
},
|
|
56
|
-
} satisfies OptionSchema;
|
|
57
|
-
|
|
58
|
-
interface RunConfig {
|
|
59
|
-
task: string;
|
|
60
|
-
env: string;
|
|
61
|
-
verbose: boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export class RunCommand extends Command<typeof options, RunConfig> {
|
|
65
|
-
readonly name = "run";
|
|
66
|
-
readonly description = "Run a task";
|
|
67
|
-
readonly options = options;
|
|
68
|
-
|
|
69
|
-
// TUI customization
|
|
70
|
-
override readonly displayName = "Run Task";
|
|
71
|
-
override readonly actionLabel = "Start Task";
|
|
72
|
-
|
|
73
|
-
async execute(ctx: AppContext, config: RunConfig): Promise<CommandResult> {
|
|
74
|
-
ctx.logger.info(`Starting task: ${config.task}`);
|
|
75
|
-
|
|
76
|
-
if (config.verbose) {
|
|
77
|
-
ctx.logger.debug(`Environment: ${config.env}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Simulate task execution
|
|
81
|
-
console.log(`Running ${config.task} in ${config.env}...`);
|
|
82
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
83
|
-
console.log("Task completed!");
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
success: true,
|
|
87
|
-
data: { task: config.task, env: config.env },
|
|
88
|
-
message: `Task ${config.task} completed successfully`
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Step 2: Create the TUI Application
|
|
95
|
-
|
|
96
|
-
Create `src/index.ts`:
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
import { TuiApplication } from "@pablozaiden/terminatui";
|
|
100
|
-
import { RunCommand } from "./commands/run";
|
|
101
|
-
|
|
102
|
-
class TaskRunnerApp extends TuiApplication {
|
|
103
|
-
constructor() {
|
|
104
|
-
super({
|
|
105
|
-
name: "taskr",
|
|
106
|
-
displayName: "🚀 Task Runner", // Shown in TUI header
|
|
107
|
-
version: "1.0.0",
|
|
108
|
-
commands: [new RunCommand()],
|
|
109
|
-
enableTui: true, // Default: true
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
await new TaskRunnerApp().run();
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Step 3: Test Both Modes
|
|
118
|
-
|
|
119
|
-
**CLI Mode** (with arguments):
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
bun src/index.ts run --task build --env production
|
|
123
|
-
# Running build in production...
|
|
124
|
-
# Task completed!
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
**TUI Mode** (no arguments):
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
bun src/index.ts
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
This opens an interactive interface:
|
|
134
|
-
- Use ↑/↓ to navigate fields
|
|
135
|
-
- Press Enter to edit a field
|
|
136
|
-
- Press Enter on "Start Task" to run
|
|
137
|
-
- Press Esc to go back
|
|
138
|
-
- Press C to see the CLI command
|
|
139
|
-
|
|
140
|
-
## TUI Metadata Reference
|
|
141
|
-
|
|
142
|
-
Add these properties to your options for TUI customization:
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
{
|
|
146
|
-
type: "string",
|
|
147
|
-
description: "...",
|
|
148
|
-
|
|
149
|
-
// TUI-specific
|
|
150
|
-
label: "Display Label", // Custom field label
|
|
151
|
-
order: 1, // Field sort order
|
|
152
|
-
group: "Settings", // Group heading
|
|
153
|
-
placeholder: "Enter...", // Placeholder text
|
|
154
|
-
tuiHidden: false, // Hide from TUI (still in CLI)
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Command TUI Properties
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
class MyCommand extends Command {
|
|
162
|
-
// Display name in command selector
|
|
163
|
-
override readonly displayName = "My Command";
|
|
164
|
-
|
|
165
|
-
// Button text (default: "Run")
|
|
166
|
-
override readonly actionLabel = "Execute";
|
|
167
|
-
|
|
168
|
-
// Skip config screen, run immediately
|
|
169
|
-
override readonly immediateExecution = false;
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
## Keyboard Shortcuts
|
|
174
|
-
|
|
175
|
-
| Key | Action |
|
|
176
|
-
|-----|--------|
|
|
177
|
-
| ↑/↓ | Navigate fields |
|
|
178
|
-
| Enter | Edit field / Run |
|
|
179
|
-
| Tab | Cycle focus |
|
|
180
|
-
| C | Show CLI command |
|
|
181
|
-
| L | Toggle logs |
|
|
182
|
-
| Ctrl+Y | Copy to clipboard |
|
|
183
|
-
| Esc | Back / Cancel |
|
|
184
|
-
|
|
185
|
-
## What You Learned
|
|
186
|
-
|
|
187
|
-
- Use `TuiApplication` instead of `Application`
|
|
188
|
-
- Add TUI metadata to options (label, order, group)
|
|
189
|
-
- Customize with `displayName` and `actionLabel`
|
|
190
|
-
- Both CLI and TUI work with the same command
|
|
191
|
-
|
|
192
|
-
## Next Steps
|
|
193
|
-
|
|
194
|
-
→ [Guide 6: Config Validation](06-config-validation.md)
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
# Guide 6: Config Validation (Normal)
|
|
2
|
-
|
|
3
|
-
Transform and validate options before execution with `buildConfig`.
|
|
4
|
-
|
|
5
|
-
## What You'll Build
|
|
6
|
-
|
|
7
|
-
A deploy CLI that validates paths, resolves environment configs, and provides helpful error messages:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
deploy --app ./myapp --env production --replicas 3
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Step 1: Define Options and Config Types
|
|
14
|
-
|
|
15
|
-
Create `src/commands/deploy.ts`:
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
import { existsSync } from "node:fs";
|
|
20
|
-
import {
|
|
21
|
-
Command,
|
|
22
|
-
ConfigValidationError,
|
|
23
|
-
type AppContext,
|
|
24
|
-
type OptionSchema,
|
|
25
|
-
type OptionValues,
|
|
26
|
-
type CommandResult
|
|
27
|
-
} from "@pablozaiden/terminatui";
|
|
28
|
-
|
|
29
|
-
// Raw CLI options
|
|
30
|
-
const options = {
|
|
31
|
-
app: {
|
|
32
|
-
type: "string",
|
|
33
|
-
description: "Path to application",
|
|
34
|
-
required: true,
|
|
35
|
-
},
|
|
36
|
-
env: {
|
|
37
|
-
type: "string",
|
|
38
|
-
description: "Deployment environment",
|
|
39
|
-
required: true,
|
|
40
|
-
enum: ["development", "staging", "production"],
|
|
41
|
-
},
|
|
42
|
-
replicas: {
|
|
43
|
-
type: "string", // CLI args are strings
|
|
44
|
-
description: "Number of replicas",
|
|
45
|
-
default: "1",
|
|
46
|
-
},
|
|
47
|
-
"dry-run": {
|
|
48
|
-
type: "boolean",
|
|
49
|
-
description: "Preview without deploying",
|
|
50
|
-
default: false,
|
|
51
|
-
},
|
|
52
|
-
} satisfies OptionSchema;
|
|
53
|
-
|
|
54
|
-
// Validated config type
|
|
55
|
-
interface DeployConfig {
|
|
56
|
-
appPath: string; // Resolved absolute path
|
|
57
|
-
appName: string; // Extracted from path
|
|
58
|
-
environment: string;
|
|
59
|
-
replicas: number; // Parsed to number
|
|
60
|
-
dryRun: boolean;
|
|
61
|
-
envConfig: { // Environment-specific settings
|
|
62
|
-
url: string;
|
|
63
|
-
timeout: number;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Step 2: Implement buildConfig
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
// Environment-specific configurations
|
|
72
|
-
const ENV_CONFIGS = {
|
|
73
|
-
development: { url: "http://localhost:3000", timeout: 5000 },
|
|
74
|
-
staging: { url: "https://staging.example.com", timeout: 10000 },
|
|
75
|
-
production: { url: "https://example.com", timeout: 30000 },
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export class DeployCommand extends Command<typeof options, DeployConfig> {
|
|
79
|
-
readonly name = "deploy";
|
|
80
|
-
readonly description = "Deploy an application";
|
|
81
|
-
readonly options = options;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Transform and validate raw options into DeployConfig.
|
|
85
|
-
* Runs before execute() - errors here show helpful messages.
|
|
86
|
-
*/
|
|
87
|
-
override buildConfig(
|
|
88
|
-
_ctx: AppContext,
|
|
89
|
-
opts: OptionValues<typeof options>
|
|
90
|
-
): DeployConfig {
|
|
91
|
-
// 1. Validate app path exists
|
|
92
|
-
const appRaw = opts["app"] as string | undefined;
|
|
93
|
-
if (!appRaw) {
|
|
94
|
-
throw new ConfigValidationError(
|
|
95
|
-
"Missing required option: app",
|
|
96
|
-
"app" // Field to highlight in TUI
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const appPath = path.resolve(appRaw);
|
|
101
|
-
if (!existsSync(appPath)) {
|
|
102
|
-
throw new ConfigValidationError(
|
|
103
|
-
`Application path does not exist: ${appPath}`,
|
|
104
|
-
"app"
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 2. Extract app name from path
|
|
109
|
-
const appName = path.basename(appPath);
|
|
110
|
-
|
|
111
|
-
// 3. Validate environment
|
|
112
|
-
const environment = opts["env"] as string;
|
|
113
|
-
if (!environment) {
|
|
114
|
-
throw new ConfigValidationError(
|
|
115
|
-
"Missing required option: env",
|
|
116
|
-
"env"
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 4. Parse and validate replicas
|
|
121
|
-
const replicasStr = opts["replicas"] as string ?? "1";
|
|
122
|
-
const replicas = parseInt(replicasStr, 10);
|
|
123
|
-
|
|
124
|
-
if (isNaN(replicas)) {
|
|
125
|
-
throw new ConfigValidationError(
|
|
126
|
-
`Replicas must be a number, got: ${replicasStr}`,
|
|
127
|
-
"replicas"
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (replicas < 1 || replicas > 10) {
|
|
132
|
-
throw new ConfigValidationError(
|
|
133
|
-
"Replicas must be between 1 and 10",
|
|
134
|
-
"replicas"
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// 5. Get environment-specific config
|
|
139
|
-
const envConfig = ENV_CONFIGS[environment as keyof typeof ENV_CONFIGS];
|
|
140
|
-
|
|
141
|
-
// 6. Return validated config
|
|
142
|
-
return {
|
|
143
|
-
appPath,
|
|
144
|
-
appName,
|
|
145
|
-
environment,
|
|
146
|
-
replicas,
|
|
147
|
-
dryRun: opts["dry-run"] as boolean ?? false,
|
|
148
|
-
envConfig,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Step 3: Implement execute with clean config
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
/**
|
|
157
|
-
* Execute with fully validated DeployConfig.
|
|
158
|
-
* No need to validate here - buildConfig already did it!
|
|
159
|
-
*/
|
|
160
|
-
async execute(ctx: AppContext, config: DeployConfig): Promise<CommandResult> {
|
|
161
|
-
ctx.logger.info(`Deploying ${config.appName} to ${config.environment}`);
|
|
162
|
-
ctx.logger.debug(`Path: ${config.appPath}`);
|
|
163
|
-
ctx.logger.debug(`Replicas: ${config.replicas}`);
|
|
164
|
-
ctx.logger.debug(`URL: ${config.envConfig.url}`);
|
|
165
|
-
|
|
166
|
-
if (config.dryRun) {
|
|
167
|
-
console.log("DRY RUN - Would deploy:");
|
|
168
|
-
console.log(` App: ${config.appName}`);
|
|
169
|
-
console.log(` Environment: ${config.environment}`);
|
|
170
|
-
console.log(` Replicas: ${config.replicas}`);
|
|
171
|
-
console.log(` Target: ${config.envConfig.url}`);
|
|
172
|
-
return { success: true, message: "Dry run completed" };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
console.log(`Deploying ${config.appName}...`);
|
|
176
|
-
console.log(` Creating ${config.replicas} replicas...`);
|
|
177
|
-
console.log(` Targeting ${config.envConfig.url}...`);
|
|
178
|
-
|
|
179
|
-
// Simulate deployment
|
|
180
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
181
|
-
|
|
182
|
-
console.log("Deployment successful!");
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
success: true,
|
|
186
|
-
data: {
|
|
187
|
-
app: config.appName,
|
|
188
|
-
environment: config.environment,
|
|
189
|
-
replicas: config.replicas,
|
|
190
|
-
url: config.envConfig.url,
|
|
191
|
-
},
|
|
192
|
-
message: `Deployed ${config.appName} to ${config.environment}`
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Step 4: Create the Application
|
|
199
|
-
|
|
200
|
-
Create `src/index.ts`:
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
import { TuiApplication } from "@pablozaiden/terminatui";
|
|
204
|
-
import { DeployCommand } from "./commands/deploy";
|
|
205
|
-
|
|
206
|
-
class DeployCLI extends TuiApplication {
|
|
207
|
-
constructor() {
|
|
208
|
-
super({
|
|
209
|
-
name: "deploy",
|
|
210
|
-
version: "1.0.0",
|
|
211
|
-
commands: [new DeployCommand()],
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
await new DeployCLI().run();
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## Step 5: Test Validation
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
# Missing required option
|
|
223
|
-
bun src/index.ts deploy --env production
|
|
224
|
-
# Error: Missing required option: app
|
|
225
|
-
|
|
226
|
-
# Invalid path
|
|
227
|
-
bun src/index.ts deploy --app ./nonexistent --env staging
|
|
228
|
-
# Error: Application path does not exist: /path/to/nonexistent
|
|
229
|
-
|
|
230
|
-
# Invalid replicas
|
|
231
|
-
bun src/index.ts deploy --app . --env production --replicas abc
|
|
232
|
-
# Error: Replicas must be a number, got: abc
|
|
233
|
-
|
|
234
|
-
# Out of range replicas
|
|
235
|
-
bun src/index.ts deploy --app . --env production --replicas 100
|
|
236
|
-
# Error: Replicas must be between 1 and 10
|
|
237
|
-
|
|
238
|
-
# Dry run (valid)
|
|
239
|
-
bun src/index.ts deploy --app . --env production --replicas 3 --dry-run
|
|
240
|
-
# DRY RUN - Would deploy: ...
|
|
241
|
-
|
|
242
|
-
# Full deploy
|
|
243
|
-
bun src/index.ts deploy --app . --env staging --replicas 2
|
|
244
|
-
# Deploying myapp...
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## Benefits of buildConfig
|
|
248
|
-
|
|
249
|
-
1. **Separation of concerns** - Validation separate from logic
|
|
250
|
-
2. **Type safety** - `execute()` receives validated `DeployConfig`
|
|
251
|
-
3. **Better errors** - `ConfigValidationError` highlights fields in TUI
|
|
252
|
-
4. **Reusable** - Works for both CLI and TUI modes
|
|
253
|
-
5. **Testable** - Easy to unit test validation logic
|
|
254
|
-
|
|
255
|
-
## What You Learned
|
|
256
|
-
|
|
257
|
-
- Use `buildConfig` to transform and validate options
|
|
258
|
-
- Throw `ConfigValidationError` with field name for TUI highlighting
|
|
259
|
-
- Parse strings to numbers and resolve paths
|
|
260
|
-
- Keep `execute()` clean with pre-validated config
|
|
261
|
-
|
|
262
|
-
## Next Steps
|
|
263
|
-
|
|
264
|
-
→ [Guide 7: Async Commands with Cancellation](07-async-cancellation.md)
|