agileflow 2.89.1 → 2.89.3
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 +10 -0
- package/lib/content-sanitizer.js +463 -0
- package/lib/error-codes.js +544 -0
- package/lib/errors.js +336 -5
- package/lib/feedback.js +561 -0
- package/lib/path-resolver.js +396 -0
- package/lib/session-registry.js +461 -0
- package/lib/smart-json-file.js +449 -0
- package/lib/validate.js +165 -11
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +40 -1440
- package/scripts/agileflow-welcome.js +2 -1
- package/scripts/lib/configure-detect.js +383 -0
- package/scripts/lib/configure-features.js +811 -0
- package/scripts/lib/configure-repair.js +314 -0
- package/scripts/lib/configure-utils.js +115 -0
- package/scripts/lib/frontmatter-parser.js +3 -3
- package/scripts/obtain-context.js +417 -113
- package/scripts/ralph-loop.js +1 -1
- package/tools/cli/commands/config.js +3 -3
- package/tools/cli/commands/doctor.js +30 -2
- package/tools/cli/commands/list.js +2 -2
- package/tools/cli/commands/uninstall.js +3 -3
- package/tools/cli/installers/core/installer.js +62 -12
- package/tools/cli/installers/ide/_interface.js +238 -0
- package/tools/cli/installers/ide/codex.js +2 -2
- package/tools/cli/installers/ide/manager.js +15 -0
- package/tools/cli/lib/content-injector.js +69 -16
- package/tools/cli/lib/ide-errors.js +163 -29
package/lib/feedback.js
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feedback.js - Unified Progress Feedback System
|
|
3
|
+
*
|
|
4
|
+
* Consolidates all progress/feedback patterns:
|
|
5
|
+
* - Status messages (success, error, warning, info)
|
|
6
|
+
* - Spinners for indeterminate progress
|
|
7
|
+
* - Progress bars for determinate progress
|
|
8
|
+
* - Task tracking for multi-step operations
|
|
9
|
+
* - Consistent formatting across all output
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - TTY detection (graceful degradation for non-interactive)
|
|
13
|
+
* - Doherty Threshold timing (operations <400ms feel instant)
|
|
14
|
+
* - Nested task support with indentation
|
|
15
|
+
* - Consistent symbol and color usage
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* const { feedback } = require('./feedback');
|
|
19
|
+
*
|
|
20
|
+
* // Simple messages
|
|
21
|
+
* feedback.success('Task completed');
|
|
22
|
+
* feedback.error('Something went wrong');
|
|
23
|
+
*
|
|
24
|
+
* // With spinner
|
|
25
|
+
* const spinner = feedback.spinner('Processing...');
|
|
26
|
+
* await doWork();
|
|
27
|
+
* spinner.succeed('Done!');
|
|
28
|
+
*
|
|
29
|
+
* // Multi-step operations
|
|
30
|
+
* const task = feedback.task('Installing', 3);
|
|
31
|
+
* task.step('Copying files');
|
|
32
|
+
* task.step('Setting permissions');
|
|
33
|
+
* task.complete('Installed successfully');
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const { c, BRAND_HEX } = require('./colors');
|
|
37
|
+
const chalk = require('chalk');
|
|
38
|
+
|
|
39
|
+
// Symbols for consistent output
|
|
40
|
+
const SYMBOLS = {
|
|
41
|
+
success: '✓',
|
|
42
|
+
error: '✗',
|
|
43
|
+
warning: '⚠',
|
|
44
|
+
info: 'ℹ',
|
|
45
|
+
bullet: '•',
|
|
46
|
+
arrow: '→',
|
|
47
|
+
pending: '○',
|
|
48
|
+
active: '●',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Braille spinner characters (smooth animation)
|
|
52
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
53
|
+
|
|
54
|
+
// Doherty Threshold - operations faster than this feel instant
|
|
55
|
+
const DOHERTY_THRESHOLD_MS = 400;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Format duration for display
|
|
59
|
+
* @param {number} ms - Duration in milliseconds
|
|
60
|
+
* @returns {string} Formatted duration
|
|
61
|
+
*/
|
|
62
|
+
function formatDuration(ms) {
|
|
63
|
+
if (ms < 1000) return `${ms}ms`;
|
|
64
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Unified Feedback class
|
|
69
|
+
* Provides consistent progress and status output
|
|
70
|
+
*/
|
|
71
|
+
class Feedback {
|
|
72
|
+
constructor(options = {}) {
|
|
73
|
+
this.isTTY = options.isTTY !== undefined ? options.isTTY : process.stdout.isTTY;
|
|
74
|
+
this.indent = options.indent || 0;
|
|
75
|
+
this.quiet = options.quiet || false;
|
|
76
|
+
this.verbose = options.verbose || false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get indentation string
|
|
81
|
+
* @returns {string}
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
_indent() {
|
|
85
|
+
return ' '.repeat(this.indent);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Print a message (respects quiet mode)
|
|
90
|
+
* @param {string} symbol - Symbol to prefix
|
|
91
|
+
* @param {string} message - Message text
|
|
92
|
+
* @param {string} color - Color to apply
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
_print(symbol, message, color = '') {
|
|
96
|
+
if (this.quiet) return;
|
|
97
|
+
const prefix = this._indent();
|
|
98
|
+
console.log(`${prefix}${color}${symbol}${c.reset} ${message}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Print success message
|
|
103
|
+
* @param {string} message - Success message
|
|
104
|
+
* @returns {Feedback} this for chaining
|
|
105
|
+
*/
|
|
106
|
+
success(message) {
|
|
107
|
+
this._print(SYMBOLS.success, message, c.green);
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Print error message
|
|
113
|
+
* @param {string} message - Error message
|
|
114
|
+
* @returns {Feedback} this for chaining
|
|
115
|
+
*/
|
|
116
|
+
error(message) {
|
|
117
|
+
this._print(SYMBOLS.error, message, c.red);
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Print warning message
|
|
123
|
+
* @param {string} message - Warning message
|
|
124
|
+
* @returns {Feedback} this for chaining
|
|
125
|
+
*/
|
|
126
|
+
warning(message) {
|
|
127
|
+
this._print(SYMBOLS.warning, message, c.yellow);
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Print info message
|
|
133
|
+
* @param {string} message - Info message
|
|
134
|
+
* @returns {Feedback} this for chaining
|
|
135
|
+
*/
|
|
136
|
+
info(message) {
|
|
137
|
+
this._print(SYMBOLS.info, message, c.blue);
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Print dim/subtle message
|
|
143
|
+
* @param {string} message - Message
|
|
144
|
+
* @returns {Feedback} this for chaining
|
|
145
|
+
*/
|
|
146
|
+
dim(message) {
|
|
147
|
+
if (this.quiet) return this;
|
|
148
|
+
const prefix = this._indent();
|
|
149
|
+
console.log(`${prefix}${c.dim}${message}${c.reset}`);
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Print verbose message (only in verbose mode)
|
|
155
|
+
* @param {string} message - Message
|
|
156
|
+
* @returns {Feedback} this for chaining
|
|
157
|
+
*/
|
|
158
|
+
debug(message) {
|
|
159
|
+
if (!this.verbose) return this;
|
|
160
|
+
this._print(SYMBOLS.bullet, message, c.dim);
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Print bullet point
|
|
166
|
+
* @param {string} message - Message
|
|
167
|
+
* @returns {Feedback} this for chaining
|
|
168
|
+
*/
|
|
169
|
+
bullet(message) {
|
|
170
|
+
this._print(SYMBOLS.bullet, message, c.dim);
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Print branded message (uses brand color)
|
|
176
|
+
* @param {string} message - Message
|
|
177
|
+
* @returns {Feedback} this for chaining
|
|
178
|
+
*/
|
|
179
|
+
brand(message) {
|
|
180
|
+
if (this.quiet) return this;
|
|
181
|
+
const prefix = this._indent();
|
|
182
|
+
console.log(`${prefix}${chalk.hex(BRAND_HEX)(message)}`);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Print empty line
|
|
188
|
+
* @returns {Feedback} this for chaining
|
|
189
|
+
*/
|
|
190
|
+
newline() {
|
|
191
|
+
if (this.quiet) return this;
|
|
192
|
+
console.log();
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Create a new spinner
|
|
198
|
+
* @param {string} message - Spinner message
|
|
199
|
+
* @param {Object} [options={}] - Spinner options
|
|
200
|
+
* @returns {FeedbackSpinner}
|
|
201
|
+
*/
|
|
202
|
+
spinner(message, options = {}) {
|
|
203
|
+
return new FeedbackSpinner(message, {
|
|
204
|
+
...options,
|
|
205
|
+
isTTY: this.isTTY,
|
|
206
|
+
indent: this.indent,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create a task tracker for multi-step operations
|
|
212
|
+
* @param {string} name - Task name
|
|
213
|
+
* @param {number} [totalSteps] - Total number of steps (optional)
|
|
214
|
+
* @returns {FeedbackTask}
|
|
215
|
+
*/
|
|
216
|
+
task(name, totalSteps = null) {
|
|
217
|
+
return new FeedbackTask(name, totalSteps, {
|
|
218
|
+
isTTY: this.isTTY,
|
|
219
|
+
indent: this.indent,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create a progress bar
|
|
225
|
+
* @param {string} label - Progress label
|
|
226
|
+
* @param {number} total - Total items
|
|
227
|
+
* @param {Object} [options={}] - Progress bar options
|
|
228
|
+
* @returns {FeedbackProgressBar}
|
|
229
|
+
*/
|
|
230
|
+
progressBar(label, total, options = {}) {
|
|
231
|
+
return new FeedbackProgressBar(label, total, {
|
|
232
|
+
...options,
|
|
233
|
+
isTTY: this.isTTY,
|
|
234
|
+
indent: this.indent,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Run an async operation with a spinner
|
|
240
|
+
* @param {string} message - Spinner message
|
|
241
|
+
* @param {Function} fn - Async function to execute
|
|
242
|
+
* @param {Object} [options={}] - Spinner options
|
|
243
|
+
* @returns {Promise<any>} Result of the async function
|
|
244
|
+
*/
|
|
245
|
+
async withSpinner(message, fn, options = {}) {
|
|
246
|
+
const spinner = this.spinner(message, options).start();
|
|
247
|
+
try {
|
|
248
|
+
const result = await fn(spinner);
|
|
249
|
+
spinner.succeed();
|
|
250
|
+
return result;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
spinner.fail(error.message);
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Create a child feedback with increased indentation
|
|
259
|
+
* @returns {Feedback}
|
|
260
|
+
*/
|
|
261
|
+
child() {
|
|
262
|
+
return new Feedback({
|
|
263
|
+
isTTY: this.isTTY,
|
|
264
|
+
indent: this.indent + 1,
|
|
265
|
+
quiet: this.quiet,
|
|
266
|
+
verbose: this.verbose,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Spinner for indeterminate progress
|
|
273
|
+
*/
|
|
274
|
+
class FeedbackSpinner {
|
|
275
|
+
constructor(message, options = {}) {
|
|
276
|
+
this.message = message;
|
|
277
|
+
this.isTTY = options.isTTY !== undefined ? options.isTTY : process.stdout.isTTY;
|
|
278
|
+
this.indent = options.indent || 0;
|
|
279
|
+
this.interval = options.interval || 80;
|
|
280
|
+
this.frameIndex = 0;
|
|
281
|
+
this.timer = null;
|
|
282
|
+
this.startTime = null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
_prefix() {
|
|
286
|
+
return ' '.repeat(this.indent);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Start the spinner
|
|
291
|
+
* @returns {FeedbackSpinner}
|
|
292
|
+
*/
|
|
293
|
+
start() {
|
|
294
|
+
if (!this.isTTY) {
|
|
295
|
+
console.log(`${this._prefix()}${c.dim}${this.message}${c.reset}`);
|
|
296
|
+
return this;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
this.startTime = Date.now();
|
|
300
|
+
this.render();
|
|
301
|
+
this.timer = setInterval(() => {
|
|
302
|
+
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
303
|
+
this.render();
|
|
304
|
+
}, this.interval);
|
|
305
|
+
|
|
306
|
+
return this;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Update spinner message
|
|
311
|
+
* @param {string} message - New message
|
|
312
|
+
* @returns {FeedbackSpinner}
|
|
313
|
+
*/
|
|
314
|
+
update(message) {
|
|
315
|
+
this.message = message;
|
|
316
|
+
if (this.isTTY && this.timer) {
|
|
317
|
+
this.render();
|
|
318
|
+
}
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Render spinner frame
|
|
324
|
+
* @private
|
|
325
|
+
*/
|
|
326
|
+
render() {
|
|
327
|
+
if (!this.isTTY) return;
|
|
328
|
+
const frame = SPINNER_FRAMES[this.frameIndex];
|
|
329
|
+
const line = `${this._prefix()}${c.cyan}${frame}${c.reset} ${this.message}`;
|
|
330
|
+
process.stdout.clearLine(0);
|
|
331
|
+
process.stdout.cursorTo(0);
|
|
332
|
+
process.stdout.write(line);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Stop spinner with result
|
|
337
|
+
* @param {string} symbol - Symbol to show
|
|
338
|
+
* @param {string} [message] - Final message
|
|
339
|
+
* @param {string} [color] - Color code
|
|
340
|
+
* @returns {FeedbackSpinner}
|
|
341
|
+
*/
|
|
342
|
+
stop(symbol, message = null, color = '') {
|
|
343
|
+
if (this.timer) {
|
|
344
|
+
clearInterval(this.timer);
|
|
345
|
+
this.timer = null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (this.isTTY) {
|
|
349
|
+
process.stdout.clearLine(0);
|
|
350
|
+
process.stdout.cursorTo(0);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const elapsed = this.startTime ? Date.now() - this.startTime : 0;
|
|
354
|
+
const suffix =
|
|
355
|
+
elapsed > DOHERTY_THRESHOLD_MS ? ` ${c.dim}(${formatDuration(elapsed)})${c.reset}` : '';
|
|
356
|
+
const msg = message || this.message;
|
|
357
|
+
|
|
358
|
+
console.log(`${this._prefix()}${color}${symbol}${c.reset} ${msg}${suffix}`);
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
succeed(message = null) {
|
|
363
|
+
return this.stop(SYMBOLS.success, message, c.green);
|
|
364
|
+
}
|
|
365
|
+
fail(message = null) {
|
|
366
|
+
return this.stop(SYMBOLS.error, message, c.red);
|
|
367
|
+
}
|
|
368
|
+
warn(message = null) {
|
|
369
|
+
return this.stop(SYMBOLS.warning, message, c.yellow);
|
|
370
|
+
}
|
|
371
|
+
info(message = null) {
|
|
372
|
+
return this.stop(SYMBOLS.info, message, c.blue);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Task tracker for multi-step operations
|
|
378
|
+
*/
|
|
379
|
+
class FeedbackTask {
|
|
380
|
+
constructor(name, totalSteps = null, options = {}) {
|
|
381
|
+
this.name = name;
|
|
382
|
+
this.totalSteps = totalSteps;
|
|
383
|
+
this.currentStep = 0;
|
|
384
|
+
this.isTTY = options.isTTY !== undefined ? options.isTTY : process.stdout.isTTY;
|
|
385
|
+
this.indent = options.indent || 0;
|
|
386
|
+
this.startTime = Date.now();
|
|
387
|
+
this.steps = [];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
_prefix() {
|
|
391
|
+
return ' '.repeat(this.indent);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Start the task (optional - for explicit start message)
|
|
396
|
+
* @param {string} [message] - Start message
|
|
397
|
+
* @returns {FeedbackTask}
|
|
398
|
+
*/
|
|
399
|
+
start(message = null) {
|
|
400
|
+
const msg = message || this.name;
|
|
401
|
+
console.log(`${this._prefix()}${c.cyan}${SYMBOLS.active}${c.reset} ${msg}`);
|
|
402
|
+
return this;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Mark a step as complete
|
|
407
|
+
* @param {string} message - Step message
|
|
408
|
+
* @returns {FeedbackTask}
|
|
409
|
+
*/
|
|
410
|
+
step(message) {
|
|
411
|
+
this.currentStep++;
|
|
412
|
+
this.steps.push(message);
|
|
413
|
+
|
|
414
|
+
let stepInfo = '';
|
|
415
|
+
if (this.totalSteps) {
|
|
416
|
+
stepInfo = ` ${c.dim}[${this.currentStep}/${this.totalSteps}]${c.reset}`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
console.log(`${this._prefix()} ${c.green}${SYMBOLS.success}${c.reset} ${message}${stepInfo}`);
|
|
420
|
+
return this;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Log a sub-item (dimmed, no step counting)
|
|
425
|
+
* @param {string} message - Item message
|
|
426
|
+
* @returns {FeedbackTask}
|
|
427
|
+
*/
|
|
428
|
+
item(message) {
|
|
429
|
+
console.log(`${this._prefix()} ${c.dim}${SYMBOLS.bullet} ${message}${c.reset}`);
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Complete the task successfully
|
|
435
|
+
* @param {string} [message] - Completion message
|
|
436
|
+
* @returns {FeedbackTask}
|
|
437
|
+
*/
|
|
438
|
+
complete(message = null) {
|
|
439
|
+
const elapsed = Date.now() - this.startTime;
|
|
440
|
+
const suffix =
|
|
441
|
+
elapsed > DOHERTY_THRESHOLD_MS ? ` ${c.dim}(${formatDuration(elapsed)})${c.reset}` : '';
|
|
442
|
+
const msg = message || `${this.name} complete`;
|
|
443
|
+
|
|
444
|
+
console.log(`${this._prefix()}${c.green}${SYMBOLS.success}${c.reset} ${msg}${suffix}`);
|
|
445
|
+
return this;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Fail the task
|
|
450
|
+
* @param {string} [message] - Failure message
|
|
451
|
+
* @returns {FeedbackTask}
|
|
452
|
+
*/
|
|
453
|
+
fail(message = null) {
|
|
454
|
+
const msg = message || `${this.name} failed`;
|
|
455
|
+
console.log(`${this._prefix()}${c.red}${SYMBOLS.error}${c.reset} ${msg}`);
|
|
456
|
+
return this;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Progress bar for determinate progress
|
|
462
|
+
*/
|
|
463
|
+
class FeedbackProgressBar {
|
|
464
|
+
constructor(label, total, options = {}) {
|
|
465
|
+
this.label = label;
|
|
466
|
+
this.total = total;
|
|
467
|
+
this.current = 0;
|
|
468
|
+
this.width = options.width || 30;
|
|
469
|
+
this.isTTY = options.isTTY !== undefined ? options.isTTY : process.stdout.isTTY;
|
|
470
|
+
this.indent = options.indent || 0;
|
|
471
|
+
this.startTime = Date.now();
|
|
472
|
+
this.lastPrintedPercent = -1;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
_prefix() {
|
|
476
|
+
return ' '.repeat(this.indent);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Update progress
|
|
481
|
+
* @param {number} current - Current count
|
|
482
|
+
* @param {string} [item] - Current item name
|
|
483
|
+
* @returns {FeedbackProgressBar}
|
|
484
|
+
*/
|
|
485
|
+
update(current, item = null) {
|
|
486
|
+
this.current = current;
|
|
487
|
+
|
|
488
|
+
if (!this.isTTY) {
|
|
489
|
+
// Non-TTY: print every 10% or on completion
|
|
490
|
+
const percent = Math.floor((current / this.total) * 100);
|
|
491
|
+
if (percent % 10 === 0 && percent !== this.lastPrintedPercent) {
|
|
492
|
+
this.lastPrintedPercent = percent;
|
|
493
|
+
console.log(`${this._prefix()}${this.label}: ${percent}% (${current}/${this.total})`);
|
|
494
|
+
}
|
|
495
|
+
return this;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const percent = this.total > 0 ? current / this.total : 0;
|
|
499
|
+
const filled = Math.round(this.width * percent);
|
|
500
|
+
const empty = this.width - filled;
|
|
501
|
+
|
|
502
|
+
const bar = `${c.green}${'█'.repeat(filled)}${c.dim}${'░'.repeat(empty)}${c.reset}`;
|
|
503
|
+
const percentStr = `${Math.round(percent * 100)}%`.padStart(4);
|
|
504
|
+
const countStr = `${current}/${this.total}`;
|
|
505
|
+
const itemStr = item ? ` ${c.dim}${item}${c.reset}` : '';
|
|
506
|
+
|
|
507
|
+
process.stdout.clearLine(0);
|
|
508
|
+
process.stdout.cursorTo(0);
|
|
509
|
+
process.stdout.write(
|
|
510
|
+
`${this._prefix()}${this.label} ${bar} ${percentStr} (${countStr})${itemStr}`
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
return this;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Increment by 1
|
|
518
|
+
* @param {string} [item] - Current item name
|
|
519
|
+
* @returns {FeedbackProgressBar}
|
|
520
|
+
*/
|
|
521
|
+
increment(item = null) {
|
|
522
|
+
return this.update(this.current + 1, item);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Complete the progress bar
|
|
527
|
+
* @param {string} [message] - Completion message
|
|
528
|
+
* @returns {FeedbackProgressBar}
|
|
529
|
+
*/
|
|
530
|
+
complete(message = null) {
|
|
531
|
+
if (this.isTTY) {
|
|
532
|
+
process.stdout.clearLine(0);
|
|
533
|
+
process.stdout.cursorTo(0);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const elapsed = Date.now() - this.startTime;
|
|
537
|
+
const suffix =
|
|
538
|
+
elapsed > DOHERTY_THRESHOLD_MS ? ` ${c.dim}(${formatDuration(elapsed)})${c.reset}` : '';
|
|
539
|
+
const msg = message || `${this.label} complete`;
|
|
540
|
+
|
|
541
|
+
console.log(
|
|
542
|
+
`${this._prefix()}${c.green}${SYMBOLS.success}${c.reset} ${msg} (${this.total} items)${suffix}`
|
|
543
|
+
);
|
|
544
|
+
return this;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Singleton instance for convenience
|
|
549
|
+
const feedback = new Feedback();
|
|
550
|
+
|
|
551
|
+
module.exports = {
|
|
552
|
+
Feedback,
|
|
553
|
+
FeedbackSpinner,
|
|
554
|
+
FeedbackTask,
|
|
555
|
+
FeedbackProgressBar,
|
|
556
|
+
feedback,
|
|
557
|
+
SYMBOLS,
|
|
558
|
+
SPINNER_FRAMES,
|
|
559
|
+
DOHERTY_THRESHOLD_MS,
|
|
560
|
+
formatDuration,
|
|
561
|
+
};
|