@effect/platform-node-shared 0.44.0 → 0.46.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/dist/cjs/NodeStream.js +3 -1
- package/dist/cjs/NodeStream.js.map +1 -1
- package/dist/cjs/internal/stream.js +54 -41
- package/dist/cjs/internal/stream.js.map +1 -1
- package/dist/cjs/internal/terminal.js +51 -62
- package/dist/cjs/internal/terminal.js.map +1 -1
- package/dist/dts/NodeStream.d.ts +4 -2
- package/dist/dts/NodeStream.d.ts.map +1 -1
- package/dist/esm/NodeStream.js +3 -1
- package/dist/esm/NodeStream.js.map +1 -1
- package/dist/esm/internal/stream.js +54 -41
- package/dist/esm/internal/stream.js.map +1 -1
- package/dist/esm/internal/terminal.js +51 -61
- package/dist/esm/internal/terminal.js.map +1 -1
- package/package.json +7 -7
- package/src/NodeStream.ts +10 -8
- package/src/internal/stream.ts +90 -95
- package/src/internal/terminal.ts +76 -101
package/src/internal/terminal.ts
CHANGED
|
@@ -1,126 +1,101 @@
|
|
|
1
1
|
import * as Error from "@effect/platform/Error"
|
|
2
2
|
import * as Terminal from "@effect/platform/Terminal"
|
|
3
3
|
import * as Effect from "effect/Effect"
|
|
4
|
+
import * as Exit from "effect/Exit"
|
|
4
5
|
import * as Layer from "effect/Layer"
|
|
6
|
+
import * as Mailbox from "effect/Mailbox"
|
|
5
7
|
import * as Option from "effect/Option"
|
|
8
|
+
import * as RcRef from "effect/RcRef"
|
|
6
9
|
import * as readline from "node:readline"
|
|
7
10
|
|
|
8
|
-
const defaultShouldQuit = (input: Terminal.UserInput)
|
|
11
|
+
const defaultShouldQuit = (input: Terminal.UserInput) =>
|
|
9
12
|
input.key.ctrl && (input.key.name === "c" || input.key.name === "d")
|
|
10
13
|
|
|
11
14
|
/** @internal */
|
|
12
|
-
export const make = (
|
|
15
|
+
export const make = Effect.fnUntraced(function*(
|
|
13
16
|
shouldQuit: (input: Terminal.UserInput) => boolean = defaultShouldQuit
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const output = yield* Effect.sync(() => globalThis.process.stdout)
|
|
17
|
+
) {
|
|
18
|
+
const stdin = process.stdin
|
|
19
|
+
const stdout = process.stdout
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
escapeCodeTimeout: 50
|
|
24
|
-
|
|
21
|
+
// Acquire readline interface with TTY setup/cleanup inside the scope
|
|
22
|
+
const rlRef = yield* RcRef.make({
|
|
23
|
+
acquire: Effect.acquireRelease(
|
|
24
|
+
Effect.sync(() => {
|
|
25
|
+
const rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 50 })
|
|
26
|
+
readline.emitKeypressEvents(stdin, rl)
|
|
27
|
+
|
|
28
|
+
if (stdin.isTTY) {
|
|
29
|
+
stdin.setRawMode(true)
|
|
30
|
+
}
|
|
31
|
+
return rl
|
|
32
|
+
}),
|
|
33
|
+
(rl) =>
|
|
34
|
+
Effect.sync(() => {
|
|
35
|
+
if (stdin.isTTY) {
|
|
36
|
+
stdin.setRawMode(false)
|
|
37
|
+
}
|
|
38
|
+
rl.close()
|
|
39
|
+
})
|
|
25
40
|
)
|
|
41
|
+
})
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
const columns = Effect.sync(() => stdout.columns ?? 0)
|
|
44
|
+
|
|
45
|
+
const readInput = Effect.gen(function*() {
|
|
46
|
+
yield* RcRef.get(rlRef)
|
|
47
|
+
const mailbox = yield* Mailbox.make<Terminal.UserInput>()
|
|
48
|
+
const handleKeypress = (s: string | undefined, k: readline.Key) => {
|
|
49
|
+
const userInput = {
|
|
50
|
+
input: Option.fromNullable(s),
|
|
51
|
+
key: { name: k.name ?? "", ctrl: !!k.ctrl, meta: !!k.meta, shift: !!k.shift }
|
|
52
|
+
}
|
|
53
|
+
if (shouldQuit(userInput)) {
|
|
54
|
+
mailbox.unsafeDone(Exit.void)
|
|
55
|
+
} else {
|
|
56
|
+
mailbox.unsafeOffer(userInput)
|
|
32
57
|
}
|
|
33
|
-
return rl
|
|
34
58
|
}
|
|
59
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => stdin.off("keypress", handleKeypress)))
|
|
60
|
+
stdin.on("keypress", handleKeypress)
|
|
61
|
+
return mailbox as Mailbox.ReadonlyMailbox<Terminal.UserInput>
|
|
62
|
+
})
|
|
35
63
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Effect.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
rl.close()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// Handle the `"keypress"` event emitted by `stdin` (forced by readline)
|
|
46
|
-
const handleKeypressEvent = (input: typeof globalThis.process.stdin) =>
|
|
47
|
-
Effect.async<Terminal.UserInput, Terminal.QuitException>((resume) => {
|
|
48
|
-
const handleKeypress = (input: string | undefined, key: readline.Key) => {
|
|
49
|
-
const userInput: Terminal.UserInput = {
|
|
50
|
-
input: Option.fromNullable(input),
|
|
51
|
-
key: {
|
|
52
|
-
name: key.name || "",
|
|
53
|
-
ctrl: key.ctrl || false,
|
|
54
|
-
meta: key.meta || false,
|
|
55
|
-
shift: key.shift || false
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (shouldQuit(userInput)) {
|
|
59
|
-
resume(Effect.fail(new Terminal.QuitException()))
|
|
60
|
-
} else {
|
|
61
|
-
resume(Effect.succeed(userInput))
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
input.once("keypress", handleKeypress)
|
|
65
|
-
return Effect.sync(() => {
|
|
66
|
-
input.removeListener("keypress", handleKeypress)
|
|
67
|
-
})
|
|
64
|
+
const readLine = RcRef.get(rlRef).pipe(
|
|
65
|
+
Effect.flatMap((readlineInterface) =>
|
|
66
|
+
Effect.async<string, Terminal.QuitException>((resume) => {
|
|
67
|
+
const onLine = (line: string) => resume(Effect.succeed(line))
|
|
68
|
+
readlineInterface.once("line", onLine)
|
|
69
|
+
return Effect.sync(() => readlineInterface.off("line", onLine))
|
|
68
70
|
})
|
|
71
|
+
),
|
|
72
|
+
Effect.scoped
|
|
73
|
+
)
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Effect.async<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
const display = (prompt: string) =>
|
|
76
|
+
Effect.uninterruptible(
|
|
77
|
+
Effect.async<void, Error.PlatformError>((resume) => {
|
|
78
|
+
stdout.write(prompt, (err) =>
|
|
79
|
+
err
|
|
80
|
+
? resume(Effect.fail(
|
|
81
|
+
new Error.BadArgument({
|
|
82
|
+
module: "Terminal",
|
|
83
|
+
method: "display",
|
|
84
|
+
description: "Failed to write prompt to stdout",
|
|
85
|
+
cause: err
|
|
86
|
+
})
|
|
87
|
+
))
|
|
88
|
+
: resume(Effect.void))
|
|
80
89
|
})
|
|
81
|
-
|
|
82
|
-
const readInput = Effect.acquireUseRelease(
|
|
83
|
-
acquireReadlineInterface.pipe(Effect.map(emitKeypressEvents)),
|
|
84
|
-
() => handleKeypressEvent(input),
|
|
85
|
-
releaseReadlineInterface
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
const readLine = Effect.acquireUseRelease(
|
|
89
|
-
acquireReadlineInterface,
|
|
90
|
-
(rl) => handleLineEvent(rl),
|
|
91
|
-
releaseReadlineInterface
|
|
92
90
|
)
|
|
93
91
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
resume(Effect.fail(
|
|
100
|
-
new Error.BadArgument({
|
|
101
|
-
module: "Terminal",
|
|
102
|
-
method: "display",
|
|
103
|
-
description: "Failed to write prompt to output",
|
|
104
|
-
cause: err
|
|
105
|
-
})
|
|
106
|
-
))
|
|
107
|
-
}
|
|
108
|
-
resume(Effect.void)
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
return Terminal.Terminal.of({
|
|
114
|
-
// The columns property can be undefined if stdout was redirected
|
|
115
|
-
columns: Effect.sync(() => output.columns || 0),
|
|
116
|
-
readInput,
|
|
117
|
-
readLine,
|
|
118
|
-
display
|
|
119
|
-
})
|
|
92
|
+
return Terminal.Terminal.of({
|
|
93
|
+
columns,
|
|
94
|
+
readInput,
|
|
95
|
+
readLine,
|
|
96
|
+
display
|
|
120
97
|
})
|
|
98
|
+
})
|
|
121
99
|
|
|
122
100
|
/** @internal */
|
|
123
|
-
export const layer: Layer.Layer<Terminal.Terminal> = Layer.scoped(
|
|
124
|
-
Terminal.Terminal,
|
|
125
|
-
make(defaultShouldQuit)
|
|
126
|
-
)
|
|
101
|
+
export const layer: Layer.Layer<Terminal.Terminal> = Layer.scoped(Terminal.Terminal, make(defaultShouldQuit))
|