@pablozaiden/terminatui 0.1.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/.devcontainer/devcontainer.json +19 -0
- package/.devcontainer/install-prerequisites.sh +49 -0
- package/.github/workflows/copilot-setup-steps.yml +32 -0
- package/.github/workflows/pull-request.yml +27 -0
- package/.github/workflows/release-npm-package.yml +78 -0
- package/LICENSE +21 -0
- package/README.md +524 -0
- package/examples/tui-app/commands/greet.ts +75 -0
- package/examples/tui-app/commands/index.ts +3 -0
- package/examples/tui-app/commands/math.ts +114 -0
- package/examples/tui-app/commands/status.ts +75 -0
- package/examples/tui-app/index.ts +34 -0
- package/guides/01-hello-world.md +96 -0
- package/guides/02-adding-options.md +103 -0
- package/guides/03-multiple-commands.md +163 -0
- package/guides/04-subcommands.md +206 -0
- package/guides/05-interactive-tui.md +194 -0
- package/guides/06-config-validation.md +264 -0
- package/guides/07-async-cancellation.md +388 -0
- package/guides/08-complete-application.md +673 -0
- package/guides/README.md +74 -0
- package/package.json +32 -0
- package/src/__tests__/application.test.ts +425 -0
- package/src/__tests__/buildCliCommand.test.ts +125 -0
- package/src/__tests__/builtins.test.ts +133 -0
- package/src/__tests__/colors.test.ts +127 -0
- package/src/__tests__/command.test.ts +157 -0
- package/src/__tests__/commandClass.test.ts +130 -0
- package/src/__tests__/context.test.ts +97 -0
- package/src/__tests__/help.test.ts +412 -0
- package/src/__tests__/parser.test.ts +268 -0
- package/src/__tests__/registry.test.ts +195 -0
- package/src/__tests__/registryNew.test.ts +160 -0
- package/src/__tests__/schemaToFields.test.ts +176 -0
- package/src/__tests__/table.test.ts +146 -0
- package/src/__tests__/tui.test.ts +26 -0
- package/src/builtins/help.ts +85 -0
- package/src/builtins/index.ts +4 -0
- package/src/builtins/settings.ts +106 -0
- package/src/builtins/version.ts +72 -0
- package/src/cli/help.ts +174 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/output/colors.ts +74 -0
- package/src/cli/output/index.ts +2 -0
- package/src/cli/output/table.ts +141 -0
- package/src/cli/parser.ts +241 -0
- package/src/commands/help.ts +50 -0
- package/src/commands/index.ts +1 -0
- package/src/components/index.ts +147 -0
- package/src/core/application.ts +461 -0
- package/src/core/command.ts +269 -0
- package/src/core/context.ts +112 -0
- package/src/core/help.ts +214 -0
- package/src/core/index.ts +15 -0
- package/src/core/logger.ts +164 -0
- package/src/core/registry.ts +140 -0
- package/src/hooks/index.ts +131 -0
- package/src/index.ts +137 -0
- package/src/registry/commandRegistry.ts +77 -0
- package/src/registry/index.ts +1 -0
- package/src/tui/TuiApp.tsx +582 -0
- package/src/tui/TuiApplication.tsx +230 -0
- package/src/tui/app.ts +29 -0
- package/src/tui/components/ActionButton.tsx +36 -0
- package/src/tui/components/CliModal.tsx +81 -0
- package/src/tui/components/CommandSelector.tsx +159 -0
- package/src/tui/components/ConfigForm.tsx +148 -0
- package/src/tui/components/EditorModal.tsx +177 -0
- package/src/tui/components/FieldRow.tsx +30 -0
- package/src/tui/components/Header.tsx +31 -0
- package/src/tui/components/JsonHighlight.tsx +128 -0
- package/src/tui/components/LogsPanel.tsx +86 -0
- package/src/tui/components/ResultsPanel.tsx +93 -0
- package/src/tui/components/StatusBar.tsx +59 -0
- package/src/tui/components/index.ts +13 -0
- package/src/tui/components/types.ts +30 -0
- package/src/tui/context/KeyboardContext.tsx +118 -0
- package/src/tui/context/index.ts +7 -0
- package/src/tui/hooks/index.ts +35 -0
- package/src/tui/hooks/useClipboard.ts +66 -0
- package/src/tui/hooks/useCommandExecutor.ts +131 -0
- package/src/tui/hooks/useConfigState.ts +171 -0
- package/src/tui/hooks/useKeyboardHandler.ts +91 -0
- package/src/tui/hooks/useLogStream.ts +96 -0
- package/src/tui/hooks/useSpinner.ts +46 -0
- package/src/tui/index.ts +65 -0
- package/src/tui/theme.ts +21 -0
- package/src/tui/utils/buildCliCommand.ts +90 -0
- package/src/tui/utils/index.ts +13 -0
- package/src/tui/utils/parameterPersistence.ts +96 -0
- package/src/tui/utils/schemaToFields.ts +144 -0
- package/src/types/command.ts +103 -0
- package/src/types/execution.ts +11 -0
- package/src/types/index.ts +1 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { AppContext, type AppConfig } from "./context.ts";
|
|
2
|
+
import { type AnyCommand, ConfigValidationError, type CommandResult } from "./command.ts";
|
|
3
|
+
import { CommandRegistry } from "./registry.ts";
|
|
4
|
+
import { ExecutionMode } from "../types/execution.ts";
|
|
5
|
+
import { LogLevel, type LoggerConfig } from "./logger.ts";
|
|
6
|
+
import { generateAppHelp, generateCommandHelp } from "./help.ts";
|
|
7
|
+
import {
|
|
8
|
+
createVersionCommand,
|
|
9
|
+
createHelpCommandForParent,
|
|
10
|
+
createRootHelpCommand,
|
|
11
|
+
} from "../builtins/index.ts";
|
|
12
|
+
import {
|
|
13
|
+
extractCommandChain,
|
|
14
|
+
schemaToParseArgsOptions,
|
|
15
|
+
parseOptionValues,
|
|
16
|
+
validateOptions,
|
|
17
|
+
} from "../cli/parser.ts";
|
|
18
|
+
import { parseArgs, type ParseArgsConfig } from "util";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Global options available on all commands.
|
|
22
|
+
* These are handled by the framework before dispatching to commands.
|
|
23
|
+
*/
|
|
24
|
+
export interface GlobalOptions {
|
|
25
|
+
"log-level"?: string;
|
|
26
|
+
"detailed-logs"?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Application configuration options.
|
|
31
|
+
*/
|
|
32
|
+
export interface ApplicationConfig {
|
|
33
|
+
/** Application name (used in CLI, help, version) */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Display name for TUI (human-readable, e.g., "My App") */
|
|
36
|
+
displayName?: string;
|
|
37
|
+
/** Application version */
|
|
38
|
+
version: string;
|
|
39
|
+
/** Optional commit hash for version display (shows "(dev)" if not set) */
|
|
40
|
+
commitHash?: string;
|
|
41
|
+
/** Commands to register */
|
|
42
|
+
commands: AnyCommand[];
|
|
43
|
+
/** Default command when no args provided (by name) */
|
|
44
|
+
defaultCommand?: string;
|
|
45
|
+
/** Logger configuration */
|
|
46
|
+
logger?: LoggerConfig;
|
|
47
|
+
/** Additional config values */
|
|
48
|
+
config?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Application lifecycle hooks.
|
|
53
|
+
*/
|
|
54
|
+
export interface ApplicationHooks {
|
|
55
|
+
/** Called before running any command */
|
|
56
|
+
onBeforeRun?: (ctx: AppContext, commandName: string) => Promise<void> | void;
|
|
57
|
+
/** Called after command completes (success or failure) */
|
|
58
|
+
onAfterRun?: (ctx: AppContext, commandName: string, error?: Error) => Promise<void> | void;
|
|
59
|
+
/** Called when an error occurs */
|
|
60
|
+
onError?: (ctx: AppContext, error: Error) => Promise<void> | void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Main application class.
|
|
65
|
+
*
|
|
66
|
+
* The Application is the entry point for a Terminatui-based CLI/TUI app.
|
|
67
|
+
* It manages the command registry, context, lifecycle, and execution flow.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const app = new Application({
|
|
72
|
+
* name: "myapp",
|
|
73
|
+
* version: "1.0.0",
|
|
74
|
+
* commands: [new RunCommand(), new CheckCommand()],
|
|
75
|
+
* defaultCommand: "interactive",
|
|
76
|
+
* });
|
|
77
|
+
*
|
|
78
|
+
* await app.run(process.argv.slice(2));
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export class Application {
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly displayName: string;
|
|
84
|
+
readonly version: string;
|
|
85
|
+
readonly commitHash?: string;
|
|
86
|
+
readonly registry: CommandRegistry;
|
|
87
|
+
readonly context: AppContext;
|
|
88
|
+
|
|
89
|
+
private readonly defaultCommandName?: string;
|
|
90
|
+
private hooks: ApplicationHooks = {};
|
|
91
|
+
|
|
92
|
+
constructor(config: ApplicationConfig) {
|
|
93
|
+
this.name = config.name;
|
|
94
|
+
this.displayName = config.displayName ?? config.name;
|
|
95
|
+
this.version = config.version;
|
|
96
|
+
this.commitHash = config.commitHash;
|
|
97
|
+
this.defaultCommandName = config.defaultCommand;
|
|
98
|
+
|
|
99
|
+
// Create context
|
|
100
|
+
const appConfig: AppConfig = {
|
|
101
|
+
name: config.name,
|
|
102
|
+
version: config.version,
|
|
103
|
+
...config.config,
|
|
104
|
+
};
|
|
105
|
+
this.context = new AppContext(appConfig, config.logger);
|
|
106
|
+
AppContext.setCurrent(this.context);
|
|
107
|
+
|
|
108
|
+
// Create registry and register commands
|
|
109
|
+
this.registry = new CommandRegistry();
|
|
110
|
+
this.registerCommands(config.commands);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Register commands and inject help subcommands.
|
|
115
|
+
*/
|
|
116
|
+
private registerCommands(commands: AnyCommand[]): void {
|
|
117
|
+
// Register version command at top level
|
|
118
|
+
this.registry.register(createVersionCommand(this.name, this.version, this.commitHash));
|
|
119
|
+
|
|
120
|
+
// Register user commands with help injected
|
|
121
|
+
for (const command of commands) {
|
|
122
|
+
this.injectHelpCommand(command);
|
|
123
|
+
this.registry.register(command);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Register root help command
|
|
127
|
+
this.registry.register(createRootHelpCommand(commands, this.name, this.version));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Recursively inject help subcommand into a command and its subcommands.
|
|
132
|
+
*/
|
|
133
|
+
private injectHelpCommand(command: AnyCommand): void {
|
|
134
|
+
// Create help subcommand for this command
|
|
135
|
+
const helpCmd = createHelpCommandForParent(command, this.name, this.version);
|
|
136
|
+
|
|
137
|
+
// Initialize subCommands array if needed
|
|
138
|
+
if (!command.subCommands) {
|
|
139
|
+
command.subCommands = [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Add help as subcommand
|
|
143
|
+
command.subCommands.push(helpCmd);
|
|
144
|
+
|
|
145
|
+
// Recursively inject into subcommands
|
|
146
|
+
for (const subCommand of command.subCommands) {
|
|
147
|
+
if (subCommand.name !== "help") {
|
|
148
|
+
this.injectHelpCommand(subCommand);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set lifecycle hooks.
|
|
155
|
+
*/
|
|
156
|
+
setHooks(hooks: ApplicationHooks): void {
|
|
157
|
+
this.hooks = { ...this.hooks, ...hooks };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Run the application with the given arguments.
|
|
162
|
+
*
|
|
163
|
+
* @param argv Command-line arguments (typically process.argv.slice(2))
|
|
164
|
+
*/
|
|
165
|
+
async run(argv: string[]): Promise<void> {
|
|
166
|
+
try {
|
|
167
|
+
// Parse global options first
|
|
168
|
+
const { globalOptions, remainingArgs } = this.parseGlobalOptions(argv);
|
|
169
|
+
this.applyGlobalOptions(globalOptions);
|
|
170
|
+
|
|
171
|
+
// Extract command path from args
|
|
172
|
+
const { commands: commandPath, remaining: flagArgs } = extractCommandChain(remainingArgs);
|
|
173
|
+
|
|
174
|
+
// Resolve command
|
|
175
|
+
const { command, remainingPath } = this.resolveCommand(commandPath);
|
|
176
|
+
|
|
177
|
+
if (!command) {
|
|
178
|
+
// No command found - show help or run default
|
|
179
|
+
if (this.defaultCommandName && commandPath.length === 0) {
|
|
180
|
+
const defaultCmd = this.registry.get(this.defaultCommandName);
|
|
181
|
+
if (defaultCmd) {
|
|
182
|
+
await this.executeCommand(defaultCmd, flagArgs, []);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Show help
|
|
188
|
+
console.log(generateAppHelp(this.registry.list(), {
|
|
189
|
+
appName: this.name,
|
|
190
|
+
version: this.version,
|
|
191
|
+
}));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check for unknown command in path
|
|
196
|
+
if (remainingPath.length > 0 && remainingPath[0] !== "help") {
|
|
197
|
+
console.error(`Unknown command: ${remainingPath.join(" ")}`);
|
|
198
|
+
process.exitCode = 1;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Execute the command
|
|
203
|
+
await this.executeCommand(command, flagArgs, commandPath);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
await this.handleError(error as Error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Resolve a command from the path.
|
|
211
|
+
*/
|
|
212
|
+
private resolveCommand(commandPath: string[]): {
|
|
213
|
+
command: AnyCommand | undefined;
|
|
214
|
+
remainingPath: string[];
|
|
215
|
+
} {
|
|
216
|
+
if (commandPath.length === 0) {
|
|
217
|
+
return { command: undefined, remainingPath: [] };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const result = this.registry.resolve(commandPath);
|
|
221
|
+
return {
|
|
222
|
+
command: result.command,
|
|
223
|
+
remainingPath: result.remainingPath,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Execute a command with full lifecycle.
|
|
229
|
+
*/
|
|
230
|
+
private async executeCommand(
|
|
231
|
+
command: AnyCommand,
|
|
232
|
+
flagArgs: string[],
|
|
233
|
+
commandPath: string[]
|
|
234
|
+
): Promise<void> {
|
|
235
|
+
// Determine execution mode
|
|
236
|
+
const mode = this.detectExecutionMode(command, flagArgs);
|
|
237
|
+
|
|
238
|
+
// Parse options
|
|
239
|
+
const schema = command.options ?? {};
|
|
240
|
+
const parseArgsConfig = schemaToParseArgsOptions(schema);
|
|
241
|
+
|
|
242
|
+
let parsedValues: Record<string, unknown> = {};
|
|
243
|
+
let parseError: string | undefined;
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const parseArgsOptions = {
|
|
247
|
+
args: flagArgs,
|
|
248
|
+
options: parseArgsConfig.options as ParseArgsConfig["options"],
|
|
249
|
+
allowPositionals: false,
|
|
250
|
+
strict: true, // Enable strict mode to catch unknown options
|
|
251
|
+
};
|
|
252
|
+
const result = parseArgs(parseArgsOptions);
|
|
253
|
+
parsedValues = result.values;
|
|
254
|
+
} catch (err) {
|
|
255
|
+
// Capture parse error (e.g., unknown option)
|
|
256
|
+
parseError = (err as Error).message;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// If there was a parse error, show it and help
|
|
260
|
+
if (parseError) {
|
|
261
|
+
console.error(`Error: ${parseError}\n`);
|
|
262
|
+
console.log(generateCommandHelp(command, {
|
|
263
|
+
appName: this.name,
|
|
264
|
+
commandPath: commandPath.length > 0 ? commandPath : [command.name],
|
|
265
|
+
}));
|
|
266
|
+
process.exitCode = 1;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let options;
|
|
271
|
+
try {
|
|
272
|
+
options = parseOptionValues(schema, parsedValues);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
// Enum validation error from parseOptionValues
|
|
275
|
+
console.error(`Error: ${(err as Error).message}\n`);
|
|
276
|
+
console.log(generateCommandHelp(command, {
|
|
277
|
+
appName: this.name,
|
|
278
|
+
commandPath: commandPath.length > 0 ? commandPath : [command.name],
|
|
279
|
+
}));
|
|
280
|
+
process.exitCode = 1;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Validate options (required, min/max, etc.)
|
|
285
|
+
const errors = validateOptions(schema, options);
|
|
286
|
+
if (errors.length > 0) {
|
|
287
|
+
for (const error of errors) {
|
|
288
|
+
console.error(`Error: ${error.message}`);
|
|
289
|
+
}
|
|
290
|
+
console.log(); // Blank line
|
|
291
|
+
console.log(generateCommandHelp(command, {
|
|
292
|
+
appName: this.name,
|
|
293
|
+
commandPath: commandPath.length > 0 ? commandPath : [command.name],
|
|
294
|
+
}));
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Call onBeforeRun hook
|
|
300
|
+
if (this.hooks.onBeforeRun) {
|
|
301
|
+
await this.hooks.onBeforeRun(this.context, command.name);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let error: Error | undefined;
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// Call beforeExecute hook on command
|
|
308
|
+
if (command.beforeExecute) {
|
|
309
|
+
await command.beforeExecute(this.context, options);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Build config if command implements buildConfig, otherwise pass options as-is
|
|
313
|
+
let config: unknown;
|
|
314
|
+
if (command.buildConfig) {
|
|
315
|
+
config = await command.buildConfig(this.context, options);
|
|
316
|
+
} else {
|
|
317
|
+
config = options;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Execute the command with the config
|
|
321
|
+
const result = await command.execute(this.context, config);
|
|
322
|
+
|
|
323
|
+
// In CLI mode, handle result output
|
|
324
|
+
if (mode === ExecutionMode.Cli && result) {
|
|
325
|
+
const commandResult = result as CommandResult;
|
|
326
|
+
if (commandResult.success) {
|
|
327
|
+
// Output data as JSON to stdout if present
|
|
328
|
+
if (commandResult.data !== undefined) {
|
|
329
|
+
console.log(JSON.stringify(commandResult.data, null, 2));
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
// Set exit code for failures
|
|
333
|
+
process.exitCode = 1;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {
|
|
337
|
+
error = e as Error;
|
|
338
|
+
} finally {
|
|
339
|
+
// Always call afterExecute hook
|
|
340
|
+
if (command.afterExecute) {
|
|
341
|
+
try {
|
|
342
|
+
await command.afterExecute(this.context, options, error);
|
|
343
|
+
} catch (afterError) {
|
|
344
|
+
// afterExecute error takes precedence if no prior error
|
|
345
|
+
if (!error) {
|
|
346
|
+
error = afterError as Error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Call onAfterRun hook
|
|
353
|
+
if (this.hooks.onAfterRun) {
|
|
354
|
+
await this.hooks.onAfterRun(this.context, command.name, error);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Re-throw if there was an error
|
|
358
|
+
if (error) {
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Detect the execution mode based on command and args.
|
|
365
|
+
*/
|
|
366
|
+
private detectExecutionMode(command: AnyCommand, args: string[]): ExecutionMode {
|
|
367
|
+
// If no args and command supports TUI, use TUI mode
|
|
368
|
+
if (args.length === 0 && command.supportsTui()) {
|
|
369
|
+
return ExecutionMode.Tui;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Otherwise use CLI mode
|
|
373
|
+
return ExecutionMode.Cli;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Parse global options from argv.
|
|
378
|
+
* Returns the parsed global options and remaining args.
|
|
379
|
+
*/
|
|
380
|
+
private parseGlobalOptions(argv: string[]): {
|
|
381
|
+
globalOptions: GlobalOptions;
|
|
382
|
+
remainingArgs: string[];
|
|
383
|
+
} {
|
|
384
|
+
const globalOptions: GlobalOptions = {};
|
|
385
|
+
const remainingArgs: string[] = [];
|
|
386
|
+
|
|
387
|
+
let i = 0;
|
|
388
|
+
while (i < argv.length) {
|
|
389
|
+
const arg = argv[i]!;
|
|
390
|
+
|
|
391
|
+
if (arg === "--log-level" && i + 1 < argv.length) {
|
|
392
|
+
globalOptions["log-level"] = argv[i + 1];
|
|
393
|
+
i += 2;
|
|
394
|
+
} else if (arg.startsWith("--log-level=")) {
|
|
395
|
+
globalOptions["log-level"] = arg.slice("--log-level=".length);
|
|
396
|
+
i += 1;
|
|
397
|
+
} else if (arg === "--detailed-logs") {
|
|
398
|
+
globalOptions["detailed-logs"] = true;
|
|
399
|
+
i += 1;
|
|
400
|
+
} else if (arg === "--no-detailed-logs") {
|
|
401
|
+
globalOptions["detailed-logs"] = false;
|
|
402
|
+
i += 1;
|
|
403
|
+
} else {
|
|
404
|
+
remainingArgs.push(arg);
|
|
405
|
+
i += 1;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return { globalOptions, remainingArgs };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Apply global options to the application context.
|
|
414
|
+
*/
|
|
415
|
+
private applyGlobalOptions(options: GlobalOptions): void {
|
|
416
|
+
const logger = this.context.logger;
|
|
417
|
+
|
|
418
|
+
// Apply detailed-logs
|
|
419
|
+
if (options["detailed-logs"] !== undefined) {
|
|
420
|
+
logger.setDetailed(options["detailed-logs"]);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Apply log-level (case-insensitive)
|
|
424
|
+
if (options["log-level"] !== undefined) {
|
|
425
|
+
const levelStr = options["log-level"].toLowerCase();
|
|
426
|
+
// Find the matching log level (case-insensitive)
|
|
427
|
+
const level = Object.entries(LogLevel).find(
|
|
428
|
+
([key, val]) => typeof val === "number" && key.toLowerCase() === levelStr
|
|
429
|
+
)?.[1] as LogLevel | undefined;
|
|
430
|
+
if (level !== undefined) {
|
|
431
|
+
logger.setMinLevel(level);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Handle an error during execution.
|
|
438
|
+
*/
|
|
439
|
+
private async handleError(error: Error): Promise<void> {
|
|
440
|
+
if (this.hooks.onError) {
|
|
441
|
+
await this.hooks.onError(this.context, error);
|
|
442
|
+
} else {
|
|
443
|
+
// Default error handling
|
|
444
|
+
if (error instanceof ConfigValidationError) {
|
|
445
|
+
// Format validation errors more clearly
|
|
446
|
+
const fieldInfo = error.field ? ` (${error.field})` : "";
|
|
447
|
+
console.error(`Configuration error${fieldInfo}: ${error.message}`);
|
|
448
|
+
} else {
|
|
449
|
+
console.error(`Error: ${error.message}`);
|
|
450
|
+
}
|
|
451
|
+
process.exitCode = 1;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Get the application context.
|
|
457
|
+
*/
|
|
458
|
+
getContext(): AppContext {
|
|
459
|
+
return this.context;
|
|
460
|
+
}
|
|
461
|
+
}
|