@gobing-ai/ts-runtime 0.2.7 → 0.2.9
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/README.md +159 -20
- package/dist/config.d.ts +10 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -58
- package/dist/context.d.ts +1 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -2
- package/dist/fs.d.ts +15 -0
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +97 -13
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/path.d.ts +7 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +55 -0
- package/dist/process-executor.d.ts +36 -0
- package/dist/process-executor.d.ts.map +1 -1
- package/dist/process-executor.js +74 -0
- package/dist/schema-validation.d.ts +42 -0
- package/dist/schema-validation.d.ts.map +1 -0
- package/dist/schema-validation.js +304 -0
- package/dist/types.d.ts +0 -11
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +41 -57
- package/src/context.ts +2 -4
- package/src/fs.ts +124 -25
- package/src/index.ts +2 -0
- package/src/path.ts +54 -0
- package/src/process-executor.ts +128 -0
- package/src/schema-validation.ts +442 -0
- package/src/types.ts +0 -10
package/src/fs.ts
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
access,
|
|
4
|
-
appendFile,
|
|
5
|
-
cp,
|
|
6
|
-
mkdir,
|
|
7
|
-
readdir,
|
|
8
|
-
readFile,
|
|
9
|
-
realpath,
|
|
10
|
-
rename,
|
|
11
|
-
rm,
|
|
12
|
-
stat,
|
|
13
|
-
writeFile,
|
|
14
|
-
} from 'node:fs/promises';
|
|
15
|
-
import { dirname, join, resolve } from 'node:path';
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirnamePath, getProcessCwd, joinPath, resolvePath } from './path';
|
|
16
3
|
|
|
17
4
|
export interface FileStat {
|
|
18
5
|
isFile(): boolean;
|
|
@@ -41,26 +28,55 @@ export interface FileSystem {
|
|
|
41
28
|
createLogStream(path: string): LogStream;
|
|
42
29
|
}
|
|
43
30
|
|
|
31
|
+
export interface SyncFileSystem {
|
|
32
|
+
readFile(path: string): string;
|
|
33
|
+
writeFile(path: string, content: string): void;
|
|
34
|
+
mkdir(path: string): void;
|
|
35
|
+
readDir(path: string): string[];
|
|
36
|
+
unlink(path: string): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type NodeFsPromises = typeof import('node:fs/promises');
|
|
40
|
+
type NodeFs = typeof import('node:fs');
|
|
41
|
+
|
|
42
|
+
let fsPromisesModule: Promise<NodeFsPromises> | null = null;
|
|
43
|
+
let fsModule: Promise<NodeFs> | null = null;
|
|
44
|
+
|
|
45
|
+
function nodeFsPromises(): Promise<NodeFsPromises> {
|
|
46
|
+
fsPromisesModule ??= import('node:fs/promises');
|
|
47
|
+
return fsPromisesModule;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function nodeFs(): Promise<NodeFs> {
|
|
51
|
+
fsModule ??= import('node:fs');
|
|
52
|
+
return fsModule;
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
export class NodeFileSystem implements FileSystem {
|
|
45
56
|
async readFile(path: string): Promise<string> {
|
|
57
|
+
const { readFile } = await nodeFsPromises();
|
|
46
58
|
return await readFile(path, 'utf-8');
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
async writeFile(path: string, content: string): Promise<void> {
|
|
62
|
+
const { writeFile } = await nodeFsPromises();
|
|
50
63
|
await ensureDirForFile(path, this);
|
|
51
64
|
await writeFile(path, content, 'utf-8');
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
async appendFile(path: string, content: string): Promise<void> {
|
|
68
|
+
const { appendFile } = await nodeFsPromises();
|
|
55
69
|
await ensureDirForFile(path, this);
|
|
56
70
|
await appendFile(path, content, 'utf-8');
|
|
57
71
|
}
|
|
58
72
|
|
|
59
73
|
async mkdir(path: string): Promise<void> {
|
|
74
|
+
const { mkdir } = await nodeFsPromises();
|
|
60
75
|
await mkdir(path, { recursive: true });
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
async exists(path: string): Promise<boolean> {
|
|
79
|
+
const { access } = await nodeFsPromises();
|
|
64
80
|
try {
|
|
65
81
|
await access(path);
|
|
66
82
|
return true;
|
|
@@ -70,14 +86,17 @@ export class NodeFileSystem implements FileSystem {
|
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
async readDir(path: string): Promise<string[]> {
|
|
89
|
+
const { readdir } = await nodeFsPromises();
|
|
73
90
|
return await readdir(path);
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
async unlink(path: string): Promise<void> {
|
|
94
|
+
const { rm } = await nodeFsPromises();
|
|
77
95
|
await rm(path, { recursive: true, force: true });
|
|
78
96
|
}
|
|
79
97
|
|
|
80
98
|
async stat(path: string): Promise<FileStat | null> {
|
|
99
|
+
const { stat } = await nodeFsPromises();
|
|
81
100
|
try {
|
|
82
101
|
const value = await stat(path);
|
|
83
102
|
return {
|
|
@@ -92,20 +111,80 @@ export class NodeFileSystem implements FileSystem {
|
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
async realpath(path: string): Promise<string> {
|
|
114
|
+
const { realpath } = await nodeFsPromises();
|
|
95
115
|
return await realpath(path);
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
async copy(src: string, dest: string): Promise<void> {
|
|
119
|
+
const { cp } = await nodeFsPromises();
|
|
99
120
|
await cp(src, dest, { recursive: true });
|
|
100
121
|
}
|
|
101
122
|
|
|
102
123
|
async rename(src: string, dest: string): Promise<void> {
|
|
124
|
+
const { rename } = await nodeFsPromises();
|
|
103
125
|
await rename(src, dest);
|
|
104
126
|
}
|
|
105
127
|
|
|
106
128
|
createLogStream(path: string): LogStream {
|
|
107
|
-
|
|
108
|
-
|
|
129
|
+
return new LazyNodeLogStream(path);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export class NodeSyncFileSystem implements SyncFileSystem {
|
|
134
|
+
readFile(path: string): string {
|
|
135
|
+
return readFileSync(path, 'utf-8');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
writeFile(path: string, content: string): void {
|
|
139
|
+
ensureDirForFileSync(path, this);
|
|
140
|
+
writeFileSync(path, content, 'utf-8');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
mkdir(path: string): void {
|
|
144
|
+
mkdirSync(path, { recursive: true });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
readDir(path: string): string[] {
|
|
148
|
+
return readdirSync(path);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
unlink(path: string): void {
|
|
152
|
+
rmSync(path, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
class LazyNodeLogStream implements LogStream {
|
|
157
|
+
private readonly ready: Promise<{
|
|
158
|
+
write: (chunk: string) => void;
|
|
159
|
+
end: () => void;
|
|
160
|
+
}>;
|
|
161
|
+
private ended = false;
|
|
162
|
+
// Single serialized chain: every write/end is appended here, so the underlying stream observes
|
|
163
|
+
// them in call order regardless of how the resolving microtasks interleave. A per-write `shift()`
|
|
164
|
+
// off a shared buffer (the previous approach) could reorder writes that arrived in the same tick.
|
|
165
|
+
private tail: Promise<unknown>;
|
|
166
|
+
|
|
167
|
+
constructor(path: string) {
|
|
168
|
+
this.ready = nodeFs().then(({ createWriteStream, mkdirSync }) => {
|
|
169
|
+
mkdirSync(dirnamePath(path), { recursive: true });
|
|
170
|
+
const stream = createWriteStream(path, { flags: 'a' });
|
|
171
|
+
return {
|
|
172
|
+
write: (chunk: string) => stream.write(chunk),
|
|
173
|
+
end: () => stream.end(),
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
this.tail = this.ready;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
write(chunk: string): void {
|
|
180
|
+
if (this.ended) return;
|
|
181
|
+
this.tail = this.tail.then(() => this.ready.then((stream) => stream.write(chunk)));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
end(): void {
|
|
185
|
+
if (this.ended) return;
|
|
186
|
+
this.ended = true;
|
|
187
|
+
this.tail = this.tail.then(() => this.ready.then((stream) => stream.end()));
|
|
109
188
|
}
|
|
110
189
|
}
|
|
111
190
|
|
|
@@ -180,12 +259,16 @@ export function getFs(): FileSystem {
|
|
|
180
259
|
}
|
|
181
260
|
|
|
182
261
|
export async function ensureDirForFile(path: string, fs = getFs()): Promise<void> {
|
|
183
|
-
await fs.mkdir(
|
|
262
|
+
await fs.mkdir(dirnamePath(path));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function ensureDirForFileSync(path: string, fs: SyncFileSystem): void {
|
|
266
|
+
fs.mkdir(dirnamePath(path));
|
|
184
267
|
}
|
|
185
268
|
|
|
186
269
|
export async function atomicWriteFile(path: string, content: string, fs = getFs()): Promise<void> {
|
|
187
270
|
await ensureDirForFile(path, fs);
|
|
188
|
-
const tempPath = `${path}.${
|
|
271
|
+
const tempPath = `${path}.${getProcessPid()}.${uniqueToken()}.tmp`;
|
|
189
272
|
await fs.writeFile(tempPath, content);
|
|
190
273
|
await fs.rename(tempPath, path);
|
|
191
274
|
}
|
|
@@ -206,7 +289,7 @@ export async function walkDir(path: string, fs = getFs()): Promise<string[]> {
|
|
|
206
289
|
const entries = (await fs.readDir(path)).sort();
|
|
207
290
|
const result: string[] = [];
|
|
208
291
|
for (const entry of entries) {
|
|
209
|
-
const fullPath =
|
|
292
|
+
const fullPath = joinPath(path, entry);
|
|
210
293
|
const entryStat = await fs.stat(fullPath);
|
|
211
294
|
if (entryStat?.isDirectory()) {
|
|
212
295
|
result.push(...(await walkDir(fullPath, fs)));
|
|
@@ -217,13 +300,13 @@ export async function walkDir(path: string, fs = getFs()): Promise<string[]> {
|
|
|
217
300
|
return result;
|
|
218
301
|
}
|
|
219
302
|
|
|
220
|
-
export function getProjectRoot(startDir =
|
|
221
|
-
let current =
|
|
303
|
+
export function getProjectRoot(startDir = getProcessCwd()): string {
|
|
304
|
+
let current = resolvePath(startDir);
|
|
222
305
|
for (let i = 0; i < 12; i++) {
|
|
223
|
-
if (
|
|
306
|
+
if (hasBunFile(joinPath(current, 'bun.lock')) || hasBunFile(joinPath(current, 'package.json'))) {
|
|
224
307
|
return current;
|
|
225
308
|
}
|
|
226
|
-
const parent =
|
|
309
|
+
const parent = dirnamePath(current);
|
|
227
310
|
if (parent === current) return startDir;
|
|
228
311
|
current = parent;
|
|
229
312
|
}
|
|
@@ -231,9 +314,25 @@ export function getProjectRoot(startDir = process.cwd()): string {
|
|
|
231
314
|
}
|
|
232
315
|
|
|
233
316
|
export function resolveProjectPath(...segments: string[]): string {
|
|
234
|
-
return
|
|
317
|
+
return resolvePath(getProjectRoot(), ...segments);
|
|
235
318
|
}
|
|
236
319
|
|
|
237
320
|
export function createLogStream(path: string, fs = getFs()): LogStream {
|
|
238
321
|
return fs.createLogStream(path);
|
|
239
322
|
}
|
|
323
|
+
|
|
324
|
+
function hasBunFile(path: string): boolean {
|
|
325
|
+
const bun = (globalThis as { Bun?: { file: (path: string) => { size: number } } }).Bun;
|
|
326
|
+
if (bun === undefined) return false;
|
|
327
|
+
return bun.file(path).size !== 0;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function getProcessPid(): number {
|
|
331
|
+
return (globalThis as { process?: { pid?: number } }).process?.pid ?? 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Two writers to the same path in the same millisecond must not share a temp name, or one clobbers
|
|
335
|
+
// the other before rename. randomUUID disambiguates; Date.now keeps names sortable for debugging.
|
|
336
|
+
function uniqueToken(): string {
|
|
337
|
+
return `${Date.now()}.${crypto.randomUUID()}`;
|
|
338
|
+
}
|
package/src/index.ts
CHANGED
package/src/path.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Runtime-portable path math. Deliberately avoids `node:path` so the same logic works on
|
|
2
|
+
// `cloudflare-workers` (no `node:*`) as on node-bun (ADR-008). POSIX-style separators throughout;
|
|
3
|
+
// Windows drive paths (`C:/...`) are normalized and treated as absolute.
|
|
4
|
+
|
|
5
|
+
export function normalizeSeparators(path: string): string {
|
|
6
|
+
return path.replaceAll('\\', '/');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isAbsolutePath(path: string): boolean {
|
|
10
|
+
return path.startsWith('/') || /^[A-Za-z]:\//.test(normalizeSeparators(path));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function dirnamePath(path: string): string {
|
|
14
|
+
const input = normalizeSeparators(path);
|
|
15
|
+
if (/^\/+$/.test(input)) return '/';
|
|
16
|
+
const normalized = input.replace(/\/+$/, '');
|
|
17
|
+
if (normalized === '' || normalized === '/') return normalized || '.';
|
|
18
|
+
const index = normalized.lastIndexOf('/');
|
|
19
|
+
if (index < 0) return '.';
|
|
20
|
+
if (index === 0) return '/';
|
|
21
|
+
return normalized.slice(0, index);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function joinPath(...segments: string[]): string {
|
|
25
|
+
const filtered = segments.filter((segment) => segment.length > 0).map(normalizeSeparators);
|
|
26
|
+
if (filtered.length === 0) return '.';
|
|
27
|
+
const absolute = isAbsolutePath(filtered[0] ?? '');
|
|
28
|
+
const joined = filtered.join('/').replace(/\/+/g, '/');
|
|
29
|
+
return absolute ? joined : joined.replace(/^\//, '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolvePath(...segments: string[]): string {
|
|
33
|
+
const candidates = segments.length === 0 ? [getProcessCwd()] : segments;
|
|
34
|
+
let resolved = '';
|
|
35
|
+
for (const segment of candidates.map(normalizeSeparators)) {
|
|
36
|
+
if (segment.length === 0) continue;
|
|
37
|
+
resolved = isAbsolutePath(segment) ? segment : joinPath(resolved || getProcessCwd(), segment);
|
|
38
|
+
}
|
|
39
|
+
const parts: string[] = [];
|
|
40
|
+
const absolute = isAbsolutePath(resolved);
|
|
41
|
+
for (const part of resolved.split('/')) {
|
|
42
|
+
if (part === '' || part === '.') continue;
|
|
43
|
+
if (part === '..') {
|
|
44
|
+
parts.pop();
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
parts.push(part);
|
|
48
|
+
}
|
|
49
|
+
return `${absolute ? '/' : ''}${parts.join('/')}` || (absolute ? '/' : '.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getProcessCwd(): string {
|
|
53
|
+
return (globalThis as { process?: { cwd?: () => string } }).process?.cwd?.() ?? '/';
|
|
54
|
+
}
|
package/src/process-executor.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface ProcessOptions {
|
|
|
13
13
|
command: string;
|
|
14
14
|
args?: string[];
|
|
15
15
|
cwd?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Environment forwarded verbatim to the child process. Caller-controlled — when launching an
|
|
18
|
+
* untrusted command, pass an explicit allowlist rather than the parent's full environment, so
|
|
19
|
+
* inherited secrets are not leaked into the subprocess.
|
|
20
|
+
*/
|
|
16
21
|
env?: Record<string, string>;
|
|
17
22
|
timeout?: number;
|
|
18
23
|
/** Maximum output buffer size in bytes (maps to execa `maxBuffer`). */
|
|
@@ -36,6 +41,32 @@ export interface ProcessExecutor {
|
|
|
36
41
|
run(options: ProcessOptions): Promise<ProcessResult>;
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
export interface SyncProcessExecutor {
|
|
45
|
+
runSync(options: Omit<ProcessOptions, 'timeout'>): ProcessResult;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PipeProcessOptions {
|
|
49
|
+
command: string;
|
|
50
|
+
args?: string[];
|
|
51
|
+
cwd?: string;
|
|
52
|
+
/** Forwarded verbatim to the child — pass an allowlist for untrusted commands (see {@link ProcessOptions.env}). */
|
|
53
|
+
env?: Record<string, string>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PipeProcess {
|
|
57
|
+
readonly pid: number | null;
|
|
58
|
+
readonly stdout: ReadableStream<Uint8Array> | null;
|
|
59
|
+
readonly stderr: ReadableStream<Uint8Array> | null;
|
|
60
|
+
readonly exited: Promise<number | null>;
|
|
61
|
+
writeStdin(input: string | Uint8Array): void;
|
|
62
|
+
endStdin(): void;
|
|
63
|
+
kill(signal?: ProcessSignal): void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PipeProcessSpawner {
|
|
67
|
+
spawn(options: PipeProcessOptions): PipeProcess;
|
|
68
|
+
}
|
|
69
|
+
|
|
39
70
|
export class NodeProcessExecutor implements ProcessExecutor {
|
|
40
71
|
constructor(private readonly config: ProcessExecutorConfig = {}) {}
|
|
41
72
|
|
|
@@ -84,6 +115,95 @@ export class NodeProcessExecutor implements ProcessExecutor {
|
|
|
84
115
|
}
|
|
85
116
|
}
|
|
86
117
|
|
|
118
|
+
export class BunSyncProcessExecutor implements SyncProcessExecutor {
|
|
119
|
+
runSync(options: Omit<ProcessOptions, 'timeout'>): ProcessResult {
|
|
120
|
+
const args = options.args ?? [];
|
|
121
|
+
const startedAt = Date.now();
|
|
122
|
+
const result = Bun.spawnSync({
|
|
123
|
+
cmd: [options.command, ...args],
|
|
124
|
+
stdout: 'pipe',
|
|
125
|
+
stderr: 'pipe',
|
|
126
|
+
stdin: 'ignore',
|
|
127
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
128
|
+
...(options.env !== undefined ? { env: options.env } : {}),
|
|
129
|
+
});
|
|
130
|
+
if (options.rejectOnError === true && result.exitCode !== 0) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`${options.command} ${args.join(' ')} failed with exit code ${result.exitCode}: ${stripFinalNewline(
|
|
133
|
+
asString(result.stderr),
|
|
134
|
+
)}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
command: options.command,
|
|
139
|
+
args,
|
|
140
|
+
exitCode: result.exitCode,
|
|
141
|
+
stdout: stripFinalNewline(asString(result.stdout)),
|
|
142
|
+
stderr: stripFinalNewline(asString(result.stderr)),
|
|
143
|
+
durationMs: Date.now() - startedAt,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export class BunPipeProcessSpawner implements PipeProcessSpawner {
|
|
149
|
+
spawn(options: PipeProcessOptions): PipeProcess {
|
|
150
|
+
const subprocess = Bun.spawn({
|
|
151
|
+
cmd: [options.command, ...(options.args ?? [])],
|
|
152
|
+
stdin: 'pipe',
|
|
153
|
+
stdout: 'pipe',
|
|
154
|
+
stderr: 'pipe',
|
|
155
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
156
|
+
...(options.env !== undefined ? { env: options.env } : {}),
|
|
157
|
+
});
|
|
158
|
+
return new BunPipeProcess(subprocess);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type BunSubprocess = ReturnType<typeof Bun.spawn>;
|
|
163
|
+
type ProcessSignal = Parameters<BunSubprocess['kill']>[0];
|
|
164
|
+
type StdinSink = {
|
|
165
|
+
write: (data: string | Uint8Array) => unknown;
|
|
166
|
+
end?: () => unknown;
|
|
167
|
+
flush?: () => unknown;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
class BunPipeProcess implements PipeProcess {
|
|
171
|
+
private readonly writer: StdinSink;
|
|
172
|
+
|
|
173
|
+
constructor(private readonly subprocess: BunSubprocess) {
|
|
174
|
+
this.writer = subprocess.stdin as StdinSink;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
get pid(): number | null {
|
|
178
|
+
return this.subprocess.pid ?? null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
get stdout(): ReadableStream<Uint8Array> | null {
|
|
182
|
+
return isReadableStream(this.subprocess.stdout) ? this.subprocess.stdout : null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
get stderr(): ReadableStream<Uint8Array> | null {
|
|
186
|
+
return isReadableStream(this.subprocess.stderr) ? this.subprocess.stderr : null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
get exited(): Promise<number | null> {
|
|
190
|
+
return this.subprocess.exited;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
writeStdin(input: string | Uint8Array): void {
|
|
194
|
+
this.writer.write(input);
|
|
195
|
+
this.writer.flush?.();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
endStdin(): void {
|
|
199
|
+
this.writer.end?.();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
kill(signal?: ProcessSignal): void {
|
|
203
|
+
this.subprocess.kill(signal);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
87
207
|
function buildExecaOptions(opts: {
|
|
88
208
|
cwd: string | undefined;
|
|
89
209
|
env: Record<string, string> | undefined;
|
|
@@ -116,3 +236,11 @@ function asString(value: string | string[] | unknown[] | Uint8Array | undefined)
|
|
|
116
236
|
if (Array.isArray(value)) return value.map(String).join('');
|
|
117
237
|
return '';
|
|
118
238
|
}
|
|
239
|
+
|
|
240
|
+
function stripFinalNewline(value: string): string {
|
|
241
|
+
return value.endsWith('\r\n') ? value.slice(0, -2) : value.endsWith('\n') ? value.slice(0, -1) : value;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
|
|
245
|
+
return value instanceof ReadableStream;
|
|
246
|
+
}
|