@effected/cli 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/cli",
3
- "version": "0.15.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
@@ -430,6 +430,7 @@ const live = (options) => Effect.gen(function* () {
430
430
  return {
431
431
  state: Effect.sync(() => state),
432
432
  logConsole: bridge.writer,
433
+ printAbove: bridge.printAbove,
433
434
  done,
434
435
  close: Effect.andThen(ending, settled)
435
436
  };
@@ -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
@@ -253,6 +253,25 @@ interface CliUiTestLive<E, S> {
253
253
  readonly advance: (duration: Duration.Input) => Effect.Effect<void>;
254
254
  /** Resize the terminal, then wait as `publish` does. */
255
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>;
256
275
  /**
257
276
  * The last frame drawn, as token markup (see {@link CliUiTest.styled}); empty before the first.
258
277
  *
@@ -284,7 +303,18 @@ interface CliUiTestLive<E, S> {
284
303
  * (a scrollback wipe) anywhere in a run.
285
304
  */
286
305
  readonly written: Effect.Effect<string>;
287
- /** 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`. */
288
318
  readonly handle: LiveHandle<S>;
289
319
  }
290
320
  /**
package/ui.d.ts CHANGED
@@ -116,6 +116,36 @@ interface LiveHandle<S> {
116
116
  * Effect's `Console` (another library's own `process.stderr` writes) still tears the frame.
117
117
  */
118
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;
119
149
  /**
120
150
  * Completes once the events have ended (the stream ended, the subscription's `PubSub` was ended with `PubSub.end` or
121
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
@@ -418,7 +448,8 @@ export declare class CliUi {
418
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.
419
449
  *
420
450
  * While a run is drawn, write logs through `logConsole`, provided around the work the view reports on: its lines
421
- * 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.
422
453
  *
423
454
  * The view draws on stdout (`UiStreams`), at stdout's colour level and glyphs, and mounts only when the run is
424
455
  * interactive (`CliInteractive`).