effective-progress 0.2.4 → 0.4.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/src/runtime.ts CHANGED
@@ -4,8 +4,9 @@ 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 { runProgressServiceRenderer } from "./renderer";
7
+ import { BuildStage, ColorStage, FrameRenderer, ShrinkStage } from "./renderer";
8
8
  import { ProgressTerminal } from "./terminal";
9
+ import { Theme, type ThemeService } from "./theme";
9
10
  import type {
10
11
  AddTaskOptions,
11
12
  ProgressService,
@@ -87,6 +88,19 @@ const updatedSnapshot = (snapshot: TaskSnapshot, options: UpdateTaskOptions): Ta
87
88
  });
88
89
  };
89
90
 
91
+ const withTransient = (snapshot: TaskSnapshot, transient: boolean): TaskSnapshot =>
92
+ new TaskSnapshot({
93
+ id: snapshot.id,
94
+ parentId: snapshot.parentId,
95
+ description: snapshot.description,
96
+ status: snapshot.status,
97
+ transient,
98
+ units: snapshot.units,
99
+ config: snapshot.config,
100
+ startedAt: snapshot.startedAt,
101
+ completedAt: snapshot.completedAt,
102
+ });
103
+
90
104
  const findInsertionIndex = (
91
105
  renderOrder: ReadonlyArray<RenderRow>,
92
106
  parentId: TaskId | null,
@@ -119,7 +133,6 @@ const removeFromRenderOrder = (
119
133
  const makeProgressService = Effect.gen(function* () {
120
134
  const rendererConfigOption = yield* Effect.serviceOption(RendererConfig);
121
135
  const progressBarConfigOption = yield* Effect.serviceOption(ProgressBarConfig);
122
-
123
136
  const rendererConfig = decodeRendererConfigSync(
124
137
  mergeConfig(
125
138
  defaultRendererConfig,
@@ -133,6 +146,7 @@ const makeProgressService = Effect.gen(function* () {
133
146
  ),
134
147
  );
135
148
  const terminal = yield* ProgressTerminal;
149
+ const frameRenderer = yield* FrameRenderer;
136
150
  const isTTY = yield* terminal.isTTY;
137
151
  const maxRetainedLogLines = Math.max(0, Math.floor(rendererConfig.maxLogLines ?? 0));
138
152
 
@@ -140,6 +154,7 @@ const makeProgressService = Effect.gen(function* () {
140
154
  const storeRef = yield* Ref.make<TaskStore>({
141
155
  tasks: new Map<TaskId, TaskSnapshot>(),
142
156
  renderOrder: [],
157
+ themes: new Map<TaskId, ThemeService>(),
143
158
  });
144
159
  const logsRef = yield* Ref.make<ReadonlyArray<string>>([]);
145
160
  const pendingLogsRef = yield* Ref.make<ReadonlyArray<string>>([]);
@@ -148,7 +163,7 @@ const makeProgressService = Effect.gen(function* () {
148
163
  const scope = yield* Effect.scope;
149
164
 
150
165
  yield* Effect.forkIn(
151
- runProgressServiceRenderer(
166
+ frameRenderer.run({
152
167
  storeRef,
153
168
  logsRef,
154
169
  pendingLogsRef,
@@ -157,7 +172,7 @@ const makeProgressService = Effect.gen(function* () {
157
172
  isTTY,
158
173
  rendererConfig,
159
174
  maxRetainedLogLines,
160
- ),
175
+ }),
161
176
  scope,
162
177
  );
163
178
 
@@ -169,6 +184,7 @@ const makeProgressService = Effect.gen(function* () {
169
184
  options.parentId === undefined
170
185
  ? yield* FiberRef.get(currentParentRef)
171
186
  : Option.some(options.parentId);
187
+ const themeOption = yield* Effect.serviceOption(Theme);
172
188
  const taskId = TaskId(yield* Ref.updateAndGet(nextTaskIdRef, (id) => id + 1));
173
189
  const units =
174
190
  options.total === undefined || options.total <= 0
@@ -190,7 +206,7 @@ const makeProgressService = Effect.gen(function* () {
190
206
  parentId: parentIdValue,
191
207
  description: options.description,
192
208
  status: "running",
193
- transient: options.transient ?? false,
209
+ transient: parentSnapshot?.transient ?? options.transient ?? false,
194
210
  units,
195
211
  config: resolvedProgressBarConfig,
196
212
  startedAt: now,
@@ -203,7 +219,11 @@ const makeProgressService = Effect.gen(function* () {
203
219
  const { index, depth } = findInsertionIndex(s.renderOrder, parentIdValue);
204
220
  const nextOrder = [...s.renderOrder];
205
221
  nextOrder.splice(index, 0, { id: taskId, depth });
206
- return { tasks: nextTasks, renderOrder: nextOrder };
222
+ const nextThemes = new Map(s.themes);
223
+ if (Option.isSome(themeOption)) {
224
+ nextThemes.set(taskId, themeOption.value);
225
+ }
226
+ return { tasks: nextTasks, renderOrder: nextOrder, themes: nextThemes };
207
227
  });
208
228
  yield* markDirty;
209
229
 
@@ -215,8 +235,32 @@ const makeProgressService = Effect.gen(function* () {
215
235
  const snapshot = store.tasks.get(taskId);
216
236
  if (!snapshot) return store;
217
237
  const nextTasks = new Map(store.tasks);
218
- nextTasks.set(taskId, updatedSnapshot(snapshot, options));
219
- return { tasks: nextTasks, renderOrder: store.renderOrder };
238
+ const nextSnapshot = updatedSnapshot(snapshot, options);
239
+ nextTasks.set(taskId, nextSnapshot);
240
+
241
+ if (options.transient !== undefined) {
242
+ for (const [candidateId, candidate] of store.tasks.entries()) {
243
+ if (candidateId === taskId) {
244
+ continue;
245
+ }
246
+
247
+ let parentId = candidate.parentId;
248
+ let isDescendant = false;
249
+ while (parentId !== null) {
250
+ if (parentId === taskId) {
251
+ isDescendant = true;
252
+ break;
253
+ }
254
+ parentId = store.tasks.get(parentId)?.parentId ?? null;
255
+ }
256
+
257
+ if (isDescendant) {
258
+ nextTasks.set(candidateId, withTransient(candidate, nextSnapshot.transient));
259
+ }
260
+ }
261
+ }
262
+
263
+ return { tasks: nextTasks, renderOrder: store.renderOrder, themes: store.themes };
220
264
  }).pipe(Effect.zipRight(markDirty));
221
265
 
222
266
  const advanceTask = (taskId: TaskId, amount = 1) =>
@@ -250,7 +294,7 @@ const makeProgressService = Effect.gen(function* () {
250
294
  }),
251
295
  );
252
296
 
253
- return { tasks: nextTasks, renderOrder: store.renderOrder };
297
+ return { tasks: nextTasks, renderOrder: store.renderOrder, themes: store.themes };
254
298
  }).pipe(Effect.zipRight(markDirty));
255
299
 
256
300
  const completeTask = (taskId: TaskId) =>
@@ -263,7 +307,13 @@ const makeProgressService = Effect.gen(function* () {
263
307
  const nextTasks = new Map(store.tasks);
264
308
  if (snapshot.transient) {
265
309
  nextTasks.delete(taskId);
266
- return { tasks: nextTasks, renderOrder: removeFromRenderOrder(store.renderOrder, taskId) };
310
+ const nextThemes = new Map(store.themes);
311
+ nextThemes.delete(taskId);
312
+ return {
313
+ tasks: nextTasks,
314
+ renderOrder: removeFromRenderOrder(store.renderOrder, taskId),
315
+ themes: nextThemes,
316
+ };
267
317
  }
268
318
 
269
319
  nextTasks.set(
@@ -286,7 +336,7 @@ const makeProgressService = Effect.gen(function* () {
286
336
  completedAt: now,
287
337
  }),
288
338
  );
289
- return { tasks: nextTasks, renderOrder: store.renderOrder };
339
+ return { tasks: nextTasks, renderOrder: store.renderOrder, themes: store.themes };
290
340
  });
291
341
  yield* markDirty;
292
342
  });
@@ -301,7 +351,13 @@ const makeProgressService = Effect.gen(function* () {
301
351
  const nextTasks = new Map(store.tasks);
302
352
  if (snapshot.transient) {
303
353
  nextTasks.delete(taskId);
304
- return { tasks: nextTasks, renderOrder: removeFromRenderOrder(store.renderOrder, taskId) };
354
+ const nextThemes = new Map(store.themes);
355
+ nextThemes.delete(taskId);
356
+ return {
357
+ tasks: nextTasks,
358
+ renderOrder: removeFromRenderOrder(store.renderOrder, taskId),
359
+ themes: nextThemes,
360
+ };
305
361
  }
306
362
 
307
363
  nextTasks.set(
@@ -318,7 +374,7 @@ const makeProgressService = Effect.gen(function* () {
318
374
  completedAt: now,
319
375
  }),
320
376
  );
321
- return { tasks: nextTasks, renderOrder: store.renderOrder };
377
+ return { tasks: nextTasks, renderOrder: store.renderOrder, themes: store.themes };
322
378
  });
323
379
  yield* markDirty;
324
380
  });
@@ -371,7 +427,7 @@ const makeProgressService = Effect.gen(function* () {
371
427
  const taskId = yield* addTask({
372
428
  ...options,
373
429
  parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : undefined,
374
- transient: options.transient ?? Option.isSome(resolvedParentId),
430
+ transient: options.transient,
375
431
  });
376
432
 
377
433
  return yield* Effect.locally(
@@ -424,22 +480,40 @@ const makeProgressService = Effect.gen(function* () {
424
480
  return Progress.of(service);
425
481
  });
426
482
 
427
- export class Progress extends Context.Tag("stromseng.dev/Progress")<Progress, ProgressService>() {
428
- static readonly Default = Layer.scoped(Progress, makeProgressService);
429
- }
430
-
431
- export const provideProgressService = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
432
- Effect.gen(function* () {
433
- const existing = yield* Effect.serviceOption(Progress);
434
- if (Option.isSome(existing)) {
435
- return yield* Effect.provideService(effect, Progress, existing.value);
436
- }
437
-
438
- const existingTerminal = yield* Effect.serviceOption(ProgressTerminal);
439
- if (Option.isSome(existingTerminal)) {
440
- return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
441
- }
483
+ export class Progress extends Context.Tag("stromseng.dev/effective-progress/Progress")<
484
+ Progress,
485
+ ProgressService
486
+ >() {
487
+ static readonly Default = Layer.unwrapEffect(
488
+ Effect.gen(function* () {
489
+ const themeOption = yield* Effect.serviceOption(Theme);
490
+ 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
+ const frameRendererOption = yield* Effect.serviceOption(FrameRenderer);
495
+ let layer: Layer.Layer<Progress, never, any> = Layer.scoped(Progress, makeProgressService);
496
+
497
+ if (Option.isNone(frameRendererOption)) {
498
+ layer = layer.pipe(Layer.provide(FrameRenderer.Default));
499
+ }
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
+ if (Option.isNone(terminalOption)) {
513
+ layer = layer.pipe(Layer.provide(ProgressTerminal.Default));
514
+ }
442
515
 
443
- const defaultLayers = Layer.provide(Progress.Default, ProgressTerminal.Default);
444
- return yield* Effect.scoped(effect.pipe(Effect.provide(defaultLayers)));
445
- });
516
+ return layer as Layer.Layer<Progress, never, never>;
517
+ }),
518
+ );
519
+ }
package/src/terminal.ts CHANGED
@@ -42,18 +42,20 @@ const withRawInputCapture: ProgressTerminalService["withRawInputCapture"] = (eff
42
42
  );
43
43
  });
44
44
 
45
+ const defaultTerminalService: ProgressTerminalService = {
46
+ isTTY: Effect.sync(() => Boolean(process.stderr.isTTY)),
47
+ stderrRows: Effect.sync(() => process.stderr.rows),
48
+ stderrColumns: Effect.sync(() => process.stderr.columns),
49
+ writeStderr: (text) =>
50
+ Effect.sync(() => {
51
+ process.stderr.write(text);
52
+ }),
53
+ withRawInputCapture,
54
+ };
55
+
45
56
  export class ProgressTerminal extends Context.Tag("stromseng.dev/ProgressTerminal")<
46
57
  ProgressTerminal,
47
58
  ProgressTerminalService
48
59
  >() {
49
- static readonly Default = Layer.succeed(ProgressTerminal, {
50
- isTTY: Effect.sync(() => Boolean(process.stderr.isTTY)),
51
- stderrRows: Effect.sync(() => process.stderr.rows),
52
- stderrColumns: Effect.sync(() => process.stderr.columns),
53
- writeStderr: (text) =>
54
- Effect.sync(() => {
55
- process.stderr.write(text);
56
- }),
57
- withRawInputCapture,
58
- } satisfies ProgressTerminalService);
60
+ static readonly Default = Layer.succeed(ProgressTerminal, defaultTerminalService);
59
61
  }
package/src/theme.ts ADDED
@@ -0,0 +1,87 @@
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
+ }
package/src/types.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import { Brand, Context, Effect, Option, Schema } from "effect";
2
2
  import type { PartialDeep } from "type-fest";
3
- import { defaultProgressBarColors, ProgressBarColorsSchema } from "./colors";
3
+ import type { ThemeService } from "./theme";
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
+ determinateTaskLayout: Schema.Literal("single-line", "two-lines"),
11
+ maxTaskWidth: Schema.optional(Schema.Number),
10
12
  });
11
13
  export type RendererConfigShape = typeof RendererConfigSchema.Type;
12
14
  export const decodeRendererConfigSync = Schema.decodeUnknownSync(RendererConfigSchema);
@@ -18,7 +20,6 @@ export const ProgressBarConfigSchema = Schema.Struct({
18
20
  emptyChar: Schema.String,
19
21
  leftBracket: Schema.String,
20
22
  rightBracket: Schema.String,
21
- colors: ProgressBarColorsSchema,
22
23
  });
23
24
  export type ProgressBarConfigShape = typeof ProgressBarConfigSchema.Type;
24
25
  export const decodeProgressBarConfigSync = Schema.decodeUnknownSync(ProgressBarConfigSchema);
@@ -28,27 +29,26 @@ export const defaultRendererConfig: RendererConfigShape = {
28
29
  renderIntervalMillis: 100, // 10 FPS
29
30
  maxLogLines: 0,
30
31
  nonTtyUpdateStep: 5,
32
+ determinateTaskLayout: "single-line",
31
33
  };
32
34
 
33
35
  export const defaultProgressBarConfig: ProgressBarConfigShape = {
34
- spinnerFrames: ["-", "\\", "|", "/"],
35
- barWidth: 30,
36
+ spinnerFrames: ["", "", "", "", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
37
+ barWidth: 40,
36
38
  fillChar: "━",
37
39
  emptyChar: "─",
38
40
  leftBracket: "",
39
41
  rightBracket: "",
40
- colors: defaultProgressBarColors,
41
42
  };
42
43
 
43
- export class RendererConfig extends Context.Tag("stromseng.dev/RendererConfig")<
44
+ export class RendererConfig extends Context.Tag("stromseng.dev/effective-progress/RendererConfig")<
44
45
  RendererConfig,
45
46
  PartialDeep<RendererConfigShape>
46
47
  >() {}
47
48
 
48
- export class ProgressBarConfig extends Context.Tag("stromseng.dev/ProgressBarConfig")<
49
- ProgressBarConfig,
50
- PartialDeep<ProgressBarConfigShape>
51
- >() {}
49
+ export class ProgressBarConfig extends Context.Tag(
50
+ "stromseng.dev/effective-progress/ProgressBarConfig",
51
+ )<ProgressBarConfig, PartialDeep<ProgressBarConfigShape>>() {}
52
52
 
53
53
  const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
54
54
 
@@ -115,6 +115,7 @@ export interface RenderRow {
115
115
  export interface TaskStore {
116
116
  readonly tasks: Map<TaskId, TaskSnapshot>;
117
117
  readonly renderOrder: ReadonlyArray<RenderRow>;
118
+ readonly themes: Map<TaskId, ThemeService>;
118
119
  }
119
120
 
120
121
  export interface ProgressService {
@@ -146,7 +147,7 @@ export interface ProgressService {
146
147
  };
147
148
  }
148
149
 
149
- export class Task extends Context.Tag("stromseng.dev/Task")<Task, TaskId>() {}
150
+ export class Task extends Context.Tag("stromseng.dev/effective-progress/Task")<Task, TaskId>() {}
150
151
 
151
152
  export class TaskAddedEvent extends Schema.TaggedClass<TaskAddedEvent>()("TaskAdded", {
152
153
  taskId: TaskIdSchema,
package/src/colors.ts DELETED
@@ -1,156 +0,0 @@
1
- import chalk from "chalk";
2
- import type { ChalkInstance } from "chalk";
3
- import { Schema } from "effect";
4
-
5
- export const StyleModifierSchema = Schema.Literal(
6
- "bold",
7
- "dim",
8
- "italic",
9
- "underline",
10
- "inverse",
11
- "hidden",
12
- "strikethrough",
13
- );
14
- export type StyleModifier = typeof StyleModifierSchema.Type;
15
-
16
- export const NamedColorSchema = Schema.Literal(
17
- "black",
18
- "red",
19
- "green",
20
- "yellow",
21
- "blue",
22
- "magenta",
23
- "cyan",
24
- "white",
25
- "blackBright",
26
- "redBright",
27
- "greenBright",
28
- "yellowBright",
29
- "blueBright",
30
- "magentaBright",
31
- "cyanBright",
32
- "whiteBright",
33
- );
34
- export type NamedColor = typeof NamedColorSchema.Type;
35
-
36
- const HexColorSchema = Schema.String.pipe(Schema.pattern(/^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/));
37
- const ColorChannelSchema = Schema.Number.pipe(
38
- Schema.int(),
39
- Schema.greaterThanOrEqualTo(0),
40
- Schema.lessThanOrEqualTo(255),
41
- );
42
- const Ansi256Schema = Schema.Number.pipe(
43
- Schema.int(),
44
- Schema.greaterThanOrEqualTo(0),
45
- Schema.lessThanOrEqualTo(255),
46
- );
47
- const ModifiersSchema = Schema.optional(Schema.Array(StyleModifierSchema));
48
-
49
- export const NamedColorStyleSchema = Schema.Struct({
50
- kind: Schema.Literal("named"),
51
- value: NamedColorSchema,
52
- modifiers: ModifiersSchema,
53
- });
54
-
55
- export const HexColorStyleSchema = Schema.Struct({
56
- kind: Schema.Literal("hex"),
57
- value: HexColorSchema,
58
- modifiers: ModifiersSchema,
59
- });
60
-
61
- export const RgbColorStyleSchema = Schema.Struct({
62
- kind: Schema.Literal("rgb"),
63
- value: Schema.Struct({
64
- r: ColorChannelSchema,
65
- g: ColorChannelSchema,
66
- b: ColorChannelSchema,
67
- }),
68
- modifiers: ModifiersSchema,
69
- });
70
-
71
- export const Ansi256ColorStyleSchema = Schema.Struct({
72
- kind: Schema.Literal("ansi256"),
73
- value: Ansi256Schema,
74
- modifiers: ModifiersSchema,
75
- });
76
-
77
- export const ColorStyleSchema = Schema.Union(
78
- NamedColorStyleSchema,
79
- HexColorStyleSchema,
80
- RgbColorStyleSchema,
81
- Ansi256ColorStyleSchema,
82
- );
83
- export type ColorStyle = typeof ColorStyleSchema.Type;
84
-
85
- export const ProgressBarColorsSchema = Schema.Struct({
86
- fill: ColorStyleSchema,
87
- empty: ColorStyleSchema,
88
- brackets: ColorStyleSchema,
89
- percent: ColorStyleSchema,
90
- spinner: ColorStyleSchema,
91
- done: ColorStyleSchema,
92
- failed: ColorStyleSchema,
93
- });
94
- export type ProgressBarColors = typeof ProgressBarColorsSchema.Type;
95
-
96
- export const defaultProgressBarColors: ProgressBarColors = {
97
- fill: { kind: "named", value: "cyan" },
98
- empty: { kind: "named", value: "white", modifiers: ["dim"] },
99
- brackets: { kind: "named", value: "white", modifiers: ["dim"] },
100
- percent: { kind: "named", value: "white", modifiers: ["bold"] },
101
- spinner: { kind: "named", value: "yellow" },
102
- done: { kind: "named", value: "green" },
103
- failed: { kind: "named", value: "red" },
104
- };
105
-
106
- const applyModifier = (instance: ChalkInstance, modifier: StyleModifier): ChalkInstance =>
107
- instance[modifier];
108
-
109
- const applyNamedColor = (instance: ChalkInstance, color: NamedColor): ChalkInstance =>
110
- instance[color];
111
-
112
- const resolveBaseStyle = (style: ColorStyle): ChalkInstance => {
113
- switch (style.kind) {
114
- case "named":
115
- return applyNamedColor(chalk, style.value);
116
- case "hex":
117
- return chalk.hex(style.value);
118
- case "rgb":
119
- return chalk.rgb(style.value.r, style.value.g, style.value.b);
120
- case "ansi256":
121
- return chalk.ansi256(style.value);
122
- }
123
- };
124
-
125
- const applyModifiers = (instance: ChalkInstance, modifiers: ReadonlyArray<StyleModifier>) => {
126
- let styled = instance;
127
- for (const modifier of modifiers) {
128
- styled = applyModifier(styled, modifier);
129
- }
130
- return styled;
131
- };
132
-
133
- export const applyColorStyle = (style: ColorStyle, text: string): string => {
134
- const styled = applyModifiers(resolveBaseStyle(style), style.modifiers ?? []);
135
- return styled(text);
136
- };
137
-
138
- export interface CompiledProgressBarColors {
139
- readonly fill: (text: string) => string;
140
- readonly empty: (text: string) => string;
141
- readonly brackets: (text: string) => string;
142
- readonly percent: (text: string) => string;
143
- readonly spinner: (text: string) => string;
144
- readonly done: (text: string) => string;
145
- readonly failed: (text: string) => string;
146
- }
147
-
148
- export const compileProgressBarColors = (colors: ProgressBarColors): CompiledProgressBarColors => ({
149
- fill: (text) => applyColorStyle(colors.fill, text),
150
- empty: (text) => applyColorStyle(colors.empty, text),
151
- brackets: (text) => applyColorStyle(colors.brackets, text),
152
- percent: (text) => applyColorStyle(colors.percent, text),
153
- spinner: (text) => applyColorStyle(colors.spinner, text),
154
- done: (text) => applyColorStyle(colors.done, text),
155
- failed: (text) => applyColorStyle(colors.failed, text),
156
- });