effective-progress 0.1.0 → 0.1.3
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 +127 -17
- package/package.json +17 -10
- package/src/api.ts +57 -76
- package/src/colors.ts +206 -0
- package/src/console.ts +3 -4
- package/src/index.ts +1 -0
- package/src/renderer.ts +131 -138
- package/src/runtime.ts +111 -51
- package/src/types.ts +33 -23
- package/src/utils.ts +20 -0
package/README.md
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
# effective-progress
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/effective-progress)
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> Pre-`1.0.0`, breaking changes may happen in any release. SemVer guarantees will begin at `1.0.0`.
|
|
7
|
+
> I recommend using only the `Progress.all` and `Progress.forEach` APIs for now, as they will likely change the least. The lower-level APIs for manual progress bar control are more likely to see breaking changes as I iterate on the design.
|
|
8
|
+
>
|
|
9
|
+
> Please open an issue or reach out if you have any questions or want to contribute!
|
|
10
|
+
> Feedback and contributions are very welcome!
|
|
11
|
+
|
|
12
|
+
<img alt="Showcase output" src="docs/images/showcase.gif" width="600" />
|
|
13
|
+
|
|
14
|
+
`effective-progress` is an [Effect](https://effect.website/)-first terminal progress library with:
|
|
4
15
|
|
|
5
16
|
- multiple progress bars
|
|
6
17
|
- nested child progress bars
|
|
7
18
|
- spinner support for indeterminate work
|
|
8
|
-
- clean log rendering alongside progress output
|
|
19
|
+
- clean log rendering alongside progress output, allowing you to simply use Effects `Console.log` or `Effect.logInfo`.
|
|
20
|
+
- simple to use `.all` and `.forEach` APIs similar to the ones you already know and love from `effect`. Just swap `Effect` for `Progress` and get progress bars for free!
|
|
9
21
|
|
|
10
22
|
## Install
|
|
11
23
|
|
|
@@ -26,18 +38,13 @@ const program = Progress.all(
|
|
|
26
38
|
yield* Console.log(`Completed task ${i + 1}`);
|
|
27
39
|
}),
|
|
28
40
|
),
|
|
29
|
-
{ description: "Running tasks in parallel",
|
|
41
|
+
{ description: "Running tasks in parallel", concurrency: 2 },
|
|
30
42
|
);
|
|
31
43
|
|
|
32
44
|
Effect.runPromise(program);
|
|
33
45
|
```
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
bun run examples/basic.ts
|
|
37
|
-
Completed task 1
|
|
38
|
-
Completed task 2
|
|
39
|
-
- Running tasks in parallel: ━━━━━━━━━━━━────────────────── 2/5 40%
|
|
40
|
-
```
|
|
47
|
+
<img alt="Basic example output" src="docs/images/basic.gif" width="600" />
|
|
41
48
|
|
|
42
49
|
## Nested example
|
|
43
50
|
|
|
@@ -62,25 +69,128 @@ const program = Progress.all(
|
|
|
62
69
|
),
|
|
63
70
|
),
|
|
64
71
|
),
|
|
65
|
-
{ description: "Running tasks in parallel",
|
|
72
|
+
{ description: "Running tasks in parallel", concurrency: 2 },
|
|
66
73
|
);
|
|
67
74
|
|
|
68
75
|
Effect.runPromise(program);
|
|
69
76
|
```
|
|
70
77
|
|
|
71
|
-
|
|
72
|
-
❯ bun run examples/nesting.ts
|
|
73
|
-
- Running tasks in parallel: ━━━━━━━━━━━━────────────────── 2/5 40%
|
|
74
|
-
- Running subtasks for task 3: ━━━━━━━━────────────────────── 4/15 27%
|
|
75
|
-
- Running subtasks for task 4: ━━━━━━━━────────────────────── 4/15 27%
|
|
76
|
-
```
|
|
78
|
+
<img alt="Nested example output" src="docs/images/nesting.gif" width="600" />
|
|
77
79
|
|
|
78
80
|
## Other examples
|
|
79
81
|
|
|
80
82
|
- `examples/simpleExample.ts` - low-boilerplate real-world flow
|
|
81
83
|
- `examples/advancedExample.ts` - full API usage with custom config and manual task control
|
|
84
|
+
- `examples/showcase.ts` - nested concurrent tasks, spinner workloads, and mixed Effect/Console logging
|
|
85
|
+
|
|
86
|
+
## Log retention
|
|
87
|
+
|
|
88
|
+
- `maxLogLines` on `RendererConfig` controls in-memory log retention.
|
|
89
|
+
- Omitted or set to `0` means no log history is kept in memory.
|
|
90
|
+
- `maxLogLines > 0` keeps only the latest `N` log lines in memory.
|
|
91
|
+
|
|
92
|
+
## Configuring renderer and progress bars
|
|
93
|
+
|
|
94
|
+
Configure global renderer behavior once, and a global base progress bar style:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { Effect } from "effect";
|
|
98
|
+
import * as Progress from "effective-progress";
|
|
99
|
+
|
|
100
|
+
const configured = program.pipe(
|
|
101
|
+
Effect.provideService(Progress.RendererConfig, {
|
|
102
|
+
maxLogLines: 12,
|
|
103
|
+
nonTtyUpdateStep: 2,
|
|
104
|
+
}),
|
|
105
|
+
Effect.provideService(Progress.ProgressBarConfig, {
|
|
106
|
+
barWidth: 36,
|
|
107
|
+
colors: {
|
|
108
|
+
fill: { kind: "hex", value: "#00b894" },
|
|
109
|
+
spinner: { kind: "ansi256", value: 214 },
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
Effect.runPromise(configured);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Task-level `progressbar` config is optional and inherits from its parent task (or from global `ProgressBarConfig` for root tasks):
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
yield *
|
|
121
|
+
progress.withTask(
|
|
122
|
+
{
|
|
123
|
+
description: "Worker pipeline",
|
|
124
|
+
progressbar: {
|
|
125
|
+
barWidth: 20,
|
|
126
|
+
spinnerFrames: [".", "o", "O", "0"],
|
|
127
|
+
colors: {
|
|
128
|
+
spinner: { kind: "named", value: "magentaBright" },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
() => Effect.sleep("1 second"),
|
|
133
|
+
);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
For manual service usage, capture logs explicitly:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const program = Progress.provide(
|
|
140
|
+
Effect.gen(function* () {
|
|
141
|
+
const progress = yield* Progress.Progress;
|
|
142
|
+
|
|
143
|
+
yield* progress.withTask({ description: "Manual task" }, () =>
|
|
144
|
+
progress.withCapturedLogs(
|
|
145
|
+
Effect.gen(function* () {
|
|
146
|
+
yield* Console.log("This log is rendered through progress output");
|
|
147
|
+
yield* Effect.sleep("1 second");
|
|
148
|
+
}),
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Progress bar colors
|
|
156
|
+
|
|
157
|
+
`progressbar.colors` is configured with typed color tokens that are validated by Effect Schema.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
progressbar: {
|
|
161
|
+
spinnerFrames: ["-", "\\", "|", "/"],
|
|
162
|
+
barWidth: 30,
|
|
163
|
+
fillChar: "━",
|
|
164
|
+
emptyChar: "─",
|
|
165
|
+
leftBracket: "",
|
|
166
|
+
rightBracket: "",
|
|
167
|
+
colors: {
|
|
168
|
+
fill: { kind: "named", value: "cyan" },
|
|
169
|
+
empty: { kind: "hex", value: "#9ca3af", modifiers: ["dim"] },
|
|
170
|
+
brackets: { kind: "rgb", value: { r: 156, g: 163, b: 175 } },
|
|
171
|
+
percent: { kind: "named", value: "whiteBright", modifiers: ["bold"] },
|
|
172
|
+
spinner: { kind: "ansi256", value: 214 },
|
|
173
|
+
done: { kind: "named", value: "greenBright" },
|
|
174
|
+
failed: { kind: "named", value: "redBright", modifiers: ["bold"] },
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Supported color styles:
|
|
180
|
+
|
|
181
|
+
- `named` (for example `cyan`, `greenBright`)
|
|
182
|
+
- `hex` (for example `#00b894`)
|
|
183
|
+
- `rgb` (for example `{ r: 0, g: 184, b: 148 }`)
|
|
184
|
+
- `ansi256` (for example `214`)
|
|
185
|
+
|
|
186
|
+
Supported modifiers:
|
|
187
|
+
|
|
188
|
+
- `bold`, `dim`, `italic`, `underline`, `inverse`, `hidden`, `strikethrough`
|
|
189
|
+
|
|
190
|
+
## Dependencies & package size
|
|
191
|
+
|
|
192
|
+
This library is designed for CLI workflows, where package size is typically a lower-priority concern. Alongside `effect`, it relies on two mature runtime dependencies: `chalk` and `es-toolkit`.
|
|
82
193
|
|
|
83
194
|
## Notes
|
|
84
195
|
|
|
85
|
-
- This is a WIP library, so expect breaking changes. Feedback and contributions are very welcome!
|
|
86
196
|
- As Effect 4.0 is around the corner with some changes to logging, there may be some adjustments needed to align with the new Effect APIs.
|
package/package.json
CHANGED
|
@@ -1,37 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effective-progress",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Effect-first terminal progress bars with nested multibar support",
|
|
5
|
+
"homepage": "https://github.com/stromseng/effective-progress#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/stromseng/effective-progress/issues"
|
|
8
|
+
},
|
|
5
9
|
"license": "MIT",
|
|
6
10
|
"repository": {
|
|
7
11
|
"type": "git",
|
|
8
12
|
"url": "git+https://github.com/stromseng/effective-progress.git"
|
|
9
13
|
},
|
|
10
|
-
"bugs": {
|
|
11
|
-
"url": "https://github.com/stromseng/effective-progress/issues"
|
|
12
|
-
},
|
|
13
|
-
"homepage": "https://github.com/stromseng/effective-progress#readme",
|
|
14
|
-
"type": "module",
|
|
15
|
-
"module": "index.ts",
|
|
16
14
|
"files": [
|
|
17
15
|
"index.ts",
|
|
18
16
|
"src",
|
|
19
17
|
"README.md",
|
|
20
18
|
"LICENSE"
|
|
21
19
|
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"module": "index.ts",
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
|
-
"prepare": "effect-language-service patch"
|
|
26
|
+
"prepare": "effect-language-service patch",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"lint": "oxlint .",
|
|
29
|
+
"format": "oxfmt --write .",
|
|
30
|
+
"format:check": "oxfmt --check ."
|
|
27
31
|
},
|
|
28
32
|
"dependencies": {
|
|
29
|
-
"@effect/language-service": "^0.73.1",
|
|
30
33
|
"chalk": "^5.6.2",
|
|
31
|
-
"effect": "^3.19.16"
|
|
34
|
+
"effect": "^3.19.16",
|
|
35
|
+
"es-toolkit": "^1.44.0",
|
|
36
|
+
"type-fest": "^5.4.4"
|
|
32
37
|
},
|
|
33
38
|
"devDependencies": {
|
|
39
|
+
"@effect/language-service": "^0.73.1",
|
|
34
40
|
"@types/bun": "latest",
|
|
41
|
+
"oxfmt": "^0.32.0",
|
|
35
42
|
"oxlint": "^1.47.0"
|
|
36
43
|
},
|
|
37
44
|
"peerDependencies": {
|
package/src/api.ts
CHANGED
|
@@ -1,134 +1,115 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import type { Concurrency } from "effect/Types";
|
|
3
3
|
import { makeProgressService } from "./runtime";
|
|
4
4
|
import { Progress } from "./types";
|
|
5
5
|
import type { TrackOptions } from "./types";
|
|
6
|
+
import { inferTotal } from "./utils";
|
|
6
7
|
|
|
7
|
-
export interface
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
11
|
-
readonly all?: {
|
|
12
|
-
readonly concurrency?: number | "unbounded";
|
|
13
|
-
readonly batching?: boolean | "inherit";
|
|
14
|
-
readonly concurrentFinalizers?: boolean;
|
|
15
|
-
};
|
|
8
|
+
export interface EffectExecutionOptions {
|
|
9
|
+
readonly concurrency?: Concurrency;
|
|
10
|
+
readonly batching?: boolean | "inherit";
|
|
11
|
+
readonly concurrentFinalizers?: boolean;
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
export interface
|
|
19
|
-
readonly
|
|
20
|
-
|
|
21
|
-
readonly batching?: boolean | "inherit";
|
|
22
|
-
readonly concurrentFinalizers?: boolean;
|
|
23
|
-
};
|
|
14
|
+
export interface EffectAllExecutionOptions extends EffectExecutionOptions {
|
|
15
|
+
readonly discard?: boolean;
|
|
16
|
+
readonly mode?: "default" | "validate" | "either";
|
|
24
17
|
}
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
export type AllOptions = Omit<TrackOptions, "total"> & EffectAllExecutionOptions;
|
|
20
|
+
export type AllReturn<
|
|
21
|
+
Arg extends ReadonlyArray<Effect.Effect<any, any, any>>,
|
|
22
|
+
O extends EffectAllExecutionOptions,
|
|
23
|
+
> =
|
|
24
|
+
[Effect.All.ReturnTuple<Arg, Effect.All.IsDiscard<O>, Effect.All.ExtractMode<O>>] extends
|
|
25
|
+
[Effect.Effect<infer A, infer E, infer R>] ? Effect.Effect<A, E, Exclude<R, Progress>>
|
|
26
|
+
: never;
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const candidate = iterable as { length?: unknown; size?: unknown };
|
|
36
|
-
if (typeof candidate.length === "number") {
|
|
37
|
-
return candidate.length;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (typeof candidate.size === "number") {
|
|
41
|
-
return candidate.size;
|
|
42
|
-
}
|
|
28
|
+
export interface ForEachExecutionOptions extends EffectExecutionOptions {
|
|
29
|
+
readonly discard?: false | undefined;
|
|
30
|
+
}
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
};
|
|
32
|
+
export type ForEachOptions = TrackOptions & ForEachExecutionOptions;
|
|
46
33
|
|
|
47
|
-
export const
|
|
34
|
+
export const provide = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
48
35
|
Effect.gen(function* () {
|
|
49
|
-
const outerConsole = yield* Console.consoleWith((console) => Effect.succeed(console));
|
|
50
36
|
const existing = yield* Effect.serviceOption(Progress);
|
|
51
37
|
if (Option.isSome(existing)) {
|
|
52
|
-
|
|
53
|
-
return yield* Effect.withConsole(
|
|
54
|
-
Effect.provideService(effect, Progress, existing.value),
|
|
55
|
-
console,
|
|
56
|
-
);
|
|
38
|
+
return yield* Effect.provideService(effect, Progress, existing.value);
|
|
57
39
|
}
|
|
58
40
|
|
|
59
41
|
return yield* Effect.scoped(
|
|
60
42
|
Effect.gen(function* () {
|
|
61
43
|
const service = yield* makeProgressService;
|
|
62
|
-
|
|
63
|
-
return yield* Effect.withConsole(Effect.provideService(effect, Progress, service), console);
|
|
44
|
+
return yield* Effect.provideService(effect, Progress, service);
|
|
64
45
|
}),
|
|
65
46
|
);
|
|
66
47
|
});
|
|
67
48
|
|
|
68
|
-
export const all = <
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
49
|
+
export const all = <
|
|
50
|
+
const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>,
|
|
51
|
+
O extends EffectAllExecutionOptions,
|
|
52
|
+
>(
|
|
53
|
+
effects: Arg,
|
|
54
|
+
options: Omit<TrackOptions, "total"> & O,
|
|
55
|
+
): AllReturn<Arg, O> =>
|
|
56
|
+
provide(
|
|
73
57
|
Effect.gen(function* () {
|
|
74
58
|
const progress = yield* Progress;
|
|
75
59
|
return yield* progress.withTask(
|
|
76
60
|
{
|
|
77
61
|
description: options.description,
|
|
78
|
-
total:
|
|
62
|
+
total: effects.length,
|
|
79
63
|
transient: options.transient,
|
|
64
|
+
progressbar: options.progressbar,
|
|
80
65
|
},
|
|
81
66
|
(taskId) =>
|
|
82
|
-
Effect.
|
|
83
|
-
effects.map((effect) =>
|
|
84
|
-
|
|
67
|
+
Effect.all(
|
|
68
|
+
effects.map((effect) =>
|
|
69
|
+
Effect.tap(progress.withCapturedLogs(effect), () => progress.advanceTask(taskId, 1)),
|
|
70
|
+
),
|
|
85
71
|
{
|
|
86
|
-
concurrency: options.
|
|
87
|
-
batching: options.
|
|
88
|
-
|
|
72
|
+
concurrency: options.concurrency,
|
|
73
|
+
batching: options.batching,
|
|
74
|
+
discard: options.discard,
|
|
75
|
+
mode: options.mode,
|
|
76
|
+
concurrentFinalizers: options.concurrentFinalizers,
|
|
89
77
|
},
|
|
90
78
|
),
|
|
91
79
|
);
|
|
92
80
|
}),
|
|
93
|
-
)
|
|
81
|
+
) as AllReturn<Arg, O>;
|
|
94
82
|
|
|
95
83
|
export const forEach = <A, B, E, R>(
|
|
96
84
|
iterable: Iterable<A>,
|
|
97
85
|
f: (item: A, index: number) => Effect.Effect<B, E, R>,
|
|
98
86
|
options: ForEachOptions,
|
|
99
87
|
): Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress>> =>
|
|
100
|
-
|
|
88
|
+
provide(
|
|
101
89
|
Effect.gen(function* () {
|
|
102
90
|
const progress = yield* Progress;
|
|
103
91
|
|
|
104
|
-
const items = Array.from(iterable);
|
|
105
|
-
const total = options.total ?? inferTotal(iterable);
|
|
106
|
-
|
|
107
92
|
return yield* progress.withTask(
|
|
108
93
|
{
|
|
109
94
|
description: options.description,
|
|
110
|
-
total,
|
|
95
|
+
total: options.total ?? inferTotal(iterable),
|
|
111
96
|
transient: options.transient,
|
|
97
|
+
progressbar: options.progressbar,
|
|
112
98
|
},
|
|
113
99
|
(taskId) =>
|
|
114
100
|
Effect.forEach(
|
|
115
|
-
|
|
116
|
-
(item, index) =>
|
|
101
|
+
iterable,
|
|
102
|
+
(item, index) =>
|
|
103
|
+
Effect.tap(progress.withCapturedLogs(f(item, index)), () =>
|
|
104
|
+
progress.advanceTask(taskId, 1),
|
|
105
|
+
),
|
|
117
106
|
{
|
|
118
|
-
concurrency: options.
|
|
119
|
-
batching: options.
|
|
120
|
-
|
|
107
|
+
concurrency: options.concurrency,
|
|
108
|
+
batching: options.batching,
|
|
109
|
+
discard: options.discard,
|
|
110
|
+
concurrentFinalizers: options.concurrentFinalizers,
|
|
121
111
|
},
|
|
122
112
|
),
|
|
123
113
|
);
|
|
124
114
|
}),
|
|
125
115
|
);
|
|
126
|
-
|
|
127
|
-
export const trackProgress = all;
|
|
128
|
-
|
|
129
|
-
export const track = <A, B, E, R>(
|
|
130
|
-
iterable: Iterable<A>,
|
|
131
|
-
options: TrackOptions,
|
|
132
|
-
f: (item: A, index: number) => Effect.Effect<B, E, R>,
|
|
133
|
-
): Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress>> =>
|
|
134
|
-
forEach(iterable, f, options);
|
package/src/colors.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
switch (modifier) {
|
|
108
|
+
case "bold":
|
|
109
|
+
return instance.bold;
|
|
110
|
+
case "dim":
|
|
111
|
+
return instance.dim;
|
|
112
|
+
case "italic":
|
|
113
|
+
return instance.italic;
|
|
114
|
+
case "underline":
|
|
115
|
+
return instance.underline;
|
|
116
|
+
case "inverse":
|
|
117
|
+
return instance.inverse;
|
|
118
|
+
case "hidden":
|
|
119
|
+
return instance.hidden;
|
|
120
|
+
case "strikethrough":
|
|
121
|
+
return instance.strikethrough;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const applyNamedColor = (instance: ChalkInstance, color: NamedColor): ChalkInstance => {
|
|
126
|
+
switch (color) {
|
|
127
|
+
case "black":
|
|
128
|
+
return instance.black;
|
|
129
|
+
case "red":
|
|
130
|
+
return instance.red;
|
|
131
|
+
case "green":
|
|
132
|
+
return instance.green;
|
|
133
|
+
case "yellow":
|
|
134
|
+
return instance.yellow;
|
|
135
|
+
case "blue":
|
|
136
|
+
return instance.blue;
|
|
137
|
+
case "magenta":
|
|
138
|
+
return instance.magenta;
|
|
139
|
+
case "cyan":
|
|
140
|
+
return instance.cyan;
|
|
141
|
+
case "white":
|
|
142
|
+
return instance.white;
|
|
143
|
+
case "blackBright":
|
|
144
|
+
return instance.blackBright;
|
|
145
|
+
case "redBright":
|
|
146
|
+
return instance.redBright;
|
|
147
|
+
case "greenBright":
|
|
148
|
+
return instance.greenBright;
|
|
149
|
+
case "yellowBright":
|
|
150
|
+
return instance.yellowBright;
|
|
151
|
+
case "blueBright":
|
|
152
|
+
return instance.blueBright;
|
|
153
|
+
case "magentaBright":
|
|
154
|
+
return instance.magentaBright;
|
|
155
|
+
case "cyanBright":
|
|
156
|
+
return instance.cyanBright;
|
|
157
|
+
case "whiteBright":
|
|
158
|
+
return instance.whiteBright;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const resolveBaseStyle = (style: ColorStyle): ChalkInstance => {
|
|
163
|
+
switch (style.kind) {
|
|
164
|
+
case "named":
|
|
165
|
+
return applyNamedColor(chalk, style.value);
|
|
166
|
+
case "hex":
|
|
167
|
+
return chalk.hex(style.value);
|
|
168
|
+
case "rgb":
|
|
169
|
+
return chalk.rgb(style.value.r, style.value.g, style.value.b);
|
|
170
|
+
case "ansi256":
|
|
171
|
+
return chalk.ansi256(style.value);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const applyModifiers = (instance: ChalkInstance, modifiers: ReadonlyArray<StyleModifier>) => {
|
|
176
|
+
let styled = instance;
|
|
177
|
+
for (const modifier of modifiers) {
|
|
178
|
+
styled = applyModifier(styled, modifier);
|
|
179
|
+
}
|
|
180
|
+
return styled;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const applyColorStyle = (style: ColorStyle, text: string): string => {
|
|
184
|
+
const styled = applyModifiers(resolveBaseStyle(style), style.modifiers ?? []);
|
|
185
|
+
return styled(text);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export interface CompiledProgressBarColors {
|
|
189
|
+
readonly fill: (text: string) => string;
|
|
190
|
+
readonly empty: (text: string) => string;
|
|
191
|
+
readonly brackets: (text: string) => string;
|
|
192
|
+
readonly percent: (text: string) => string;
|
|
193
|
+
readonly spinner: (text: string) => string;
|
|
194
|
+
readonly done: (text: string) => string;
|
|
195
|
+
readonly failed: (text: string) => string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const compileProgressBarColors = (colors: ProgressBarColors): CompiledProgressBarColors => ({
|
|
199
|
+
fill: (text) => applyColorStyle(colors.fill, text),
|
|
200
|
+
empty: (text) => applyColorStyle(colors.empty, text),
|
|
201
|
+
brackets: (text) => applyColorStyle(colors.brackets, text),
|
|
202
|
+
percent: (text) => applyColorStyle(colors.percent, text),
|
|
203
|
+
spinner: (text) => applyColorStyle(colors.spinner, text),
|
|
204
|
+
done: (text) => applyColorStyle(colors.done, text),
|
|
205
|
+
failed: (text) => applyColorStyle(colors.failed, text),
|
|
206
|
+
});
|
package/src/console.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { Console, Effect } from "effect";
|
|
2
2
|
import { formatWithOptions } from "node:util";
|
|
3
|
-
import type { ProgressService } from "./types";
|
|
4
3
|
|
|
5
4
|
export const makeProgressConsole = (
|
|
6
|
-
|
|
5
|
+
progressLog: (...args: ReadonlyArray<unknown>) => Effect.Effect<void, never, never>,
|
|
7
6
|
outerConsole: Console.Console,
|
|
8
7
|
): Console.Console => {
|
|
9
|
-
const log = (...args: ReadonlyArray<unknown>) =>
|
|
8
|
+
const log = (...args: ReadonlyArray<unknown>) => progressLog(...args);
|
|
10
9
|
const unsafeLog = (...args: ReadonlyArray<unknown>) => {
|
|
11
|
-
Effect.runFork(
|
|
10
|
+
Effect.runFork(progressLog(...args));
|
|
12
11
|
};
|
|
13
12
|
|
|
14
13
|
const delegate = (effect: Effect.Effect<void, never, never>) => effect;
|
package/src/index.ts
CHANGED