@gobing-ai/ts-runtime 0.3.0 → 0.3.2
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 +234 -176
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/context.d.ts +28 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +45 -2
- package/dist/file-system-cf.d.ts +25 -0
- package/dist/file-system-cf.d.ts.map +1 -0
- package/dist/file-system-cf.js +59 -0
- package/dist/file-system-node.d.ts +29 -0
- package/dist/file-system-node.d.ts.map +1 -0
- package/dist/file-system-node.js +94 -0
- package/dist/file-system.d.ts +47 -0
- package/dist/file-system.d.ts.map +1 -0
- package/dist/file-system.js +0 -0
- package/dist/fs.d.ts +31 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +32 -19
- package/dist/index.d.ts +21 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +65 -4
- package/dist/platform.d.ts +12 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +41 -0
- package/dist/plugin/capability-registry.d.ts +35 -0
- package/dist/plugin/capability-registry.d.ts.map +1 -0
- package/dist/plugin/capability-registry.js +43 -0
- package/dist/plugin/extension-loader.d.ts +66 -0
- package/dist/plugin/extension-loader.d.ts.map +1 -0
- package/dist/plugin/extension-loader.js +47 -0
- package/dist/plugin/extension-path.d.ts +15 -0
- package/dist/plugin/extension-path.d.ts.map +1 -0
- package/dist/plugin/extension-path.js +20 -0
- package/dist/plugin/index.d.ts +4 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +3 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +1 -0
- package/dist/process-executor.d.ts +77 -19
- package/dist/process-executor.d.ts.map +1 -1
- package/dist/process-executor.js +209 -37
- package/dist/runtime-cf.d.ts +6 -0
- package/dist/runtime-cf.d.ts.map +1 -0
- package/dist/runtime-cf.js +33 -0
- package/dist/runtime-factory.d.ts +24 -0
- package/dist/runtime-factory.d.ts.map +1 -0
- package/dist/runtime-factory.js +0 -0
- package/dist/runtime-node-bun.d.ts +8 -0
- package/dist/runtime-node-bun.d.ts.map +1 -0
- package/dist/runtime-node-bun.js +67 -0
- package/dist/schema-validation.d.ts +16 -0
- package/dist/schema-validation.d.ts.map +1 -1
- package/dist/schema-validation.js +9 -4
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/config.ts +16 -4
- package/src/context.ts +58 -4
- package/src/file-system-cf.ts +74 -0
- package/src/file-system-node.ts +122 -0
- package/src/file-system.ts +55 -0
- package/src/fs.ts +35 -18
- package/src/index.ts +57 -2
- package/src/path.ts +68 -4
- package/src/platform.ts +47 -0
- package/src/plugin/capability-registry.ts +58 -0
- package/src/plugin/extension-loader.ts +105 -0
- package/src/plugin/extension-path.ts +20 -0
- package/src/plugin/index.ts +3 -0
- package/src/plugin.ts +1 -0
- package/src/process-executor.ts +296 -58
- package/src/runtime-cf.ts +44 -0
- package/src/runtime-factory.ts +28 -0
- package/src/runtime-node-bun.ts +83 -0
- package/src/schema-validation.ts +20 -4
- package/src/types.ts +4 -0
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plugin/index.js';
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
+
/** Controls how stdout/stderr is captured: buffered in memory or streamed to the caller's terminal. */
|
|
1
2
|
export type OutputPolicy = {
|
|
2
3
|
mode: 'buffered';
|
|
3
4
|
} | {
|
|
4
5
|
mode: 'stream';
|
|
5
6
|
isTTY?: boolean;
|
|
6
7
|
};
|
|
8
|
+
/** Shared configuration for a process executor (default timeout, output buffering, output policy). */
|
|
7
9
|
export interface ProcessExecutorConfig {
|
|
8
10
|
defaultTimeout?: number;
|
|
9
11
|
defaultMaxOutput?: number;
|
|
10
12
|
output?: OutputPolicy;
|
|
13
|
+
events?: ProcessEventSink;
|
|
14
|
+
tracer?: TracerPort;
|
|
11
15
|
}
|
|
16
|
+
/** Options for spawning a child process. */
|
|
12
17
|
export interface ProcessOptions {
|
|
13
18
|
command: string;
|
|
14
19
|
args?: string[];
|
|
15
20
|
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
|
-
*/
|
|
21
21
|
env?: Record<string, string>;
|
|
22
22
|
timeout?: number;
|
|
23
|
-
/** Maximum output buffer size in bytes (maps to execa `maxBuffer`). */
|
|
24
23
|
maxOutput?: number;
|
|
25
24
|
label?: string;
|
|
26
25
|
rejectOnError?: boolean;
|
|
27
26
|
forceBuffered?: boolean;
|
|
28
27
|
}
|
|
28
|
+
/** Result of a completed child process, including exit code, captured output, and duration. */
|
|
29
29
|
export interface ProcessResult {
|
|
30
30
|
command: string;
|
|
31
31
|
args: string[];
|
|
@@ -35,19 +35,46 @@ export interface ProcessResult {
|
|
|
35
35
|
signal?: string;
|
|
36
36
|
durationMs: number;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
/** Reason a process completion event was emitted. */
|
|
39
|
+
export type ProcessExitReason = 'exit' | 'signal' | 'timeout' | 'error';
|
|
40
|
+
/** Payload emitted for process execution observability. */
|
|
41
|
+
export interface ProcessEventDetail {
|
|
42
|
+
command: string;
|
|
43
|
+
args: string[];
|
|
44
|
+
exitCode: number | null;
|
|
45
|
+
signal?: string;
|
|
46
|
+
durationMs: number;
|
|
47
|
+
reason: ProcessExitReason;
|
|
48
|
+
timestamp: string;
|
|
49
|
+
label?: string;
|
|
50
|
+
error?: string;
|
|
40
51
|
}
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
/** Zero-dependency structural event sink for process observability. */
|
|
53
|
+
export interface ProcessEventSink {
|
|
54
|
+
emit(event: 'process.started' | 'process.exited', detail: ProcessEventDetail): void;
|
|
55
|
+
}
|
|
56
|
+
/** Typed process event map, consumable by `EventBus<ProcessEvents>` in higher layers. */
|
|
57
|
+
export type ProcessEvents = {
|
|
58
|
+
'process.started': (detail: ProcessEventDetail) => void;
|
|
59
|
+
'process.exited': (detail: ProcessEventDetail) => void;
|
|
60
|
+
};
|
|
61
|
+
/** Minimal structural tracing port; concrete adapters live above `ts-runtime`. */
|
|
62
|
+
export interface TracerPort {
|
|
63
|
+
traceAsync<T>(name: string, fn: (span: unknown) => Promise<T>): Promise<T>;
|
|
43
64
|
}
|
|
65
|
+
/** Options for spawning a long-running interactive process. */
|
|
44
66
|
export interface PipeProcessOptions {
|
|
45
67
|
command: string;
|
|
46
68
|
args?: string[];
|
|
47
69
|
cwd?: string;
|
|
48
|
-
/** Forwarded verbatim to the child — pass an allowlist for untrusted commands (see {@link ProcessOptions.env}). */
|
|
49
70
|
env?: Record<string, string>;
|
|
71
|
+
label?: string;
|
|
50
72
|
}
|
|
73
|
+
/** Signal values accepted by subprocess kill. */
|
|
74
|
+
type BunSubprocess = ReturnType<typeof Bun.spawn>;
|
|
75
|
+
/** Signal values accepted by subprocess kill (e.g. 'SIGTERM', 'SIGKILL'). */
|
|
76
|
+
export type ProcessSignal = Parameters<BunSubprocess['kill']>[0];
|
|
77
|
+
/** Handle to a running pipe process with streaming stdout/stderr and stdin write support. */
|
|
51
78
|
export interface PipeProcess {
|
|
52
79
|
readonly pid: number | null;
|
|
53
80
|
readonly stdout: ReadableStream<Uint8Array> | null;
|
|
@@ -57,21 +84,52 @@ export interface PipeProcess {
|
|
|
57
84
|
endStdin(): void;
|
|
58
85
|
kill(signal?: ProcessSignal): void;
|
|
59
86
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Runtime-agnostic process executor wrapping `execa`.
|
|
89
|
+
*
|
|
90
|
+
* Every invocation supports timeout enforcement, output capture, and
|
|
91
|
+
* configurable output policy (buffered vs streamed).
|
|
92
|
+
*/
|
|
93
|
+
export declare class ProcessExecutor {
|
|
64
94
|
private readonly config;
|
|
65
95
|
constructor(config?: ProcessExecutorConfig);
|
|
96
|
+
/**
|
|
97
|
+
* Run a command, buffered by default. Returns a structured {@link ProcessResult}.
|
|
98
|
+
* Does NOT throw on non-zero exit codes unless `rejectOnError` is set.
|
|
99
|
+
*/
|
|
66
100
|
run(options: ProcessOptions): Promise<ProcessResult>;
|
|
101
|
+
private runUntraced;
|
|
102
|
+
/**
|
|
103
|
+
* Spawn a long-running interactive process with streaming I/O.
|
|
104
|
+
*
|
|
105
|
+
* Uses `Bun.spawn` for bidirectional pipe communication (stdin write,
|
|
106
|
+
* stdout/stderr as ReadableStreams). Returns a {@link PipeProcess} handle.
|
|
107
|
+
*/
|
|
108
|
+
runStreaming(options: PipeProcessOptions): PipeProcess;
|
|
109
|
+
private trace;
|
|
110
|
+
private emitExitedFromResult;
|
|
111
|
+
private emitProcessEvent;
|
|
67
112
|
}
|
|
68
|
-
|
|
113
|
+
/**
|
|
114
|
+
* @deprecated Use {@link ProcessExecutor} directly.
|
|
115
|
+
* This subclass is kept for backward compatibility.
|
|
116
|
+
*/
|
|
117
|
+
export declare class NodeProcessExecutor extends ProcessExecutor {
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* @deprecated Use `Bun.spawnSync` or `child_process.spawnSync` directly.
|
|
121
|
+
* Synchronous process execution is no longer recommended from ts-runtime.
|
|
122
|
+
* This class is kept for backward compatibility.
|
|
123
|
+
*/
|
|
124
|
+
export declare class BunSyncProcessExecutor {
|
|
69
125
|
runSync(options: Omit<ProcessOptions, 'timeout'>): ProcessResult;
|
|
70
126
|
}
|
|
71
|
-
|
|
127
|
+
/**
|
|
128
|
+
* @deprecated Use {@link ProcessExecutor.runStreaming} instead.
|
|
129
|
+
* This class is kept for backward compatibility.
|
|
130
|
+
*/
|
|
131
|
+
export declare class BunPipeProcessSpawner {
|
|
72
132
|
spawn(options: PipeProcessOptions): PipeProcess;
|
|
73
133
|
}
|
|
74
|
-
type BunSubprocess = ReturnType<typeof Bun.spawn>;
|
|
75
|
-
type ProcessSignal = Parameters<BunSubprocess['kill']>[0];
|
|
76
134
|
export {};
|
|
77
135
|
//# sourceMappingURL=process-executor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-executor.d.ts","sourceRoot":"","sources":["../src/process-executor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"process-executor.d.ts","sourceRoot":"","sources":["../src/process-executor.ts"],"names":[],"mappings":"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"}
|
package/dist/process-executor.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { isatty } from 'node:tty';
|
|
2
2
|
import { execa } from 'execa';
|
|
3
|
-
|
|
3
|
+
// ── ProcessExecutor ───────────────────────────────────────────────────────
|
|
4
|
+
/**
|
|
5
|
+
* Runtime-agnostic process executor wrapping `execa`.
|
|
6
|
+
*
|
|
7
|
+
* Every invocation supports timeout enforcement, output capture, and
|
|
8
|
+
* configurable output policy (buffered vs streamed).
|
|
9
|
+
*/
|
|
10
|
+
export class ProcessExecutor {
|
|
4
11
|
config;
|
|
5
12
|
constructor(config = {}) {
|
|
6
13
|
this.config = config;
|
|
7
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Run a command, buffered by default. Returns a structured {@link ProcessResult}.
|
|
17
|
+
* Does NOT throw on non-zero exit codes unless `rejectOnError` is set.
|
|
18
|
+
*/
|
|
8
19
|
async run(options) {
|
|
20
|
+
return this.trace('process.run', () => this.runUntraced(options));
|
|
21
|
+
}
|
|
22
|
+
async runUntraced(options) {
|
|
9
23
|
const args = options.args ?? [];
|
|
10
24
|
const execaOptions = buildExecaOptions({
|
|
11
25
|
cwd: options.cwd,
|
|
@@ -16,9 +30,19 @@ export class NodeProcessExecutor {
|
|
|
16
30
|
outputPolicy: this.config.output,
|
|
17
31
|
forceBuffered: options.forceBuffered ?? false,
|
|
18
32
|
});
|
|
33
|
+
const startedAt = Date.now();
|
|
34
|
+
this.emitProcessEvent('process.started', {
|
|
35
|
+
command: options.command,
|
|
36
|
+
args,
|
|
37
|
+
exitCode: null,
|
|
38
|
+
durationMs: 0,
|
|
39
|
+
reason: 'exit',
|
|
40
|
+
timestamp: new Date(startedAt).toISOString(),
|
|
41
|
+
...(options.label !== undefined ? { label: options.label } : {}),
|
|
42
|
+
});
|
|
19
43
|
try {
|
|
20
44
|
const result = await execa(options.command, args, execaOptions);
|
|
21
|
-
|
|
45
|
+
const processResult = {
|
|
22
46
|
command: options.command,
|
|
23
47
|
args,
|
|
24
48
|
exitCode: result.exitCode ?? null,
|
|
@@ -27,23 +51,190 @@ export class NodeProcessExecutor {
|
|
|
27
51
|
...(result.signalDescription !== undefined ? { signal: result.signalDescription } : {}),
|
|
28
52
|
durationMs: result.durationMs,
|
|
29
53
|
};
|
|
54
|
+
this.emitExitedFromResult(options, processResult, result);
|
|
55
|
+
return processResult;
|
|
30
56
|
}
|
|
31
57
|
catch (error) {
|
|
32
|
-
if (options.rejectOnError)
|
|
33
|
-
throw error;
|
|
34
58
|
const failed = error;
|
|
35
|
-
|
|
59
|
+
const processResult = {
|
|
36
60
|
command: options.command,
|
|
37
61
|
args,
|
|
38
62
|
exitCode: failed.exitCode ?? null,
|
|
39
63
|
stdout: asString(failed.stdout),
|
|
40
64
|
stderr: asString(failed.stderr),
|
|
41
|
-
...(failed.signalDescription !== undefined
|
|
42
|
-
|
|
65
|
+
...(failed.signalDescription !== undefined
|
|
66
|
+
? { signal: failed.signalDescription }
|
|
67
|
+
: failed.signal !== undefined
|
|
68
|
+
? { signal: failed.signal }
|
|
69
|
+
: {}),
|
|
70
|
+
durationMs: failed.durationMs ?? Date.now() - startedAt,
|
|
43
71
|
};
|
|
72
|
+
this.emitExitedFromResult(options, processResult, error, error);
|
|
73
|
+
if (options.rejectOnError)
|
|
74
|
+
throw error;
|
|
75
|
+
return processResult;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Spawn a long-running interactive process with streaming I/O.
|
|
80
|
+
*
|
|
81
|
+
* Uses `Bun.spawn` for bidirectional pipe communication (stdin write,
|
|
82
|
+
* stdout/stderr as ReadableStreams). Returns a {@link PipeProcess} handle.
|
|
83
|
+
*/
|
|
84
|
+
runStreaming(options) {
|
|
85
|
+
const args = options.args ?? [];
|
|
86
|
+
void this.config.tracer?.traceAsync('process.runStreaming', async () => undefined).catch(() => undefined);
|
|
87
|
+
try {
|
|
88
|
+
const startedAt = Date.now();
|
|
89
|
+
this.emitProcessEvent('process.started', {
|
|
90
|
+
command: options.command,
|
|
91
|
+
args,
|
|
92
|
+
exitCode: null,
|
|
93
|
+
durationMs: 0,
|
|
94
|
+
reason: 'exit',
|
|
95
|
+
timestamp: new Date(startedAt).toISOString(),
|
|
96
|
+
...(options.label !== undefined ? { label: options.label } : {}),
|
|
97
|
+
});
|
|
98
|
+
const subprocess = Bun.spawn({
|
|
99
|
+
cmd: [options.command, ...args],
|
|
100
|
+
stdin: 'pipe',
|
|
101
|
+
stdout: 'pipe',
|
|
102
|
+
stderr: 'pipe',
|
|
103
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
104
|
+
...(options.env !== undefined ? { env: options.env } : {}),
|
|
105
|
+
});
|
|
106
|
+
return new ObservedPipeProcess(new BunPipeProcess(subprocess), this.config.events, {
|
|
107
|
+
command: options.command,
|
|
108
|
+
args,
|
|
109
|
+
startedAt,
|
|
110
|
+
...(options.label !== undefined ? { label: options.label } : {}),
|
|
111
|
+
});
|
|
44
112
|
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
this.emitProcessEvent('process.exited', {
|
|
115
|
+
command: options.command,
|
|
116
|
+
args,
|
|
117
|
+
exitCode: null,
|
|
118
|
+
durationMs: 0,
|
|
119
|
+
reason: 'error',
|
|
120
|
+
timestamp: new Date().toISOString(),
|
|
121
|
+
...(options.label !== undefined ? { label: options.label } : {}),
|
|
122
|
+
error: errorMessage(error),
|
|
123
|
+
});
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async trace(name, fn) {
|
|
128
|
+
if (this.config.tracer === undefined)
|
|
129
|
+
return await fn();
|
|
130
|
+
return await this.config.tracer.traceAsync(name, async () => await fn());
|
|
131
|
+
}
|
|
132
|
+
emitExitedFromResult(options, result, completion, error) {
|
|
133
|
+
const reason = isTimedOut(completion)
|
|
134
|
+
? 'timeout'
|
|
135
|
+
: result.signal !== undefined
|
|
136
|
+
? 'signal'
|
|
137
|
+
: error
|
|
138
|
+
? 'error'
|
|
139
|
+
: 'exit';
|
|
140
|
+
this.emitProcessEvent('process.exited', {
|
|
141
|
+
command: result.command,
|
|
142
|
+
args: result.args,
|
|
143
|
+
exitCode: result.exitCode,
|
|
144
|
+
...(result.signal !== undefined ? { signal: result.signal } : {}),
|
|
145
|
+
durationMs: result.durationMs,
|
|
146
|
+
reason,
|
|
147
|
+
timestamp: new Date().toISOString(),
|
|
148
|
+
...(options.label !== undefined ? { label: options.label } : {}),
|
|
149
|
+
...(error !== undefined ? { error: errorMessage(error) } : {}),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
emitProcessEvent(event, detail) {
|
|
153
|
+
this.config.events?.emit(event, detail);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
class ObservedPipeProcess {
|
|
157
|
+
inner;
|
|
158
|
+
killedWith;
|
|
159
|
+
exited;
|
|
160
|
+
constructor(inner, events, context) {
|
|
161
|
+
this.inner = inner;
|
|
162
|
+
this.exited = inner.exited.then((exitCode) => {
|
|
163
|
+
events?.emit('process.exited', {
|
|
164
|
+
command: context.command,
|
|
165
|
+
args: context.args,
|
|
166
|
+
exitCode,
|
|
167
|
+
...(this.killedWith !== undefined ? { signal: String(this.killedWith) } : {}),
|
|
168
|
+
durationMs: Date.now() - context.startedAt,
|
|
169
|
+
reason: this.killedWith !== undefined ? 'signal' : 'exit',
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
...(context.label !== undefined ? { label: context.label } : {}),
|
|
172
|
+
});
|
|
173
|
+
return exitCode;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
get pid() {
|
|
177
|
+
return this.inner.pid;
|
|
178
|
+
}
|
|
179
|
+
get stdout() {
|
|
180
|
+
return this.inner.stdout;
|
|
181
|
+
}
|
|
182
|
+
get stderr() {
|
|
183
|
+
return this.inner.stderr;
|
|
184
|
+
}
|
|
185
|
+
writeStdin(input) {
|
|
186
|
+
this.inner.writeStdin(input);
|
|
187
|
+
}
|
|
188
|
+
endStdin() {
|
|
189
|
+
this.inner.endStdin();
|
|
190
|
+
}
|
|
191
|
+
kill(signal) {
|
|
192
|
+
this.killedWith = signal;
|
|
193
|
+
this.inner.kill(signal);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
class BunPipeProcess {
|
|
197
|
+
subprocess;
|
|
198
|
+
writer;
|
|
199
|
+
constructor(subprocess) {
|
|
200
|
+
this.subprocess = subprocess;
|
|
201
|
+
this.writer = subprocess.stdin;
|
|
202
|
+
}
|
|
203
|
+
get pid() {
|
|
204
|
+
return this.subprocess.pid ?? null;
|
|
45
205
|
}
|
|
206
|
+
get stdout() {
|
|
207
|
+
return isReadableStream(this.subprocess.stdout) ? this.subprocess.stdout : null;
|
|
208
|
+
}
|
|
209
|
+
get stderr() {
|
|
210
|
+
return isReadableStream(this.subprocess.stderr) ? this.subprocess.stderr : null;
|
|
211
|
+
}
|
|
212
|
+
get exited() {
|
|
213
|
+
return this.subprocess.exited;
|
|
214
|
+
}
|
|
215
|
+
writeStdin(input) {
|
|
216
|
+
this.writer.write(input);
|
|
217
|
+
this.writer.flush?.();
|
|
218
|
+
}
|
|
219
|
+
endStdin() {
|
|
220
|
+
this.writer.end?.();
|
|
221
|
+
}
|
|
222
|
+
kill(signal) {
|
|
223
|
+
this.subprocess.kill(signal);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ── Deprecated backward-compatible subclasses ─────────────────────────────
|
|
227
|
+
/**
|
|
228
|
+
* @deprecated Use {@link ProcessExecutor} directly.
|
|
229
|
+
* This subclass is kept for backward compatibility.
|
|
230
|
+
*/
|
|
231
|
+
export class NodeProcessExecutor extends ProcessExecutor {
|
|
46
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* @deprecated Use `Bun.spawnSync` or `child_process.spawnSync` directly.
|
|
235
|
+
* Synchronous process execution is no longer recommended from ts-runtime.
|
|
236
|
+
* This class is kept for backward compatibility.
|
|
237
|
+
*/
|
|
47
238
|
export class BunSyncProcessExecutor {
|
|
48
239
|
runSync(options) {
|
|
49
240
|
const args = options.args ?? [];
|
|
@@ -69,6 +260,10 @@ export class BunSyncProcessExecutor {
|
|
|
69
260
|
};
|
|
70
261
|
}
|
|
71
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* @deprecated Use {@link ProcessExecutor.runStreaming} instead.
|
|
265
|
+
* This class is kept for backward compatibility.
|
|
266
|
+
*/
|
|
72
267
|
export class BunPipeProcessSpawner {
|
|
73
268
|
spawn(options) {
|
|
74
269
|
const subprocess = Bun.spawn({
|
|
@@ -82,36 +277,7 @@ export class BunPipeProcessSpawner {
|
|
|
82
277
|
return new BunPipeProcess(subprocess);
|
|
83
278
|
}
|
|
84
279
|
}
|
|
85
|
-
|
|
86
|
-
subprocess;
|
|
87
|
-
writer;
|
|
88
|
-
constructor(subprocess) {
|
|
89
|
-
this.subprocess = subprocess;
|
|
90
|
-
this.writer = subprocess.stdin;
|
|
91
|
-
}
|
|
92
|
-
get pid() {
|
|
93
|
-
return this.subprocess.pid ?? null;
|
|
94
|
-
}
|
|
95
|
-
get stdout() {
|
|
96
|
-
return isReadableStream(this.subprocess.stdout) ? this.subprocess.stdout : null;
|
|
97
|
-
}
|
|
98
|
-
get stderr() {
|
|
99
|
-
return isReadableStream(this.subprocess.stderr) ? this.subprocess.stderr : null;
|
|
100
|
-
}
|
|
101
|
-
get exited() {
|
|
102
|
-
return this.subprocess.exited;
|
|
103
|
-
}
|
|
104
|
-
writeStdin(input) {
|
|
105
|
-
this.writer.write(input);
|
|
106
|
-
this.writer.flush?.();
|
|
107
|
-
}
|
|
108
|
-
endStdin() {
|
|
109
|
-
this.writer.end?.();
|
|
110
|
-
}
|
|
111
|
-
kill(signal) {
|
|
112
|
-
this.subprocess.kill(signal);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
280
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
115
281
|
function buildExecaOptions(opts) {
|
|
116
282
|
const canStream = !opts.forceBuffered &&
|
|
117
283
|
opts.outputPolicy?.mode === 'stream' &&
|
|
@@ -142,3 +308,9 @@ function stripFinalNewline(value) {
|
|
|
142
308
|
function isReadableStream(value) {
|
|
143
309
|
return value instanceof ReadableStream;
|
|
144
310
|
}
|
|
311
|
+
function isTimedOut(error) {
|
|
312
|
+
return typeof error === 'object' && error !== null && 'timedOut' in error && error.timedOut === true;
|
|
313
|
+
}
|
|
314
|
+
function errorMessage(error) {
|
|
315
|
+
return error instanceof Error ? error.message : String(error);
|
|
316
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-cf.d.ts","sourceRoot":"","sources":["../src/runtime-cf.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAMxD;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,cA6BtC,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parse as parseYaml } from 'yaml';
|
|
2
|
+
import { buildConfigFromObject } from './config.js';
|
|
3
|
+
import { createCfFileSystem } from './file-system-cf.js';
|
|
4
|
+
/** Binding name for the YAML config text blob set in wrangler.toml. */
|
|
5
|
+
const CONFIG_YAML_BINDING = 'CONFIG_YAML';
|
|
6
|
+
/**
|
|
7
|
+
* Cloudflare Workers runtime factory (no filesystem, no process execution).
|
|
8
|
+
*/
|
|
9
|
+
export const cloudflareWorkersFactory = {
|
|
10
|
+
runtimeName: 'cloudflare-workers',
|
|
11
|
+
capabilities: {
|
|
12
|
+
hasFilesystem: false,
|
|
13
|
+
hasProcessExecution: false,
|
|
14
|
+
hasPersistentStorage: false,
|
|
15
|
+
},
|
|
16
|
+
createFileSystem: () => createCfFileSystem(),
|
|
17
|
+
createProcessExecutor: (_config) => {
|
|
18
|
+
throw new Error('ProcessExecutor is not available on Cloudflare Workers.');
|
|
19
|
+
},
|
|
20
|
+
async loadConfig(options) {
|
|
21
|
+
const yamlString = options?.envBindings?.[CONFIG_YAML_BINDING];
|
|
22
|
+
let raw = {};
|
|
23
|
+
if (yamlString) {
|
|
24
|
+
try {
|
|
25
|
+
raw = parseYaml(yamlString);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Fall through to schema defaults on parse failure
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return buildConfigFromObject(raw, options);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Config } from './config';
|
|
2
|
+
import type { FileSystem } from './file-system';
|
|
3
|
+
import type { ProcessExecutor, ProcessExecutorConfig } from './process-executor';
|
|
4
|
+
import type { LoadConfigOptions, RuntimeCapabilities, RuntimeName } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Abstract factory for creating runtime-aware infrastructure.
|
|
7
|
+
*
|
|
8
|
+
* Each runtime environment (Node/Bun, Cloudflare Workers) provides its own
|
|
9
|
+
* implementation. Consumers call {@link loadRuntimeFactory} to get the
|
|
10
|
+
* appropriate factory, then use it to create FileSystem, ProcessExecutor,
|
|
11
|
+
* and load configuration.
|
|
12
|
+
*/
|
|
13
|
+
export interface RuntimeFactory {
|
|
14
|
+
/** Stable runtime identity owned by the factory implementation. */
|
|
15
|
+
readonly runtimeName: RuntimeName;
|
|
16
|
+
readonly capabilities: RuntimeCapabilities;
|
|
17
|
+
/** Create a runtime-specific file system (real fs for Node/Bun, stub for CF Workers). */
|
|
18
|
+
createFileSystem(): FileSystem;
|
|
19
|
+
/** Create a runtime-specific process executor (execa-backed for Node/Bun, throws on CF Workers). */
|
|
20
|
+
createProcessExecutor(config?: ProcessExecutorConfig): ProcessExecutor;
|
|
21
|
+
/** Load config from the runtime's config backend. */
|
|
22
|
+
loadConfig(options?: LoadConfigOptions): Promise<Config>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=runtime-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-factory.d.ts","sourceRoot":"","sources":["../src/runtime-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEnF;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAElC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAE3C,yFAAyF;IACzF,gBAAgB,IAAI,UAAU,CAAC;IAE/B,oGAAoG;IACpG,qBAAqB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,eAAe,CAAC;IAEvE,qDAAqD;IACrD,UAAU,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5D"}
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RuntimeFactory } from './runtime-factory';
|
|
2
|
+
/** @internal — reset the cached filesystem (for test isolation). */
|
|
3
|
+
export declare function _resetNodeFileSystem(): void;
|
|
4
|
+
/**
|
|
5
|
+
* Node.js / Bun runtime factory (filesystem, process execution, config).
|
|
6
|
+
*/
|
|
7
|
+
export declare const nodeBunFactory: RuntimeFactory;
|
|
8
|
+
//# sourceMappingURL=runtime-node-bun.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-node-bun.d.ts","sourceRoot":"","sources":["../src/runtime-node-bun.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAUxD,oEAAoE;AACpE,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAgB5B,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { parse as parseYaml } from 'yaml';
|
|
2
|
+
import { buildConfigFromObject, getProcessEnv } from './config.js';
|
|
3
|
+
import { createNodeFileSystem } from './file-system-node.js';
|
|
4
|
+
import { ProcessExecutor } from './process-executor.js';
|
|
5
|
+
// Lazy re-initialisable singleton for test isolation.
|
|
6
|
+
let _nodeFileSystem;
|
|
7
|
+
function getNodeFileSystem() {
|
|
8
|
+
if (!_nodeFileSystem)
|
|
9
|
+
_nodeFileSystem = createNodeFileSystem();
|
|
10
|
+
return _nodeFileSystem;
|
|
11
|
+
}
|
|
12
|
+
/** @internal — reset the cached filesystem (for test isolation). */
|
|
13
|
+
export function _resetNodeFileSystem() {
|
|
14
|
+
_nodeFileSystem = undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Node.js / Bun runtime factory (filesystem, process execution, config).
|
|
18
|
+
*/
|
|
19
|
+
export const nodeBunFactory = {
|
|
20
|
+
runtimeName: 'node-bun',
|
|
21
|
+
capabilities: {
|
|
22
|
+
hasFilesystem: true,
|
|
23
|
+
hasProcessExecution: true,
|
|
24
|
+
hasPersistentStorage: true,
|
|
25
|
+
},
|
|
26
|
+
createFileSystem: () => getNodeFileSystem(),
|
|
27
|
+
createProcessExecutor: (config) => new ProcessExecutor(config),
|
|
28
|
+
async loadConfig(options) {
|
|
29
|
+
return loadNodeConfig(options);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
// ── Config loading (Node/Bun) ────────────────────────────────────────────
|
|
33
|
+
/**
|
|
34
|
+
* Load config for Node/Bun: reads YAML from filesystem via {@link FileSystem},
|
|
35
|
+
* applies env-variable interpolation, merges overrides, and validates.
|
|
36
|
+
*/
|
|
37
|
+
async function loadNodeConfig(options) {
|
|
38
|
+
const fs = getNodeFileSystem();
|
|
39
|
+
const raw = readYamlConfig(fs);
|
|
40
|
+
return buildConfigFromObject(raw ?? {}, options);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read and parse YAML config from filesystem.
|
|
44
|
+
*
|
|
45
|
+
* Searches common config locations in order:
|
|
46
|
+
* 1. `CONFIG_PATH` env var (absolute path)
|
|
47
|
+
* 2. `./config/config.yaml` (repo-local)
|
|
48
|
+
* 3. `./config/config.example.yaml` (fallback)
|
|
49
|
+
*
|
|
50
|
+
* Returns `null` if no config file is found.
|
|
51
|
+
*/
|
|
52
|
+
function readYamlConfig(fs) {
|
|
53
|
+
const candidates = [getProcessEnv().CONFIG_PATH, 'config/config.yaml', 'config/config.example.yaml'].filter((p) => typeof p === 'string' && p.length > 0);
|
|
54
|
+
for (const candidate of candidates) {
|
|
55
|
+
const resolved = fs.resolve(candidate);
|
|
56
|
+
if (fs.exists(resolved)) {
|
|
57
|
+
const content = fs.readFile(resolved);
|
|
58
|
+
try {
|
|
59
|
+
return parseYaml(content);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Try next candidate on parse failure
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|