@pablozaiden/terminatui 0.6.3 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablozaiden/terminatui",
3
- "version": "0.6.3",
3
+ "version": "0.7.1",
4
4
  "description": "Terminal UI and Command Line Application Framework",
5
5
  "repository": {
6
6
  "url": "https://github.com/PabloZaiden/terminatui",
@@ -21,8 +21,8 @@
21
21
  "test": "bun test"
22
22
  },
23
23
  "dependencies": {
24
- "@opentui/core": "0.1.75",
25
- "@opentui/react": "0.1.75",
24
+ "@opentui/core": "0.1.87",
25
+ "@opentui/react": "0.1.87",
26
26
  "ink": "6.6.0",
27
27
  "ink-select-input": "6.2.0",
28
28
  "ink-text-input": "6.0.0",
@@ -58,8 +58,11 @@ export interface ApplicationConfig {
58
58
  version: string;
59
59
  /** Optional commit hash for version display (shows "(dev)" if not set) */
60
60
  commitHash?: string;
61
- /** Commands to register */
62
- commands: AnyCommand[];
61
+ /**
62
+ * Commands to register.
63
+ * Can be omitted and registered later via `registerCommands()`.
64
+ */
65
+ commands?: AnyCommand[];
63
66
  /** Default command when no args provided (by name) */
64
67
  defaultCommand?: string;
65
68
  /** Logger configuration */
@@ -88,6 +91,7 @@ export interface ApplicationHooks {
88
91
  *
89
92
  * @example
90
93
  * ```typescript
94
+ * // Option 1: Pass commands in constructor (original API, still works)
91
95
  * const app = new Application({
92
96
  * name: "myapp",
93
97
  * version: "1.0.0",
@@ -97,6 +101,25 @@ export interface ApplicationHooks {
97
101
  *
98
102
  * await app.run();
99
103
  * ```
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * // Option 2: Register commands dynamically after construction
108
+ * const app = new Application({
109
+ * name: "myapp",
110
+ * version: "1.0.0",
111
+ * });
112
+ *
113
+ * // Access context before registering commands
114
+ * app.context.setService("db", await connectToDb());
115
+ *
116
+ * // Register commands that may depend on context
117
+ * app.registerCommands([
118
+ * new DynamicCommand(app.context),
119
+ * ]);
120
+ *
121
+ * await app.run();
122
+ * ```
100
123
  */
101
124
  export class Application {
102
125
  readonly name: string;
@@ -127,6 +150,7 @@ export class Application {
127
150
 
128
151
  private readonly defaultCommandName?: string;
129
152
  private hooks: ApplicationHooks = {};
153
+ private commandsRegistered = false;
130
154
 
131
155
  constructor(config: ApplicationConfig) {
132
156
  this.name = config.name;
@@ -146,15 +170,57 @@ export class Application {
146
170
 
147
171
  context.logger.silly(`Application initialized: ${this.name} v${this.version}`);
148
172
 
149
- // Create registry and register commands
173
+ // Create registry
150
174
  this.registry = new CommandRegistry();
151
- this.registerCommands(config.commands);
175
+
176
+ // If commands were provided in config (even empty array), register them now
177
+ // This maintains backward compatibility - passing commands: [] still registers built-ins
178
+ if (config.commands !== undefined) {
179
+ this.registerCommands(config.commands);
180
+ }
152
181
  }
153
182
 
154
183
  /**
155
- * Register commands and inject help subcommands.
184
+ * Register commands with the application.
185
+ *
186
+ * This method can be called after construction to register commands dynamically.
187
+ * This is useful when commands depend on context or other runtime information.
188
+ *
189
+ * @param commands Array of commands to register
190
+ * @throws If commands have already been registered
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const app = new Application({
195
+ * name: "myapp",
196
+ * version: "1.0.0",
197
+ * });
198
+ *
199
+ * // Access context to set up services
200
+ * app.context.setService("db", await connectToDb());
201
+ *
202
+ * // Register commands that depend on context
203
+ * app.registerCommands([
204
+ * new DbQueryCommand(),
205
+ * new DbMigrateCommand(),
206
+ * ]);
207
+ *
208
+ * await app.run();
209
+ * ```
210
+ */
211
+ registerCommands(commands: AnyCommand[]): void {
212
+ if (this.commandsRegistered) {
213
+ throw new Error("Commands have already been registered. registerCommands() can only be called once.");
214
+ }
215
+
216
+ this.commandsRegistered = true;
217
+ this.doRegisterCommands(commands);
218
+ }
219
+
220
+ /**
221
+ * Internal method to register commands and inject help subcommands.
156
222
  */
157
- private registerCommands(commands: AnyCommand[]): void {
223
+ private doRegisterCommands(commands: AnyCommand[]): void {
158
224
  this.assertNoReservedCommands(commands);
159
225
 
160
226
  // Register version command at top level
@@ -244,6 +310,13 @@ export class Application {
244
310
  * Useful for tests or manual programmatic invocation.
245
311
  */
246
312
  async runFromArgs(argv: string[]): Promise<void> {
313
+ // Ensure commands have been registered before running
314
+ if (!this.commandsRegistered) {
315
+ throw new Error(
316
+ "No commands registered. Call registerCommands() before run(), or provide commands in the constructor config."
317
+ );
318
+ }
319
+
247
320
  // configure logger
248
321
  AppContext.current.logger.onLogEvent((event) => {
249
322
  process.stderr.write(event.message + "\n");
@@ -0,0 +1,66 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+
5
+ export async function buildBinary(
6
+ rootDir: string,
7
+ entrypoint: string,
8
+ binaryName: string,
9
+ plugins : Bun.BunPlugin[] = []) {
10
+
11
+ // create a temp directory for output in the os temp directory
12
+ const outDir = fs.mkdtempSync(path.join(os.tmpdir(), `${binaryName}-build-`));
13
+ const finalOutDir = `${rootDir}/dist`;
14
+
15
+ // Parse --target argument (e.g., --target=linux-x64)
16
+ const targetArg = process.argv.find(arg => arg.startsWith('--target='));
17
+ const target = targetArg?.split('=')[1] as
18
+ | 'bun-linux-x64'
19
+ | 'bun-linux-arm64'
20
+ | 'bun-darwin-x64'
21
+ | 'bun-darwin-arm64'
22
+ | 'bun-windows-x64'
23
+ | undefined;
24
+
25
+ const isWindowsBinary = target?.startsWith('bun-windows') || (!target && os.platform() === 'win32');
26
+ const outfile = isWindowsBinary ? `${outDir}/${binaryName}.exe` : `${outDir}/${binaryName}`;
27
+ console.info('Building binary...');
28
+ if (target) {
29
+ console.info(`Target: ${target}`);
30
+ }
31
+
32
+ const result = await Bun.build({
33
+ entrypoints: [entrypoint],
34
+ compile: target
35
+ ? { outfile, target }
36
+ : { outfile },
37
+ plugins: plugins,
38
+ minify: true,
39
+ sourcemap: true,
40
+ define: {
41
+ 'process.env.NODE_ENV': JSON.stringify('production'),
42
+ },
43
+ });
44
+
45
+ if (!result.success) {
46
+ console.error('Build failed:');
47
+ for (const log of result.logs) {
48
+ console.error(log);
49
+ }
50
+ process.exit(1);
51
+ }
52
+
53
+
54
+ console.info('Ensuring dist directory exists...');
55
+ fs.mkdirSync(finalOutDir, { recursive: true });
56
+
57
+ console.info('Copying built file to dist directory...');
58
+ const finalBinaryBaseName = target ? `${binaryName}-${target.replace('bun-', '')}` : binaryName;
59
+ const finalBinaryPath = `${finalOutDir}/${finalBinaryBaseName}${isWindowsBinary ? '.exe' : ''}`;
60
+ fs.copyFileSync(outfile, finalBinaryPath);
61
+
62
+ console.info('Cleaning up temporary files...');
63
+ fs.rmSync(outDir, { recursive: true, force: true });
64
+
65
+ console.info('Build completed:', finalBinaryPath);
66
+ }
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from "./cli/parser.ts";
6
6
  export * from "./cli/output/colors.ts";
7
7
 
8
8
  export * from "./core/application.ts";
9
+ export * from "./core/builder.ts";
9
10
  export * from "./core/command.ts";
10
11
  export * from "./core/context.ts";
11
12
  export * from "./core/help.ts";