@gobing-ai/ts-runtime 0.3.1 → 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/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 +2 -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/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/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
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { FileSystem } from './file-system';
|
|
2
|
+
/** A single JSON Schema validation failure — records the JSON pointer path and a human-readable message. */
|
|
1
3
|
export interface JsonSchemaViolation {
|
|
2
4
|
path: string;
|
|
3
5
|
message: string;
|
|
4
6
|
}
|
|
7
|
+
/** Subset of JSON Schema 2020-12 keywords used for runtime configuration validation. */
|
|
5
8
|
export interface JsonSchema {
|
|
6
9
|
type?: string | string[];
|
|
7
10
|
required?: string[];
|
|
@@ -15,6 +18,7 @@ export interface JsonSchema {
|
|
|
15
18
|
$ref?: string;
|
|
16
19
|
$defs?: Record<string, JsonSchema>;
|
|
17
20
|
}
|
|
21
|
+
/** Options for loading and validating structured configuration files (YAML/JSON with `$schema`). */
|
|
18
22
|
export interface StructuredConfigLoadOptions {
|
|
19
23
|
validateSchema?: boolean;
|
|
20
24
|
/**
|
|
@@ -30,13 +34,25 @@ export interface StructuredConfigLoadOptions {
|
|
|
30
34
|
* Injectable for testing.
|
|
31
35
|
*/
|
|
32
36
|
resolve?: (specifier: string, from: string) => string;
|
|
37
|
+
/**
|
|
38
|
+
* File system used to read the config and local schema files. Defaults to the
|
|
39
|
+
* deprecated `getFs()` global; supply a {@link FileSystem} from the runtime factory
|
|
40
|
+
* (`createNodeFileSystem()`) to route reads through the factory path or to inject a
|
|
41
|
+
* virtual file system in tests.
|
|
42
|
+
*/
|
|
43
|
+
fileSystem?: Pick<FileSystem, 'readFile'>;
|
|
33
44
|
}
|
|
45
|
+
/** Error thrown when structured config validation fails, carrying the list of {@link JsonSchemaViolation}s. */
|
|
34
46
|
export declare class StructuredConfigSchemaError extends Error {
|
|
35
47
|
readonly violations: readonly JsonSchemaViolation[];
|
|
36
48
|
constructor(message: string, violations?: readonly JsonSchemaViolation[]);
|
|
37
49
|
}
|
|
50
|
+
/** Reads a config file from disk, parses it (YAML or JSON), and validates against its declared `$schema`. */
|
|
38
51
|
export declare function loadStructuredConfig(path: string, options?: StructuredConfigLoadOptions): Promise<unknown>;
|
|
52
|
+
/** Parses a config string (YAML or JSON) and validates against its declared `$schema` if present. */
|
|
39
53
|
export declare function parseStructuredConfig(content: string, source: string, options?: StructuredConfigLoadOptions): Promise<unknown>;
|
|
54
|
+
/** Extracts the `$schema` reference from a parsed config object, resolves and fetches the schema, then validates. */
|
|
40
55
|
export declare function validateDeclaredJsonSchema(value: unknown, source: string, options?: StructuredConfigLoadOptions): Promise<void>;
|
|
56
|
+
/** Validates a value against a {@link JsonSchema}, returning a list of {@link JsonSchemaViolation}s. Does not throw. */
|
|
41
57
|
export declare function validateJsonSchema(value: unknown, schema: JsonSchema, path?: string, defs?: Record<string, JsonSchema>, seenRefs?: ReadonlySet<string>): JsonSchemaViolation[];
|
|
42
58
|
//# sourceMappingURL=schema-validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-validation.d.ts","sourceRoot":"","sources":["../src/schema-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema-validation.d.ts","sourceRoot":"","sources":["../src/schema-validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAUhD,4GAA4G;AAC5G,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,oBAAoB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAC5C,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACtC;AAED,oGAAoG;AACpG,MAAM,WAAW,2BAA2B;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACtD;;;;;OAKG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;CAC7C;AAED,+GAA+G;AAC/G,qBAAa,2BAA4B,SAAQ,KAAK;IAG9C,QAAQ,CAAC,UAAU,EAAE,SAAS,mBAAmB,EAAE;gBADnD,OAAO,EAAE,MAAM,EACN,UAAU,GAAE,SAAS,mBAAmB,EAAO;CAK/D;AAED,6GAA6G;AAC7G,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAAC,OAAO,CAAC,CAGpH;AAED,qGAAqG;AACrG,wBAAsB,qBAAqB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,qHAAqH;AACrH,wBAAsB,0BAA0B,CAC5C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED,wHAAwH;AACxH,wBAAgB,kBAAkB,CAC9B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,IAAI,SAAK,EACT,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAM,EACrC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GAC1C,mBAAmB,EAAE,CAwDvB"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { parse as parseYaml } from 'yaml';
|
|
2
2
|
import { getFs } from './fs.js';
|
|
3
|
-
import { dirnamePath, isAbsolutePath, joinPath } from './path.js';
|
|
3
|
+
import { dirnamePath, getProcessCwd, isAbsolutePath, joinPath } from './path.js';
|
|
4
4
|
/** Default time budget for a single remote schema fetch. */
|
|
5
5
|
const REMOTE_SCHEMA_FETCH_TIMEOUT_MS = 5_000;
|
|
6
6
|
/** Upper bound on a remote schema body. A timeout alone lets a slow multi-GB drip exhaust memory. */
|
|
7
7
|
const REMOTE_SCHEMA_MAX_BYTES = 5 * 1024 * 1024;
|
|
8
|
+
/** Error thrown when structured config validation fails, carrying the list of {@link JsonSchemaViolation}s. */
|
|
8
9
|
export class StructuredConfigSchemaError extends Error {
|
|
9
10
|
violations;
|
|
10
11
|
constructor(message, violations = []) {
|
|
@@ -13,10 +14,12 @@ export class StructuredConfigSchemaError extends Error {
|
|
|
13
14
|
this.name = 'StructuredConfigSchemaError';
|
|
14
15
|
}
|
|
15
16
|
}
|
|
17
|
+
/** Reads a config file from disk, parses it (YAML or JSON), and validates against its declared `$schema`. */
|
|
16
18
|
export async function loadStructuredConfig(path, options = {}) {
|
|
17
|
-
const content = await getFs().readFile(path);
|
|
19
|
+
const content = await (options.fileSystem ?? getFs()).readFile(path);
|
|
18
20
|
return await parseStructuredConfig(content, path, options);
|
|
19
21
|
}
|
|
22
|
+
/** Parses a config string (YAML or JSON) and validates against its declared `$schema` if present. */
|
|
20
23
|
export async function parseStructuredConfig(content, source, options = {}) {
|
|
21
24
|
const parsed = source.endsWith('.json') ? JSON.parse(content) : parseYaml(content);
|
|
22
25
|
if (options.validateSchema !== false) {
|
|
@@ -24,6 +27,7 @@ export async function parseStructuredConfig(content, source, options = {}) {
|
|
|
24
27
|
}
|
|
25
28
|
return parsed;
|
|
26
29
|
}
|
|
30
|
+
/** Extracts the `$schema` reference from a parsed config object, resolves and fetches the schema, then validates. */
|
|
27
31
|
export async function validateDeclaredJsonSchema(value, source, options = {}) {
|
|
28
32
|
if (!isObject(value))
|
|
29
33
|
return;
|
|
@@ -49,6 +53,7 @@ export async function validateDeclaredJsonSchema(value, source, options = {}) {
|
|
|
49
53
|
.join('; ')}`, violations);
|
|
50
54
|
}
|
|
51
55
|
}
|
|
56
|
+
/** Validates a value against a {@link JsonSchema}, returning a list of {@link JsonSchemaViolation}s. Does not throw. */
|
|
52
57
|
export function validateJsonSchema(value, schema, path = '', defs = {}, seenRefs = new Set()) {
|
|
53
58
|
const violations = [];
|
|
54
59
|
// Applicator keywords compose with their siblings (logical AND), per JSON Schema 2020-12 —
|
|
@@ -183,7 +188,7 @@ function resolvePackageSchema(specifier, source, resolve) {
|
|
|
183
188
|
if (subpath.length === 0) {
|
|
184
189
|
throw new StructuredConfigSchemaError(`Package schema ref "${specifier}" referenced by "${source}" must include a path within the package`);
|
|
185
190
|
}
|
|
186
|
-
const from = isRemoteRef(source) ?
|
|
191
|
+
const from = isRemoteRef(source) ? getProcessCwd() : dirnamePath(source);
|
|
187
192
|
try {
|
|
188
193
|
// Resolve the package root via its always-present package.json, then join the subpath.
|
|
189
194
|
// This sidesteps `exports` gating on arbitrary JSON subpaths.
|
|
@@ -211,7 +216,7 @@ async function readSchema(schemaLocation, options) {
|
|
|
211
216
|
}
|
|
212
217
|
return await readBoundedBody(response, schemaLocation);
|
|
213
218
|
}
|
|
214
|
-
return await getFs().readFile(schemaLocation);
|
|
219
|
+
return await (options.fileSystem ?? getFs()).readFile(schemaLocation);
|
|
215
220
|
}
|
|
216
221
|
/**
|
|
217
222
|
* Read a response body under a hard byte cap. `Content-Length` is a fast-path reject, but servers
|
package/dist/types.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { Config } from './config';
|
|
2
|
+
/** Identifier for the target runtime platform. */
|
|
2
3
|
export type RuntimeName = 'node-bun' | 'cloudflare-workers' | 'test';
|
|
4
|
+
/** Feature flags describing what a runtime platform supports (filesystem, process execution, persistent storage). */
|
|
3
5
|
export interface RuntimeCapabilities {
|
|
4
6
|
readonly hasFilesystem: boolean;
|
|
5
7
|
readonly hasProcessExecution: boolean;
|
|
6
8
|
readonly hasPersistentStorage: boolean;
|
|
7
9
|
}
|
|
10
|
+
/** Options passed to config loading functions, including overrides and environment variable bindings. */
|
|
8
11
|
export interface LoadConfigOptions {
|
|
9
12
|
overrides?: Partial<Config>;
|
|
10
13
|
envBindings?: Record<string, unknown>;
|
|
11
14
|
}
|
|
15
|
+
/** Minimal distributed tracing context for W3C trace/span propagation. */
|
|
12
16
|
export interface SpanContext {
|
|
13
17
|
traceId: string;
|
|
14
18
|
spanId: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;CAC1C;AAED,MAAM,WAAW,iBAAiB;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CAC1D"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,kDAAkD;AAClD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAErE,qHAAqH;AACrH,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;CAC1C;AAED,yGAAyG;AACzG,MAAM,WAAW,iBAAiB;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CAC1D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-runtime",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "@gobing-ai/ts-runtime — Runtime abstractions for Bun, Node, and Cloudflare Workers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-runtime-v<version> && git push --tags' && exit 1"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@gobing-ai/ts-utils": "^0.3.
|
|
61
|
+
"@gobing-ai/ts-utils": "^0.3.2",
|
|
62
62
|
"execa": "^9.5.0",
|
|
63
63
|
"yaml": "^2.7.0",
|
|
64
64
|
"zod": "^4.1.0"
|