@effected/cli 0.14.0 → 0.16.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/Doc.js CHANGED
@@ -379,12 +379,19 @@ var Doc = class {
379
379
  /**
380
380
  * Lines, one per entry, in every renderer: markdown joins them with hard breaks so they never collapse into one.
381
381
  *
382
+ * @remarks
383
+ * `truncate` and `wrap: false` hold for every entry as they do for {@link Doc.line}: each entry is cut to the width,
384
+ * or kept whole on one line, rather than wrapped. Markdown keeps every entry whole either way.
385
+ *
382
386
  * @param lines - the entries; each takes a string, an inline or an array of either
387
+ * @param options - `truncate`, to cut each entry to the width; `wrap: false`, to keep each whole
383
388
  */
384
- static lines(lines) {
389
+ static lines(lines, options) {
385
390
  return freeze({
386
391
  _tag: "Lines",
387
- lines: frozenArray(lines.map(inlines))
392
+ lines: frozenArray(lines.map(inlines)),
393
+ ...options?.truncate === void 0 ? {} : { truncate: options.truncate },
394
+ ...options?.wrap === void 0 ? {} : { wrap: options.wrap }
388
395
  });
389
396
  }
390
397
  /**
package/index.d.ts CHANGED
@@ -879,6 +879,8 @@ type Block = {
879
879
  } | {
880
880
  readonly _tag: "Lines";
881
881
  readonly lines: ReadonlyArray<ReadonlyArray<Inline>>;
882
+ readonly truncate?: boolean;
883
+ readonly wrap?: boolean;
882
884
  } | {
883
885
  readonly _tag: "Line";
884
886
  readonly content: ReadonlyArray<Inline>;
@@ -1312,9 +1314,17 @@ export declare class Doc {
1312
1314
  /**
1313
1315
  * Lines, one per entry, in every renderer: markdown joins them with hard breaks so they never collapse into one.
1314
1316
  *
1317
+ * @remarks
1318
+ * `truncate` and `wrap: false` hold for every entry as they do for {@link Doc.line}: each entry is cut to the width,
1319
+ * or kept whole on one line, rather than wrapped. Markdown keeps every entry whole either way.
1320
+ *
1315
1321
  * @param lines - the entries; each takes a string, an inline or an array of either
1322
+ * @param options - `truncate`, to cut each entry to the width; `wrap: false`, to keep each whole
1316
1323
  */
1317
- static lines(lines: ReadonlyArray<InlineInput>): BlockOf<"Lines">;
1324
+ static lines(lines: ReadonlyArray<InlineInput>, options?: {
1325
+ readonly truncate?: boolean;
1326
+ readonly wrap?: boolean;
1327
+ }): BlockOf<"Lines">;
1318
1328
  /**
1319
1329
  * One line of content; with `truncate`, it is cut to the width with the glyph set's ellipsis instead of wrapping,
1320
1330
  * and with `wrap: false` it is kept whole on one line whatever the width.
@@ -227,6 +227,13 @@ const countsLayout = (walk, block) => {
227
227
  suffix
228
228
  ], " "))];
229
229
  };
230
+ /** One `Line`, or one entry of a `Lines`: cut with `truncate`, kept whole with `wrap: false`, wrapped otherwise. */
231
+ const lineOf = (walk, content, options, width) => {
232
+ const spans = oneLine(inline(walk, content));
233
+ if (options.truncate === true) return [trimLine(truncateSpans(spans, width, walk.ctx.glyphs.ellipsis))];
234
+ if (options.wrap === false) return [trimLine(spans)];
235
+ return spans.length === 0 ? [[]] : wrapSpans(spans, width, { hardBreak: false }).map(trimLine);
236
+ };
230
237
  /**
231
238
  * A block as lines. `compact` is set on a compact list's item: a section there joins its title and children with no
232
239
  * blank lines between them.
@@ -275,15 +282,11 @@ const blockLines = (walk, block, width, compact = false) => {
275
282
  case "Annotation": return [];
276
283
  case "CountsTable": return tableLines(walk, countsTableOf(block), width);
277
284
  case "Lines": return block.lines.flatMap((entry) => {
285
+ if (block.truncate === true || block.wrap === false) return lineOf(walk, entry, block, width);
278
286
  const spans = inline(walk, entry);
279
287
  return spans.length === 0 ? [[]] : wrapSpans(spans, width, { hardBreak: false }).map(trimLine);
280
288
  });
281
- case "Line": {
282
- const spans = oneLine(inline(walk, block.content));
283
- if (block.truncate === true) return [trimLine(truncateSpans(spans, width, walk.ctx.glyphs.ellipsis))];
284
- if (block.wrap === false) return [trimLine(spans)];
285
- return spans.length === 0 ? [[]] : wrapSpans(spans, width, { hardBreak: false }).map(trimLine);
286
- }
289
+ case "Line": return lineOf(walk, block.content, block, width);
287
290
  case "DiffText": {
288
291
  const cap = capOf(block.cap);
289
292
  const lines = textLines(block.text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/cli",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "private": false,
5
5
  "description": "The presentation boundary of an effect/cli program: audience-aware output, a document IR and renderers, editor links, failure reports and logging, plus opt-in Ink screens, widgets and a live view",
6
6
  "keywords": [
package/ui/CliUi.js CHANGED
@@ -262,7 +262,8 @@ var CliUi = class CliUi {
262
262
  * mount to its end, so a `CliUi.run` during a run waits for the run to end, and one between runs mounts at once.
263
263
  *
264
264
  * While a run is drawn, write logs through `logConsole`, provided around the work the view reports on: its lines
265
- * land above the frame. A line written to the terminal any other way tears the frame.
265
+ * land above the frame. A line written to the terminal any other way tears the frame. A host forwarding output it
266
+ * did not write itself prints it with `printAbove`, which says whether a frame was mounted to print it above.
266
267
  *
267
268
  * The view draws on stdout (`UiStreams`), at stdout's colour level and glyphs, and mounts only when the run is
268
269
  * interactive (`CliInteractive`).
package/ui/CliUiLive.js CHANGED
@@ -10,7 +10,6 @@ import { UiStreams } from "./UiStreams.js";
10
10
  import { makeInkConsole } from "./internal/inkConsole.js";
11
11
  import { LazyViewShapeError, loadView } from "./internal/lazyView.js";
12
12
  import { mountPermit } from "./internal/mountPermit.js";
13
- import { drainPerformance, resolveDrain } from "./internal/perfDrain.js";
14
13
  import { UiRenderOptions } from "./internal/renderOptions.js";
15
14
  import { uiProviders } from "./internal/UiProviders.js";
16
15
  import { useTerminalSize } from "./UiTheme.js";
@@ -63,7 +62,6 @@ const live = (options) => Effect.gen(function* () {
63
62
  const interactive = yield* CliInteractive;
64
63
  const streams = yield* UiStreams;
65
64
  const overrides = yield* UiRenderOptions;
66
- const drain = yield* resolveDrain(options.drainPerformance ?? "auto");
67
65
  const bridge = yield* makeInkConsole;
68
66
  const inbox = yield* Queue.unbounded();
69
67
  const source = options.events;
@@ -148,7 +146,6 @@ const live = (options) => Effect.gen(function* () {
148
146
  }, elementOf(state, frame))
149
147
  });
150
148
  const text = yield* Effect.scoped(Effect.andThen(withInkColour(colour), Effect.sync(() => ink.renderToString(tree, { columns }))));
151
- drainPerformance(drain);
152
149
  if (failure !== void 0) return yield* warnOnce(current, failure.error);
153
150
  bridge.print(neutralize ? CommandNeutralizer.text(text) : text);
154
151
  });
@@ -220,7 +217,7 @@ const live = (options) => Effect.gen(function* () {
220
217
  }))))
221
218
  });
222
219
  yield* Effect.acquireRelease(Effect.sync(() => {
223
- const instance = ink.render(tree, {
220
+ return ink.render(tree, {
224
221
  stdin: streams.stdin,
225
222
  stdout: streams.stdout,
226
223
  stderr: streams.stderr,
@@ -230,13 +227,10 @@ const live = (options) => Effect.gen(function* () {
230
227
  ...overrides.onRender === void 0 ? {} : { onRender: overrides.onRender },
231
228
  ...overrides.maxFps === void 0 ? {} : { maxFps: overrides.maxFps }
232
229
  });
233
- drainPerformance(drain);
234
- return instance;
235
230
  }), (instance) => Effect.promise(async () => {
236
231
  bridge.detach();
237
232
  const exited = instance.waitUntilExit();
238
233
  instance.unmount();
239
- drainPerformance(drain);
240
234
  await exited.catch(() => void 0);
241
235
  }));
242
236
  yield* Effect.forkIn(Effect.repeat(Effect.flatMap(frameOf, (at) => Queue.offer(inbox, {
@@ -270,7 +264,6 @@ const live = (options) => Effect.gen(function* () {
270
264
  yield* Effect.callback((resume) => {
271
265
  mounted.slot.swap(elementOf(shown, frame), () => queueMicrotask(() => resume(Effect.void)));
272
266
  });
273
- drainPerformance(drain);
274
267
  current.frame = frame;
275
268
  if (current.failed === void 0) {
276
269
  current.painted = true;
@@ -437,6 +430,7 @@ const live = (options) => Effect.gen(function* () {
437
430
  return {
438
431
  state: Effect.sync(() => state),
439
432
  logConsole: bridge.writer,
433
+ printAbove: bridge.printAbove,
440
434
  done,
441
435
  close: Effect.andThen(ending, settled)
442
436
  };
package/ui/UiTheme.js CHANGED
@@ -69,6 +69,37 @@ const Styled = (props) => {
69
69
  };
70
70
  /** A reported size, or `fallback` when it is unknown: absent, or not positive (a pty `script` opens reports 0x0). */
71
71
  const known = (reported, fallback) => reported !== void 0 && reported > 0 ? reported : fallback;
72
+ /** The redraws following each stream's size, and the one `resize` listener that calls them all. */
73
+ const resizeFollowers = /* @__PURE__ */ new WeakMap();
74
+ /**
75
+ * Follow `stdout`'s size with `redraw`, returning the unfollow. However many components follow one stream, it holds
76
+ * one `resize` listener, added with the first and removed with the last: one per component passed Node's default
77
+ * limit of 10 on any list of `DocView` rows and printed a `MaxListenersExceededWarning` into the frame.
78
+ */
79
+ const followResize = (stdout, redraw) => {
80
+ let entry = resizeFollowers.get(stdout);
81
+ if (entry === void 0) {
82
+ const redraws = /* @__PURE__ */ new Set();
83
+ const onResize = () => {
84
+ for (const each of redraws) each();
85
+ };
86
+ entry = {
87
+ redraws,
88
+ onResize
89
+ };
90
+ resizeFollowers.set(stdout, entry);
91
+ stdout.on("resize", onResize);
92
+ }
93
+ const { redraws, onResize } = entry;
94
+ redraws.add(redraw);
95
+ return () => {
96
+ redraws.delete(redraw);
97
+ if (redraws.size === 0 && resizeFollowers.get(stdout) === entry) {
98
+ resizeFollowers.delete(stdout);
99
+ stdout.off("resize", onResize);
100
+ }
101
+ };
102
+ };
72
103
  /**
73
104
  * The usable terminal size: the stdout Ink draws on, less one column and one row, re-read on every render and when
74
105
  * the terminal resizes; or, under a `UiProvider` given a `size`, that size less one column and one row.
@@ -89,6 +120,9 @@ const known = (reported, fallback) => reported !== void 0 && reported > 0 ? repo
89
120
  * same paint, so give a long row Ink's `wrap: "truncate-end"` too: on a shrink Ink then clips it rather than letting
90
121
  * the terminal wrap it.
91
122
  *
123
+ * Every component following one stdout shares a single `resize` listener on it, so a screen of many rows (a
124
+ * `Viewport` of `DocView`s) holds one listener, not one per row.
125
+ *
92
126
  * A React hook: call it from a component rendered inside an Ink tree; it needs no screen, but reads a `UiProvider`'s
93
127
  * size when there is one.
94
128
  *
@@ -100,13 +134,7 @@ const useTerminalSize = () => {
100
134
  const override = react.useContext(screenContext())?.size;
101
135
  const [, redraw] = react.useReducer((count) => count + 1, 0);
102
136
  const followsStdout = override === void 0;
103
- react.useEffect(() => {
104
- if (!followsStdout) return void 0;
105
- stdout.on("resize", redraw);
106
- return () => {
107
- stdout.off("resize", redraw);
108
- };
109
- }, [stdout, followsStdout]);
137
+ react.useEffect(() => followsStdout ? followResize(stdout, redraw) : void 0, [stdout, followsStdout]);
110
138
  const reported = stdout;
111
139
  const columns = override?.columns ?? reported.columns;
112
140
  const rows = override?.rows ?? reported.rows;
@@ -135,6 +135,11 @@ const makeInkConsole = Effect.gen(function* () {
135
135
  if (attached !== void 0) attached.out(data);
136
136
  else streams.stdout.write(data);
137
137
  },
138
+ printAbove: (stream, text) => {
139
+ if (attached === void 0) return false;
140
+ (stream === "stdout" ? attached.out : attached.err)(`${text}\n`);
141
+ return true;
142
+ },
138
143
  Bridge,
139
144
  detach: () => {
140
145
  attached = void 0;
@@ -714,12 +714,17 @@ var CliUiTest = class {
714
714
  end: Effect.andThen(Queue.end(queue), handle.done),
715
715
  advance: (duration) => settled(TestClock.adjust(duration)),
716
716
  resize: (nextColumns, nextRows) => settled(Effect.sync(() => terminal.fake.resize(nextColumns, nextRows))),
717
+ write: (stream, bytes) => Effect.sync(() => {
718
+ terminal.fake.streams[stream].write(bytes);
719
+ }),
717
720
  frame: Effect.sync(() => trimLines(styled(last()))),
718
721
  rawFrame: Effect.sync(last),
719
722
  plainFrame: Effect.sync(() => trimLines(last().replace(ESCAPES, ""))),
720
723
  frames: Effect.sync(() => raws().map((raw) => trimLines(styled(raw)))),
721
724
  transcript: Effect.sync(() => screenAfter(terminal.fake.written(), terminal.fake.streams.stdout.rows).join("\n")),
722
725
  written: Effect.sync(() => terminal.fake.written()),
726
+ stdoutWritten: Effect.sync(() => terminal.fake.stdout()),
727
+ stderrWritten: Effect.sync(() => terminal.fake.stderr()),
723
728
  handle
724
729
  };
725
730
  });
package/ui-testing.d.ts CHANGED
@@ -179,11 +179,14 @@ interface CliUiTestSession {
179
179
  * before the call is not missed. It waits at most 2 s, then dies naming the screen's number, the text it waited
180
180
  * for and how many screens had mounted. The returned screen's frames start at its own mount. A screen that crashed
181
181
  * makes `next` die with the crash instead.
182
+ *
183
+ * A `CliUi.live` run counts as a mount too, each run of the view one of its own: a program that draws a live
184
+ * progress view before its first screen takes one `next` per run before the screen's.
182
185
  */
183
186
  readonly next: (options?: CliUiTestNextOptions) => Effect.Effect<CliUiTestScreen>;
184
187
  /**
185
188
  * How many screens have mounted so far: every run that started mounting, one whose thunk threw before Ink drew
186
- * included.
189
+ * included, and every run of a `CliUi.live` view.
187
190
  */
188
191
  readonly mounts: Effect.Effect<number>;
189
192
  /** What the program wrote to stdout through `Console` (`log`, `info`, `debug`), one line per call. */
@@ -250,6 +253,25 @@ interface CliUiTestLive<E, S> {
250
253
  readonly advance: (duration: Duration.Input) => Effect.Effect<void>;
251
254
  /** Resize the terminal, then wait as `publish` does. */
252
255
  readonly resize: (columns: number, rows: number) => Effect.Effect<void>;
256
+ /**
257
+ * Write `bytes` straight to the terminal's `stream`, past Ink and past the view: what a child process or another
258
+ * library writing to the process's own stdout or stderr does while a frame is drawn.
259
+ *
260
+ * @remarks
261
+ * The bytes go to the same in-memory stream the view draws on, so `written` and `transcript` hold them in the order
262
+ * they were written. Nothing is redrawn and nothing is waited for: a raw line under a mounted frame does its harm at
263
+ * the frame's next redraw (`advance`, `publish`), which erases as many lines as the frame had, counted from the
264
+ * cursor the raw line moved, and so leaves the frame's top row stranded in the scrollback above it. That is the
265
+ * failure to reproduce before proving a fix routes the line through `handle.printAbove` or `handle.logConsole`.
266
+ *
267
+ * Write whole lines (end `bytes` with `\n`) to model a line landing under the frame. A write made while a frame is
268
+ * still due from Ink's throttle can be read as that frame by `frame` and `frames`, which are best-effort;
269
+ * `transcript` and `written` are the authority.
270
+ *
271
+ * @param stream - the terminal stream to write to
272
+ * @param bytes - the bytes to write, as given: escapes are not removed
273
+ */
274
+ readonly write: (stream: "stdout" | "stderr", bytes: string) => Effect.Effect<void>;
253
275
  /**
254
276
  * The last frame drawn, as token markup (see {@link CliUiTest.styled}); empty before the first.
255
277
  *
@@ -281,7 +303,18 @@ interface CliUiTestLive<E, S> {
281
303
  * (a scrollback wipe) anywhere in a run.
282
304
  */
283
305
  readonly written: Effect.Effect<string>;
284
- /** The view's own handle: its `state`, its `logConsole`, `done` and `close`. */
306
+ /**
307
+ * Every byte written to the terminal's stdout alone, escapes included: {@link CliUiTestLive.written} without stderr.
308
+ *
309
+ * @remarks
310
+ * With {@link CliUiTestLive.stderrWritten}, what to assert a line's stream on. Ink draws the frame, and erases and
311
+ * repaints it around a line logged above it, on stdout whichever stream the line is for, so a line routed to stderr
312
+ * shows in `stderrWritten` and never here.
313
+ */
314
+ readonly stdoutWritten: Effect.Effect<string>;
315
+ /** Every byte written to the terminal's stderr alone, escapes included: {@link CliUiTestLive.written} without stdout. */
316
+ readonly stderrWritten: Effect.Effect<string>;
317
+ /** The view's own handle: its `state`, its `logConsole`, `printAbove`, `done` and `close`. */
285
318
  readonly handle: LiveHandle<S>;
286
319
  }
287
320
  /**
package/ui.d.ts CHANGED
@@ -92,13 +92,6 @@ interface LiveOptions<E, S> {
92
92
  * turns without events. Anything but a positive, finite number is a defect.
93
93
  */
94
94
  readonly tickMillis?: number;
95
- /**
96
- * Clear React's user-timing entries after every render: `true`, `false`, or `"auto"` (the default), which clears
97
- * unless `NODE_ENV` is exactly `"production"`. React's development build records them on every render and never
98
- * clears them. The clear is process-wide: it removes every `measure` entry, a program's own included; marks are left
99
- * alone.
100
- */
101
- readonly drainPerformance?: boolean | "auto";
102
95
  }
103
96
  /**
104
97
  * The handle of a live view.
@@ -123,6 +116,36 @@ interface LiveHandle<S> {
123
116
  * Effect's `Console` (another library's own `process.stderr` writes) still tears the frame.
124
117
  */
125
118
  readonly logConsole: Console.Console;
119
+ /**
120
+ * Print one line above the frame if a run's frame is mounted now, and say whether it did: `true` when `line` and a
121
+ * line break went through Ink's writer for `stream`, above the frame, and `false`, having written nothing, when no
122
+ * frame is mounted.
123
+ *
124
+ * @remarks
125
+ * For a host that forwards output it did not write itself (a child process's stderr, a test runner's captured
126
+ * streams) and must know where it went. `logConsole` answers the same question silently, writing to `UiStreams`
127
+ * when no frame is mounted; `printAbove` leaves that case to the caller, who may have somewhere better to send the
128
+ * line, or may already be writing to the stream it would land on.
129
+ *
130
+ * A frame is mounted only during an interactive run: from the run's mount until just before its unmount. Between
131
+ * runs, before the first, after `close`, in a degraded run, and always when the view is not interactive (an agent,
132
+ * CI, a pipe, `TERM=dumb`, which never mounts), it returns `false`. The check and the write are one synchronous step,
133
+ * so the answer is never stale: a separate "is a frame mounted" query could be answered by one run and acted on in
134
+ * the gap before the next. Synchronous, like `logConsole`, so a Node stream's `write` callback can call it.
135
+ *
136
+ * `true` means Ink's writer accepted the line, not that it reached the terminal: while a render has handed the
137
+ * terminal to a child process (`useApp().suspendTerminal`), Ink drops what its writers are handed, and so does
138
+ * `logConsole`.
139
+ *
140
+ * The line is written as given: no formatting, no group indent, and no sanitising. A line break inside `line` is
141
+ * kept, and each of its lines lands above the frame. A cursor movement inside it (a `\r`, a cursor-up, an erase
142
+ * line, as a child's own progress bar writes) moves the cursor Ink repaints the frame from, and tears the frame.
143
+ *
144
+ * @param stream - the stream the line belongs on: Ink's stdout writer or its stderr writer
145
+ * @param line - the text to print, without a trailing line break
146
+ * @returns `true` if Ink's writer above a mounted frame took the line; `false`, with nothing written, otherwise
147
+ */
148
+ readonly printAbove: (stream: "stdout" | "stderr", line: string) => boolean;
126
149
  /**
127
150
  * Completes once the events have ended (the stream ended, the subscription's `PubSub` was ended with `PubSub.end` or
128
151
  * shut down, or `close` ended them) and the last run's frame is committed. Dies with what the view died of: a `reduce` that threw, or a
@@ -425,7 +448,8 @@ export declare class CliUi {
425
448
  * mount to its end, so a `CliUi.run` during a run waits for the run to end, and one between runs mounts at once.
426
449
  *
427
450
  * While a run is drawn, write logs through `logConsole`, provided around the work the view reports on: its lines
428
- * land above the frame. A line written to the terminal any other way tears the frame.
451
+ * land above the frame. A line written to the terminal any other way tears the frame. A host forwarding output it
452
+ * did not write itself prints it with `printAbove`, which says whether a frame was mounted to print it above.
429
453
  *
430
454
  * The view draws on stdout (`UiStreams`), at stdout's colour level and glyphs, and mounts only when the run is
431
455
  * interactive (`CliInteractive`).
@@ -1779,6 +1803,9 @@ export declare const Styled: (props: StyledProps) => ReactElement;
1779
1803
  * same paint, so give a long row Ink's `wrap: "truncate-end"` too: on a shrink Ink then clips it rather than letting
1780
1804
  * the terminal wrap it.
1781
1805
  *
1806
+ * Every component following one stdout shares a single `resize` listener on it, so a screen of many rows (a
1807
+ * `Viewport` of `DocView`s) holds one listener, not one per row.
1808
+ *
1782
1809
  * A React hook: call it from a component rendered inside an Ink tree; it needs no screen, but reads a `UiProvider`'s
1783
1810
  * size when there is one.
1784
1811
  *
@@ -1,33 +0,0 @@
1
- import { Config, Effect } from "effect";
2
-
3
- //#region src/ui/internal/perfDrain.ts
4
- /**
5
- * Whether a drain mode drains: `true` and `false` as given, and `"auto"` unless `NODE_ENV` is exactly `"production"`.
6
- *
7
- * @remarks
8
- * React picks its development build whenever `NODE_ENV` is not exactly `"production"`, and that build records
9
- * user-timing entries on every render and never clears them. An
10
- * unset `NODE_ENV`, the common case for a CLI, leaks like `"development"`, so `"auto"` drains then too. `NODE_ENV` is
11
- * read through `Config`, so a test sets it with a `ConfigProvider` and `./ui` reads no `process`.
12
- *
13
- * @internal
14
- */
15
- const resolveDrain = (mode) => mode === "auto" ? Config.String("NODE_ENV").pipe(Config.withDefault(""), Effect.map((env) => env !== "production"), Effect.orElseSucceed(() => true)) : Effect.succeed(mode);
16
- /**
17
- * Clear every user-timing `measure` entry in the process, when `drain` is true.
18
- *
19
- * @remarks
20
- * The clear is global: it removes a consumer's own measures too, because the platform's `clearMeasures` filters only
21
- * by name, and React's entries (`Update`, `Mount`, tagged `detail.devtools`) share their names with anything a
22
- * consumer might call a measure. Marks are left alone: React's development build leaks measures only, never marks,
23
- * so clearing marks would only take a host's own. A runtime without `performance` is left alone.
24
- *
25
- * @internal
26
- */
27
- const drainPerformance = (drain) => {
28
- if (!drain) return;
29
- globalThis.performance?.clearMeasures?.();
30
- };
31
-
32
- //#endregion
33
- export { drainPerformance, resolveDrain };