refactor: apply minimal tfcode branding

- 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.
This commit is contained in:
Gab
2026-03-24 13:19:59 +11:00
parent 8bcbd40e9b
commit a8b73fd754
608 changed files with 26 additions and 32 deletions

View File

@@ -0,0 +1,159 @@
import { afterEach, describe, expect, mock, test } from "bun:test"
import { WorkspaceID } from "../../src/control-plane/schema"
import { Hono } from "hono"
import { tmpdir } from "../fixture/fixture"
import { Project } from "../../src/project/project"
import { WorkspaceTable } from "../../src/control-plane/workspace.sql"
import { Instance } from "../../src/project/instance"
import { WorkspaceContext } from "../../src/control-plane/workspace-context"
import { Database } from "../../src/storage/db"
import { resetDatabase } from "../fixture/db"
import * as adaptors from "../../src/control-plane/adaptors"
import type { Adaptor } from "../../src/control-plane/types"
import { Flag } from "../../src/flag/flag"
afterEach(async () => {
mock.restore()
await resetDatabase()
})
const original = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
// @ts-expect-error don't do this normally, but it works
Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true
afterEach(() => {
// @ts-expect-error don't do this normally, but it works
Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = original
})
type State = {
workspace?: "first" | "second"
calls: Array<{ method: string; url: string; body?: string }>
}
const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert
async function setup(state: State) {
const TestAdaptor: Adaptor = {
configure(config) {
return config
},
async create() {
throw new Error("not used")
},
async remove() {},
async fetch(_config: unknown, input: RequestInfo | URL, init?: RequestInit) {
const url =
input instanceof Request || input instanceof URL
? input.toString()
: new URL(input, "http://workspace.test").toString()
const request = new Request(url, init)
const body = request.method === "GET" || request.method === "HEAD" ? undefined : await request.text()
state.calls.push({
method: request.method,
url: `${new URL(request.url).pathname}${new URL(request.url).search}`,
body,
})
return new Response("proxied", { status: 202 })
},
}
adaptors.installAdaptor("testing", TestAdaptor)
await using tmp = await tmpdir({ git: true })
const { project } = await Project.fromDirectory(tmp.path)
const id1 = WorkspaceID.ascending()
const id2 = WorkspaceID.ascending()
Database.use((db) =>
db
.insert(WorkspaceTable)
.values([
{
id: id1,
branch: "main",
project_id: project.id,
type: remote.type,
name: remote.name,
},
{
id: id2,
branch: "main",
project_id: project.id,
type: "worktree",
directory: tmp.path,
name: "local",
},
])
.run(),
)
const { WorkspaceRouterMiddleware } = await import("../../src/control-plane/workspace-router-middleware")
const app = new Hono().use(WorkspaceRouterMiddleware)
return {
id1,
id2,
app,
async request(input: RequestInfo | URL, init?: RequestInit) {
return Instance.provide({
directory: tmp.path,
fn: async () =>
WorkspaceContext.provide({
workspaceID: state.workspace === "first" ? id1 : id2,
fn: () => app.request(input, init),
}),
})
},
}
}
describe("control-plane/session-proxy-middleware", () => {
test("forwards non-GET session requests for workspaces", async () => {
const state: State = {
workspace: "first",
calls: [],
}
const ctx = await setup(state)
ctx.app.post("/session/foo", (c) => c.text("local", 200))
const response = await ctx.request("http://workspace.test/session/foo?x=1", {
method: "POST",
body: JSON.stringify({ hello: "world" }),
headers: {
"content-type": "application/json",
},
})
expect(response.status).toBe(202)
expect(await response.text()).toBe("proxied")
expect(state.calls).toEqual([
{
method: "POST",
url: "/session/foo?x=1",
body: '{"hello":"world"}',
},
])
})
// It will behave this way when we have syncing
//
// test("does not forward GET requests", async () => {
// const state: State = {
// workspace: "first",
// calls: [],
// }
// const ctx = await setup(state)
// ctx.app.get("/session/foo", (c) => c.text("local", 200))
// const response = await ctx.request("http://workspace.test/session/foo?x=1")
// expect(response.status).toBe(200)
// expect(await response.text()).toBe("local")
// expect(state.calls).toEqual([])
// })
})

View File

@@ -0,0 +1,56 @@
import { afterEach, describe, expect, test } from "bun:test"
import { parseSSE } from "../../src/control-plane/sse"
import { resetDatabase } from "../fixture/db"
afterEach(async () => {
await resetDatabase()
})
function stream(chunks: string[]) {
return new ReadableStream<Uint8Array>({
start(controller) {
const encoder = new TextEncoder()
chunks.forEach((chunk) => controller.enqueue(encoder.encode(chunk)))
controller.close()
},
})
}
describe("control-plane/sse", () => {
test("parses JSON events with CRLF and multiline data blocks", async () => {
const events: unknown[] = []
const stop = new AbortController()
await parseSSE(
stream([
'data: {"type":"one","properties":{"ok":true}}\r\n\r\n',
'data: {"type":"two",\r\ndata: "properties":{"n":2}}\r\n\r\n',
]),
stop.signal,
(event) => events.push(event),
)
expect(events).toEqual([
{ type: "one", properties: { ok: true } },
{ type: "two", properties: { n: 2 } },
])
})
test("falls back to sse.message for non-json payload", async () => {
const events: unknown[] = []
const stop = new AbortController()
await parseSSE(stream(["id: abc\nretry: 1500\ndata: hello world\n\n"]), stop.signal, (event) => events.push(event))
expect(events).toEqual([
{
type: "sse.message",
properties: {
data: "hello world",
id: "abc",
retry: 1500,
},
},
])
})
})

View File

@@ -0,0 +1,70 @@
import { afterEach, describe, expect, test } from "bun:test"
import { Log } from "../../src/util/log"
import { WorkspaceServer } from "../../src/control-plane/workspace-server/server"
import { parseSSE } from "../../src/control-plane/sse"
import { GlobalBus } from "../../src/bus/global"
import { resetDatabase } from "../fixture/db"
import { tmpdir } from "../fixture/fixture"
afterEach(async () => {
await resetDatabase()
})
Log.init({ print: false })
describe("control-plane/workspace-server SSE", () => {
test("streams GlobalBus events and parseSSE reads them", async () => {
await using tmp = await tmpdir({ git: true })
const app = WorkspaceServer.App()
const stop = new AbortController()
const seen: unknown[] = []
try {
const response = await app.request("/event", {
signal: stop.signal,
headers: {
"x-opencode-workspace": "wrk_test_workspace",
"x-opencode-directory": tmp.path,
},
})
expect(response.status).toBe(200)
expect(response.body).toBeDefined()
const done = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("timed out waiting for workspace.test event"))
}, 3000)
void parseSSE(response.body!, stop.signal, (event) => {
seen.push(event)
const next = event as { type?: string }
if (next.type === "server.connected") {
GlobalBus.emit("event", {
payload: {
type: "workspace.test",
properties: { ok: true },
},
})
return
}
if (next.type !== "workspace.test") return
clearTimeout(timeout)
resolve()
}).catch((error) => {
clearTimeout(timeout)
reject(error)
})
})
await done
expect(seen.some((event) => (event as { type?: string }).type === "server.connected")).toBe(true)
expect(seen).toContainEqual({
type: "workspace.test",
properties: { ok: true },
})
} finally {
stop.abort()
}
})
})

View File

@@ -0,0 +1,99 @@
import { afterEach, describe, expect, mock, test } from "bun:test"
import { WorkspaceID } from "../../src/control-plane/schema"
import { Log } from "../../src/util/log"
import { tmpdir } from "../fixture/fixture"
import { Project } from "../../src/project/project"
import { Database } from "../../src/storage/db"
import { WorkspaceTable } from "../../src/control-plane/workspace.sql"
import { GlobalBus } from "../../src/bus/global"
import { resetDatabase } from "../fixture/db"
import * as adaptors from "../../src/control-plane/adaptors"
import type { Adaptor } from "../../src/control-plane/types"
afterEach(async () => {
mock.restore()
await resetDatabase()
})
Log.init({ print: false })
const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert
const TestAdaptor: Adaptor = {
configure(config) {
return config
},
async create() {
throw new Error("not used")
},
async remove() {},
async fetch(_config: unknown, _input: RequestInfo | URL, _init?: RequestInit) {
const body = new ReadableStream<Uint8Array>({
start(controller) {
const encoder = new TextEncoder()
controller.enqueue(encoder.encode('data: {"type":"remote.ready","properties":{}}\n\n'))
controller.close()
},
})
return new Response(body, {
status: 200,
headers: {
"content-type": "text/event-stream",
},
})
},
}
adaptors.installAdaptor("testing", TestAdaptor)
describe("control-plane/workspace.startSyncing", () => {
test("syncs only remote workspaces and emits remote SSE events", async () => {
const { Workspace } = await import("../../src/control-plane/workspace")
await using tmp = await tmpdir({ git: true })
const { project } = await Project.fromDirectory(tmp.path)
const id1 = WorkspaceID.ascending()
const id2 = WorkspaceID.ascending()
Database.use((db) =>
db
.insert(WorkspaceTable)
.values([
{
id: id1,
branch: "main",
project_id: project.id,
type: remote.type,
name: remote.name,
},
{
id: id2,
branch: "main",
project_id: project.id,
type: "worktree",
directory: tmp.path,
name: "local",
},
])
.run(),
)
const done = new Promise<void>((resolve) => {
const listener = (event: { directory?: string; payload: { type: string } }) => {
if (event.directory !== id1) return
if (event.payload.type !== "remote.ready") return
GlobalBus.off("event", listener)
resolve()
}
GlobalBus.on("event", listener)
})
const sync = Workspace.startSyncing(project)
await Promise.race([
done,
new Promise((_, reject) => setTimeout(() => reject(new Error("timed out waiting for sync event")), 2000)),
])
await sync.stop()
})
})