@burger-api/cli 0.6.6 → 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/CHANGELOG.md +66 -57
- package/README.md +565 -656
- package/package.json +50 -50
- package/src/commands/add.ts +201 -201
- package/src/commands/build.ts +250 -250
- package/src/commands/create.ts +229 -229
- package/src/commands/list.ts +88 -88
- package/src/commands/serve.ts +100 -100
- package/src/index.ts +59 -59
- package/src/types/index.ts +53 -53
- package/src/utils/github.ts +260 -260
- package/src/utils/logger.ts +478 -478
- package/src/utils/templates.ts +1116 -1120
package/src/utils/logger.ts
CHANGED
|
@@ -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.
|
|
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.1
|
|
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
|
+
}
|