5d5fab36bb
Fixes 257 typecheck errors surfaced after the upstream merge: - Update imports to new module paths after upstream PR #24554 removed module barrel files (@/config, @/session, @/util, etc.) and PR #24309 renamed @opencode-ai/shared to @opencode-ai/core. - Migrate loadMode to use Info.zod.safeParse for consistency with loadAgents and ConfigCommand (unblocks KilocodeConfig.handleInvalid). - Drop removed Npm.outdated mock from tests. - Add missing Config.defaultLayer to bash-permission-metadata test runtime. - Fix Tips component signature (kilocode drops upstream's 'connected' prop). - Fix toast.tsx undefined 'duration' reference (should be toastOptions.duration). - Move filesystem-containment test from deleted shared package to core.
83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
import { $ } from "bun"
|
|
import { describe, expect } from "bun:test"
|
|
import * as fs from "fs/promises"
|
|
import path from "path"
|
|
import { Effect, Layer } from "effect"
|
|
import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner"
|
|
import { Worktree } from "../../src/worktree"
|
|
import { provideTmpdirInstance } from "../fixture/fixture"
|
|
import { testEffect } from "../lib/effect"
|
|
|
|
const it = testEffect(Layer.mergeAll(Worktree.defaultLayer, CrossSpawnSpawner.defaultLayer))
|
|
|
|
describe("Worktree.remove lock retries", () => {
|
|
it.live("retries transient git remove lock failures", () =>
|
|
provideTmpdirInstance(
|
|
(root) =>
|
|
Effect.gen(function* () {
|
|
const svc = yield* Worktree.Service
|
|
const name = `remove-retry-${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")
|
|
const state = path.join(bin, "attempt")
|
|
yield* Effect.promise(() => fs.mkdir(bin, { recursive: true }))
|
|
yield* Effect.promise(() =>
|
|
Bun.write(
|
|
shim,
|
|
[
|
|
"#!/bin/bash",
|
|
`REAL_GIT=${JSON.stringify(real)}`,
|
|
`STATE=${JSON.stringify(state)}`,
|
|
'if [ "$1" = "worktree" ] && [ "$2" = "remove" ] && [ ! -f "$STATE" ]; then',
|
|
' touch "$STATE"',
|
|
' echo "fatal: EBUSY: resource busy or locked, rmdir $4" >&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}`)
|
|
}),
|
|
{ git: true },
|
|
),
|
|
)
|
|
})
|