import * as NodeServices from "@effect/platform-node/NodeServices";
import { describe, expect, it } from "@effect/vitest";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Fiber from "effect/Fiber";
import * as Layer from "effect/Layer";
import { TestClock } from "effect/testing";
import { VcsProcessExitError, VcsProcessTimeoutError } from "@t3tools/contracts";
import * as VcsProcess from "./VcsProcess.ts";
const run = (input: VcsProcess.VcsProcessInput) =>
Effect.gen(function* () {
const process = yield* VcsProcess.VcsProcess;
return yield* process.run(input);
});
const liveLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer));
const provideLive = (effect: Effect.Effect) =>
effect.pipe(Effect.provide(liveLayer));
describe("VcsProcess.run", () => {
it.effect("collects stdout", () =>
Effect.gen(function* () {
const result = yield* run({
operation: "test.stdout",
command: "node",
args: ["-e", "process.stdout.write('hello')"],
cwd: process.cwd(),
});
expect(result.stdout).toBe("hello");
expect(result.stderr).toBe("");
expect(result.stdoutTruncated).toBe(false);
expect(result.stderrTruncated).toBe(false);
}).pipe(provideLive),
);
it.effect("writes stdin before waiting for exit", () =>
Effect.gen(function* () {
const result = yield* run({
operation: "test.stdin",
command: "node",
args: [
"-e",
[
"process.stdin.setEncoding('utf8');",
"let data='';",
"process.stdin.on('data', chunk => { data += chunk; });",
"process.stdin.on('end', () => { process.stdout.write(data); });",
].join(""),
],
cwd: process.cwd(),
stdin: "stdin payload",
});
expect(result.stdout).toBe("stdin payload");
}).pipe(provideLive),
);
it.effect("fails with VcsProcessExitError for non-zero exits by default", () =>
Effect.gen(function* () {
const error = yield* run({
operation: "test.exit",
command: "node",
args: ["-e", "process.stderr.write('boom'); process.exit(2)"],
cwd: process.cwd(),
}).pipe(Effect.flip);
expect(error).toBeInstanceOf(VcsProcessExitError);
}).pipe(provideLive),
);
it.effect("returns output when non-zero exits are allowed", () =>
Effect.gen(function* () {
const result = yield* run({
operation: "test.allowed-exit",
command: "node",
args: ["-e", "process.stderr.write('boom'); process.exit(2)"],
cwd: process.cwd(),
allowNonZeroExit: true,
});
expect(result.exitCode).toBe(2);
expect(result.stderr).toBe("boom");
}).pipe(provideLive),
);
it.effect("truncates output and appends the marker when requested", () =>
Effect.gen(function* () {
const result = yield* run({
operation: "test.truncate-marker",
command: "node",
args: ["-e", "process.stdout.write('x'.repeat(2048))"],
cwd: process.cwd(),
maxOutputBytes: 128,
appendTruncationMarker: true,
});
expect(result.stdoutTruncated).toBe(true);
expect(result.stdout).toContain("[truncated]");
expect(result.stderrTruncated).toBe(false);
}).pipe(provideLive),
);
it.effect("truncates without the marker when truncation markers are disabled", () =>
Effect.gen(function* () {
const result = yield* run({
operation: "test.truncate-silent",
command: "node",
args: ["-e", "process.stdout.write('x'.repeat(2048))"],
cwd: process.cwd(),
maxOutputBytes: 128,
});
expect(result.stdoutTruncated).toBe(true);
expect(result.stdout).not.toContain("[truncated]");
}).pipe(provideLive),
);
it.effect("fails with VcsProcessTimeoutError on timeout", () =>
Effect.gen(function* () {
const errorFiber = yield* run({
operation: "test.timeout",
command: "node",
args: ["-e", "setTimeout(() => {}, 5000)"],
cwd: process.cwd(),
timeoutMs: 50,
}).pipe(Effect.flip, Effect.forkScoped);
yield* Effect.yieldNow;
yield* TestClock.adjust(Duration.millis(50));
const error = yield* Fiber.join(errorFiber);
expect(error).toBeInstanceOf(VcsProcessTimeoutError);
}).pipe(provideLive),
);
});