@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 +244 -0
- package/docs/API.md +191 -0
- package/docs/USAGE.md +572 -0
- package/package.json +1 -1
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
|
+
[](https://www.npmjs.com/package/@sandro-sikic/maker)
|
|
6
|
+
[](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
|