effective-progress 0.4.1 → 0.4.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 +93 -71
- package/package.json +8 -5
- package/src/api.ts +57 -21
- package/src/console.ts +130 -38
- 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 +761 -741
- package/src/runtime.ts +37 -86
- package/src/types.ts +26 -9
- package/src/theme.ts +0 -87
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
- multiple nested tree-like progress bars
|
|
17
17
|
- spinner support for “we have no idea how long this takes” work
|
|
18
|
-
-
|
|
18
|
+
- keep using `Console.log` / `Effect.logInfo` while raw console calls are buffered and replayed through your existing `Console` / logger between frame renders
|
|
19
19
|
- familiar `.all` and `.forEach` APIs — swap `Effect` for `Progress`, get progress bars basically for free
|
|
20
20
|
- flicker-free rendering (in theory) by drawing everything in a single terminal frame
|
|
21
21
|
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
bun add effective-progress
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
This shows the simplest usage: iterate items with a single progress bar.
|
|
29
31
|
|
|
30
32
|
```ts
|
|
31
33
|
import { Console, Effect } from "effect";
|
|
@@ -46,7 +48,7 @@ Effect.runPromise(program);
|
|
|
46
48
|
|
|
47
49
|
<img alt="Basic example output" src="docs/images/basic.gif" width="600" />
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
### Nested example
|
|
50
52
|
|
|
51
53
|
Run:
|
|
52
54
|
|
|
@@ -77,30 +79,29 @@ Effect.runPromise(program);
|
|
|
77
79
|
|
|
78
80
|
<img alt="Nested example output" src="docs/images/nesting.gif" width="600" />
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
### Other examples
|
|
81
83
|
|
|
82
84
|
- `examples/simpleExample.ts` - low-boilerplate real-world flow
|
|
83
85
|
- `examples/advancedExample.ts` - full API usage with custom config and manual task control
|
|
84
86
|
- `examples/showcase.ts` - nested concurrent tasks, spinner workloads, and mixed Effect/Console logging
|
|
85
87
|
- `examples/performance.ts` - stress-style run with high log volume and deeply nested progress updates
|
|
86
|
-
- `examples/themeDepthPalette.ts` - depth-aware nested theming via `Theme.depthPalette`
|
|
87
|
-
- `examples/twoLineWidthCap.ts` - two-line determinate layout with `maxTaskWidth` clamping
|
|
88
|
-
- `examples/customColorStage.ts` - override `ColorStage` to emit plain (no ANSI) frame output
|
|
89
88
|
|
|
90
|
-
##
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
### Console replay
|
|
91
92
|
|
|
92
|
-
- `
|
|
93
|
-
-
|
|
94
|
-
-
|
|
93
|
+
- `Progress.task`, `Progress.all`, and `Progress.forEach` buffer `Console.log`/`Console.dir` calls and replay them through the outer `Console`.
|
|
94
|
+
- Calls are replayed between progress frame renders to avoid tearing the TTY frame.
|
|
95
|
+
- Formatting is controlled by the API consumer's logger/console implementation.
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
### Configuring renderer and progress bars
|
|
97
98
|
|
|
98
99
|
Configure global renderer behavior once, and a global base progress bar style:
|
|
99
100
|
|
|
100
101
|
Defaults:
|
|
101
102
|
|
|
102
|
-
-
|
|
103
|
-
-
|
|
103
|
+
- columns: `DescriptionColumn`, `BarColumn`, `AmountColumn`, `ElapsedColumn`, `EtaColumn`
|
|
104
|
+
- total progress width: `80`
|
|
104
105
|
- bar width: `40`
|
|
105
106
|
|
|
106
107
|
```ts
|
|
@@ -109,7 +110,7 @@ import * as Progress from "effective-progress";
|
|
|
109
110
|
|
|
110
111
|
const configured = program.pipe(
|
|
111
112
|
Effect.provideService(Progress.RendererConfig, {
|
|
112
|
-
|
|
113
|
+
width: 80,
|
|
113
114
|
nonTtyUpdateStep: 2,
|
|
114
115
|
}),
|
|
115
116
|
Effect.provideService(Progress.ProgressBarConfig, {
|
|
@@ -120,6 +121,49 @@ const configured = program.pipe(
|
|
|
120
121
|
Effect.runPromise(configured);
|
|
121
122
|
```
|
|
122
123
|
|
|
124
|
+
You can customize column order/content Rich-style by providing a `columns` array:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const configured = program.pipe(
|
|
128
|
+
Effect.provideService(Progress.RendererConfig, {
|
|
129
|
+
columns: [
|
|
130
|
+
Progress.DescriptionColumn.Default(),
|
|
131
|
+
Progress.BarColumn.make({ track: Progress.Track.fr(1) }),
|
|
132
|
+
Progress.AmountColumn.Default(),
|
|
133
|
+
"•",
|
|
134
|
+
Progress.ElapsedColumn.Default(),
|
|
135
|
+
"•",
|
|
136
|
+
Progress.EtaColumn.Default(),
|
|
137
|
+
],
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
For full terminal width rendering, set:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
Effect.provideService(Progress.RendererConfig, {
|
|
146
|
+
width: "fullwidth",
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Description-specific caps should be configured on `DescriptionColumn` (for example `DescriptionColumn.make({ maxWidth: 40 })`) rather than globally.
|
|
151
|
+
|
|
152
|
+
Per top-level call, you can override render config via helper APIs:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
const run = Progress.task(effect, {
|
|
156
|
+
description: "work",
|
|
157
|
+
render: {
|
|
158
|
+
columns: [Progress.DescriptionColumn.Default(), "|", Progress.AmountColumn.Default()],
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const wrapped = Progress.withRenderConfig(run, {
|
|
163
|
+
columns: [Progress.DescriptionColumn.Default()],
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
123
167
|
Task-level `progressbar` config is optional and inherits from its parent task (or from global `ProgressBarConfig` for root tasks):
|
|
124
168
|
|
|
125
169
|
```ts
|
|
@@ -133,6 +177,40 @@ yield *
|
|
|
133
177
|
});
|
|
134
178
|
```
|
|
135
179
|
|
|
180
|
+
## Manual task control
|
|
181
|
+
|
|
182
|
+
For manual usage, `task` still provides the current `Task` context, while logs continue through your outer `Console`:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
const program = Progress.task(
|
|
186
|
+
Effect.gen(function* () {
|
|
187
|
+
const currentTask = yield* Progress.Task;
|
|
188
|
+
yield* Console.log("This log is handled by the outer Console", { taskId: currentTask });
|
|
189
|
+
yield* Effect.sleep("1 second");
|
|
190
|
+
}),
|
|
191
|
+
{ description: "Manual task" },
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Column customization
|
|
196
|
+
|
|
197
|
+
Built-in columns are exported as classes with `Default()` and `make()` helpers.
|
|
198
|
+
You can also pass your own objects/classes implementing `ProgressColumn`:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const CustomColumn: Progress.ProgressColumn = {
|
|
202
|
+
id: "custom",
|
|
203
|
+
render: () => "extra",
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const program = Progress.task(myEffect, {
|
|
207
|
+
description: "Work",
|
|
208
|
+
render: {
|
|
209
|
+
columns: [Progress.DescriptionColumn.Default(), Progress.BarColumn.Default(), CustomColumn],
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
136
214
|
## Terminal service and mocking
|
|
137
215
|
|
|
138
216
|
`effective-progress` now exposes a `ProgressTerminal` service that controls terminal detection and I/O:
|
|
@@ -162,62 +240,6 @@ const program = Progress.task(Effect.sleep("100 millis"), { description: "work"
|
|
|
162
240
|
);
|
|
163
241
|
```
|
|
164
242
|
|
|
165
|
-
## Manual task control
|
|
166
|
-
|
|
167
|
-
For manual usage, `task` captures logs implicitly and provides the current `Task` context:
|
|
168
|
-
|
|
169
|
-
```ts
|
|
170
|
-
const program = Progress.task(
|
|
171
|
-
Effect.gen(function* () {
|
|
172
|
-
const currentTask = yield* Progress.Task;
|
|
173
|
-
yield* Console.log("This log is rendered through progress output", { taskId: currentTask });
|
|
174
|
-
yield* Effect.sleep("1 second");
|
|
175
|
-
}),
|
|
176
|
-
{ description: "Manual task" },
|
|
177
|
-
);
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Themes and render stages
|
|
181
|
-
|
|
182
|
-
Coloring is configured through the `Theme` service.
|
|
183
|
-
|
|
184
|
-
```ts
|
|
185
|
-
import chalk from "chalk";
|
|
186
|
-
import { Effect } from "effect";
|
|
187
|
-
import * as Progress from "effective-progress";
|
|
188
|
-
|
|
189
|
-
const program = Progress.task(myEffect, { description: "Work" }).pipe(
|
|
190
|
-
Effect.provideService(
|
|
191
|
-
Progress.Theme,
|
|
192
|
-
Progress.Theme.of({
|
|
193
|
-
styles: {
|
|
194
|
-
plain: (text) => text,
|
|
195
|
-
barFill: chalk.hex("#00b894"),
|
|
196
|
-
barEmpty: chalk.white.dim,
|
|
197
|
-
barBracket: chalk.rgb(180, 190, 210),
|
|
198
|
-
spinner: chalk.ansi256(214),
|
|
199
|
-
statusDone: chalk.greenBright,
|
|
200
|
-
statusFailed: chalk.redBright.bold,
|
|
201
|
-
text: chalk.white,
|
|
202
|
-
units: chalk.whiteBright.bold,
|
|
203
|
-
eta: chalk.gray,
|
|
204
|
-
elapsed: chalk.gray,
|
|
205
|
-
treeConnector: chalk.gray,
|
|
206
|
-
},
|
|
207
|
-
depthPalette: (depth, role) => (role === "text" && depth > 0 ? chalk.cyanBright : undefined),
|
|
208
|
-
}),
|
|
209
|
-
),
|
|
210
|
-
);
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Render internals are split into overrideable stages:
|
|
214
|
-
|
|
215
|
-
- `BuildStage`: logical rows/cells/segments
|
|
216
|
-
- `ShrinkStage`: width fitting/collapse
|
|
217
|
-
- `ColorStage`: role -> styled terminal strings
|
|
218
|
-
|
|
219
|
-
You can replace any stage with `Effect.provideService(...)` while keeping defaults for the rest.
|
|
220
|
-
|
|
221
243
|
## Dependencies & package size
|
|
222
244
|
|
|
223
245
|
This library is designed for CLI workflows, where package size is typically a lower-priority concern. Alongside `effect` though, I will strive to only rely on other high quality packages.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effective-progress",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Effect-first terminal progress bars with nested multibar support",
|
|
5
5
|
"homepage": "https://github.com/stromseng/effective-progress#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
],
|
|
20
20
|
"type": "module",
|
|
21
21
|
"module": "index.ts",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20.12.0"
|
|
24
|
+
},
|
|
22
25
|
"publishConfig": {
|
|
23
26
|
"access": "public"
|
|
24
27
|
},
|
|
@@ -30,18 +33,18 @@
|
|
|
30
33
|
"format:check": "oxfmt --check ."
|
|
31
34
|
},
|
|
32
35
|
"dependencies": {
|
|
33
|
-
"chalk": "^5.6.2",
|
|
34
|
-
"effect": "^3.19.17",
|
|
35
36
|
"es-toolkit": "^1.44.0",
|
|
36
37
|
"type-fest": "^5.4.4"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@effect/language-service": "^0.73.1",
|
|
40
41
|
"@types/bun": "latest",
|
|
42
|
+
"effect": "^3.19.17",
|
|
41
43
|
"oxfmt": "^0.32.0",
|
|
42
|
-
"oxlint": "^1.47.0"
|
|
44
|
+
"oxlint": "^1.47.0",
|
|
45
|
+
"typescript": "^5"
|
|
43
46
|
},
|
|
44
47
|
"peerDependencies": {
|
|
45
|
-
"
|
|
48
|
+
"effect": "^3.19.17"
|
|
46
49
|
}
|
|
47
50
|
}
|
package/src/api.ts
CHANGED
|
@@ -1,20 +1,49 @@
|
|
|
1
1
|
import { Effect, Exit, Option } from "effect";
|
|
2
2
|
import { dual } from "effect/Function";
|
|
3
3
|
import type { Concurrency } from "effect/Types";
|
|
4
|
+
import type { PartialDeep } from "type-fest";
|
|
4
5
|
import { Progress } from "./runtime";
|
|
5
|
-
import { Task } from "./types";
|
|
6
|
-
import type { AddTaskOptions, TrackOptions } from "./types";
|
|
6
|
+
import { RendererConfig, Task } from "./types";
|
|
7
|
+
import type { AddTaskOptions, RendererConfigShape, TrackOptions } from "./types";
|
|
7
8
|
import { inferTotal } from "./utils";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
interface RenderOverrideOptions {
|
|
11
|
+
readonly render?: PartialDeep<RendererConfigShape>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const provideProgress = <A, E, R>(
|
|
15
|
+
effect: Effect.Effect<A, E, R>,
|
|
16
|
+
renderConfig: PartialDeep<RendererConfigShape> | undefined,
|
|
17
|
+
) =>
|
|
10
18
|
Effect.gen(function* () {
|
|
11
19
|
const existing = yield* Effect.serviceOption(Progress);
|
|
12
20
|
if (Option.isSome(existing)) {
|
|
13
21
|
return yield* Effect.provideService(effect, Progress, existing.value);
|
|
14
22
|
}
|
|
15
|
-
|
|
23
|
+
if (renderConfig === undefined) {
|
|
24
|
+
return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return yield* Effect.scoped(
|
|
28
|
+
effect.pipe(
|
|
29
|
+
Effect.provide(Progress.Default),
|
|
30
|
+
Effect.provideService(RendererConfig, renderConfig),
|
|
31
|
+
),
|
|
32
|
+
);
|
|
16
33
|
});
|
|
17
34
|
|
|
35
|
+
export const withRenderConfig: {
|
|
36
|
+
<A, E, R>(
|
|
37
|
+
effect: Effect.Effect<A, E, R>,
|
|
38
|
+
config: PartialDeep<RendererConfigShape>,
|
|
39
|
+
): Effect.Effect<A, E, Exclude<R, RendererConfig>>;
|
|
40
|
+
(
|
|
41
|
+
config: PartialDeep<RendererConfigShape>,
|
|
42
|
+
): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, RendererConfig>>;
|
|
43
|
+
} = dual(2, <A, E, R>(effect: Effect.Effect<A, E, R>, config: PartialDeep<RendererConfigShape>) =>
|
|
44
|
+
Effect.provideService(effect, RendererConfig, config),
|
|
45
|
+
);
|
|
46
|
+
|
|
18
47
|
export interface EffectExecutionOptions {
|
|
19
48
|
readonly concurrency?: Concurrency;
|
|
20
49
|
readonly batching?: boolean | "inherit";
|
|
@@ -26,7 +55,9 @@ export interface EffectAllExecutionOptions extends EffectExecutionOptions {
|
|
|
26
55
|
readonly mode?: "default" | "validate" | "either";
|
|
27
56
|
}
|
|
28
57
|
|
|
29
|
-
export type AllOptions = Omit<TrackOptions, "total"> &
|
|
58
|
+
export type AllOptions = Omit<TrackOptions, "total"> &
|
|
59
|
+
EffectAllExecutionOptions &
|
|
60
|
+
RenderOverrideOptions;
|
|
30
61
|
export type AllReturn<
|
|
31
62
|
Arg extends
|
|
32
63
|
| ReadonlyArray<Effect.Effect<any, any, any>>
|
|
@@ -46,26 +77,29 @@ export interface ForEachExecutionOptions extends EffectExecutionOptions {
|
|
|
46
77
|
readonly discard?: false | undefined;
|
|
47
78
|
}
|
|
48
79
|
|
|
49
|
-
export type ForEachOptions = TrackOptions & ForEachExecutionOptions;
|
|
80
|
+
export type ForEachOptions = TrackOptions & ForEachExecutionOptions & RenderOverrideOptions;
|
|
81
|
+
|
|
82
|
+
export type TaskOptions = AddTaskOptions & RenderOverrideOptions;
|
|
50
83
|
|
|
51
84
|
export const task: {
|
|
52
85
|
<A, E, R>(
|
|
53
86
|
effect: Effect.Effect<A, E, R>,
|
|
54
|
-
options:
|
|
87
|
+
options: TaskOptions,
|
|
55
88
|
): Effect.Effect<A, E, Exclude<R, Progress | Task>>;
|
|
56
89
|
<A, E, R>(
|
|
57
|
-
options:
|
|
90
|
+
options: TaskOptions,
|
|
58
91
|
): (effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Progress | Task>>;
|
|
59
|
-
} = dual(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
92
|
+
} = dual(2, <A, E, R>(effect: Effect.Effect<A, E, R>, options: TaskOptions) => {
|
|
93
|
+
const { render, ...taskOptions } = options;
|
|
94
|
+
|
|
95
|
+
return provideProgress(
|
|
96
|
+
Effect.gen(function* () {
|
|
97
|
+
const progress = yield* Progress;
|
|
98
|
+
return yield* progress.withTask(effect, taskOptions);
|
|
99
|
+
}),
|
|
100
|
+
render,
|
|
101
|
+
) as Effect.Effect<A, E, Exclude<R, Progress | Task>>;
|
|
102
|
+
});
|
|
69
103
|
|
|
70
104
|
type AllArg =
|
|
71
105
|
| ReadonlyArray<Effect.Effect<any, any, any>>
|
|
@@ -85,14 +119,14 @@ const countEffects = (effects: AllArg): number =>
|
|
|
85
119
|
export const all: {
|
|
86
120
|
<const Arg extends AllArg, O extends EffectAllExecutionOptions>(
|
|
87
121
|
effects: Arg,
|
|
88
|
-
options: Omit<TrackOptions, "total"> & O,
|
|
122
|
+
options: Omit<TrackOptions, "total"> & O & RenderOverrideOptions,
|
|
89
123
|
): AllReturn<Arg, O>;
|
|
90
|
-
<O extends EffectAllExecutionOptions>(
|
|
124
|
+
<O extends EffectAllExecutionOptions & RenderOverrideOptions>(
|
|
91
125
|
options: Omit<TrackOptions, "total"> & O,
|
|
92
126
|
): <const Arg extends AllArg>(effects: Arg) => AllReturn<Arg, O>;
|
|
93
127
|
} = dual(
|
|
94
128
|
2,
|
|
95
|
-
<const Arg extends AllArg, O extends EffectAllExecutionOptions>(
|
|
129
|
+
<const Arg extends AllArg, O extends EffectAllExecutionOptions & RenderOverrideOptions>(
|
|
96
130
|
effects: Arg,
|
|
97
131
|
options: Omit<TrackOptions, "total"> & O,
|
|
98
132
|
) =>
|
|
@@ -136,6 +170,7 @@ export const all: {
|
|
|
136
170
|
},
|
|
137
171
|
);
|
|
138
172
|
}),
|
|
173
|
+
options.render,
|
|
139
174
|
) as AllReturn<Arg, O>,
|
|
140
175
|
);
|
|
141
176
|
|
|
@@ -195,5 +230,6 @@ export const forEach: {
|
|
|
195
230
|
},
|
|
196
231
|
);
|
|
197
232
|
}),
|
|
233
|
+
options.render,
|
|
198
234
|
) as Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress | Task>>,
|
|
199
235
|
);
|
package/src/console.ts
CHANGED
|
@@ -1,85 +1,177 @@
|
|
|
1
|
-
import { Console, Effect } from "effect";
|
|
2
|
-
import { formatWithOptions } from "node:util";
|
|
1
|
+
import { Console, Effect, Ref } from "effect";
|
|
3
2
|
|
|
4
|
-
export
|
|
5
|
-
|
|
3
|
+
export type BufferedConsoleMethod =
|
|
4
|
+
| "assert"
|
|
5
|
+
| "debug"
|
|
6
|
+
| "dir"
|
|
7
|
+
| "dirxml"
|
|
8
|
+
| "error"
|
|
9
|
+
| "group"
|
|
10
|
+
| "groupCollapsed"
|
|
11
|
+
| "groupEnd"
|
|
12
|
+
| "info"
|
|
13
|
+
| "log"
|
|
14
|
+
| "table"
|
|
15
|
+
| "trace"
|
|
16
|
+
| "warn";
|
|
17
|
+
|
|
18
|
+
export interface BufferedConsoleCall {
|
|
19
|
+
readonly method: BufferedConsoleMethod;
|
|
20
|
+
readonly args: ReadonlyArray<unknown>;
|
|
21
|
+
readonly unsafe: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ConsoleBridge {
|
|
25
|
+
readonly progressConsole: Console.Console;
|
|
26
|
+
readonly appendLog: (call: BufferedConsoleCall) => Effect.Effect<void, never, never>;
|
|
27
|
+
readonly replayLogs: (
|
|
28
|
+
logs: ReadonlyArray<BufferedConsoleCall>,
|
|
29
|
+
) => Effect.Effect<void, never, never>;
|
|
30
|
+
readonly log: (...args: ReadonlyArray<unknown>) => Effect.Effect<void, never, never>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const replayBufferedConsoleCall = (
|
|
6
34
|
outerConsole: Console.Console,
|
|
35
|
+
call: BufferedConsoleCall,
|
|
36
|
+
): Effect.Effect<void, never, never> => {
|
|
37
|
+
type DirOptions = Parameters<Console.Console["dir"]>[1];
|
|
38
|
+
type GroupOptions = Parameters<Console.Console["group"]>[0];
|
|
39
|
+
|
|
40
|
+
switch (call.method) {
|
|
41
|
+
case "assert": {
|
|
42
|
+
const [condition, ...rest] = call.args;
|
|
43
|
+
return outerConsole.assert(condition as boolean, ...rest);
|
|
44
|
+
}
|
|
45
|
+
case "debug":
|
|
46
|
+
return outerConsole.debug(...call.args);
|
|
47
|
+
case "dir":
|
|
48
|
+
return outerConsole.dir(call.args[0], call.args[1] as DirOptions);
|
|
49
|
+
case "dirxml":
|
|
50
|
+
return outerConsole.dirxml(...call.args);
|
|
51
|
+
case "error":
|
|
52
|
+
return outerConsole.error(...call.args);
|
|
53
|
+
case "group":
|
|
54
|
+
return outerConsole.group(call.args[0] as GroupOptions);
|
|
55
|
+
case "groupCollapsed":
|
|
56
|
+
return outerConsole.group(call.args[0] as GroupOptions);
|
|
57
|
+
case "groupEnd":
|
|
58
|
+
return outerConsole.groupEnd;
|
|
59
|
+
case "info":
|
|
60
|
+
return outerConsole.info(...call.args);
|
|
61
|
+
case "log":
|
|
62
|
+
return outerConsole.log(...call.args);
|
|
63
|
+
case "table":
|
|
64
|
+
return outerConsole.table(call.args[0], call.args[1] as ReadonlyArray<string> | undefined);
|
|
65
|
+
case "trace":
|
|
66
|
+
return outerConsole.trace(...call.args);
|
|
67
|
+
case "warn":
|
|
68
|
+
return outerConsole.warn(...call.args);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const makeProgressConsole = (
|
|
73
|
+
appendLog: (call: BufferedConsoleCall) => Effect.Effect<void, never, never>,
|
|
7
74
|
): Console.Console => {
|
|
8
|
-
const log = (...args: ReadonlyArray<unknown>) =>
|
|
9
|
-
|
|
10
|
-
|
|
75
|
+
const log = (method: BufferedConsoleMethod, ...args: ReadonlyArray<unknown>) =>
|
|
76
|
+
appendLog({ method, args, unsafe: false });
|
|
77
|
+
const unsafeLog = (method: BufferedConsoleMethod, ...args: ReadonlyArray<unknown>) => {
|
|
78
|
+
Effect.runSync(appendLog({ method, args, unsafe: true }));
|
|
11
79
|
};
|
|
12
80
|
|
|
13
|
-
const delegate = (effect: Effect.Effect<void, never, never>) => effect;
|
|
14
|
-
|
|
15
81
|
return Console.Console.of({
|
|
16
82
|
[Console.TypeId]: Console.TypeId,
|
|
17
|
-
assert(condition, ...args)
|
|
18
|
-
return condition ? Effect.void : log("Assertion failed:", ...args);
|
|
19
|
-
},
|
|
83
|
+
assert: (condition, ...args) => log("assert", condition, ...args),
|
|
20
84
|
clear: Effect.void,
|
|
21
85
|
count: (_label) => Effect.void,
|
|
22
86
|
countReset: (_label) => Effect.void,
|
|
23
|
-
debug: (...args) => log(...args),
|
|
24
|
-
dir: (item, options) => log(
|
|
25
|
-
dirxml: (...args) => log(...args),
|
|
26
|
-
error: (...args) => log(...args),
|
|
27
|
-
group: (...args) => log(...args),
|
|
28
|
-
groupEnd:
|
|
29
|
-
info: (...args) => log(...args),
|
|
30
|
-
log: (...args) => log(...args),
|
|
31
|
-
table: (tabularData, properties) => log(tabularData, properties),
|
|
87
|
+
debug: (...args) => log("debug", ...args),
|
|
88
|
+
dir: (item, options) => log("dir", item, options),
|
|
89
|
+
dirxml: (...args) => log("dirxml", ...args),
|
|
90
|
+
error: (...args) => log("error", ...args),
|
|
91
|
+
group: (...args) => log("group", ...args),
|
|
92
|
+
groupEnd: log("groupEnd"),
|
|
93
|
+
info: (...args) => log("info", ...args),
|
|
94
|
+
log: (...args) => log("log", ...args),
|
|
95
|
+
table: (tabularData, properties) => log("table", tabularData, properties),
|
|
32
96
|
time: (_label) => Effect.void,
|
|
33
97
|
timeEnd: (_label) => Effect.void,
|
|
34
|
-
timeLog: (_label, ...args) => log(...args),
|
|
35
|
-
trace: (...args) =>
|
|
36
|
-
warn: (...args) => log(...args),
|
|
98
|
+
timeLog: (_label, ...args) => log("info", ...args),
|
|
99
|
+
trace: (...args) => log("trace", ...args),
|
|
100
|
+
warn: (...args) => log("warn", ...args),
|
|
37
101
|
unsafe: {
|
|
38
102
|
assert(condition, ...args) {
|
|
39
|
-
|
|
103
|
+
unsafeLog("assert", condition, ...args);
|
|
40
104
|
},
|
|
41
105
|
clear() {},
|
|
42
106
|
count(_label) {},
|
|
43
107
|
countReset(_label) {},
|
|
44
108
|
debug(...args) {
|
|
45
|
-
unsafeLog(...args);
|
|
109
|
+
unsafeLog("debug", ...args);
|
|
46
110
|
},
|
|
47
111
|
dir(item, options) {
|
|
48
|
-
unsafeLog(
|
|
112
|
+
unsafeLog("dir", item, options);
|
|
49
113
|
},
|
|
50
114
|
dirxml(...args) {
|
|
51
|
-
unsafeLog(...args);
|
|
115
|
+
unsafeLog("dirxml", ...args);
|
|
52
116
|
},
|
|
53
117
|
error(...args) {
|
|
54
|
-
unsafeLog(...args);
|
|
118
|
+
unsafeLog("error", ...args);
|
|
55
119
|
},
|
|
56
120
|
group(...args) {
|
|
57
|
-
unsafeLog(...args);
|
|
121
|
+
unsafeLog("group", ...args);
|
|
58
122
|
},
|
|
59
123
|
groupCollapsed(...args) {
|
|
60
|
-
unsafeLog(...args);
|
|
124
|
+
unsafeLog("groupCollapsed", ...args);
|
|
125
|
+
},
|
|
126
|
+
groupEnd() {
|
|
127
|
+
unsafeLog("groupEnd");
|
|
61
128
|
},
|
|
62
|
-
groupEnd() {},
|
|
63
129
|
info(...args) {
|
|
64
|
-
unsafeLog(...args);
|
|
130
|
+
unsafeLog("info", ...args);
|
|
65
131
|
},
|
|
66
132
|
log(...args) {
|
|
67
|
-
unsafeLog(...args);
|
|
133
|
+
unsafeLog("log", ...args);
|
|
68
134
|
},
|
|
69
135
|
table(tabularData, properties) {
|
|
70
|
-
unsafeLog(tabularData, properties);
|
|
136
|
+
unsafeLog("table", tabularData, properties);
|
|
71
137
|
},
|
|
72
138
|
time(_label) {},
|
|
73
139
|
timeEnd(_label) {},
|
|
74
140
|
timeLog(_label, ...args) {
|
|
75
|
-
unsafeLog(...args);
|
|
141
|
+
unsafeLog("info", ...args);
|
|
76
142
|
},
|
|
77
143
|
trace(...args) {
|
|
78
|
-
|
|
144
|
+
unsafeLog("trace", ...args);
|
|
79
145
|
},
|
|
80
146
|
warn(...args) {
|
|
81
|
-
unsafeLog(...args);
|
|
147
|
+
unsafeLog("warn", ...args);
|
|
82
148
|
},
|
|
83
149
|
},
|
|
84
150
|
});
|
|
85
151
|
};
|
|
152
|
+
|
|
153
|
+
export const makeConsoleBridge = (
|
|
154
|
+
outerConsole: Console.Console,
|
|
155
|
+
pendingLogsRef: Ref.Ref<ReadonlyArray<BufferedConsoleCall>>,
|
|
156
|
+
markDirty: Effect.Effect<void, never, never>,
|
|
157
|
+
): ConsoleBridge => {
|
|
158
|
+
const appendLog = (call: BufferedConsoleCall) =>
|
|
159
|
+
call.args.length === 0
|
|
160
|
+
? Effect.void
|
|
161
|
+
: Ref.update(pendingLogsRef, (logs) => [...logs, call]).pipe(Effect.zipRight(markDirty));
|
|
162
|
+
|
|
163
|
+
const replayLogs = (logs: ReadonlyArray<BufferedConsoleCall>) =>
|
|
164
|
+
Effect.forEach(logs, (call) => replayBufferedConsoleCall(outerConsole, call), {
|
|
165
|
+
discard: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const log = (...args: ReadonlyArray<unknown>) =>
|
|
169
|
+
args.length === 0 ? Effect.void : appendLog({ method: "log", args, unsafe: false });
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
progressConsole: makeProgressConsole(appendLog),
|
|
173
|
+
appendLog,
|
|
174
|
+
replayLogs,
|
|
175
|
+
log,
|
|
176
|
+
};
|
|
177
|
+
};
|