summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-13 21:23:15 -0400
committerGitHub <[email protected]>2026-04-13 21:23:15 -0400
commit36745caa2a406bfb817e775c8285efc29d4fba26 (patch)
tree2a7fca6b37820fa11973e139c3ef47037af126d7
parentc2403d0f155b55ad067e742ed6f091312445d24e (diff)
downloadopencode-36745caa2a406bfb817e775c8285efc29d4fba26.tar.gz
opencode-36745caa2a406bfb817e775c8285efc29d4fba26.zip
refactor(worktree): remove async facade exports (#22369)
-rw-r--r--packages/opencode/src/control-plane/adaptors/worktree.ts19
-rw-r--r--packages/opencode/src/server/instance/experimental.ts6
-rw-r--r--packages/opencode/src/worktree/index.ts22
-rw-r--r--packages/opencode/test/project/worktree-remove.test.ts208
-rw-r--r--packages/opencode/test/project/worktree.test.ts297
5 files changed, 303 insertions, 249 deletions
diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts
index 6cc4c20a4..2bfb7deba 100644
--- a/packages/opencode/src/control-plane/adaptors/worktree.ts
+++ b/packages/opencode/src/control-plane/adaptors/worktree.ts
@@ -1,4 +1,5 @@
import z from "zod"
+import { AppRuntime } from "@/effect/app-runtime"
import { Worktree } from "@/worktree"
import { type WorkspaceAdaptor, WorkspaceInfo } from "../types"
@@ -12,7 +13,7 @@ export const WorktreeAdaptor: WorkspaceAdaptor = {
name: "Worktree",
description: "Create a git worktree",
async configure(info) {
- const worktree = await Worktree.makeWorktreeInfo(undefined)
+ const worktree = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.makeWorktreeInfo()))
return {
...info,
name: worktree.name,
@@ -22,15 +23,19 @@ export const WorktreeAdaptor: WorkspaceAdaptor = {
},
async create(info) {
const config = WorktreeConfig.parse(info)
- await Worktree.createFromInfo({
- name: config.name,
- directory: config.directory,
- branch: config.branch,
- })
+ await AppRuntime.runPromise(
+ Worktree.Service.use((svc) =>
+ svc.createFromInfo({
+ name: config.name,
+ directory: config.directory,
+ branch: config.branch,
+ }),
+ ),
+ )
},
async remove(info) {
const config = WorktreeConfig.parse(info)
- await Worktree.remove({ directory: config.directory })
+ await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove({ directory: config.directory })))
},
target(info) {
const config = WorktreeConfig.parse(info)
diff --git a/packages/opencode/src/server/instance/experimental.ts b/packages/opencode/src/server/instance/experimental.ts
index ca8b89fa6..9d2378a8d 100644
--- a/packages/opencode/src/server/instance/experimental.ts
+++ b/packages/opencode/src/server/instance/experimental.ts
@@ -254,7 +254,7 @@ export const ExperimentalRoutes = lazy(() =>
validator("json", Worktree.CreateInput.optional()),
async (c) => {
const body = c.req.valid("json")
- const worktree = await Worktree.create(body)
+ const worktree = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.create(body)))
return c.json(worktree)
},
)
@@ -301,7 +301,7 @@ export const ExperimentalRoutes = lazy(() =>
validator("json", Worktree.RemoveInput),
async (c) => {
const body = c.req.valid("json")
- await Worktree.remove(body)
+ await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove(body)))
await Project.removeSandbox(Instance.project.id, body.directory)
return c.json(true)
},
@@ -327,7 +327,7 @@ export const ExperimentalRoutes = lazy(() =>
validator("json", Worktree.ResetInput),
async (c) => {
const body = c.req.valid("json")
- await Worktree.reset(body)
+ await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.reset(body)))
return c.json(true)
},
)
diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts
index b6430fa6c..18240524a 100644
--- a/packages/opencode/src/worktree/index.ts
+++ b/packages/opencode/src/worktree/index.ts
@@ -18,7 +18,6 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { NodePath } from "@effect/platform-node"
import { AppFileSystem } from "@/filesystem"
import { BootstrapRuntime } from "@/effect/bootstrap-runtime"
-import { makeRuntime } from "@/effect/run-service"
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
import { InstanceState } from "@/effect/instance-state"
@@ -598,25 +597,4 @@ export namespace Worktree {
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(NodePath.layer),
)
- const { runPromise } = makeRuntime(Service, defaultLayer)
-
- export async function makeWorktreeInfo(name?: string) {
- return runPromise((svc) => svc.makeWorktreeInfo(name))
- }
-
- export async function createFromInfo(info: Info, startCommand?: string) {
- return runPromise((svc) => svc.createFromInfo(info, startCommand))
- }
-
- export async function create(input?: CreateInput) {
- return runPromise((svc) => svc.create(input))
- }
-
- export async function remove(input: RemoveInput) {
- return runPromise((svc) => svc.remove(input))
- }
-
- export async function reset(input: ResetInput) {
- return runPromise((svc) => svc.reset(input))
- }
}
diff --git a/packages/opencode/test/project/worktree-remove.test.ts b/packages/opencode/test/project/worktree-remove.test.ts
index a6b5bb7c3..5fb2beb28 100644
--- a/packages/opencode/test/project/worktree-remove.test.ts
+++ b/packages/opencode/test/project/worktree-remove.test.ts
@@ -1,96 +1,126 @@
-import { describe, expect, test } from "bun:test"
import { $ } from "bun"
-import fs from "fs/promises"
+import { describe, expect } from "bun:test"
+import * as fs from "fs/promises"
import path from "path"
-import { Instance } from "../../src/project/instance"
+import { Effect, Layer } from "effect"
+import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
import { Worktree } from "../../src/worktree"
-import { Filesystem } from "../../src/util/filesystem"
-import { tmpdir } from "../fixture/fixture"
+import { provideTmpdirInstance } from "../fixture/fixture"
+import { testEffect } from "../lib/effect"
-const wintest = process.platform === "win32" ? test : test.skip
+const it = testEffect(Layer.mergeAll(Worktree.defaultLayer, CrossSpawnSpawner.defaultLayer))
+const wintest = process.platform === "win32" ? it.live : it.live.skip
describe("Worktree.remove", () => {
- test("continues when git remove exits non-zero after detaching", async () => {
- await using tmp = await tmpdir({ git: true })
- const root = tmp.path
- const name = `remove-regression-${Date.now().toString(36)}`
- const branch = `opencode/${name}`
- const dir = path.join(root, "..", name)
-
- await $`git worktree add --no-checkout -b ${branch} ${dir}`.cwd(root).quiet()
- await $`git reset --hard`.cwd(dir).quiet()
-
- const real = (await $`which git`.quiet().text()).trim()
- expect(real).toBeTruthy()
-
- const bin = path.join(root, "bin")
- const shim = path.join(bin, "git")
- await fs.mkdir(bin, { recursive: true })
- await Bun.write(
- shim,
- [
- "#!/bin/bash",
- `REAL_GIT=${JSON.stringify(real)}`,
- 'if [ "$1" = "worktree" ] && [ "$2" = "remove" ]; then',
- ' "$REAL_GIT" "$@" >/dev/null 2>&1',
- ' echo "fatal: failed to remove worktree: Directory not empty" >&2',
- " exit 1",
- "fi",
- 'exec "$REAL_GIT" "$@"',
- ].join("\n"),
- )
- await fs.chmod(shim, 0o755)
-
- const prev = process.env.PATH ?? ""
- process.env.PATH = `${bin}${path.delimiter}${prev}`
-
- const ok = await (async () => {
- try {
- return await Instance.provide({
- directory: root,
- fn: () => Worktree.remove({ directory: dir }),
- })
- } finally {
- process.env.PATH = prev
- }
- })()
-
- expect(ok).toBe(true)
- expect(await Filesystem.exists(dir)).toBe(false)
-
- const list = await $`git worktree list --porcelain`.cwd(root).quiet().text()
- expect(list).not.toContain(`worktree ${dir}`)
-
- const ref = await $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(root).quiet().nothrow()
- expect(ref.exitCode).not.toBe(0)
- })
-
- wintest("stops fsmonitor before removing a worktree", async () => {
- await using tmp = await tmpdir({ git: true })
- const root = tmp.path
- const name = `remove-fsmonitor-${Date.now().toString(36)}`
- const branch = `opencode/${name}`
- const dir = path.join(root, "..", name)
-
- await $`git worktree add --no-checkout -b ${branch} ${dir}`.cwd(root).quiet()
- await $`git reset --hard`.cwd(dir).quiet()
- await $`git config core.fsmonitor true`.cwd(dir).quiet()
- await $`git fsmonitor--daemon stop`.cwd(dir).quiet().nothrow()
- await Bun.write(path.join(dir, "tracked.txt"), "next\n")
- await $`git diff`.cwd(dir).quiet()
-
- const before = await $`git fsmonitor--daemon status`.cwd(dir).quiet().nothrow()
- expect(before.exitCode).toBe(0)
-
- const ok = await Instance.provide({
- directory: root,
- fn: () => Worktree.remove({ directory: dir }),
- })
-
- expect(ok).toBe(true)
- expect(await Filesystem.exists(dir)).toBe(false)
-
- const ref = await $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(root).quiet().nothrow()
- expect(ref.exitCode).not.toBe(0)
- })
+ it.live("continues when git remove exits non-zero after detaching", () =>
+ provideTmpdirInstance(
+ (root) =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const name = `remove-regression-${Date.now().toString(36)}`
+ const branch = `opencode/${name}`
+ const dir = path.join(root, "..", name)
+
+ yield* Effect.promise(() => $`git worktree add --no-checkout -b ${branch} ${dir}`.cwd(root).quiet())
+ yield* Effect.promise(() => $`git reset --hard`.cwd(dir).quiet())
+
+ const real = (yield* Effect.promise(() => $`which git`.quiet().text())).trim()
+ expect(real).toBeTruthy()
+
+ const bin = path.join(root, "bin")
+ const shim = path.join(bin, "git")
+ yield* Effect.promise(() => fs.mkdir(bin, { recursive: true }))
+ yield* Effect.promise(() =>
+ Bun.write(
+ shim,
+ [
+ "#!/bin/bash",
+ `REAL_GIT=${JSON.stringify(real)}`,
+ 'if [ "$1" = "worktree" ] && [ "$2" = "remove" ]; then',
+ ' "$REAL_GIT" "$@" >/dev/null 2>&1',
+ ' echo "fatal: failed to remove worktree: Directory not empty" >&2',
+ " exit 1",
+ "fi",
+ 'exec "$REAL_GIT" "$@"',
+ ].join("\n"),
+ ),
+ )
+ yield* Effect.promise(() => fs.chmod(shim, 0o755))
+
+ const prev = yield* Effect.acquireRelease(
+ Effect.sync(() => {
+ const prev = process.env.PATH ?? ""
+ process.env.PATH = `${bin}${path.delimiter}${prev}`
+ return prev
+ }),
+ (prev) =>
+ Effect.sync(() => {
+ process.env.PATH = prev
+ }),
+ )
+ void prev
+
+ const ok = yield* svc.remove({ directory: dir })
+
+ expect(ok).toBe(true)
+ expect(
+ yield* Effect.promise(() =>
+ fs
+ .stat(dir)
+ .then(() => true)
+ .catch(() => false),
+ ),
+ ).toBe(false)
+
+ const list = yield* Effect.promise(() => $`git worktree list --porcelain`.cwd(root).quiet().text())
+ expect(list).not.toContain(`worktree ${dir}`)
+
+ const ref = yield* Effect.promise(() =>
+ $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(root).quiet().nothrow(),
+ )
+ expect(ref.exitCode).not.toBe(0)
+ }),
+ { git: true },
+ ),
+ )
+
+ wintest("stops fsmonitor before removing a worktree", () =>
+ provideTmpdirInstance(
+ (root) =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const name = `remove-fsmonitor-${Date.now().toString(36)}`
+ const branch = `opencode/${name}`
+ const dir = path.join(root, "..", name)
+
+ yield* Effect.promise(() => $`git worktree add --no-checkout -b ${branch} ${dir}`.cwd(root).quiet())
+ yield* Effect.promise(() => $`git reset --hard`.cwd(dir).quiet())
+ yield* Effect.promise(() => $`git config core.fsmonitor true`.cwd(dir).quiet())
+ yield* Effect.promise(() => $`git fsmonitor--daemon stop`.cwd(dir).quiet().nothrow())
+ yield* Effect.promise(() => Bun.write(path.join(dir, "tracked.txt"), "next\n"))
+ yield* Effect.promise(() => $`git diff`.cwd(dir).quiet())
+
+ const before = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(dir).quiet().nothrow())
+ expect(before.exitCode).toBe(0)
+
+ const ok = yield* svc.remove({ directory: dir })
+
+ expect(ok).toBe(true)
+ expect(
+ yield* Effect.promise(() =>
+ fs
+ .stat(dir)
+ .then(() => true)
+ .catch(() => false),
+ ),
+ ).toBe(false)
+
+ const ref = yield* Effect.promise(() =>
+ $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(root).quiet().nothrow(),
+ )
+ expect(ref.exitCode).not.toBe(0)
+ }),
+ { git: true },
+ ),
+ )
})
diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts
index dd91c772a..c0fe63551 100644
--- a/packages/opencode/test/project/worktree.test.ts
+++ b/packages/opencode/test/project/worktree.test.ts
@@ -1,16 +1,16 @@
import { $ } from "bun"
-import { afterEach, describe, expect, test } from "bun:test"
-
-const wintest = process.platform !== "win32" ? test : test.skip
-import fs from "fs/promises"
+import { afterEach, describe, expect } from "bun:test"
+import * as fs from "fs/promises"
import path from "path"
+import { Cause, Effect, Exit, Layer } from "effect"
+import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
import { Instance } from "../../src/project/instance"
import { Worktree } from "../../src/worktree"
-import { tmpdir } from "../fixture/fixture"
+import { provideInstance, provideTmpdirInstance } from "../fixture/fixture"
+import { testEffect } from "../lib/effect"
-function withInstance(directory: string, fn: () => Promise<any>) {
- return Instance.provide({ directory, fn })
-}
+const it = testEffect(Layer.mergeAll(Worktree.defaultLayer, CrossSpawnSpawner.defaultLayer))
+const wintest = process.platform !== "win32" ? it.live : it.live.skip
function normalize(input: string) {
return input.replace(/\\/g, "/").toLowerCase()
@@ -40,134 +40,175 @@ describe("Worktree", () => {
afterEach(() => Instance.disposeAll())
describe("makeWorktreeInfo", () => {
- test("returns info with name, branch, and directory", async () => {
- await using tmp = await tmpdir({ git: true })
-
- const info = await withInstance(tmp.path, () => Worktree.makeWorktreeInfo())
-
- expect(info.name).toBeDefined()
- expect(typeof info.name).toBe("string")
- expect(info.branch).toBe(`opencode/${info.name}`)
- expect(info.directory).toContain(info.name)
- })
-
- test("uses provided name as base", async () => {
- await using tmp = await tmpdir({ git: true })
-
- const info = await withInstance(tmp.path, () => Worktree.makeWorktreeInfo("my-feature"))
-
- expect(info.name).toBe("my-feature")
- expect(info.branch).toBe("opencode/my-feature")
- })
-
- test("slugifies the provided name", async () => {
- await using tmp = await tmpdir({ git: true })
-
- const info = await withInstance(tmp.path, () => Worktree.makeWorktreeInfo("My Feature Branch!"))
-
- expect(info.name).toBe("my-feature-branch")
- })
-
- test("throws NotGitError for non-git directories", async () => {
- await using tmp = await tmpdir()
-
- await expect(withInstance(tmp.path, () => Worktree.makeWorktreeInfo())).rejects.toThrow("WorktreeNotGitError")
- })
+ it.live("returns info with name, branch, and directory", () =>
+ provideTmpdirInstance(
+ () =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const info = yield* svc.makeWorktreeInfo()
+
+ expect(info.name).toBeDefined()
+ expect(typeof info.name).toBe("string")
+ expect(info.branch).toBe(`opencode/${info.name}`)
+ expect(info.directory).toContain(info.name)
+ }),
+ { git: true },
+ ),
+ )
+
+ it.live("uses provided name as base", () =>
+ provideTmpdirInstance(
+ () =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const info = yield* svc.makeWorktreeInfo("my-feature")
+
+ expect(info.name).toBe("my-feature")
+ expect(info.branch).toBe("opencode/my-feature")
+ }),
+ { git: true },
+ ),
+ )
+
+ it.live("slugifies the provided name", () =>
+ provideTmpdirInstance(
+ () =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const info = yield* svc.makeWorktreeInfo("My Feature Branch!")
+
+ expect(info.name).toBe("my-feature-branch")
+ }),
+ { git: true },
+ ),
+ )
+
+ it.live("throws NotGitError for non-git directories", () =>
+ provideTmpdirInstance(() =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const exit = yield* Effect.exit(svc.makeWorktreeInfo())
+
+ expect(Exit.isFailure(exit)).toBe(true)
+ if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Worktree.NotGitError)
+ }),
+ ),
+ )
})
describe("create + remove lifecycle", () => {
- test("create returns worktree info and remove cleans up", async () => {
- await using tmp = await tmpdir({ git: true })
-
- const info = await withInstance(tmp.path, () => Worktree.create())
-
- expect(info.name).toBeDefined()
- expect(info.branch).toStartWith("opencode/")
- expect(info.directory).toBeDefined()
-
- // Wait for bootstrap to complete
- await Bun.sleep(1000)
-
- const ok = await withInstance(tmp.path, () => Worktree.remove({ directory: info.directory }))
- expect(ok).toBe(true)
- })
-
- test("create returns after setup and fires Event.Ready after bootstrap", async () => {
- await using tmp = await tmpdir({ git: true })
- const ready = waitReady()
-
- const info = await withInstance(tmp.path, () => Worktree.create())
-
- // create returns before bootstrap completes, but the worktree already exists
- expect(info.name).toBeDefined()
- expect(info.branch).toStartWith("opencode/")
-
- const text = await $`git worktree list --porcelain`.cwd(tmp.path).quiet().text()
- const dir = await fs.realpath(info.directory).catch(() => info.directory)
- expect(normalize(text)).toContain(normalize(dir))
-
- // Event.Ready fires after bootstrap finishes in the background
- const props = await ready
- expect(props.name).toBe(info.name)
- expect(props.branch).toBe(info.branch)
-
- // Cleanup
- await withInstance(info.directory, () => Instance.dispose())
- await Bun.sleep(100)
- await withInstance(tmp.path, () => Worktree.remove({ directory: info.directory }))
- })
-
- test("create with custom name", async () => {
- await using tmp = await tmpdir({ git: true })
- const ready = waitReady()
-
- const info = await withInstance(tmp.path, () => Worktree.create({ name: "test-workspace" }))
-
- expect(info.name).toBe("test-workspace")
- expect(info.branch).toBe("opencode/test-workspace")
-
- // Cleanup
- await ready
- await withInstance(info.directory, () => Instance.dispose())
- await Bun.sleep(100)
- await withInstance(tmp.path, () => Worktree.remove({ directory: info.directory }))
- })
+ it.live("create returns worktree info and remove cleans up", () =>
+ provideTmpdirInstance(
+ () =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const info = yield* svc.create()
+
+ expect(info.name).toBeDefined()
+ expect(info.branch).toStartWith("opencode/")
+ expect(info.directory).toBeDefined()
+
+ yield* Effect.promise(() => Bun.sleep(1000))
+
+ const ok = yield* svc.remove({ directory: info.directory })
+ expect(ok).toBe(true)
+ }),
+ { git: true },
+ ),
+ )
+
+ it.live("create returns after setup and fires Event.Ready after bootstrap", () =>
+ provideTmpdirInstance(
+ (dir) =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const ready = waitReady()
+ const info = yield* svc.create()
+
+ expect(info.name).toBeDefined()
+ expect(info.branch).toStartWith("opencode/")
+
+ const text = yield* Effect.promise(() => $`git worktree list --porcelain`.cwd(dir).quiet().text())
+ const next = yield* Effect.promise(() => fs.realpath(info.directory).catch(() => info.directory))
+ expect(normalize(text)).toContain(normalize(next))
+
+ const props = yield* Effect.promise(() => ready)
+ expect(props.name).toBe(info.name)
+ expect(props.branch).toBe(info.branch)
+
+ yield* Effect.promise(() => Instance.dispose()).pipe(provideInstance(info.directory))
+ yield* Effect.promise(() => Bun.sleep(100))
+ yield* svc.remove({ directory: info.directory })
+ }),
+ { git: true },
+ ),
+ )
+
+ it.live("create with custom name", () =>
+ provideTmpdirInstance(
+ () =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const ready = waitReady()
+ const info = yield* svc.create({ name: "test-workspace" })
+
+ expect(info.name).toBe("test-workspace")
+ expect(info.branch).toBe("opencode/test-workspace")
+
+ yield* Effect.promise(() => ready)
+ yield* Effect.promise(() => Instance.dispose()).pipe(provideInstance(info.directory))
+ yield* Effect.promise(() => Bun.sleep(100))
+ yield* svc.remove({ directory: info.directory })
+ }),
+ { git: true },
+ ),
+ )
})
describe("createFromInfo", () => {
- wintest("creates and bootstraps git worktree", async () => {
- await using tmp = await tmpdir({ git: true })
-
- const info = await withInstance(tmp.path, () => Worktree.makeWorktreeInfo("from-info-test"))
- await withInstance(tmp.path, () => Worktree.createFromInfo(info))
-
- // Worktree should exist in git (normalize slashes for Windows)
- const list = await $`git worktree list --porcelain`.cwd(tmp.path).quiet().text()
- const normalizedList = list.replace(/\\/g, "/")
- const normalizedDir = info.directory.replace(/\\/g, "/")
- expect(normalizedList).toContain(normalizedDir)
-
- // Cleanup
- await withInstance(tmp.path, () => Worktree.remove({ directory: info.directory }))
- })
+ wintest("creates and bootstraps git worktree", () =>
+ provideTmpdirInstance(
+ (dir) =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const info = yield* svc.makeWorktreeInfo("from-info-test")
+ yield* svc.createFromInfo(info)
+
+ const list = yield* Effect.promise(() => $`git worktree list --porcelain`.cwd(dir).quiet().text())
+ const normalizedList = list.replace(/\\/g, "/")
+ const normalizedDir = info.directory.replace(/\\/g, "/")
+ expect(normalizedList).toContain(normalizedDir)
+
+ yield* svc.remove({ directory: info.directory })
+ }),
+ { git: true },
+ ),
+ )
})
describe("remove edge cases", () => {
- test("remove non-existent directory succeeds silently", async () => {
- await using tmp = await tmpdir({ git: true })
-
- const ok = await withInstance(tmp.path, () =>
- Worktree.remove({ directory: path.join(tmp.path, "does-not-exist") }),
- )
- expect(ok).toBe(true)
- })
-
- test("throws NotGitError for non-git directories", async () => {
- await using tmp = await tmpdir()
-
- await expect(withInstance(tmp.path, () => Worktree.remove({ directory: "/tmp/fake" }))).rejects.toThrow(
- "WorktreeNotGitError",
- )
- })
+ it.live("remove non-existent directory succeeds silently", () =>
+ provideTmpdirInstance(
+ (dir) =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const ok = yield* svc.remove({ directory: path.join(dir, "does-not-exist") })
+ expect(ok).toBe(true)
+ }),
+ { git: true },
+ ),
+ )
+
+ it.live("throws NotGitError for non-git directories", () =>
+ provideTmpdirInstance(() =>
+ Effect.gen(function* () {
+ const svc = yield* Worktree.Service
+ const exit = yield* Effect.exit(svc.remove({ directory: "/tmp/fake" }))
+
+ expect(Exit.isFailure(exit)).toBe(true)
+ if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Worktree.NotGitError)
+ }),
+ ),
+ )
})
})