@opencode-ai/util 0.0.0-beta-17492

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.
Files changed (57) hide show
  1. package/dist/bom.d.ts +22 -0
  2. package/dist/bom.js +37 -0
  3. package/dist/cross-spawn-spawner.d.ts +3 -0
  4. package/dist/cross-spawn-spawner.js +406 -0
  5. package/dist/effect/app-node-platform.d.ts +6 -0
  6. package/dist/effect/app-node-platform.js +11 -0
  7. package/dist/effect/app-node.d.ts +50 -0
  8. package/dist/effect/app-node.js +8 -0
  9. package/dist/effect/layer-node.d.ts +79 -0
  10. package/dist/effect/layer-node.js +181 -0
  11. package/dist/effect/memo-map.d.ts +2 -0
  12. package/dist/effect/memo-map.js +2 -0
  13. package/dist/effect/runtime.d.ts +8 -0
  14. package/dist/effect/runtime.js +16 -0
  15. package/dist/effect/service-use.d.ts +7 -0
  16. package/dist/effect/service-use.js +27 -0
  17. package/dist/effect-flock.d.ts +31 -0
  18. package/dist/effect-flock.js +185 -0
  19. package/dist/flock.d.ts +30 -0
  20. package/dist/flock.js +273 -0
  21. package/dist/fs-util.d.ts +137 -0
  22. package/dist/fs-util.js +212 -0
  23. package/dist/glob.d.ts +12 -0
  24. package/dist/glob.js +26 -0
  25. package/dist/global-roots.d.ts +8 -0
  26. package/dist/global-roots.js +17 -0
  27. package/dist/global-roots.workerd.d.ts +7 -0
  28. package/dist/global-roots.workerd.js +15 -0
  29. package/dist/global.d.ts +30 -0
  30. package/dist/global.js +54 -0
  31. package/dist/hash.d.ts +4 -0
  32. package/dist/hash.js +12 -0
  33. package/dist/npm-config.d.ts +4 -0
  34. package/dist/npm-config.js +34 -0
  35. package/dist/npm.d.ts +28 -0
  36. package/dist/npm.js +161 -0
  37. package/dist/observability/logging.d.ts +6 -0
  38. package/dist/observability/logging.js +71 -0
  39. package/dist/observability/otlp.d.ts +18 -0
  40. package/dist/observability/otlp.js +76 -0
  41. package/dist/observability/shared.d.ts +1 -0
  42. package/dist/observability/shared.js +7 -0
  43. package/dist/observability.d.ts +13 -0
  44. package/dist/observability.js +37 -0
  45. package/dist/patch.d.ts +43 -0
  46. package/dist/patch.js +332 -0
  47. package/dist/process.d.ts +54 -0
  48. package/dist/process.js +162 -0
  49. package/dist/runtime/import.bun.d.ts +2 -0
  50. package/dist/runtime/import.bun.js +6 -0
  51. package/dist/runtime/import.node.d.ts +2 -0
  52. package/dist/runtime/import.node.js +36 -0
  53. package/dist/runtime-import.d.ts +1 -0
  54. package/dist/runtime-import.js +1 -0
  55. package/dist/session-title-fallback.d.ts +16 -0
  56. package/dist/session-title-fallback.js +23 -0
  57. package/package.json +72 -0
package/dist/patch.js ADDED
@@ -0,0 +1,332 @@
1
+ export * as Patch from "./patch.js";
2
+ import { Result, Schema } from "effect";
3
+ import { Bom } from "./bom.js";
4
+ export class BoundaryError extends Schema.TaggedErrorClass()("Patch.BoundaryError", {
5
+ boundary: Schema.Literals(["first", "last"]),
6
+ }) {
7
+ get message() {
8
+ return `The ${this.boundary} line of the patch must be '${this.boundary === "first" ? "*** Begin Patch" : "*** End Patch"}'`;
9
+ }
10
+ }
11
+ export class InvalidHunkError extends Schema.TaggedErrorClass()("Patch.InvalidHunkError", {
12
+ line: Schema.String,
13
+ lineNumber: Schema.Number,
14
+ reason: Schema.optional(Schema.String),
15
+ }) {
16
+ get message() {
17
+ if (this.reason)
18
+ return `Invalid hunk at line ${this.lineNumber}: ${this.reason}`;
19
+ return `Invalid hunk at line ${this.lineNumber}: '${this.line}' is not a valid hunk header. Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'`;
20
+ }
21
+ }
22
+ export function parse(patchText) {
23
+ const lines = stripHeredoc(patchText.trim())
24
+ .split("\n")
25
+ .map((line) => (line.endsWith("\r") ? line.slice(0, -1) : line));
26
+ const begin = lines[0]?.trim() === "*** Begin Patch" ? 0 : -1;
27
+ const end = lines.at(-1)?.trim() === "*** End Patch" ? lines.length - 1 : -1;
28
+ if (begin === -1)
29
+ return Result.fail(new BoundaryError({ boundary: "first" }));
30
+ if (end === -1 || begin >= end)
31
+ return Result.fail(new BoundaryError({ boundary: "last" }));
32
+ const hunks = [];
33
+ let index = begin + 1;
34
+ while (index < end) {
35
+ const line = lines[index];
36
+ const header = line.trim();
37
+ if (index === begin + 1 &&
38
+ header.startsWith("*** Environment ID:") &&
39
+ header.slice("*** Environment ID:".length).trim()) {
40
+ index++;
41
+ continue;
42
+ }
43
+ if (header.startsWith("*** Add File: ")) {
44
+ const path = header.slice("*** Add File: ".length).trim();
45
+ const parsed = parseAdd(lines, index + 1, end, path);
46
+ if ("error" in parsed)
47
+ return Result.fail(parsed.error);
48
+ hunks.push({ type: "add", path, contents: parsed.content });
49
+ index = parsed.next;
50
+ continue;
51
+ }
52
+ if (header.startsWith("*** Delete File: ")) {
53
+ const path = header.slice("*** Delete File: ".length).trim();
54
+ const next = lines[index + 1]?.trim();
55
+ if (index + 1 < end && next !== undefined && !isBoundary(next)) {
56
+ if (next.startsWith("*** ")) {
57
+ return Result.fail(new InvalidHunkError({ line: next, lineNumber: index + 2 }));
58
+ }
59
+ return Result.fail(new InvalidHunkError({
60
+ line: next,
61
+ lineNumber: index + 2,
62
+ reason: `Unexpected line after Delete File '${path}': '${next}'. Delete hunks do not contain body lines`,
63
+ }));
64
+ }
65
+ hunks.push({ type: "delete", path });
66
+ index++;
67
+ continue;
68
+ }
69
+ if (header.startsWith("*** Update File: ")) {
70
+ const path = header.slice("*** Update File: ".length).trim();
71
+ let next = index + 1;
72
+ let movePath;
73
+ while (lines[next]?.trimEnd() === "*** End of File")
74
+ next++;
75
+ const move = lines[next]?.trimEnd();
76
+ if (move === "*** Move to:" || move?.startsWith("*** Move to: ")) {
77
+ movePath = move.slice("*** Move to: ".length).trim();
78
+ if (!movePath) {
79
+ return Result.fail(new InvalidHunkError({
80
+ line: lines[next].trim(),
81
+ lineNumber: next + 1,
82
+ reason: `Move destination for '${path}' must not be empty`,
83
+ }));
84
+ }
85
+ next++;
86
+ }
87
+ const parsed = parseUpdate(lines, next, end, path, index);
88
+ if ("error" in parsed)
89
+ return Result.fail(parsed.error);
90
+ hunks.push({ type: "update", path, movePath, chunks: parsed.chunks });
91
+ index = parsed.next;
92
+ continue;
93
+ }
94
+ return Result.fail(new InvalidHunkError({ line: header, lineNumber: index + 1 }));
95
+ }
96
+ return Result.succeed(hunks);
97
+ }
98
+ export function derive(path, chunks, original) {
99
+ const source = Bom.split(original);
100
+ const lines = source.text.split("\n");
101
+ if (lines.at(-1) === "")
102
+ lines.pop();
103
+ const replacements = computeReplacements(lines, path, chunks);
104
+ const updated = [...lines];
105
+ for (const [start, remove, insert] of replacements.toReversed())
106
+ updated.splice(start, remove, ...insert);
107
+ if (updated.at(-1) !== "")
108
+ updated.push("");
109
+ const next = Bom.split(updated.join("\n"));
110
+ return { content: next.text, bom: source.bom || next.bom };
111
+ }
112
+ export function joinBom(text, bom) {
113
+ return Bom.join(text, bom);
114
+ }
115
+ function parseAdd(lines, start, end, path) {
116
+ const content = [];
117
+ let index = start;
118
+ while (index < end && !isBoundary(lines[index].trim())) {
119
+ if (!lines[index].startsWith("+")) {
120
+ const line = lines[index].trim();
121
+ return {
122
+ error: new InvalidHunkError({
123
+ line,
124
+ lineNumber: index + 1,
125
+ reason: `Invalid Add File line for '${path}': expected a line starting with '+', got '${line}'`,
126
+ }),
127
+ };
128
+ }
129
+ content.push(lines[index].slice(1));
130
+ index++;
131
+ }
132
+ return { content: content.join("\n"), next: index };
133
+ }
134
+ function parseUpdate(lines, start, end, path, hunk) {
135
+ const chunks = [];
136
+ let index = start;
137
+ let afterEndOfFile = false;
138
+ while (index < end) {
139
+ const line = lines[index];
140
+ const updateLine = line.trimEnd();
141
+ if (afterEndOfFile) {
142
+ if (updateLine === "") {
143
+ index++;
144
+ continue;
145
+ }
146
+ if (updateLine === "@@" || updateLine.startsWith("@@ "))
147
+ afterEndOfFile = false;
148
+ else if (isBoundary(updateLine))
149
+ break;
150
+ else {
151
+ return {
152
+ error: new InvalidHunkError({
153
+ line,
154
+ lineNumber: index + 1,
155
+ reason: `Expected update hunk to start with a @@ context marker, got: '${line}'`,
156
+ }),
157
+ };
158
+ }
159
+ }
160
+ if (updateLine === "*** End of File") {
161
+ const chunk = chunks.at(-1);
162
+ if (chunk && chunk.oldLines.length === 0 && chunk.newLines.length === 0) {
163
+ return {
164
+ error: new InvalidHunkError({
165
+ line: updateLine,
166
+ lineNumber: index + 1,
167
+ reason: "Update hunk does not contain any lines",
168
+ }),
169
+ };
170
+ }
171
+ if (chunk) {
172
+ chunk.endOfFile = true;
173
+ afterEndOfFile = true;
174
+ }
175
+ index++;
176
+ continue;
177
+ }
178
+ if (isBoundary(updateLine))
179
+ break;
180
+ if (updateLine === "@@" || updateLine.startsWith("@@ ")) {
181
+ const previous = chunks.at(-1);
182
+ if (previous && previous.oldLines.length === 0 && previous.newLines.length === 0) {
183
+ return {
184
+ error: new InvalidHunkError({
185
+ line,
186
+ lineNumber: index + 1,
187
+ reason: `Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`,
188
+ }),
189
+ };
190
+ }
191
+ chunks.push({
192
+ oldLines: [],
193
+ newLines: [],
194
+ changeContext: updateLine === "@@" ? undefined : updateLine.slice("@@ ".length),
195
+ });
196
+ index++;
197
+ continue;
198
+ }
199
+ if (chunks.length === 0)
200
+ chunks.push({ oldLines: [], newLines: [] });
201
+ const chunk = chunks.at(-1);
202
+ if (line === "") {
203
+ chunk.oldLines.push("");
204
+ chunk.newLines.push("");
205
+ index++;
206
+ continue;
207
+ }
208
+ if (line.startsWith(" ")) {
209
+ chunk.oldLines.push(line.slice(1));
210
+ chunk.newLines.push(line.slice(1));
211
+ index++;
212
+ continue;
213
+ }
214
+ if (line.startsWith("-")) {
215
+ chunk.oldLines.push(line.slice(1));
216
+ index++;
217
+ continue;
218
+ }
219
+ if (line.startsWith("+")) {
220
+ chunk.newLines.push(line.slice(1));
221
+ index++;
222
+ continue;
223
+ }
224
+ const populated = chunk.oldLines.length > 0 || chunk.newLines.length > 0;
225
+ return {
226
+ error: new InvalidHunkError({
227
+ line,
228
+ lineNumber: index + 1,
229
+ reason: populated
230
+ ? `Expected update hunk to start with a @@ context marker, got: '${line}'`
231
+ : `Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`,
232
+ }),
233
+ };
234
+ }
235
+ if (chunks.length === 0) {
236
+ return {
237
+ error: new InvalidHunkError({
238
+ line: lines[hunk].trim(),
239
+ lineNumber: hunk + 1,
240
+ reason: `Update file hunk for path '${path}' is empty`,
241
+ }),
242
+ };
243
+ }
244
+ const last = chunks.at(-1);
245
+ if (last.oldLines.length === 0 && last.newLines.length === 0) {
246
+ const line = lines[index].trim();
247
+ return {
248
+ error: new InvalidHunkError({
249
+ line,
250
+ lineNumber: index + 1,
251
+ reason: line === "*** End Patch"
252
+ ? "Update hunk does not contain any lines"
253
+ : `Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`,
254
+ }),
255
+ };
256
+ }
257
+ return { chunks, next: index };
258
+ }
259
+ function isBoundary(line) {
260
+ return (line === "*** End Patch" ||
261
+ line.startsWith("*** Add File: ") ||
262
+ line.startsWith("*** Delete File: ") ||
263
+ line.startsWith("*** Update File: "));
264
+ }
265
+ function computeReplacements(lines, path, chunks) {
266
+ const replacements = [];
267
+ let lineIndex = 0;
268
+ for (const chunk of chunks) {
269
+ if (chunk.changeContext) {
270
+ const context = seek(lines, [chunk.changeContext], lineIndex);
271
+ if (context === -1)
272
+ throw new Error(`Failed to find context '${chunk.changeContext}' in ${path}`);
273
+ lineIndex = context + 1;
274
+ }
275
+ if (chunk.oldLines.length === 0) {
276
+ replacements.push([lines.length, 0, chunk.newLines]);
277
+ continue;
278
+ }
279
+ let oldLines = chunk.oldLines;
280
+ let newLines = chunk.newLines;
281
+ let found = seek(lines, oldLines, lineIndex, chunk.endOfFile);
282
+ if (found === -1 && oldLines.at(-1) === "") {
283
+ oldLines = oldLines.slice(0, -1);
284
+ if (newLines.at(-1) === "")
285
+ newLines = newLines.slice(0, -1);
286
+ found = seek(lines, oldLines, lineIndex, chunk.endOfFile);
287
+ }
288
+ if (found === -1 && chunk.oldLines.every((line) => line === "")) {
289
+ const expected = chunk.oldLines.length === 1 ? "an expected blank line" : `${chunk.oldLines.length} consecutive blank lines`;
290
+ throw new Error(`Failed to find ${expected} in ${path}`);
291
+ }
292
+ if (found === -1)
293
+ throw new Error(`Failed to find expected lines in ${path}:\n${chunk.oldLines.join("\n")}`);
294
+ replacements.push([found, oldLines.length, newLines]);
295
+ lineIndex = found + oldLines.length;
296
+ }
297
+ return replacements.toSorted((left, right) => left[0] - right[0]);
298
+ }
299
+ function seek(lines, pattern, start, eof = false) {
300
+ if (pattern.length === 0)
301
+ return -1;
302
+ if (eof) {
303
+ const offset = lines.length - pattern.length;
304
+ if (offset < start)
305
+ return -1;
306
+ for (const compare of [exact, rstrip, trim, normalized]) {
307
+ if (matches(lines, pattern, offset, compare))
308
+ return offset;
309
+ }
310
+ return -1;
311
+ }
312
+ for (const compare of [exact, rstrip, trim, normalized]) {
313
+ for (let offset = start; offset <= lines.length - pattern.length; offset++) {
314
+ if (matches(lines, pattern, offset, compare))
315
+ return offset;
316
+ }
317
+ }
318
+ return -1;
319
+ }
320
+ function matches(lines, pattern, offset, compare) {
321
+ return pattern.every((line, index) => compare(lines[offset + index], line));
322
+ }
323
+ const exact = (left, right) => left === right;
324
+ const rstrip = (left, right) => left.trimEnd() === right.trimEnd();
325
+ const trim = (left, right) => left.trim() === right.trim();
326
+ const normalized = (left, right) => normalize(left.trim()) === normalize(right.trim());
327
+ const normalize = (value) => value
328
+ .replace(/[‘’‚‛]/g, "'")
329
+ .replace(/[“”„‟]/g, '"')
330
+ .replace(/[‐‑‒–—―−]/g, "-")
331
+ .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " ");
332
+ const stripHeredoc = (input) => input.match(/^(?:cat\s+)?<<(['"]?)(\w+)\1\s*\n([\s\S]*?)\n\2\s*$/)?.[3] ?? input;
@@ -0,0 +1,54 @@
1
+ import { Context, Duration, Effect, Schema, Stream } from "effect";
2
+ import type { PlatformError } from "effect/PlatformError";
3
+ import { ChildProcess } from "effect/unstable/process";
4
+ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
5
+ declare const AppProcessError_base: Schema.Class<AppProcessError, Schema.TaggedStruct<"AppProcessError", {
6
+ readonly command: Schema.String;
7
+ readonly exitCode: Schema.optional<Schema.Number>;
8
+ readonly stderr: Schema.optional<Schema.String>;
9
+ readonly cause: Schema.optional<Schema.Defect>;
10
+ }>, import("effect/Cause").YieldableError>;
11
+ export declare class AppProcessError extends AppProcessError_base {
12
+ get message(): string;
13
+ }
14
+ export interface RunOptions {
15
+ readonly combineOutput?: boolean;
16
+ readonly maxOutputBytes?: number;
17
+ readonly maxErrorBytes?: number;
18
+ readonly signal?: AbortSignal;
19
+ readonly timeout?: Duration.Input;
20
+ readonly stdin?: string | Uint8Array | Stream.Stream<Uint8Array, PlatformError>;
21
+ }
22
+ export interface RunStreamOptions {
23
+ readonly signal?: AbortSignal;
24
+ readonly includeStderr?: boolean;
25
+ readonly okExitCodes?: ReadonlyArray<number>;
26
+ readonly maxErrorBytes?: number;
27
+ }
28
+ export interface RunResult {
29
+ readonly command: string;
30
+ readonly exitCode: number;
31
+ readonly output?: Buffer;
32
+ readonly stdout: Buffer;
33
+ readonly stderr: Buffer;
34
+ readonly outputTruncated?: boolean;
35
+ readonly stdoutTruncated: boolean;
36
+ readonly stderrTruncated: boolean;
37
+ }
38
+ export type Interface = ChildProcessSpawner["Service"] & {
39
+ readonly run: (command: ChildProcess.Command, options?: RunOptions) => Effect.Effect<RunResult, AppProcessError>;
40
+ readonly runStream: (command: ChildProcess.Command, options?: RunStreamOptions) => Stream.Stream<string, AppProcessError>;
41
+ };
42
+ declare const Service_base: Context.ServiceClass<Service, "@opencode/AppProcess", Interface>;
43
+ export declare class Service extends Service_base {
44
+ }
45
+ export declare const requireSuccess: (result: RunResult) => Effect.Effect<RunResult, AppProcessError>;
46
+ export declare const requireExitIn: (codes: ReadonlyArray<number>) => (result: RunResult) => Effect.Effect<RunResult, AppProcessError>;
47
+ export declare const abortError: (signal: AbortSignal) => Error;
48
+ export declare const waitForAbort: (signal: AbortSignal) => Effect.Effect<never, Error, never>;
49
+ export declare const collectStream: (stream: Stream.Stream<Uint8Array, PlatformError>, maxOutputBytes: number | undefined) => Effect.Effect<{
50
+ buffer: Buffer<ArrayBuffer>;
51
+ truncated: boolean;
52
+ }, PlatformError, never>;
53
+ export declare const node: import("./effect/layer-node.js").Node<Service, never, import("./effect/layer-node.js").Tag<"global">>;
54
+ export * as AppProcess from "./process.js";
@@ -0,0 +1,162 @@
1
+ import { Context, Duration, Effect, Fiber, Layer, Schema, Stream } from "effect";
2
+ import { ChildProcess } from "effect/unstable/process";
3
+ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
4
+ import { CrossSpawnSpawner } from "./cross-spawn-spawner.js";
5
+ import { makeGlobalNode } from "./effect/app-node.js";
6
+ export class AppProcessError extends Schema.TaggedErrorClass()("AppProcessError", {
7
+ command: Schema.String,
8
+ exitCode: Schema.optional(Schema.Number),
9
+ stderr: Schema.optional(Schema.String),
10
+ cause: Schema.optional(Schema.Defect()),
11
+ }) {
12
+ get message() {
13
+ const detail = this.stderr?.trim() || (this.cause instanceof Error ? this.cause.message : this.cause && String(this.cause));
14
+ const status = this.exitCode === undefined ? "" : ` (exit ${this.exitCode})`;
15
+ return `Command failed${status}: ${this.command}${detail ? `: ${detail}` : ""}`;
16
+ }
17
+ }
18
+ export class Service extends Context.Service()("@opencode/AppProcess") {
19
+ }
20
+ export const requireSuccess = (result) => result.exitCode === 0
21
+ ? Effect.succeed(result)
22
+ : Effect.fail(new AppProcessError({
23
+ command: result.command,
24
+ exitCode: result.exitCode,
25
+ stderr: result.stderr.toString("utf8"),
26
+ }));
27
+ export const requireExitIn = (codes) => (result) => codes.includes(result.exitCode)
28
+ ? Effect.succeed(result)
29
+ : Effect.fail(new AppProcessError({
30
+ command: result.command,
31
+ exitCode: result.exitCode,
32
+ stderr: result.stderr.toString("utf8"),
33
+ }));
34
+ const describeCommand = (command) => {
35
+ if (command._tag === "StandardCommand") {
36
+ return command.args.length ? `${command.command} ${command.args.join(" ")}` : command.command;
37
+ }
38
+ return `${describeCommand(command.left)} | ${describeCommand(command.right)}`;
39
+ };
40
+ const wrapError = (description, cause) => cause instanceof AppProcessError ? cause : new AppProcessError({ command: description, cause });
41
+ export const abortError = (signal) => {
42
+ const reason = signal.reason;
43
+ if (reason instanceof Error)
44
+ return reason;
45
+ const err = new Error("Aborted");
46
+ err.name = "AbortError";
47
+ return err;
48
+ };
49
+ export const waitForAbort = (signal) => Effect.callback((resume) => {
50
+ if (signal.aborted) {
51
+ resume(Effect.fail(abortError(signal)));
52
+ return;
53
+ }
54
+ const onabort = () => resume(Effect.fail(abortError(signal)));
55
+ signal.addEventListener("abort", onabort, { once: true });
56
+ return Effect.sync(() => signal.removeEventListener("abort", onabort));
57
+ });
58
+ const normalizeStdin = (input) => typeof input === "string"
59
+ ? Stream.make(new TextEncoder().encode(input))
60
+ : input instanceof Uint8Array
61
+ ? Stream.make(input)
62
+ : input;
63
+ export const collectStream = (stream, maxOutputBytes) => Stream.runFold(stream, () => ({ chunks: [], bytes: 0, truncated: false }), (acc, chunk) => {
64
+ if (maxOutputBytes === undefined) {
65
+ acc.chunks.push(chunk);
66
+ acc.bytes += chunk.length;
67
+ return acc;
68
+ }
69
+ const remaining = maxOutputBytes - acc.bytes;
70
+ if (remaining > 0)
71
+ acc.chunks.push(remaining >= chunk.length ? chunk : chunk.slice(0, remaining));
72
+ acc.bytes += chunk.length;
73
+ acc.truncated = acc.truncated || acc.bytes > maxOutputBytes;
74
+ return acc;
75
+ }).pipe(Effect.map((x) => ({ buffer: Buffer.concat(x.chunks), truncated: x.truncated })));
76
+ const layer = Layer.effect(Service, Effect.gen(function* () {
77
+ const spawner = yield* ChildProcessSpawner;
78
+ const runCommand = (command, options) => {
79
+ const description = describeCommand(command);
80
+ const collect = Effect.scoped(Effect.gen(function* () {
81
+ const handle = yield* spawner.spawn(command);
82
+ if (options?.combineOutput) {
83
+ const [output, exitCode] = yield* Effect.all([collectStream(handle.all, options.maxOutputBytes), handle.exitCode], { concurrency: "unbounded" });
84
+ return {
85
+ command: description,
86
+ exitCode,
87
+ output: output.buffer,
88
+ stdout: Buffer.alloc(0),
89
+ stderr: Buffer.alloc(0),
90
+ outputTruncated: output.truncated,
91
+ stdoutTruncated: false,
92
+ stderrTruncated: false,
93
+ };
94
+ }
95
+ const [stdout, stderr, exitCode] = yield* Effect.all([
96
+ collectStream(handle.stdout, options?.maxOutputBytes),
97
+ collectStream(handle.stderr, options?.maxErrorBytes),
98
+ handle.exitCode,
99
+ ], { concurrency: "unbounded" });
100
+ return {
101
+ command: description,
102
+ exitCode,
103
+ stdout: stdout.buffer,
104
+ stderr: stderr.buffer,
105
+ stdoutTruncated: stdout.truncated,
106
+ stderrTruncated: stderr.truncated,
107
+ };
108
+ }));
109
+ const timed = options?.timeout
110
+ ? Effect.timeoutOrElse(collect, {
111
+ duration: options.timeout,
112
+ orElse: () => Effect.fail(new AppProcessError({ command: description, cause: new Error("Timed out") })),
113
+ })
114
+ : collect;
115
+ const aborted = options?.signal
116
+ ? timed.pipe(Effect.raceFirst(waitForAbort(options.signal).pipe(Effect.mapError((cause) => wrapError(description, cause)))))
117
+ : timed;
118
+ return aborted.pipe(Effect.catch((cause) => Effect.fail(wrapError(description, cause))));
119
+ };
120
+ const run = Effect.fn("AppProcess.run")(function* (command, options) {
121
+ if (options?.stdin === undefined)
122
+ return yield* runCommand(command, options);
123
+ if (command._tag !== "StandardCommand") {
124
+ return yield* new AppProcessError({
125
+ command: describeCommand(command),
126
+ cause: new Error("stdin option only supports StandardCommand; received PipedCommand"),
127
+ });
128
+ }
129
+ const next = ChildProcess.make(command.command, command.args, {
130
+ ...command.options,
131
+ stdin: normalizeStdin(options.stdin),
132
+ });
133
+ return yield* runCommand(next, options);
134
+ });
135
+ const runStream = (command, options) => {
136
+ const description = describeCommand(command);
137
+ const okExitCodes = options?.okExitCodes;
138
+ const built = Stream.unwrap(Effect.gen(function* () {
139
+ const handle = yield* spawner.spawn(command);
140
+ const stderrFiber = yield* Effect.forkScoped(collectStream(handle.stderr, options?.maxErrorBytes).pipe(Effect.map((x) => x.buffer.toString("utf8"))));
141
+ const source = options?.includeStderr === true ? handle.all : handle.stdout;
142
+ const lines = source.pipe(Stream.decodeText, Stream.splitLines, Stream.filter((line) => line.length > 0));
143
+ const tail = Stream.unwrap(Effect.gen(function* () {
144
+ const code = yield* handle.exitCode;
145
+ if (okExitCodes && okExitCodes.length > 0 && !okExitCodes.includes(code)) {
146
+ const stderr = yield* Fiber.join(stderrFiber);
147
+ return Stream.fail(new AppProcessError({ command: description, exitCode: code, stderr }));
148
+ }
149
+ return Stream.empty;
150
+ }));
151
+ return Stream.concat(lines, tail);
152
+ }));
153
+ const mapped = built.pipe(Stream.catch((cause) => Stream.fail(wrapError(description, cause))));
154
+ if (!options?.signal)
155
+ return mapped;
156
+ const signal = options.signal;
157
+ return mapped.pipe(Stream.interruptWhen(waitForAbort(signal).pipe(Effect.mapError((cause) => wrapError(description, cause)))));
158
+ };
159
+ return Service.of({ ...spawner, run, runStream });
160
+ }));
161
+ export const node = makeGlobalNode({ service: Service, layer: layer, deps: [CrossSpawnSpawner.node] });
162
+ export * as AppProcess from "./process.js";
@@ -0,0 +1,2 @@
1
+ export declare function importModule(specifier: string): Promise<unknown>;
2
+ export declare function resolveModule(specifier: string, directory: string): string;
@@ -0,0 +1,6 @@
1
+ export function importModule(specifier) {
2
+ return import(specifier);
3
+ }
4
+ export function resolveModule(specifier, directory) {
5
+ return import.meta.resolve(specifier, directory);
6
+ }
@@ -0,0 +1,2 @@
1
+ export declare function importModule(specifier: string): Promise<unknown>;
2
+ export declare function resolveModule(specifier: string, directory: string): string;
@@ -0,0 +1,36 @@
1
+ import { Script, constants } from "node:vm";
2
+ import { createRequire, registerHooks } from "node:module";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { resolve } from "resolve.exports";
6
+ let conditions = [];
7
+ const conditionHooks = registerHooks({
8
+ resolve(specifier, context, nextResolve) {
9
+ conditions = context.conditions;
10
+ return nextResolve(specifier, context);
11
+ },
12
+ });
13
+ await new Script('import("node:module")', {
14
+ importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
15
+ }).runInThisContext();
16
+ conditionHooks.deregister();
17
+ export async function importModule(specifier) {
18
+ const imported = (await new Script(`import(${JSON.stringify(specifier)})`, {
19
+ importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
20
+ }).runInThisContext());
21
+ if (typeof imported !== "object" || imported === null)
22
+ return imported;
23
+ const module = imported;
24
+ const exports = module["module.exports"];
25
+ if (exports !== module.default || (typeof exports !== "object" && typeof exports !== "function") || exports === null)
26
+ return imported;
27
+ return Object.assign({}, module, exports);
28
+ }
29
+ export function resolveModule(specifier, directory) {
30
+ const pkg = createRequire(import.meta.url)(path.join(directory, "package.json"));
31
+ const target = resolve(pkg, specifier, { conditions, unsafe: true })?.[0];
32
+ if (target)
33
+ return pathToFileURL(path.resolve(directory, target)).href;
34
+ const legacyTarget = specifier === pkg.name ? directory : path.resolve(directory, specifier.slice(pkg.name.length + 1));
35
+ return pathToFileURL(createRequire(path.join(directory, "package.json")).resolve(legacyTarget)).href;
36
+ }
@@ -0,0 +1 @@
1
+ export { importModule, resolveModule } from "#runtime-import";
@@ -0,0 +1 @@
1
+ export { importModule, resolveModule } from "#runtime-import";
@@ -0,0 +1,16 @@
1
+ export * as SessionTitleFallback from "./session-title-fallback.js";
2
+ interface Info {
3
+ readonly title?: string;
4
+ readonly parentID?: string;
5
+ readonly time: {
6
+ readonly created: number;
7
+ };
8
+ }
9
+ /** Supplies the timestamped title required by compatibility surfaces. */
10
+ export declare function withTimestampedFallback(info: Info): string;
11
+ /** Supplies a compact human label and collapses historical timestamped fallbacks. */
12
+ export declare function displayLabel(info: Pick<Info, "title" | "parentID">): string;
13
+ /** Recognizes missing and historical root or child fallback titles. */
14
+ export declare function isFallbackTitle(title?: string): boolean;
15
+ /** Recognizes a missing title or the exact root fallback for this session. */
16
+ export declare function isExactRootFallback(info: Pick<Info, "title" | "time">): boolean;
@@ -0,0 +1,23 @@
1
+ export * as SessionTitleFallback from "./session-title-fallback.js";
2
+ const pattern = /^(New session|Child session) - \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
3
+ /** Supplies the timestamped title required by compatibility surfaces. */
4
+ export function withTimestampedFallback(info) {
5
+ return info.title ?? fallback(info);
6
+ }
7
+ /** Supplies a compact human label and collapses historical timestamped fallbacks. */
8
+ export function displayLabel(info) {
9
+ if (!info.title)
10
+ return info.parentID ? "Child session" : "New session";
11
+ return info.title.match(pattern)?.[1] ?? info.title;
12
+ }
13
+ /** Recognizes missing and historical root or child fallback titles. */
14
+ export function isFallbackTitle(title) {
15
+ return title === undefined || pattern.test(title);
16
+ }
17
+ /** Recognizes a missing title or the exact root fallback for this session. */
18
+ export function isExactRootFallback(info) {
19
+ return info.title === undefined || info.title === fallback({ time: info.time });
20
+ }
21
+ function fallback(info) {
22
+ return `${info.parentID ? "Child" : "New"} session - ${new Date(info.time.created).toISOString()}`;
23
+ }