302 lines · 10.6 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path";import * as Sink from "effect/Sink";import * as Stream from "effect/Stream";import { ChildProcessSpawner } from "effect/unstable/process"; import { referenceRepos } from "./lib/reference-repos.ts";import {  planReferenceRepoSync,  resolveReferenceRepoRef,  syncReferenceRepos,} from "./sync-reference-repos.ts"; const encoder = new TextEncoder();const effectSmol = referenceRepos[0]!;const alchemyEffect = referenceRepos[1]!; function mockHandle(  options: {    readonly exitCode?: number;    readonly stdout?: string;    readonly stderr?: string;  } = {},) {  return ChildProcessSpawner.makeHandle({    pid: ChildProcessSpawner.ProcessId(1),    exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(options.exitCode ?? 0)),    isRunning: Effect.succeed(false),    kill: () => Effect.void,    unref: Effect.succeed(Effect.void),    stdin: Sink.drain,    stdout: Stream.make(encoder.encode(options.stdout ?? "done\n")),    stderr: Stream.make(encoder.encode(options.stderr ?? "")),    all: Stream.empty,    getInputFd: () => Sink.drain,    getOutputFd: () => Stream.empty,  });} function mockSpawnerLayer(  commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }>,  handle = mockHandle(),) {  return Layer.succeed(    ChildProcessSpawner.ChildProcessSpawner,    ChildProcessSpawner.make((command) => {      const childProcess = command as unknown as {        readonly command: string;        readonly args: ReadonlyArray<string>;      };      commands.push({        command: childProcess.command,        args: childProcess.args,      });      return Effect.succeed(handle);    }),  );} it.layer(NodeServices.layer)("sync-reference-repos", (it) => {  it.effect("resolves the effect-smol tag from the root catalog", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-version-",      });      yield* fs.writeFileString(        path.join(rootDir, "pnpm-workspace.yaml"),        "catalog:\n  effect: 4.0.0-beta.73\n",      );       assert.equal(        yield* resolveReferenceRepoRef(effectSmol, rootDir, false),        "effect@4.0.0-beta.73",      );    }),  );   it.effect("uses the latest branch without reading package versions", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-latest-",      });       assert.equal(yield* resolveReferenceRepoRef(effectSmol, rootDir, true), "main");    }),  );   it.effect("preserves version source read context and the filesystem cause", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-read-error-",      });      const sourcePath = path.join(rootDir, effectSmol.versionSourcePath);       const error = yield* resolveReferenceRepoRef(effectSmol, rootDir, false).pipe(Effect.flip);       if (error._tag !== "ReferenceRepoVersionSourceError") {        assert.fail(`Unexpected error: ${error._tag}`);      }      assert.equal(error.operation, "read");      assert.equal(error.repoId, effectSmol.id);      assert.equal(error.sourcePath, sourcePath);      assert.ok(error.cause !== undefined);      assert.ok(!error.message.includes(String((error.cause as Error).message)));    }),  );   it.effect("preserves version source parse context and the schema cause", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-parse-error-",      });      const sourcePath = path.join(rootDir, alchemyEffect.versionSourcePath);      yield* fs.makeDirectory(path.dirname(sourcePath), { recursive: true });      yield* fs.writeFileString(sourcePath, "{");       const error = yield* resolveReferenceRepoRef(alchemyEffect, rootDir, false).pipe(Effect.flip);       if (error._tag !== "ReferenceRepoVersionSourceError") {        assert.fail(`Unexpected error: ${error._tag}`);      }      assert.equal(error.operation, "parse");      assert.equal(error.repoId, alchemyEffect.id);      assert.equal(error.sourcePath, sourcePath);      assert.ok(error.cause !== undefined);      assert.ok(!error.message.includes(String((error.cause as Error).message)));    }),  );   it.effect("reports the unresolved package path without inventing a cause", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-resolution-error-",      });      const sourcePath = path.join(rootDir, alchemyEffect.versionSourcePath);      yield* fs.makeDirectory(path.dirname(sourcePath), { recursive: true });      yield* fs.writeFileString(sourcePath, '{"dependencies":{}}');       const error = yield* resolveReferenceRepoRef(alchemyEffect, rootDir, false).pipe(Effect.flip);       if (error._tag !== "ReferenceRepoVersionResolutionError") {        assert.fail(`Unexpected error: ${error._tag}`);      }      assert.equal(error.repoId, alchemyEffect.id);      assert.equal(error.sourcePath, sourcePath);      assert.deepStrictEqual(error.packageVersionPath, ["dependencies", "alchemy"]);      assert.ok(!("cause" in error));    }),  );   it.effect("resolves the alchemy-effect tag from the relay package", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-alchemy-version-",      });      yield* fs.makeDirectory(path.join(rootDir, "infra", "relay"), { recursive: true });      yield* fs.writeFileString(        path.join(rootDir, "infra", "relay", "package.json"),        '{"dependencies":{"alchemy":"2.0.0-beta.49"}}',      );       assert.equal(yield* resolveReferenceRepoRef(alchemyEffect, rootDir, false), "v2.0.0-beta.49");    }),  );   it.effect("plans an add for a missing subtree and a pull for an existing subtree", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-plan-",      });      yield* fs.writeFileString(        path.join(rootDir, "pnpm-workspace.yaml"),        "catalog:\n  effect: 4.0.0-beta.73\n",      );       const addPlan = yield* planReferenceRepoSync(effectSmol, rootDir, false);      assert.equal(addPlan.action, "add");      assert.deepStrictEqual(addPlan.args, [        "subtree",        "add",        "--prefix=.repos/effect-smol",        "https://github.com/Effect-TS/effect-smol.git",        "effect@4.0.0-beta.73",        "--squash",      ]);       yield* fs.makeDirectory(path.join(rootDir, effectSmol.prefix), { recursive: true });      assert.equal((yield* planReferenceRepoSync(effectSmol, rootDir, false)).action, "pull");    }),  );   it.effect("runs the planned git subtree command through the process service", () => {    const commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }> = [];     return Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-run-",      });      yield* fs.writeFileString(        path.join(rootDir, "pnpm-workspace.yaml"),        "catalog:\n  effect: 4.0.0-beta.73\n",      );       yield* syncReferenceRepos({ rootDir, repoId: "effect-smol" }).pipe(        Effect.provide(mockSpawnerLayer(commands)),      );       assert.deepStrictEqual(commands, [        {          command: "git",          args: [            "subtree",            "add",            "--prefix=.repos/effect-smol",            "https://github.com/Effect-TS/effect-smol.git",            "effect@4.0.0-beta.73",            "--squash",          ],        },      ]);    });  });   it.effect("rejects unknown repo selectors", () =>    Effect.gen(function* () {      const error = yield* syncReferenceRepos({        repoId: "missing",        dryRun: true,      }).pipe(Effect.flip);       if (error._tag !== "ReferenceRepoSelectionError") {        assert.fail(`Unexpected error: ${error._tag}`);      }      assert.equal(error.repoId, "missing");      assert.deepStrictEqual(error.expectedRepoIds, ["effect-smol", "alchemy-effect"]);      assert.ok(!("cause" in error));    }),  );   it.effect("reports non-zero git exits without retaining process output", () => {    const commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }> = [];     return Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const rootDir = yield* fs.makeTempDirectoryScoped({        prefix: "sync-reference-repos-exit-error-",      });      yield* fs.writeFileString(        path.join(rootDir, "pnpm-workspace.yaml"),        "catalog:\n  effect: 4.0.0-beta.73\n",      );       const error = yield* syncReferenceRepos({ rootDir, repoId: "effect-smol" }).pipe(        Effect.provide(          mockSpawnerLayer(            commands,            mockHandle({ exitCode: 23, stderr: "subtree failed secret-token-value\n" }),          ),        ),        Effect.flip,      );       if (error._tag !== "ReferenceRepoGitSubtreeError") {        assert.fail(`Unexpected error: ${error._tag}`);      }      assert.equal(error.operation, "exit");      assert.equal(error.repoId, effectSmol.id);      assert.equal(error.action, "add");      assert.equal(error.repository, effectSmol.repository);      assert.equal(error.ref, "effect@4.0.0-beta.73");      assert.equal(error.rootDir, rootDir);      assert.equal(error.argumentCount, commands[0]?.args.length);      assert.equal(error.exitCode, 23);      assert.equal(error.stdoutLength, 5);      assert.equal(error.stderrLength, 34);      assert.notProperty(error, "args");      assert.notProperty(error, "stderr");      assert.notInclude(error.message, "secret-token-value");      assert.ok(!("cause" in error));    });  });});