Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/terminal-display-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@effect/platform": patch
"@effect/platform-node-shared": patch
---

Add `displayError` method to `Terminal` for writing to stderr.
3 changes: 3 additions & 0 deletions packages/cli/test/services/MockTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,16 @@ export const make = Effect.gen(function*() {

const display: MockTerminal["display"] = (input) => Console.log(input)

const displayError: MockTerminal["displayError"] = (input) => Console.error(input)

const readInput: MockTerminal["readInput"] = Effect.succeed(queue)

return MockTerminal.of({
columns: Effect.succeed(80),
rows: Effect.succeed(24),
isTTY: Effect.succeed(true),
display,
displayError,
readInput,
readLine: Effect.succeed(""),
inputKey,
Expand Down
21 changes: 20 additions & 1 deletion packages/platform-node-shared/src/internal/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const make = Effect.fnUntraced(function*(
) {
const stdin = process.stdin
const stdout = process.stdout
const stderr = process.stderr

// Acquire readline interface with TTY setup/cleanup inside the scope
const rlRef = yield* RcRef.make({
Expand Down Expand Up @@ -90,13 +91,31 @@ export const make = Effect.fnUntraced(function*(
})
)

const displayError = (prompt: string) =>
Effect.uninterruptible(
Effect.async<void, Error.PlatformError>((resume) => {
stderr.write(prompt, (err) =>
err
? resume(Effect.fail(
new Error.BadArgument({
module: "Terminal",
method: "displayError",
description: "Failed to write prompt to stderr",
cause: err
})
))
: resume(Effect.void))
})
)

return Terminal.Terminal.of({
columns,
rows,
isTTY,
readInput,
readLine,
display
display,
displayError
})
})

Expand Down
45 changes: 45 additions & 0 deletions packages/platform-node-shared/test/Terminal.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
import * as NodeTerminal from "@effect/platform-node-shared/NodeTerminal"
import * as Terminal from "@effect/platform/Terminal"
import { describe, expect, it } from "@effect/vitest"
import * as Chunk from "effect/Chunk"
import * as Effect from "effect/Effect"
import * as Ref from "effect/Ref"

const runPromise = <E, A>(self: Effect.Effect<A, E, Terminal.Terminal>) =>
Effect.runPromise(
Effect.provide(self, NodeTerminal.layer)
)

const makeTestTerminal = Effect.gen(function*() {
const displayOutput = yield* Ref.make(Chunk.empty<string>())
const displayErrorOutput = yield* Ref.make(Chunk.empty<string>())

const terminal = Terminal.Terminal.of({
columns: Effect.succeed(80),
rows: Effect.succeed(24),
isTTY: Effect.succeed(true),
readInput: Effect.die("not implemented"),
readLine: Effect.die("not implemented"),
display: (text) => Ref.update(displayOutput, Chunk.append(text)),
displayError: (text) => Ref.update(displayErrorOutput, Chunk.append(text))
})

return {
terminal,
displayOutput,
displayErrorOutput
}
})

describe("Terminal", () => {
it("columns", () =>
runPromise(Effect.gen(function*() {
Expand All @@ -29,4 +52,26 @@ describe("Terminal", () => {
const isTTY = yield* terminal.isTTY
expect(typeof isTTY).toEqual("boolean")
})))

it("display", () =>
Effect.gen(function*() {
const { displayOutput, terminal } = yield* makeTestTerminal

yield* terminal.display("hello")
yield* terminal.display("world")

const output = yield* Ref.get(displayOutput)
expect(Chunk.toArray(output)).toEqual(["hello", "world"])
}).pipe(Effect.runPromise))

it("displayError", () =>
Effect.gen(function*() {
const { displayErrorOutput, terminal } = yield* makeTestTerminal

yield* terminal.displayError("error1")
yield* terminal.displayError("error2")

const errors = yield* Ref.get(displayErrorOutput)
expect(Chunk.toArray(errors)).toEqual(["error1", "error2"])
}).pipe(Effect.runPromise))
})
4 changes: 4 additions & 0 deletions packages/platform/src/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export interface Terminal {
* Displays text to the the default standard output.
*/
readonly display: (text: string) => Effect<void, PlatformError>
/**
* Displays text to the the default standard error.
*/
readonly displayError: (text: string) => Effect<void, PlatformError>
}

/**
Expand Down