mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
- Rename packages/opencode → packages/tfcode (directory only) - Rename bin/opencode → bin/tfcode (CLI binary) - Rename .opencode → .tfcode (config directory) - Update package.json name and bin field - Update config directory path references (.tfcode) - Keep internal code references as 'opencode' for easy upstream sync - Keep @opencode-ai/* workspace package names This minimal branding approach allows clean merges from upstream opencode repository while providing tfcode branding for users.
126 lines
4.1 KiB
TypeScript
126 lines
4.1 KiB
TypeScript
import { $ } from "bun"
|
|
import { afterEach, describe, expect, test } from "bun:test"
|
|
import fs from "fs/promises"
|
|
import path from "path"
|
|
import { Effect, Layer, ManagedRuntime } from "effect"
|
|
import { tmpdir } from "../fixture/fixture"
|
|
import { watcherConfigLayer, withServices } from "../fixture/instance"
|
|
import { FileWatcher } from "../../src/file/watcher"
|
|
import { Instance } from "../../src/project/instance"
|
|
import { GlobalBus } from "../../src/bus/global"
|
|
import { Vcs } from "../../src/project/vcs"
|
|
|
|
// Skip in CI — native @parcel/watcher binding needed
|
|
const describeVcs = FileWatcher.hasNativeBinding() && !process.env.CI ? describe : describe.skip
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function withVcs(
|
|
directory: string,
|
|
body: (rt: ManagedRuntime.ManagedRuntime<FileWatcher.Service | Vcs.Service, never>) => Promise<void>,
|
|
) {
|
|
return withServices(
|
|
directory,
|
|
Layer.merge(FileWatcher.layer, Vcs.layer),
|
|
async (rt) => {
|
|
await rt.runPromise(FileWatcher.Service.use((s) => s.init()))
|
|
await rt.runPromise(Vcs.Service.use((s) => s.init()))
|
|
await Bun.sleep(500)
|
|
await body(rt)
|
|
},
|
|
{ provide: [watcherConfigLayer] },
|
|
)
|
|
}
|
|
|
|
type BranchEvent = { directory?: string; payload: { type: string; properties: { branch?: string } } }
|
|
|
|
/** Wait for a Vcs.Event.BranchUpdated event on GlobalBus, with retry polling as fallback */
|
|
function nextBranchUpdate(directory: string, timeout = 10_000) {
|
|
return new Promise<string | undefined>((resolve, reject) => {
|
|
let settled = false
|
|
|
|
const timer = setTimeout(() => {
|
|
if (settled) return
|
|
settled = true
|
|
GlobalBus.off("event", on)
|
|
reject(new Error("timed out waiting for BranchUpdated event"))
|
|
}, timeout)
|
|
|
|
function on(evt: BranchEvent) {
|
|
if (evt.directory !== directory) return
|
|
if (evt.payload.type !== Vcs.Event.BranchUpdated.type) return
|
|
if (settled) return
|
|
settled = true
|
|
clearTimeout(timer)
|
|
GlobalBus.off("event", on)
|
|
resolve(evt.payload.properties.branch)
|
|
}
|
|
|
|
GlobalBus.on("event", on)
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describeVcs("Vcs", () => {
|
|
afterEach(async () => {
|
|
await Instance.disposeAll()
|
|
})
|
|
|
|
test("branch() returns current branch name", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
|
|
await withVcs(tmp.path, async (rt) => {
|
|
const branch = await rt.runPromise(Vcs.Service.use((s) => s.branch()))
|
|
expect(branch).toBeDefined()
|
|
expect(typeof branch).toBe("string")
|
|
})
|
|
})
|
|
|
|
test("branch() returns undefined for non-git directories", async () => {
|
|
await using tmp = await tmpdir()
|
|
|
|
await withVcs(tmp.path, async (rt) => {
|
|
const branch = await rt.runPromise(Vcs.Service.use((s) => s.branch()))
|
|
expect(branch).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
test("publishes BranchUpdated when .git/HEAD changes", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
const branch = `test-${Math.random().toString(36).slice(2)}`
|
|
await $`git branch ${branch}`.cwd(tmp.path).quiet()
|
|
|
|
await withVcs(tmp.path, async () => {
|
|
const pending = nextBranchUpdate(tmp.path)
|
|
|
|
const head = path.join(tmp.path, ".git", "HEAD")
|
|
await fs.writeFile(head, `ref: refs/heads/${branch}\n`)
|
|
|
|
const updated = await pending
|
|
expect(updated).toBe(branch)
|
|
})
|
|
})
|
|
|
|
test("branch() reflects the new branch after HEAD change", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
const branch = `test-${Math.random().toString(36).slice(2)}`
|
|
await $`git branch ${branch}`.cwd(tmp.path).quiet()
|
|
|
|
await withVcs(tmp.path, async (rt) => {
|
|
const pending = nextBranchUpdate(tmp.path)
|
|
|
|
const head = path.join(tmp.path, ".git", "HEAD")
|
|
await fs.writeFile(head, `ref: refs/heads/${branch}\n`)
|
|
|
|
await pending
|
|
const current = await rt.runPromise(Vcs.Service.use((s) => s.branch()))
|
|
expect(current).toBe(branch)
|
|
})
|
|
})
|
|
})
|