effective-progress 0.5.3 → 0.6.0
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 +23 -31
- package/dist/index.d.mts +30 -21
- package/dist/index.mjs +1384 -586
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,454 @@
|
|
|
1
|
-
import { Brand, Clock, Context, Effect, Exit, FiberRef, Layer, Option,
|
|
1
|
+
import { Brand, Cause, Clock, Context, Effect, Exit, FiberRef, Layer, Option, Schema } from "effect";
|
|
2
2
|
import { dual } from "effect/Function";
|
|
3
|
-
import { Writable } from "node:stream";
|
|
4
3
|
import { Box, Text, render } from "ink";
|
|
5
|
-
import {
|
|
4
|
+
import { useEffect, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import stringWidth from "string-width";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
|
|
8
|
+
//#region src/types.ts
|
|
9
|
+
const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
|
|
10
|
+
const TaskId = Brand.nominal();
|
|
11
|
+
const TaskStatusSchema = Schema.Literal("running", "done", "failed");
|
|
12
|
+
const TaskCountDisplaySchema = Schema.Literal("processedOnly", "detailed");
|
|
13
|
+
var DeterminateTaskUnits = class extends Schema.TaggedClass()("DeterminateTaskUnits", {
|
|
14
|
+
succeeded: Schema.Number,
|
|
15
|
+
failed: Schema.Number,
|
|
16
|
+
processed: Schema.Number,
|
|
17
|
+
total: Schema.Number
|
|
18
|
+
}) {};
|
|
19
|
+
var IndeterminateTaskUnits = class extends Schema.TaggedClass()("IndeterminateTaskUnits", {}) {};
|
|
20
|
+
const TaskUnitsSchema = Schema.Union(DeterminateTaskUnits, IndeterminateTaskUnits);
|
|
21
|
+
var TaskSnapshot = class extends Schema.TaggedClass()("TaskSnapshot", {
|
|
22
|
+
id: TaskIdSchema,
|
|
23
|
+
parentId: Schema.NullOr(TaskIdSchema),
|
|
24
|
+
description: Schema.String,
|
|
25
|
+
status: TaskStatusSchema,
|
|
26
|
+
countDisplay: TaskCountDisplaySchema,
|
|
27
|
+
transient: Schema.Boolean,
|
|
28
|
+
units: TaskUnitsSchema,
|
|
29
|
+
startedAt: Schema.Number,
|
|
30
|
+
completedAt: Schema.NullOr(Schema.Number)
|
|
31
|
+
}) {};
|
|
32
|
+
var Task = class extends Context.Tag("stromseng.dev/effective-progress/Task")() {};
|
|
33
|
+
var TaskAddedEvent = class extends Schema.TaggedClass()("TaskAdded", {
|
|
34
|
+
taskId: TaskIdSchema,
|
|
35
|
+
parentId: Schema.NullOr(TaskIdSchema),
|
|
36
|
+
description: Schema.String,
|
|
37
|
+
total: Schema.optional(Schema.Number),
|
|
38
|
+
transient: Schema.Boolean,
|
|
39
|
+
countDisplay: TaskCountDisplaySchema
|
|
40
|
+
}) {};
|
|
41
|
+
var TaskUpdatedEvent = class extends Schema.TaggedClass()("TaskUpdated", {
|
|
42
|
+
taskId: TaskIdSchema,
|
|
43
|
+
description: Schema.optional(Schema.String),
|
|
44
|
+
succeeded: Schema.optional(Schema.Number),
|
|
45
|
+
failed: Schema.optional(Schema.Number),
|
|
46
|
+
processed: Schema.optional(Schema.Number),
|
|
47
|
+
total: Schema.optional(Schema.Number),
|
|
48
|
+
transient: Schema.optional(Schema.Boolean),
|
|
49
|
+
countDisplay: Schema.optional(TaskCountDisplaySchema)
|
|
50
|
+
}) {};
|
|
51
|
+
var TaskAdvancedEvent = class extends Schema.TaggedClass()("TaskAdvanced", {
|
|
52
|
+
taskId: TaskIdSchema,
|
|
53
|
+
amount: Schema.Number,
|
|
54
|
+
kind: Schema.Literal("succeeded", "failed")
|
|
55
|
+
}) {};
|
|
56
|
+
var TaskCompletedEvent = class extends Schema.TaggedClass()("TaskCompleted", { taskId: TaskIdSchema }) {};
|
|
57
|
+
var TaskFailedEvent = class extends Schema.TaggedClass()("TaskFailed", { taskId: TaskIdSchema }) {};
|
|
58
|
+
var TaskRemovedEvent = class extends Schema.TaggedClass()("TaskRemoved", { taskId: TaskIdSchema }) {};
|
|
59
|
+
const ProgressTaskEventSchema = Schema.Union(TaskAddedEvent, TaskUpdatedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskFailedEvent, TaskRemovedEvent);
|
|
60
|
+
const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema);
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/ink-renderer/snapshot/render-snapshot.ts
|
|
64
|
+
const orderedVisibleTasks = (store) => store.renderOrder.flatMap((row) => {
|
|
65
|
+
const snapshot = store.tasks.get(row.id);
|
|
66
|
+
if (!snapshot || snapshot.transient && snapshot.status !== "running") return [];
|
|
67
|
+
return [{
|
|
68
|
+
snapshot,
|
|
69
|
+
depth: row.depth
|
|
70
|
+
}];
|
|
71
|
+
});
|
|
72
|
+
const computeTreeInfo = (ordered) => {
|
|
73
|
+
const hasNextSiblingByIndex = Array.from({ length: ordered.length }, () => false);
|
|
74
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
75
|
+
const depth = ordered[i].depth;
|
|
76
|
+
for (let j = i + 1; j < ordered.length; j++) {
|
|
77
|
+
const candidateDepth = ordered[j].depth;
|
|
78
|
+
if (candidateDepth < depth) break;
|
|
79
|
+
if (candidateDepth === depth) {
|
|
80
|
+
hasNextSiblingByIndex[i] = true;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const ancestorStateByDepth = [];
|
|
86
|
+
return ordered.map((entry, index) => {
|
|
87
|
+
const depth = entry.depth;
|
|
88
|
+
ancestorStateByDepth.length = depth;
|
|
89
|
+
const hasChildren = index + 1 < ordered.length && ordered[index + 1] !== void 0 && ordered[index + 1].depth > depth;
|
|
90
|
+
const tree = {
|
|
91
|
+
depth,
|
|
92
|
+
hasNextSibling: hasNextSiblingByIndex[index] ?? false,
|
|
93
|
+
hasChildren,
|
|
94
|
+
ancestorHasNextSibling: [...ancestorStateByDepth]
|
|
95
|
+
};
|
|
96
|
+
ancestorStateByDepth[depth] = hasNextSiblingByIndex[index] ?? false;
|
|
97
|
+
return {
|
|
98
|
+
...entry,
|
|
99
|
+
tree
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
const toRenderSnapshot = (store) => {
|
|
104
|
+
const visibleTasks = orderedVisibleTasks(store);
|
|
105
|
+
const hasRunningTasks = visibleTasks.some((entry) => entry.snapshot.status === "running");
|
|
106
|
+
return {
|
|
107
|
+
rows: computeTreeInfo(visibleTasks).map((entry) => ({
|
|
108
|
+
task: entry.snapshot,
|
|
109
|
+
tree: entry.tree
|
|
110
|
+
})),
|
|
111
|
+
hasRunningTasks
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/ink-renderer/store.ts
|
|
117
|
+
const normalizeDeterminateCounts = (counts) => {
|
|
118
|
+
const total = Math.max(0, counts.total);
|
|
119
|
+
const failed = Math.min(total, Math.max(0, counts.failed));
|
|
120
|
+
const succeeded = Math.min(total - failed, Math.max(0, counts.succeeded));
|
|
121
|
+
return new DeterminateTaskUnits({
|
|
122
|
+
succeeded,
|
|
123
|
+
failed,
|
|
124
|
+
processed: succeeded + failed,
|
|
125
|
+
total
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const updateDeterminateCounts = (units, options) => normalizeDeterminateCounts({
|
|
129
|
+
succeeded: options.succeeded ?? units.succeeded,
|
|
130
|
+
failed: options.failed ?? units.failed,
|
|
131
|
+
total: options.total ?? units.total
|
|
132
|
+
});
|
|
133
|
+
const updatedSnapshot = (snapshot, options) => {
|
|
134
|
+
const currentUnits = snapshot.units;
|
|
135
|
+
const units = (() => {
|
|
136
|
+
if (options.total !== void 0) {
|
|
137
|
+
if (options.total <= 0) return new IndeterminateTaskUnits({});
|
|
138
|
+
if (currentUnits._tag === "DeterminateTaskUnits") return updateDeterminateCounts(currentUnits, options);
|
|
139
|
+
return normalizeDeterminateCounts({
|
|
140
|
+
succeeded: options.succeeded ?? 0,
|
|
141
|
+
failed: options.failed ?? 0,
|
|
142
|
+
total: options.total
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (currentUnits._tag === "DeterminateTaskUnits") {
|
|
146
|
+
if (options.succeeded === void 0 && options.failed === void 0) return currentUnits;
|
|
147
|
+
return updateDeterminateCounts(currentUnits, options);
|
|
148
|
+
}
|
|
149
|
+
return currentUnits;
|
|
150
|
+
})();
|
|
151
|
+
return new TaskSnapshot({
|
|
152
|
+
id: snapshot.id,
|
|
153
|
+
parentId: snapshot.parentId,
|
|
154
|
+
description: options.description ?? snapshot.description,
|
|
155
|
+
status: snapshot.status,
|
|
156
|
+
countDisplay: options.countDisplay ?? snapshot.countDisplay,
|
|
157
|
+
transient: options.transient ?? snapshot.transient,
|
|
158
|
+
units,
|
|
159
|
+
startedAt: snapshot.startedAt,
|
|
160
|
+
completedAt: snapshot.completedAt
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
const withTransient = (snapshot, transient) => new TaskSnapshot({
|
|
164
|
+
id: snapshot.id,
|
|
165
|
+
parentId: snapshot.parentId,
|
|
166
|
+
description: snapshot.description,
|
|
167
|
+
status: snapshot.status,
|
|
168
|
+
countDisplay: snapshot.countDisplay,
|
|
169
|
+
transient,
|
|
170
|
+
units: snapshot.units,
|
|
171
|
+
startedAt: snapshot.startedAt,
|
|
172
|
+
completedAt: snapshot.completedAt
|
|
173
|
+
});
|
|
174
|
+
const findInsertionIndex = (renderOrder, parentId) => {
|
|
175
|
+
if (parentId === null) return {
|
|
176
|
+
index: renderOrder.length,
|
|
177
|
+
depth: 0
|
|
178
|
+
};
|
|
179
|
+
const parentIdx = renderOrder.findIndex((row) => row.id === parentId);
|
|
180
|
+
if (parentIdx === -1) return {
|
|
181
|
+
index: renderOrder.length,
|
|
182
|
+
depth: 0
|
|
183
|
+
};
|
|
184
|
+
const parentDepth = renderOrder[parentIdx].depth;
|
|
185
|
+
let i = parentIdx + 1;
|
|
186
|
+
while (i < renderOrder.length && renderOrder[i].depth > parentDepth) i++;
|
|
187
|
+
return {
|
|
188
|
+
index: i,
|
|
189
|
+
depth: parentDepth + 1
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
const removeFromRenderOrder = (renderOrder, taskId) => {
|
|
193
|
+
const idx = renderOrder.findIndex((row) => row.id === taskId);
|
|
194
|
+
if (idx === -1) return renderOrder;
|
|
195
|
+
const taskDepth = renderOrder[idx].depth;
|
|
196
|
+
let end = idx + 1;
|
|
197
|
+
while (end < renderOrder.length && renderOrder[end].depth > taskDepth) end++;
|
|
198
|
+
const next = [...renderOrder];
|
|
199
|
+
next.splice(idx, end - idx);
|
|
200
|
+
return next;
|
|
201
|
+
};
|
|
202
|
+
const SNAPSHOT_PUBLISH_INTERVAL_MILLIS = 50;
|
|
203
|
+
const makeProgressRenderStore = () => {
|
|
204
|
+
let nextTaskId = 0;
|
|
205
|
+
let state = {
|
|
206
|
+
tasks: /* @__PURE__ */ new Map(),
|
|
207
|
+
renderOrder: []
|
|
208
|
+
};
|
|
209
|
+
let publishedSnapshot = toRenderSnapshot(state);
|
|
210
|
+
let hasPendingPublish = false;
|
|
211
|
+
let lastPublishAt = 0;
|
|
212
|
+
let publishTimeout;
|
|
213
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
214
|
+
const notifyListeners = () => {
|
|
215
|
+
for (const listener of listeners) listener();
|
|
216
|
+
};
|
|
217
|
+
const publishNow = () => {
|
|
218
|
+
hasPendingPublish = false;
|
|
219
|
+
lastPublishAt = Date.now();
|
|
220
|
+
publishedSnapshot = toRenderSnapshot(state);
|
|
221
|
+
notifyListeners();
|
|
222
|
+
};
|
|
223
|
+
const clearScheduledPublish = () => {
|
|
224
|
+
if (publishTimeout === void 0) return;
|
|
225
|
+
clearTimeout(publishTimeout);
|
|
226
|
+
publishTimeout = void 0;
|
|
227
|
+
};
|
|
228
|
+
const schedulePublish = () => {
|
|
229
|
+
if (!hasPendingPublish) return;
|
|
230
|
+
const now = Date.now();
|
|
231
|
+
const waitMillis = Math.max(0, SNAPSHOT_PUBLISH_INTERVAL_MILLIS - (now - lastPublishAt));
|
|
232
|
+
if (waitMillis === 0) {
|
|
233
|
+
clearScheduledPublish();
|
|
234
|
+
publishNow();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (publishTimeout !== void 0) return;
|
|
238
|
+
publishTimeout = setTimeout(() => {
|
|
239
|
+
publishTimeout = void 0;
|
|
240
|
+
if (!hasPendingPublish) return;
|
|
241
|
+
publishNow();
|
|
242
|
+
}, waitMillis);
|
|
243
|
+
};
|
|
244
|
+
const publish = (nextState) => {
|
|
245
|
+
if (nextState === state) return;
|
|
246
|
+
state = nextState;
|
|
247
|
+
hasPendingPublish = true;
|
|
248
|
+
schedulePublish();
|
|
249
|
+
};
|
|
250
|
+
const updateState = (transform) => {
|
|
251
|
+
publish(transform(state));
|
|
252
|
+
};
|
|
253
|
+
return {
|
|
254
|
+
getSnapshot: () => publishedSnapshot,
|
|
255
|
+
subscribe: (listener) => {
|
|
256
|
+
listeners.add(listener);
|
|
257
|
+
return () => {
|
|
258
|
+
listeners.delete(listener);
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
flush: () => {
|
|
262
|
+
if (!hasPendingPublish) return;
|
|
263
|
+
clearScheduledPublish();
|
|
264
|
+
publishNow();
|
|
265
|
+
},
|
|
266
|
+
addTask: (options) => Effect.gen(function* () {
|
|
267
|
+
const taskId = TaskId(++nextTaskId);
|
|
268
|
+
const units = options.total === void 0 || options.total <= 0 ? new IndeterminateTaskUnits({}) : normalizeDeterminateCounts({
|
|
269
|
+
succeeded: 0,
|
|
270
|
+
failed: 0,
|
|
271
|
+
total: options.total
|
|
272
|
+
});
|
|
273
|
+
const parentSnapshot = options.parentId === void 0 ? void 0 : state.tasks.get(options.parentId);
|
|
274
|
+
const now = yield* Clock.currentTimeMillis;
|
|
275
|
+
const parentId = options.parentId ?? null;
|
|
276
|
+
const countDisplay = options.countDisplay ?? parentSnapshot?.countDisplay ?? "detailed";
|
|
277
|
+
const task = new TaskSnapshot({
|
|
278
|
+
id: taskId,
|
|
279
|
+
parentId,
|
|
280
|
+
description: options.description,
|
|
281
|
+
status: "running",
|
|
282
|
+
countDisplay,
|
|
283
|
+
transient: (parentSnapshot?.transient ?? false) || (options.transient ?? false),
|
|
284
|
+
units,
|
|
285
|
+
startedAt: now,
|
|
286
|
+
completedAt: null
|
|
287
|
+
});
|
|
288
|
+
updateState((current) => {
|
|
289
|
+
const nextTasks = new Map(current.tasks);
|
|
290
|
+
nextTasks.set(taskId, task);
|
|
291
|
+
const { index, depth } = findInsertionIndex(current.renderOrder, parentId);
|
|
292
|
+
const nextRenderOrder = [...current.renderOrder];
|
|
293
|
+
nextRenderOrder.splice(index, 0, {
|
|
294
|
+
id: taskId,
|
|
295
|
+
depth
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
tasks: nextTasks,
|
|
299
|
+
renderOrder: nextRenderOrder
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
return taskId;
|
|
303
|
+
}),
|
|
304
|
+
updateTask: (taskId, options) => Effect.sync(() => {
|
|
305
|
+
updateState((current) => {
|
|
306
|
+
const currentTask = current.tasks.get(taskId);
|
|
307
|
+
if (!currentTask) return current;
|
|
308
|
+
const nextTasks = new Map(current.tasks);
|
|
309
|
+
const nextTask = updatedSnapshot(currentTask, options);
|
|
310
|
+
nextTasks.set(taskId, nextTask);
|
|
311
|
+
if (options.transient !== void 0) for (const [candidateId, candidate] of current.tasks.entries()) {
|
|
312
|
+
if (candidateId === taskId) continue;
|
|
313
|
+
let parentId = candidate.parentId;
|
|
314
|
+
let isDescendant = false;
|
|
315
|
+
while (parentId !== null) {
|
|
316
|
+
if (parentId === taskId) {
|
|
317
|
+
isDescendant = true;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
parentId = current.tasks.get(parentId)?.parentId ?? null;
|
|
321
|
+
}
|
|
322
|
+
if (isDescendant) nextTasks.set(candidateId, withTransient(candidate, nextTask.transient));
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
tasks: nextTasks,
|
|
326
|
+
renderOrder: current.renderOrder
|
|
327
|
+
};
|
|
328
|
+
});
|
|
329
|
+
}),
|
|
330
|
+
addSuccess: (taskId, amount = 1) => Effect.sync(() => {
|
|
331
|
+
updateState((current) => {
|
|
332
|
+
const currentTask = current.tasks.get(taskId);
|
|
333
|
+
if (!currentTask || currentTask.units._tag !== "DeterminateTaskUnits") return current;
|
|
334
|
+
const nextTasks = new Map(current.tasks);
|
|
335
|
+
nextTasks.set(taskId, new TaskSnapshot({
|
|
336
|
+
id: currentTask.id,
|
|
337
|
+
parentId: currentTask.parentId,
|
|
338
|
+
description: currentTask.description,
|
|
339
|
+
status: currentTask.status,
|
|
340
|
+
countDisplay: currentTask.countDisplay,
|
|
341
|
+
transient: currentTask.transient,
|
|
342
|
+
units: normalizeDeterminateCounts({
|
|
343
|
+
succeeded: currentTask.units.succeeded + amount,
|
|
344
|
+
failed: currentTask.units.failed,
|
|
345
|
+
total: currentTask.units.total
|
|
346
|
+
}),
|
|
347
|
+
startedAt: currentTask.startedAt,
|
|
348
|
+
completedAt: currentTask.completedAt
|
|
349
|
+
}));
|
|
350
|
+
return {
|
|
351
|
+
tasks: nextTasks,
|
|
352
|
+
renderOrder: current.renderOrder
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
}),
|
|
356
|
+
addFailure: (taskId, amount = 1) => Effect.sync(() => {
|
|
357
|
+
updateState((current) => {
|
|
358
|
+
const currentTask = current.tasks.get(taskId);
|
|
359
|
+
if (!currentTask || currentTask.units._tag !== "DeterminateTaskUnits") return current;
|
|
360
|
+
const nextTasks = new Map(current.tasks);
|
|
361
|
+
nextTasks.set(taskId, new TaskSnapshot({
|
|
362
|
+
id: currentTask.id,
|
|
363
|
+
parentId: currentTask.parentId,
|
|
364
|
+
description: currentTask.description,
|
|
365
|
+
status: currentTask.status,
|
|
366
|
+
countDisplay: currentTask.countDisplay,
|
|
367
|
+
transient: currentTask.transient,
|
|
368
|
+
units: normalizeDeterminateCounts({
|
|
369
|
+
succeeded: currentTask.units.succeeded,
|
|
370
|
+
failed: currentTask.units.failed + amount,
|
|
371
|
+
total: currentTask.units.total
|
|
372
|
+
}),
|
|
373
|
+
startedAt: currentTask.startedAt,
|
|
374
|
+
completedAt: currentTask.completedAt
|
|
375
|
+
}));
|
|
376
|
+
return {
|
|
377
|
+
tasks: nextTasks,
|
|
378
|
+
renderOrder: current.renderOrder
|
|
379
|
+
};
|
|
380
|
+
});
|
|
381
|
+
}),
|
|
382
|
+
completeTask: (taskId) => Effect.gen(function* () {
|
|
383
|
+
const now = yield* Clock.currentTimeMillis;
|
|
384
|
+
updateState((current) => {
|
|
385
|
+
const currentTask = current.tasks.get(taskId);
|
|
386
|
+
if (!currentTask) return current;
|
|
387
|
+
const nextTasks = new Map(current.tasks);
|
|
388
|
+
if (currentTask.transient) {
|
|
389
|
+
nextTasks.delete(taskId);
|
|
390
|
+
return {
|
|
391
|
+
tasks: nextTasks,
|
|
392
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId)
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
nextTasks.set(taskId, new TaskSnapshot({
|
|
396
|
+
id: currentTask.id,
|
|
397
|
+
parentId: currentTask.parentId,
|
|
398
|
+
description: currentTask.description,
|
|
399
|
+
status: "done",
|
|
400
|
+
countDisplay: currentTask.countDisplay,
|
|
401
|
+
transient: currentTask.transient,
|
|
402
|
+
units: currentTask.units._tag === "DeterminateTaskUnits" ? normalizeDeterminateCounts({
|
|
403
|
+
succeeded: currentTask.units.total - currentTask.units.failed,
|
|
404
|
+
failed: currentTask.units.failed,
|
|
405
|
+
total: currentTask.units.total
|
|
406
|
+
}) : currentTask.units,
|
|
407
|
+
startedAt: currentTask.startedAt,
|
|
408
|
+
completedAt: now
|
|
409
|
+
}));
|
|
410
|
+
return {
|
|
411
|
+
tasks: nextTasks,
|
|
412
|
+
renderOrder: current.renderOrder
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
}),
|
|
416
|
+
failTask: (taskId) => Effect.gen(function* () {
|
|
417
|
+
const now = yield* Clock.currentTimeMillis;
|
|
418
|
+
updateState((current) => {
|
|
419
|
+
const currentTask = current.tasks.get(taskId);
|
|
420
|
+
if (!currentTask) return current;
|
|
421
|
+
const nextTasks = new Map(current.tasks);
|
|
422
|
+
if (currentTask.transient) {
|
|
423
|
+
nextTasks.delete(taskId);
|
|
424
|
+
return {
|
|
425
|
+
tasks: nextTasks,
|
|
426
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId)
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
nextTasks.set(taskId, new TaskSnapshot({
|
|
430
|
+
id: currentTask.id,
|
|
431
|
+
parentId: currentTask.parentId,
|
|
432
|
+
description: currentTask.description,
|
|
433
|
+
status: "failed",
|
|
434
|
+
countDisplay: currentTask.countDisplay,
|
|
435
|
+
transient: currentTask.transient,
|
|
436
|
+
units: currentTask.units,
|
|
437
|
+
startedAt: currentTask.startedAt,
|
|
438
|
+
completedAt: now
|
|
439
|
+
}));
|
|
440
|
+
return {
|
|
441
|
+
tasks: nextTasks,
|
|
442
|
+
renderOrder: current.renderOrder
|
|
443
|
+
};
|
|
444
|
+
});
|
|
445
|
+
}),
|
|
446
|
+
getTask: (taskId) => Effect.sync(() => Option.fromNullable(state.tasks.get(taskId))),
|
|
447
|
+
listTasks: Effect.sync(() => Array.from(state.tasks.values()))
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
//#endregion
|
|
7
452
|
//#region src/ink-renderer/format.ts
|
|
8
453
|
const SPINNER_FRAMES = [
|
|
9
454
|
"⠋",
|
|
@@ -34,669 +479,999 @@ const formatElapsed = (task, now) => {
|
|
|
34
479
|
};
|
|
35
480
|
const formatEta = (task, now) => {
|
|
36
481
|
if (task.status !== "running" || task.units._tag !== "DeterminateTaskUnits") return "";
|
|
37
|
-
const {
|
|
38
|
-
const remaining = total -
|
|
39
|
-
if (
|
|
482
|
+
const { processed, total } = task.units;
|
|
483
|
+
const remaining = total - processed;
|
|
484
|
+
if (processed <= 0 || remaining <= 0) return "";
|
|
40
485
|
const elapsedMillis = Math.max(1, now - task.startedAt);
|
|
41
|
-
return formatDurationSeconds(Math.max(0, Math.floor(elapsedMillis /
|
|
486
|
+
return formatDurationSeconds(Math.max(0, Math.floor(elapsedMillis / processed * remaining)) / 1e3);
|
|
42
487
|
};
|
|
43
|
-
const
|
|
488
|
+
const getTaskIndicator = (task, tick) => {
|
|
489
|
+
if (task.status === "running") return {
|
|
490
|
+
symbol: SPINNER_FRAMES[tick % SPINNER_FRAMES.length] ?? SPINNER_FRAMES[0],
|
|
491
|
+
color: "yellow"
|
|
492
|
+
};
|
|
493
|
+
if (task.status === "failed") return {
|
|
494
|
+
symbol: "✗",
|
|
495
|
+
color: "red"
|
|
496
|
+
};
|
|
497
|
+
if (task.units._tag !== "DeterminateTaskUnits") return {
|
|
498
|
+
symbol: "✓",
|
|
499
|
+
color: "green"
|
|
500
|
+
};
|
|
501
|
+
const { succeeded, failed, processed, total } = task.units;
|
|
502
|
+
if (failed === 0 && processed === total) return {
|
|
503
|
+
symbol: "✓",
|
|
504
|
+
color: "green"
|
|
505
|
+
};
|
|
506
|
+
if (failed > 0 && succeeded > 0) return {
|
|
507
|
+
symbol: "~",
|
|
508
|
+
color: "yellow"
|
|
509
|
+
};
|
|
510
|
+
if (failed > 0 && succeeded === 0) return {
|
|
511
|
+
symbol: "✗",
|
|
512
|
+
color: "red"
|
|
513
|
+
};
|
|
514
|
+
return {
|
|
515
|
+
symbol: "✓",
|
|
516
|
+
color: "green"
|
|
517
|
+
};
|
|
518
|
+
};
|
|
519
|
+
const formatDeterminateAmountParts = (task) => {
|
|
520
|
+
if (task.units._tag !== "DeterminateTaskUnits") return;
|
|
521
|
+
const totalText = `${task.units.total}`;
|
|
522
|
+
const width = totalText.length;
|
|
523
|
+
const processedText = `${task.units.processed}`;
|
|
524
|
+
return {
|
|
525
|
+
succeeded: task.countDisplay === "detailed" ? `${task.units.succeeded}`.padStart(width, " ") : "",
|
|
526
|
+
failed: task.countDisplay === "detailed" ? `${task.units.failed}`.padStart(width, " ") : "",
|
|
527
|
+
processed: processedText,
|
|
528
|
+
total: totalText
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
const formatAmount = (task, _tick) => {
|
|
44
532
|
if (task.units._tag === "DeterminateTaskUnits") {
|
|
45
|
-
const
|
|
46
|
-
|
|
533
|
+
const parts = formatDeterminateAmountParts(task);
|
|
534
|
+
if (parts === void 0) return "";
|
|
535
|
+
if (task.countDisplay === "detailed") return `${parts.succeeded} ${parts.failed} ${parts.processed}/${parts.total}`;
|
|
536
|
+
return `${parts.processed}/${parts.total}`;
|
|
47
537
|
}
|
|
48
|
-
if (task.status === "running"
|
|
49
|
-
return task.status === "
|
|
538
|
+
if (task.status === "running" && task.units._tag === "IndeterminateTaskUnits") return "";
|
|
539
|
+
return task.status === "failed" ? "✗" : "";
|
|
50
540
|
};
|
|
51
541
|
|
|
52
542
|
//#endregion
|
|
53
|
-
//#region src/ink-renderer/
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
543
|
+
//#region src/ink-renderer/columns/determinate.ts
|
|
544
|
+
const isDeterminate = (task) => task.units._tag === "DeterminateTaskUnits";
|
|
545
|
+
const hasDeterminateRows = (rows) => rows.some((row) => isDeterminate(row.task));
|
|
546
|
+
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/ink-renderer/columns/spec.ts
|
|
549
|
+
const WIDTH_CACHE_LIMIT = 4096;
|
|
550
|
+
const widthCache = /* @__PURE__ */ new Map();
|
|
551
|
+
const textWidth = (text) => {
|
|
552
|
+
const cached = widthCache.get(text);
|
|
553
|
+
if (cached !== void 0) return cached;
|
|
554
|
+
const width = stringWidth(text);
|
|
555
|
+
if (widthCache.size >= WIDTH_CACHE_LIMIT) widthCache.clear();
|
|
556
|
+
widthCache.set(text, width);
|
|
557
|
+
return width;
|
|
58
558
|
};
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
559
|
+
const resolveColumnSpec = (spec, resistance) => ({
|
|
560
|
+
id: spec.id,
|
|
561
|
+
grow: spec.grow,
|
|
562
|
+
canHide: spec.canHide,
|
|
563
|
+
variants: spec.variants.map((variant) => ({
|
|
564
|
+
id: variant.id,
|
|
565
|
+
minWidth: variant.minWidth,
|
|
566
|
+
idealWidth: variant.idealWidth,
|
|
567
|
+
maxWidth: variant.maxWidth,
|
|
568
|
+
shrinkResistance: resistance.shrink(spec.id, variant.id),
|
|
569
|
+
demoteResistance: resistance.demote(spec.id, variant.id),
|
|
570
|
+
hideResistance: resistance.hide(spec.id, variant.id),
|
|
571
|
+
renderCell: variant.renderCell
|
|
572
|
+
}))
|
|
573
|
+
});
|
|
574
|
+
const resolveColumnSpecs = (specs, resistance) => specs.flatMap((spec) => spec.variants.length > 0 ? [resolveColumnSpec(spec, resistance)] : []);
|
|
575
|
+
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/ink-renderer/columns/amount-column.tsx
|
|
578
|
+
const padLeft = (value, width) => value.padStart(Math.max(0, width), " ");
|
|
579
|
+
const padRight = (value, width) => value.padEnd(Math.max(0, width), " ");
|
|
580
|
+
const blank = (width) => " ".repeat(Math.max(0, width));
|
|
581
|
+
const shouldShowDetailedCounts = (task) => task.units._tag === "DeterminateTaskUnits" && task.countDisplay === "detailed";
|
|
582
|
+
const SucceededCountColumn = ({ task, width }) => {
|
|
583
|
+
if (width <= 0) return null;
|
|
584
|
+
if (task.units._tag !== "DeterminateTaskUnits") return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
585
|
+
if (!shouldShowDetailedCounts(task)) return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
586
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
587
|
+
color: "green",
|
|
588
|
+
children: padLeft(`${task.units.succeeded}`, width)
|
|
589
|
+
});
|
|
590
|
+
};
|
|
591
|
+
const FailedCountColumn = ({ task, width }) => {
|
|
592
|
+
if (width <= 0) return null;
|
|
593
|
+
if (task.units._tag !== "DeterminateTaskUnits") return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
594
|
+
if (!shouldShowDetailedCounts(task)) return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
595
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
596
|
+
color: "red",
|
|
597
|
+
children: padLeft(`${task.units.failed}`, width)
|
|
598
|
+
});
|
|
599
|
+
};
|
|
600
|
+
const ProcessedCountColumn = ({ task, width }) => {
|
|
601
|
+
if (width <= 0) return null;
|
|
602
|
+
if (task.units._tag !== "DeterminateTaskUnits") return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
603
|
+
return /* @__PURE__ */ jsx(Text, { children: padLeft(`${task.units.processed}`, width) });
|
|
604
|
+
};
|
|
605
|
+
const AmountSeparatorColumn = ({ task, tick }) => {
|
|
606
|
+
if (task.units._tag === "DeterminateTaskUnits") return /* @__PURE__ */ jsx(Text, { children: "/" });
|
|
607
|
+
const symbol = formatAmount(task, tick);
|
|
608
|
+
if (task.status === "failed") return /* @__PURE__ */ jsx(Text, {
|
|
609
|
+
color: "red",
|
|
610
|
+
children: symbol
|
|
611
|
+
});
|
|
612
|
+
if (task.status === "running") return /* @__PURE__ */ jsx(Text, {
|
|
613
|
+
color: "yellow",
|
|
614
|
+
children: symbol
|
|
615
|
+
});
|
|
616
|
+
return /* @__PURE__ */ jsx(Text, { children: symbol });
|
|
617
|
+
};
|
|
618
|
+
const TotalCountColumn = ({ task, width }) => {
|
|
619
|
+
if (width <= 0) return null;
|
|
620
|
+
if (task.units._tag !== "DeterminateTaskUnits") return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
621
|
+
return /* @__PURE__ */ jsx(Text, { children: padRight(`${task.units.total}`, width) });
|
|
622
|
+
};
|
|
623
|
+
const AmountColumn = ({ task, tick, layout }) => {
|
|
624
|
+
if (layout.kind === "text") {
|
|
625
|
+
const text = formatAmount(task, tick);
|
|
626
|
+
if (task.status === "failed") return /* @__PURE__ */ jsx(Text, {
|
|
627
|
+
wrap: "truncate-end",
|
|
628
|
+
color: "red",
|
|
629
|
+
children: text
|
|
630
|
+
});
|
|
631
|
+
if (task.status === "running") return /* @__PURE__ */ jsx(Text, {
|
|
632
|
+
wrap: "truncate-end",
|
|
633
|
+
color: "yellow",
|
|
634
|
+
children: text
|
|
635
|
+
});
|
|
636
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
637
|
+
wrap: "truncate-end",
|
|
638
|
+
children: text
|
|
639
|
+
});
|
|
71
640
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
641
|
+
if (layout.kind === "processed") return /* @__PURE__ */ jsxs(Box, {
|
|
642
|
+
flexDirection: "row",
|
|
643
|
+
children: [
|
|
644
|
+
/* @__PURE__ */ jsx(ProcessedCountColumn, {
|
|
645
|
+
task,
|
|
646
|
+
width: layout.processedWidth
|
|
647
|
+
}),
|
|
648
|
+
/* @__PURE__ */ jsx(AmountSeparatorColumn, {
|
|
649
|
+
task,
|
|
650
|
+
tick
|
|
651
|
+
}),
|
|
652
|
+
/* @__PURE__ */ jsx(TotalCountColumn, {
|
|
653
|
+
task,
|
|
654
|
+
width: layout.totalWidth
|
|
655
|
+
})
|
|
656
|
+
]
|
|
657
|
+
});
|
|
658
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
659
|
+
flexDirection: "row",
|
|
660
|
+
children: [
|
|
661
|
+
layout.succeededWidth > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SucceededCountColumn, {
|
|
662
|
+
task,
|
|
663
|
+
width: layout.succeededWidth
|
|
664
|
+
}), /* @__PURE__ */ jsx(Text, { children: ` ` })] }) : null,
|
|
665
|
+
layout.failedWidth > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FailedCountColumn, {
|
|
666
|
+
task,
|
|
667
|
+
width: layout.failedWidth
|
|
668
|
+
}), /* @__PURE__ */ jsx(Text, { children: ` ` })] }) : null,
|
|
669
|
+
/* @__PURE__ */ jsx(ProcessedCountColumn, {
|
|
670
|
+
task,
|
|
671
|
+
width: layout.processedWidth
|
|
672
|
+
}),
|
|
673
|
+
/* @__PURE__ */ jsx(AmountSeparatorColumn, {
|
|
674
|
+
task,
|
|
675
|
+
tick
|
|
676
|
+
}),
|
|
677
|
+
/* @__PURE__ */ jsx(TotalCountColumn, {
|
|
678
|
+
task,
|
|
679
|
+
width: layout.totalWidth
|
|
680
|
+
})
|
|
681
|
+
]
|
|
88
682
|
});
|
|
89
683
|
};
|
|
90
|
-
|
|
91
|
-
//#endregion
|
|
92
|
-
//#region src/ink-renderer/layout.ts
|
|
93
|
-
const DEFAULT_BAR_WIDTH = 20;
|
|
94
|
-
const MIN_DESCRIPTION_WIDTH = 8;
|
|
95
|
-
const MIN_BAR_WIDTH = 8;
|
|
96
|
-
const MIN_ELAPSED_WIDTH = 3;
|
|
97
|
-
const MIN_AMOUNT_WIDTH = 1;
|
|
98
|
-
const BASELINE_ROW_WIDTH = 100;
|
|
99
|
-
const MIN_DESCRIPTION_COLUMNS_FOR_TREE = 24;
|
|
100
|
-
const RESERVED_ELAPSED_WIDTH_UP_TO_ONE_HOUR = Array.from("59m 59s").length;
|
|
101
|
-
const RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR = Array.from("ETA: 59m 59s").length;
|
|
102
|
-
const textWidth = (text) => Array.from(text).length;
|
|
103
|
-
const computeWidths = (rows, now, tick, terminalColumns, includeTree = true) => {
|
|
684
|
+
const computeAmountMetrics = (rows, tick) => {
|
|
104
685
|
let hasDeterminate = false;
|
|
105
|
-
let
|
|
106
|
-
let
|
|
107
|
-
let
|
|
108
|
-
let eta = RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR;
|
|
686
|
+
let hasDetailed = false;
|
|
687
|
+
let totalDigits = 0;
|
|
688
|
+
let simpleTextWidth = 0;
|
|
109
689
|
for (const row of rows) {
|
|
110
|
-
const { task
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (task.status === "running" && task.units._tag === "DeterminateTaskUnits") {
|
|
117
|
-
const etaValue = formatEta(task, now);
|
|
118
|
-
const etaText = `ETA: ${etaValue.length > 0 ? etaValue : "--"}`;
|
|
119
|
-
eta = Math.max(eta, textWidth(etaText));
|
|
690
|
+
const { task } = row;
|
|
691
|
+
if (isDeterminate(task)) {
|
|
692
|
+
hasDeterminate = true;
|
|
693
|
+
totalDigits = Math.max(totalDigits, textWidth(`${task.units.total}`));
|
|
694
|
+
if (task.countDisplay === "detailed") hasDetailed = true;
|
|
695
|
+
continue;
|
|
120
696
|
}
|
|
121
|
-
|
|
122
|
-
let widths = {
|
|
123
|
-
description,
|
|
124
|
-
bar: hasDeterminate ? DEFAULT_BAR_WIDTH : 0,
|
|
125
|
-
amount,
|
|
126
|
-
elapsed,
|
|
127
|
-
eta
|
|
128
|
-
};
|
|
129
|
-
const visible = (w) => [
|
|
130
|
-
w.description,
|
|
131
|
-
w.bar,
|
|
132
|
-
w.amount,
|
|
133
|
-
w.elapsed,
|
|
134
|
-
w.eta
|
|
135
|
-
].filter((width) => width > 0);
|
|
136
|
-
const total = (w) => {
|
|
137
|
-
const cols = visible(w);
|
|
138
|
-
return cols.reduce((sum, width) => sum + width, 0) + Math.max(0, cols.length - 1);
|
|
139
|
-
};
|
|
140
|
-
const baselineTarget = Math.max(BASELINE_ROW_WIDTH, total(widths));
|
|
141
|
-
const target = terminalColumns === void 0 ? baselineTarget : Math.max(1, Math.min(Math.max(1, Math.floor(terminalColumns)), baselineTarget));
|
|
142
|
-
if (total(widths) < target) widths.description += target - total(widths);
|
|
143
|
-
else if (total(widths) > target) {
|
|
144
|
-
let overflow = total(widths) - target;
|
|
145
|
-
const reduceBy = (key, min) => {
|
|
146
|
-
if (overflow <= 0) return;
|
|
147
|
-
const current = widths[key];
|
|
148
|
-
if (current <= min) return;
|
|
149
|
-
const reducible = current - min;
|
|
150
|
-
const delta = Math.min(reducible, overflow);
|
|
151
|
-
widths = {
|
|
152
|
-
...widths,
|
|
153
|
-
[key]: current - delta
|
|
154
|
-
};
|
|
155
|
-
overflow -= delta;
|
|
156
|
-
};
|
|
157
|
-
reduceBy("description", MIN_DESCRIPTION_WIDTH);
|
|
158
|
-
reduceBy("eta", 0);
|
|
159
|
-
reduceBy("bar", MIN_BAR_WIDTH);
|
|
160
|
-
reduceBy("bar", 0);
|
|
161
|
-
reduceBy("elapsed", MIN_ELAPSED_WIDTH);
|
|
162
|
-
reduceBy("amount", MIN_AMOUNT_WIDTH);
|
|
163
|
-
reduceBy("description", 0);
|
|
164
|
-
if (total(widths) < target) widths.description += target - total(widths);
|
|
697
|
+
simpleTextWidth = Math.max(simpleTextWidth, textWidth(formatAmount(task, tick)));
|
|
165
698
|
}
|
|
166
699
|
return {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
elapsed: widths.elapsed,
|
|
172
|
-
eta: widths.eta
|
|
700
|
+
hasDeterminate,
|
|
701
|
+
hasDetailed,
|
|
702
|
+
totalDigits: Math.max(1, totalDigits),
|
|
703
|
+
simpleTextWidth
|
|
173
704
|
};
|
|
174
705
|
};
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
706
|
+
const detailedAmountLayout = (metrics) => ({
|
|
707
|
+
kind: "detailed",
|
|
708
|
+
succeededWidth: metrics.hasDetailed ? metrics.totalDigits : 0,
|
|
709
|
+
failedWidth: metrics.hasDetailed ? metrics.totalDigits : 0,
|
|
710
|
+
processedWidth: metrics.totalDigits,
|
|
711
|
+
totalWidth: metrics.totalDigits
|
|
712
|
+
});
|
|
713
|
+
const processedAmountLayout = (metrics) => ({
|
|
714
|
+
kind: "processed",
|
|
715
|
+
processedWidth: metrics.totalDigits,
|
|
716
|
+
totalWidth: metrics.totalDigits
|
|
717
|
+
});
|
|
718
|
+
const detailedAmountWidth = (metrics) => metrics.totalDigits + 1 + metrics.totalDigits + (metrics.hasDetailed ? metrics.totalDigits + 1 + metrics.totalDigits + 1 : 0);
|
|
719
|
+
const processedAmountWidth = (metrics) => metrics.totalDigits + 1 + metrics.totalDigits;
|
|
720
|
+
const createAmountColumnSpec = (context) => {
|
|
721
|
+
const metrics = computeAmountMetrics(context.rows, context.tick);
|
|
722
|
+
if (!metrics.hasDeterminate && metrics.simpleTextWidth <= 0) return;
|
|
723
|
+
const detailedLayout = detailedAmountLayout(metrics);
|
|
724
|
+
const processedLayout = processedAmountLayout(metrics);
|
|
725
|
+
const detailedWidth = detailedAmountWidth(metrics);
|
|
726
|
+
const processedWidth = processedAmountWidth(metrics);
|
|
727
|
+
return {
|
|
728
|
+
id: "amount",
|
|
729
|
+
grow: 0,
|
|
730
|
+
canHide: true,
|
|
731
|
+
variants: metrics.hasDeterminate && metrics.hasDetailed ? [{
|
|
732
|
+
id: "detailed",
|
|
733
|
+
minWidth: detailedWidth,
|
|
734
|
+
idealWidth: detailedWidth,
|
|
735
|
+
renderCell: (row) => /* @__PURE__ */ jsx(AmountColumn, {
|
|
736
|
+
task: row.task,
|
|
737
|
+
tick: context.tick,
|
|
738
|
+
layout: detailedLayout
|
|
739
|
+
})
|
|
740
|
+
}, {
|
|
741
|
+
id: "processed",
|
|
742
|
+
minWidth: processedWidth,
|
|
743
|
+
idealWidth: processedWidth,
|
|
744
|
+
renderCell: (row) => /* @__PURE__ */ jsx(AmountColumn, {
|
|
745
|
+
task: row.task,
|
|
746
|
+
tick: context.tick,
|
|
747
|
+
layout: processedLayout
|
|
748
|
+
})
|
|
749
|
+
}] : metrics.hasDeterminate ? [{
|
|
750
|
+
id: "processed",
|
|
751
|
+
minWidth: processedWidth,
|
|
752
|
+
idealWidth: processedWidth,
|
|
753
|
+
renderCell: (row) => /* @__PURE__ */ jsx(AmountColumn, {
|
|
754
|
+
task: row.task,
|
|
755
|
+
tick: context.tick,
|
|
756
|
+
layout: processedLayout
|
|
757
|
+
})
|
|
758
|
+
}] : [{
|
|
759
|
+
id: "text",
|
|
760
|
+
minWidth: 0,
|
|
761
|
+
idealWidth: metrics.simpleTextWidth,
|
|
762
|
+
renderCell: (row) => /* @__PURE__ */ jsx(AmountColumn, {
|
|
763
|
+
task: row.task,
|
|
764
|
+
tick: context.tick,
|
|
765
|
+
layout: { kind: "text" }
|
|
766
|
+
})
|
|
767
|
+
}]
|
|
768
|
+
};
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region src/ink-renderer/columns/bar-column.tsx
|
|
773
|
+
const DEFAULT_BAR_WIDTH = 30;
|
|
774
|
+
const MIN_BAR_WIDTH = 8;
|
|
775
|
+
const segmentLengths = (width, total, succeeded, failed) => {
|
|
776
|
+
if (total <= 0) return {
|
|
777
|
+
succeeded: 0,
|
|
778
|
+
failed: 0,
|
|
779
|
+
remaining: width
|
|
780
|
+
};
|
|
781
|
+
const succeededEnd = Math.round(succeeded / total * width);
|
|
782
|
+
const failedEnd = Math.round((succeeded + failed) / total * width);
|
|
783
|
+
const succeededLength = Math.max(0, Math.min(width, succeededEnd));
|
|
784
|
+
const failedLength = Math.max(0, Math.min(width, failedEnd) - succeededLength);
|
|
785
|
+
return {
|
|
786
|
+
succeeded: succeededLength,
|
|
787
|
+
failed: failedLength,
|
|
788
|
+
remaining: Math.max(0, width - succeededLength - failedLength)
|
|
789
|
+
};
|
|
790
|
+
};
|
|
791
|
+
const BarColumn = ({ task, width }) => {
|
|
792
|
+
if (task.units._tag !== "DeterminateTaskUnits") return /* @__PURE__ */ jsx(Text, {});
|
|
793
|
+
const lengths = segmentLengths(Math.max(1, Math.floor(width)), task.units.total, task.units.succeeded, task.units.failed);
|
|
794
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
795
|
+
wrap: "truncate-end",
|
|
796
|
+
children: [
|
|
797
|
+
/* @__PURE__ */ jsx(Text, {
|
|
798
|
+
color: "green",
|
|
799
|
+
children: "━".repeat(lengths.succeeded)
|
|
800
|
+
}),
|
|
801
|
+
/* @__PURE__ */ jsx(Text, {
|
|
802
|
+
color: "red",
|
|
803
|
+
children: "━".repeat(lengths.failed)
|
|
804
|
+
}),
|
|
805
|
+
/* @__PURE__ */ jsx(Text, {
|
|
806
|
+
color: "gray",
|
|
807
|
+
children: "─".repeat(lengths.remaining)
|
|
808
|
+
})
|
|
809
|
+
]
|
|
810
|
+
});
|
|
811
|
+
};
|
|
812
|
+
const createBarColumnSpec = (context, isTTY) => {
|
|
813
|
+
if (!hasDeterminateRows(context.rows)) return;
|
|
181
814
|
return {
|
|
182
|
-
|
|
183
|
-
|
|
815
|
+
id: "bar",
|
|
816
|
+
grow: 0,
|
|
817
|
+
canHide: true,
|
|
818
|
+
variants: [{
|
|
819
|
+
id: "full",
|
|
820
|
+
minWidth: MIN_BAR_WIDTH,
|
|
821
|
+
idealWidth: DEFAULT_BAR_WIDTH,
|
|
822
|
+
maxWidth: DEFAULT_BAR_WIDTH,
|
|
823
|
+
renderCell: (row, width) => /* @__PURE__ */ jsx(BarColumn, {
|
|
824
|
+
task: row.task,
|
|
825
|
+
tree: row.tree,
|
|
826
|
+
now: context.now,
|
|
827
|
+
tick: context.tick,
|
|
828
|
+
isTTY,
|
|
829
|
+
width: Math.max(1, Math.min(width, DEFAULT_BAR_WIDTH))
|
|
830
|
+
})
|
|
831
|
+
}, {
|
|
832
|
+
id: "compact",
|
|
833
|
+
minWidth: 1,
|
|
834
|
+
idealWidth: MIN_BAR_WIDTH,
|
|
835
|
+
maxWidth: MIN_BAR_WIDTH,
|
|
836
|
+
renderCell: (row, width) => /* @__PURE__ */ jsx(BarColumn, {
|
|
837
|
+
task: row.task,
|
|
838
|
+
tree: row.tree,
|
|
839
|
+
now: context.now,
|
|
840
|
+
tick: context.tick,
|
|
841
|
+
isTTY,
|
|
842
|
+
width: Math.max(1, width)
|
|
843
|
+
})
|
|
844
|
+
}]
|
|
184
845
|
};
|
|
185
846
|
};
|
|
186
847
|
|
|
187
848
|
//#endregion
|
|
188
|
-
//#region src/ink-renderer/
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
children: text
|
|
194
|
-
});
|
|
849
|
+
//#region src/ink-renderer/view/tree-prefix.ts
|
|
850
|
+
const treeAncestorPrefix = (tree) => tree.ancestorHasNextSibling.slice(1).map((hasNext) => hasNext ? "│ " : " ").join("");
|
|
851
|
+
const renderTreePrefix = (tree) => {
|
|
852
|
+
if (tree.depth <= 0) return "";
|
|
853
|
+
return `${treeAncestorPrefix(tree)}${tree.hasNextSibling ? "├─ " : "└─ "}`;
|
|
195
854
|
};
|
|
196
855
|
|
|
197
856
|
//#endregion
|
|
198
|
-
//#region src/ink-renderer/columns/
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const safeTotal = Math.max(1, task.units.total);
|
|
204
|
-
const ratio = task.status === "done" ? 1 : clamp(task.units.completed / safeTotal, 0, 1);
|
|
205
|
-
const filled = Math.round(barWidth * ratio);
|
|
206
|
-
const empty = Math.max(0, barWidth - filled);
|
|
207
|
-
const bar = `${"━".repeat(filled)}${"─".repeat(empty)}`;
|
|
208
|
-
return /* @__PURE__ */ jsx(Text, {
|
|
857
|
+
//#region src/ink-renderer/columns/description-column.tsx
|
|
858
|
+
const DescriptionColumn = ({ task, tree, showTree, tick }) => {
|
|
859
|
+
const treePrefix = showTree ? renderTreePrefix(tree) : "";
|
|
860
|
+
const indicator = getTaskIndicator(task, tick);
|
|
861
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
209
862
|
wrap: "truncate-end",
|
|
210
|
-
|
|
211
|
-
|
|
863
|
+
children: [
|
|
864
|
+
treePrefix,
|
|
865
|
+
/* @__PURE__ */ jsx(Text, {
|
|
866
|
+
color: indicator.color,
|
|
867
|
+
children: indicator.symbol
|
|
868
|
+
}),
|
|
869
|
+
` ${task.description}`
|
|
870
|
+
]
|
|
212
871
|
});
|
|
213
872
|
};
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
});
|
|
873
|
+
const MIN_DESCRIPTION_WIDTH = 8;
|
|
874
|
+
const MIN_DESCRIPTION_WITH_TREE_WIDTH = 20;
|
|
875
|
+
const DESCRIPTION_PADDING_GROWTH_LIMIT = 20;
|
|
876
|
+
const maxDescriptionWidth = (rows, showTree) => rows.reduce((max, row) => {
|
|
877
|
+
const treePrefix = showTree ? renderTreePrefix(row.tree) : "";
|
|
878
|
+
return Math.max(max, textWidth(`${treePrefix}${row.task.description}`) + 2);
|
|
879
|
+
}, MIN_DESCRIPTION_WIDTH);
|
|
880
|
+
const createDescriptionColumnSpec = (context, isTTY) => {
|
|
881
|
+
const treeIdeal = maxDescriptionWidth(context.rows, true);
|
|
882
|
+
const plainIdeal = maxDescriptionWidth(context.rows, false);
|
|
883
|
+
const treeVariantIdeal = Math.max(MIN_DESCRIPTION_WITH_TREE_WIDTH, treeIdeal);
|
|
884
|
+
const plainVariantIdeal = Math.max(MIN_DESCRIPTION_WIDTH, plainIdeal);
|
|
885
|
+
return {
|
|
886
|
+
id: "description",
|
|
887
|
+
grow: 1,
|
|
888
|
+
canHide: false,
|
|
889
|
+
variants: [{
|
|
890
|
+
id: "tree",
|
|
891
|
+
minWidth: MIN_DESCRIPTION_WITH_TREE_WIDTH,
|
|
892
|
+
idealWidth: treeVariantIdeal,
|
|
893
|
+
maxWidth: Math.max(DESCRIPTION_PADDING_GROWTH_LIMIT, treeVariantIdeal),
|
|
894
|
+
renderCell: (row) => /* @__PURE__ */ jsx(DescriptionColumn, {
|
|
895
|
+
task: row.task,
|
|
896
|
+
tree: row.tree,
|
|
897
|
+
now: context.now,
|
|
898
|
+
tick: context.tick,
|
|
899
|
+
isTTY,
|
|
900
|
+
showTree: true
|
|
901
|
+
})
|
|
902
|
+
}, {
|
|
903
|
+
id: "plain",
|
|
904
|
+
minWidth: MIN_DESCRIPTION_WIDTH,
|
|
905
|
+
idealWidth: plainVariantIdeal,
|
|
906
|
+
maxWidth: Math.max(DESCRIPTION_PADDING_GROWTH_LIMIT, plainVariantIdeal),
|
|
907
|
+
renderCell: (row) => /* @__PURE__ */ jsx(DescriptionColumn, {
|
|
908
|
+
task: row.task,
|
|
909
|
+
tree: row.tree,
|
|
910
|
+
now: context.now,
|
|
911
|
+
tick: context.tick,
|
|
912
|
+
isTTY,
|
|
913
|
+
showTree: false
|
|
914
|
+
})
|
|
915
|
+
}]
|
|
916
|
+
};
|
|
917
|
+
};
|
|
221
918
|
|
|
222
919
|
//#endregion
|
|
223
920
|
//#region src/ink-renderer/columns/elapsed-column.tsx
|
|
224
921
|
const ElapsedColumn = ({ task, now }) => /* @__PURE__ */ jsx(Text, {
|
|
922
|
+
wrap: "truncate-end",
|
|
225
923
|
color: "gray",
|
|
226
924
|
children: formatElapsed(task, now)
|
|
227
925
|
});
|
|
926
|
+
const MIN_ELAPSED_WIDTH = 2;
|
|
927
|
+
const RESERVED_ELAPSED_WIDTH_UP_TO_ONE_HOUR = Array.from("59m 59s").length;
|
|
928
|
+
const maxElapsedWidth = (rows, now) => rows.reduce((max, row) => Math.max(max, textWidth(formatElapsed(row.task, now))), MIN_ELAPSED_WIDTH);
|
|
929
|
+
const createElapsedColumnSpec = (context, isTTY) => {
|
|
930
|
+
const elapsedContentWidth = maxElapsedWidth(context.rows, context.now);
|
|
931
|
+
return {
|
|
932
|
+
id: "elapsed",
|
|
933
|
+
grow: 0,
|
|
934
|
+
canHide: false,
|
|
935
|
+
variants: [{
|
|
936
|
+
id: "stable",
|
|
937
|
+
minWidth: elapsedContentWidth,
|
|
938
|
+
idealWidth: hasDeterminateRows(context.rows) ? Math.max(elapsedContentWidth, RESERVED_ELAPSED_WIDTH_UP_TO_ONE_HOUR) : elapsedContentWidth,
|
|
939
|
+
renderCell: (row) => /* @__PURE__ */ jsx(ElapsedColumn, {
|
|
940
|
+
task: row.task,
|
|
941
|
+
tree: row.tree,
|
|
942
|
+
now: context.now,
|
|
943
|
+
tick: context.tick,
|
|
944
|
+
isTTY
|
|
945
|
+
})
|
|
946
|
+
}, {
|
|
947
|
+
id: "compact",
|
|
948
|
+
minWidth: MIN_ELAPSED_WIDTH,
|
|
949
|
+
idealWidth: elapsedContentWidth,
|
|
950
|
+
renderCell: (row) => /* @__PURE__ */ jsx(ElapsedColumn, {
|
|
951
|
+
task: row.task,
|
|
952
|
+
tree: row.tree,
|
|
953
|
+
now: context.now,
|
|
954
|
+
tick: context.tick,
|
|
955
|
+
isTTY
|
|
956
|
+
})
|
|
957
|
+
}]
|
|
958
|
+
};
|
|
959
|
+
};
|
|
228
960
|
|
|
229
961
|
//#endregion
|
|
230
962
|
//#region src/ink-renderer/columns/eta-column.tsx
|
|
231
|
-
const
|
|
232
|
-
|
|
963
|
+
const primaryUnit = (duration) => duration.split(" ")[0] ?? duration;
|
|
964
|
+
const etaDurationText = (task, now) => {
|
|
965
|
+
if (task.status !== "running" || !isDeterminate(task)) return;
|
|
233
966
|
const eta = formatEta(task, now);
|
|
967
|
+
return eta.length > 0 ? eta : "--";
|
|
968
|
+
};
|
|
969
|
+
const EtaColumn = ({ task, now, mode }) => {
|
|
970
|
+
const duration = etaDurationText(task, now);
|
|
971
|
+
if (duration === void 0) return /* @__PURE__ */ jsx(Text, {});
|
|
234
972
|
return /* @__PURE__ */ jsx(Text, {
|
|
235
973
|
wrap: "truncate-end",
|
|
236
974
|
color: "gray",
|
|
237
|
-
children:
|
|
975
|
+
children: mode === "prefixed" ? `ETA: ${duration}` : mode === "primary" ? primaryUnit(duration) : duration
|
|
238
976
|
});
|
|
239
977
|
};
|
|
978
|
+
const RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR = Array.from("ETA: 59m 59s").length;
|
|
979
|
+
const computeEtaMetrics = (rows, now) => {
|
|
980
|
+
let hasEta = false;
|
|
981
|
+
let prefixedWidth = 0;
|
|
982
|
+
let durationWidth = 0;
|
|
983
|
+
let primaryUnitWidth = 0;
|
|
984
|
+
for (const row of rows) {
|
|
985
|
+
const duration = etaDurationText(row.task, now);
|
|
986
|
+
if (duration === void 0) continue;
|
|
987
|
+
hasEta = true;
|
|
988
|
+
const prefixed = `ETA: ${duration}`;
|
|
989
|
+
prefixedWidth = Math.max(prefixedWidth, textWidth(prefixed));
|
|
990
|
+
durationWidth = Math.max(durationWidth, textWidth(duration));
|
|
991
|
+
primaryUnitWidth = Math.max(primaryUnitWidth, textWidth(primaryUnit(duration)));
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
hasEta,
|
|
995
|
+
prefixedWidth,
|
|
996
|
+
durationWidth: Math.max(2, durationWidth),
|
|
997
|
+
primaryUnitWidth: Math.max(2, primaryUnitWidth)
|
|
998
|
+
};
|
|
999
|
+
};
|
|
1000
|
+
const createEtaColumnSpec = (context, isTTY) => {
|
|
1001
|
+
const metrics = computeEtaMetrics(context.rows, context.now);
|
|
1002
|
+
if (!metrics.hasEta) return;
|
|
1003
|
+
return {
|
|
1004
|
+
id: "eta",
|
|
1005
|
+
grow: 0,
|
|
1006
|
+
canHide: true,
|
|
1007
|
+
variants: [
|
|
1008
|
+
{
|
|
1009
|
+
id: "prefixed",
|
|
1010
|
+
minWidth: metrics.prefixedWidth,
|
|
1011
|
+
idealWidth: Math.max(metrics.prefixedWidth, RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR),
|
|
1012
|
+
renderCell: (row) => /* @__PURE__ */ jsx(EtaColumn, {
|
|
1013
|
+
task: row.task,
|
|
1014
|
+
tree: row.tree,
|
|
1015
|
+
now: context.now,
|
|
1016
|
+
tick: context.tick,
|
|
1017
|
+
isTTY,
|
|
1018
|
+
mode: "prefixed"
|
|
1019
|
+
})
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
id: "duration",
|
|
1023
|
+
minWidth: metrics.durationWidth,
|
|
1024
|
+
idealWidth: metrics.durationWidth,
|
|
1025
|
+
renderCell: (row) => /* @__PURE__ */ jsx(EtaColumn, {
|
|
1026
|
+
task: row.task,
|
|
1027
|
+
tree: row.tree,
|
|
1028
|
+
now: context.now,
|
|
1029
|
+
tick: context.tick,
|
|
1030
|
+
isTTY,
|
|
1031
|
+
mode: "duration"
|
|
1032
|
+
})
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
id: "primary",
|
|
1036
|
+
minWidth: metrics.primaryUnitWidth,
|
|
1037
|
+
idealWidth: metrics.primaryUnitWidth,
|
|
1038
|
+
renderCell: (row) => /* @__PURE__ */ jsx(EtaColumn, {
|
|
1039
|
+
task: row.task,
|
|
1040
|
+
tree: row.tree,
|
|
1041
|
+
now: context.now,
|
|
1042
|
+
tick: context.tick,
|
|
1043
|
+
isTTY,
|
|
1044
|
+
mode: "primary"
|
|
1045
|
+
})
|
|
1046
|
+
}
|
|
1047
|
+
]
|
|
1048
|
+
};
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region src/ink-renderer/columns/planner.ts
|
|
1053
|
+
const clampInt = (value, min, max) => Math.max(min, Math.min(max, Math.floor(value)));
|
|
1054
|
+
const activeColumns = (columns) => columns.filter((column) => !column.hidden);
|
|
1055
|
+
const visibleColumns = (columns) => columns.filter((column) => !column.hidden && column.width > 0);
|
|
1056
|
+
const visibleGapWidth = (columns) => Math.max(0, visibleColumns(columns).length - 1);
|
|
1057
|
+
const totalWidth = (columns) => visibleColumns(columns).reduce((sum, column) => sum + column.width, 0) + visibleGapWidth(columns);
|
|
1058
|
+
const currentVariant = (column) => column.variants[column.variantIndex];
|
|
1059
|
+
const variantMaxWidth = (column) => {
|
|
1060
|
+
const variant = currentVariant(column);
|
|
1061
|
+
return variant.maxWidth === void 0 ? Number.POSITIVE_INFINITY : Math.max(variant.minWidth, variant.maxWidth);
|
|
1062
|
+
};
|
|
1063
|
+
const reduceOverflowByShrink = (columns, overflow) => {
|
|
1064
|
+
let remaining = overflow;
|
|
1065
|
+
const candidates = activeColumns(columns).filter((column) => column.width > currentVariant(column).minWidth).sort((a, b) => currentVariant(a).shrinkResistance - currentVariant(b).shrinkResistance);
|
|
1066
|
+
for (const column of candidates) {
|
|
1067
|
+
if (remaining <= 0) break;
|
|
1068
|
+
const variant = currentVariant(column);
|
|
1069
|
+
const reducible = column.width - variant.minWidth;
|
|
1070
|
+
if (reducible <= 0) continue;
|
|
1071
|
+
const delta = Math.min(reducible, remaining);
|
|
1072
|
+
column.width -= delta;
|
|
1073
|
+
remaining -= delta;
|
|
1074
|
+
}
|
|
1075
|
+
return remaining;
|
|
1076
|
+
};
|
|
1077
|
+
const nextDemoteCandidate = (columns) => activeColumns(columns).filter((column) => column.variantIndex + 1 < column.variants.length).sort((a, b) => currentVariant(a).demoteResistance - currentVariant(b).demoteResistance)[0];
|
|
1078
|
+
const applyDemote = (column) => {
|
|
1079
|
+
const nextVariant = column.variants[column.variantIndex + 1];
|
|
1080
|
+
column.variantIndex += 1;
|
|
1081
|
+
const nextMaxWidth = nextVariant.maxWidth === void 0 ? Number.POSITIVE_INFINITY : Math.max(nextVariant.minWidth, nextVariant.maxWidth);
|
|
1082
|
+
column.width = Math.max(nextVariant.minWidth, Math.min(column.width, nextVariant.idealWidth, nextMaxWidth));
|
|
1083
|
+
};
|
|
1084
|
+
const nextHideCandidate = (columns) => activeColumns(columns).filter((column) => column.spec.canHide).sort((a, b) => currentVariant(a).hideResistance - currentVariant(b).hideResistance)[0];
|
|
1085
|
+
const applyHide = (column) => {
|
|
1086
|
+
column.hidden = true;
|
|
1087
|
+
column.width = 0;
|
|
1088
|
+
};
|
|
1089
|
+
const distributeGrowth = (columns, extra) => {
|
|
1090
|
+
if (extra <= 0) return;
|
|
1091
|
+
let remaining = extra;
|
|
1092
|
+
while (remaining > 0) {
|
|
1093
|
+
const candidates = activeColumns(columns).filter((column) => column.spec.grow > 0 && column.width < variantMaxWidth(column));
|
|
1094
|
+
if (candidates.length === 0) break;
|
|
1095
|
+
const growSum = candidates.reduce((sum, column) => sum + Math.max(0, column.spec.grow), 0);
|
|
1096
|
+
if (growSum <= 0) break;
|
|
1097
|
+
let distributed = 0;
|
|
1098
|
+
for (const column of candidates) {
|
|
1099
|
+
const weight = Math.max(0, column.spec.grow);
|
|
1100
|
+
if (weight <= 0) continue;
|
|
1101
|
+
const headroom = Math.max(0, variantMaxWidth(column) - column.width);
|
|
1102
|
+
if (headroom <= 0) continue;
|
|
1103
|
+
const share = Math.min(headroom, Math.floor(remaining * weight / growSum));
|
|
1104
|
+
if (share <= 0) continue;
|
|
1105
|
+
column.width += share;
|
|
1106
|
+
distributed += share;
|
|
1107
|
+
}
|
|
1108
|
+
if (distributed === 0) {
|
|
1109
|
+
for (const column of candidates) {
|
|
1110
|
+
if (remaining <= 0) break;
|
|
1111
|
+
if (Math.max(0, variantMaxWidth(column) - column.width) <= 0) continue;
|
|
1112
|
+
column.width += 1;
|
|
1113
|
+
distributed += 1;
|
|
1114
|
+
remaining -= 1;
|
|
1115
|
+
}
|
|
1116
|
+
if (distributed === 0) break;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
remaining -= distributed;
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
const planColumns = ({ context, baselineWidth, columns }) => {
|
|
1123
|
+
const mutable = columns.flatMap((column) => {
|
|
1124
|
+
if (column.variants.length === 0) return [];
|
|
1125
|
+
const first = column.variants[0];
|
|
1126
|
+
return [{
|
|
1127
|
+
spec: column,
|
|
1128
|
+
variants: column.variants,
|
|
1129
|
+
variantIndex: 0,
|
|
1130
|
+
width: Math.max(first.minWidth, Math.min(first.idealWidth, first.maxWidth === void 0 ? Number.POSITIVE_INFINITY : Math.max(first.minWidth, first.maxWidth))),
|
|
1131
|
+
hidden: false
|
|
1132
|
+
}];
|
|
1133
|
+
});
|
|
1134
|
+
if (mutable.length === 0) return {
|
|
1135
|
+
rowWidth: 0,
|
|
1136
|
+
columns: []
|
|
1137
|
+
};
|
|
1138
|
+
const naturalWidth = totalWidth(mutable);
|
|
1139
|
+
const clampedTerminal = context.terminalColumns === void 0 ? void 0 : clampInt(context.terminalColumns, 1, Number.POSITIVE_INFINITY);
|
|
1140
|
+
const baselineTarget = Math.max(1, Math.max(baselineWidth, naturalWidth));
|
|
1141
|
+
const target = clampedTerminal === void 0 ? baselineTarget : Math.min(baselineTarget, clampedTerminal);
|
|
1142
|
+
if (naturalWidth < target) distributeGrowth(mutable, target - naturalWidth);
|
|
1143
|
+
else if (naturalWidth > target) {
|
|
1144
|
+
let overflow = naturalWidth - target;
|
|
1145
|
+
let progressed = true;
|
|
1146
|
+
while (overflow > 0 && progressed) {
|
|
1147
|
+
const before = overflow;
|
|
1148
|
+
overflow = reduceOverflowByShrink(mutable, overflow);
|
|
1149
|
+
if (overflow <= 0) break;
|
|
1150
|
+
const demoteCandidate = nextDemoteCandidate(mutable);
|
|
1151
|
+
const hideCandidate = nextHideCandidate(mutable);
|
|
1152
|
+
if (demoteCandidate === void 0 && hideCandidate === void 0) {
|
|
1153
|
+
progressed = before !== overflow;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if ((demoteCandidate === void 0 ? Number.POSITIVE_INFINITY : currentVariant(demoteCandidate).demoteResistance) <= (hideCandidate === void 0 ? Number.POSITIVE_INFINITY : currentVariant(hideCandidate).hideResistance) && demoteCandidate !== void 0) applyDemote(demoteCandidate);
|
|
1157
|
+
else if (hideCandidate !== void 0) applyHide(hideCandidate);
|
|
1158
|
+
overflow = Math.max(0, totalWidth(mutable) - target);
|
|
1159
|
+
progressed = true;
|
|
1160
|
+
}
|
|
1161
|
+
const compactedWidth = totalWidth(mutable);
|
|
1162
|
+
if (compactedWidth < target) distributeGrowth(mutable, target - compactedWidth);
|
|
1163
|
+
}
|
|
1164
|
+
const visible = visibleColumns(mutable);
|
|
1165
|
+
return {
|
|
1166
|
+
rowWidth: totalWidth(mutable),
|
|
1167
|
+
columns: visible.map((column) => {
|
|
1168
|
+
const variant = currentVariant(column);
|
|
1169
|
+
return {
|
|
1170
|
+
id: column.spec.id,
|
|
1171
|
+
variantId: variant.id,
|
|
1172
|
+
width: column.width,
|
|
1173
|
+
renderCell: variant.renderCell
|
|
1174
|
+
};
|
|
1175
|
+
})
|
|
1176
|
+
};
|
|
1177
|
+
};
|
|
240
1178
|
|
|
241
1179
|
//#endregion
|
|
242
|
-
//#region src/ink-renderer/
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
1180
|
+
//#region src/ink-renderer/columns/layout.tsx
|
|
1181
|
+
/**
|
|
1182
|
+
* Frame planning algorithm (per render tick):
|
|
1183
|
+
*
|
|
1184
|
+
* 1. Ask each column module to produce a logical column spec (variants + measured widths).
|
|
1185
|
+
* 2. Apply shared resistance rules (shrink / demote / hide) to those variants.
|
|
1186
|
+
* 3. Feed resolved columns into `planColumns`.
|
|
1187
|
+
* 4. `planColumns` shrinks, demotes, and hides until the row fits target width.
|
|
1188
|
+
* 5. Render rows with the selected variant and width for each visible column.
|
|
1189
|
+
*/
|
|
1190
|
+
const BASELINE_ROW_WIDTH = 150;
|
|
1191
|
+
const DEFAULT_RESISTANCE = 1e3;
|
|
1192
|
+
const variantOrderKey = ({ columnId, variantId }) => `${columnId}:${variantId}`;
|
|
1193
|
+
const SHRINK_ORDER = [
|
|
1194
|
+
{
|
|
1195
|
+
columnId: "eta",
|
|
1196
|
+
variantId: "prefixed"
|
|
1197
|
+
},
|
|
1198
|
+
{
|
|
1199
|
+
columnId: "elapsed",
|
|
1200
|
+
variantId: "stable"
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
columnId: "eta",
|
|
1204
|
+
variantId: "duration"
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
columnId: "eta",
|
|
1208
|
+
variantId: "primary"
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
columnId: "bar",
|
|
1212
|
+
variantId: "compact"
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
columnId: "bar",
|
|
1216
|
+
variantId: "full"
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
columnId: "amount",
|
|
1220
|
+
variantId: "text"
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
columnId: "elapsed",
|
|
1224
|
+
variantId: "compact"
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
columnId: "description",
|
|
1228
|
+
variantId: "tree"
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
columnId: "amount",
|
|
1232
|
+
variantId: "detailed"
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
columnId: "amount",
|
|
1236
|
+
variantId: "processed"
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
columnId: "description",
|
|
1240
|
+
variantId: "plain"
|
|
1241
|
+
}
|
|
1242
|
+
];
|
|
1243
|
+
const DEMOTE_ORDER = [
|
|
1244
|
+
{
|
|
1245
|
+
columnId: "description",
|
|
1246
|
+
variantId: "tree"
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
columnId: "eta",
|
|
1250
|
+
variantId: "prefixed"
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
columnId: "eta",
|
|
1254
|
+
variantId: "duration"
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
columnId: "elapsed",
|
|
1258
|
+
variantId: "stable"
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
columnId: "bar",
|
|
1262
|
+
variantId: "full"
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
columnId: "amount",
|
|
1266
|
+
variantId: "detailed"
|
|
1267
|
+
}
|
|
1268
|
+
];
|
|
1269
|
+
const HIDE_ORDER = [
|
|
1270
|
+
{
|
|
1271
|
+
columnId: "eta",
|
|
1272
|
+
variantId: "primary"
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
columnId: "eta",
|
|
1276
|
+
variantId: "prefixed"
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
columnId: "eta",
|
|
1280
|
+
variantId: "duration"
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
columnId: "bar",
|
|
1284
|
+
variantId: "full"
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
columnId: "bar",
|
|
1288
|
+
variantId: "compact"
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
columnId: "amount",
|
|
1292
|
+
variantId: "text"
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
columnId: "amount",
|
|
1296
|
+
variantId: "detailed"
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
columnId: "amount",
|
|
1300
|
+
variantId: "processed"
|
|
1301
|
+
}
|
|
1302
|
+
];
|
|
1303
|
+
const toResistanceLookup = (order) => new Map(order.map((item, index) => [variantOrderKey(item), index + 1]));
|
|
1304
|
+
const shrinkResistanceLookup = toResistanceLookup(SHRINK_ORDER);
|
|
1305
|
+
const demoteResistanceLookup = toResistanceLookup(DEMOTE_ORDER);
|
|
1306
|
+
const hideResistanceLookup = toResistanceLookup(HIDE_ORDER);
|
|
1307
|
+
const resistanceFor = (lookup, columnId, variantId) => lookup.get(`${columnId}:${variantId}`) ?? DEFAULT_RESISTANCE;
|
|
1308
|
+
const buildColumns = (context, isTTY) => {
|
|
1309
|
+
return resolveColumnSpecs([
|
|
1310
|
+
createDescriptionColumnSpec(context, isTTY),
|
|
1311
|
+
createBarColumnSpec(context, isTTY),
|
|
1312
|
+
createAmountColumnSpec(context),
|
|
1313
|
+
createElapsedColumnSpec(context, isTTY),
|
|
1314
|
+
createEtaColumnSpec(context, isTTY)
|
|
1315
|
+
].filter((spec) => spec !== void 0), {
|
|
1316
|
+
shrink: (columnId, variantId) => resistanceFor(shrinkResistanceLookup, columnId, variantId),
|
|
1317
|
+
demote: (columnId, variantId) => resistanceFor(demoteResistanceLookup, columnId, variantId),
|
|
1318
|
+
hide: (columnId, variantId) => resistanceFor(hideResistanceLookup, columnId, variantId)
|
|
1319
|
+
});
|
|
1320
|
+
};
|
|
1321
|
+
const computeFrameLayout = (rows, now, tick, terminalColumns, isTTY) => {
|
|
1322
|
+
const context = {
|
|
1323
|
+
rows,
|
|
247
1324
|
now,
|
|
248
1325
|
tick,
|
|
249
|
-
|
|
250
|
-
showTree: widths.showTree
|
|
1326
|
+
terminalColumns
|
|
251
1327
|
};
|
|
252
|
-
return
|
|
1328
|
+
return planColumns({
|
|
1329
|
+
context,
|
|
1330
|
+
columns: buildColumns(context, isTTY),
|
|
1331
|
+
baselineWidth: hasDeterminateRows(rows) ? BASELINE_ROW_WIDTH : 1
|
|
1332
|
+
});
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
//#endregion
|
|
1336
|
+
//#region src/ink-renderer/view/task-row.tsx
|
|
1337
|
+
const TaskRow = ({ row, layout }) => {
|
|
1338
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
253
1339
|
flexDirection: "row",
|
|
254
|
-
minWidth:
|
|
255
|
-
children:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}),
|
|
262
|
-
widths.bar > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
263
|
-
width: widths.bar,
|
|
264
|
-
flexShrink: 0,
|
|
265
|
-
marginRight: 1,
|
|
266
|
-
children: /* @__PURE__ */ jsx(BarColumn, {
|
|
267
|
-
...props,
|
|
268
|
-
width: Math.max(1, Math.min(widths.bar, DEFAULT_BAR_WIDTH))
|
|
269
|
-
})
|
|
270
|
-
}) : null,
|
|
271
|
-
/* @__PURE__ */ jsx(Box, {
|
|
272
|
-
width: widths.amount,
|
|
273
|
-
flexShrink: 0,
|
|
274
|
-
marginRight: 1,
|
|
275
|
-
children: /* @__PURE__ */ jsx(AmountColumn, { ...props })
|
|
276
|
-
}),
|
|
277
|
-
/* @__PURE__ */ jsx(Box, {
|
|
278
|
-
width: widths.elapsed,
|
|
279
|
-
flexShrink: 0,
|
|
280
|
-
marginRight: 1,
|
|
281
|
-
children: /* @__PURE__ */ jsx(ElapsedColumn, { ...props })
|
|
282
|
-
}),
|
|
283
|
-
widths.eta > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
284
|
-
width: widths.eta,
|
|
285
|
-
flexShrink: 0,
|
|
286
|
-
children: /* @__PURE__ */ jsx(EtaColumn, { ...props })
|
|
287
|
-
}) : null
|
|
288
|
-
]
|
|
1340
|
+
minWidth: layout.rowWidth,
|
|
1341
|
+
children: layout.columns.map((column, index) => /* @__PURE__ */ jsx(Box, {
|
|
1342
|
+
width: column.width,
|
|
1343
|
+
flexShrink: column.id === "description" ? 1 : 0,
|
|
1344
|
+
marginRight: index < layout.columns.length - 1 ? 1 : 0,
|
|
1345
|
+
children: column.renderCell(row, column.width)
|
|
1346
|
+
}, column.id))
|
|
289
1347
|
});
|
|
290
1348
|
};
|
|
291
1349
|
|
|
292
1350
|
//#endregion
|
|
293
|
-
//#region src/ink-renderer/
|
|
294
|
-
const
|
|
295
|
-
const
|
|
1351
|
+
//#region src/ink-renderer/view/progress-view.tsx
|
|
1352
|
+
const ProgressView = ({ rows, now, tick, isTTY, terminalColumns }) => {
|
|
1353
|
+
const layout = computeFrameLayout(rows, now, tick, terminalColumns, isTTY);
|
|
296
1354
|
return /* @__PURE__ */ jsx(Box, {
|
|
297
1355
|
flexDirection: "column",
|
|
298
1356
|
children: rows.map((row) => /* @__PURE__ */ jsx(TaskRow, {
|
|
299
1357
|
row,
|
|
300
|
-
|
|
301
|
-
tick,
|
|
302
|
-
isTTY,
|
|
303
|
-
widths
|
|
1358
|
+
layout
|
|
304
1359
|
}, row.task.id))
|
|
305
1360
|
});
|
|
306
1361
|
};
|
|
307
1362
|
|
|
308
1363
|
//#endregion
|
|
309
|
-
//#region src/ink-renderer/
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
//#endregion
|
|
324
|
-
//#region src/ink-renderer/service.tsx
|
|
325
|
-
const RENDER_INTERVAL_MILLIS = 100;
|
|
326
|
-
const hasRunningSpinners = (tasks) => tasks.some((task) => task.status === "running" && task.units._tag === "IndeterminateTaskUnits");
|
|
327
|
-
const createInkWritable = (terminal) => new Writable({ write(chunk, _encoding, callback) {
|
|
328
|
-
try {
|
|
329
|
-
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : `${chunk}`;
|
|
330
|
-
Effect.runSync(terminal.writeStderr(text));
|
|
331
|
-
callback();
|
|
332
|
-
} catch (error) {
|
|
333
|
-
callback(error);
|
|
334
|
-
}
|
|
335
|
-
} });
|
|
336
|
-
const makeDefaultInkRenderer = () => ({ run: (storeRef, dirtyRef, terminal, isTTY) => Effect.gen(function* () {
|
|
337
|
-
const output = createInkWritable(terminal);
|
|
338
|
-
let instance;
|
|
339
|
-
let tick = 0;
|
|
340
|
-
let rendererActive = false;
|
|
341
|
-
const renderStore = (store, now, terminalColumns) => Effect.sync(() => {
|
|
342
|
-
const app = /* @__PURE__ */ jsx(ProgressApp, {
|
|
343
|
-
rows: toTaskRows(store),
|
|
344
|
-
now,
|
|
345
|
-
tick,
|
|
346
|
-
isTTY,
|
|
347
|
-
terminalColumns
|
|
348
|
-
});
|
|
349
|
-
if (instance === void 0) {
|
|
350
|
-
instance = render(app, {
|
|
351
|
-
stdout: output,
|
|
352
|
-
stderr: output,
|
|
353
|
-
patchConsole: true,
|
|
354
|
-
exitOnCtrlC: false,
|
|
355
|
-
debug: false
|
|
356
|
-
});
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
instance.rerender(app);
|
|
360
|
-
});
|
|
361
|
-
return yield* Effect.gen(function* () {
|
|
362
|
-
rendererActive = true;
|
|
363
|
-
while (true) {
|
|
364
|
-
const dirty = yield* Ref.getAndSet(dirtyRef, false);
|
|
365
|
-
const store = yield* Ref.get(storeRef);
|
|
366
|
-
const tasks = Array.from(store.tasks.values()).filter((task) => !(task.transient && task.status !== "running"));
|
|
367
|
-
if (dirty || hasRunningSpinners(tasks)) yield* renderStore(store, yield* Clock.currentTimeMillis, isTTY ? yield* terminal.stderrColumns : void 0);
|
|
368
|
-
tick += 1;
|
|
369
|
-
yield* Effect.sleep(RENDER_INTERVAL_MILLIS);
|
|
370
|
-
}
|
|
371
|
-
}).pipe(Effect.ensuring(Effect.gen(function* () {
|
|
372
|
-
if (rendererActive) yield* renderStore(yield* Ref.get(storeRef), yield* Clock.currentTimeMillis, isTTY ? yield* terminal.stderrColumns : void 0);
|
|
373
|
-
yield* Effect.sync(() => {
|
|
374
|
-
instance?.unmount();
|
|
375
|
-
});
|
|
376
|
-
})));
|
|
377
|
-
}) });
|
|
378
|
-
var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
|
|
379
|
-
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeDefaultInkRenderer()));
|
|
1364
|
+
//#region src/ink-renderer/view/hooks/use-now-clock.ts
|
|
1365
|
+
const useNowClock = (active, intervalMillis) => {
|
|
1366
|
+
const [now, setNow] = useState(() => Date.now());
|
|
1367
|
+
useEffect(() => {
|
|
1368
|
+
if (!active) return;
|
|
1369
|
+
setNow(Date.now());
|
|
1370
|
+
const interval = setInterval(() => {
|
|
1371
|
+
setNow(Date.now());
|
|
1372
|
+
}, intervalMillis);
|
|
1373
|
+
return () => {
|
|
1374
|
+
clearInterval(interval);
|
|
1375
|
+
};
|
|
1376
|
+
}, [active, intervalMillis]);
|
|
1377
|
+
return now;
|
|
380
1378
|
};
|
|
381
1379
|
|
|
382
1380
|
//#endregion
|
|
383
|
-
//#region src/
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
try {
|
|
397
|
-
stdin.off("data", onData);
|
|
398
|
-
stdin.setRawMode?.(wasRaw);
|
|
399
|
-
stdin.pause();
|
|
400
|
-
} catch {}
|
|
401
|
-
}));
|
|
402
|
-
});
|
|
403
|
-
const defaultTerminalService = {
|
|
404
|
-
isTTY: Effect.sync(() => Boolean(process.stderr.isTTY)),
|
|
405
|
-
stderrRows: Effect.sync(() => process.stderr.rows),
|
|
406
|
-
stderrColumns: Effect.sync(() => process.stderr.columns),
|
|
407
|
-
writeStderr: (text) => Effect.sync(() => {
|
|
408
|
-
process.stderr.write(text);
|
|
409
|
-
}),
|
|
410
|
-
withRawInputCapture
|
|
411
|
-
};
|
|
412
|
-
var ProgressTerminal = class ProgressTerminal extends Context.Tag("stromseng.dev/ProgressTerminal")() {
|
|
413
|
-
static Default = Layer.succeed(ProgressTerminal, defaultTerminalService);
|
|
1381
|
+
//#region src/ink-renderer/view/hooks/use-spinner-clock.ts
|
|
1382
|
+
const useSpinnerClock = (active, intervalMillis) => {
|
|
1383
|
+
const [tick, setTick] = useState(0);
|
|
1384
|
+
useEffect(() => {
|
|
1385
|
+
if (!active) return;
|
|
1386
|
+
const interval = setInterval(() => {
|
|
1387
|
+
setTick((current) => current + 1);
|
|
1388
|
+
}, intervalMillis);
|
|
1389
|
+
return () => {
|
|
1390
|
+
clearInterval(interval);
|
|
1391
|
+
};
|
|
1392
|
+
}, [active, intervalMillis]);
|
|
1393
|
+
return tick;
|
|
414
1394
|
};
|
|
415
1395
|
|
|
416
1396
|
//#endregion
|
|
417
|
-
//#region src/
|
|
418
|
-
const
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
transient: Schema.Boolean,
|
|
433
|
-
units: TaskUnitsSchema,
|
|
434
|
-
startedAt: Schema.Number,
|
|
435
|
-
completedAt: Schema.NullOr(Schema.Number)
|
|
436
|
-
}) {};
|
|
437
|
-
var Task = class extends Context.Tag("stromseng.dev/effective-progress/Task")() {};
|
|
438
|
-
var TaskAddedEvent = class extends Schema.TaggedClass()("TaskAdded", {
|
|
439
|
-
taskId: TaskIdSchema,
|
|
440
|
-
parentId: Schema.NullOr(TaskIdSchema),
|
|
441
|
-
description: Schema.String,
|
|
442
|
-
total: Schema.optional(Schema.Number),
|
|
443
|
-
transient: Schema.Boolean
|
|
444
|
-
}) {};
|
|
445
|
-
var TaskUpdatedEvent = class extends Schema.TaggedClass()("TaskUpdated", {
|
|
446
|
-
taskId: TaskIdSchema,
|
|
447
|
-
description: Schema.optional(Schema.String),
|
|
448
|
-
completed: Schema.optional(Schema.Number),
|
|
449
|
-
total: Schema.optional(Schema.Number),
|
|
450
|
-
transient: Schema.optional(Schema.Boolean)
|
|
451
|
-
}) {};
|
|
452
|
-
var TaskAdvancedEvent = class extends Schema.TaggedClass()("TaskAdvanced", {
|
|
453
|
-
taskId: TaskIdSchema,
|
|
454
|
-
amount: Schema.Number
|
|
455
|
-
}) {};
|
|
456
|
-
var TaskCompletedEvent = class extends Schema.TaggedClass()("TaskCompleted", { taskId: TaskIdSchema }) {};
|
|
457
|
-
var TaskFailedEvent = class extends Schema.TaggedClass()("TaskFailed", { taskId: TaskIdSchema }) {};
|
|
458
|
-
var TaskRemovedEvent = class extends Schema.TaggedClass()("TaskRemoved", { taskId: TaskIdSchema }) {};
|
|
459
|
-
const ProgressTaskEventSchema = Schema.Union(TaskAddedEvent, TaskUpdatedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskFailedEvent, TaskRemovedEvent);
|
|
460
|
-
const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema);
|
|
1397
|
+
//#region src/ink-renderer/view/render-root.tsx
|
|
1398
|
+
const SPINNER_INTERVAL_MILLIS = 100;
|
|
1399
|
+
const NOW_INTERVAL_MILLIS = 1e3;
|
|
1400
|
+
const ProgressRoot = ({ store, isTTY, getTerminalColumns }) => {
|
|
1401
|
+
const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
1402
|
+
const tick = useSpinnerClock(snapshot.hasRunningTasks, SPINNER_INTERVAL_MILLIS);
|
|
1403
|
+
const now = useNowClock(snapshot.hasRunningTasks, NOW_INTERVAL_MILLIS);
|
|
1404
|
+
return /* @__PURE__ */ jsx(ProgressView, {
|
|
1405
|
+
rows: snapshot.rows,
|
|
1406
|
+
now,
|
|
1407
|
+
tick,
|
|
1408
|
+
isTTY,
|
|
1409
|
+
terminalColumns: getTerminalColumns()
|
|
1410
|
+
});
|
|
1411
|
+
};
|
|
461
1412
|
|
|
462
1413
|
//#endregion
|
|
463
|
-
//#region src/
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
return currentUnits;
|
|
483
|
-
})();
|
|
484
|
-
return new TaskSnapshot({
|
|
485
|
-
id: snapshot.id,
|
|
486
|
-
parentId: snapshot.parentId,
|
|
487
|
-
description: options.description ?? snapshot.description,
|
|
488
|
-
status: snapshot.status,
|
|
489
|
-
transient: options.transient ?? snapshot.transient,
|
|
490
|
-
units,
|
|
491
|
-
startedAt: snapshot.startedAt,
|
|
492
|
-
completedAt: snapshot.completedAt
|
|
1414
|
+
//#region src/services/ink-renderer.tsx
|
|
1415
|
+
const MAX_FPS = 12;
|
|
1416
|
+
const makeDefaultInkRenderer = () => ({ run: (store, stdio, isTTY) => Effect.sync(() => render(/* @__PURE__ */ jsx(ProgressRoot, {
|
|
1417
|
+
store,
|
|
1418
|
+
isTTY,
|
|
1419
|
+
getTerminalColumns: () => isTTY ? stdio.stderr.columns : void 0
|
|
1420
|
+
}), {
|
|
1421
|
+
stdout: stdio.stdout,
|
|
1422
|
+
stderr: stdio.stderr,
|
|
1423
|
+
patchConsole: true,
|
|
1424
|
+
exitOnCtrlC: false,
|
|
1425
|
+
debug: false,
|
|
1426
|
+
maxFps: MAX_FPS
|
|
1427
|
+
})).pipe(Effect.flatMap((instance) => Effect.never.pipe(Effect.ensuring(Effect.gen(function* () {
|
|
1428
|
+
store.flush();
|
|
1429
|
+
yield* Effect.sleep("0 millis");
|
|
1430
|
+
yield* Effect.sync(() => {
|
|
1431
|
+
instance.unmount();
|
|
493
1432
|
});
|
|
1433
|
+
}))))) });
|
|
1434
|
+
var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
|
|
1435
|
+
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeDefaultInkRenderer()));
|
|
494
1436
|
};
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
units: snapshot.units,
|
|
502
|
-
startedAt: snapshot.startedAt,
|
|
503
|
-
completedAt: snapshot.completedAt
|
|
504
|
-
});
|
|
505
|
-
const findInsertionIndex = (renderOrder, parentId) => {
|
|
506
|
-
if (parentId === null) return {
|
|
507
|
-
index: renderOrder.length,
|
|
508
|
-
depth: 0
|
|
509
|
-
};
|
|
510
|
-
const parentIdx = renderOrder.findIndex((row) => row.id === parentId);
|
|
511
|
-
if (parentIdx === -1) return {
|
|
512
|
-
index: renderOrder.length,
|
|
513
|
-
depth: 0
|
|
514
|
-
};
|
|
515
|
-
const parentDepth = renderOrder[parentIdx].depth;
|
|
516
|
-
let i = parentIdx + 1;
|
|
517
|
-
while (i < renderOrder.length && renderOrder[i].depth > parentDepth) i++;
|
|
518
|
-
return {
|
|
519
|
-
index: i,
|
|
520
|
-
depth: parentDepth + 1
|
|
521
|
-
};
|
|
1437
|
+
|
|
1438
|
+
//#endregion
|
|
1439
|
+
//#region src/services/stdio.ts
|
|
1440
|
+
const defaultStdioService = {
|
|
1441
|
+
stdout: process.stdout,
|
|
1442
|
+
stderr: process.stderr
|
|
522
1443
|
};
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (idx === -1) return renderOrder;
|
|
526
|
-
const taskDepth = renderOrder[idx].depth;
|
|
527
|
-
let end = idx + 1;
|
|
528
|
-
while (end < renderOrder.length && renderOrder[end].depth > taskDepth) end++;
|
|
529
|
-
const next = [...renderOrder];
|
|
530
|
-
next.splice(idx, end - idx);
|
|
531
|
-
return next;
|
|
1444
|
+
var ProgressStdio = class ProgressStdio extends Context.Tag("stromseng.dev/effective-progress/ProgressStdio")() {
|
|
1445
|
+
static Default = Layer.succeed(ProgressStdio, defaultStdioService);
|
|
532
1446
|
};
|
|
1447
|
+
|
|
1448
|
+
//#endregion
|
|
1449
|
+
//#region src/services/progress.ts
|
|
533
1450
|
const makeProgressService = Effect.gen(function* () {
|
|
534
|
-
const
|
|
1451
|
+
const stdio = yield* ProgressStdio;
|
|
535
1452
|
const inkRenderer = yield* InkRenderer;
|
|
536
1453
|
const outerConsole = yield* Effect.console;
|
|
537
|
-
const isTTY =
|
|
538
|
-
const
|
|
539
|
-
const storeRef = yield* Ref.make({
|
|
540
|
-
tasks: /* @__PURE__ */ new Map(),
|
|
541
|
-
renderOrder: []
|
|
542
|
-
});
|
|
543
|
-
const dirtyRef = yield* Ref.make(true);
|
|
1454
|
+
const isTTY = Boolean(stdio.stderr.isTTY);
|
|
1455
|
+
const store = makeProgressRenderStore();
|
|
544
1456
|
const currentParentRef = yield* FiberRef.make(Option.none());
|
|
545
1457
|
const scope = yield* Effect.scope;
|
|
546
|
-
const markDirty = Ref.set(dirtyRef, true);
|
|
547
1458
|
const log = (...args) => args.length === 0 ? Effect.void : outerConsole.log(...args);
|
|
548
|
-
yield* Effect.forkIn(inkRenderer.run(
|
|
1459
|
+
yield* Effect.forkIn(inkRenderer.run(store, stdio, isTTY), scope);
|
|
549
1460
|
yield* Effect.sleep("0 millis");
|
|
550
1461
|
const addTask = (options) => Effect.gen(function* () {
|
|
551
1462
|
const resolvedParentId = options.parentId === void 0 ? yield* FiberRef.get(currentParentRef) : Option.some(options.parentId);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
total: Math.max(0, options.total)
|
|
556
|
-
});
|
|
557
|
-
const store = yield* Ref.get(storeRef);
|
|
558
|
-
const parentSnapshot = Option.isSome(resolvedParentId) ? store.tasks.get(resolvedParentId.value) : void 0;
|
|
559
|
-
const now = yield* Clock.currentTimeMillis;
|
|
560
|
-
const parentIdValue = Option.getOrNull(resolvedParentId);
|
|
561
|
-
const snapshot = new TaskSnapshot({
|
|
562
|
-
id: taskId,
|
|
563
|
-
parentId: parentIdValue,
|
|
564
|
-
description: options.description,
|
|
565
|
-
status: "running",
|
|
566
|
-
transient: parentSnapshot?.transient ?? options.transient ?? false,
|
|
567
|
-
units,
|
|
568
|
-
startedAt: now,
|
|
569
|
-
completedAt: null
|
|
570
|
-
});
|
|
571
|
-
yield* Ref.update(storeRef, (s) => {
|
|
572
|
-
const nextTasks = new Map(s.tasks);
|
|
573
|
-
nextTasks.set(taskId, snapshot);
|
|
574
|
-
const { index, depth } = findInsertionIndex(s.renderOrder, parentIdValue);
|
|
575
|
-
const nextOrder = [...s.renderOrder];
|
|
576
|
-
nextOrder.splice(index, 0, {
|
|
577
|
-
id: taskId,
|
|
578
|
-
depth
|
|
579
|
-
});
|
|
580
|
-
return {
|
|
581
|
-
tasks: nextTasks,
|
|
582
|
-
renderOrder: nextOrder
|
|
583
|
-
};
|
|
584
|
-
});
|
|
585
|
-
yield* markDirty;
|
|
586
|
-
return taskId;
|
|
587
|
-
});
|
|
588
|
-
const updateTask = (taskId, options) => Ref.update(storeRef, (store) => {
|
|
589
|
-
const snapshot = store.tasks.get(taskId);
|
|
590
|
-
if (!snapshot) return store;
|
|
591
|
-
const nextTasks = new Map(store.tasks);
|
|
592
|
-
const nextSnapshot = updatedSnapshot(snapshot, options);
|
|
593
|
-
nextTasks.set(taskId, nextSnapshot);
|
|
594
|
-
if (options.transient !== void 0) for (const [candidateId, candidate] of store.tasks.entries()) {
|
|
595
|
-
if (candidateId === taskId) continue;
|
|
596
|
-
let parentId = candidate.parentId;
|
|
597
|
-
let isDescendant = false;
|
|
598
|
-
while (parentId !== null) {
|
|
599
|
-
if (parentId === taskId) {
|
|
600
|
-
isDescendant = true;
|
|
601
|
-
break;
|
|
602
|
-
}
|
|
603
|
-
parentId = store.tasks.get(parentId)?.parentId ?? null;
|
|
604
|
-
}
|
|
605
|
-
if (isDescendant) nextTasks.set(candidateId, withTransient(candidate, nextSnapshot.transient));
|
|
606
|
-
}
|
|
607
|
-
return {
|
|
608
|
-
tasks: nextTasks,
|
|
609
|
-
renderOrder: store.renderOrder
|
|
610
|
-
};
|
|
611
|
-
}).pipe(Effect.zipRight(markDirty));
|
|
612
|
-
const advanceTask = (taskId, amount = 1) => Ref.update(storeRef, (store) => {
|
|
613
|
-
const snapshot = store.tasks.get(taskId);
|
|
614
|
-
if (!snapshot) return store;
|
|
615
|
-
const units = snapshot.units._tag === "DeterminateTaskUnits" ? new DeterminateTaskUnits({
|
|
616
|
-
completed: Math.min(snapshot.units.total, snapshot.units.completed + amount),
|
|
617
|
-
total: snapshot.units.total
|
|
618
|
-
}) : new IndeterminateTaskUnits({ spinnerFrame: Math.max(0, snapshot.units.spinnerFrame + amount) });
|
|
619
|
-
const nextTasks = new Map(store.tasks);
|
|
620
|
-
nextTasks.set(taskId, new TaskSnapshot({
|
|
621
|
-
id: snapshot.id,
|
|
622
|
-
parentId: snapshot.parentId,
|
|
623
|
-
description: snapshot.description,
|
|
624
|
-
status: snapshot.status,
|
|
625
|
-
transient: snapshot.transient,
|
|
626
|
-
units,
|
|
627
|
-
startedAt: snapshot.startedAt,
|
|
628
|
-
completedAt: snapshot.completedAt
|
|
629
|
-
}));
|
|
630
|
-
return {
|
|
631
|
-
tasks: nextTasks,
|
|
632
|
-
renderOrder: store.renderOrder
|
|
633
|
-
};
|
|
634
|
-
}).pipe(Effect.zipRight(markDirty));
|
|
635
|
-
const completeTask = (taskId) => Effect.gen(function* () {
|
|
636
|
-
const now = yield* Clock.currentTimeMillis;
|
|
637
|
-
yield* Ref.update(storeRef, (store) => {
|
|
638
|
-
const snapshot = store.tasks.get(taskId);
|
|
639
|
-
if (!snapshot) return store;
|
|
640
|
-
const nextTasks = new Map(store.tasks);
|
|
641
|
-
if (snapshot.transient) {
|
|
642
|
-
nextTasks.delete(taskId);
|
|
643
|
-
return {
|
|
644
|
-
tasks: nextTasks,
|
|
645
|
-
renderOrder: removeFromRenderOrder(store.renderOrder, taskId)
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
nextTasks.set(taskId, new TaskSnapshot({
|
|
649
|
-
id: snapshot.id,
|
|
650
|
-
parentId: snapshot.parentId,
|
|
651
|
-
description: snapshot.description,
|
|
652
|
-
status: "done",
|
|
653
|
-
transient: snapshot.transient,
|
|
654
|
-
units: snapshot.units._tag === "DeterminateTaskUnits" ? new DeterminateTaskUnits({
|
|
655
|
-
completed: snapshot.units.total,
|
|
656
|
-
total: snapshot.units.total
|
|
657
|
-
}) : snapshot.units,
|
|
658
|
-
startedAt: snapshot.startedAt,
|
|
659
|
-
completedAt: now
|
|
660
|
-
}));
|
|
661
|
-
return {
|
|
662
|
-
tasks: nextTasks,
|
|
663
|
-
renderOrder: store.renderOrder
|
|
664
|
-
};
|
|
665
|
-
});
|
|
666
|
-
yield* markDirty;
|
|
667
|
-
});
|
|
668
|
-
const failTask = (taskId) => Effect.gen(function* () {
|
|
669
|
-
const now = yield* Clock.currentTimeMillis;
|
|
670
|
-
yield* Ref.update(storeRef, (store) => {
|
|
671
|
-
const snapshot = store.tasks.get(taskId);
|
|
672
|
-
if (!snapshot) return store;
|
|
673
|
-
const nextTasks = new Map(store.tasks);
|
|
674
|
-
if (snapshot.transient) {
|
|
675
|
-
nextTasks.delete(taskId);
|
|
676
|
-
return {
|
|
677
|
-
tasks: nextTasks,
|
|
678
|
-
renderOrder: removeFromRenderOrder(store.renderOrder, taskId)
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
nextTasks.set(taskId, new TaskSnapshot({
|
|
682
|
-
id: snapshot.id,
|
|
683
|
-
parentId: snapshot.parentId,
|
|
684
|
-
description: snapshot.description,
|
|
685
|
-
status: "failed",
|
|
686
|
-
transient: snapshot.transient,
|
|
687
|
-
units: snapshot.units,
|
|
688
|
-
startedAt: snapshot.startedAt,
|
|
689
|
-
completedAt: now
|
|
690
|
-
}));
|
|
691
|
-
return {
|
|
692
|
-
tasks: nextTasks,
|
|
693
|
-
renderOrder: store.renderOrder
|
|
694
|
-
};
|
|
1463
|
+
return yield* store.addTask({
|
|
1464
|
+
...options,
|
|
1465
|
+
parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : void 0
|
|
695
1466
|
});
|
|
696
|
-
yield* markDirty;
|
|
697
1467
|
});
|
|
698
|
-
const
|
|
699
|
-
const
|
|
1468
|
+
const updateTask = store.updateTask;
|
|
1469
|
+
const advanceTask = store.addSuccess;
|
|
1470
|
+
const advanceTaskFailed = store.addFailure;
|
|
1471
|
+
const completeTask = store.completeTask;
|
|
1472
|
+
const failTask = store.failTask;
|
|
1473
|
+
const getTask = store.getTask;
|
|
1474
|
+
const listTasks = store.listTasks;
|
|
700
1475
|
const runTask = dual(2, (effect, options) => Effect.gen(function* () {
|
|
701
1476
|
const inheritedParentId = yield* FiberRef.get(currentParentRef);
|
|
702
1477
|
const resolvedParentId = options.parentId === void 0 ? inheritedParentId : Option.some(options.parentId);
|
|
@@ -711,6 +1486,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
711
1486
|
addTask,
|
|
712
1487
|
updateTask,
|
|
713
1488
|
advanceTask,
|
|
1489
|
+
advanceTaskFailed,
|
|
714
1490
|
completeTask,
|
|
715
1491
|
failTask,
|
|
716
1492
|
log,
|
|
@@ -732,11 +1508,11 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
732
1508
|
});
|
|
733
1509
|
var Progress = class Progress extends Context.Tag("stromseng.dev/effective-progress/Progress")() {
|
|
734
1510
|
static Default = Layer.unwrapEffect(Effect.gen(function* () {
|
|
735
|
-
const
|
|
1511
|
+
const stdioOption = yield* Effect.serviceOption(ProgressStdio);
|
|
736
1512
|
const inkRendererOption = yield* Effect.serviceOption(InkRenderer);
|
|
737
1513
|
let layer = Layer.scoped(Progress, makeProgressService);
|
|
738
1514
|
if (Option.isNone(inkRendererOption)) layer = layer.pipe(Layer.provide(InkRenderer.Default));
|
|
739
|
-
if (Option.isNone(
|
|
1515
|
+
if (Option.isNone(stdioOption)) layer = layer.pipe(Layer.provide(ProgressStdio.Default));
|
|
740
1516
|
return layer;
|
|
741
1517
|
}));
|
|
742
1518
|
};
|
|
@@ -765,11 +1541,29 @@ const task = dual(2, (effect, options) => {
|
|
|
765
1541
|
});
|
|
766
1542
|
const wrapEffects = (effects, tap) => Array.isArray(effects) ? effects.map(tap) : Object.fromEntries(Object.entries(effects).map(([k, effect]) => [k, tap(effect)]));
|
|
767
1543
|
const countEffects = (effects) => Array.isArray(effects) ? effects.length : Object.keys(effects).length;
|
|
1544
|
+
const isCollectAllMode = (mode) => mode === "either" || mode === "validate";
|
|
1545
|
+
const allCountDisplay = (mode) => isCollectAllMode(mode) ? "detailed" : "processedOnly";
|
|
1546
|
+
const wrapTrackedEffect = (progress, taskId, effect) => Effect.gen(function* () {
|
|
1547
|
+
const exit = yield* Effect.exit(effect);
|
|
1548
|
+
if (Exit.isSuccess(exit)) {
|
|
1549
|
+
yield* progress.advanceTask(taskId, 1);
|
|
1550
|
+
return exit.value;
|
|
1551
|
+
}
|
|
1552
|
+
if (Cause.isInterruptedOnly(exit.cause)) return yield* Effect.failCause(exit.cause);
|
|
1553
|
+
yield* progress.advanceTaskFailed(taskId, 1);
|
|
1554
|
+
return yield* Effect.failCause(exit.cause);
|
|
1555
|
+
});
|
|
1556
|
+
const isTaskFullyProcessed = (progress, taskId) => Effect.gen(function* () {
|
|
1557
|
+
const taskOption = yield* progress.getTask(taskId);
|
|
1558
|
+
if (Option.isNone(taskOption) || taskOption.value.units._tag !== "DeterminateTaskUnits") return false;
|
|
1559
|
+
const { processed, total } = taskOption.value.units;
|
|
1560
|
+
return processed >= total;
|
|
1561
|
+
});
|
|
768
1562
|
const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* () {
|
|
769
1563
|
const progress = yield* Progress;
|
|
770
1564
|
return yield* progress.runTask(Effect.gen(function* () {
|
|
771
1565
|
const taskId = yield* Task;
|
|
772
|
-
const exit = yield* Effect.exit(Effect.all(wrapEffects(effects, (effect) =>
|
|
1566
|
+
const exit = yield* Effect.exit(Effect.all(wrapEffects(effects, (effect) => wrapTrackedEffect(progress, taskId, effect)), {
|
|
773
1567
|
concurrency: options.concurrency,
|
|
774
1568
|
batching: options.batching,
|
|
775
1569
|
discard: options.discard,
|
|
@@ -777,6 +1571,8 @@ const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* (
|
|
|
777
1571
|
concurrentFinalizers: options.concurrentFinalizers
|
|
778
1572
|
}));
|
|
779
1573
|
if (Exit.isSuccess(exit)) yield* progress.completeTask(taskId);
|
|
1574
|
+
else if (!isCollectAllMode(options.mode)) yield* progress.failTask(taskId);
|
|
1575
|
+
else if (yield* isTaskFullyProcessed(progress, taskId)) yield* progress.completeTask(taskId);
|
|
780
1576
|
else yield* progress.failTask(taskId);
|
|
781
1577
|
return yield* Exit.match(exit, {
|
|
782
1578
|
onFailure: Effect.failCause,
|
|
@@ -785,14 +1581,15 @@ const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* (
|
|
|
785
1581
|
}), {
|
|
786
1582
|
description: options.description,
|
|
787
1583
|
total: countEffects(effects),
|
|
788
|
-
transient: options.transient
|
|
1584
|
+
transient: options.transient,
|
|
1585
|
+
countDisplay: allCountDisplay(options.mode)
|
|
789
1586
|
});
|
|
790
1587
|
})));
|
|
791
1588
|
const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(function* () {
|
|
792
1589
|
const progress = yield* Progress;
|
|
793
1590
|
return yield* progress.runTask(Effect.gen(function* () {
|
|
794
1591
|
const taskId = yield* Task;
|
|
795
|
-
const exit = yield* Effect.exit(Effect.forEach(iterable, (item, index) =>
|
|
1592
|
+
const exit = yield* Effect.exit(Effect.forEach(iterable, (item, index) => wrapTrackedEffect(progress, taskId, f(item, index)), {
|
|
796
1593
|
concurrency: options.concurrency,
|
|
797
1594
|
batching: options.batching,
|
|
798
1595
|
discard: options.discard,
|
|
@@ -807,9 +1604,10 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
|
|
|
807
1604
|
}), {
|
|
808
1605
|
description: options.description,
|
|
809
1606
|
total: options.total ?? inferTotal(iterable),
|
|
810
|
-
transient: options.transient
|
|
1607
|
+
transient: options.transient,
|
|
1608
|
+
countDisplay: "processedOnly"
|
|
811
1609
|
});
|
|
812
1610
|
})));
|
|
813
1611
|
|
|
814
1612
|
//#endregion
|
|
815
|
-
export { DeterminateTaskUnits, IndeterminateTaskUnits, Progress,
|
|
1613
|
+
export { DeterminateTaskUnits, IndeterminateTaskUnits, Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|