import { createRequire } from "node:module"; import { Effect, FileSystem, Layer, Path } from "effect";import { PtyAdapter, PtyAdapterShape, PtyExitEvent, PtyProcess } from "../Services/PTY"; let didEnsureSpawnHelperExecutable = false; const resolveNodePtySpawnHelperPath = Effect.gen(function* () { const requireForNodePty = createRequire(import.meta.url); const path = yield* Path.Path; const fs = yield* FileSystem.FileSystem; const packageJsonPath = requireForNodePty.resolve("node-pty/package.json"); const packageDir = path.dirname(packageJsonPath); const candidates = [ path.join(packageDir, "build", "Release", "spawn-helper"), path.join(packageDir, "build", "Debug", "spawn-helper"), path.join(packageDir, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"), ]; for (const candidate of candidates) { if (yield* fs.exists(candidate)) { return candidate; } } return null;}).pipe(Effect.orElseSucceed(() => null)); export const ensureNodePtySpawnHelperExecutable = Effect.fn(function* (explicitPath?: string) { const fs = yield* FileSystem.FileSystem; if (process.platform === "win32") return; if (!explicitPath && didEnsureSpawnHelperExecutable) return; const helperPath = explicitPath ?? (yield* resolveNodePtySpawnHelperPath); if (!helperPath) return; if (!explicitPath) { didEnsureSpawnHelperExecutable = true; } const stat = yield* fs.stat(helperPath); const mode = stat.mode & 0o777; if ((mode & 0o111) === 0) { yield* fs.chmod(helperPath, mode | 0o111); }}); class NodePtyProcess implements PtyProcess { constructor(private readonly process: import("node-pty").IPty) {} get pid(): number { return this.process.pid; } write(data: string): void { this.process.write(data); } resize(cols: number, rows: number): void { this.process.resize(cols, rows); } kill(signal?: string): void { this.process.kill(signal); } onData(callback: (data: string) => void): () => void { const disposable = this.process.onData(callback); return () => { disposable.dispose(); }; } onExit(callback: (event: PtyExitEvent) => void): () => void { const disposable = this.process.onExit((event) => { callback({ exitCode: event.exitCode, signal: event.signal ?? null, }); }); return () => { disposable.dispose(); }; }} export const NodePtyAdapterLive = Layer.effect( PtyAdapter, Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const nodePty = yield* Effect.promise(() => import("node-pty")); const ensureNodePtySpawnHelperExecutableCached = yield* Effect.cached( ensureNodePtySpawnHelperExecutable().pipe( Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path), Effect.orElseSucceed(() => undefined), ), ); return { spawn: Effect.fn(function* (input) { yield* ensureNodePtySpawnHelperExecutableCached; const ptyProcess = nodePty.spawn(input.shell, input.args ?? [], { cwd: input.cwd, cols: input.cols, rows: input.rows, env: input.env, name: globalThis.process.platform === "win32" ? "xterm-color" : "xterm-256color", }); return new NodePtyProcess(ptyProcess); }), } satisfies PtyAdapterShape; }),);
import { createRequire } from "node:module";
import { Effect, FileSystem, Layer, Path } from "effect";
import { PtyAdapter, PtyAdapterShape, PtyExitEvent, PtyProcess } from "../Services/PTY";
let didEnsureSpawnHelperExecutable = false;
const resolveNodePtySpawnHelperPath = Effect.gen(function* () {
const requireForNodePty = createRequire(import.meta.url);
const path = yield* Path.Path;
const fs = yield* FileSystem.FileSystem;
const packageJsonPath = requireForNodePty.resolve("node-pty/package.json");
const packageDir = path.dirname(packageJsonPath);
const candidates = [
path.join(packageDir, "build", "Release", "spawn-helper"),
path.join(packageDir, "build", "Debug", "spawn-helper"),
path.join(packageDir, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
];
for (const candidate of candidates) {
if (yield* fs.exists(candidate)) {
return candidate;
}
}
return null;
}).pipe(Effect.orElseSucceed(() => null));
export const ensureNodePtySpawnHelperExecutable = Effect.fn(function* (explicitPath?: string) {
const fs = yield* FileSystem.FileSystem;
if (process.platform === "win32") return;
if (!explicitPath && didEnsureSpawnHelperExecutable) return;
const helperPath = explicitPath ?? (yield* resolveNodePtySpawnHelperPath);
if (!helperPath) return;
if (!explicitPath) {
didEnsureSpawnHelperExecutable = true;
}
const stat = yield* fs.stat(helperPath);
const mode = stat.mode & 0o777;
if ((mode & 0o111) === 0) {
yield* fs.chmod(helperPath, mode | 0o111);
}
});
class NodePtyProcess implements PtyProcess {
constructor(private readonly process: import("node-pty").IPty) {}
get pid(): number {
return this.process.pid;
}
write(data: string): void {
this.process.write(data);
}
resize(cols: number, rows: number): void {
this.process.resize(cols, rows);
}
kill(signal?: string): void {
this.process.kill(signal);
}
onData(callback: (data: string) => void): () => void {
const disposable = this.process.onData(callback);
return () => {
disposable.dispose();
};
}
onExit(callback: (event: PtyExitEvent) => void): () => void {
const disposable = this.process.onExit((event) => {
callback({
exitCode: event.exitCode,
signal: event.signal ?? null,
});
});
return () => {
disposable.dispose();
};
}
}
export const NodePtyAdapterLive = Layer.effect(
PtyAdapter,
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const nodePty = yield* Effect.promise(() => import("node-pty"));
const ensureNodePtySpawnHelperExecutableCached = yield* Effect.cached(
ensureNodePtySpawnHelperExecutable().pipe(
Effect.provideService(FileSystem.FileSystem, fs),
Effect.provideService(Path.Path, path),
Effect.orElseSucceed(() => undefined),
),
);
return {
spawn: Effect.fn(function* (input) {
yield* ensureNodePtySpawnHelperExecutableCached;
const ptyProcess = nodePty.spawn(input.shell, input.args ?? [], {
cwd: input.cwd,
cols: input.cols,
rows: input.rows,
env: input.env,
name: globalThis.process.platform === "win32" ? "xterm-color" : "xterm-256color",
});
return new NodePtyProcess(ptyProcess);
}),
} satisfies PtyAdapterShape;
}),
);