@sandro-sikic/maker 1.0.8 → 1.0.9

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 ADDED
@@ -0,0 +1,244 @@
1
+ # Maker
2
+
3
+ A lightweight library for building interactive command-line tools with ease.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@sandro-sikic/maker)](https://www.npmjs.com/package/@sandro-sikic/maker)
6
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
7
+
8
+ ## Features
9
+
10
+ ✨ **Simple API** - Just 5 core functions to build powerful CLI tools
11
+ 🎯 **Interactive Prompts** - Built-in support for user input via [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js)
12
+ ⚡ **Command Execution** - Run shell commands with streaming output
13
+ 🎨 **Beautiful Spinners** - Visual feedback with [ora](https://github.com/sindresorhus/ora)
14
+ 🛡️ **Graceful Shutdown** - Automatic cleanup on exit signals
15
+ 📘 **TypeScript Support** - Full type definitions included
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @sandro-sikic/maker
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```javascript
26
+ import * as maker from '@sandro-sikic/maker';
27
+
28
+ // Initialize CLI environment
29
+ maker.init();
30
+
31
+ // Prompt user for input
32
+ const name = await maker.prompt.input({
33
+ message: 'What is your project name?',
34
+ });
35
+
36
+ // Show progress with spinner
37
+ const loading = maker.spinner('Creating project...').start();
38
+
39
+ // Run shell commands
40
+ await maker.run(`mkdir ${name}`);
41
+ await maker.run(`cd ${name} && npm init -y`);
42
+
43
+ loading.succeed('Project created! 🎉');
44
+ ```
45
+
46
+ ## API Overview
47
+
48
+ ### Core Functions
49
+
50
+ | Function | Description |
51
+ | -------------------- | -------------------------------------------------- |
52
+ | `init()` | Validates interactive terminal environment |
53
+ | `run(command, opts)` | Executes shell commands with streaming output |
54
+ | `onExit(callback)` | Registers cleanup function for graceful shutdown |
55
+ | `prompt.*` | Interactive prompts (input, select, confirm, etc.) |
56
+ | `spinner(text)` | Creates terminal loading indicators |
57
+
58
+ ### Example: Simple Build Tool
59
+
60
+ ```javascript
61
+ import * as maker from '@sandro-sikic/maker';
62
+
63
+ async function build() {
64
+ maker.init();
65
+
66
+ // Register cleanup
67
+ maker.onExit(() => {
68
+ console.log('Cleanup complete');
69
+ });
70
+
71
+ // Confirm action
72
+ const shouldBuild = await maker.prompt.confirm({
73
+ message: 'Start build?',
74
+ default: true,
75
+ });
76
+
77
+ if (!shouldBuild) return;
78
+
79
+ // Execute with spinner
80
+ const building = maker.spinner('Building...').start();
81
+ const result = await maker.run('npm run build');
82
+
83
+ if (result.isError) {
84
+ building.fail('Build failed!');
85
+ process.exit(1);
86
+ }
87
+
88
+ building.succeed('Build complete!');
89
+ }
90
+
91
+ build();
92
+ ```
93
+
94
+ ## Documentation
95
+
96
+ 📖 **[Complete Usage Guide](./docs/USAGE.md)** - Detailed documentation with examples
97
+ ⚡ **[API Quick Reference](./docs/API.md)** - Fast lookup for all functions
98
+
99
+ ## API Details
100
+
101
+ ### `init()`
102
+
103
+ Ensures your CLI is running in an interactive terminal. Always call this first.
104
+
105
+ ```javascript
106
+ maker.init();
107
+ ```
108
+
109
+ ### `run(command, opts)`
110
+
111
+ Execute shell commands with real-time output streaming.
112
+
113
+ ```javascript
114
+ const result = await maker.run('npm test');
115
+
116
+ if (result.isError) {
117
+ console.error('Command failed:', result.stderr);
118
+ }
119
+ ```
120
+
121
+ **Returns:** `{ output, stdout, stderr, code, isError, error }`
122
+
123
+ ### `onExit(callback)`
124
+
125
+ Register cleanup handlers for graceful shutdown (SIGINT, SIGTERM, SIGQUIT).
126
+
127
+ ```javascript
128
+ maker.onExit(async () => {
129
+ await closeDatabase();
130
+ await stopServer();
131
+ });
132
+ ```
133
+
134
+ ### `prompt.*`
135
+
136
+ Interactive prompts powered by [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js):
137
+
138
+ ```javascript
139
+ await maker.prompt.input({ message: 'Name?' });
140
+ await maker.prompt.confirm({ message: 'Continue?' });
141
+ await maker.prompt.select({ message: 'Choose:', choices: [...] });
142
+ await maker.prompt.checkbox({ message: 'Select:', choices: [...] });
143
+ await maker.prompt.password({ message: 'API key:' });
144
+ ```
145
+
146
+ ### `spinner(text)`
147
+
148
+ Create terminal spinners with [ora](https://github.com/sindresorhus/ora):
149
+
150
+ ```javascript
151
+ const s = maker.spinner('Loading...').start();
152
+ s.succeed('Done!'); // ✔
153
+ s.fail('Failed!'); // ✖
154
+ s.warn('Warning!'); // ⚠
155
+ s.info('Info!'); // ℹ
156
+ ```
157
+
158
+ ## Real-World Example
159
+
160
+ ```javascript
161
+ import * as maker from '@sandro-sikic/maker';
162
+
163
+ async function setupProject() {
164
+ maker.init();
165
+
166
+ // Get project configuration
167
+ const config = {
168
+ name: await maker.prompt.input({
169
+ message: 'Project name:',
170
+ }),
171
+ framework: await maker.prompt.select({
172
+ message: 'Framework:',
173
+ choices: [
174
+ { name: 'React', value: 'react' },
175
+ { name: 'Vue', value: 'vue' },
176
+ { name: 'Angular', value: 'angular' },
177
+ ],
178
+ }),
179
+ features: await maker.prompt.checkbox({
180
+ message: 'Features:',
181
+ choices: [
182
+ { name: 'TypeScript', value: 'typescript' },
183
+ { name: 'ESLint', value: 'eslint' },
184
+ { name: 'Testing', value: 'testing' },
185
+ ],
186
+ }),
187
+ };
188
+
189
+ // Confirm setup
190
+ const proceed = await maker.prompt.confirm({
191
+ message: 'Create project?',
192
+ default: true,
193
+ });
194
+
195
+ if (!proceed) {
196
+ console.log('Cancelled');
197
+ return;
198
+ }
199
+
200
+ // Setup with progress indicators
201
+ const setup = maker.spinner('Creating project...').start();
202
+
203
+ await maker.run(`mkdir ${config.name}`);
204
+ setup.text = 'Installing dependencies...';
205
+ await maker.run(`cd ${config.name} && npm init -y`);
206
+ await maker.run(`cd ${config.name} && npm install ${config.framework}`);
207
+
208
+ for (const feature of config.features) {
209
+ setup.text = `Installing ${feature}...`;
210
+ await maker.run(`cd ${config.name} && npm install ${feature}`);
211
+ }
212
+
213
+ setup.succeed('Project ready! 🚀');
214
+ console.log(`\nNext:\n cd ${config.name}\n npm start`);
215
+ }
216
+
217
+ setupProject();
218
+ ```
219
+
220
+ ## TypeScript
221
+
222
+ Full TypeScript support with included type definitions:
223
+
224
+ ```typescript
225
+ import { run, RunResult, Ora } from '@sandro-sikic/maker';
226
+
227
+ const result: RunResult = await run('echo "Hello"');
228
+ const spinner: Ora = maker.spinner('Loading...');
229
+ ```
230
+
231
+ ## Repository
232
+
233
+ [github.com/sandro-sikic/maker](https://github.com/sandro-sikic/maker)
234
+
235
+ ## License
236
+
237
+ ISC
238
+
239
+ ## Credits
240
+
241
+ Built with:
242
+
243
+ - [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js) - Interactive prompts
244
+ - [ora](https://github.com/sindresorhus/ora) - Terminal spinners
package/docs/API.md ADDED
@@ -0,0 +1,191 @@
1
+ # Maker - API Quick Reference
2
+
3
+ Quick reference guide for `@sandro-sikic/maker` functions.
4
+
5
+ ---
6
+
7
+ ## `init()`
8
+
9
+ Validates interactive terminal environment. **Call first** in your CLI app.
10
+
11
+ ```javascript
12
+ maker.init();
13
+ ```
14
+
15
+ Exits with code 1 if not running in an interactive terminal (TTY).
16
+
17
+ ---
18
+
19
+ ## `run(command, opts)`
20
+
21
+ Execute shell commands with streaming output.
22
+
23
+ ```javascript
24
+ const result = await run('npm install', { maxLines: 10000 });
25
+ ```
26
+
27
+ **Parameters:**
28
+
29
+ - `command` - Shell command string (required)
30
+ - `opts.maxLines` - Max output lines to capture (default: 10000)
31
+
32
+ **Returns:**
33
+
34
+ ```javascript
35
+ {
36
+ output: string, // Combined stdout + stderr
37
+ stdout: string, // Standard output
38
+ stderr: string, // Standard error
39
+ code: number|null, // Exit code
40
+ isError: boolean, // true if failed
41
+ error: Error|null // Spawn error if any
42
+ }
43
+ ```
44
+
45
+ **Usage patterns:**
46
+
47
+ ```javascript
48
+ // Foreground (waits for completion)
49
+ await run('npm test');
50
+
51
+ // Background (non-blocking)
52
+ run('npm run dev');
53
+
54
+ // Error handling
55
+ const result = await run('npm build');
56
+ if (result.isError) {
57
+ console.error('Failed:', result.stderr);
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## `onExit(callback)`
64
+
65
+ Register cleanup function for graceful shutdown.
66
+
67
+ ```javascript
68
+ onExit(async () => {
69
+ await cleanup();
70
+ });
71
+ ```
72
+
73
+ **Parameters:**
74
+
75
+ - `callback` - Sync or async cleanup function
76
+
77
+ **Returns:**
78
+
79
+ - Unregister function
80
+
81
+ **Triggers on:**
82
+
83
+ - SIGINT (Ctrl+C)
84
+ - SIGTERM
85
+ - SIGQUIT
86
+
87
+ ---
88
+
89
+ ## `prompt.*`
90
+
91
+ Interactive prompts ([inquirer](https://github.com/SBoudrias/Inquirer.js/tree/master/packages/prompts)).
92
+
93
+ ```javascript
94
+ // Text input
95
+ await prompt.input({ message: 'Name?' });
96
+
97
+ // Yes/no
98
+ await prompt.confirm({ message: 'Continue?' });
99
+
100
+ // Select one
101
+ await prompt.select({
102
+ message: 'Choose:',
103
+ choices: [
104
+ { name: 'Option 1', value: '1' },
105
+ { name: 'Option 2', value: '2' }
106
+ ]
107
+ });
108
+
109
+ // Select multiple
110
+ await prompt.checkbox({
111
+ message: 'Select:',
112
+ choices: [...]
113
+ });
114
+
115
+ // Password
116
+ await prompt.password({ message: 'API key:' });
117
+
118
+ // Number
119
+ await prompt.number({ message: 'Port:', default: 3000 });
120
+ ```
121
+
122
+ ---
123
+
124
+ ## `spinner(text)`
125
+
126
+ Terminal spinner ([ora](https://github.com/sindresorhus/ora)).
127
+
128
+ ```javascript
129
+ const s = spinner('Loading...').start();
130
+
131
+ // Update text
132
+ s.text = 'Still loading...';
133
+
134
+ // Complete
135
+ s.succeed('Done!'); // ✔
136
+ s.fail('Failed!'); // ✖
137
+ s.warn('Warning!'); // ⚠
138
+ s.info('Info!'); // ℹ
139
+ s.stop(); // Stop and clear
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Complete Example
145
+
146
+ ```javascript
147
+ import * as maker from '@sandro-sikic/maker';
148
+
149
+ // 1. Initialize
150
+ maker.init();
151
+
152
+ // 2. Register cleanup
153
+ maker.onExit(async () => {
154
+ console.log('Cleaning up...');
155
+ });
156
+
157
+ // 3. Prompt user
158
+ const name = await maker.prompt.input({
159
+ message: 'Project name:',
160
+ default: 'my-app',
161
+ });
162
+
163
+ const confirmed = await maker.prompt.confirm({
164
+ message: 'Install dependencies?',
165
+ });
166
+
167
+ // 4. Run commands with spinner
168
+ if (confirmed) {
169
+ const s = maker.spinner('Installing...').start();
170
+
171
+ const result = await maker.run(`npm create ${name}`);
172
+
173
+ if (result.isError) {
174
+ s.fail('Installation failed');
175
+ process.exit(1);
176
+ }
177
+
178
+ s.succeed('Installation complete!');
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## TypeScript Types
185
+
186
+ ```typescript
187
+ import type { RunResult, Ora } from '@sandro-sikic/maker';
188
+
189
+ // Also exports all @inquirer/prompts types
190
+ import type { ConfirmPrompt, InputPrompt } from '@sandro-sikic/maker';
191
+ ```
package/docs/USAGE.md ADDED
@@ -0,0 +1,572 @@
1
+ # Maker - Usage Guide
2
+
3
+ A lightweight library for building interactive command-line tools with ease. Maker provides utilities for running shell commands, handling user prompts, displaying spinners, and managing graceful shutdowns.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @sandro-sikic/maker
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```javascript
14
+ import * as maker from '@sandro-sikic/maker';
15
+
16
+ maker.init();
17
+
18
+ const answer = await maker.prompt.input({
19
+ message: 'What is your name?',
20
+ });
21
+
22
+ const loading = maker.spinner('Processing...').start();
23
+ await maker.run('npm install');
24
+ loading.succeed('Installation complete!');
25
+ ```
26
+
27
+ ## API Reference
28
+
29
+ ### `init()`
30
+
31
+ Initializes the CLI environment and validates that the application is running in an interactive terminal (TTY). This should be called at the start of your CLI application.
32
+
33
+ **Returns:** `void`
34
+
35
+ **Throws:** Exits with code 1 if not running in an interactive terminal
36
+
37
+ **Example:**
38
+
39
+ ```javascript
40
+ import { init } from '@sandro-sikic/maker';
41
+
42
+ init(); // Call this first in your CLI app
43
+ ```
44
+
45
+ **Why use it?**
46
+ Prevents your CLI from running in incompatible environments like the Node REPL, non-interactive terminals, or debug consoles where user input cannot be properly handled.
47
+
48
+ ---
49
+
50
+ ### `run(command, opts)`
51
+
52
+ Executes a shell command with real-time output streaming to the parent process. Both stdout and stderr are displayed as the command runs, and the last N lines (default: 10,000) are captured for inspection.
53
+
54
+ **Parameters:**
55
+
56
+ - `command` (string, required) - The shell command to execute
57
+ - `opts` (object, optional)
58
+ - `maxLines` (number) - Maximum number of output lines to retain in the captured output (default: 10000)
59
+
60
+ **Returns:** `Promise<RunResult>`
61
+
62
+ ```typescript
63
+ type RunResult = {
64
+ output: string; // Combined stdout + stderr
65
+ stdout: string; // Standard output only
66
+ stderr: string; // Standard error only
67
+ code: number | null; // Exit code (null if spawn error)
68
+ isError: boolean; // true if command failed or spawn error
69
+ error: Error | null; // Error object if spawn failed
70
+ };
71
+ ```
72
+
73
+ **Behavior:**
74
+
75
+ - **Foreground:** Use `await run(...)` to wait for command completion before continuing
76
+ - **Background:** Call `run(...)` without await to let it execute concurrently
77
+
78
+ **Examples:**
79
+
80
+ **Basic usage (foreground):**
81
+
82
+ ```javascript
83
+ import { run } from '@sandro-sikic/maker';
84
+
85
+ // Wait for command to complete
86
+ const result = await run('npm test');
87
+
88
+ if (result.isError) {
89
+ console.error('Tests failed!', result.stderr);
90
+ process.exit(1);
91
+ }
92
+ ```
93
+
94
+ **Background execution:**
95
+
96
+ ```javascript
97
+ // Start command and continue immediately
98
+ run('npm run dev'); // Runs in background
99
+
100
+ // Your code continues here while the command executes
101
+ await someOtherTask();
102
+ ```
103
+
104
+ **With custom output limit:**
105
+
106
+ ```javascript
107
+ // Only keep last 100 lines of output
108
+ const result = await run('npm run build', { maxLines: 100 });
109
+ console.log('Build output:', result.stdout);
110
+ ```
111
+
112
+ **Multiple sequential commands:**
113
+
114
+ ```javascript
115
+ await run('npm install');
116
+ await run('npm run build');
117
+ await run('npm test');
118
+ ```
119
+
120
+ ---
121
+
122
+ ### `onExit(callback)`
123
+
124
+ Registers a cleanup function that executes gracefully when the process receives termination signals (SIGINT, SIGTERM, SIGQUIT). Perfect for cleanup tasks like closing database connections, saving state, or stopping servers.
125
+
126
+ **Parameters:**
127
+
128
+ - `callback` (function, required) - Sync or async function to execute on exit. Can return a Promise.
129
+
130
+ **Returns:** `Function` - An unregister function to remove the handler
131
+
132
+ **Example:**
133
+
134
+ **Basic cleanup:**
135
+
136
+ ```javascript
137
+ import { onExit } from '@sandro-sikic/maker';
138
+
139
+ onExit(() => {
140
+ console.log('Cleaning up...');
141
+ // Close connections, save state, etc.
142
+ });
143
+ ```
144
+
145
+ **Async cleanup:**
146
+
147
+ ```javascript
148
+ onExit(async () => {
149
+ await database.close();
150
+ await server.stop();
151
+ console.log('Shutdown complete');
152
+ });
153
+ ```
154
+
155
+ **Unregister handler:**
156
+
157
+ ```javascript
158
+ const cleanup = onExit(() => {
159
+ console.log('Exiting...');
160
+ });
161
+
162
+ // Later, if needed:
163
+ cleanup(); // Removes the exit handler
164
+ ```
165
+
166
+ **Features:**
167
+
168
+ - Automatically displays a "Gracefully shutting down..." spinner during cleanup
169
+ - Prevents multiple executions if signal is received multiple times
170
+ - Catches and logs errors in your callback
171
+ - Ensures `process.exit(0)` is called after cleanup completes
172
+
173
+ ---
174
+
175
+ ### `prompt`
176
+
177
+ Direct re-export of [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js/tree/master/packages/prompts) with all its interactive prompt types.
178
+
179
+ **Available prompt types:**
180
+
181
+ - `input` - Free text input
182
+ - `number` - Numeric input
183
+ - `confirm` - Yes/no confirmation
184
+ - `select` - Choose one option from a list
185
+ - `checkbox` - Choose multiple options
186
+ - `password` - Hidden input for sensitive data
187
+ - `editor` - Launch external editor for multi-line input
188
+ - `search` - Select with search filtering
189
+ - `rawlist` - List selection by number
190
+
191
+ **Examples:**
192
+
193
+ **Text input:**
194
+
195
+ ```javascript
196
+ import { prompt } from '@sandro-sikic/maker';
197
+
198
+ const name = await prompt.input({
199
+ message: 'What is your name?',
200
+ default: 'John Doe',
201
+ });
202
+ ```
203
+
204
+ **Confirmation:**
205
+
206
+ ```javascript
207
+ const confirmed = await prompt.confirm({
208
+ message: 'Continue with installation?',
209
+ default: true,
210
+ });
211
+
212
+ if (!confirmed) {
213
+ process.exit(0);
214
+ }
215
+ ```
216
+
217
+ **Select from list:**
218
+
219
+ ```javascript
220
+ const framework = await prompt.select({
221
+ message: 'Choose a framework:',
222
+ choices: [
223
+ { name: 'React', value: 'react' },
224
+ { name: 'Vue', value: 'vue' },
225
+ { name: 'Angular', value: 'angular' },
226
+ ],
227
+ });
228
+
229
+ console.log(`Selected: ${framework}`);
230
+ ```
231
+
232
+ **Multiple selection:**
233
+
234
+ ```javascript
235
+ const features = await prompt.checkbox({
236
+ message: 'Select features to install:',
237
+ choices: [
238
+ { name: 'TypeScript', value: 'typescript', checked: true },
239
+ { name: 'ESLint', value: 'eslint' },
240
+ { name: 'Prettier', value: 'prettier' },
241
+ ],
242
+ });
243
+
244
+ console.log('Installing:', features.join(', '));
245
+ ```
246
+
247
+ **Password input:**
248
+
249
+ ```javascript
250
+ const apiKey = await prompt.password({
251
+ message: 'Enter your API key:',
252
+ mask: '*',
253
+ });
254
+ ```
255
+
256
+ For more details on prompt options and types, see the [Inquirer.js documentation](https://github.com/SBoudrias/Inquirer.js/tree/master/packages/prompts).
257
+
258
+ ---
259
+
260
+ ### `spinner`
261
+
262
+ Direct re-export of [ora](https://github.com/sindresorhus/ora) for elegant terminal spinners and status indicators.
263
+
264
+ **Basic usage:**
265
+
266
+ ```javascript
267
+ import { spinner } from '@sandro-sikic/maker';
268
+
269
+ const loading = spinner('Loading...').start();
270
+
271
+ // Do some work...
272
+ await someAsyncTask();
273
+
274
+ loading.succeed('Done!');
275
+ ```
276
+
277
+ **Common methods:**
278
+
279
+ - `start()` - Begin spinning
280
+ - `succeed(text?)` - Complete with success (✔)
281
+ - `fail(text?)` - Complete with error (✖)
282
+ - `warn(text?)` - Complete with warning (⚠)
283
+ - `info(text?)` - Complete with info (ℹ)
284
+ - `stop()` - Stop and clear
285
+ - `clear()` - Clear from terminal
286
+
287
+ **Examples:**
288
+
289
+ **Success/failure flow:**
290
+
291
+ ```javascript
292
+ const build = spinner('Building project...').start();
293
+
294
+ try {
295
+ await run('npm run build');
296
+ build.succeed('Build completed successfully');
297
+ } catch (error) {
298
+ build.fail('Build failed');
299
+ throw error;
300
+ }
301
+ ```
302
+
303
+ **Update spinner text:**
304
+
305
+ ```javascript
306
+ const progress = spinner('Step 1/3').start();
307
+
308
+ await step1();
309
+ progress.text = 'Step 2/3';
310
+
311
+ await step2();
312
+ progress.text = 'Step 3/3';
313
+
314
+ await step3();
315
+ progress.succeed('All steps completed!');
316
+ ```
317
+
318
+ **Multiple sequential operations:**
319
+
320
+ ```javascript
321
+ const install = spinner('Installing dependencies...').start();
322
+ await run('npm install');
323
+ install.succeed('Dependencies installed');
324
+
325
+ const build = spinner('Building application...').start();
326
+ await run('npm run build');
327
+ build.succeed('Build complete');
328
+
329
+ const test = spinner('Running tests...').start();
330
+ await run('npm test');
331
+ test.succeed('All tests passed');
332
+ ```
333
+
334
+ For advanced spinner options and customization, see the [ora documentation](https://github.com/sindresorhus/ora).
335
+
336
+ ---
337
+
338
+ ## Complete Examples
339
+
340
+ ### Example 1: Simple CLI Tool
341
+
342
+ ```javascript
343
+ import * as maker from '@sandro-sikic/maker';
344
+
345
+ async function main() {
346
+ maker.init();
347
+
348
+ const projectName = await maker.prompt.input({
349
+ message: 'Project name:',
350
+ default: 'my-project',
351
+ });
352
+
353
+ const installing = maker.spinner('Creating project...').start();
354
+ await maker.run(`mkdir ${projectName}`);
355
+ await maker.run(`cd ${projectName} && npm init -y`);
356
+ installing.succeed(`Project ${projectName} created!`);
357
+ }
358
+
359
+ main();
360
+ ```
361
+
362
+ ### Example 2: Build Tool with Cleanup
363
+
364
+ ```javascript
365
+ import * as maker from '@sandro-sikic/maker';
366
+
367
+ async function buildTool() {
368
+ maker.init();
369
+
370
+ // Register cleanup handler
371
+ maker.onExit(async () => {
372
+ console.log('Cleaning up build artifacts...');
373
+ await maker.run('rm -rf .temp');
374
+ });
375
+
376
+ const shouldBuild = await maker.prompt.confirm({
377
+ message: 'Start build process?',
378
+ default: true,
379
+ });
380
+
381
+ if (!shouldBuild) {
382
+ console.log('Build cancelled');
383
+ process.exit(0);
384
+ }
385
+
386
+ const building = maker.spinner('Building...').start();
387
+ const result = await maker.run('npm run build');
388
+
389
+ if (result.isError) {
390
+ building.fail('Build failed!');
391
+ console.error(result.stderr);
392
+ process.exit(1);
393
+ }
394
+
395
+ building.succeed('Build successful!');
396
+ }
397
+
398
+ buildTool();
399
+ ```
400
+
401
+ ### Example 3: Interactive Setup Wizard
402
+
403
+ ```javascript
404
+ import * as maker from '@sandro-sikic/maker';
405
+
406
+ async function setup() {
407
+ maker.init();
408
+
409
+ console.log('Welcome to the Setup Wizard!\n');
410
+
411
+ const config = {
412
+ name: await maker.prompt.input({
413
+ message: 'Project name:',
414
+ }),
415
+
416
+ framework: await maker.prompt.select({
417
+ message: 'Choose a framework:',
418
+ choices: [
419
+ { name: 'React', value: 'react' },
420
+ { name: 'Vue', value: 'vue' },
421
+ { name: 'Vanilla', value: 'vanilla' },
422
+ ],
423
+ }),
424
+
425
+ features: await maker.prompt.checkbox({
426
+ message: 'Additional features:',
427
+ choices: [
428
+ { name: 'TypeScript', value: 'typescript' },
429
+ { name: 'ESLint', value: 'eslint' },
430
+ { name: 'Testing', value: 'testing' },
431
+ ],
432
+ }),
433
+ };
434
+
435
+ console.log('\nConfiguration:', config);
436
+
437
+ const confirm = await maker.prompt.confirm({
438
+ message: 'Proceed with installation?',
439
+ default: true,
440
+ });
441
+
442
+ if (!confirm) {
443
+ console.log('Setup cancelled');
444
+ return;
445
+ }
446
+
447
+ const installing = maker.spinner('Setting up project...').start();
448
+
449
+ // Create project directory
450
+ await maker.run(`mkdir ${config.name}`);
451
+
452
+ // Install framework
453
+ installing.text = `Installing ${config.framework}...`;
454
+ await maker.run(`cd ${config.name} && npm init -y`);
455
+ await maker.run(`cd ${config.name} && npm install ${config.framework}`);
456
+
457
+ // Install features
458
+ for (const feature of config.features) {
459
+ installing.text = `Installing ${feature}...`;
460
+ await maker.run(`cd ${config.name} && npm install ${feature}`);
461
+ }
462
+
463
+ installing.succeed('Project setup complete! 🎉');
464
+ console.log(`\nNext steps:\n cd ${config.name}\n npm start`);
465
+ }
466
+
467
+ setup();
468
+ ```
469
+
470
+ ### Example 4: Development Server Manager
471
+
472
+ ```javascript
473
+ import * as maker from '@sandro-sikic/maker';
474
+
475
+ async function devServer() {
476
+ maker.init();
477
+
478
+ // Graceful shutdown handler
479
+ maker.onExit(async () => {
480
+ console.log('\nStopping development server...');
481
+ // Additional cleanup if needed
482
+ });
483
+
484
+ const port = await maker.prompt.number({
485
+ message: 'Port number:',
486
+ default: 3000,
487
+ });
488
+
489
+ const watch = await maker.prompt.confirm({
490
+ message: 'Enable watch mode?',
491
+ default: true,
492
+ });
493
+
494
+ const starting = maker.spinner('Starting server...').start();
495
+
496
+ // Start server in background
497
+ const watchFlag = watch ? '--watch' : '';
498
+ maker.run(`npm run dev ${watchFlag} -- --port ${port}`);
499
+
500
+ await new Promise((resolve) => setTimeout(resolve, 2000));
501
+ starting.succeed(`Server running on http://localhost:${port}`);
502
+
503
+ console.log('\nPress Ctrl+C to stop the server');
504
+
505
+ // Keep process alive
506
+ await new Promise(() => {});
507
+ }
508
+
509
+ devServer();
510
+ ```
511
+
512
+ ## TypeScript Support
513
+
514
+ This library includes TypeScript definitions. Import types as needed:
515
+
516
+ ```typescript
517
+ import { run, RunResult, Ora } from '@sandro-sikic/maker';
518
+
519
+ const result: RunResult = await run('echo "Hello"');
520
+ const spinner: Ora = maker.spinner('Loading...');
521
+ ```
522
+
523
+ ## Best Practices
524
+
525
+ 1. **Always call `init()` first** - Ensures your CLI runs in a proper terminal environment
526
+
527
+ 2. **Use spinners for long operations** - Provides visual feedback that something is happening
528
+
529
+ 3. **Handle errors gracefully** - Check `result.isError` from `run()` calls and provide helpful error messages
530
+
531
+ 4. **Register cleanup handlers** - Use `onExit()` for proper resource cleanup
532
+
533
+ 5. **Provide good defaults** - Use the `default` option in prompts to speed up common workflows
534
+
535
+ 6. **Combine with async/await** - The API is designed for clean async/await patterns
536
+
537
+ 7. **Stream long-running commands** - Let `run()` stream output in real-time for better UX
538
+
539
+ ## Troubleshooting
540
+
541
+ ### "This TUI requires an interactive terminal"
542
+
543
+ This error means your code isn't running in a proper terminal. Make sure you're:
544
+
545
+ - Running in a real terminal (not Node REPL)
546
+ - Not running in a non-interactive environment (like CI without proper TTY setup)
547
+ - Not running in a debug console that doesn't support TTY
548
+
549
+ ### Spinner not visible
550
+
551
+ If spinners don't appear:
552
+
553
+ - Ensure you called `init()` first
554
+ - Check that you're in an interactive terminal
555
+ - Verify CI environments have TTY support if running in CI
556
+
557
+ ### Command output truncated
558
+
559
+ By default, `run()` keeps the last 10,000 lines. For commands with massive output:
560
+
561
+ ```javascript
562
+ // Increase the line limit
563
+ await run('very-verbose-command', { maxLines: 50000 });
564
+ ```
565
+
566
+ ## License
567
+
568
+ ISC
569
+
570
+ ## Repository
571
+
572
+ https://github.com/sandro-sikic/maker
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sandro-sikic/maker",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "description": "A library to create dev cli",
6
6
  "main": "index.js",