import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest";import { Effect, FileSystem, Layer, Path, PlatformError } from "effect";import { ChildProcessSpawner } from "effect/unstable/process";import { describe } from "vitest"; import type { VcsError } from "@t3tools/contracts";import type { VcsProcessInput, VcsProcessOutput } from "./VcsProcess.ts";import { VcsProcess, layer as VcsProcessLayer } from "./VcsProcess.ts";import * as JjVcsDriver from "./JjVcsDriver.ts";import { runVcsDriverContractSuite } from "./testing/VcsDriverContractHarness.ts"; const JjContractLayer = JjVcsDriver.vcsLayer.pipe( Layer.provideMerge(VcsProcessLayer), Layer.provideMerge(NodeServices.layer),); const commandCalls = (calls: ReadonlyArray<VcsProcessInput>) => calls.map((call) => [call.command].concat(call.args)); const processOutput = (stdout: string, exitCode = 0, stderr = ""): VcsProcessOutput => ({ exitCode: ChildProcessSpawner.ExitCode(exitCode), stdout, stderr, stdoutTruncated: false, stderrTruncated: false,}); const runJj = (cwd: string, args: ReadonlyArray<string>) => Effect.gen(function* () { const process = yield* VcsProcess; yield* process.run({ operation: "JjVcsDriver.contract.jj", command: "jj", cwd, args, timeoutMs: 10_000, }); }); type JjContractError = PlatformError.PlatformError | VcsError; runVcsDriverContractSuite<VcsProcess, JjContractError>({ name: "JJ", kind: "jj", layer: JjContractLayer, fixture: { createRepo: (cwd) => runJj(cwd, ["git", "init", "--colocate"]), writeFile: (cwd, relativePath, contents) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const absolutePath = path.join(cwd, relativePath); yield* fileSystem.makeDirectory(path.dirname(absolutePath), { recursive: true }); yield* fileSystem.writeFileString(absolutePath, contents); }), ignorePath: (cwd, pattern) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* fileSystem.writeFileString(path.join(cwd, ".gitignore"), `${pattern}\n`); }), },}); describe("JjVcsDriver", () => { it.effect("detects repository identity with jj root", () => { const calls: VcsProcessInput[] = []; return Effect.gen(function* () { const driver = yield* JjVcsDriver.makeVcsDriverShape(); const identity = yield* driver.detectRepository("/repo/src"); assert.equal(identity?.kind, "jj"); assert.equal(identity?.rootPath, "/repo"); assert.equal(identity?.metadataPath, "/repo/.jj"); assert.deepStrictEqual(commandCalls(calls), [["jj", "--no-pager", "root"]]); }).pipe( Effect.provide( Layer.mergeAll( Layer.mock(VcsProcess)({ run: (input) => Effect.sync(() => { calls.push(input); return processOutput("/repo\n"); }), }), NodeServices.layer, ), ), ); }); it.effect("lists workspace files using jj file list", () => { let observedInput: VcsProcessInput | null = null; return Effect.gen(function* () { const driver = yield* JjVcsDriver.makeVcsDriverShape(); const result = yield* driver.listWorkspaceFiles("/repo"); assert.deepStrictEqual(result.paths, ["README.md", "src/index.ts"]); assert.deepStrictEqual(observedInput?.args, ["--no-pager", "file", "list"]); }).pipe( Effect.provide( Layer.mergeAll( Layer.mock(VcsProcess)({ run: (input) => Effect.sync(() => { observedInput = input; return processOutput("README.md\nsrc/index.ts\n"); }), }), NodeServices.layer, ), ), ); }); it.effect("filters paths with the git ignore oracle", () => { const calls: VcsProcessInput[] = []; return Effect.gen(function* () { const driver = yield* JjVcsDriver.makeVcsDriverShape(); const result = yield* driver.filterIgnoredPaths("/repo", [ "keep.ts", "debug.log", "src/index.ts", ]); assert.deepStrictEqual(result, ["keep.ts", "src/index.ts"]); assert.equal(calls[0]?.command, "git"); assert.deepStrictEqual(calls[0]?.args.slice(-2), ["init", "--bare"]); assert.equal(calls[1]?.command, "git"); assert.deepStrictEqual(calls[1]?.args.slice(-4), [ "check-ignore", "--no-index", "-z", "--stdin", ]); assert.equal(calls[1]?.stdin, "keep.ts\0debug.log\0src/index.ts\0"); }).pipe( Effect.provide( Layer.mergeAll( Layer.mock(VcsProcess)({ run: (input) => Effect.sync(() => { calls.push(input); if (input.command === "git" && input.args.includes("check-ignore")) { return processOutput("debug.log\0"); } return processOutput(""); }), }), NodeServices.layer, ), ), ); }); it.effect("forwards execute env to the VCS process", () => { let observedEnv: NodeJS.ProcessEnv | undefined; return Effect.gen(function* () { const driver = yield* JjVcsDriver.makeVcsDriverShape(); yield* driver.execute({ operation: "JjVcsDriver.test.env", cwd: "/repo", args: ["status"], env: { JJ_CONFIG: "/tmp/t3-jj-config.toml", }, }); assert.deepStrictEqual(observedEnv, { JJ_CONFIG: "/tmp/t3-jj-config.toml", }); }).pipe( Effect.provide( Layer.mergeAll( Layer.mock(VcsProcess)({ run: (input) => Effect.sync(() => { observedEnv = input.env; return processOutput(""); }), }), NodeServices.layer, ), ), ); });});
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, it } from "@effect/vitest";
import { Effect, FileSystem, Layer, Path, PlatformError } from "effect";
import { ChildProcessSpawner } from "effect/unstable/process";
import { describe } from "vitest";
import type { VcsError } from "@t3tools/contracts";
import type { VcsProcessInput, VcsProcessOutput } from "./VcsProcess.ts";
import { VcsProcess, layer as VcsProcessLayer } from "./VcsProcess.ts";
import * as JjVcsDriver from "./JjVcsDriver.ts";
import { runVcsDriverContractSuite } from "./testing/VcsDriverContractHarness.ts";
const JjContractLayer = JjVcsDriver.vcsLayer.pipe(
Layer.provideMerge(VcsProcessLayer),
Layer.provideMerge(NodeServices.layer),
);
const commandCalls = (calls: ReadonlyArray<VcsProcessInput>) =>
calls.map((call) => [call.command].concat(call.args));
const processOutput = (stdout: string, exitCode = 0, stderr = ""): VcsProcessOutput => ({
exitCode: ChildProcessSpawner.ExitCode(exitCode),
stdout,
stderr,
stdoutTruncated: false,
stderrTruncated: false,
});
const runJj = (cwd: string, args: ReadonlyArray<string>) =>
Effect.gen(function* () {
const process = yield* VcsProcess;
yield* process.run({
operation: "JjVcsDriver.contract.jj",
command: "jj",
cwd,
args,
timeoutMs: 10_000,
});
});
type JjContractError = PlatformError.PlatformError | VcsError;
runVcsDriverContractSuite<VcsProcess, JjContractError>({
name: "JJ",
kind: "jj",
layer: JjContractLayer,
fixture: {
createRepo: (cwd) => runJj(cwd, ["git", "init", "--colocate"]),
writeFile: (cwd, relativePath, contents) =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const absolutePath = path.join(cwd, relativePath);
yield* fileSystem.makeDirectory(path.dirname(absolutePath), { recursive: true });
yield* fileSystem.writeFileString(absolutePath, contents);
}),
ignorePath: (cwd, pattern) =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
yield* fileSystem.writeFileString(path.join(cwd, ".gitignore"), `${pattern}\n`);
}),
},
});
describe("JjVcsDriver", () => {
it.effect("detects repository identity with jj root", () => {
const calls: VcsProcessInput[] = [];
return Effect.gen(function* () {
const driver = yield* JjVcsDriver.makeVcsDriverShape();
const identity = yield* driver.detectRepository("/repo/src");
assert.equal(identity?.kind, "jj");
assert.equal(identity?.rootPath, "/repo");
assert.equal(identity?.metadataPath, "/repo/.jj");
assert.deepStrictEqual(commandCalls(calls), [["jj", "--no-pager", "root"]]);
}).pipe(
Effect.provide(
Layer.mergeAll(
Layer.mock(VcsProcess)({
run: (input) =>
Effect.sync(() => {
calls.push(input);
return processOutput("/repo\n");
}),
}),
NodeServices.layer,
),
),
);
});
it.effect("lists workspace files using jj file list", () => {
let observedInput: VcsProcessInput | null = null;
return Effect.gen(function* () {
const driver = yield* JjVcsDriver.makeVcsDriverShape();
const result = yield* driver.listWorkspaceFiles("/repo");
assert.deepStrictEqual(result.paths, ["README.md", "src/index.ts"]);
assert.deepStrictEqual(observedInput?.args, ["--no-pager", "file", "list"]);
}).pipe(
Effect.provide(
Layer.mergeAll(
Layer.mock(VcsProcess)({
run: (input) =>
Effect.sync(() => {
observedInput = input;
return processOutput("README.md\nsrc/index.ts\n");
}),
}),
NodeServices.layer,
),
),
);
});
it.effect("filters paths with the git ignore oracle", () => {
const calls: VcsProcessInput[] = [];
return Effect.gen(function* () {
const driver = yield* JjVcsDriver.makeVcsDriverShape();
const result = yield* driver.filterIgnoredPaths("/repo", [
"keep.ts",
"debug.log",
"src/index.ts",
]);
assert.deepStrictEqual(result, ["keep.ts", "src/index.ts"]);
assert.equal(calls[0]?.command, "git");
assert.deepStrictEqual(calls[0]?.args.slice(-2), ["init", "--bare"]);
assert.equal(calls[1]?.command, "git");
assert.deepStrictEqual(calls[1]?.args.slice(-4), [
"check-ignore",
"--no-index",
"-z",
"--stdin",
]);
assert.equal(calls[1]?.stdin, "keep.ts\0debug.log\0src/index.ts\0");
}).pipe(
Effect.provide(
Layer.mergeAll(
Layer.mock(VcsProcess)({
run: (input) =>
Effect.sync(() => {
calls.push(input);
if (input.command === "git" && input.args.includes("check-ignore")) {
return processOutput("debug.log\0");
}
return processOutput("");
}),
}),
NodeServices.layer,
),
),
);
});
it.effect("forwards execute env to the VCS process", () => {
let observedEnv: NodeJS.ProcessEnv | undefined;
return Effect.gen(function* () {
const driver = yield* JjVcsDriver.makeVcsDriverShape();
yield* driver.execute({
operation: "JjVcsDriver.test.env",
cwd: "/repo",
args: ["status"],
env: {
JJ_CONFIG: "/tmp/t3-jj-config.toml",
},
});
assert.deepStrictEqual(observedEnv, {
JJ_CONFIG: "/tmp/t3-jj-config.toml",
});
}).pipe(
Effect.provide(
Layer.mergeAll(
Layer.mock(VcsProcess)({
run: (input) =>
Effect.sync(() => {
observedEnv = input.env;
return processOutput("");
}),
}),
NodeServices.layer,
),
),
);
});
});