@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/dist/fs.js CHANGED
@@ -1,22 +1,36 @@
1
- import { createWriteStream, mkdirSync } from 'node:fs';
2
- import { access, appendFile, cp, mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile, } from 'node:fs/promises';
3
- 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.js';
3
+ let fsPromisesModule = null;
4
+ let fsModule = null;
5
+ function nodeFsPromises() {
6
+ fsPromisesModule ??= import('node:fs/promises');
7
+ return fsPromisesModule;
8
+ }
9
+ function nodeFs() {
10
+ fsModule ??= import('node:fs');
11
+ return fsModule;
12
+ }
4
13
  export class NodeFileSystem {
5
14
  async readFile(path) {
15
+ const { readFile } = await nodeFsPromises();
6
16
  return await readFile(path, 'utf-8');
7
17
  }
8
18
  async writeFile(path, content) {
19
+ const { writeFile } = await nodeFsPromises();
9
20
  await ensureDirForFile(path, this);
10
21
  await writeFile(path, content, 'utf-8');
11
22
  }
12
23
  async appendFile(path, content) {
24
+ const { appendFile } = await nodeFsPromises();
13
25
  await ensureDirForFile(path, this);
14
26
  await appendFile(path, content, 'utf-8');
15
27
  }
16
28
  async mkdir(path) {
29
+ const { mkdir } = await nodeFsPromises();
17
30
  await mkdir(path, { recursive: true });
18
31
  }
19
32
  async exists(path) {
33
+ const { access } = await nodeFsPromises();
20
34
  try {
21
35
  await access(path);
22
36
  return true;
@@ -26,12 +40,15 @@ export class NodeFileSystem {
26
40
  }
27
41
  }
28
42
  async readDir(path) {
43
+ const { readdir } = await nodeFsPromises();
29
44
  return await readdir(path);
30
45
  }
31
46
  async unlink(path) {
47
+ const { rm } = await nodeFsPromises();
32
48
  await rm(path, { recursive: true, force: true });
33
49
  }
34
50
  async stat(path) {
51
+ const { stat } = await nodeFsPromises();
35
52
  try {
36
53
  const value = await stat(path);
37
54
  return {
@@ -46,17 +63,67 @@ export class NodeFileSystem {
46
63
  }
47
64
  }
48
65
  async realpath(path) {
66
+ const { realpath } = await nodeFsPromises();
49
67
  return await realpath(path);
50
68
  }
51
69
  async copy(src, dest) {
70
+ const { cp } = await nodeFsPromises();
52
71
  await cp(src, dest, { recursive: true });
53
72
  }
54
73
  async rename(src, dest) {
74
+ const { rename } = await nodeFsPromises();
55
75
  await rename(src, dest);
56
76
  }
57
77
  createLogStream(path) {
58
- mkdirSync(dirname(path), { recursive: true });
59
- return createWriteStream(path, { flags: 'a' });
78
+ return new LazyNodeLogStream(path);
79
+ }
80
+ }
81
+ export class NodeSyncFileSystem {
82
+ readFile(path) {
83
+ return readFileSync(path, 'utf-8');
84
+ }
85
+ writeFile(path, content) {
86
+ ensureDirForFileSync(path, this);
87
+ writeFileSync(path, content, 'utf-8');
88
+ }
89
+ mkdir(path) {
90
+ mkdirSync(path, { recursive: true });
91
+ }
92
+ readDir(path) {
93
+ return readdirSync(path);
94
+ }
95
+ unlink(path) {
96
+ rmSync(path, { recursive: true, force: true });
97
+ }
98
+ }
99
+ class LazyNodeLogStream {
100
+ ready;
101
+ ended = false;
102
+ // Single serialized chain: every write/end is appended here, so the underlying stream observes
103
+ // them in call order regardless of how the resolving microtasks interleave. A per-write `shift()`
104
+ // off a shared buffer (the previous approach) could reorder writes that arrived in the same tick.
105
+ tail;
106
+ constructor(path) {
107
+ this.ready = nodeFs().then(({ createWriteStream, mkdirSync }) => {
108
+ mkdirSync(dirnamePath(path), { recursive: true });
109
+ const stream = createWriteStream(path, { flags: 'a' });
110
+ return {
111
+ write: (chunk) => stream.write(chunk),
112
+ end: () => stream.end(),
113
+ };
114
+ });
115
+ this.tail = this.ready;
116
+ }
117
+ write(chunk) {
118
+ if (this.ended)
119
+ return;
120
+ this.tail = this.tail.then(() => this.ready.then((stream) => stream.write(chunk)));
121
+ }
122
+ end() {
123
+ if (this.ended)
124
+ return;
125
+ this.ended = true;
126
+ this.tail = this.tail.then(() => this.ready.then((stream) => stream.end()));
60
127
  }
61
128
  }
62
129
  const CLOUDFLARE_FS_ERROR = 'FileSystem is not available on Cloudflare Workers. Use D1, KV, or R2.';
@@ -113,11 +180,14 @@ export function getFs() {
113
180
  return activeFileSystem;
114
181
  }
115
182
  export async function ensureDirForFile(path, fs = getFs()) {
116
- await fs.mkdir(dirname(path));
183
+ await fs.mkdir(dirnamePath(path));
184
+ }
185
+ export function ensureDirForFileSync(path, fs) {
186
+ fs.mkdir(dirnamePath(path));
117
187
  }
118
188
  export async function atomicWriteFile(path, content, fs = getFs()) {
119
189
  await ensureDirForFile(path, fs);
120
- const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
190
+ const tempPath = `${path}.${getProcessPid()}.${uniqueToken()}.tmp`;
121
191
  await fs.writeFile(tempPath, content);
122
192
  await fs.rename(tempPath, path);
123
193
  }
@@ -134,7 +204,7 @@ export async function walkDir(path, fs = getFs()) {
134
204
  const entries = (await fs.readDir(path)).sort();
135
205
  const result = [];
136
206
  for (const entry of entries) {
137
- const fullPath = join(path, entry);
207
+ const fullPath = joinPath(path, entry);
138
208
  const entryStat = await fs.stat(fullPath);
139
209
  if (entryStat?.isDirectory()) {
140
210
  result.push(...(await walkDir(fullPath, fs)));
@@ -145,13 +215,13 @@ export async function walkDir(path, fs = getFs()) {
145
215
  }
146
216
  return result;
147
217
  }
148
- export function getProjectRoot(startDir = process.cwd()) {
149
- let current = resolve(startDir);
218
+ export function getProjectRoot(startDir = getProcessCwd()) {
219
+ let current = resolvePath(startDir);
150
220
  for (let i = 0; i < 12; i++) {
151
- if (Bun.file(join(current, 'bun.lock')).size !== 0 || Bun.file(join(current, 'package.json')).size !== 0) {
221
+ if (hasBunFile(joinPath(current, 'bun.lock')) || hasBunFile(joinPath(current, 'package.json'))) {
152
222
  return current;
153
223
  }
154
- const parent = dirname(current);
224
+ const parent = dirnamePath(current);
155
225
  if (parent === current)
156
226
  return startDir;
157
227
  current = parent;
@@ -159,8 +229,22 @@ export function getProjectRoot(startDir = process.cwd()) {
159
229
  return startDir;
160
230
  }
161
231
  export function resolveProjectPath(...segments) {
162
- return resolve(getProjectRoot(), ...segments);
232
+ return resolvePath(getProjectRoot(), ...segments);
163
233
  }
164
234
  export function createLogStream(path, fs = getFs()) {
165
235
  return fs.createLogStream(path);
166
236
  }
237
+ function hasBunFile(path) {
238
+ const bun = globalThis.Bun;
239
+ if (bun === undefined)
240
+ return false;
241
+ return bun.file(path).size !== 0;
242
+ }
243
+ function getProcessPid() {
244
+ return globalThis.process?.pid ?? 0;
245
+ }
246
+ // Two writers to the same path in the same millisecond must not share a temp name, or one clobbers
247
+ // the other before rename. randomUUID disambiguates; Date.now keeps names sortable for debugging.
248
+ function uniqueToken() {
249
+ return `${Date.now()}.${crypto.randomUUID()}`;
250
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './config';
2
2
  export * from './context';
3
3
  export * from './fs';
4
+ export * from './path';
4
5
  export * from './process-executor';
6
+ export * from './schema-validation';
5
7
  export * from './types';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,MAAM,CAAC;AACrB,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,MAAM,CAAC;AACrB,cAAc,QAAQ,CAAC;AACvB,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './config.js';
2
2
  export * from './context.js';
3
3
  export * from './fs.js';
4
+ export * from './path.js';
4
5
  export * from './process-executor.js';
6
+ export * from './schema-validation.js';
5
7
  export * from './types.js';
package/dist/path.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export declare function normalizeSeparators(path: string): string;
2
+ export declare function isAbsolutePath(path: string): boolean;
3
+ export declare function dirnamePath(path: string): string;
4
+ export declare function joinPath(...segments: string[]): string;
5
+ export declare function resolvePath(...segments: string[]): string;
6
+ export declare function getProcessCwd(): string;
7
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAShD;AAED,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMtD;AAED,wBAAgB,WAAW,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAkBzD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
package/dist/path.js ADDED
@@ -0,0 +1,55 @@
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
+ export function normalizeSeparators(path) {
5
+ return path.replaceAll('\\', '/');
6
+ }
7
+ export function isAbsolutePath(path) {
8
+ return path.startsWith('/') || /^[A-Za-z]:\//.test(normalizeSeparators(path));
9
+ }
10
+ export function dirnamePath(path) {
11
+ const input = normalizeSeparators(path);
12
+ if (/^\/+$/.test(input))
13
+ return '/';
14
+ const normalized = input.replace(/\/+$/, '');
15
+ if (normalized === '' || normalized === '/')
16
+ return normalized || '.';
17
+ const index = normalized.lastIndexOf('/');
18
+ if (index < 0)
19
+ return '.';
20
+ if (index === 0)
21
+ return '/';
22
+ return normalized.slice(0, index);
23
+ }
24
+ export function joinPath(...segments) {
25
+ const filtered = segments.filter((segment) => segment.length > 0).map(normalizeSeparators);
26
+ if (filtered.length === 0)
27
+ return '.';
28
+ const absolute = isAbsolutePath(filtered[0] ?? '');
29
+ const joined = filtered.join('/').replace(/\/+/g, '/');
30
+ return absolute ? joined : joined.replace(/^\//, '');
31
+ }
32
+ export function resolvePath(...segments) {
33
+ const candidates = segments.length === 0 ? [getProcessCwd()] : segments;
34
+ let resolved = '';
35
+ for (const segment of candidates.map(normalizeSeparators)) {
36
+ if (segment.length === 0)
37
+ continue;
38
+ resolved = isAbsolutePath(segment) ? segment : joinPath(resolved || getProcessCwd(), segment);
39
+ }
40
+ const parts = [];
41
+ const absolute = isAbsolutePath(resolved);
42
+ for (const part of resolved.split('/')) {
43
+ if (part === '' || part === '.')
44
+ continue;
45
+ if (part === '..') {
46
+ parts.pop();
47
+ continue;
48
+ }
49
+ parts.push(part);
50
+ }
51
+ return `${absolute ? '/' : ''}${parts.join('/')}` || (absolute ? '/' : '.');
52
+ }
53
+ export function getProcessCwd() {
54
+ return globalThis.process?.cwd?.() ?? '/';
55
+ }
@@ -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`). */
@@ -33,9 +38,40 @@ export interface ProcessResult {
33
38
  export interface ProcessExecutor {
34
39
  run(options: ProcessOptions): Promise<ProcessResult>;
35
40
  }
41
+ export interface SyncProcessExecutor {
42
+ runSync(options: Omit<ProcessOptions, 'timeout'>): ProcessResult;
43
+ }
44
+ export interface PipeProcessOptions {
45
+ command: string;
46
+ args?: string[];
47
+ cwd?: string;
48
+ /** Forwarded verbatim to the child — pass an allowlist for untrusted commands (see {@link ProcessOptions.env}). */
49
+ env?: Record<string, string>;
50
+ }
51
+ export interface PipeProcess {
52
+ readonly pid: number | null;
53
+ readonly stdout: ReadableStream<Uint8Array> | null;
54
+ readonly stderr: ReadableStream<Uint8Array> | null;
55
+ readonly exited: Promise<number | null>;
56
+ writeStdin(input: string | Uint8Array): void;
57
+ endStdin(): void;
58
+ kill(signal?: ProcessSignal): void;
59
+ }
60
+ export interface PipeProcessSpawner {
61
+ spawn(options: PipeProcessOptions): PipeProcess;
62
+ }
36
63
  export declare class NodeProcessExecutor implements ProcessExecutor {
37
64
  private readonly config;
38
65
  constructor(config?: ProcessExecutorConfig);
39
66
  run(options: ProcessOptions): Promise<ProcessResult>;
40
67
  }
68
+ export declare class BunSyncProcessExecutor implements SyncProcessExecutor {
69
+ runSync(options: Omit<ProcessOptions, 'timeout'>): ProcessResult;
70
+ }
71
+ export declare class BunPipeProcessSpawner implements PipeProcessSpawner {
72
+ spawn(options: PipeProcessOptions): PipeProcess;
73
+ }
74
+ type BunSubprocess = ReturnType<typeof Bun.spawn>;
75
+ type ProcessSignal = Parameters<BunSubprocess['kill']>[0];
76
+ export {};
41
77
  //# sourceMappingURL=process-executor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"process-executor.d.ts","sourceRoot":"","sources":["../src/process-executor.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtF,MAAM,WAAW,qBAAqB;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC5B,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACxD;AAED,qBAAa,mBAAoB,YAAW,eAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,GAAE,qBAA0B;IAEzD,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CA2C7D"}
1
+ {"version":3,"file":"process-executor.d.ts","sourceRoot":"","sources":["../src/process-executor.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtF,MAAM,WAAW,qBAAqB;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC5B,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,mBAAmB;IAChC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,aAAa,CAAC;CACpE;AAED,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mHAAmH;IACnH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAC7C,QAAQ,IAAI,IAAI,CAAC;IACjB,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IAC/B,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAAC;CACnD;AAED,qBAAa,mBAAoB,YAAW,eAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,GAAE,qBAA0B;IAEzD,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CA2C7D;AAED,qBAAa,sBAAuB,YAAW,mBAAmB;IAC9D,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,aAAa;CA2BnE;AAED,qBAAa,qBAAsB,YAAW,kBAAkB;IAC5D,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW;CAWlD;AAED,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;AAClD,KAAK,aAAa,GAAG,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -44,6 +44,74 @@ export class NodeProcessExecutor {
44
44
  }
45
45
  }
46
46
  }
47
+ export class BunSyncProcessExecutor {
48
+ runSync(options) {
49
+ const args = options.args ?? [];
50
+ const startedAt = Date.now();
51
+ const result = Bun.spawnSync({
52
+ cmd: [options.command, ...args],
53
+ stdout: 'pipe',
54
+ stderr: 'pipe',
55
+ stdin: 'ignore',
56
+ ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
57
+ ...(options.env !== undefined ? { env: options.env } : {}),
58
+ });
59
+ if (options.rejectOnError === true && result.exitCode !== 0) {
60
+ throw new Error(`${options.command} ${args.join(' ')} failed with exit code ${result.exitCode}: ${stripFinalNewline(asString(result.stderr))}`);
61
+ }
62
+ return {
63
+ command: options.command,
64
+ args,
65
+ exitCode: result.exitCode,
66
+ stdout: stripFinalNewline(asString(result.stdout)),
67
+ stderr: stripFinalNewline(asString(result.stderr)),
68
+ durationMs: Date.now() - startedAt,
69
+ };
70
+ }
71
+ }
72
+ export class BunPipeProcessSpawner {
73
+ spawn(options) {
74
+ const subprocess = Bun.spawn({
75
+ cmd: [options.command, ...(options.args ?? [])],
76
+ stdin: 'pipe',
77
+ stdout: 'pipe',
78
+ stderr: 'pipe',
79
+ ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
80
+ ...(options.env !== undefined ? { env: options.env } : {}),
81
+ });
82
+ return new BunPipeProcess(subprocess);
83
+ }
84
+ }
85
+ class BunPipeProcess {
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
+ }
47
115
  function buildExecaOptions(opts) {
48
116
  const canStream = !opts.forceBuffered &&
49
117
  opts.outputPolicy?.mode === 'stream' &&
@@ -68,3 +136,9 @@ function asString(value) {
68
136
  return value.map(String).join('');
69
137
  return '';
70
138
  }
139
+ function stripFinalNewline(value) {
140
+ return value.endsWith('\r\n') ? value.slice(0, -2) : value.endsWith('\n') ? value.slice(0, -1) : value;
141
+ }
142
+ function isReadableStream(value) {
143
+ return value instanceof ReadableStream;
144
+ }
@@ -0,0 +1,42 @@
1
+ export interface JsonSchemaViolation {
2
+ path: string;
3
+ message: string;
4
+ }
5
+ export interface JsonSchema {
6
+ type?: string | string[];
7
+ required?: string[];
8
+ properties?: Record<string, JsonSchema>;
9
+ additionalProperties?: boolean | JsonSchema;
10
+ items?: JsonSchema;
11
+ enum?: unknown[];
12
+ const?: unknown;
13
+ oneOf?: JsonSchema[];
14
+ anyOf?: JsonSchema[];
15
+ $ref?: string;
16
+ $defs?: Record<string, JsonSchema>;
17
+ }
18
+ export interface StructuredConfigLoadOptions {
19
+ validateSchema?: boolean;
20
+ /**
21
+ * Allow `http(s)://` `$schema` refs. Off by default: remote fetches are an SSRF/DoS surface when
22
+ * configs are authored by third parties. Prefer bundled package-specifier refs (resolved from
23
+ * `node_modules`). Supplying `fetch` explicitly also opts into remote resolution.
24
+ */
25
+ allowRemote?: boolean;
26
+ fetch?: (input: string) => Promise<Response>;
27
+ /**
28
+ * Module resolver for bare package-specifier `$schema` refs (e.g.
29
+ * `@gobing-ai/ts-rule-engine/schemas/rule-file.schema.json`). Defaults to `Bun.resolveSync`.
30
+ * Injectable for testing.
31
+ */
32
+ resolve?: (specifier: string, from: string) => string;
33
+ }
34
+ export declare class StructuredConfigSchemaError extends Error {
35
+ readonly violations: readonly JsonSchemaViolation[];
36
+ constructor(message: string, violations?: readonly JsonSchemaViolation[]);
37
+ }
38
+ export declare function loadStructuredConfig(path: string, options?: StructuredConfigLoadOptions): Promise<unknown>;
39
+ export declare function parseStructuredConfig(content: string, source: string, options?: StructuredConfigLoadOptions): Promise<unknown>;
40
+ export declare function validateDeclaredJsonSchema(value: unknown, source: string, options?: StructuredConfigLoadOptions): Promise<void>;
41
+ export declare function validateJsonSchema(value: unknown, schema: JsonSchema, path?: string, defs?: Record<string, JsonSchema>, seenRefs?: ReadonlySet<string>): JsonSchemaViolation[];
42
+ //# sourceMappingURL=schema-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-validation.d.ts","sourceRoot":"","sources":["../src/schema-validation.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,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,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;CACzD;AAED,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,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAAC,OAAO,CAAC,CAGpH;AAED,wBAAsB,qBAAqB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,wBAAsB,0BAA0B,CAC5C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED,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"}