effective-progress 0.2.3 → 0.2.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effective-progress",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
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": {
package/src/renderer.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Effect, Ref, Schema } from "effect";
1
+ import { Clock, Duration, Effect, Ref, Schema } from "effect";
2
2
  import {
3
3
  type CompiledProgressBarColors,
4
4
  compileProgressBarColors,
@@ -27,34 +27,45 @@ const renderDeterminate = (
27
27
  return `${colors.brackets(progressbar.leftBracket)}${bar}${colors.brackets(progressbar.rightBracket)} ${units.completed}/${units.total} ${colors.percent(percent + "%")}`;
28
28
  };
29
29
 
30
+ const formatElapsed = (snapshot: TaskSnapshot, now: number): string => {
31
+ const elapsedMillis = (snapshot.completedAt ?? now) - snapshot.startedAt;
32
+ const duration =
33
+ snapshot.status === "running"
34
+ ? Duration.seconds(Math.floor(elapsedMillis / 1000))
35
+ : Duration.millis(elapsedMillis);
36
+ return ` (${Duration.format(duration)})`;
37
+ };
38
+
30
39
  const buildTaskLine = (
31
40
  snapshot: TaskSnapshot,
32
41
  depth: number,
33
42
  tick: number,
34
43
  colors: CompiledProgressBarColors,
44
+ now: number,
35
45
  ): string => {
36
46
  const progressbar = snapshot.config;
37
47
  const prefix = `${" ".repeat(depth)}- ${snapshot.description}: `;
48
+ const elapsed = formatElapsed(snapshot, now);
38
49
 
39
50
  if (snapshot.status === "failed") {
40
- return `${prefix}${colors.failed("[failed]")}`;
51
+ return `${prefix}${colors.failed("[failed]")}${elapsed}`;
41
52
  }
42
53
 
43
54
  if (snapshot.status === "done") {
44
55
  if (snapshot.units._tag === "DeterminateTaskUnits") {
45
- return `${prefix}${colors.done("[done]")} ${snapshot.units.completed}/${snapshot.units.total}`;
56
+ return `${prefix}${colors.done("[done]")} ${snapshot.units.completed}/${snapshot.units.total}${elapsed}`;
46
57
  }
47
- return `${prefix}${colors.done("[done]")}`;
58
+ return `${prefix}${colors.done("[done]")}${elapsed}`;
48
59
  }
49
60
 
50
61
  if (snapshot.units._tag === "DeterminateTaskUnits") {
51
- return prefix + renderDeterminate(snapshot.units, progressbar, colors);
62
+ return prefix + renderDeterminate(snapshot.units, progressbar, colors) + elapsed;
52
63
  }
53
64
 
54
65
  const frames = progressbar.spinnerFrames;
55
66
  const frameIndex = (snapshot.units.spinnerFrame + tick) % frames.length;
56
67
  const frame = frames[frameIndex] ?? frames[0]!;
57
- return `${prefix}${colors.spinner(frame)}`;
68
+ return `${prefix}${colors.spinner(frame)}${elapsed}`;
58
69
  };
59
70
 
60
71
  export const runProgressServiceRenderer = (
@@ -170,10 +181,11 @@ export const runProgressServiceRenderer = (
170
181
  if (!snapshot || (snapshot.transient && snapshot.status !== "running")) return [];
171
182
  return [{ snapshot, depth: row.depth }];
172
183
  });
184
+ const now = yield* Clock.currentTimeMillis;
173
185
  const frameTick = mode === "final" ? tick + 1 : tick;
174
186
  const taskLines = ordered.map(({ snapshot, depth }) => {
175
187
  const lineTick = isTTY ? frameTick : 0;
176
- return buildTaskLine(snapshot, depth, lineTick, getCompiledColors(snapshot.config));
188
+ return buildTaskLine(snapshot, depth, lineTick, getCompiledColors(snapshot.config), now);
177
189
  });
178
190
 
179
191
  if (isTTY) {
package/src/runtime.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, Effect, Exit, FiberRef, Layer, Option, Ref } from "effect";
1
+ import { Clock, Context, Effect, Exit, FiberRef, Layer, Option, Ref } from "effect";
2
2
  import { dual } from "effect/Function";
3
3
  import { mergeWith } from "es-toolkit/object";
4
4
  import { formatWithOptions } from "node:util";
@@ -82,6 +82,8 @@ const updatedSnapshot = (snapshot: TaskSnapshot, options: UpdateTaskOptions): Ta
82
82
  transient: options.transient ?? snapshot.transient,
83
83
  units,
84
84
  config: snapshot.config,
85
+ startedAt: snapshot.startedAt,
86
+ completedAt: snapshot.completedAt,
85
87
  });
86
88
  };
87
89
 
@@ -181,6 +183,7 @@ const makeProgressService = Effect.gen(function* () {
181
183
  mergeConfig(inheritedProgressBarConfig, options.progressbar),
182
184
  );
183
185
 
186
+ const now = yield* Clock.currentTimeMillis;
184
187
  const parentIdValue = Option.getOrNull(resolvedParentId);
185
188
  const snapshot = new TaskSnapshot({
186
189
  id: taskId,
@@ -190,6 +193,8 @@ const makeProgressService = Effect.gen(function* () {
190
193
  transient: options.transient ?? false,
191
194
  units,
192
195
  config: resolvedProgressBarConfig,
196
+ startedAt: now,
197
+ completedAt: null,
193
198
  });
194
199
 
195
200
  yield* Ref.update(storeRef, (s) => {
@@ -240,6 +245,8 @@ const makeProgressService = Effect.gen(function* () {
240
245
  transient: snapshot.transient,
241
246
  units,
242
247
  config: snapshot.config,
248
+ startedAt: snapshot.startedAt,
249
+ completedAt: snapshot.completedAt,
243
250
  }),
244
251
  );
245
252
 
@@ -247,62 +254,74 @@ const makeProgressService = Effect.gen(function* () {
247
254
  }).pipe(Effect.zipRight(markDirty));
248
255
 
249
256
  const completeTask = (taskId: TaskId) =>
250
- Ref.update(storeRef, (store) => {
251
- const snapshot = store.tasks.get(taskId);
252
- if (!snapshot) return store;
253
-
254
- const nextTasks = new Map(store.tasks);
255
- if (snapshot.transient) {
256
- nextTasks.delete(taskId);
257
- return { tasks: nextTasks, renderOrder: removeFromRenderOrder(store.renderOrder, taskId) };
258
- }
259
-
260
- nextTasks.set(
261
- taskId,
262
- new TaskSnapshot({
263
- id: snapshot.id,
264
- parentId: snapshot.parentId,
265
- description: snapshot.description,
266
- status: "done",
267
- transient: snapshot.transient,
268
- units:
269
- snapshot.units._tag === "DeterminateTaskUnits"
270
- ? new DeterminateTaskUnits({
271
- completed: snapshot.units.total,
272
- total: snapshot.units.total,
273
- })
274
- : snapshot.units,
275
- config: snapshot.config,
276
- }),
277
- );
278
- return { tasks: nextTasks, renderOrder: store.renderOrder };
279
- }).pipe(Effect.zipRight(markDirty));
257
+ Effect.gen(function* () {
258
+ const now = yield* Clock.currentTimeMillis;
259
+ yield* Ref.update(storeRef, (store) => {
260
+ const snapshot = store.tasks.get(taskId);
261
+ if (!snapshot) return store;
262
+
263
+ const nextTasks = new Map(store.tasks);
264
+ if (snapshot.transient) {
265
+ nextTasks.delete(taskId);
266
+ return { tasks: nextTasks, renderOrder: removeFromRenderOrder(store.renderOrder, taskId) };
267
+ }
268
+
269
+ nextTasks.set(
270
+ taskId,
271
+ new TaskSnapshot({
272
+ id: snapshot.id,
273
+ parentId: snapshot.parentId,
274
+ description: snapshot.description,
275
+ status: "done",
276
+ transient: snapshot.transient,
277
+ units:
278
+ snapshot.units._tag === "DeterminateTaskUnits"
279
+ ? new DeterminateTaskUnits({
280
+ completed: snapshot.units.total,
281
+ total: snapshot.units.total,
282
+ })
283
+ : snapshot.units,
284
+ config: snapshot.config,
285
+ startedAt: snapshot.startedAt,
286
+ completedAt: now,
287
+ }),
288
+ );
289
+ return { tasks: nextTasks, renderOrder: store.renderOrder };
290
+ });
291
+ yield* markDirty;
292
+ });
280
293
 
281
294
  const failTask = (taskId: TaskId) =>
282
- Ref.update(storeRef, (store) => {
283
- const snapshot = store.tasks.get(taskId);
284
- if (!snapshot) return store;
285
-
286
- const nextTasks = new Map(store.tasks);
287
- if (snapshot.transient) {
288
- nextTasks.delete(taskId);
289
- return { tasks: nextTasks, renderOrder: removeFromRenderOrder(store.renderOrder, taskId) };
290
- }
291
-
292
- nextTasks.set(
293
- taskId,
294
- new TaskSnapshot({
295
- id: snapshot.id,
296
- parentId: snapshot.parentId,
297
- description: snapshot.description,
298
- status: "failed",
299
- transient: snapshot.transient,
300
- units: snapshot.units,
301
- config: snapshot.config,
302
- }),
303
- );
304
- return { tasks: nextTasks, renderOrder: store.renderOrder };
305
- }).pipe(Effect.zipRight(markDirty));
295
+ Effect.gen(function* () {
296
+ const now = yield* Clock.currentTimeMillis;
297
+ yield* Ref.update(storeRef, (store) => {
298
+ const snapshot = store.tasks.get(taskId);
299
+ if (!snapshot) return store;
300
+
301
+ const nextTasks = new Map(store.tasks);
302
+ if (snapshot.transient) {
303
+ nextTasks.delete(taskId);
304
+ return { tasks: nextTasks, renderOrder: removeFromRenderOrder(store.renderOrder, taskId) };
305
+ }
306
+
307
+ nextTasks.set(
308
+ taskId,
309
+ new TaskSnapshot({
310
+ id: snapshot.id,
311
+ parentId: snapshot.parentId,
312
+ description: snapshot.description,
313
+ status: "failed",
314
+ transient: snapshot.transient,
315
+ units: snapshot.units,
316
+ config: snapshot.config,
317
+ startedAt: snapshot.startedAt,
318
+ completedAt: now,
319
+ }),
320
+ );
321
+ return { tasks: nextTasks, renderOrder: store.renderOrder };
322
+ });
323
+ yield* markDirty;
324
+ });
306
325
 
307
326
  const appendLog = (args: ReadonlyArray<unknown>) =>
308
327
  Effect.gen(function* () {
package/src/types.ts CHANGED
@@ -25,7 +25,7 @@ export const decodeProgressBarConfigSync = Schema.decodeUnknownSync(ProgressBarC
25
25
 
26
26
  export const defaultRendererConfig: RendererConfigShape = {
27
27
  disableUserInput: true,
28
- renderIntervalMillis: 50, // 20 FPS
28
+ renderIntervalMillis: 100, // 10 FPS
29
29
  maxLogLines: 0,
30
30
  nonTtyUpdateStep: 5,
31
31
  };
@@ -103,6 +103,8 @@ export class TaskSnapshot extends Schema.TaggedClass<TaskSnapshot>()("TaskSnapsh
103
103
  transient: Schema.Boolean,
104
104
  units: TaskUnitsSchema,
105
105
  config: ProgressBarConfigSchema,
106
+ startedAt: Schema.Number,
107
+ completedAt: Schema.NullOr(Schema.Number),
106
108
  }) {}
107
109
 
108
110
  export interface RenderRow {