movehat 0.1.1 → 0.1.2
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/dist/commands/compile.d.ts +13 -0
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +31 -11
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/fork/create.d.ts +20 -1
- package/dist/commands/fork/create.d.ts.map +1 -1
- package/dist/commands/fork/create.js +54 -20
- package/dist/commands/fork/create.js.map +1 -1
- package/dist/commands/fork/list.d.ts +12 -1
- package/dist/commands/fork/list.d.ts.map +1 -1
- package/dist/commands/fork/list.js +50 -22
- package/dist/commands/fork/list.js.map +1 -1
- package/dist/commands/init.d.ts +19 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +64 -29
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts +11 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +44 -18
- package/dist/commands/update.js.map +1 -1
- package/dist/helpers/banner.d.ts +17 -0
- package/dist/helpers/banner.d.ts.map +1 -1
- package/dist/helpers/banner.js +38 -23
- package/dist/helpers/banner.js.map +1 -1
- package/dist/helpers/version-check.d.ts +12 -1
- package/dist/helpers/version-check.d.ts.map +1 -1
- package/dist/helpers/version-check.js +17 -7
- package/dist/helpers/version-check.js.map +1 -1
- package/dist/ui/colors.d.ts +77 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +121 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/formatters.d.ts +171 -0
- package/dist/ui/formatters.d.ts.map +1 -0
- package/dist/ui/formatters.js +186 -0
- package/dist/ui/formatters.js.map +1 -0
- package/dist/ui/index.d.ts +58 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +60 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/logger.d.ts +160 -0
- package/dist/ui/logger.d.ts.map +1 -0
- package/dist/ui/logger.js +206 -0
- package/dist/ui/logger.js.map +1 -0
- package/dist/ui/spinner.d.ts +106 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +120 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/symbols.d.ts +50 -0
- package/dist/ui/symbols.d.ts.map +1 -0
- package/dist/ui/symbols.js +64 -0
- package/dist/ui/symbols.js.map +1 -0
- package/dist/ui/table.d.ts +67 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +143 -0
- package/dist/ui/table.js.map +1 -0
- package/package.json +8 -1
- package/src/commands/compile.ts +32 -11
- package/src/commands/fork/create.ts +59 -20
- package/src/commands/fork/list.ts +52 -22
- package/src/commands/init.ts +111 -74
- package/src/commands/update.ts +52 -19
- package/src/helpers/banner.ts +45 -29
- package/src/helpers/version-check.ts +20 -8
- package/src/ui/colors.ts +141 -0
- package/src/ui/formatters.ts +246 -0
- package/src/ui/index.ts +62 -0
- package/src/ui/logger.ts +226 -0
- package/src/ui/spinner.ts +171 -0
- package/src/ui/symbols.ts +74 -0
- package/src/ui/table.ts +191 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { colors } from './colors.js';
|
|
2
|
+
import { symbols } from './symbols.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Border color options for boxes
|
|
6
|
+
*/
|
|
7
|
+
export type BorderColor = 'dim' | 'primary' | 'success' | 'error' | 'warning';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Box formatting options
|
|
11
|
+
*/
|
|
12
|
+
export interface BoxOptions {
|
|
13
|
+
/** Padding inside the box (default: 1) */
|
|
14
|
+
padding?: number;
|
|
15
|
+
/** Margin outside the box (default: 0) */
|
|
16
|
+
margin?: number;
|
|
17
|
+
/** Border color (default: 'dim') */
|
|
18
|
+
borderColor?: BorderColor;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a box around text with Unicode borders
|
|
23
|
+
*
|
|
24
|
+
* @param content - Text content to box (supports multiline)
|
|
25
|
+
* @param options - Box formatting options
|
|
26
|
+
* @returns Formatted box string
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const message = box('Update available: 1.0.0 → 1.1.0');
|
|
30
|
+
* console.log(message);
|
|
31
|
+
* // ┌────────────────────────────────┐
|
|
32
|
+
* // │ Update available: 1.0.0 → 1.1.0│
|
|
33
|
+
* // └────────────────────────────────┘
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const warning = box(
|
|
37
|
+
* 'This feature is deprecated\nUse the new API instead',
|
|
38
|
+
* { borderColor: 'warning', padding: 2 }
|
|
39
|
+
* );
|
|
40
|
+
*/
|
|
41
|
+
export const box = (
|
|
42
|
+
content: string,
|
|
43
|
+
options: BoxOptions = {}
|
|
44
|
+
): string => {
|
|
45
|
+
const { padding = 1, margin = 0, borderColor = 'dim' } = options;
|
|
46
|
+
|
|
47
|
+
const lines = content.split('\n');
|
|
48
|
+
const maxWidth = Math.max(...lines.map(l => l.length));
|
|
49
|
+
const innerWidth = maxWidth + padding * 2;
|
|
50
|
+
|
|
51
|
+
const marginStr = ' '.repeat(margin);
|
|
52
|
+
const paddingStr = ' '.repeat(padding);
|
|
53
|
+
|
|
54
|
+
const colorFn = colors[borderColor] || colors.dim;
|
|
55
|
+
|
|
56
|
+
const top = marginStr + colorFn(symbols.boxTopLeft + symbols.boxTop.repeat(innerWidth) + symbols.boxTopRight);
|
|
57
|
+
const bottom = marginStr + colorFn(symbols.boxBottomLeft + symbols.boxBottom.repeat(innerWidth) + symbols.boxBottomRight);
|
|
58
|
+
|
|
59
|
+
const middle = lines.map(line => {
|
|
60
|
+
const padded = line.padEnd(maxWidth);
|
|
61
|
+
return marginStr + colorFn(symbols.boxLeft) + paddingStr + padded + paddingStr + colorFn(symbols.boxRight);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return [top, ...middle, bottom].join('\n');
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* List formatting options
|
|
69
|
+
*/
|
|
70
|
+
export interface ListOptions {
|
|
71
|
+
/** Number of spaces to indent (default: 0) */
|
|
72
|
+
indent?: number;
|
|
73
|
+
/** Starting number for numbered lists (default: 1) */
|
|
74
|
+
startFrom?: number;
|
|
75
|
+
/** Custom symbol for bullet lists */
|
|
76
|
+
symbol?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a numbered list
|
|
81
|
+
*
|
|
82
|
+
* @param items - Array of items to list
|
|
83
|
+
* @param options - List formatting options
|
|
84
|
+
* @returns Formatted numbered list string
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* const steps = numberedList([
|
|
88
|
+
* 'cd my-project',
|
|
89
|
+
* 'npm install',
|
|
90
|
+
* 'npm test'
|
|
91
|
+
* ]);
|
|
92
|
+
* console.log(steps);
|
|
93
|
+
* // 1. cd my-project
|
|
94
|
+
* // 2. npm install
|
|
95
|
+
* // 3. npm test
|
|
96
|
+
*/
|
|
97
|
+
export const numberedList = (
|
|
98
|
+
items: string[],
|
|
99
|
+
options: ListOptions = {}
|
|
100
|
+
): string => {
|
|
101
|
+
const { indent = 0, startFrom = 1 } = options;
|
|
102
|
+
const prefix = ' '.repeat(indent);
|
|
103
|
+
|
|
104
|
+
return items
|
|
105
|
+
.map((item, idx) => `${prefix}${startFrom + idx}. ${item}`)
|
|
106
|
+
.join('\n');
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create a bulleted list
|
|
111
|
+
*
|
|
112
|
+
* @param items - Array of items to list
|
|
113
|
+
* @param options - List formatting options
|
|
114
|
+
* @returns Formatted bulleted list string
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* const features = bulletList([
|
|
118
|
+
* 'Feature A',
|
|
119
|
+
* 'Feature B',
|
|
120
|
+
* 'Feature C'
|
|
121
|
+
* ]);
|
|
122
|
+
* console.log(features);
|
|
123
|
+
* // • Feature A
|
|
124
|
+
* // • Feature B
|
|
125
|
+
* // • Feature C
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const tasks = bulletList(
|
|
129
|
+
* ['Task 1', 'Task 2'],
|
|
130
|
+
* { indent: 2, symbol: '→' }
|
|
131
|
+
* );
|
|
132
|
+
*/
|
|
133
|
+
export const bulletList = (
|
|
134
|
+
items: string[],
|
|
135
|
+
options: ListOptions = {}
|
|
136
|
+
): string => {
|
|
137
|
+
const { indent = 0, symbol = symbols.bullet } = options;
|
|
138
|
+
const prefix = ' '.repeat(indent);
|
|
139
|
+
|
|
140
|
+
return items
|
|
141
|
+
.map(item => `${prefix}${symbol} ${item}`)
|
|
142
|
+
.join('\n');
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Indent text block by a specified number of spaces
|
|
147
|
+
*
|
|
148
|
+
* @param text - Text to indent (supports multiline)
|
|
149
|
+
* @param spaces - Number of spaces to indent
|
|
150
|
+
* @returns Indented text
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* const code = 'function test() {\n return true;\n}';
|
|
154
|
+
* console.log(indent(code, 4));
|
|
155
|
+
* // function test() {
|
|
156
|
+
* // return true;
|
|
157
|
+
* // }
|
|
158
|
+
*/
|
|
159
|
+
export const indent = (text: string, spaces: number): string => {
|
|
160
|
+
const prefix = ' '.repeat(spaces);
|
|
161
|
+
return text.split('\n').map(line => prefix + line).join('\n');
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a horizontal divider line
|
|
166
|
+
*
|
|
167
|
+
* @param width - Width of the divider (default: 60)
|
|
168
|
+
* @param char - Character to use (default: symbols.line)
|
|
169
|
+
* @returns Formatted divider string
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* console.log(divider());
|
|
173
|
+
* // ────────────────────────────────────────────────────────────
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* console.log(divider(40, '='));
|
|
177
|
+
* // ========================================
|
|
178
|
+
*/
|
|
179
|
+
export const divider = (
|
|
180
|
+
width: number = 60,
|
|
181
|
+
char: string = symbols.line
|
|
182
|
+
): string => {
|
|
183
|
+
return colors.dim(char.repeat(width));
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Format file path with dimmed parent directories
|
|
188
|
+
* Highlights the filename while dimming the directory path
|
|
189
|
+
*
|
|
190
|
+
* @param filePath - Full file path
|
|
191
|
+
* @returns Formatted path string
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* console.log(formatPath('/Users/name/project/src/file.ts'));
|
|
195
|
+
* // /Users/name/project/src/file.ts
|
|
196
|
+
* // (where the directory is dimmed and filename is bright)
|
|
197
|
+
*/
|
|
198
|
+
export const formatPath = (filePath: string): string => {
|
|
199
|
+
const parts = filePath.split('/');
|
|
200
|
+
const fileName = parts.pop() || '';
|
|
201
|
+
const dirPath = parts.join('/');
|
|
202
|
+
|
|
203
|
+
return colors.dim(dirPath + '/') + fileName;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create a section header with divider
|
|
208
|
+
* Combines a bold title with a horizontal line
|
|
209
|
+
*
|
|
210
|
+
* @param title - Section title
|
|
211
|
+
* @param options - Formatting options
|
|
212
|
+
* @returns Formatted section header
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* console.log(sectionHeader('Fork Details'));
|
|
216
|
+
* //
|
|
217
|
+
* // Fork Details
|
|
218
|
+
* // ────────────────────────────────────────────────────────────
|
|
219
|
+
*/
|
|
220
|
+
export const sectionHeader = (
|
|
221
|
+
title: string,
|
|
222
|
+
options: { width?: number; char?: string } = {}
|
|
223
|
+
): string => {
|
|
224
|
+
const { width = 60, char = symbols.line } = options;
|
|
225
|
+
return `\n${colors.brandBright(title)}\n${divider(width, char)}`;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Format a command for display with dimmed prompt
|
|
230
|
+
* Useful for showing example commands to users
|
|
231
|
+
*
|
|
232
|
+
* @param command - Command string
|
|
233
|
+
* @returns Formatted command with $ prompt
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* console.log(formatCommand('movehat compile'));
|
|
237
|
+
* // $ movehat compile
|
|
238
|
+
* // (where $ is dimmed)
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* console.log(formatCommand('npm install'));
|
|
242
|
+
* // $ npm install
|
|
243
|
+
*/
|
|
244
|
+
export const formatCommand = (command: string): string => {
|
|
245
|
+
return `${colors.dim('$')} ${command}`;
|
|
246
|
+
};
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Movehat UI Module
|
|
3
|
+
*
|
|
4
|
+
* Provides a comprehensive set of UI utilities for the CLI including:
|
|
5
|
+
* - Colors and gradients (picocolors wrapper)
|
|
6
|
+
* - Cross-platform symbols (figures wrapper)
|
|
7
|
+
* - Structured logging system
|
|
8
|
+
* - Spinners for async operations (ora wrapper)
|
|
9
|
+
* - Table formatting (cli-table3 wrapper)
|
|
10
|
+
* - Text formatting utilities
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Named imports (recommended)
|
|
14
|
+
* import { logger, spinner, createTable } from './ui/index.js';
|
|
15
|
+
*
|
|
16
|
+
* logger.info('Starting compilation...');
|
|
17
|
+
* const spin = spinner({ text: 'Compiling...' });
|
|
18
|
+
* // ... async work ...
|
|
19
|
+
* spin.succeed('Compilation complete!');
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Namespace import
|
|
23
|
+
* import { ui } from './ui/index.js';
|
|
24
|
+
*
|
|
25
|
+
* ui.logger.info('Starting...');
|
|
26
|
+
* ui.logger.success('Done!');
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// Re-export everything from submodules for named imports
|
|
30
|
+
export * from './colors.js';
|
|
31
|
+
export * from './symbols.js';
|
|
32
|
+
export * from './logger.js';
|
|
33
|
+
export * from './spinner.js';
|
|
34
|
+
export * from './table.js';
|
|
35
|
+
export * from './formatters.js';
|
|
36
|
+
|
|
37
|
+
// Also export as namespace for convenience
|
|
38
|
+
import * as _colors from './colors.js';
|
|
39
|
+
import * as _symbols from './symbols.js';
|
|
40
|
+
import * as _logger from './logger.js';
|
|
41
|
+
import * as _spinner from './spinner.js';
|
|
42
|
+
import * as _table from './table.js';
|
|
43
|
+
import * as _formatters from './formatters.js';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Unified UI namespace
|
|
47
|
+
* Provides all UI utilities in a single namespace object
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* import { ui } from './ui/index.js';
|
|
51
|
+
*
|
|
52
|
+
* ui.logger.info('Processing...');
|
|
53
|
+
* ui.logger.success('Done!');
|
|
54
|
+
*/
|
|
55
|
+
export const ui = {
|
|
56
|
+
colors: _colors,
|
|
57
|
+
symbols: _symbols,
|
|
58
|
+
logger: _logger,
|
|
59
|
+
spinner: _spinner,
|
|
60
|
+
table: _table,
|
|
61
|
+
formatters: _formatters,
|
|
62
|
+
};
|
package/src/ui/logger.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { colors } from './colors.js';
|
|
2
|
+
import { coloredSymbol, symbols } from './symbols.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Available log levels
|
|
6
|
+
*/
|
|
7
|
+
export type LogLevel = 'info' | 'success' | 'error' | 'warning' | 'debug';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Logger configuration options
|
|
11
|
+
*/
|
|
12
|
+
export interface LoggerConfig {
|
|
13
|
+
/** Suppress all output when true */
|
|
14
|
+
silent?: boolean;
|
|
15
|
+
/** Minimum log level to display */
|
|
16
|
+
level?: LogLevel;
|
|
17
|
+
/** Include timestamps in log messages */
|
|
18
|
+
timestamp?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Internal logger state
|
|
23
|
+
*/
|
|
24
|
+
let config: LoggerConfig = {
|
|
25
|
+
silent: false,
|
|
26
|
+
level: 'info',
|
|
27
|
+
timestamp: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Configure logger globally
|
|
32
|
+
*
|
|
33
|
+
* @param newConfig - Partial configuration to merge with current config
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Silence all logs for testing
|
|
37
|
+
* configureLogger({ silent: true });
|
|
38
|
+
*
|
|
39
|
+
* // Enable timestamps
|
|
40
|
+
* configureLogger({ timestamp: true });
|
|
41
|
+
*/
|
|
42
|
+
export const configureLogger = (newConfig: Partial<LoggerConfig>): void => {
|
|
43
|
+
config = { ...config, ...newConfig };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format message with optional indentation
|
|
48
|
+
*/
|
|
49
|
+
const formatMessage = (message: string, indent: number = 0): string => {
|
|
50
|
+
const prefix = ' '.repeat(indent);
|
|
51
|
+
return message.split('\n').map(line => prefix + line).join('\n');
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Info message (cyan)
|
|
56
|
+
* Use for general information and status updates
|
|
57
|
+
*
|
|
58
|
+
* @param message - Message to log
|
|
59
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* logger.info('Starting compilation...');
|
|
63
|
+
* logger.info('Network: testnet', 2);
|
|
64
|
+
*/
|
|
65
|
+
export const info = (message: string, indent: number = 0): void => {
|
|
66
|
+
if (config.silent) return;
|
|
67
|
+
const formatted = formatMessage(message, indent);
|
|
68
|
+
console.log(`${coloredSymbol('info')} ${formatted}`);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Success message (green)
|
|
73
|
+
* Use for completed operations and positive outcomes
|
|
74
|
+
*
|
|
75
|
+
* @param message - Message to log
|
|
76
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* logger.success('Compilation finished!');
|
|
80
|
+
* logger.success('All tests passed', 2);
|
|
81
|
+
*/
|
|
82
|
+
export const success = (message: string, indent: number = 0): void => {
|
|
83
|
+
if (config.silent) return;
|
|
84
|
+
const formatted = formatMessage(message, indent);
|
|
85
|
+
console.log(`${coloredSymbol('success')} ${formatted}`);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Error message (red)
|
|
90
|
+
* Use for errors and failures
|
|
91
|
+
*
|
|
92
|
+
* @param message - Message to log
|
|
93
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* logger.error('Compilation failed');
|
|
97
|
+
* logger.error('File not found: config.ts', 2);
|
|
98
|
+
*/
|
|
99
|
+
export const error = (message: string, indent: number = 0): void => {
|
|
100
|
+
if (config.silent) return;
|
|
101
|
+
const formatted = formatMessage(message, indent);
|
|
102
|
+
console.error(`${coloredSymbol('error')} ${formatted}`);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Warning message (yellow)
|
|
107
|
+
* Use for warnings and deprecated features
|
|
108
|
+
*
|
|
109
|
+
* @param message - Message to log
|
|
110
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* logger.warning('Deprecated API used');
|
|
114
|
+
* logger.warning('This feature will be removed in v2.0', 2);
|
|
115
|
+
*/
|
|
116
|
+
export const warning = (message: string, indent: number = 0): void => {
|
|
117
|
+
if (config.silent) return;
|
|
118
|
+
const formatted = formatMessage(message, indent);
|
|
119
|
+
console.warn(`${coloredSymbol('warning')} ${formatted}`);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Plain message without symbol
|
|
124
|
+
* Use for continuation lines or when symbol is not appropriate
|
|
125
|
+
*
|
|
126
|
+
* @param message - Message to log
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* logger.info('Fork Details:');
|
|
130
|
+
* logger.plain(' Chain ID: 126');
|
|
131
|
+
* logger.plain(' Network: testnet');
|
|
132
|
+
*/
|
|
133
|
+
export const plain = (message: string): void => {
|
|
134
|
+
if (config.silent) return;
|
|
135
|
+
console.log(message);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Empty line
|
|
140
|
+
* Use for visual spacing between sections
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* logger.success('Build complete');
|
|
144
|
+
* logger.newline();
|
|
145
|
+
* logger.info('Next steps:');
|
|
146
|
+
*/
|
|
147
|
+
export const newline = (): void => {
|
|
148
|
+
if (config.silent) return;
|
|
149
|
+
console.log();
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Section header (bold, brand color)
|
|
154
|
+
* Use for major section dividers
|
|
155
|
+
*
|
|
156
|
+
* @param title - Section title
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* logger.section('Fork Details');
|
|
160
|
+
* logger.kv('Chain ID', '126', 2);
|
|
161
|
+
* logger.kv('Network', 'testnet', 2);
|
|
162
|
+
*/
|
|
163
|
+
export const section = (title: string): void => {
|
|
164
|
+
if (config.silent) return;
|
|
165
|
+
console.log(`\n${colors.brandBright(title)}`);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Key-value pair
|
|
170
|
+
* Use for displaying structured data
|
|
171
|
+
*
|
|
172
|
+
* @param key - The key/label
|
|
173
|
+
* @param value - The value
|
|
174
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* logger.kv('Network', 'testnet', 2);
|
|
178
|
+
* logger.kv('Chain ID', '126', 2);
|
|
179
|
+
*/
|
|
180
|
+
export const kv = (key: string, value: string, indent: number = 0): void => {
|
|
181
|
+
if (config.silent) return;
|
|
182
|
+
const prefix = ' '.repeat(indent);
|
|
183
|
+
console.log(`${prefix}${colors.dim(key)}: ${value}`);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* List item with bullet
|
|
188
|
+
* Use for lists and enumerated items
|
|
189
|
+
*
|
|
190
|
+
* @param text - Item text
|
|
191
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* logger.info('Next steps:');
|
|
195
|
+
* logger.item('cd my-project', 2);
|
|
196
|
+
* logger.item('npm install', 2);
|
|
197
|
+
* logger.item('npm test', 2);
|
|
198
|
+
*/
|
|
199
|
+
export const item = (text: string, indent: number = 0): void => {
|
|
200
|
+
if (config.silent) return;
|
|
201
|
+
const prefix = ' '.repeat(indent);
|
|
202
|
+
console.log(`${prefix}${symbols.bullet} ${text}`);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Logger namespace export
|
|
207
|
+
* Provides all logging functions in a single namespace
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* import { logger } from './ui/index.js';
|
|
211
|
+
*
|
|
212
|
+
* logger.info('Starting...');
|
|
213
|
+
* logger.success('Done!');
|
|
214
|
+
*/
|
|
215
|
+
export const logger = {
|
|
216
|
+
configure: configureLogger,
|
|
217
|
+
info,
|
|
218
|
+
success,
|
|
219
|
+
error,
|
|
220
|
+
warning,
|
|
221
|
+
plain,
|
|
222
|
+
newline,
|
|
223
|
+
section,
|
|
224
|
+
kv,
|
|
225
|
+
item,
|
|
226
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import ora, { type Ora, type Options as OraOptions } from 'ora';
|
|
2
|
+
import { shouldUseColor } from './colors.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Spinner color options
|
|
6
|
+
*/
|
|
7
|
+
export type SpinnerColor = 'yellow' | 'green' | 'cyan' | 'red' | 'blue' | 'magenta' | 'white' | 'gray';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Spinner configuration options
|
|
11
|
+
*/
|
|
12
|
+
export interface SpinnerOptions {
|
|
13
|
+
/** Text to display next to the spinner */
|
|
14
|
+
text: string;
|
|
15
|
+
/** Spinner color (default: 'yellow' for Movehat brand) */
|
|
16
|
+
color?: SpinnerColor;
|
|
17
|
+
/** Spinner animation type (default: 'dots') */
|
|
18
|
+
spinner?: OraOptions['spinner'];
|
|
19
|
+
/** Number of spaces to indent (default: 0) */
|
|
20
|
+
indent?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create and start a spinner
|
|
25
|
+
* Automatically disabled in non-TTY environments (CI, pipes)
|
|
26
|
+
*
|
|
27
|
+
* @param options - Spinner configuration
|
|
28
|
+
* @returns Ora spinner instance
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const spin = spinner({ text: 'Compiling contracts...' });
|
|
32
|
+
* await longRunningTask();
|
|
33
|
+
* spin.succeed('Compilation complete!');
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const spin = spinner({ text: 'Fetching data...', color: 'cyan' });
|
|
37
|
+
* try {
|
|
38
|
+
* await fetchData();
|
|
39
|
+
* spin.succeed('Data fetched!');
|
|
40
|
+
* } catch (error) {
|
|
41
|
+
* spin.fail('Failed to fetch data');
|
|
42
|
+
* }
|
|
43
|
+
*/
|
|
44
|
+
export const spinner = (options: SpinnerOptions): Ora => {
|
|
45
|
+
const { text, color = 'yellow', spinner = 'dots', indent = 0 } = options;
|
|
46
|
+
|
|
47
|
+
const prefixSpaces = ' '.repeat(indent);
|
|
48
|
+
|
|
49
|
+
const oraOptions: OraOptions = {
|
|
50
|
+
text: prefixSpaces + text,
|
|
51
|
+
color,
|
|
52
|
+
spinner,
|
|
53
|
+
// Disable spinner if not TTY (CI environments, piped output)
|
|
54
|
+
isEnabled: shouldUseColor() && Boolean(process.stdout.isTTY),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return ora(oraOptions).start();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Execute async task with spinner
|
|
62
|
+
* Handles success/error automatically and always cleans up
|
|
63
|
+
*
|
|
64
|
+
* @param startText - Initial spinner text
|
|
65
|
+
* @param task - Async function to execute
|
|
66
|
+
* @param successText - Text to show on success (optional, defaults to startText without '...')
|
|
67
|
+
* @param errorText - Text to show on error (optional, defaults to error message)
|
|
68
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
69
|
+
* @returns Promise resolving to task result
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* const data = await withSpinner(
|
|
73
|
+
* 'Fetching network data...',
|
|
74
|
+
* async () => await fetchData(),
|
|
75
|
+
* 'Data fetched successfully'
|
|
76
|
+
* );
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* await withSpinner(
|
|
80
|
+
* 'Creating fork...',
|
|
81
|
+
* async () => await createFork(),
|
|
82
|
+
* 'Fork created!',
|
|
83
|
+
* 'Failed to create fork'
|
|
84
|
+
* );
|
|
85
|
+
*/
|
|
86
|
+
export const withSpinner = async <T>(
|
|
87
|
+
startText: string,
|
|
88
|
+
task: () => Promise<T>,
|
|
89
|
+
successText?: string,
|
|
90
|
+
errorText?: string,
|
|
91
|
+
indent: number = 0
|
|
92
|
+
): Promise<T> => {
|
|
93
|
+
const spin = spinner({ text: startText, indent });
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const result = await task();
|
|
97
|
+
spin.succeed(successText || startText.replace(/\.\.\.?$/, ''));
|
|
98
|
+
return result;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
101
|
+
spin.fail(errorText || `Failed: ${errMsg}`);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Spinner chain for sequential operations
|
|
108
|
+
* Manages multiple spinners in sequence
|
|
109
|
+
*/
|
|
110
|
+
export interface SpinnerChain {
|
|
111
|
+
/**
|
|
112
|
+
* Add and execute a step in the chain
|
|
113
|
+
*/
|
|
114
|
+
add<T>(text: string, task: () => Promise<T>, indent?: number): Promise<T>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Complete the chain (cleanup)
|
|
118
|
+
*/
|
|
119
|
+
complete(): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create a sequential spinner chain
|
|
124
|
+
* Useful for multi-step processes like initialization
|
|
125
|
+
*
|
|
126
|
+
* @returns SpinnerChain interface
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* const steps = createSpinnerChain();
|
|
130
|
+
*
|
|
131
|
+
* await steps.add('Creating directories...', async () => {
|
|
132
|
+
* await mkdir(projectPath);
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* await steps.add('Copying templates...', async () => {
|
|
136
|
+
* await copyFiles();
|
|
137
|
+
* });
|
|
138
|
+
*
|
|
139
|
+
* await steps.add('Installing dependencies...', async () => {
|
|
140
|
+
* await npmInstall();
|
|
141
|
+
* });
|
|
142
|
+
*
|
|
143
|
+
* steps.complete();
|
|
144
|
+
*/
|
|
145
|
+
export const createSpinnerChain = (): SpinnerChain => {
|
|
146
|
+
let currentSpinner: Ora | null = null;
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
async add<T>(
|
|
150
|
+
text: string,
|
|
151
|
+
task: () => Promise<T>,
|
|
152
|
+
indent: number = 0
|
|
153
|
+
): Promise<T> {
|
|
154
|
+
currentSpinner = spinner({ text, indent });
|
|
155
|
+
try {
|
|
156
|
+
const result = await task();
|
|
157
|
+
currentSpinner.succeed();
|
|
158
|
+
return result;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
currentSpinner.fail();
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
complete() {
|
|
166
|
+
if (currentSpinner) {
|
|
167
|
+
currentSpinner.stop();
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
};
|