effective-progress 0.11.0 → 0.11.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/README.md +5 -1
- package/dist/index.d.mts +19 -3
- package/dist/index.mjs +165 -103
- package/package.json +2 -5
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
> Pre-`1.0.0`, breaking changes may happen in any minor release. SemVer guarantees will begin at `1.0.0`.
|
|
7
7
|
> I recommend using only the `Progress.all` and `Progress.forEach` APIs for now, as they will likely change the least. The lower-level APIs for manual progress bar control are more likely to see breaking changes as I iterate on the design.
|
|
8
8
|
>
|
|
9
|
+
> I am currently waiting on https://github.com/anomalyco/opentui/issues/204 to swap the renderer to opentui.
|
|
10
|
+
>
|
|
9
11
|
> Please open an issue or reach out if you have any questions or want to contribute!
|
|
10
12
|
> Feedback and contributions are very welcome!
|
|
11
13
|
|
|
@@ -145,8 +147,10 @@ Effect.runPromise(program);
|
|
|
145
147
|
### Ink renderer behavior
|
|
146
148
|
|
|
147
149
|
- Rendering is powered by [Ink](https://github.com/vadimdemedes/ink).
|
|
148
|
-
- Built-in columns are exposed as `Progress.Columns.description()`, `bar()`, `amount()`, `elapsed()`, `eta()`, `spacer()`, and `defaults()`.
|
|
150
|
+
- Built-in columns are exposed as `Progress.Columns.description()`, `bar()`, `amount()`, `elapsedEta()`, `elapsed()`, `eta()`, `spacer()`, and `defaults()`.
|
|
151
|
+
- `elapsedEta()` renders a compact clock-style column as `elapsed<eta` using the shape `00:00<00:00`; `defaults()` now uses that combined column.
|
|
149
152
|
- Determinate bars are segmented by outcome: succeeded (green), failed (red), and remaining (neutral).
|
|
153
|
+
- `bar()` defaults to a fixed width of `30`; pass `bar({ size: "fullwidth" })` to consume remaining row width or `bar({ size: 12 })` for an explicit width.
|
|
150
154
|
- Determinate amount text shows counters without prefixes: `<succeeded> <failed> <processed>/<total>`.
|
|
151
155
|
- Counts can exceed `total`; the amount text keeps those raw values (for example `12/10`) while the bar stays visually clamped at full.
|
|
152
156
|
- `total: 0` is valid for determinate tasks and renders as a full bar by default.
|
package/dist/index.d.mts
CHANGED
|
@@ -108,6 +108,11 @@ declare const TaskUnitsSchema: Schema.Struct<{
|
|
|
108
108
|
total: Schema.optional<typeof Schema.Number>;
|
|
109
109
|
}>;
|
|
110
110
|
type TaskUnits = typeof TaskUnitsSchema.Type;
|
|
111
|
+
declare const TaskProgressSampleSchema: Schema.Struct<{
|
|
112
|
+
timestamp: typeof Schema.Number;
|
|
113
|
+
processed: typeof Schema.Number;
|
|
114
|
+
}>;
|
|
115
|
+
type TaskProgressSample = typeof TaskProgressSampleSchema.Type;
|
|
111
116
|
declare const TaskSnapshotSchema: Schema.Struct<{
|
|
112
117
|
id: Schema.brand<typeof Schema.Number, "TaskId">;
|
|
113
118
|
parentId: Schema.NullOr<Schema.brand<typeof Schema.Number, "TaskId">>;
|
|
@@ -123,6 +128,10 @@ declare const TaskSnapshotSchema: Schema.Struct<{
|
|
|
123
128
|
}>;
|
|
124
129
|
startedAt: typeof Schema.Number;
|
|
125
130
|
completedAt: Schema.NullOr<typeof Schema.Number>;
|
|
131
|
+
progressSamples: Schema.Array$<Schema.Struct<{
|
|
132
|
+
timestamp: typeof Schema.Number;
|
|
133
|
+
processed: typeof Schema.Number;
|
|
134
|
+
}>>;
|
|
126
135
|
metadata: typeof Schema.Unknown;
|
|
127
136
|
}>;
|
|
128
137
|
type TaskSnapshot = typeof TaskSnapshotSchema.Type;
|
|
@@ -296,9 +305,10 @@ interface BarPrepared {
|
|
|
296
305
|
//#region src/renderer/columns/description-column.d.ts
|
|
297
306
|
interface DescriptionPrepared {
|
|
298
307
|
readonly minTreeWidth: number;
|
|
308
|
+
readonly preferredWidth: number;
|
|
299
309
|
}
|
|
300
310
|
declare namespace columns_d_exports {
|
|
301
|
-
export { AmountLayout, BarPrepared, DescriptionPrepared, SpacerOptions, amount, bar, defaults, description, elapsed, eta, resolveColumnSizeValue, spacer };
|
|
311
|
+
export { AmountLayout, BarOptions, BarPrepared, DescriptionPrepared, SpacerOptions, amount, bar, defaults, description, elapsed, elapsedEta, eta, resolveColumnSizeValue, spacer };
|
|
302
312
|
}
|
|
303
313
|
interface SpacerOptions {
|
|
304
314
|
readonly flexGrow?: number;
|
|
@@ -306,6 +316,9 @@ interface SpacerOptions {
|
|
|
306
316
|
readonly flexBasis?: number;
|
|
307
317
|
readonly minWidth?: number;
|
|
308
318
|
}
|
|
319
|
+
interface BarOptions {
|
|
320
|
+
readonly size?: number | "fullwidth";
|
|
321
|
+
}
|
|
309
322
|
declare const spacer: <M = unknown>({
|
|
310
323
|
flexGrow,
|
|
311
324
|
flexShrink,
|
|
@@ -313,9 +326,12 @@ declare const spacer: <M = unknown>({
|
|
|
313
326
|
minWidth
|
|
314
327
|
}?: SpacerOptions) => ColumnDef<M>;
|
|
315
328
|
declare const description: () => ColumnDef<any, DescriptionPrepared>;
|
|
316
|
-
declare const bar: (
|
|
329
|
+
declare const bar: ({
|
|
330
|
+
size
|
|
331
|
+
}?: BarOptions) => ColumnDef<any, BarPrepared>;
|
|
317
332
|
declare const amount: () => ColumnDef<any, AmountLayout>;
|
|
318
333
|
declare const elapsed: () => ColumnDef<any>;
|
|
334
|
+
declare const elapsedEta: () => ColumnDef<any>;
|
|
319
335
|
declare const eta: () => ColumnDef<any>;
|
|
320
336
|
declare const defaults: () => ReadonlyArray<ColumnDef<any, any>>;
|
|
321
337
|
declare const resolveColumnSizeValue: <P>(value: ColumnSizeValue<P> | undefined, prepared: P) => number | undefined;
|
|
@@ -330,4 +346,4 @@ declare class ProgressStdio extends ProgressStdio_base {
|
|
|
330
346
|
static readonly Default: Layer.Layer<ProgressStdio, never, never>;
|
|
331
347
|
}
|
|
332
348
|
//#endregion
|
|
333
|
-
export { AddTaskOptions, AllOptions, AllReturn, CellInfo, ColumnAlign, ColumnDef, ColumnRenderContext, ColumnSizeValue, columns_d_exports as Columns, EffectAllExecutionOptions, EffectExecutionOptions, ForEachExecutionOptions, ForEachOptions, Progress, ProgressService, ProgressStdio, ProgressStdioService, ProgressTaskEvent, ProgressTaskEventSchema, RenderRow, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplay, TaskCountDisplaySchema, TaskFailedEvent, TaskHandle, TaskId, TaskOptions, TaskRemovedEvent, TaskRowDerived, TaskSnapshot, TaskSnapshotSchema, TaskStatus, TaskStatusSchema, TaskStore, TaskTreeInfo, TaskUnits, TaskUnitsSchema, TaskUpdatedEvent, TrackOptions, UpdateTaskOptions, all, decodeProgressTaskEvent, forEach, task$1 as task };
|
|
349
|
+
export { AddTaskOptions, AllOptions, AllReturn, CellInfo, ColumnAlign, ColumnDef, ColumnRenderContext, ColumnSizeValue, columns_d_exports as Columns, EffectAllExecutionOptions, EffectExecutionOptions, ForEachExecutionOptions, ForEachOptions, Progress, ProgressService, ProgressStdio, ProgressStdioService, ProgressTaskEvent, ProgressTaskEventSchema, RenderRow, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplay, TaskCountDisplaySchema, TaskFailedEvent, TaskHandle, TaskId, TaskOptions, TaskProgressSample, TaskProgressSampleSchema, TaskRemovedEvent, TaskRowDerived, TaskSnapshot, TaskSnapshotSchema, TaskStatus, TaskStatusSchema, TaskStore, TaskTreeInfo, TaskUnits, TaskUnitsSchema, TaskUpdatedEvent, TrackOptions, UpdateTaskOptions, all, decodeProgressTaskEvent, forEach, task$1 as task };
|
package/dist/index.mjs
CHANGED
|
@@ -18,6 +18,10 @@ const TaskUnitsSchema = Schema.Struct({
|
|
|
18
18
|
processed: Schema.Number,
|
|
19
19
|
total: Schema.optional(Schema.Number)
|
|
20
20
|
});
|
|
21
|
+
const TaskProgressSampleSchema = Schema.Struct({
|
|
22
|
+
timestamp: Schema.Number,
|
|
23
|
+
processed: Schema.Number
|
|
24
|
+
});
|
|
21
25
|
const TaskSnapshotSchema = Schema.Struct({
|
|
22
26
|
id: TaskIdSchema,
|
|
23
27
|
parentId: Schema.NullOr(TaskIdSchema),
|
|
@@ -28,6 +32,7 @@ const TaskSnapshotSchema = Schema.Struct({
|
|
|
28
32
|
units: TaskUnitsSchema,
|
|
29
33
|
startedAt: Schema.Number,
|
|
30
34
|
completedAt: Schema.NullOr(Schema.Number),
|
|
35
|
+
progressSamples: Schema.Array(TaskProgressSampleSchema),
|
|
31
36
|
metadata: Schema.Unknown
|
|
32
37
|
});
|
|
33
38
|
const TaskSnapshot = (snapshot) => snapshot;
|
|
@@ -63,6 +68,8 @@ const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema
|
|
|
63
68
|
|
|
64
69
|
//#endregion
|
|
65
70
|
//#region src/renderer/store.ts
|
|
71
|
+
const ETA_SAMPLE_WINDOW_MILLIS = 3e4;
|
|
72
|
+
const ETA_SAMPLE_MAX_LENGTH = 1e3;
|
|
66
73
|
const hasExplicitTotal = (options) => Object.prototype.hasOwnProperty.call(options, "total");
|
|
67
74
|
const sanitizeTotalOnAdd = (total) => {
|
|
68
75
|
if (total === void 0) return;
|
|
@@ -86,7 +93,25 @@ const normalizeUnits = (counts) => {
|
|
|
86
93
|
total: counts.total
|
|
87
94
|
};
|
|
88
95
|
};
|
|
89
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Returns a new progress sample deque with the latest processed count appended.
|
|
98
|
+
*
|
|
99
|
+
* Samples are retained for the ETA rolling window and capped by count so very chatty tasks do not
|
|
100
|
+
* grow memory without bound.
|
|
101
|
+
*/
|
|
102
|
+
const appendProgressSample = (samples, now, processed) => {
|
|
103
|
+
const previousSamples = samples ?? [];
|
|
104
|
+
if (previousSamples.at(-1)?.processed === processed) return previousSamples;
|
|
105
|
+
const windowStart = now - ETA_SAMPLE_WINDOW_MILLIS;
|
|
106
|
+
const nextSamples = [...previousSamples, {
|
|
107
|
+
timestamp: now,
|
|
108
|
+
processed
|
|
109
|
+
}];
|
|
110
|
+
while (nextSamples.length > 2 && (nextSamples.length > ETA_SAMPLE_MAX_LENGTH || nextSamples[1].timestamp < windowStart)) nextSamples.shift();
|
|
111
|
+
return nextSamples;
|
|
112
|
+
};
|
|
113
|
+
/** Applies mutable task fields and records a progress sample when the processed count changes. */
|
|
114
|
+
const updatedSnapshot = (snapshot, options, now) => {
|
|
90
115
|
const currentUnits = snapshot.units;
|
|
91
116
|
const units = options.succeeded === void 0 && options.failed === void 0 && options.total === void 0 && !hasExplicitTotal(options) ? currentUnits : normalizeUnits({
|
|
92
117
|
succeeded: options.succeeded ?? currentUnits.succeeded,
|
|
@@ -94,30 +119,14 @@ const updatedSnapshot = (snapshot, options) => {
|
|
|
94
119
|
total: hasExplicitTotal(options) ? sanitizeTotalOnUpdate(options.total) : currentUnits.total
|
|
95
120
|
});
|
|
96
121
|
return TaskSnapshot({
|
|
97
|
-
|
|
98
|
-
parentId: snapshot.parentId,
|
|
122
|
+
...snapshot,
|
|
99
123
|
description: options.description ?? snapshot.description,
|
|
100
|
-
status: snapshot.status,
|
|
101
124
|
countDisplay: options.countDisplay ?? snapshot.countDisplay,
|
|
102
125
|
transient: options.transient ?? snapshot.transient,
|
|
103
126
|
units,
|
|
104
|
-
|
|
105
|
-
completedAt: snapshot.completedAt,
|
|
106
|
-
metadata: snapshot.metadata
|
|
127
|
+
progressSamples: appendProgressSample(snapshot.progressSamples, now, units.processed)
|
|
107
128
|
});
|
|
108
129
|
};
|
|
109
|
-
const withTransient = (snapshot, transient) => TaskSnapshot({
|
|
110
|
-
id: snapshot.id,
|
|
111
|
-
parentId: snapshot.parentId,
|
|
112
|
-
description: snapshot.description,
|
|
113
|
-
status: snapshot.status,
|
|
114
|
-
countDisplay: snapshot.countDisplay,
|
|
115
|
-
transient,
|
|
116
|
-
units: snapshot.units,
|
|
117
|
-
startedAt: snapshot.startedAt,
|
|
118
|
-
completedAt: snapshot.completedAt,
|
|
119
|
-
metadata: snapshot.metadata
|
|
120
|
-
});
|
|
121
130
|
const findInsertionIndex = (renderOrder, parentId) => {
|
|
122
131
|
if (parentId === null) return {
|
|
123
132
|
index: renderOrder.length,
|
|
@@ -250,6 +259,10 @@ const makeProgressRenderStore = () => {
|
|
|
250
259
|
units,
|
|
251
260
|
startedAt: now,
|
|
252
261
|
completedAt: null,
|
|
262
|
+
progressSamples: [{
|
|
263
|
+
timestamp: now,
|
|
264
|
+
processed: units.processed
|
|
265
|
+
}],
|
|
253
266
|
metadata: options.metadata
|
|
254
267
|
});
|
|
255
268
|
updateState((current) => {
|
|
@@ -279,14 +292,15 @@ const makeProgressRenderStore = () => {
|
|
|
279
292
|
});
|
|
280
293
|
return taskId;
|
|
281
294
|
}),
|
|
282
|
-
updateTask: (taskId, options) => Effect.
|
|
295
|
+
updateTask: (taskId, options) => Effect.gen(function* () {
|
|
296
|
+
const now = yield* Clock.currentTimeMillis;
|
|
283
297
|
updateState((current) => {
|
|
284
298
|
const currentTask = current.tasks.get(taskId);
|
|
285
299
|
if (!currentTask) return {
|
|
286
300
|
state: current,
|
|
287
301
|
events: []
|
|
288
302
|
};
|
|
289
|
-
const nextTask = updatedSnapshot(currentTask, options);
|
|
303
|
+
const nextTask = updatedSnapshot(currentTask, options, now);
|
|
290
304
|
const nextTasks = new Map(current.tasks);
|
|
291
305
|
nextTasks.set(taskId, nextTask);
|
|
292
306
|
const events = [new TaskUpdatedEvent({
|
|
@@ -302,7 +316,10 @@ const makeProgressRenderStore = () => {
|
|
|
302
316
|
if (options.transient !== void 0) for (const candidateId of subtreeTaskIds(current.renderOrder, taskId).slice(1)) {
|
|
303
317
|
const candidate = current.tasks.get(candidateId);
|
|
304
318
|
if (!candidate) continue;
|
|
305
|
-
const nextCandidate =
|
|
319
|
+
const nextCandidate = TaskSnapshot({
|
|
320
|
+
...candidate,
|
|
321
|
+
transient: nextTask.transient
|
|
322
|
+
});
|
|
306
323
|
nextTasks.set(candidateId, nextCandidate);
|
|
307
324
|
events.push(new TaskUpdatedEvent({
|
|
308
325
|
taskId: candidateId,
|
|
@@ -319,29 +336,24 @@ const makeProgressRenderStore = () => {
|
|
|
319
336
|
};
|
|
320
337
|
});
|
|
321
338
|
}),
|
|
322
|
-
incrementSucceeded: (taskId, amount = 1) => Effect.
|
|
339
|
+
incrementSucceeded: (taskId, amount = 1) => Effect.gen(function* () {
|
|
340
|
+
const now = yield* Clock.currentTimeMillis;
|
|
323
341
|
updateState((current) => {
|
|
324
342
|
const currentTask = current.tasks.get(taskId);
|
|
325
343
|
if (!currentTask) return {
|
|
326
344
|
state: current,
|
|
327
345
|
events: []
|
|
328
346
|
};
|
|
347
|
+
const units = normalizeUnits({
|
|
348
|
+
succeeded: currentTask.units.succeeded + amount,
|
|
349
|
+
failed: currentTask.units.failed,
|
|
350
|
+
total: currentTask.units.total
|
|
351
|
+
});
|
|
329
352
|
const nextTasks = new Map(current.tasks);
|
|
330
353
|
nextTasks.set(taskId, TaskSnapshot({
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
status: currentTask.status,
|
|
335
|
-
countDisplay: currentTask.countDisplay,
|
|
336
|
-
transient: currentTask.transient,
|
|
337
|
-
units: normalizeUnits({
|
|
338
|
-
succeeded: currentTask.units.succeeded + amount,
|
|
339
|
-
failed: currentTask.units.failed,
|
|
340
|
-
total: currentTask.units.total
|
|
341
|
-
}),
|
|
342
|
-
startedAt: currentTask.startedAt,
|
|
343
|
-
completedAt: currentTask.completedAt,
|
|
344
|
-
metadata: currentTask.metadata
|
|
354
|
+
...currentTask,
|
|
355
|
+
units,
|
|
356
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
345
357
|
}));
|
|
346
358
|
return {
|
|
347
359
|
state: {
|
|
@@ -357,29 +369,24 @@ const makeProgressRenderStore = () => {
|
|
|
357
369
|
};
|
|
358
370
|
});
|
|
359
371
|
}),
|
|
360
|
-
incrementFailed: (taskId, amount = 1) => Effect.
|
|
372
|
+
incrementFailed: (taskId, amount = 1) => Effect.gen(function* () {
|
|
373
|
+
const now = yield* Clock.currentTimeMillis;
|
|
361
374
|
updateState((current) => {
|
|
362
375
|
const currentTask = current.tasks.get(taskId);
|
|
363
376
|
if (!currentTask) return {
|
|
364
377
|
state: current,
|
|
365
378
|
events: []
|
|
366
379
|
};
|
|
380
|
+
const units = normalizeUnits({
|
|
381
|
+
succeeded: currentTask.units.succeeded,
|
|
382
|
+
failed: currentTask.units.failed + amount,
|
|
383
|
+
total: currentTask.units.total
|
|
384
|
+
});
|
|
367
385
|
const nextTasks = new Map(current.tasks);
|
|
368
386
|
nextTasks.set(taskId, TaskSnapshot({
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
status: currentTask.status,
|
|
373
|
-
countDisplay: currentTask.countDisplay,
|
|
374
|
-
transient: currentTask.transient,
|
|
375
|
-
units: normalizeUnits({
|
|
376
|
-
succeeded: currentTask.units.succeeded,
|
|
377
|
-
failed: currentTask.units.failed + amount,
|
|
378
|
-
total: currentTask.units.total
|
|
379
|
-
}),
|
|
380
|
-
startedAt: currentTask.startedAt,
|
|
381
|
-
completedAt: currentTask.completedAt,
|
|
382
|
-
metadata: currentTask.metadata
|
|
387
|
+
...currentTask,
|
|
388
|
+
units,
|
|
389
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
383
390
|
}));
|
|
384
391
|
return {
|
|
385
392
|
state: {
|
|
@@ -422,25 +429,21 @@ const makeProgressRenderStore = () => {
|
|
|
422
429
|
events: [new TaskCompletedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
423
430
|
};
|
|
424
431
|
}
|
|
432
|
+
const units = currentTask.units.total !== void 0 ? currentTask.units.processed < currentTask.units.total ? normalizeUnits({
|
|
433
|
+
succeeded: currentTask.units.succeeded + (currentTask.units.total - currentTask.units.processed),
|
|
434
|
+
failed: currentTask.units.failed,
|
|
435
|
+
total: currentTask.units.total
|
|
436
|
+
}) : currentTask.units : currentTask.units.processed > 0 ? normalizeUnits({
|
|
437
|
+
succeeded: currentTask.units.succeeded,
|
|
438
|
+
failed: currentTask.units.failed,
|
|
439
|
+
total: currentTask.units.processed
|
|
440
|
+
}) : currentTask.units;
|
|
425
441
|
nextTasks.set(taskId, TaskSnapshot({
|
|
426
|
-
|
|
427
|
-
parentId: currentTask.parentId,
|
|
428
|
-
description: currentTask.description,
|
|
442
|
+
...currentTask,
|
|
429
443
|
status: "done",
|
|
430
|
-
|
|
431
|
-
transient: currentTask.transient,
|
|
432
|
-
units: currentTask.units.total !== void 0 ? currentTask.units.processed < currentTask.units.total ? normalizeUnits({
|
|
433
|
-
succeeded: currentTask.units.succeeded + (currentTask.units.total - currentTask.units.processed),
|
|
434
|
-
failed: currentTask.units.failed,
|
|
435
|
-
total: currentTask.units.total
|
|
436
|
-
}) : currentTask.units : currentTask.units.processed > 0 ? normalizeUnits({
|
|
437
|
-
succeeded: currentTask.units.succeeded,
|
|
438
|
-
failed: currentTask.units.failed,
|
|
439
|
-
total: currentTask.units.processed
|
|
440
|
-
}) : currentTask.units,
|
|
441
|
-
startedAt: currentTask.startedAt,
|
|
444
|
+
units,
|
|
442
445
|
completedAt: now,
|
|
443
|
-
|
|
446
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
444
447
|
}));
|
|
445
448
|
return {
|
|
446
449
|
state: {
|
|
@@ -480,16 +483,9 @@ const makeProgressRenderStore = () => {
|
|
|
480
483
|
};
|
|
481
484
|
}
|
|
482
485
|
nextTasks.set(taskId, TaskSnapshot({
|
|
483
|
-
|
|
484
|
-
parentId: currentTask.parentId,
|
|
485
|
-
description: currentTask.description,
|
|
486
|
+
...currentTask,
|
|
486
487
|
status: "failed",
|
|
487
|
-
|
|
488
|
-
transient: currentTask.transient,
|
|
489
|
-
units: currentTask.units,
|
|
490
|
-
startedAt: currentTask.startedAt,
|
|
491
|
-
completedAt: now,
|
|
492
|
-
metadata: currentTask.metadata
|
|
488
|
+
completedAt: now
|
|
493
489
|
}));
|
|
494
490
|
return {
|
|
495
491
|
state: {
|
|
@@ -512,15 +508,7 @@ const makeProgressRenderStore = () => {
|
|
|
512
508
|
};
|
|
513
509
|
const nextTasks = new Map(current.tasks);
|
|
514
510
|
nextTasks.set(taskId, TaskSnapshot({
|
|
515
|
-
|
|
516
|
-
parentId: currentTask.parentId,
|
|
517
|
-
description: currentTask.description,
|
|
518
|
-
status: currentTask.status,
|
|
519
|
-
countDisplay: currentTask.countDisplay,
|
|
520
|
-
transient: currentTask.transient,
|
|
521
|
-
units: currentTask.units,
|
|
522
|
-
startedAt: currentTask.startedAt,
|
|
523
|
-
completedAt: currentTask.completedAt,
|
|
511
|
+
...currentTask,
|
|
524
512
|
metadata
|
|
525
513
|
}));
|
|
526
514
|
return {
|
|
@@ -635,17 +623,58 @@ const formatDurationSeconds = (seconds) => {
|
|
|
635
623
|
const mins = Math.floor(value % 3600 / 60);
|
|
636
624
|
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
637
625
|
};
|
|
626
|
+
const formatClockDurationSeconds = (seconds) => {
|
|
627
|
+
const value = Math.max(0, Math.floor(seconds));
|
|
628
|
+
const hours = Math.floor(value / 3600);
|
|
629
|
+
const mins = Math.floor(value % 3600 / 60);
|
|
630
|
+
const secs = value % 60;
|
|
631
|
+
const clock = `${`${mins}`.padStart(2, "0")}:${`${secs}`.padStart(2, "0")}`;
|
|
632
|
+
return hours > 0 ? `${`${hours}`.padStart(2, "0")}:${clock}` : clock;
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* Estimates remaining time from the task's retained progress sample deque.
|
|
636
|
+
*
|
|
637
|
+
* Returns undefined until there are at least two samples with positive processed and time deltas.
|
|
638
|
+
*/
|
|
639
|
+
const getSmoothedEtaMillis = (task) => {
|
|
640
|
+
const { processed, total } = task.units;
|
|
641
|
+
const remaining = total - processed;
|
|
642
|
+
if (processed <= 0 || remaining <= 0) return;
|
|
643
|
+
const samples = task.progressSamples;
|
|
644
|
+
const lastSample = samples.at(-1);
|
|
645
|
+
if (lastSample === void 0) return;
|
|
646
|
+
const firstSample = samples[0];
|
|
647
|
+
if (firstSample === void 0 || firstSample === lastSample) return;
|
|
648
|
+
const deltaProcessed = lastSample.processed - firstSample.processed;
|
|
649
|
+
const deltaMillis = lastSample.timestamp - firstSample.timestamp;
|
|
650
|
+
if (deltaProcessed <= 0 || deltaMillis <= 0) return;
|
|
651
|
+
return Math.max(0, Math.floor(remaining * deltaMillis / deltaProcessed));
|
|
652
|
+
};
|
|
638
653
|
const formatElapsed = (task, now) => {
|
|
639
654
|
return formatDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
|
|
640
655
|
};
|
|
656
|
+
const formatElapsedClock = (task, now) => {
|
|
657
|
+
return formatClockDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
|
|
658
|
+
};
|
|
641
659
|
const formatEta = (task, now) => {
|
|
642
660
|
if (task.status !== "running" || !isDeterminate$1(task)) return "";
|
|
643
661
|
const { processed, total } = task.units;
|
|
644
662
|
const remaining = total - processed;
|
|
645
663
|
if (processed <= 0 || remaining <= 0) return "";
|
|
646
|
-
const
|
|
647
|
-
|
|
664
|
+
const etaMillis = getSmoothedEtaMillis(task);
|
|
665
|
+
if (etaMillis === void 0) return "";
|
|
666
|
+
return formatClockDurationSeconds(etaMillis / 1e3);
|
|
648
667
|
};
|
|
668
|
+
const formatEtaClock = (task, now) => {
|
|
669
|
+
if (task.status !== "running" || !isDeterminate$1(task)) return;
|
|
670
|
+
const { processed, total } = task.units;
|
|
671
|
+
const remaining = total - processed;
|
|
672
|
+
if (processed <= 0 || remaining <= 0) return;
|
|
673
|
+
const etaMillis = getSmoothedEtaMillis(task);
|
|
674
|
+
if (etaMillis === void 0) return;
|
|
675
|
+
return formatClockDurationSeconds(etaMillis / 1e3);
|
|
676
|
+
};
|
|
677
|
+
const formatElapsedEta = (task, now) => `${formatElapsedClock(task, now)}<${formatEtaClock(task, now) ?? "00:00"}`;
|
|
649
678
|
const formatDeterminateAmountParts = (task) => {
|
|
650
679
|
if (!isDeterminate$1(task)) return;
|
|
651
680
|
const totalText = `${task.units.total}`;
|
|
@@ -826,7 +855,10 @@ const getTaskIndicator = (task, tick, spinnerType = DEFAULT_SPINNER_TYPE) => {
|
|
|
826
855
|
color: "green"
|
|
827
856
|
};
|
|
828
857
|
};
|
|
829
|
-
const prepareDescription = (rows) => ({
|
|
858
|
+
const prepareDescription = (rows) => ({
|
|
859
|
+
minTreeWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + MIN_TREE_DESCRIPTION_TEXT_WIDTH), MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2),
|
|
860
|
+
preferredWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + row.derived.descriptionWidth), 2)
|
|
861
|
+
});
|
|
830
862
|
const TaskIndicatorGlyph = ({ task, tick, spinnerType = DEFAULT_SPINNER_TYPE }) => {
|
|
831
863
|
const indicator = getTaskIndicator(task, tick, spinnerType);
|
|
832
864
|
return /* @__PURE__ */ jsx(Text, {
|
|
@@ -861,6 +893,16 @@ const DescriptionCell = ({ cell, width, minTreeWidth, spinnerTick }) => {
|
|
|
861
893
|
});
|
|
862
894
|
};
|
|
863
895
|
|
|
896
|
+
//#endregion
|
|
897
|
+
//#region src/renderer/columns/elapsed-eta-column.tsx
|
|
898
|
+
const ElapsedEtaCell = ({ task, now }) => {
|
|
899
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
900
|
+
wrap: "truncate-end",
|
|
901
|
+
color: "gray",
|
|
902
|
+
children: formatElapsedEta(task, now)
|
|
903
|
+
});
|
|
904
|
+
};
|
|
905
|
+
|
|
864
906
|
//#endregion
|
|
865
907
|
//#region src/renderer/columns/elapsed-column.tsx
|
|
866
908
|
const ElapsedCell = ({ task, now }) => {
|
|
@@ -891,10 +933,17 @@ var columns_exports = /* @__PURE__ */ __exportAll({
|
|
|
891
933
|
defaults: () => defaults,
|
|
892
934
|
description: () => description,
|
|
893
935
|
elapsed: () => elapsed,
|
|
936
|
+
elapsedEta: () => elapsedEta,
|
|
894
937
|
eta: () => eta,
|
|
895
938
|
resolveColumnSizeValue: () => resolveColumnSizeValue,
|
|
896
939
|
spacer: () => spacer
|
|
897
940
|
});
|
|
941
|
+
const DEFAULT_BAR_SIZE = 30;
|
|
942
|
+
const resolveBarSize = (size) => {
|
|
943
|
+
if (size === "fullwidth") return size;
|
|
944
|
+
if (typeof size !== "number" || !Number.isFinite(size)) return DEFAULT_BAR_SIZE;
|
|
945
|
+
return Math.max(1, Math.floor(size));
|
|
946
|
+
};
|
|
898
947
|
const spacer = ({ flexGrow, flexShrink, flexBasis, minWidth } = {}) => ({
|
|
899
948
|
render: () => null,
|
|
900
949
|
flexGrow,
|
|
@@ -904,8 +953,8 @@ const spacer = ({ flexGrow, flexShrink, flexBasis, minWidth } = {}) => ({
|
|
|
904
953
|
});
|
|
905
954
|
const description = () => ({
|
|
906
955
|
prepare: prepareDescription,
|
|
907
|
-
flexGrow: 1,
|
|
908
956
|
flexShrink: 1,
|
|
957
|
+
flexBasis: (prepared) => prepared.preferredWidth,
|
|
909
958
|
minWidth: 1,
|
|
910
959
|
render: (cell, ctx) => /* @__PURE__ */ jsx(DescriptionCell, {
|
|
911
960
|
cell,
|
|
@@ -914,16 +963,20 @@ const description = () => ({
|
|
|
914
963
|
spinnerTick: ctx.spinnerTick
|
|
915
964
|
})
|
|
916
965
|
});
|
|
917
|
-
const bar = () =>
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
966
|
+
const bar = ({ size } = {}) => {
|
|
967
|
+
const resolvedSize = resolveBarSize(size);
|
|
968
|
+
return {
|
|
969
|
+
prepare: prepareBar,
|
|
970
|
+
flexGrow: (prepared) => prepared.hasDeterminateRows && resolvedSize === "fullwidth" ? 1 : 0,
|
|
971
|
+
flexShrink: (prepared) => prepared.hasDeterminateRows ? 1 : 0,
|
|
972
|
+
flexBasis: (prepared) => prepared.hasDeterminateRows ? resolvedSize === "fullwidth" ? DEFAULT_BAR_SIZE : resolvedSize : 0,
|
|
973
|
+
minWidth: (prepared) => prepared.hasDeterminateRows ? resolvedSize === "fullwidth" ? 4 : resolvedSize : 0,
|
|
974
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(BarCell, {
|
|
975
|
+
task,
|
|
976
|
+
width: ctx.width
|
|
977
|
+
})
|
|
978
|
+
};
|
|
979
|
+
};
|
|
927
980
|
const amount = () => ({
|
|
928
981
|
prepare: measureAmountLayout,
|
|
929
982
|
align: "right",
|
|
@@ -940,9 +993,19 @@ const elapsed = () => ({
|
|
|
940
993
|
now: ctx.now
|
|
941
994
|
})
|
|
942
995
|
});
|
|
996
|
+
const elapsedEta = () => ({
|
|
997
|
+
align: "right",
|
|
998
|
+
flexShrink: 0,
|
|
999
|
+
minWidth: 11,
|
|
1000
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(ElapsedEtaCell, {
|
|
1001
|
+
task,
|
|
1002
|
+
now: ctx.now
|
|
1003
|
+
})
|
|
1004
|
+
});
|
|
943
1005
|
const eta = () => ({
|
|
944
1006
|
align: "right",
|
|
945
1007
|
flexShrink: 0,
|
|
1008
|
+
minWidth: 8,
|
|
946
1009
|
render: ({ task }, ctx) => /* @__PURE__ */ jsx(EtaCell, {
|
|
947
1010
|
task,
|
|
948
1011
|
now: ctx.now
|
|
@@ -952,8 +1015,7 @@ const defaults = () => [
|
|
|
952
1015
|
description(),
|
|
953
1016
|
bar(),
|
|
954
1017
|
amount(),
|
|
955
|
-
|
|
956
|
-
eta()
|
|
1018
|
+
elapsedEta()
|
|
957
1019
|
];
|
|
958
1020
|
const resolveColumnSizeValue = (value, prepared) => {
|
|
959
1021
|
if (typeof value === "function") return value(prepared);
|
|
@@ -1431,4 +1493,4 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
|
|
|
1431
1493
|
})));
|
|
1432
1494
|
|
|
1433
1495
|
//#endregion
|
|
1434
|
-
export { columns_exports as Columns, Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|
|
1496
|
+
export { columns_exports as Columns, Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskProgressSampleSchema, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effective-progress",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Effect-first terminal progress bars with nested multibar support",
|
|
5
5
|
"homepage": "https://github.com/stromseng/effective-progress#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"cli-spinners": "^3.4.0",
|
|
46
46
|
"fast-string-width": "^3.0.2",
|
|
47
|
-
"ink": "^
|
|
47
|
+
"ink": "^7.0.1",
|
|
48
48
|
"react": "^19.2.4"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
@@ -64,8 +64,5 @@
|
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=20.12.0"
|
|
67
|
-
},
|
|
68
|
-
"patchedDependencies": {
|
|
69
|
-
"ink@6.8.0": "patches/ink@6.8.0.patch"
|
|
70
67
|
}
|
|
71
68
|
}
|