@burger-api/cli 0.6.6 → 0.7.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.
@@ -1,478 +1,478 @@
1
- /**
2
- * Beautiful Console Output
3
- *
4
- * Makes the CLI look nice with colors and symbols.
5
- * Uses standard ANSI codes that work in all terminals.
6
- * Automatically falls back to ASCII on older Windows terminals.
7
- *
8
- * No dependencies needed - just plain JavaScript!
9
- */
10
-
11
- /**
12
- * Detect if the terminal supports Unicode symbols
13
- * Returns false for Windows CMD and older PowerShell to use ASCII fallbacks
14
- */
15
- function supportsUnicode(): boolean {
16
- // Check if we're on Windows
17
- if (process.platform !== 'win32') {
18
- return true; // macOS and Linux support Unicode
19
- }
20
-
21
- // Check for Windows Terminal (supports Unicode)
22
- if (process.env.WT_SESSION) {
23
- return true;
24
- }
25
-
26
- // Check for VS Code terminal (supports Unicode)
27
- if (process.env.TERM_PROGRAM === 'vscode') {
28
- return true;
29
- }
30
-
31
- // Check for ConEmu/Cmder (supports Unicode)
32
- if (process.env.ConEmuANSI === 'ON') {
33
- return true;
34
- }
35
-
36
- // Check for modern terminal emulators
37
- if (process.env.TERM && process.env.TERM !== 'dumb') {
38
- return true;
39
- }
40
-
41
- // Check for CI environments (usually support Unicode)
42
- if (process.env.CI) {
43
- return true;
44
- }
45
-
46
- // Default to ASCII for Windows CMD and older PowerShell
47
- return false;
48
- }
49
-
50
- /**
51
- * ANSI color codes for terminal output
52
- * These are special character sequences that tell the terminal to change colors
53
- */
54
- const colors = {
55
- reset: '\x1b[0m', // Reset to default color
56
- bright: '\x1b[1m', // Make text bright/bold
57
- dim: '\x1b[2m', // Make text dim
58
-
59
- // Regular colors
60
- red: '\x1b[31m',
61
- green: '\x1b[32m',
62
- yellow: '\x1b[33m',
63
- blue: '\x1b[34m',
64
- magenta: '\x1b[35m',
65
- cyan: '\x1b[36m',
66
- white: '\x1b[37m',
67
- gray: '\x1b[90m',
68
-
69
- // Background colors (for highlighting)
70
- bgRed: '\x1b[41m',
71
- bgGreen: '\x1b[42m',
72
- bgYellow: '\x1b[43m',
73
- bgBlue: '\x1b[44m',
74
- };
75
-
76
- /**
77
- * Unicode symbols for modern terminals
78
- */
79
- const unicodeSymbols = {
80
- success: '[OK]',
81
- error: '[X]',
82
- info: '[i]',
83
- warning: '[!]',
84
- arrow: '[->]',
85
- bullet: '[•]',
86
- star: '[★]',
87
- };
88
-
89
- /**
90
- * ASCII fallback symbols for older Windows terminals (CMD, older PowerShell)
91
- */
92
- const asciiSymbols = {
93
- success: '[OK]',
94
- error: '[X]',
95
- info: '[i]',
96
- warning: '[!]',
97
- arrow: '[->]',
98
- bullet: '•',
99
- star: '[*]',
100
- };
101
-
102
- /**
103
- * Pretty symbols for different message types
104
- * Automatically uses ASCII fallbacks on older Windows terminals
105
- */
106
- const symbols = supportsUnicode() ? unicodeSymbols : asciiSymbols;
107
-
108
- /**
109
- * Show a success message (green with checkmark)
110
- * Use this when something completes successfully
111
- *
112
- * @param message - The message to display
113
- * @example
114
- * success('Project created successfully!')
115
- */
116
- export function success(message: string): void {
117
- console.log(`${colors.green}${symbols.success}${colors.reset} ${message}`);
118
- }
119
-
120
- /**
121
- * Show an error message (red with X)
122
- * Use this when something goes wrong
123
- *
124
- * @param message - The error message to display
125
- * @example
126
- * error('Failed to download file')
127
- */
128
- export function error(message: string): void {
129
- console.log(`${colors.red}${symbols.error}${colors.reset} ${message}`);
130
- }
131
-
132
- /**
133
- * Show an info message (blue with info symbol)
134
- * Use this for general information
135
- *
136
- * @param message - The info message to display
137
- * @example
138
- * info('Downloading templates...')
139
- */
140
- export function info(message: string): void {
141
- console.log(`${colors.blue}${symbols.info}${colors.reset} ${message}`);
142
- }
143
-
144
- /**
145
- * Show a warning message (yellow with warning symbol)
146
- * Use this for warnings that aren't errors
147
- *
148
- * @param message - The warning message to display
149
- * @example
150
- * warning('This will overwrite existing files')
151
- */
152
- export function warning(message: string): void {
153
- console.log(`${colors.yellow}${symbols.warning}${colors.reset} ${message}`);
154
- }
155
-
156
- /**
157
- * Show a message with an arrow
158
- * Useful for showing steps or progress
159
- *
160
- * @param message - The message to display
161
- * @example
162
- * step('Installing dependencies...')
163
- */
164
- export function step(message: string): void {
165
- console.log(`${colors.cyan}${symbols.arrow}${colors.reset} ${message}`);
166
- }
167
-
168
- /**
169
- * Return a highlighted string (bold and bright)
170
- * Use this for important text that needs attention
171
- *
172
- * @param message - The message to highlight
173
- * @returns Formatted string with ANSI codes
174
- * @example
175
- * console.log(`Visit ${highlight('http://localhost:4000')}`);
176
- */
177
- export function highlight(message: string): string {
178
- return `${colors.bright}${message}${colors.reset}`;
179
- }
180
-
181
- /**
182
- * Show a dimmed message (gray and dim)
183
- * Use this for less important information
184
- *
185
- * @param message - The message to dim
186
- * @example
187
- * dim('You can skip this step if you want')
188
- */
189
- export function dim(message: string): void {
190
- console.log(`${colors.gray}${colors.dim}${message}${colors.reset}`);
191
- }
192
-
193
- /**
194
- * Show a message with a bullet point
195
- * Useful for lists
196
- *
197
- * @param message - The message to display
198
- * @example
199
- * bullet('CORS middleware')
200
- */
201
- export function bullet(message: string): void {
202
- console.log(` ${colors.gray}${symbols.bullet}${colors.reset} ${message}`);
203
- }
204
-
205
- /**
206
- * Print a blank line
207
- * Helps with spacing and readability
208
- */
209
- export function newline(): void {
210
- console.log();
211
- }
212
-
213
- /**
214
- * Get the line character based on terminal support
215
- */
216
- const lineChar = supportsUnicode() ? '─' : '-';
217
-
218
- /**
219
- * Print a horizontal line separator
220
- * Use this to separate sections
221
- *
222
- * @example
223
- * separator()
224
- */
225
- export function separator(): void {
226
- console.log(colors.gray + lineChar.repeat(50) + colors.reset);
227
- }
228
-
229
- /**
230
- * Print a header with a title
231
- * Makes sections stand out
232
- *
233
- * @param title - The header title
234
- * @example
235
- * header('Available Middleware')
236
- */
237
- export function header(title: string): void {
238
- newline();
239
- console.log(`${colors.bright}${colors.cyan}${title}${colors.reset}`);
240
- console.log(colors.gray + lineChar.repeat(title.length) + colors.reset);
241
- newline();
242
- }
243
-
244
- /**
245
- * Show a command that the user can run
246
- * Displays it in a nice format
247
- *
248
- * @param command - The command to display
249
- * @example
250
- * command('bun install')
251
- */
252
- export function command(command: string): void {
253
- console.log(
254
- ` ${colors.dim}$${colors.reset} ${colors.cyan}${command}${colors.reset}`
255
- );
256
- }
257
-
258
- /**
259
- * Show code or file content
260
- * Displays it in a monospace-looking format
261
- *
262
- * @param code - The code to display
263
- * @example
264
- * code('import { Burger } from "burger-api"')
265
- */
266
- export function code(code: string): void {
267
- console.log(` ${colors.gray}${code}${colors.reset}`);
268
- }
269
-
270
- /**
271
- * Spinner frames - Unicode for modern terminals, ASCII for CMD
272
- */
273
- const spinnerFrames = supportsUnicode()
274
- ? ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
275
- : ['|', '/', '-', '\\'];
276
-
277
- /**
278
- * Simple spinner class for showing progress
279
- * Shows an animated spinner while something is loading
280
- *
281
- * @example
282
- * const spin = spinner('Downloading...');
283
- * // do some work
284
- * spin.stop('Done!');
285
- */
286
- export class Spinner {
287
- private frames = spinnerFrames;
288
- private currentFrame = 0;
289
- private intervalId: Timer | null = null;
290
- private message: string;
291
-
292
- constructor(message: string) {
293
- this.message = message;
294
- this.start();
295
- }
296
-
297
- /**
298
- * Start the spinner animation
299
- */
300
- private start(): void {
301
- // Hide cursor
302
- process.stdout.write('\x1B[?25l');
303
-
304
- // Show first frame
305
- this.render();
306
-
307
- // Update every 80ms for smooth animation
308
- this.intervalId = setInterval(() => {
309
- this.currentFrame = (this.currentFrame + 1) % this.frames.length;
310
- this.render();
311
- }, 80);
312
- }
313
-
314
- /**
315
- * Render the current frame
316
- */
317
- private render(): void {
318
- // Clear the line and move cursor to beginning
319
- process.stdout.write('\r\x1B[K');
320
- // Write the spinner and message
321
- process.stdout.write(
322
- `${colors.cyan}${this.frames[this.currentFrame]}${colors.reset} ${
323
- this.message
324
- }`
325
- );
326
- }
327
-
328
- /**
329
- * Update the spinner message
330
- *
331
- * @param message - New message to display
332
- */
333
- update(message: string): void {
334
- this.message = message;
335
- this.render();
336
- }
337
-
338
- /**
339
- * Stop the spinner and show final message
340
- *
341
- * @param finalMessage - Optional message to show when done
342
- * @param isError - Whether this is an error (shows X instead of checkmark)
343
- */
344
- stop(finalMessage?: string, isError = false): void {
345
- if (this.intervalId) {
346
- clearInterval(this.intervalId);
347
- this.intervalId = null;
348
- }
349
-
350
- // Clear the spinner line
351
- process.stdout.write('\r\x1B[K');
352
-
353
- // Show cursor again
354
- process.stdout.write('\x1B[?25h');
355
-
356
- // Show final message if provided
357
- if (finalMessage) {
358
- if (isError) {
359
- error(finalMessage);
360
- } else {
361
- success(finalMessage);
362
- }
363
- }
364
- }
365
- }
366
-
367
- /**
368
- * Create and return a new spinner
369
- * This is a helper function to make it easier to use
370
- *
371
- * @param message - The message to display while spinning
372
- * @returns A Spinner instance
373
- * @example
374
- * const spin = spinner('Loading...');
375
- * await doSomething();
376
- * spin.stop('Done!');
377
- */
378
- export function spinner(message: string): Spinner {
379
- return new Spinner(message);
380
- }
381
-
382
- /**
383
- * Format a file size in a human-readable way
384
- * Converts bytes to KB, MB, etc.
385
- *
386
- * @param bytes - Size in bytes
387
- * @returns Formatted string like "1.5 MB"
388
- * @example
389
- * formatSize(1500000) // "1.43 MB"
390
- */
391
- export function formatSize(bytes: number): string {
392
- if (bytes < 1024) return bytes + ' B';
393
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
394
- if (bytes < 1024 * 1024 * 1024)
395
- return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
396
- return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
397
- }
398
-
399
- const LOGO_TEXT = `
400
- ██╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ███████╗██████╗ █████╗ ██████╗ ██╗
401
- ╚██╗ ██╔══██╗██║ ██║██╔══██╗██╔════╝ ██╔════╝██╔══██╗██╔══██╗██╔══██╗██║
402
- ╚██╗ ██████╦╝██║ ██║██████╔╝██║ ██╗ █████╗ ██████╔╝███████║██████╔╝██║
403
- ██╔╝ ██╔══██╗██║ ██║██╔══██╗██║ ╚██╗██╔══╝ ██╔══██╗██╔══██║██╔═══╝ ██║
404
- ██╔╝ ██████╦╝╚██████╔╝██║ ██║╚██████╔╝███████╗██║ ██║██║ ██║██║ ██║
405
- ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
406
- CLI tool for BurgerAPI projects - v0.6.6
407
- `.trim();
408
-
409
- /**
410
- * Show ASCII art banner for BurgerAPI CLI
411
- * Displays when CLI starts
412
- * Uses ASCII-safe characters for Windows CMD compatibility
413
- */
414
- export function showBanner(): void {
415
- const bannerColor = '\x1b[38;2;255;204;153m'; // Warm orange color
416
-
417
- const reset = '\x1b[0m';
418
-
419
- // Unicode banner for modern terminals
420
- console.log(`${bannerColor}
421
- ${LOGO_TEXT}
422
-
423
- ${reset}`);
424
- }
425
-
426
- /**
427
- * Create a simple table for displaying data
428
- *
429
- * @param rows - Array of row data
430
- * @example
431
- * table([
432
- * ['Name', 'Version'],
433
- * ['burger-api', '0.6.6'],
434
- * ['bun', '1.3.1']
435
- * ]);
436
- */
437
- export function table(rows: string[][]): void {
438
- if (rows.length === 0) return;
439
-
440
- // Calculate column widths
441
- const colWidths: number[] = [];
442
- for (let col = 0; col < (rows[0]?.length ?? 0); col++) {
443
- let maxWidth = 0;
444
- for (const row of rows) {
445
- if (row[col]?.length && (row[col]?.length ?? 0) > maxWidth) {
446
- maxWidth = row[col]?.length ?? 0;
447
- }
448
- }
449
- colWidths.push(maxWidth + 2); // Add padding
450
- }
451
-
452
- // Print header (first row) with different styling
453
- const header = rows[0];
454
- let headerStr = '';
455
- for (let i = 0; i < (header?.length ?? 0); i++) {
456
- headerStr += `${colors.bright}${(header?.[i] ?? '').padEnd(
457
- colWidths[i] ?? 0
458
- )}${colors.reset}`;
459
- }
460
- console.log(headerStr);
461
-
462
- // Print separator
463
- console.log(
464
- colors.gray +
465
- lineChar.repeat(colWidths.reduce((a, b) => a + b, 0)) +
466
- colors.reset
467
- );
468
-
469
- // Print data rows
470
- for (let i = 1; i < rows.length; i++) {
471
- const row = rows[i];
472
- let rowStr = '';
473
- for (let j = 0; j < (row?.length ?? 0); j++) {
474
- rowStr += (row?.[j] ?? '').padEnd(colWidths[j ?? 0] ?? 0);
475
- }
476
- console.log(rowStr);
477
- }
478
- }
1
+ /**
2
+ * Beautiful Console Output
3
+ *
4
+ * Makes the CLI look nice with colors and symbols.
5
+ * Uses standard ANSI codes that work in all terminals.
6
+ * Automatically falls back to ASCII on older Windows terminals.
7
+ *
8
+ * No dependencies needed - just plain JavaScript!
9
+ */
10
+
11
+ /**
12
+ * Detect if the terminal supports Unicode symbols
13
+ * Returns false for Windows CMD and older PowerShell to use ASCII fallbacks
14
+ */
15
+ function supportsUnicode(): boolean {
16
+ // Check if we're on Windows
17
+ if (process.platform !== 'win32') {
18
+ return true; // macOS and Linux support Unicode
19
+ }
20
+
21
+ // Check for Windows Terminal (supports Unicode)
22
+ if (process.env.WT_SESSION) {
23
+ return true;
24
+ }
25
+
26
+ // Check for VS Code terminal (supports Unicode)
27
+ if (process.env.TERM_PROGRAM === 'vscode') {
28
+ return true;
29
+ }
30
+
31
+ // Check for ConEmu/Cmder (supports Unicode)
32
+ if (process.env.ConEmuANSI === 'ON') {
33
+ return true;
34
+ }
35
+
36
+ // Check for modern terminal emulators
37
+ if (process.env.TERM && process.env.TERM !== 'dumb') {
38
+ return true;
39
+ }
40
+
41
+ // Check for CI environments (usually support Unicode)
42
+ if (process.env.CI) {
43
+ return true;
44
+ }
45
+
46
+ // Default to ASCII for Windows CMD and older PowerShell
47
+ return false;
48
+ }
49
+
50
+ /**
51
+ * ANSI color codes for terminal output
52
+ * These are special character sequences that tell the terminal to change colors
53
+ */
54
+ const colors = {
55
+ reset: '\x1b[0m', // Reset to default color
56
+ bright: '\x1b[1m', // Make text bright/bold
57
+ dim: '\x1b[2m', // Make text dim
58
+
59
+ // Regular colors
60
+ red: '\x1b[31m',
61
+ green: '\x1b[32m',
62
+ yellow: '\x1b[33m',
63
+ blue: '\x1b[34m',
64
+ magenta: '\x1b[35m',
65
+ cyan: '\x1b[36m',
66
+ white: '\x1b[37m',
67
+ gray: '\x1b[90m',
68
+
69
+ // Background colors (for highlighting)
70
+ bgRed: '\x1b[41m',
71
+ bgGreen: '\x1b[42m',
72
+ bgYellow: '\x1b[43m',
73
+ bgBlue: '\x1b[44m',
74
+ };
75
+
76
+ /**
77
+ * Unicode symbols for modern terminals
78
+ */
79
+ const unicodeSymbols = {
80
+ success: '[OK]',
81
+ error: '[X]',
82
+ info: '[i]',
83
+ warning: '[!]',
84
+ arrow: '[->]',
85
+ bullet: '[•]',
86
+ star: '[★]',
87
+ };
88
+
89
+ /**
90
+ * ASCII fallback symbols for older Windows terminals (CMD, older PowerShell)
91
+ */
92
+ const asciiSymbols = {
93
+ success: '[OK]',
94
+ error: '[X]',
95
+ info: '[i]',
96
+ warning: '[!]',
97
+ arrow: '[->]',
98
+ bullet: '•',
99
+ star: '[*]',
100
+ };
101
+
102
+ /**
103
+ * Pretty symbols for different message types
104
+ * Automatically uses ASCII fallbacks on older Windows terminals
105
+ */
106
+ const symbols = supportsUnicode() ? unicodeSymbols : asciiSymbols;
107
+
108
+ /**
109
+ * Show a success message (green with checkmark)
110
+ * Use this when something completes successfully
111
+ *
112
+ * @param message - The message to display
113
+ * @example
114
+ * success('Project created successfully!')
115
+ */
116
+ export function success(message: string): void {
117
+ console.log(`${colors.green}${symbols.success}${colors.reset} ${message}`);
118
+ }
119
+
120
+ /**
121
+ * Show an error message (red with X)
122
+ * Use this when something goes wrong
123
+ *
124
+ * @param message - The error message to display
125
+ * @example
126
+ * error('Failed to download file')
127
+ */
128
+ export function error(message: string): void {
129
+ console.log(`${colors.red}${symbols.error}${colors.reset} ${message}`);
130
+ }
131
+
132
+ /**
133
+ * Show an info message (blue with info symbol)
134
+ * Use this for general information
135
+ *
136
+ * @param message - The info message to display
137
+ * @example
138
+ * info('Downloading templates...')
139
+ */
140
+ export function info(message: string): void {
141
+ console.log(`${colors.blue}${symbols.info}${colors.reset} ${message}`);
142
+ }
143
+
144
+ /**
145
+ * Show a warning message (yellow with warning symbol)
146
+ * Use this for warnings that aren't errors
147
+ *
148
+ * @param message - The warning message to display
149
+ * @example
150
+ * warning('This will overwrite existing files')
151
+ */
152
+ export function warning(message: string): void {
153
+ console.log(`${colors.yellow}${symbols.warning}${colors.reset} ${message}`);
154
+ }
155
+
156
+ /**
157
+ * Show a message with an arrow
158
+ * Useful for showing steps or progress
159
+ *
160
+ * @param message - The message to display
161
+ * @example
162
+ * step('Installing dependencies...')
163
+ */
164
+ export function step(message: string): void {
165
+ console.log(`${colors.cyan}${symbols.arrow}${colors.reset} ${message}`);
166
+ }
167
+
168
+ /**
169
+ * Return a highlighted string (bold and bright)
170
+ * Use this for important text that needs attention
171
+ *
172
+ * @param message - The message to highlight
173
+ * @returns Formatted string with ANSI codes
174
+ * @example
175
+ * console.log(`Visit ${highlight('http://localhost:4000')}`);
176
+ */
177
+ export function highlight(message: string): string {
178
+ return `${colors.bright}${message}${colors.reset}`;
179
+ }
180
+
181
+ /**
182
+ * Show a dimmed message (gray and dim)
183
+ * Use this for less important information
184
+ *
185
+ * @param message - The message to dim
186
+ * @example
187
+ * dim('You can skip this step if you want')
188
+ */
189
+ export function dim(message: string): void {
190
+ console.log(`${colors.gray}${colors.dim}${message}${colors.reset}`);
191
+ }
192
+
193
+ /**
194
+ * Show a message with a bullet point
195
+ * Useful for lists
196
+ *
197
+ * @param message - The message to display
198
+ * @example
199
+ * bullet('CORS middleware')
200
+ */
201
+ export function bullet(message: string): void {
202
+ console.log(` ${colors.gray}${symbols.bullet}${colors.reset} ${message}`);
203
+ }
204
+
205
+ /**
206
+ * Print a blank line
207
+ * Helps with spacing and readability
208
+ */
209
+ export function newline(): void {
210
+ console.log();
211
+ }
212
+
213
+ /**
214
+ * Get the line character based on terminal support
215
+ */
216
+ const lineChar = supportsUnicode() ? '─' : '-';
217
+
218
+ /**
219
+ * Print a horizontal line separator
220
+ * Use this to separate sections
221
+ *
222
+ * @example
223
+ * separator()
224
+ */
225
+ export function separator(): void {
226
+ console.log(colors.gray + lineChar.repeat(50) + colors.reset);
227
+ }
228
+
229
+ /**
230
+ * Print a header with a title
231
+ * Makes sections stand out
232
+ *
233
+ * @param title - The header title
234
+ * @example
235
+ * header('Available Middleware')
236
+ */
237
+ export function header(title: string): void {
238
+ newline();
239
+ console.log(`${colors.bright}${colors.cyan}${title}${colors.reset}`);
240
+ console.log(colors.gray + lineChar.repeat(title.length) + colors.reset);
241
+ newline();
242
+ }
243
+
244
+ /**
245
+ * Show a command that the user can run
246
+ * Displays it in a nice format
247
+ *
248
+ * @param command - The command to display
249
+ * @example
250
+ * command('bun install')
251
+ */
252
+ export function command(command: string): void {
253
+ console.log(
254
+ ` ${colors.dim}$${colors.reset} ${colors.cyan}${command}${colors.reset}`
255
+ );
256
+ }
257
+
258
+ /**
259
+ * Show code or file content
260
+ * Displays it in a monospace-looking format
261
+ *
262
+ * @param code - The code to display
263
+ * @example
264
+ * code('import { Burger } from "burger-api"')
265
+ */
266
+ export function code(code: string): void {
267
+ console.log(` ${colors.gray}${code}${colors.reset}`);
268
+ }
269
+
270
+ /**
271
+ * Spinner frames - Unicode for modern terminals, ASCII for CMD
272
+ */
273
+ const spinnerFrames = supportsUnicode()
274
+ ? ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
275
+ : ['|', '/', '-', '\\'];
276
+
277
+ /**
278
+ * Simple spinner class for showing progress
279
+ * Shows an animated spinner while something is loading
280
+ *
281
+ * @example
282
+ * const spin = spinner('Downloading...');
283
+ * // do some work
284
+ * spin.stop('Done!');
285
+ */
286
+ export class Spinner {
287
+ private frames = spinnerFrames;
288
+ private currentFrame = 0;
289
+ private intervalId: Timer | null = null;
290
+ private message: string;
291
+
292
+ constructor(message: string) {
293
+ this.message = message;
294
+ this.start();
295
+ }
296
+
297
+ /**
298
+ * Start the spinner animation
299
+ */
300
+ private start(): void {
301
+ // Hide cursor
302
+ process.stdout.write('\x1B[?25l');
303
+
304
+ // Show first frame
305
+ this.render();
306
+
307
+ // Update every 80ms for smooth animation
308
+ this.intervalId = setInterval(() => {
309
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
310
+ this.render();
311
+ }, 80);
312
+ }
313
+
314
+ /**
315
+ * Render the current frame
316
+ */
317
+ private render(): void {
318
+ // Clear the line and move cursor to beginning
319
+ process.stdout.write('\r\x1B[K');
320
+ // Write the spinner and message
321
+ process.stdout.write(
322
+ `${colors.cyan}${this.frames[this.currentFrame]}${colors.reset} ${
323
+ this.message
324
+ }`
325
+ );
326
+ }
327
+
328
+ /**
329
+ * Update the spinner message
330
+ *
331
+ * @param message - New message to display
332
+ */
333
+ update(message: string): void {
334
+ this.message = message;
335
+ this.render();
336
+ }
337
+
338
+ /**
339
+ * Stop the spinner and show final message
340
+ *
341
+ * @param finalMessage - Optional message to show when done
342
+ * @param isError - Whether this is an error (shows X instead of checkmark)
343
+ */
344
+ stop(finalMessage?: string, isError = false): void {
345
+ if (this.intervalId) {
346
+ clearInterval(this.intervalId);
347
+ this.intervalId = null;
348
+ }
349
+
350
+ // Clear the spinner line
351
+ process.stdout.write('\r\x1B[K');
352
+
353
+ // Show cursor again
354
+ process.stdout.write('\x1B[?25h');
355
+
356
+ // Show final message if provided
357
+ if (finalMessage) {
358
+ if (isError) {
359
+ error(finalMessage);
360
+ } else {
361
+ success(finalMessage);
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Create and return a new spinner
369
+ * This is a helper function to make it easier to use
370
+ *
371
+ * @param message - The message to display while spinning
372
+ * @returns A Spinner instance
373
+ * @example
374
+ * const spin = spinner('Loading...');
375
+ * await doSomething();
376
+ * spin.stop('Done!');
377
+ */
378
+ export function spinner(message: string): Spinner {
379
+ return new Spinner(message);
380
+ }
381
+
382
+ /**
383
+ * Format a file size in a human-readable way
384
+ * Converts bytes to KB, MB, etc.
385
+ *
386
+ * @param bytes - Size in bytes
387
+ * @returns Formatted string like "1.5 MB"
388
+ * @example
389
+ * formatSize(1500000) // "1.43 MB"
390
+ */
391
+ export function formatSize(bytes: number): string {
392
+ if (bytes < 1024) return bytes + ' B';
393
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
394
+ if (bytes < 1024 * 1024 * 1024)
395
+ return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
396
+ return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
397
+ }
398
+
399
+ const LOGO_TEXT = `
400
+ ██╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ███████╗██████╗ █████╗ ██████╗ ██╗
401
+ ╚██╗ ██╔══██╗██║ ██║██╔══██╗██╔════╝ ██╔════╝██╔══██╗██╔══██╗██╔══██╗██║
402
+ ╚██╗ ██████╦╝██║ ██║██████╔╝██║ ██╗ █████╗ ██████╔╝███████║██████╔╝██║
403
+ ██╔╝ ██╔══██╗██║ ██║██╔══██╗██║ ╚██╗██╔══╝ ██╔══██╗██╔══██║██╔═══╝ ██║
404
+ ██╔╝ ██████╦╝╚██████╔╝██║ ██║╚██████╔╝███████╗██║ ██║██║ ██║██║ ██║
405
+ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
406
+ CLI tool for BurgerAPI projects - v0.7.0
407
+ `.trim();
408
+
409
+ /**
410
+ * Show ASCII art banner for BurgerAPI CLI
411
+ * Displays when CLI starts
412
+ * Uses ASCII-safe characters for Windows CMD compatibility
413
+ */
414
+ export function showBanner(): void {
415
+ const bannerColor = '\x1b[38;2;255;204;153m'; // Warm orange color
416
+
417
+ const reset = '\x1b[0m';
418
+
419
+ // Unicode banner for modern terminals
420
+ console.log(`${bannerColor}
421
+ ${LOGO_TEXT}
422
+
423
+ ${reset}`);
424
+ }
425
+
426
+ /**
427
+ * Create a simple table for displaying data
428
+ *
429
+ * @param rows - Array of row data
430
+ * @example
431
+ * table([
432
+ * ['Name', 'Version'],
433
+ * ['burger-api', '0.6.6'],
434
+ * ['bun', '1.3.1']
435
+ * ]);
436
+ */
437
+ export function table(rows: string[][]): void {
438
+ if (rows.length === 0) return;
439
+
440
+ // Calculate column widths
441
+ const colWidths: number[] = [];
442
+ for (let col = 0; col < (rows[0]?.length ?? 0); col++) {
443
+ let maxWidth = 0;
444
+ for (const row of rows) {
445
+ if (row[col]?.length && (row[col]?.length ?? 0) > maxWidth) {
446
+ maxWidth = row[col]?.length ?? 0;
447
+ }
448
+ }
449
+ colWidths.push(maxWidth + 2); // Add padding
450
+ }
451
+
452
+ // Print header (first row) with different styling
453
+ const header = rows[0];
454
+ let headerStr = '';
455
+ for (let i = 0; i < (header?.length ?? 0); i++) {
456
+ headerStr += `${colors.bright}${(header?.[i] ?? '').padEnd(
457
+ colWidths[i] ?? 0
458
+ )}${colors.reset}`;
459
+ }
460
+ console.log(headerStr);
461
+
462
+ // Print separator
463
+ console.log(
464
+ colors.gray +
465
+ lineChar.repeat(colWidths.reduce((a, b) => a + b, 0)) +
466
+ colors.reset
467
+ );
468
+
469
+ // Print data rows
470
+ for (let i = 1; i < rows.length; i++) {
471
+ const row = rows[i];
472
+ let rowStr = '';
473
+ for (let j = 0; j < (row?.length ?? 0); j++) {
474
+ rowStr += (row?.[j] ?? '').padEnd(colWidths[j ?? 0] ?? 0);
475
+ }
476
+ console.log(rowStr);
477
+ }
478
+ }