effective-progress 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -63
- package/package.json +1 -1
- package/src/api.ts +57 -21
- package/src/index.ts +0 -1
- package/src/renderer/ansi.ts +138 -0
- package/src/renderer/tree.ts +68 -0
- package/src/renderer/types.ts +67 -0
- package/src/renderer.ts +747 -720
- package/src/runtime.ts +23 -42
- package/src/types.ts +27 -7
- package/src/theme.ts +0 -87
package/src/runtime.ts
CHANGED
|
@@ -4,13 +4,13 @@ import { mergeWith } from "es-toolkit/object";
|
|
|
4
4
|
import { formatWithOptions } from "node:util";
|
|
5
5
|
import type { PartialDeep } from "type-fest";
|
|
6
6
|
import { makeProgressConsole } from "./console";
|
|
7
|
-
import {
|
|
7
|
+
import { Columns, FrameRenderer } from "./renderer";
|
|
8
8
|
import { ProgressTerminal } from "./terminal";
|
|
9
|
-
import { Theme, type ThemeService } from "./theme";
|
|
10
9
|
import type {
|
|
11
10
|
AddTaskOptions,
|
|
12
11
|
ProgressService,
|
|
13
12
|
RenderRow,
|
|
13
|
+
RendererConfigShape,
|
|
14
14
|
TaskStore,
|
|
15
15
|
UpdateTaskOptions,
|
|
16
16
|
} from "./types";
|
|
@@ -130,21 +130,31 @@ const removeFromRenderOrder = (
|
|
|
130
130
|
return next;
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
+
const withDefaultColumns = (rendererConfig: RendererConfigShape): RendererConfigShape => ({
|
|
134
|
+
...rendererConfig,
|
|
135
|
+
columns: rendererConfig.columns.length > 0 ? rendererConfig.columns : Columns.defaults(),
|
|
136
|
+
});
|
|
137
|
+
|
|
133
138
|
const makeProgressService = Effect.gen(function* () {
|
|
134
139
|
const rendererConfigOption = yield* Effect.serviceOption(RendererConfig);
|
|
135
140
|
const progressBarConfigOption = yield* Effect.serviceOption(ProgressBarConfig);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
|
|
142
|
+
const rendererConfig = withDefaultColumns(
|
|
143
|
+
decodeRendererConfigSync(
|
|
144
|
+
mergeConfig(
|
|
145
|
+
defaultRendererConfig,
|
|
146
|
+
Option.isSome(rendererConfigOption) ? rendererConfigOption.value : undefined,
|
|
147
|
+
),
|
|
140
148
|
),
|
|
141
149
|
);
|
|
150
|
+
|
|
142
151
|
const progressBarConfig = decodeProgressBarConfigSync(
|
|
143
152
|
mergeConfig(
|
|
144
153
|
defaultProgressBarConfig,
|
|
145
154
|
Option.isSome(progressBarConfigOption) ? progressBarConfigOption.value : undefined,
|
|
146
155
|
),
|
|
147
156
|
);
|
|
157
|
+
|
|
148
158
|
const terminal = yield* ProgressTerminal;
|
|
149
159
|
const frameRenderer = yield* FrameRenderer;
|
|
150
160
|
const isTTY = yield* terminal.isTTY;
|
|
@@ -154,7 +164,6 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
154
164
|
const storeRef = yield* Ref.make<TaskStore>({
|
|
155
165
|
tasks: new Map<TaskId, TaskSnapshot>(),
|
|
156
166
|
renderOrder: [],
|
|
157
|
-
themes: new Map<TaskId, ThemeService>(),
|
|
158
167
|
});
|
|
159
168
|
const logsRef = yield* Ref.make<ReadonlyArray<string>>([]);
|
|
160
169
|
const pendingLogsRef = yield* Ref.make<ReadonlyArray<string>>([]);
|
|
@@ -163,7 +172,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
163
172
|
const scope = yield* Effect.scope;
|
|
164
173
|
|
|
165
174
|
yield* Effect.forkIn(
|
|
166
|
-
frameRenderer.run(
|
|
175
|
+
frameRenderer.run(
|
|
167
176
|
storeRef,
|
|
168
177
|
logsRef,
|
|
169
178
|
pendingLogsRef,
|
|
@@ -172,7 +181,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
172
181
|
isTTY,
|
|
173
182
|
rendererConfig,
|
|
174
183
|
maxRetainedLogLines,
|
|
175
|
-
|
|
184
|
+
),
|
|
176
185
|
scope,
|
|
177
186
|
);
|
|
178
187
|
|
|
@@ -184,7 +193,6 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
184
193
|
options.parentId === undefined
|
|
185
194
|
? yield* FiberRef.get(currentParentRef)
|
|
186
195
|
: Option.some(options.parentId);
|
|
187
|
-
const themeOption = yield* Effect.serviceOption(Theme);
|
|
188
196
|
const taskId = TaskId(yield* Ref.updateAndGet(nextTaskIdRef, (id) => id + 1));
|
|
189
197
|
const units =
|
|
190
198
|
options.total === undefined || options.total <= 0
|
|
@@ -219,11 +227,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
219
227
|
const { index, depth } = findInsertionIndex(s.renderOrder, parentIdValue);
|
|
220
228
|
const nextOrder = [...s.renderOrder];
|
|
221
229
|
nextOrder.splice(index, 0, { id: taskId, depth });
|
|
222
|
-
|
|
223
|
-
if (Option.isSome(themeOption)) {
|
|
224
|
-
nextThemes.set(taskId, themeOption.value);
|
|
225
|
-
}
|
|
226
|
-
return { tasks: nextTasks, renderOrder: nextOrder, themes: nextThemes };
|
|
230
|
+
return { tasks: nextTasks, renderOrder: nextOrder };
|
|
227
231
|
});
|
|
228
232
|
yield* markDirty;
|
|
229
233
|
|
|
@@ -260,7 +264,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
260
264
|
}
|
|
261
265
|
}
|
|
262
266
|
|
|
263
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder
|
|
267
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
264
268
|
}).pipe(Effect.zipRight(markDirty));
|
|
265
269
|
|
|
266
270
|
const advanceTask = (taskId: TaskId, amount = 1) =>
|
|
@@ -294,7 +298,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
294
298
|
}),
|
|
295
299
|
);
|
|
296
300
|
|
|
297
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder
|
|
301
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
298
302
|
}).pipe(Effect.zipRight(markDirty));
|
|
299
303
|
|
|
300
304
|
const completeTask = (taskId: TaskId) =>
|
|
@@ -307,12 +311,9 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
307
311
|
const nextTasks = new Map(store.tasks);
|
|
308
312
|
if (snapshot.transient) {
|
|
309
313
|
nextTasks.delete(taskId);
|
|
310
|
-
const nextThemes = new Map(store.themes);
|
|
311
|
-
nextThemes.delete(taskId);
|
|
312
314
|
return {
|
|
313
315
|
tasks: nextTasks,
|
|
314
316
|
renderOrder: removeFromRenderOrder(store.renderOrder, taskId),
|
|
315
|
-
themes: nextThemes,
|
|
316
317
|
};
|
|
317
318
|
}
|
|
318
319
|
|
|
@@ -336,7 +337,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
336
337
|
completedAt: now,
|
|
337
338
|
}),
|
|
338
339
|
);
|
|
339
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder
|
|
340
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
340
341
|
});
|
|
341
342
|
yield* markDirty;
|
|
342
343
|
});
|
|
@@ -351,12 +352,9 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
351
352
|
const nextTasks = new Map(store.tasks);
|
|
352
353
|
if (snapshot.transient) {
|
|
353
354
|
nextTasks.delete(taskId);
|
|
354
|
-
const nextThemes = new Map(store.themes);
|
|
355
|
-
nextThemes.delete(taskId);
|
|
356
355
|
return {
|
|
357
356
|
tasks: nextTasks,
|
|
358
357
|
renderOrder: removeFromRenderOrder(store.renderOrder, taskId),
|
|
359
|
-
themes: nextThemes,
|
|
360
358
|
};
|
|
361
359
|
}
|
|
362
360
|
|
|
@@ -374,7 +372,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
374
372
|
completedAt: now,
|
|
375
373
|
}),
|
|
376
374
|
);
|
|
377
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder
|
|
375
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
378
376
|
});
|
|
379
377
|
yield* markDirty;
|
|
380
378
|
});
|
|
@@ -385,7 +383,6 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
385
383
|
return;
|
|
386
384
|
}
|
|
387
385
|
|
|
388
|
-
// TODO: Might wanna replace this or make it configurable. Look for other options.
|
|
389
386
|
const message = formatWithOptions(
|
|
390
387
|
{
|
|
391
388
|
colors: isTTY,
|
|
@@ -486,29 +483,13 @@ export class Progress extends Context.Tag("stromseng.dev/effective-progress/Prog
|
|
|
486
483
|
>() {
|
|
487
484
|
static readonly Default = Layer.unwrapEffect(
|
|
488
485
|
Effect.gen(function* () {
|
|
489
|
-
const themeOption = yield* Effect.serviceOption(Theme);
|
|
490
486
|
const terminalOption = yield* Effect.serviceOption(ProgressTerminal);
|
|
491
|
-
const buildStageOption = yield* Effect.serviceOption(BuildStage);
|
|
492
|
-
const shrinkStageOption = yield* Effect.serviceOption(ShrinkStage);
|
|
493
|
-
const colorStageOption = yield* Effect.serviceOption(ColorStage);
|
|
494
487
|
const frameRendererOption = yield* Effect.serviceOption(FrameRenderer);
|
|
495
488
|
let layer: Layer.Layer<Progress, never, any> = Layer.scoped(Progress, makeProgressService);
|
|
496
489
|
|
|
497
490
|
if (Option.isNone(frameRendererOption)) {
|
|
498
491
|
layer = layer.pipe(Layer.provide(FrameRenderer.Default));
|
|
499
492
|
}
|
|
500
|
-
if (Option.isNone(colorStageOption)) {
|
|
501
|
-
layer = layer.pipe(Layer.provide(ColorStage.Default));
|
|
502
|
-
}
|
|
503
|
-
if (Option.isNone(shrinkStageOption)) {
|
|
504
|
-
layer = layer.pipe(Layer.provide(ShrinkStage.Default));
|
|
505
|
-
}
|
|
506
|
-
if (Option.isNone(buildStageOption)) {
|
|
507
|
-
layer = layer.pipe(Layer.provide(BuildStage.Default));
|
|
508
|
-
}
|
|
509
|
-
if (Option.isNone(themeOption)) {
|
|
510
|
-
layer = layer.pipe(Layer.provide(Theme.Default));
|
|
511
|
-
}
|
|
512
493
|
if (Option.isNone(terminalOption)) {
|
|
513
494
|
layer = layer.pipe(Layer.provide(ProgressTerminal.Default));
|
|
514
495
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { Brand, Context, Effect, Option, Schema } from "effect";
|
|
2
2
|
import type { PartialDeep } from "type-fest";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ProgressColumn } from "./renderer";
|
|
4
4
|
|
|
5
5
|
export const RendererConfigSchema = Schema.Struct({
|
|
6
6
|
disableUserInput: Schema.Boolean,
|
|
7
7
|
renderIntervalMillis: Schema.Number,
|
|
8
8
|
maxLogLines: Schema.optional(Schema.Number),
|
|
9
9
|
nonTtyUpdateStep: Schema.Number,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
width: Schema.Union(Schema.Number, Schema.Literal("fullwidth")),
|
|
11
|
+
columnGap: Schema.Number,
|
|
12
|
+
columns: Schema.Array(Schema.Unknown),
|
|
12
13
|
});
|
|
13
|
-
export type RendererConfigShape =
|
|
14
|
-
|
|
14
|
+
export type RendererConfigShape = {
|
|
15
|
+
readonly disableUserInput: boolean;
|
|
16
|
+
readonly renderIntervalMillis: number;
|
|
17
|
+
readonly maxLogLines?: number;
|
|
18
|
+
readonly nonTtyUpdateStep: number;
|
|
19
|
+
readonly width: number | "fullwidth";
|
|
20
|
+
readonly columnGap: number;
|
|
21
|
+
readonly columns: ReadonlyArray<ProgressColumn | string>;
|
|
22
|
+
};
|
|
23
|
+
const decodeRendererConfigSchemaSync = Schema.decodeUnknownSync(RendererConfigSchema);
|
|
24
|
+
export const decodeRendererConfigSync = (input: unknown): RendererConfigShape => {
|
|
25
|
+
if (typeof input === "object" && input !== null && "determinateTaskLayout" in input) {
|
|
26
|
+
throw new Error("determinateTaskLayout has been removed. Use columns instead.");
|
|
27
|
+
}
|
|
28
|
+
if (typeof input === "object" && input !== null && "maxTaskWidth" in input) {
|
|
29
|
+
throw new Error("maxTaskWidth has been removed. Use width on RendererConfig instead.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return decodeRendererConfigSchemaSync(input) as RendererConfigShape;
|
|
33
|
+
};
|
|
15
34
|
|
|
16
35
|
export const ProgressBarConfigSchema = Schema.Struct({
|
|
17
36
|
spinnerFrames: Schema.NonEmptyArray(Schema.String),
|
|
@@ -29,7 +48,9 @@ export const defaultRendererConfig: RendererConfigShape = {
|
|
|
29
48
|
renderIntervalMillis: 100, // 10 FPS
|
|
30
49
|
maxLogLines: 0,
|
|
31
50
|
nonTtyUpdateStep: 5,
|
|
32
|
-
|
|
51
|
+
width: 120,
|
|
52
|
+
columnGap: 1,
|
|
53
|
+
columns: [],
|
|
33
54
|
};
|
|
34
55
|
|
|
35
56
|
export const defaultProgressBarConfig: ProgressBarConfigShape = {
|
|
@@ -115,7 +136,6 @@ export interface RenderRow {
|
|
|
115
136
|
export interface TaskStore {
|
|
116
137
|
readonly tasks: Map<TaskId, TaskSnapshot>;
|
|
117
138
|
readonly renderOrder: ReadonlyArray<RenderRow>;
|
|
118
|
-
readonly themes: Map<TaskId, ThemeService>;
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
export interface ProgressService {
|
package/src/theme.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import chalk, { type ChalkInstance } from "chalk";
|
|
2
|
-
import { Context, Effect, Layer } from "effect";
|
|
3
|
-
|
|
4
|
-
export type ThemeRole =
|
|
5
|
-
| "plain"
|
|
6
|
-
| "barFill"
|
|
7
|
-
| "barEmpty"
|
|
8
|
-
| "barBracket"
|
|
9
|
-
| "spinner"
|
|
10
|
-
| "statusDone"
|
|
11
|
-
| "statusFailed"
|
|
12
|
-
| "text"
|
|
13
|
-
| "units"
|
|
14
|
-
| "eta"
|
|
15
|
-
| "elapsed"
|
|
16
|
-
| "treeConnector";
|
|
17
|
-
|
|
18
|
-
export type ThemeStyle = (text: string) => string;
|
|
19
|
-
|
|
20
|
-
export interface ThemeService {
|
|
21
|
-
readonly styles: Readonly<Record<ThemeRole, ThemeStyle>>;
|
|
22
|
-
readonly depthPalette?: (depth: number, role: ThemeRole) => ThemeStyle | undefined;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class Theme extends Context.Tag("stromseng.dev/effective-progress/Theme")<
|
|
26
|
-
Theme,
|
|
27
|
-
ThemeService
|
|
28
|
-
>() {
|
|
29
|
-
static readonly Default = Layer.succeed(
|
|
30
|
-
Theme,
|
|
31
|
-
Theme.of({
|
|
32
|
-
styles: {
|
|
33
|
-
plain: (text) => text,
|
|
34
|
-
barFill: chalk.blue,
|
|
35
|
-
barEmpty: chalk.white.dim,
|
|
36
|
-
barBracket: chalk.white.dim,
|
|
37
|
-
spinner: chalk.yellow,
|
|
38
|
-
statusDone: chalk.green,
|
|
39
|
-
statusFailed: chalk.red,
|
|
40
|
-
text: (text) => text,
|
|
41
|
-
units: chalk.whiteBright,
|
|
42
|
-
eta: chalk.gray,
|
|
43
|
-
elapsed: chalk.gray,
|
|
44
|
-
treeConnector: chalk.gray,
|
|
45
|
-
},
|
|
46
|
-
}),
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
static readonly Rainbow = Layer.unwrapEffect(
|
|
50
|
-
Effect.gen(function* () {
|
|
51
|
-
let colorNumber = 17;
|
|
52
|
-
yield* Effect.fork(
|
|
53
|
-
Effect.gen(function* () {
|
|
54
|
-
while (true) {
|
|
55
|
-
colorNumber = (colorNumber + 1) % 256;
|
|
56
|
-
yield* Effect.sleep("200 millis");
|
|
57
|
-
}
|
|
58
|
-
}),
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const dynamic =
|
|
62
|
-
(instance: ChalkInstance): ThemeStyle =>
|
|
63
|
-
(text) =>
|
|
64
|
-
instance.ansi256(colorNumber)(text);
|
|
65
|
-
|
|
66
|
-
return Layer.succeed(
|
|
67
|
-
Theme,
|
|
68
|
-
Theme.of({
|
|
69
|
-
styles: {
|
|
70
|
-
plain: (text) => text,
|
|
71
|
-
barFill: dynamic(chalk),
|
|
72
|
-
barEmpty: dynamic(chalk.dim),
|
|
73
|
-
barBracket: dynamic(chalk),
|
|
74
|
-
spinner: dynamic(chalk),
|
|
75
|
-
statusDone: dynamic(chalk.bold),
|
|
76
|
-
statusFailed: dynamic(chalk.bold),
|
|
77
|
-
text: dynamic(chalk),
|
|
78
|
-
units: dynamic(chalk),
|
|
79
|
-
eta: dynamic(chalk),
|
|
80
|
-
elapsed: dynamic(chalk),
|
|
81
|
-
treeConnector: dynamic(chalk),
|
|
82
|
-
},
|
|
83
|
-
}),
|
|
84
|
-
);
|
|
85
|
-
}),
|
|
86
|
-
);
|
|
87
|
-
}
|