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 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
- - clean log rendering alongside progress output, so you can keep using `Console.log` / `Effect.logInfo` without wrecking the UI
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
- This shows the simplest usage: iterate 100 items with a single progress bar.
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
- ## Nested example
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
- ## Other examples
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
- ## Log retention
89
+ ## Configuration
90
+
91
+ ### Console replay
91
92
 
92
- - `maxLogLines` on `RendererConfig` controls in-memory log retention.
93
- - Omitted or set to `0` means no log history is kept in memory.
94
- - `maxLogLines > 0` keeps only the latest `N` log lines in memory.
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
- ## Configuring renderer and progress bars
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
- - determinate layout: `single-line`
103
- - max task width cap: unset (uses terminal width)
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
- maxLogLines: 12,
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.1",
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
- "typescript": "^5"
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
- const provideProgress = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
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
- return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
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"> & EffectAllExecutionOptions;
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: AddTaskOptions,
87
+ options: TaskOptions,
55
88
  ): Effect.Effect<A, E, Exclude<R, Progress | Task>>;
56
89
  <A, E, R>(
57
- options: AddTaskOptions,
90
+ options: TaskOptions,
58
91
  ): (effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Progress | Task>>;
59
- } = dual(
60
- 2,
61
- <A, E, R>(effect: Effect.Effect<A, E, R>, options: AddTaskOptions) =>
62
- provideProgress(
63
- Effect.gen(function* () {
64
- const progress = yield* Progress;
65
- return yield* progress.withTask(effect, options);
66
- }),
67
- ) as Effect.Effect<A, E, Exclude<R, Progress | Task>>,
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 const makeProgressConsole = (
5
- progressLog: (...args: ReadonlyArray<unknown>) => Effect.Effect<void, never, never>,
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>) => progressLog(...args);
9
- const unsafeLog = (...args: ReadonlyArray<unknown>) => {
10
- Effect.runFork(progressLog(...args));
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(formatWithOptions(options ?? {}, "%O", item)),
25
- dirxml: (...args) => log(...args),
26
- error: (...args) => log(...args),
27
- group: (...args) => log(...args),
28
- groupEnd: Effect.void,
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) => delegate(outerConsole.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
- if (!condition) unsafeLog("Assertion failed:", ...args);
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(formatWithOptions(options ?? {}, "%O", item));
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
- outerConsole.unsafe.trace(...args);
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
+ };
package/src/index.ts CHANGED
@@ -2,5 +2,4 @@ export * from "./api";
2
2
  export * from "./renderer";
3
3
  export { Progress } from "./runtime";
4
4
  export * from "./terminal";
5
- export * from "./theme";
6
5
  export * from "./types";