@optique/run 1.3.2 → 1.3.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/dist/output.cjs +127 -0
- package/dist/output.d.cts +98 -0
- package/dist/output.d.ts +98 -0
- package/dist/output.js +123 -0
- package/dist/print.cjs +19 -8
- package/dist/print.d.cts +9 -0
- package/dist/print.d.ts +9 -0
- package/dist/print.js +19 -8
- package/dist/run.cjs +4 -3
- package/dist/run.d.cts +60 -0
- package/dist/run.d.ts +60 -0
- package/dist/run.js +4 -3
- package/dist/valueparser.cjs +1 -1
- package/dist/valueparser.js +1 -1
- package/package.json +4 -4
package/dist/output.cjs
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const node_fs = require_rolldown_runtime.__toESM(require("node:fs"));
|
|
3
|
+
const node_process = require_rolldown_runtime.__toESM(require("node:process"));
|
|
4
|
+
|
|
5
|
+
//#region src/output.ts
|
|
6
|
+
let sleepCell;
|
|
7
|
+
function sleepSync(ms) {
|
|
8
|
+
sleepCell ??= new Int32Array(new SharedArrayBuffer(4));
|
|
9
|
+
Atomics.wait(sleepCell, 0, 0, ms);
|
|
10
|
+
}
|
|
11
|
+
function getErrorCode(error) {
|
|
12
|
+
return typeof error === "object" && error != null && "code" in error ? error.code : void 0;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Writes all of the given bytes to a file descriptor synchronously.
|
|
16
|
+
*
|
|
17
|
+
* Partial writes resume from the next unwritten byte. When the descriptor
|
|
18
|
+
* is non-blocking and temporarily full (`EAGAIN`/`EWOULDBLOCK`), this sleeps
|
|
19
|
+
* briefly and retries, blocking the current thread until the reader catches
|
|
20
|
+
* up. When the reader has closed the pipe (`EPIPE`) or reset the socket
|
|
21
|
+
* (`ECONNRESET`), the rest of the output cannot be delivered, so this returns
|
|
22
|
+
* silently.
|
|
23
|
+
*
|
|
24
|
+
* @param fd The file descriptor to write to.
|
|
25
|
+
* @param bytes The bytes to write.
|
|
26
|
+
* @param deps Dependencies to use instead of the defaults, for testing.
|
|
27
|
+
* @throws {Error} If writing fails for any other reason, or if a write makes
|
|
28
|
+
* no progress.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
function writeAllSync(fd, bytes, deps = {}) {
|
|
32
|
+
const writeSync = deps.writeSync ?? node_fs.writeSync;
|
|
33
|
+
const sleep = deps.sleep ?? sleepSync;
|
|
34
|
+
let offset = 0;
|
|
35
|
+
while (offset < bytes.length) {
|
|
36
|
+
let written;
|
|
37
|
+
try {
|
|
38
|
+
written = writeSync(fd, bytes, offset, bytes.length - offset);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const code = getErrorCode(error);
|
|
41
|
+
if (code === "EAGAIN" || code === "EWOULDBLOCK") {
|
|
42
|
+
sleep(1);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (code === "EINTR") continue;
|
|
46
|
+
if (code === "EPIPE" || code === "ECONNRESET") return;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
if (written <= 0) throw new Error(`Failed to write output to file descriptor ${fd}: no bytes were written.`);
|
|
50
|
+
offset += written;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reads the encoding a writable stream applies to strings, which
|
|
55
|
+
* `setDefaultEncoding()` changes. Node.js and Bun expose it only through
|
|
56
|
+
* the stream's internal state, so this returns `undefined` when that is not
|
|
57
|
+
* available.
|
|
58
|
+
*
|
|
59
|
+
* @param stream The stream to inspect.
|
|
60
|
+
* @returns The default encoding, or `undefined` if it cannot be read.
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
function getDefaultEncoding(stream) {
|
|
64
|
+
if (!("_writableState" in stream)) return void 0;
|
|
65
|
+
const state = stream._writableState;
|
|
66
|
+
return typeof state === "object" && state != null && "defaultEncoding" in state ? state.defaultEncoding : void 0;
|
|
67
|
+
}
|
|
68
|
+
function isUtf8Encoding(encoding) {
|
|
69
|
+
return encoding === void 0 || typeof encoding === "string" && ["utf8", "utf-8"].includes(encoding.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decides whether output for the given stream should bypass the stream and
|
|
73
|
+
* be written directly to its file descriptor.
|
|
74
|
+
*
|
|
75
|
+
* TTYs keep going through the stream, since terminal writes are synchronous
|
|
76
|
+
* on POSIX and Windows consoles need the stream's own handling. Windows is
|
|
77
|
+
* excluded altogether. Streams without a file descriptor, such as
|
|
78
|
+
* `process.stdout` in a Node.js worker thread, also keep going through the
|
|
79
|
+
* stream, since writing to fd 1 or 2 would bypass the worker's redirection.
|
|
80
|
+
* Deno is excluded as well: its streams do not lose queued output on exit,
|
|
81
|
+
* and its `fs.writeSync()` can write part of the buffer to a non-blocking
|
|
82
|
+
* pipe and then throw `EAGAIN` without reporting how much it wrote, which
|
|
83
|
+
* would make retrying duplicate output. Finally, streams whose default
|
|
84
|
+
* encoding is not UTF-8 keep going through the stream, since direct writes
|
|
85
|
+
* always encode the text as UTF-8.
|
|
86
|
+
*
|
|
87
|
+
* @param stream The stream to inspect.
|
|
88
|
+
* @param platform The value of `process.platform`.
|
|
89
|
+
* @param deno Whether the current runtime is Deno.
|
|
90
|
+
* @returns `true` if output should be written directly to `stream.fd`.
|
|
91
|
+
* @internal
|
|
92
|
+
*/
|
|
93
|
+
function shouldWriteDirectly(stream, platform, deno) {
|
|
94
|
+
const fd = stream.fd;
|
|
95
|
+
return !deno && platform !== "win32" && !stream.isTTY && typeof fd === "number" && Number.isInteger(fd) && fd >= 0 && isUtf8Encoding(stream.defaultEncoding);
|
|
96
|
+
}
|
|
97
|
+
const encoder = new TextEncoder();
|
|
98
|
+
/**
|
|
99
|
+
* Writes text to standard output or standard error such that, when the
|
|
100
|
+
* stream is a pipe or a file, the text has been completely handed to the
|
|
101
|
+
* operating system by the time this returns (unless the reader closed the
|
|
102
|
+
* pipe). Otherwise, it falls back to the stream's `write()` method.
|
|
103
|
+
*
|
|
104
|
+
* Note that output queued in the stream by earlier `write()` calls is not
|
|
105
|
+
* flushed, and that text written directly can overtake it.
|
|
106
|
+
*
|
|
107
|
+
* @param streamName The stream to write to.
|
|
108
|
+
* @param text The text to write.
|
|
109
|
+
* @throws {Error} If writing to the file descriptor fails.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
function writeOutput(streamName, text) {
|
|
113
|
+
const stream = node_process.default[streamName];
|
|
114
|
+
const streamLike = {
|
|
115
|
+
isTTY: stream.isTTY,
|
|
116
|
+
fd: stream.fd,
|
|
117
|
+
defaultEncoding: getDefaultEncoding(stream)
|
|
118
|
+
};
|
|
119
|
+
if (shouldWriteDirectly(streamLike, node_process.default.platform, "Deno" in globalThis)) writeAllSync(stream.fd, encoder.encode(text));
|
|
120
|
+
else stream.write(text);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//#endregion
|
|
124
|
+
exports.getDefaultEncoding = getDefaultEncoding;
|
|
125
|
+
exports.shouldWriteDirectly = shouldWriteDirectly;
|
|
126
|
+
exports.writeAllSync = writeAllSync;
|
|
127
|
+
exports.writeOutput = writeOutput;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/output.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Dependencies of {@link writeAllSync}, injectable for testing.
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
interface WriteAllSyncDeps {
|
|
7
|
+
/**
|
|
8
|
+
* Writes bytes to a file descriptor and returns how many bytes were
|
|
9
|
+
* written.
|
|
10
|
+
* @default `writeSync()` from `node:fs`
|
|
11
|
+
*/
|
|
12
|
+
readonly writeSync?: (fd: number, buffer: Uint8Array, offset: number, length: number) => number;
|
|
13
|
+
/**
|
|
14
|
+
* Blocks the current thread for the given number of milliseconds.
|
|
15
|
+
* @default A sleep based on `Atomics.wait()`
|
|
16
|
+
*/
|
|
17
|
+
readonly sleep?: (ms: number) => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Writes all of the given bytes to a file descriptor synchronously.
|
|
21
|
+
*
|
|
22
|
+
* Partial writes resume from the next unwritten byte. When the descriptor
|
|
23
|
+
* is non-blocking and temporarily full (`EAGAIN`/`EWOULDBLOCK`), this sleeps
|
|
24
|
+
* briefly and retries, blocking the current thread until the reader catches
|
|
25
|
+
* up. When the reader has closed the pipe (`EPIPE`) or reset the socket
|
|
26
|
+
* (`ECONNRESET`), the rest of the output cannot be delivered, so this returns
|
|
27
|
+
* silently.
|
|
28
|
+
*
|
|
29
|
+
* @param fd The file descriptor to write to.
|
|
30
|
+
* @param bytes The bytes to write.
|
|
31
|
+
* @param deps Dependencies to use instead of the defaults, for testing.
|
|
32
|
+
* @throws {Error} If writing fails for any other reason, or if a write makes
|
|
33
|
+
* no progress.
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
declare function writeAllSync(fd: number, bytes: Uint8Array, deps?: WriteAllSyncDeps): void;
|
|
37
|
+
/**
|
|
38
|
+
* The subset of a writable stdio stream that {@link shouldWriteDirectly}
|
|
39
|
+
* inspects.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
interface StdioStreamLike {
|
|
43
|
+
readonly isTTY?: boolean;
|
|
44
|
+
readonly fd?: unknown;
|
|
45
|
+
/** The encoding the stream applies to strings, if known. */
|
|
46
|
+
readonly defaultEncoding?: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reads the encoding a writable stream applies to strings, which
|
|
50
|
+
* `setDefaultEncoding()` changes. Node.js and Bun expose it only through
|
|
51
|
+
* the stream's internal state, so this returns `undefined` when that is not
|
|
52
|
+
* available.
|
|
53
|
+
*
|
|
54
|
+
* @param stream The stream to inspect.
|
|
55
|
+
* @returns The default encoding, or `undefined` if it cannot be read.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
declare function getDefaultEncoding(stream: object): unknown;
|
|
59
|
+
/**
|
|
60
|
+
* Decides whether output for the given stream should bypass the stream and
|
|
61
|
+
* be written directly to its file descriptor.
|
|
62
|
+
*
|
|
63
|
+
* TTYs keep going through the stream, since terminal writes are synchronous
|
|
64
|
+
* on POSIX and Windows consoles need the stream's own handling. Windows is
|
|
65
|
+
* excluded altogether. Streams without a file descriptor, such as
|
|
66
|
+
* `process.stdout` in a Node.js worker thread, also keep going through the
|
|
67
|
+
* stream, since writing to fd 1 or 2 would bypass the worker's redirection.
|
|
68
|
+
* Deno is excluded as well: its streams do not lose queued output on exit,
|
|
69
|
+
* and its `fs.writeSync()` can write part of the buffer to a non-blocking
|
|
70
|
+
* pipe and then throw `EAGAIN` without reporting how much it wrote, which
|
|
71
|
+
* would make retrying duplicate output. Finally, streams whose default
|
|
72
|
+
* encoding is not UTF-8 keep going through the stream, since direct writes
|
|
73
|
+
* always encode the text as UTF-8.
|
|
74
|
+
*
|
|
75
|
+
* @param stream The stream to inspect.
|
|
76
|
+
* @param platform The value of `process.platform`.
|
|
77
|
+
* @param deno Whether the current runtime is Deno.
|
|
78
|
+
* @returns `true` if output should be written directly to `stream.fd`.
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
declare function shouldWriteDirectly(stream: StdioStreamLike, platform: string, deno: boolean): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Writes text to standard output or standard error such that, when the
|
|
84
|
+
* stream is a pipe or a file, the text has been completely handed to the
|
|
85
|
+
* operating system by the time this returns (unless the reader closed the
|
|
86
|
+
* pipe). Otherwise, it falls back to the stream's `write()` method.
|
|
87
|
+
*
|
|
88
|
+
* Note that output queued in the stream by earlier `write()` calls is not
|
|
89
|
+
* flushed, and that text written directly can overtake it.
|
|
90
|
+
*
|
|
91
|
+
* @param streamName The stream to write to.
|
|
92
|
+
* @param text The text to write.
|
|
93
|
+
* @throws {Error} If writing to the file descriptor fails.
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
declare function writeOutput(streamName: "stdout" | "stderr", text: string): void;
|
|
97
|
+
//#endregion
|
|
98
|
+
export { StdioStreamLike, WriteAllSyncDeps, getDefaultEncoding, shouldWriteDirectly, writeAllSync, writeOutput };
|
package/dist/output.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/output.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Dependencies of {@link writeAllSync}, injectable for testing.
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
interface WriteAllSyncDeps {
|
|
7
|
+
/**
|
|
8
|
+
* Writes bytes to a file descriptor and returns how many bytes were
|
|
9
|
+
* written.
|
|
10
|
+
* @default `writeSync()` from `node:fs`
|
|
11
|
+
*/
|
|
12
|
+
readonly writeSync?: (fd: number, buffer: Uint8Array, offset: number, length: number) => number;
|
|
13
|
+
/**
|
|
14
|
+
* Blocks the current thread for the given number of milliseconds.
|
|
15
|
+
* @default A sleep based on `Atomics.wait()`
|
|
16
|
+
*/
|
|
17
|
+
readonly sleep?: (ms: number) => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Writes all of the given bytes to a file descriptor synchronously.
|
|
21
|
+
*
|
|
22
|
+
* Partial writes resume from the next unwritten byte. When the descriptor
|
|
23
|
+
* is non-blocking and temporarily full (`EAGAIN`/`EWOULDBLOCK`), this sleeps
|
|
24
|
+
* briefly and retries, blocking the current thread until the reader catches
|
|
25
|
+
* up. When the reader has closed the pipe (`EPIPE`) or reset the socket
|
|
26
|
+
* (`ECONNRESET`), the rest of the output cannot be delivered, so this returns
|
|
27
|
+
* silently.
|
|
28
|
+
*
|
|
29
|
+
* @param fd The file descriptor to write to.
|
|
30
|
+
* @param bytes The bytes to write.
|
|
31
|
+
* @param deps Dependencies to use instead of the defaults, for testing.
|
|
32
|
+
* @throws {Error} If writing fails for any other reason, or if a write makes
|
|
33
|
+
* no progress.
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
declare function writeAllSync(fd: number, bytes: Uint8Array, deps?: WriteAllSyncDeps): void;
|
|
37
|
+
/**
|
|
38
|
+
* The subset of a writable stdio stream that {@link shouldWriteDirectly}
|
|
39
|
+
* inspects.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
interface StdioStreamLike {
|
|
43
|
+
readonly isTTY?: boolean;
|
|
44
|
+
readonly fd?: unknown;
|
|
45
|
+
/** The encoding the stream applies to strings, if known. */
|
|
46
|
+
readonly defaultEncoding?: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reads the encoding a writable stream applies to strings, which
|
|
50
|
+
* `setDefaultEncoding()` changes. Node.js and Bun expose it only through
|
|
51
|
+
* the stream's internal state, so this returns `undefined` when that is not
|
|
52
|
+
* available.
|
|
53
|
+
*
|
|
54
|
+
* @param stream The stream to inspect.
|
|
55
|
+
* @returns The default encoding, or `undefined` if it cannot be read.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
declare function getDefaultEncoding(stream: object): unknown;
|
|
59
|
+
/**
|
|
60
|
+
* Decides whether output for the given stream should bypass the stream and
|
|
61
|
+
* be written directly to its file descriptor.
|
|
62
|
+
*
|
|
63
|
+
* TTYs keep going through the stream, since terminal writes are synchronous
|
|
64
|
+
* on POSIX and Windows consoles need the stream's own handling. Windows is
|
|
65
|
+
* excluded altogether. Streams without a file descriptor, such as
|
|
66
|
+
* `process.stdout` in a Node.js worker thread, also keep going through the
|
|
67
|
+
* stream, since writing to fd 1 or 2 would bypass the worker's redirection.
|
|
68
|
+
* Deno is excluded as well: its streams do not lose queued output on exit,
|
|
69
|
+
* and its `fs.writeSync()` can write part of the buffer to a non-blocking
|
|
70
|
+
* pipe and then throw `EAGAIN` without reporting how much it wrote, which
|
|
71
|
+
* would make retrying duplicate output. Finally, streams whose default
|
|
72
|
+
* encoding is not UTF-8 keep going through the stream, since direct writes
|
|
73
|
+
* always encode the text as UTF-8.
|
|
74
|
+
*
|
|
75
|
+
* @param stream The stream to inspect.
|
|
76
|
+
* @param platform The value of `process.platform`.
|
|
77
|
+
* @param deno Whether the current runtime is Deno.
|
|
78
|
+
* @returns `true` if output should be written directly to `stream.fd`.
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
declare function shouldWriteDirectly(stream: StdioStreamLike, platform: string, deno: boolean): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Writes text to standard output or standard error such that, when the
|
|
84
|
+
* stream is a pipe or a file, the text has been completely handed to the
|
|
85
|
+
* operating system by the time this returns (unless the reader closed the
|
|
86
|
+
* pipe). Otherwise, it falls back to the stream's `write()` method.
|
|
87
|
+
*
|
|
88
|
+
* Note that output queued in the stream by earlier `write()` calls is not
|
|
89
|
+
* flushed, and that text written directly can overtake it.
|
|
90
|
+
*
|
|
91
|
+
* @param streamName The stream to write to.
|
|
92
|
+
* @param text The text to write.
|
|
93
|
+
* @throws {Error} If writing to the file descriptor fails.
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
declare function writeOutput(streamName: "stdout" | "stderr", text: string): void;
|
|
97
|
+
//#endregion
|
|
98
|
+
export { StdioStreamLike, WriteAllSyncDeps, getDefaultEncoding, shouldWriteDirectly, writeAllSync, writeOutput };
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { writeSync } from "node:fs";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
//#region src/output.ts
|
|
5
|
+
let sleepCell;
|
|
6
|
+
function sleepSync(ms) {
|
|
7
|
+
sleepCell ??= new Int32Array(new SharedArrayBuffer(4));
|
|
8
|
+
Atomics.wait(sleepCell, 0, 0, ms);
|
|
9
|
+
}
|
|
10
|
+
function getErrorCode(error) {
|
|
11
|
+
return typeof error === "object" && error != null && "code" in error ? error.code : void 0;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Writes all of the given bytes to a file descriptor synchronously.
|
|
15
|
+
*
|
|
16
|
+
* Partial writes resume from the next unwritten byte. When the descriptor
|
|
17
|
+
* is non-blocking and temporarily full (`EAGAIN`/`EWOULDBLOCK`), this sleeps
|
|
18
|
+
* briefly and retries, blocking the current thread until the reader catches
|
|
19
|
+
* up. When the reader has closed the pipe (`EPIPE`) or reset the socket
|
|
20
|
+
* (`ECONNRESET`), the rest of the output cannot be delivered, so this returns
|
|
21
|
+
* silently.
|
|
22
|
+
*
|
|
23
|
+
* @param fd The file descriptor to write to.
|
|
24
|
+
* @param bytes The bytes to write.
|
|
25
|
+
* @param deps Dependencies to use instead of the defaults, for testing.
|
|
26
|
+
* @throws {Error} If writing fails for any other reason, or if a write makes
|
|
27
|
+
* no progress.
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
function writeAllSync(fd, bytes, deps = {}) {
|
|
31
|
+
const writeSync$1 = deps.writeSync ?? writeSync;
|
|
32
|
+
const sleep = deps.sleep ?? sleepSync;
|
|
33
|
+
let offset = 0;
|
|
34
|
+
while (offset < bytes.length) {
|
|
35
|
+
let written;
|
|
36
|
+
try {
|
|
37
|
+
written = writeSync$1(fd, bytes, offset, bytes.length - offset);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const code = getErrorCode(error);
|
|
40
|
+
if (code === "EAGAIN" || code === "EWOULDBLOCK") {
|
|
41
|
+
sleep(1);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (code === "EINTR") continue;
|
|
45
|
+
if (code === "EPIPE" || code === "ECONNRESET") return;
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
if (written <= 0) throw new Error(`Failed to write output to file descriptor ${fd}: no bytes were written.`);
|
|
49
|
+
offset += written;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Reads the encoding a writable stream applies to strings, which
|
|
54
|
+
* `setDefaultEncoding()` changes. Node.js and Bun expose it only through
|
|
55
|
+
* the stream's internal state, so this returns `undefined` when that is not
|
|
56
|
+
* available.
|
|
57
|
+
*
|
|
58
|
+
* @param stream The stream to inspect.
|
|
59
|
+
* @returns The default encoding, or `undefined` if it cannot be read.
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
function getDefaultEncoding(stream) {
|
|
63
|
+
if (!("_writableState" in stream)) return void 0;
|
|
64
|
+
const state = stream._writableState;
|
|
65
|
+
return typeof state === "object" && state != null && "defaultEncoding" in state ? state.defaultEncoding : void 0;
|
|
66
|
+
}
|
|
67
|
+
function isUtf8Encoding(encoding) {
|
|
68
|
+
return encoding === void 0 || typeof encoding === "string" && ["utf8", "utf-8"].includes(encoding.toLowerCase());
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Decides whether output for the given stream should bypass the stream and
|
|
72
|
+
* be written directly to its file descriptor.
|
|
73
|
+
*
|
|
74
|
+
* TTYs keep going through the stream, since terminal writes are synchronous
|
|
75
|
+
* on POSIX and Windows consoles need the stream's own handling. Windows is
|
|
76
|
+
* excluded altogether. Streams without a file descriptor, such as
|
|
77
|
+
* `process.stdout` in a Node.js worker thread, also keep going through the
|
|
78
|
+
* stream, since writing to fd 1 or 2 would bypass the worker's redirection.
|
|
79
|
+
* Deno is excluded as well: its streams do not lose queued output on exit,
|
|
80
|
+
* and its `fs.writeSync()` can write part of the buffer to a non-blocking
|
|
81
|
+
* pipe and then throw `EAGAIN` without reporting how much it wrote, which
|
|
82
|
+
* would make retrying duplicate output. Finally, streams whose default
|
|
83
|
+
* encoding is not UTF-8 keep going through the stream, since direct writes
|
|
84
|
+
* always encode the text as UTF-8.
|
|
85
|
+
*
|
|
86
|
+
* @param stream The stream to inspect.
|
|
87
|
+
* @param platform The value of `process.platform`.
|
|
88
|
+
* @param deno Whether the current runtime is Deno.
|
|
89
|
+
* @returns `true` if output should be written directly to `stream.fd`.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
function shouldWriteDirectly(stream, platform, deno) {
|
|
93
|
+
const fd = stream.fd;
|
|
94
|
+
return !deno && platform !== "win32" && !stream.isTTY && typeof fd === "number" && Number.isInteger(fd) && fd >= 0 && isUtf8Encoding(stream.defaultEncoding);
|
|
95
|
+
}
|
|
96
|
+
const encoder = new TextEncoder();
|
|
97
|
+
/**
|
|
98
|
+
* Writes text to standard output or standard error such that, when the
|
|
99
|
+
* stream is a pipe or a file, the text has been completely handed to the
|
|
100
|
+
* operating system by the time this returns (unless the reader closed the
|
|
101
|
+
* pipe). Otherwise, it falls back to the stream's `write()` method.
|
|
102
|
+
*
|
|
103
|
+
* Note that output queued in the stream by earlier `write()` calls is not
|
|
104
|
+
* flushed, and that text written directly can overtake it.
|
|
105
|
+
*
|
|
106
|
+
* @param streamName The stream to write to.
|
|
107
|
+
* @param text The text to write.
|
|
108
|
+
* @throws {Error} If writing to the file descriptor fails.
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
function writeOutput(streamName, text) {
|
|
112
|
+
const stream = process[streamName];
|
|
113
|
+
const streamLike = {
|
|
114
|
+
isTTY: stream.isTTY,
|
|
115
|
+
fd: stream.fd,
|
|
116
|
+
defaultEncoding: getDefaultEncoding(stream)
|
|
117
|
+
};
|
|
118
|
+
if (shouldWriteDirectly(streamLike, process.platform, "Deno" in globalThis)) writeAllSync(stream.fd, encoder.encode(text));
|
|
119
|
+
else stream.write(text);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
export { getDefaultEncoding, shouldWriteDirectly, writeAllSync, writeOutput };
|
package/dist/print.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_output = require('./output.cjs');
|
|
2
3
|
const __optique_core_internal_terminal = require_rolldown_runtime.__toESM(require("@optique/core/internal/terminal"));
|
|
3
4
|
const node_process = require_rolldown_runtime.__toESM(require("node:process"));
|
|
4
5
|
|
|
@@ -50,8 +51,12 @@ function printError(message, options = {}) {
|
|
|
50
51
|
quotes,
|
|
51
52
|
maxWidth: options.maxWidth ?? output.columns
|
|
52
53
|
});
|
|
53
|
-
|
|
54
|
-
if (options.exitCode
|
|
54
|
+
const text = formatted + "\n";
|
|
55
|
+
if (options.exitCode == null) output.write(text);
|
|
56
|
+
else {
|
|
57
|
+
require_output.writeOutput(stream, text);
|
|
58
|
+
node_process.default.exit(options.exitCode);
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
/**
|
|
57
62
|
* Creates a custom printer function with predefined formatting options.
|
|
@@ -84,7 +89,17 @@ function printError(message, options = {}) {
|
|
|
84
89
|
*/
|
|
85
90
|
function createPrinter(options = {}) {
|
|
86
91
|
const stream = options.stream ?? "stdout";
|
|
87
|
-
const
|
|
92
|
+
const format = createFormatter(options);
|
|
93
|
+
return (message) => {
|
|
94
|
+
node_process.default[stream].write(format(message) + "\n");
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a function that formats messages for the given stream, resolving
|
|
99
|
+
* terminal-dependent defaults once, at creation time.
|
|
100
|
+
*/
|
|
101
|
+
function createFormatter(options) {
|
|
102
|
+
const output = node_process.default[options.stream ?? "stdout"];
|
|
88
103
|
const formatMessage = (0, __optique_core_internal_terminal.resolveMessageFormatter)(options);
|
|
89
104
|
const formatOptions = {
|
|
90
105
|
initialWidth: options.initialWidth,
|
|
@@ -92,11 +107,7 @@ function createPrinter(options = {}) {
|
|
|
92
107
|
quotes: options.quotes,
|
|
93
108
|
maxWidth: options.maxWidth ?? output.columns
|
|
94
109
|
};
|
|
95
|
-
return (message) =>
|
|
96
|
-
const formatted = formatMessage(message, formatOptions);
|
|
97
|
-
if (stream === "stderr") node_process.default.stderr.write(formatted + "\n");
|
|
98
|
-
else node_process.default.stdout.write(formatted + "\n");
|
|
99
|
-
};
|
|
110
|
+
return (message) => formatMessage(message, formatOptions);
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
//#endregion
|
package/dist/print.d.cts
CHANGED
|
@@ -31,6 +31,12 @@ interface PrintErrorOptions extends PrintOptions {
|
|
|
31
31
|
/**
|
|
32
32
|
* Exit code to use when exiting the process.
|
|
33
33
|
* If specified, the process will exit with this code after printing the error.
|
|
34
|
+
*
|
|
35
|
+
* When the output stream is a pipe or a file on POSIX systems under Node.js
|
|
36
|
+
* or Bun, the error message is written directly to its file descriptor, and
|
|
37
|
+
* the process exits only after the whole message has been written, even if
|
|
38
|
+
* the reader is slow. Output that earlier calls queued in the stream is not
|
|
39
|
+
* flushed.
|
|
34
40
|
*/
|
|
35
41
|
readonly exitCode?: number;
|
|
36
42
|
}
|
|
@@ -95,6 +101,9 @@ declare function print(message: Message, options?: PrintOptions): void;
|
|
|
95
101
|
* @param options Optional formatting options and exit code.
|
|
96
102
|
* @throws {TypeError} If initialWidth is not a finite integer.
|
|
97
103
|
* @throws {RangeError} If initialWidth is negative or a theme color is invalid.
|
|
104
|
+
* @throws {Error} If `exitCode` is specified and writing the message directly
|
|
105
|
+
* to the output stream's file descriptor fails for a reason other
|
|
106
|
+
* than the reader having gone away (for example, `ENOSPC`).
|
|
98
107
|
*
|
|
99
108
|
* @example
|
|
100
109
|
* ```typescript
|
package/dist/print.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ interface PrintErrorOptions extends PrintOptions {
|
|
|
31
31
|
/**
|
|
32
32
|
* Exit code to use when exiting the process.
|
|
33
33
|
* If specified, the process will exit with this code after printing the error.
|
|
34
|
+
*
|
|
35
|
+
* When the output stream is a pipe or a file on POSIX systems under Node.js
|
|
36
|
+
* or Bun, the error message is written directly to its file descriptor, and
|
|
37
|
+
* the process exits only after the whole message has been written, even if
|
|
38
|
+
* the reader is slow. Output that earlier calls queued in the stream is not
|
|
39
|
+
* flushed.
|
|
34
40
|
*/
|
|
35
41
|
readonly exitCode?: number;
|
|
36
42
|
}
|
|
@@ -95,6 +101,9 @@ declare function print(message: Message, options?: PrintOptions): void;
|
|
|
95
101
|
* @param options Optional formatting options and exit code.
|
|
96
102
|
* @throws {TypeError} If initialWidth is not a finite integer.
|
|
97
103
|
* @throws {RangeError} If initialWidth is negative or a theme color is invalid.
|
|
104
|
+
* @throws {Error} If `exitCode` is specified and writing the message directly
|
|
105
|
+
* to the output stream's file descriptor fails for a reason other
|
|
106
|
+
* than the reader having gone away (for example, `ENOSPC`).
|
|
98
107
|
*
|
|
99
108
|
* @example
|
|
100
109
|
* ```typescript
|
package/dist/print.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { writeOutput } from "./output.js";
|
|
1
2
|
import { renderErrorMessage, resolveMessageFormatter } from "@optique/core/internal/terminal";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
|
|
@@ -49,8 +50,12 @@ function printError(message, options = {}) {
|
|
|
49
50
|
quotes,
|
|
50
51
|
maxWidth: options.maxWidth ?? output.columns
|
|
51
52
|
});
|
|
52
|
-
|
|
53
|
-
if (options.exitCode
|
|
53
|
+
const text = formatted + "\n";
|
|
54
|
+
if (options.exitCode == null) output.write(text);
|
|
55
|
+
else {
|
|
56
|
+
writeOutput(stream, text);
|
|
57
|
+
process.exit(options.exitCode);
|
|
58
|
+
}
|
|
54
59
|
}
|
|
55
60
|
/**
|
|
56
61
|
* Creates a custom printer function with predefined formatting options.
|
|
@@ -83,7 +88,17 @@ function printError(message, options = {}) {
|
|
|
83
88
|
*/
|
|
84
89
|
function createPrinter(options = {}) {
|
|
85
90
|
const stream = options.stream ?? "stdout";
|
|
86
|
-
const
|
|
91
|
+
const format = createFormatter(options);
|
|
92
|
+
return (message) => {
|
|
93
|
+
process[stream].write(format(message) + "\n");
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Creates a function that formats messages for the given stream, resolving
|
|
98
|
+
* terminal-dependent defaults once, at creation time.
|
|
99
|
+
*/
|
|
100
|
+
function createFormatter(options) {
|
|
101
|
+
const output = process[options.stream ?? "stdout"];
|
|
87
102
|
const formatMessage = resolveMessageFormatter(options);
|
|
88
103
|
const formatOptions = {
|
|
89
104
|
initialWidth: options.initialWidth,
|
|
@@ -91,11 +106,7 @@ function createPrinter(options = {}) {
|
|
|
91
106
|
quotes: options.quotes,
|
|
92
107
|
maxWidth: options.maxWidth ?? output.columns
|
|
93
108
|
};
|
|
94
|
-
return (message) =>
|
|
95
|
-
const formatted = formatMessage(message, formatOptions);
|
|
96
|
-
if (stream === "stderr") process.stderr.write(formatted + "\n");
|
|
97
|
-
else process.stdout.write(formatted + "\n");
|
|
98
|
-
};
|
|
109
|
+
return (message) => formatMessage(message, formatOptions);
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
//#endregion
|
package/dist/run.cjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_output = require('./output.cjs');
|
|
2
3
|
const require_terminal = require('./terminal.cjs');
|
|
3
4
|
const __optique_core_internal_terminal = require_rolldown_runtime.__toESM(require("@optique/core/internal/terminal"));
|
|
4
5
|
const __optique_core_facade = require_rolldown_runtime.__toESM(require("@optique/core/facade"));
|
|
5
|
-
const node_path = require_rolldown_runtime.__toESM(require("node:path"));
|
|
6
6
|
const node_process = require_rolldown_runtime.__toESM(require("node:process"));
|
|
7
|
+
const node_path = require_rolldown_runtime.__toESM(require("node:path"));
|
|
7
8
|
|
|
8
9
|
//#region src/run.ts
|
|
9
10
|
function getProgramHelpMetadata(metadata) {
|
|
@@ -68,10 +69,10 @@ function buildCoreOptions(options, programMetadata) {
|
|
|
68
69
|
const programName = options.programName ?? node_path.default.basename(node_process.default.argv[1] ?? "cli");
|
|
69
70
|
const args = options.args ?? node_process.default.argv.slice(2);
|
|
70
71
|
const stdout = options.stdout ?? ((line) => {
|
|
71
|
-
|
|
72
|
+
require_output.writeOutput("stdout", `${line}\n`);
|
|
72
73
|
});
|
|
73
74
|
const stderr = options.stderr ?? ((line) => {
|
|
74
|
-
|
|
75
|
+
require_output.writeOutput("stderr", `${line}\n`);
|
|
75
76
|
});
|
|
76
77
|
const onExit = options.onExit ?? ((exitCode) => node_process.default.exit(exitCode));
|
|
77
78
|
const colors = options.colors ?? require_terminal.detectColorSupport(node_process.default.stdout, require_terminal.readEnvironmentVariable);
|
package/dist/run.d.cts
CHANGED
|
@@ -34,6 +34,14 @@ interface RunOptions {
|
|
|
34
34
|
* Function used to output help and usage messages. Assumes it prints the
|
|
35
35
|
* ending newline.
|
|
36
36
|
*
|
|
37
|
+
* When `process.stdout` is a pipe or a file on POSIX systems under Node.js or
|
|
38
|
+
* Bun, the default writes directly to its file descriptor and returns only
|
|
39
|
+
* after the whole text has been written, even if the reader is slow, so that
|
|
40
|
+
* exiting the process right afterward does not truncate the output.
|
|
41
|
+
*
|
|
42
|
+
* A custom writer using `console.log()` can lose large piped output on
|
|
43
|
+
* affected Bun versions. See the Bun output warning on {@link run}.
|
|
44
|
+
*
|
|
37
45
|
* @default Writes to `process.stdout` with a trailing newline
|
|
38
46
|
*/
|
|
39
47
|
readonly stdout?: (text: string) => void;
|
|
@@ -41,12 +49,32 @@ interface RunOptions {
|
|
|
41
49
|
* Function used to output error messages. Assumes it prints the ending
|
|
42
50
|
* newline.
|
|
43
51
|
*
|
|
52
|
+
* When `process.stderr` is a pipe or a file on POSIX systems under Node.js or
|
|
53
|
+
* Bun, the default writes directly to its file descriptor and returns only
|
|
54
|
+
* after the whole text has been written, even if the reader is slow, so that
|
|
55
|
+
* exiting the process right afterward does not truncate the output.
|
|
56
|
+
*
|
|
57
|
+
* A custom writer using `console.error()` can lose large piped output on
|
|
58
|
+
* affected Bun versions. See the Bun output warning on {@link run}.
|
|
59
|
+
*
|
|
44
60
|
* @default Writes to `process.stderr` with a trailing newline
|
|
45
61
|
*/
|
|
46
62
|
readonly stderr?: (text: string) => void;
|
|
47
63
|
/**
|
|
48
64
|
* Function used to exit the process on help/version display or parse error.
|
|
49
65
|
*
|
|
66
|
+
* This hook is synchronous and runs immediately after an output handler
|
|
67
|
+
* returns. With the default `process.exit()` handler, custom `stdout` and
|
|
68
|
+
* `stderr` handlers must finish writing synchronously before returning.
|
|
69
|
+
* The default exit handler also does not wait for writes queued by
|
|
70
|
+
* application code.
|
|
71
|
+
*
|
|
72
|
+
* To wait for asynchronous writes, record their completion promises and
|
|
73
|
+
* throw an exception carrying the exit code from `onExit`. Catch that
|
|
74
|
+
* exception outside the runner, await the recorded promises, then call
|
|
75
|
+
* `process.exit()` with that code.
|
|
76
|
+
* An arbitrary delay does not guarantee that output has been flushed.
|
|
77
|
+
*
|
|
50
78
|
* @default `process.exit`
|
|
51
79
|
*/
|
|
52
80
|
readonly onExit?: (exitCode: number) => never;
|
|
@@ -343,6 +371,25 @@ type AcceptExactOptionalRunOptions<TOptions> = [TOptions] extends [RunOptions |
|
|
|
343
371
|
* - Exit the process with appropriate codes on help or error
|
|
344
372
|
* - Format output according to terminal capabilities
|
|
345
373
|
*
|
|
374
|
+
* On affected Bun versions, importing `@optique/run` can cause large
|
|
375
|
+
* application `console.log()` output to be silently truncated when piped,
|
|
376
|
+
* even after successful parsing and natural process exit. This is
|
|
377
|
+
* [Bun issue #36419](https://github.com/oven-sh/bun/issues/36419), reported
|
|
378
|
+
* on macOS arm64 with Bun 1.3.14 and 1.4.2. Use `process.stdout.write()`
|
|
379
|
+
* instead of `console.log()` for application data, and `process.stderr.write()`
|
|
380
|
+
* instead of `console.error()` for diagnostics. Both console methods can lose
|
|
381
|
+
* large piped output. Let the process exit naturally. If explicit termination
|
|
382
|
+
* is necessary, wait for the completion callbacks of all pending stdout/stderr
|
|
383
|
+
* writes before calling `process.exit()`; a delay is not a flush guarantee.
|
|
384
|
+
* Waiting after `console.log()` or `console.error()` cannot recover dropped
|
|
385
|
+
* bytes.
|
|
386
|
+
*
|
|
387
|
+
* Setting `colors` and `maxWidth` explicitly does not avoid this Bun bug:
|
|
388
|
+
* importing `node:process`, which this module does, was enough to trigger it
|
|
389
|
+
* in Bun 1.3.14. The upstream fix was merged in
|
|
390
|
+
* [Bun PR #43868](https://github.com/oven-sh/bun/pull/43868); check whether
|
|
391
|
+
* your Bun release includes it.
|
|
392
|
+
*
|
|
346
393
|
* @template T The parser type being executed.
|
|
347
394
|
* @param parser The command-line parser to execute.
|
|
348
395
|
* @param options Configuration options for customizing behavior.
|
|
@@ -351,6 +398,9 @@ type AcceptExactOptionalRunOptions<TOptions> = [TOptions] extends [RunOptions |
|
|
|
351
398
|
* color, even when colors are disabled.
|
|
352
399
|
* @returns The parsed result if successful. On help display or parse errors,
|
|
353
400
|
* the function will call `process.exit()` and not return.
|
|
401
|
+
* @throws {Error} If the default `stdout` or `stderr` writer fails to write
|
|
402
|
+
* to the stream's file descriptor for a reason other than the reader
|
|
403
|
+
* having gone away (for example, `ENOSPC`).
|
|
354
404
|
*
|
|
355
405
|
* @example
|
|
356
406
|
* ```typescript
|
|
@@ -425,6 +475,8 @@ declare function run<T extends Parser<Mode, unknown, unknown>>(parser: T, option
|
|
|
425
475
|
* Use this when you know your parser is sync-only to get direct return values
|
|
426
476
|
* without Promise wrappers.
|
|
427
477
|
*
|
|
478
|
+
* See {@link run} for Bun's piped console output warning and workarounds.
|
|
479
|
+
*
|
|
428
480
|
* @template T The sync parser type being executed.
|
|
429
481
|
* @param parser The synchronous command-line parser to execute.
|
|
430
482
|
* @param options Configuration options for customizing behavior.
|
|
@@ -433,6 +485,9 @@ declare function run<T extends Parser<Mode, unknown, unknown>>(parser: T, option
|
|
|
433
485
|
* @returns The parsed result if successful.
|
|
434
486
|
* @throws {TypeError} If an async parser (or a {@link Program} wrapping one)
|
|
435
487
|
* is passed at runtime. Use {@link run} or {@link runAsync} instead.
|
|
488
|
+
* @throws {Error} If the default `stdout` or `stderr` writer fails to write
|
|
489
|
+
* to the stream's file descriptor for a reason other than the reader
|
|
490
|
+
* having gone away (for example, `ENOSPC`).
|
|
436
491
|
* @since 0.9.0
|
|
437
492
|
*/
|
|
438
493
|
declare function runSync<T extends Parser<"sync", unknown, unknown>, TContexts extends readonly SourceContext<unknown>[]>(parser: T, options: RunOptions & {
|
|
@@ -452,12 +507,17 @@ declare function runSync<T extends Parser<"sync", unknown, unknown>>(parser: T,
|
|
|
452
507
|
* Promise. Use this when working with parsers that may contain async
|
|
453
508
|
* value parsers.
|
|
454
509
|
*
|
|
510
|
+
* See {@link run} for Bun's piped console output warning and workarounds.
|
|
511
|
+
*
|
|
455
512
|
* @template T The parser type being executed.
|
|
456
513
|
* @param parser The command-line parser to execute.
|
|
457
514
|
* @param options Configuration options for customizing behavior.
|
|
458
515
|
* @throws {RangeError} If rendering help or errors encounters an invalid theme
|
|
459
516
|
* color, even when colors are disabled.
|
|
460
517
|
* @returns A Promise of the parsed result if successful.
|
|
518
|
+
* @throws {Error} If the default `stdout` or `stderr` writer fails to write
|
|
519
|
+
* to the stream's file descriptor for a reason other than the reader
|
|
520
|
+
* having gone away (for example, `ENOSPC`).
|
|
461
521
|
* @since 0.9.0
|
|
462
522
|
*/
|
|
463
523
|
declare function runAsync<T extends Parser<Mode, unknown, unknown>, TContexts extends readonly SourceContext<unknown>[]>(parser: T, options: RunOptions & {
|
package/dist/run.d.ts
CHANGED
|
@@ -34,6 +34,14 @@ interface RunOptions {
|
|
|
34
34
|
* Function used to output help and usage messages. Assumes it prints the
|
|
35
35
|
* ending newline.
|
|
36
36
|
*
|
|
37
|
+
* When `process.stdout` is a pipe or a file on POSIX systems under Node.js or
|
|
38
|
+
* Bun, the default writes directly to its file descriptor and returns only
|
|
39
|
+
* after the whole text has been written, even if the reader is slow, so that
|
|
40
|
+
* exiting the process right afterward does not truncate the output.
|
|
41
|
+
*
|
|
42
|
+
* A custom writer using `console.log()` can lose large piped output on
|
|
43
|
+
* affected Bun versions. See the Bun output warning on {@link run}.
|
|
44
|
+
*
|
|
37
45
|
* @default Writes to `process.stdout` with a trailing newline
|
|
38
46
|
*/
|
|
39
47
|
readonly stdout?: (text: string) => void;
|
|
@@ -41,12 +49,32 @@ interface RunOptions {
|
|
|
41
49
|
* Function used to output error messages. Assumes it prints the ending
|
|
42
50
|
* newline.
|
|
43
51
|
*
|
|
52
|
+
* When `process.stderr` is a pipe or a file on POSIX systems under Node.js or
|
|
53
|
+
* Bun, the default writes directly to its file descriptor and returns only
|
|
54
|
+
* after the whole text has been written, even if the reader is slow, so that
|
|
55
|
+
* exiting the process right afterward does not truncate the output.
|
|
56
|
+
*
|
|
57
|
+
* A custom writer using `console.error()` can lose large piped output on
|
|
58
|
+
* affected Bun versions. See the Bun output warning on {@link run}.
|
|
59
|
+
*
|
|
44
60
|
* @default Writes to `process.stderr` with a trailing newline
|
|
45
61
|
*/
|
|
46
62
|
readonly stderr?: (text: string) => void;
|
|
47
63
|
/**
|
|
48
64
|
* Function used to exit the process on help/version display or parse error.
|
|
49
65
|
*
|
|
66
|
+
* This hook is synchronous and runs immediately after an output handler
|
|
67
|
+
* returns. With the default `process.exit()` handler, custom `stdout` and
|
|
68
|
+
* `stderr` handlers must finish writing synchronously before returning.
|
|
69
|
+
* The default exit handler also does not wait for writes queued by
|
|
70
|
+
* application code.
|
|
71
|
+
*
|
|
72
|
+
* To wait for asynchronous writes, record their completion promises and
|
|
73
|
+
* throw an exception carrying the exit code from `onExit`. Catch that
|
|
74
|
+
* exception outside the runner, await the recorded promises, then call
|
|
75
|
+
* `process.exit()` with that code.
|
|
76
|
+
* An arbitrary delay does not guarantee that output has been flushed.
|
|
77
|
+
*
|
|
50
78
|
* @default `process.exit`
|
|
51
79
|
*/
|
|
52
80
|
readonly onExit?: (exitCode: number) => never;
|
|
@@ -343,6 +371,25 @@ type AcceptExactOptionalRunOptions<TOptions> = [TOptions] extends [RunOptions |
|
|
|
343
371
|
* - Exit the process with appropriate codes on help or error
|
|
344
372
|
* - Format output according to terminal capabilities
|
|
345
373
|
*
|
|
374
|
+
* On affected Bun versions, importing `@optique/run` can cause large
|
|
375
|
+
* application `console.log()` output to be silently truncated when piped,
|
|
376
|
+
* even after successful parsing and natural process exit. This is
|
|
377
|
+
* [Bun issue #36419](https://github.com/oven-sh/bun/issues/36419), reported
|
|
378
|
+
* on macOS arm64 with Bun 1.3.14 and 1.4.2. Use `process.stdout.write()`
|
|
379
|
+
* instead of `console.log()` for application data, and `process.stderr.write()`
|
|
380
|
+
* instead of `console.error()` for diagnostics. Both console methods can lose
|
|
381
|
+
* large piped output. Let the process exit naturally. If explicit termination
|
|
382
|
+
* is necessary, wait for the completion callbacks of all pending stdout/stderr
|
|
383
|
+
* writes before calling `process.exit()`; a delay is not a flush guarantee.
|
|
384
|
+
* Waiting after `console.log()` or `console.error()` cannot recover dropped
|
|
385
|
+
* bytes.
|
|
386
|
+
*
|
|
387
|
+
* Setting `colors` and `maxWidth` explicitly does not avoid this Bun bug:
|
|
388
|
+
* importing `node:process`, which this module does, was enough to trigger it
|
|
389
|
+
* in Bun 1.3.14. The upstream fix was merged in
|
|
390
|
+
* [Bun PR #43868](https://github.com/oven-sh/bun/pull/43868); check whether
|
|
391
|
+
* your Bun release includes it.
|
|
392
|
+
*
|
|
346
393
|
* @template T The parser type being executed.
|
|
347
394
|
* @param parser The command-line parser to execute.
|
|
348
395
|
* @param options Configuration options for customizing behavior.
|
|
@@ -351,6 +398,9 @@ type AcceptExactOptionalRunOptions<TOptions> = [TOptions] extends [RunOptions |
|
|
|
351
398
|
* color, even when colors are disabled.
|
|
352
399
|
* @returns The parsed result if successful. On help display or parse errors,
|
|
353
400
|
* the function will call `process.exit()` and not return.
|
|
401
|
+
* @throws {Error} If the default `stdout` or `stderr` writer fails to write
|
|
402
|
+
* to the stream's file descriptor for a reason other than the reader
|
|
403
|
+
* having gone away (for example, `ENOSPC`).
|
|
354
404
|
*
|
|
355
405
|
* @example
|
|
356
406
|
* ```typescript
|
|
@@ -425,6 +475,8 @@ declare function run<T extends Parser<Mode, unknown, unknown>>(parser: T, option
|
|
|
425
475
|
* Use this when you know your parser is sync-only to get direct return values
|
|
426
476
|
* without Promise wrappers.
|
|
427
477
|
*
|
|
478
|
+
* See {@link run} for Bun's piped console output warning and workarounds.
|
|
479
|
+
*
|
|
428
480
|
* @template T The sync parser type being executed.
|
|
429
481
|
* @param parser The synchronous command-line parser to execute.
|
|
430
482
|
* @param options Configuration options for customizing behavior.
|
|
@@ -433,6 +485,9 @@ declare function run<T extends Parser<Mode, unknown, unknown>>(parser: T, option
|
|
|
433
485
|
* @returns The parsed result if successful.
|
|
434
486
|
* @throws {TypeError} If an async parser (or a {@link Program} wrapping one)
|
|
435
487
|
* is passed at runtime. Use {@link run} or {@link runAsync} instead.
|
|
488
|
+
* @throws {Error} If the default `stdout` or `stderr` writer fails to write
|
|
489
|
+
* to the stream's file descriptor for a reason other than the reader
|
|
490
|
+
* having gone away (for example, `ENOSPC`).
|
|
436
491
|
* @since 0.9.0
|
|
437
492
|
*/
|
|
438
493
|
declare function runSync<T extends Parser<"sync", unknown, unknown>, TContexts extends readonly SourceContext<unknown>[]>(parser: T, options: RunOptions & {
|
|
@@ -452,12 +507,17 @@ declare function runSync<T extends Parser<"sync", unknown, unknown>>(parser: T,
|
|
|
452
507
|
* Promise. Use this when working with parsers that may contain async
|
|
453
508
|
* value parsers.
|
|
454
509
|
*
|
|
510
|
+
* See {@link run} for Bun's piped console output warning and workarounds.
|
|
511
|
+
*
|
|
455
512
|
* @template T The parser type being executed.
|
|
456
513
|
* @param parser The command-line parser to execute.
|
|
457
514
|
* @param options Configuration options for customizing behavior.
|
|
458
515
|
* @throws {RangeError} If rendering help or errors encounters an invalid theme
|
|
459
516
|
* color, even when colors are disabled.
|
|
460
517
|
* @returns A Promise of the parsed result if successful.
|
|
518
|
+
* @throws {Error} If the default `stdout` or `stderr` writer fails to write
|
|
519
|
+
* to the stream's file descriptor for a reason other than the reader
|
|
520
|
+
* having gone away (for example, `ENOSPC`).
|
|
461
521
|
* @since 0.9.0
|
|
462
522
|
*/
|
|
463
523
|
declare function runAsync<T extends Parser<Mode, unknown, unknown>, TContexts extends readonly SourceContext<unknown>[]>(parser: T, options: RunOptions & {
|
package/dist/run.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { writeOutput } from "./output.js";
|
|
1
2
|
import { detectColorSupport, detectTerminalWidth, readEnvironmentVariable } from "./terminal.js";
|
|
2
3
|
import { withAutomaticWidth } from "@optique/core/internal/terminal";
|
|
3
4
|
import { runParser, runWith, runWithSync } from "@optique/core/facade";
|
|
4
|
-
import path from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
|
+
import path from "node:path";
|
|
6
7
|
|
|
7
8
|
//#region src/run.ts
|
|
8
9
|
function getProgramHelpMetadata(metadata) {
|
|
@@ -67,10 +68,10 @@ function buildCoreOptions(options, programMetadata) {
|
|
|
67
68
|
const programName = options.programName ?? path.basename(process.argv[1] ?? "cli");
|
|
68
69
|
const args = options.args ?? process.argv.slice(2);
|
|
69
70
|
const stdout = options.stdout ?? ((line) => {
|
|
70
|
-
|
|
71
|
+
writeOutput("stdout", `${line}\n`);
|
|
71
72
|
});
|
|
72
73
|
const stderr = options.stderr ?? ((line) => {
|
|
73
|
-
|
|
74
|
+
writeOutput("stderr", `${line}\n`);
|
|
74
75
|
});
|
|
75
76
|
const onExit = options.onExit ?? ((exitCode) => process.exit(exitCode));
|
|
76
77
|
const colors = options.colors ?? detectColorSupport(process.stdout, readEnvironmentVariable);
|
package/dist/valueparser.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const node_fs = require_rolldown_runtime.__toESM(require("node:fs"));
|
|
2
3
|
const node_path = require_rolldown_runtime.__toESM(require("node:path"));
|
|
3
4
|
const __optique_core_message = require_rolldown_runtime.__toESM(require("@optique/core/message"));
|
|
4
5
|
const __optique_core_nonempty = require_rolldown_runtime.__toESM(require("@optique/core/nonempty"));
|
|
5
|
-
const node_fs = require_rolldown_runtime.__toESM(require("node:fs"));
|
|
6
6
|
|
|
7
7
|
//#region src/valueparser.ts
|
|
8
8
|
/**
|
package/dist/valueparser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
1
2
|
import { basename, dirname, extname } from "node:path";
|
|
2
3
|
import { message, text } from "@optique/core/message";
|
|
3
4
|
import { ensureNonEmptyString } from "@optique/core/nonempty";
|
|
4
|
-
import { existsSync, statSync } from "node:fs";
|
|
5
5
|
|
|
6
6
|
//#region src/valueparser.ts
|
|
7
7
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/run",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Type-safe combinatorial command-line interface parser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CLI",
|
|
@@ -76,11 +76,11 @@
|
|
|
76
76
|
},
|
|
77
77
|
"sideEffects": false,
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@optique/core": "1.3.
|
|
79
|
+
"@optique/core": "1.3.3"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@optique/config": "1.3.
|
|
83
|
-
"@optique/env": "1.3.
|
|
82
|
+
"@optique/config": "1.3.3",
|
|
83
|
+
"@optique/env": "1.3.3",
|
|
84
84
|
"@types/node": "^24.0.0",
|
|
85
85
|
"fast-check": "^4.7.0",
|
|
86
86
|
"tsdown": "^0.13.0",
|