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,14 @@
import { describe, expect, test } from "bun:test"
import { decodeDataUrl } from "../../src/util/data-url"
describe("decodeDataUrl", () => {
test("decodes base64 data URLs", () => {
const body = '{\n "ok": true\n}\n'
const url = `data:text/plain;base64,${Buffer.from(body).toString("base64")}`
expect(decodeDataUrl(url)).toBe(body)
})
test("decodes plain data URLs", () => {
expect(decodeDataUrl("data:text/plain,hello%20world")).toBe("hello world")
})
})

View File

@@ -0,0 +1,61 @@
import { describe, expect, test } from "bun:test"
import { Schema } from "effect"
import { zod } from "../../src/util/effect-zod"
describe("util.effect-zod", () => {
test("converts class schemas for route dto shapes", () => {
class Method extends Schema.Class<Method>("ProviderAuthMethod")({
type: Schema.Union([Schema.Literal("oauth"), Schema.Literal("api")]),
label: Schema.String,
}) {}
const out = zod(Method)
expect(out.meta()?.ref).toBe("ProviderAuthMethod")
expect(
out.parse({
type: "oauth",
label: "OAuth",
}),
).toEqual({
type: "oauth",
label: "OAuth",
})
})
test("converts structs with optional fields, arrays, and records", () => {
const out = zod(
Schema.Struct({
foo: Schema.optional(Schema.String),
bar: Schema.Array(Schema.Number),
baz: Schema.Record(Schema.String, Schema.Boolean),
}),
)
expect(
out.parse({
bar: [1, 2],
baz: { ok: true },
}),
).toEqual({
bar: [1, 2],
baz: { ok: true },
})
expect(
out.parse({
foo: "hi",
bar: [1],
baz: { ok: false },
}),
).toEqual({
foo: "hi",
bar: [1],
baz: { ok: false },
})
})
test("throws for unsupported tuple schemas", () => {
expect(() => zod(Schema.Tuple([Schema.String, Schema.Number]))).toThrow("unsupported effect schema")
})
})

View File

@@ -0,0 +1,558 @@
import { describe, test, expect } from "bun:test"
import path from "path"
import fs from "fs/promises"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
describe("filesystem", () => {
describe("exists()", () => {
test("returns true for existing file", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.txt")
await fs.writeFile(filepath, "content", "utf-8")
expect(await Filesystem.exists(filepath)).toBe(true)
})
test("returns false for non-existent file", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "does-not-exist.txt")
expect(await Filesystem.exists(filepath)).toBe(false)
})
test("returns true for existing directory", async () => {
await using tmp = await tmpdir()
const dirpath = path.join(tmp.path, "subdir")
await fs.mkdir(dirpath)
expect(await Filesystem.exists(dirpath)).toBe(true)
})
})
describe("isDir()", () => {
test("returns true for directory", async () => {
await using tmp = await tmpdir()
const dirpath = path.join(tmp.path, "testdir")
await fs.mkdir(dirpath)
expect(await Filesystem.isDir(dirpath)).toBe(true)
})
test("returns false for file", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.txt")
await fs.writeFile(filepath, "content", "utf-8")
expect(await Filesystem.isDir(filepath)).toBe(false)
})
test("returns false for non-existent path", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "does-not-exist")
expect(await Filesystem.isDir(filepath)).toBe(false)
})
})
describe("size()", () => {
test("returns file size", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.txt")
const content = "Hello, World!"
await fs.writeFile(filepath, content, "utf-8")
expect(await Filesystem.size(filepath)).toBe(content.length)
})
test("returns 0 for non-existent file", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "does-not-exist.txt")
expect(await Filesystem.size(filepath)).toBe(0)
})
test("returns directory size", async () => {
await using tmp = await tmpdir()
const dirpath = path.join(tmp.path, "testdir")
await fs.mkdir(dirpath)
// Directories have size on some systems
const size = await Filesystem.size(dirpath)
expect(typeof size).toBe("number")
})
})
describe("readText()", () => {
test("reads file content", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.txt")
const content = "Hello, World!"
await fs.writeFile(filepath, content, "utf-8")
expect(await Filesystem.readText(filepath)).toBe(content)
})
test("throws for non-existent file", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "does-not-exist.txt")
await expect(Filesystem.readText(filepath)).rejects.toThrow()
})
test("reads UTF-8 content correctly", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "unicode.txt")
const content = "Hello 世界 🌍"
await fs.writeFile(filepath, content, "utf-8")
expect(await Filesystem.readText(filepath)).toBe(content)
})
})
describe("readJson()", () => {
test("reads and parses JSON", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.json")
const data = { key: "value", nested: { array: [1, 2, 3] } }
await fs.writeFile(filepath, JSON.stringify(data), "utf-8")
const result: typeof data = await Filesystem.readJson(filepath)
expect(result).toEqual(data)
})
test("throws for invalid JSON", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "invalid.json")
await fs.writeFile(filepath, "{ invalid json", "utf-8")
await expect(Filesystem.readJson(filepath)).rejects.toThrow()
})
test("throws for non-existent file", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "does-not-exist.json")
await expect(Filesystem.readJson(filepath)).rejects.toThrow()
})
test("returns typed data", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "typed.json")
interface Config {
name: string
version: number
}
const data: Config = { name: "test", version: 1 }
await fs.writeFile(filepath, JSON.stringify(data), "utf-8")
const result = await Filesystem.readJson<Config>(filepath)
expect(result.name).toBe("test")
expect(result.version).toBe(1)
})
})
describe("readBytes()", () => {
test("reads file as buffer", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.txt")
const content = "Hello, World!"
await fs.writeFile(filepath, content, "utf-8")
const buffer = await Filesystem.readBytes(filepath)
expect(buffer).toBeInstanceOf(Buffer)
expect(buffer.toString("utf-8")).toBe(content)
})
test("throws for non-existent file", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "does-not-exist.bin")
await expect(Filesystem.readBytes(filepath)).rejects.toThrow()
})
})
describe("write()", () => {
test("writes text content", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.txt")
const content = "Hello, World!"
await Filesystem.write(filepath, content)
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
test("writes buffer content", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.bin")
const content = Buffer.from([0x00, 0x01, 0x02, 0x03])
await Filesystem.write(filepath, content)
const read = await fs.readFile(filepath)
expect(read).toEqual(content)
})
test("writes with permissions", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "protected.txt")
const content = "secret"
await Filesystem.write(filepath, content, 0o600)
const stats = await fs.stat(filepath)
// Check permissions on Unix
if (process.platform !== "win32") {
expect(stats.mode & 0o777).toBe(0o600)
}
})
test("creates parent directories", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "nested", "deep", "file.txt")
const content = "nested content"
await Filesystem.write(filepath, content)
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
})
describe("writeJson()", () => {
test("writes JSON data", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "data.json")
const data = { key: "value", number: 42 }
await Filesystem.writeJson(filepath, data)
const content = await fs.readFile(filepath, "utf-8")
expect(JSON.parse(content)).toEqual(data)
})
test("writes formatted JSON", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "pretty.json")
const data = { key: "value" }
await Filesystem.writeJson(filepath, data)
const content = await fs.readFile(filepath, "utf-8")
expect(content).toContain("\n")
expect(content).toContain(" ")
})
test("writes with permissions", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "config.json")
const data = { secret: "data" }
await Filesystem.writeJson(filepath, data, 0o600)
const stats = await fs.stat(filepath)
if (process.platform !== "win32") {
expect(stats.mode & 0o777).toBe(0o600)
}
})
})
describe("mimeType()", () => {
test("returns correct MIME type for JSON", () => {
expect(Filesystem.mimeType("test.json")).toContain("application/json")
})
test("returns correct MIME type for JavaScript", () => {
expect(Filesystem.mimeType("test.js")).toContain("javascript")
})
test("returns MIME type for TypeScript (or video/mp2t due to extension conflict)", () => {
const mime = Filesystem.mimeType("test.ts")
// .ts is ambiguous: TypeScript vs MPEG-2 TS video
expect(mime === "video/mp2t" || mime === "application/typescript" || mime === "text/typescript").toBe(true)
})
test("returns correct MIME type for images", () => {
expect(Filesystem.mimeType("test.png")).toContain("image/png")
expect(Filesystem.mimeType("test.jpg")).toContain("image/jpeg")
})
test("returns default for unknown extension", () => {
expect(Filesystem.mimeType("test.unknown")).toBe("application/octet-stream")
})
test("handles files without extension", () => {
expect(Filesystem.mimeType("Makefile")).toBe("application/octet-stream")
})
})
describe("windowsPath()", () => {
test("converts Git Bash paths", () => {
if (process.platform === "win32") {
expect(Filesystem.windowsPath("/c/Users/test")).toBe("C:/Users/test")
expect(Filesystem.windowsPath("/d/dev/project")).toBe("D:/dev/project")
} else {
expect(Filesystem.windowsPath("/c/Users/test")).toBe("/c/Users/test")
}
})
test("converts Cygwin paths", () => {
if (process.platform === "win32") {
expect(Filesystem.windowsPath("/cygdrive/c/Users/test")).toBe("C:/Users/test")
expect(Filesystem.windowsPath("/cygdrive/x/dev/project")).toBe("X:/dev/project")
} else {
expect(Filesystem.windowsPath("/cygdrive/c/Users/test")).toBe("/cygdrive/c/Users/test")
}
})
test("converts WSL paths", () => {
if (process.platform === "win32") {
expect(Filesystem.windowsPath("/mnt/c/Users/test")).toBe("C:/Users/test")
expect(Filesystem.windowsPath("/mnt/z/dev/project")).toBe("Z:/dev/project")
} else {
expect(Filesystem.windowsPath("/mnt/c/Users/test")).toBe("/mnt/c/Users/test")
}
})
test("ignores normal Windows paths", () => {
expect(Filesystem.windowsPath("C:/Users/test")).toBe("C:/Users/test")
expect(Filesystem.windowsPath("D:\\dev\\project")).toBe("D:\\dev\\project")
})
})
describe("writeStream()", () => {
test("writes from Web ReadableStream", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "streamed.txt")
const content = "Hello from stream!"
const encoder = new TextEncoder()
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(content))
controller.close()
},
})
await Filesystem.writeStream(filepath, stream)
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
test("writes from Node.js Readable stream", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "node-streamed.txt")
const content = "Hello from Node stream!"
const { Readable } = await import("stream")
const stream = Readable.from([content])
await Filesystem.writeStream(filepath, stream)
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
test("writes binary data from Web ReadableStream", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "binary.dat")
const binaryData = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0xff])
const stream = new ReadableStream({
start(controller) {
controller.enqueue(binaryData)
controller.close()
},
})
await Filesystem.writeStream(filepath, stream)
const read = await fs.readFile(filepath)
expect(Buffer.from(read)).toEqual(Buffer.from(binaryData))
})
test("writes large content in chunks", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "large.txt")
const chunks = ["chunk1", "chunk2", "chunk3", "chunk4", "chunk5"]
const stream = new ReadableStream({
start(controller) {
for (const chunk of chunks) {
controller.enqueue(new TextEncoder().encode(chunk))
}
controller.close()
},
})
await Filesystem.writeStream(filepath, stream)
expect(await fs.readFile(filepath, "utf-8")).toBe(chunks.join(""))
})
test("creates parent directories", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "nested", "deep", "streamed.txt")
const content = "nested stream content"
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(content))
controller.close()
},
})
await Filesystem.writeStream(filepath, stream)
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
test("writes with permissions", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "protected-stream.txt")
const content = "secret stream content"
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(content))
controller.close()
},
})
await Filesystem.writeStream(filepath, stream, 0o600)
const stats = await fs.stat(filepath)
if (process.platform !== "win32") {
expect(stats.mode & 0o777).toBe(0o600)
}
})
test("writes executable with permissions", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "script.sh")
const content = "#!/bin/bash\necho hello"
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(content))
controller.close()
},
})
await Filesystem.writeStream(filepath, stream, 0o755)
const stats = await fs.stat(filepath)
if (process.platform !== "win32") {
expect(stats.mode & 0o777).toBe(0o755)
}
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
})
describe("resolve()", () => {
test("resolves slash-prefixed drive paths on Windows", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const forward = tmp.path.replaceAll("\\", "/")
expect(Filesystem.resolve(`/${forward}`)).toBe(Filesystem.normalizePath(tmp.path))
})
test("resolves slash-prefixed drive roots on Windows", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const drive = tmp.path[0].toUpperCase()
expect(Filesystem.resolve(`/${drive}:`)).toBe(Filesystem.resolve(`${drive}:/`))
})
test("resolves Git Bash and MSYS2 paths on Windows", async () => {
// Git Bash and MSYS2 both use /<drive>/... paths on Windows.
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const drive = tmp.path[0].toLowerCase()
const rest = tmp.path.slice(2).replaceAll("\\", "/")
expect(Filesystem.resolve(`/${drive}${rest}`)).toBe(Filesystem.normalizePath(tmp.path))
})
test("resolves Git Bash and MSYS2 drive roots on Windows", async () => {
// Git Bash and MSYS2 both use /<drive> paths on Windows.
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const drive = tmp.path[0].toLowerCase()
expect(Filesystem.resolve(`/${drive}`)).toBe(Filesystem.resolve(`${drive.toUpperCase()}:/`))
})
test("resolves Cygwin paths on Windows", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const drive = tmp.path[0].toLowerCase()
const rest = tmp.path.slice(2).replaceAll("\\", "/")
expect(Filesystem.resolve(`/cygdrive/${drive}${rest}`)).toBe(Filesystem.normalizePath(tmp.path))
})
test("resolves Cygwin drive roots on Windows", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const drive = tmp.path[0].toLowerCase()
expect(Filesystem.resolve(`/cygdrive/${drive}`)).toBe(Filesystem.resolve(`${drive.toUpperCase()}:/`))
})
test("resolves WSL mount paths on Windows", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const drive = tmp.path[0].toLowerCase()
const rest = tmp.path.slice(2).replaceAll("\\", "/")
expect(Filesystem.resolve(`/mnt/${drive}${rest}`)).toBe(Filesystem.normalizePath(tmp.path))
})
test("resolves WSL mount roots on Windows", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const drive = tmp.path[0].toLowerCase()
expect(Filesystem.resolve(`/mnt/${drive}`)).toBe(Filesystem.resolve(`${drive.toUpperCase()}:/`))
})
test("resolves symlinked directory to canonical path", async () => {
await using tmp = await tmpdir()
const target = path.join(tmp.path, "real")
await fs.mkdir(target)
const link = path.join(tmp.path, "link")
await fs.symlink(target, link)
expect(Filesystem.resolve(link)).toBe(Filesystem.resolve(target))
})
test("returns unresolved path when target does not exist", async () => {
await using tmp = await tmpdir()
const missing = path.join(tmp.path, "does-not-exist-" + Date.now())
const result = Filesystem.resolve(missing)
expect(result).toBe(Filesystem.normalizePath(path.resolve(missing)))
})
test("throws ELOOP on symlink cycle", async () => {
await using tmp = await tmpdir()
const a = path.join(tmp.path, "a")
const b = path.join(tmp.path, "b")
await fs.symlink(b, a)
await fs.symlink(a, b)
expect(() => Filesystem.resolve(a)).toThrow()
})
// Windows: chmod(0o000) is a no-op, so EACCES cannot be triggered
test("throws EACCES on permission-denied symlink target", async () => {
if (process.platform === "win32") return
if (process.getuid?.() === 0) return // skip when running as root
await using tmp = await tmpdir()
const dir = path.join(tmp.path, "restricted")
await fs.mkdir(dir)
const link = path.join(tmp.path, "link")
await fs.symlink(dir, link)
await fs.chmod(dir, 0o000)
try {
expect(() => Filesystem.resolve(path.join(link, "child"))).toThrow()
} finally {
await fs.chmod(dir, 0o755)
}
})
// Windows: traversing through a file throws ENOENT (not ENOTDIR),
// which resolve() catches as a fallback instead of rethrowing
test("rethrows non-ENOENT errors", async () => {
if (process.platform === "win32") return
await using tmp = await tmpdir()
const file = path.join(tmp.path, "not-a-directory")
await fs.writeFile(file, "x")
expect(() => Filesystem.resolve(path.join(file, "child"))).toThrow()
})
})
})

View File

@@ -0,0 +1,59 @@
import { describe, expect, test } from "bun:test"
import { formatDuration } from "../../src/util/format"
describe("util.format", () => {
describe("formatDuration", () => {
test("returns empty string for zero or negative values", () => {
expect(formatDuration(0)).toBe("")
expect(formatDuration(-1)).toBe("")
expect(formatDuration(-100)).toBe("")
})
test("formats seconds under a minute", () => {
expect(formatDuration(1)).toBe("1s")
expect(formatDuration(30)).toBe("30s")
expect(formatDuration(59)).toBe("59s")
})
test("formats minutes under an hour", () => {
expect(formatDuration(60)).toBe("1m")
expect(formatDuration(61)).toBe("1m 1s")
expect(formatDuration(90)).toBe("1m 30s")
expect(formatDuration(120)).toBe("2m")
expect(formatDuration(330)).toBe("5m 30s")
expect(formatDuration(3599)).toBe("59m 59s")
})
test("formats hours under a day", () => {
expect(formatDuration(3600)).toBe("1h")
expect(formatDuration(3660)).toBe("1h 1m")
expect(formatDuration(7200)).toBe("2h")
expect(formatDuration(8100)).toBe("2h 15m")
expect(formatDuration(86399)).toBe("23h 59m")
})
test("formats days under a week", () => {
expect(formatDuration(86400)).toBe("~1 day")
expect(formatDuration(172800)).toBe("~2 days")
expect(formatDuration(259200)).toBe("~3 days")
expect(formatDuration(604799)).toBe("~6 days")
})
test("formats weeks", () => {
expect(formatDuration(604800)).toBe("~1 week")
expect(formatDuration(1209600)).toBe("~2 weeks")
expect(formatDuration(1609200)).toBe("~2 weeks")
})
test("handles boundary values correctly", () => {
expect(formatDuration(59)).toBe("59s")
expect(formatDuration(60)).toBe("1m")
expect(formatDuration(3599)).toBe("59m 59s")
expect(formatDuration(3600)).toBe("1h")
expect(formatDuration(86399)).toBe("23h 59m")
expect(formatDuration(86400)).toBe("~1 day")
expect(formatDuration(604799)).toBe("~6 days")
expect(formatDuration(604800)).toBe("~1 week")
})
})
})

View File

@@ -0,0 +1,164 @@
import { describe, test, expect } from "bun:test"
import path from "path"
import fs from "fs/promises"
import { Glob } from "../../src/util/glob"
import { tmpdir } from "../fixture/fixture"
describe("Glob", () => {
describe("scan()", () => {
test("finds files matching pattern", async () => {
await using tmp = await tmpdir()
await fs.writeFile(path.join(tmp.path, "a.txt"), "", "utf-8")
await fs.writeFile(path.join(tmp.path, "b.txt"), "", "utf-8")
await fs.writeFile(path.join(tmp.path, "c.md"), "", "utf-8")
const results = await Glob.scan("*.txt", { cwd: tmp.path })
expect(results.sort()).toEqual(["a.txt", "b.txt"])
})
test("returns absolute paths when absolute option is true", async () => {
await using tmp = await tmpdir()
await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
const results = await Glob.scan("*.txt", { cwd: tmp.path, absolute: true })
expect(results[0]).toBe(path.join(tmp.path, "file.txt"))
})
test("excludes directories by default", async () => {
await using tmp = await tmpdir()
await fs.mkdir(path.join(tmp.path, "subdir"))
await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
const results = await Glob.scan("*", { cwd: tmp.path })
expect(results).toEqual(["file.txt"])
})
test("excludes directories when include is 'file'", async () => {
await using tmp = await tmpdir()
await fs.mkdir(path.join(tmp.path, "subdir"))
await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
const results = await Glob.scan("*", { cwd: tmp.path, include: "file" })
expect(results).toEqual(["file.txt"])
})
test("includes directories when include is 'all'", async () => {
await using tmp = await tmpdir()
await fs.mkdir(path.join(tmp.path, "subdir"))
await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
const results = await Glob.scan("*", { cwd: tmp.path, include: "all" })
expect(results.sort()).toEqual(["file.txt", "subdir"])
})
test("handles nested patterns", async () => {
await using tmp = await tmpdir()
await fs.mkdir(path.join(tmp.path, "nested"), { recursive: true })
await fs.writeFile(path.join(tmp.path, "nested", "deep.txt"), "", "utf-8")
const results = await Glob.scan("**/*.txt", { cwd: tmp.path })
expect(results).toEqual([path.join("nested", "deep.txt")])
})
test("returns empty array for no matches", async () => {
await using tmp = await tmpdir()
const results = await Glob.scan("*.nonexistent", { cwd: tmp.path })
expect(results).toEqual([])
})
test("does not follow symlinks by default", async () => {
await using tmp = await tmpdir()
await fs.mkdir(path.join(tmp.path, "realdir"))
await fs.writeFile(path.join(tmp.path, "realdir", "file.txt"), "", "utf-8")
await fs.symlink(path.join(tmp.path, "realdir"), path.join(tmp.path, "linkdir"))
const results = await Glob.scan("**/*.txt", { cwd: tmp.path })
expect(results).toEqual([path.join("realdir", "file.txt")])
})
test("follows symlinks when symlink option is true", async () => {
await using tmp = await tmpdir()
await fs.mkdir(path.join(tmp.path, "realdir"))
await fs.writeFile(path.join(tmp.path, "realdir", "file.txt"), "", "utf-8")
await fs.symlink(path.join(tmp.path, "realdir"), path.join(tmp.path, "linkdir"))
const results = await Glob.scan("**/*.txt", { cwd: tmp.path, symlink: true })
expect(results.sort()).toEqual([path.join("linkdir", "file.txt"), path.join("realdir", "file.txt")])
})
test("includes dotfiles when dot option is true", async () => {
await using tmp = await tmpdir()
await fs.writeFile(path.join(tmp.path, ".hidden"), "", "utf-8")
await fs.writeFile(path.join(tmp.path, "visible"), "", "utf-8")
const results = await Glob.scan("*", { cwd: tmp.path, dot: true })
expect(results.sort()).toEqual([".hidden", "visible"])
})
test("excludes dotfiles when dot option is false", async () => {
await using tmp = await tmpdir()
await fs.writeFile(path.join(tmp.path, ".hidden"), "", "utf-8")
await fs.writeFile(path.join(tmp.path, "visible"), "", "utf-8")
const results = await Glob.scan("*", { cwd: tmp.path, dot: false })
expect(results).toEqual(["visible"])
})
})
describe("scanSync()", () => {
test("finds files matching pattern synchronously", async () => {
await using tmp = await tmpdir()
await fs.writeFile(path.join(tmp.path, "a.txt"), "", "utf-8")
await fs.writeFile(path.join(tmp.path, "b.txt"), "", "utf-8")
const results = Glob.scanSync("*.txt", { cwd: tmp.path })
expect(results.sort()).toEqual(["a.txt", "b.txt"])
})
test("respects options", async () => {
await using tmp = await tmpdir()
await fs.mkdir(path.join(tmp.path, "subdir"))
await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
const results = Glob.scanSync("*", { cwd: tmp.path, include: "all" })
expect(results.sort()).toEqual(["file.txt", "subdir"])
})
})
describe("match()", () => {
test("matches simple patterns", () => {
expect(Glob.match("*.txt", "file.txt")).toBe(true)
expect(Glob.match("*.txt", "file.js")).toBe(false)
})
test("matches directory patterns", () => {
expect(Glob.match("**/*.js", "src/index.js")).toBe(true)
expect(Glob.match("**/*.js", "src/index.ts")).toBe(false)
})
test("matches dot files", () => {
expect(Glob.match(".*", ".gitignore")).toBe(true)
expect(Glob.match("**/*.md", ".github/README.md")).toBe(true)
})
test("matches brace expansion", () => {
expect(Glob.match("*.{js,ts}", "file.js")).toBe(true)
expect(Glob.match("*.{js,ts}", "file.ts")).toBe(true)
expect(Glob.match("*.{js,ts}", "file.py")).toBe(false)
})
})
})

View File

@@ -0,0 +1,36 @@
import { describe, expect, test } from "bun:test"
import { iife } from "../../src/util/iife"
describe("util.iife", () => {
test("should execute function immediately and return result", () => {
let called = false
const result = iife(() => {
called = true
return 42
})
expect(called).toBe(true)
expect(result).toBe(42)
})
test("should work with async functions", async () => {
let called = false
const result = await iife(async () => {
called = true
return "async result"
})
expect(called).toBe(true)
expect(result).toBe("async result")
})
test("should handle functions with no return value", () => {
let called = false
const result = iife(() => {
called = true
})
expect(called).toBe(true)
expect(result).toBeUndefined()
})
})

View File

@@ -0,0 +1,50 @@
import { describe, expect, test } from "bun:test"
import { lazy } from "../../src/util/lazy"
describe("util.lazy", () => {
test("should call function only once", () => {
let callCount = 0
const getValue = () => {
callCount++
return "expensive value"
}
const lazyValue = lazy(getValue)
expect(callCount).toBe(0)
const result1 = lazyValue()
expect(result1).toBe("expensive value")
expect(callCount).toBe(1)
const result2 = lazyValue()
expect(result2).toBe("expensive value")
expect(callCount).toBe(1)
})
test("should preserve the same reference", () => {
const obj = { value: 42 }
const lazyObj = lazy(() => obj)
const result1 = lazyObj()
const result2 = lazyObj()
expect(result1).toBe(obj)
expect(result2).toBe(obj)
expect(result1).toBe(result2)
})
test("should work with different return types", () => {
const lazyString = lazy(() => "string")
const lazyNumber = lazy(() => 123)
const lazyBoolean = lazy(() => true)
const lazyNull = lazy(() => null)
const lazyUndefined = lazy(() => undefined)
expect(lazyString()).toBe("string")
expect(lazyNumber()).toBe(123)
expect(lazyBoolean()).toBe(true)
expect(lazyNull()).toBe(null)
expect(lazyUndefined()).toBe(undefined)
})
})

View File

@@ -0,0 +1,72 @@
import { describe, expect, test } from "bun:test"
import { Lock } from "../../src/util/lock"
function tick() {
return new Promise<void>((r) => queueMicrotask(r))
}
async function flush(n = 5) {
for (let i = 0; i < n; i++) await tick()
}
describe("util.lock", () => {
test("writer exclusivity: blocks reads and other writes while held", async () => {
const key = "lock:" + Math.random().toString(36).slice(2)
const state = {
writer2: false,
reader: false,
writers: 0,
}
// Acquire writer1
using writer1 = await Lock.write(key)
state.writers++
expect(state.writers).toBe(1)
// Start writer2 candidate (should block)
const writer2Task = (async () => {
const w = await Lock.write(key)
state.writers++
expect(state.writers).toBe(1)
state.writer2 = true
// Hold for a tick so reader cannot slip in
await tick()
return w
})()
// Start reader candidate (should block)
const readerTask = (async () => {
const r = await Lock.read(key)
state.reader = true
return r
})()
// Flush microtasks and assert neither acquired
await flush()
expect(state.writer2).toBe(false)
expect(state.reader).toBe(false)
// Release writer1
writer1[Symbol.dispose]()
state.writers--
// writer2 should acquire next
const writer2 = await writer2Task
expect(state.writer2).toBe(true)
// Reader still blocked while writer2 held
await flush()
expect(state.reader).toBe(false)
// Release writer2
writer2[Symbol.dispose]()
state.writers--
// Reader should now acquire
const reader = await readerTask
expect(state.reader).toBe(true)
reader[Symbol.dispose]()
})
})

View File

@@ -0,0 +1,59 @@
import { describe, expect, test } from "bun:test"
import path from "path"
import { Module } from "@opencode-ai/util/module"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
describe("util.module", () => {
test("resolves package subpaths from the provided dir", async () => {
await using tmp = await tmpdir()
const root = path.join(tmp.path, "proj")
const file = path.join(root, "node_modules/typescript/lib/tsserver.js")
await Filesystem.write(file, "export {}\n")
await Filesystem.writeJson(path.join(root, "node_modules/typescript/package.json"), { name: "typescript" })
expect(Module.resolve("typescript/lib/tsserver.js", root)).toBe(file)
})
test("resolves packages through ancestor node_modules", async () => {
await using tmp = await tmpdir()
const root = path.join(tmp.path, "proj")
const cwd = path.join(root, "apps/web")
const file = path.join(root, "node_modules/eslint/lib/api.js")
await Filesystem.write(file, "export {}\n")
await Filesystem.writeJson(path.join(root, "node_modules/eslint/package.json"), {
name: "eslint",
main: "lib/api.js",
})
await Filesystem.write(path.join(cwd, ".keep"), "")
expect(Module.resolve("eslint", cwd)).toBe(file)
})
test("resolves relative to the provided dir", async () => {
await using tmp = await tmpdir()
const a = path.join(tmp.path, "a")
const b = path.join(tmp.path, "b")
const left = path.join(a, "node_modules/biome/index.js")
const right = path.join(b, "node_modules/biome/index.js")
await Filesystem.write(left, "export {}\n")
await Filesystem.write(right, "export {}\n")
await Filesystem.writeJson(path.join(a, "node_modules/biome/package.json"), {
name: "biome",
main: "index.js",
})
await Filesystem.writeJson(path.join(b, "node_modules/biome/package.json"), {
name: "biome",
main: "index.js",
})
expect(Module.resolve("biome", a)).toBe(left)
expect(Module.resolve("biome", b)).toBe(right)
expect(Module.resolve("biome", a)).not.toBe(Module.resolve("biome", b))
})
test("returns undefined when resolution fails", async () => {
await using tmp = await tmpdir()
expect(Module.resolve("missing-package", tmp.path)).toBeUndefined()
})
})

View File

@@ -0,0 +1,128 @@
import { describe, expect, test } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { Process } from "../../src/util/process"
import { tmpdir } from "../fixture/fixture"
function node(script: string) {
return [process.execPath, "-e", script]
}
describe("util.process", () => {
test("captures stdout and stderr", async () => {
const out = await Process.run(node('process.stdout.write("out");process.stderr.write("err")'))
expect(out.code).toBe(0)
expect(out.stdout.toString()).toBe("out")
expect(out.stderr.toString()).toBe("err")
})
test("returns code when nothrow is enabled", async () => {
const out = await Process.run(node("process.exit(7)"), { nothrow: true })
expect(out.code).toBe(7)
})
test("throws RunFailedError on non-zero exit", async () => {
const err = await Process.run(node('process.stderr.write("bad");process.exit(3)')).catch((error) => error)
expect(err).toBeInstanceOf(Process.RunFailedError)
if (!(err instanceof Process.RunFailedError)) throw err
expect(err.code).toBe(3)
expect(err.stderr.toString()).toBe("bad")
})
test("aborts a running process", async () => {
const abort = new AbortController()
const started = Date.now()
setTimeout(() => abort.abort(), 25)
const out = await Process.run(node("setInterval(() => {}, 1000)"), {
abort: abort.signal,
nothrow: true,
})
expect(out.code).not.toBe(0)
expect(Date.now() - started).toBeLessThan(1000)
}, 3000)
test("kills after timeout when process ignores terminate signal", async () => {
if (process.platform === "win32") return
const abort = new AbortController()
const started = Date.now()
setTimeout(() => abort.abort(), 25)
const out = await Process.run(node('process.on("SIGTERM", () => {}); setInterval(() => {}, 1000)'), {
abort: abort.signal,
nothrow: true,
timeout: 25,
})
expect(out.code).not.toBe(0)
expect(Date.now() - started).toBeLessThan(1000)
}, 3000)
test("uses cwd when spawning commands", async () => {
await using tmp = await tmpdir()
const out = await Process.run(node("process.stdout.write(process.cwd())"), {
cwd: tmp.path,
})
expect(out.stdout.toString()).toBe(tmp.path)
})
test("merges environment overrides", async () => {
const out = await Process.run(node('process.stdout.write(process.env.OPENCODE_TEST ?? "")'), {
env: {
OPENCODE_TEST: "set",
},
})
expect(out.stdout.toString()).toBe("set")
})
test("uses shell in run on Windows", async () => {
if (process.platform !== "win32") return
const out = await Process.run(["set", "OPENCODE_TEST_SHELL"], {
shell: true,
env: {
OPENCODE_TEST_SHELL: "ok",
},
})
expect(out.code).toBe(0)
expect(out.stdout.toString()).toContain("OPENCODE_TEST_SHELL=ok")
})
test("runs cmd scripts with spaces on Windows without shell", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const dir = path.join(tmp.path, "with space")
const file = path.join(dir, "echo cmd.cmd")
await fs.mkdir(dir, { recursive: true })
await Bun.write(file, "@echo off\r\nif %~1==--stdio exit /b 0\r\nexit /b 7\r\n")
const proc = Process.spawn([file, "--stdio"], {
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
})
expect(await proc.exited).toBe(0)
})
test("rejects missing commands without leaking unhandled errors", async () => {
await using tmp = await tmpdir()
const cmd = path.join(tmp.path, "missing" + (process.platform === "win32" ? ".cmd" : ""))
const err = await Process.spawn([cmd], {
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
}).exited.catch((err) => err)
expect(err).toBeInstanceOf(Error)
if (!(err instanceof Error)) throw err
expect(err).toMatchObject({
code: "ENOENT",
})
})
})

View File

@@ -0,0 +1,21 @@
import { describe, expect, test } from "bun:test"
import { withTimeout } from "../../src/util/timeout"
describe("util.timeout", () => {
test("should resolve when promise completes before timeout", async () => {
const fastPromise = new Promise<string>((resolve) => {
setTimeout(() => resolve("fast"), 10)
})
const result = await withTimeout(fastPromise, 100)
expect(result).toBe("fast")
})
test("should reject when promise exceeds timeout", async () => {
const slowPromise = new Promise<string>((resolve) => {
setTimeout(() => resolve("slow"), 200)
})
await expect(withTimeout(slowPromise, 50)).rejects.toThrow("Operation timed out after 50ms")
})
})

View File

@@ -0,0 +1,100 @@
import { describe, expect, test } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { which } from "../../src/util/which"
import { tmpdir } from "../fixture/fixture"
async function cmd(dir: string, name: string, exec = true) {
const ext = process.platform === "win32" ? ".cmd" : ""
const file = path.join(dir, name + ext)
const body = process.platform === "win32" ? "@echo off\r\n" : "#!/bin/sh\n"
await fs.writeFile(file, body)
if (process.platform !== "win32") {
await fs.chmod(file, exec ? 0o755 : 0o644)
}
return file
}
function env(PATH: string): NodeJS.ProcessEnv {
return {
PATH,
PATHEXT: process.env["PATHEXT"],
}
}
function envPath(Path: string): NodeJS.ProcessEnv {
return {
Path,
PathExt: process.env["PathExt"] ?? process.env["PATHEXT"],
}
}
function same(a: string | null, b: string) {
if (process.platform === "win32") {
expect(a?.toLowerCase()).toBe(b.toLowerCase())
return
}
expect(a).toBe(b)
}
describe("util.which", () => {
test("returns null when command is missing", () => {
expect(which("opencode-missing-command-for-test")).toBeNull()
})
test("finds a command from PATH override", async () => {
await using tmp = await tmpdir()
const bin = path.join(tmp.path, "bin")
await fs.mkdir(bin)
const file = await cmd(bin, "tool")
same(which("tool", env(bin)), file)
})
test("uses first PATH match", async () => {
await using tmp = await tmpdir()
const a = path.join(tmp.path, "a")
const b = path.join(tmp.path, "b")
await fs.mkdir(a)
await fs.mkdir(b)
const first = await cmd(a, "dupe")
await cmd(b, "dupe")
same(which("dupe", env([a, b].join(path.delimiter))), first)
})
test("returns null for non-executable file on unix", async () => {
if (process.platform === "win32") return
await using tmp = await tmpdir()
const bin = path.join(tmp.path, "bin")
await fs.mkdir(bin)
await cmd(bin, "noexec", false)
expect(which("noexec", env(bin))).toBeNull()
})
test("uses PATHEXT on windows", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const bin = path.join(tmp.path, "bin")
await fs.mkdir(bin)
const file = path.join(bin, "pathext.CMD")
await fs.writeFile(file, "@echo off\r\n")
expect(which("pathext", { PATH: bin, PATHEXT: ".CMD" })).toBe(file)
})
test("uses Windows Path casing fallback", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const bin = path.join(tmp.path, "bin")
await fs.mkdir(bin)
const file = await cmd(bin, "mixed")
same(which("mixed", envPath(bin)), file)
})
})

View File

@@ -0,0 +1,90 @@
import { test, expect } from "bun:test"
import { Wildcard } from "../../src/util/wildcard"
test("match handles glob tokens", () => {
expect(Wildcard.match("file1.txt", "file?.txt")).toBe(true)
expect(Wildcard.match("file12.txt", "file?.txt")).toBe(false)
expect(Wildcard.match("foo+bar", "foo+bar")).toBe(true)
})
test("match with trailing space+wildcard matches command with or without args", () => {
// "ls *" should match "ls" (no args) and "ls -la" (with args)
expect(Wildcard.match("ls", "ls *")).toBe(true)
expect(Wildcard.match("ls -la", "ls *")).toBe(true)
expect(Wildcard.match("ls foo bar", "ls *")).toBe(true)
// "ls*" (no space) should NOT match "ls" alone — wait, it should because .* matches empty
// but it WILL match "lstmeval" which is the dangerous case users should avoid
expect(Wildcard.match("ls", "ls*")).toBe(true)
expect(Wildcard.match("lstmeval", "ls*")).toBe(true)
// "ls *" (with space) should NOT match "lstmeval"
expect(Wildcard.match("lstmeval", "ls *")).toBe(false)
// multi-word commands
expect(Wildcard.match("git status", "git *")).toBe(true)
expect(Wildcard.match("git", "git *")).toBe(true)
expect(Wildcard.match("git commit -m foo", "git *")).toBe(true)
})
test("all picks the most specific pattern", () => {
const rules = {
"*": "deny",
"git *": "ask",
"git status": "allow",
}
expect(Wildcard.all("git status", rules)).toBe("allow")
expect(Wildcard.all("git log", rules)).toBe("ask")
expect(Wildcard.all("echo hi", rules)).toBe("deny")
})
test("allStructured matches command sequences", () => {
const rules = {
"git *": "ask",
"git status*": "allow",
}
expect(Wildcard.allStructured({ head: "git", tail: ["status", "--short"] }, rules)).toBe("allow")
expect(Wildcard.allStructured({ head: "npm", tail: ["run", "build", "--watch"] }, { "npm run *": "allow" })).toBe(
"allow",
)
expect(Wildcard.allStructured({ head: "ls", tail: ["-la"] }, rules)).toBeUndefined()
})
test("allStructured prioritizes flag-specific patterns", () => {
const rules = {
"find *": "allow",
"find * -delete*": "ask",
"sort*": "allow",
"sort -o *": "ask",
}
expect(Wildcard.allStructured({ head: "find", tail: ["src", "-delete"] }, rules)).toBe("ask")
expect(Wildcard.allStructured({ head: "find", tail: ["src", "-print"] }, rules)).toBe("allow")
expect(Wildcard.allStructured({ head: "sort", tail: ["-o", "out.txt"] }, rules)).toBe("ask")
expect(Wildcard.allStructured({ head: "sort", tail: ["--reverse"] }, rules)).toBe("allow")
})
test("allStructured handles sed flags", () => {
const rules = {
"sed * -i*": "ask",
"sed -n*": "allow",
}
expect(Wildcard.allStructured({ head: "sed", tail: ["-i", "file"] }, rules)).toBe("ask")
expect(Wildcard.allStructured({ head: "sed", tail: ["-i.bak", "file"] }, rules)).toBe("ask")
expect(Wildcard.allStructured({ head: "sed", tail: ["-n", "1p", "file"] }, rules)).toBe("allow")
expect(Wildcard.allStructured({ head: "sed", tail: ["-i", "-n", "/./p", "myfile.txt"] }, rules)).toBe("ask")
})
test("match normalizes slashes for cross-platform globbing", () => {
expect(Wildcard.match("C:\\Windows\\System32\\*", "C:/Windows/System32/*")).toBe(true)
expect(Wildcard.match("C:/Windows/System32/drivers", "C:\\Windows\\System32\\*")).toBe(true)
})
test("match handles case-insensitivity on Windows", () => {
if (process.platform === "win32") {
expect(Wildcard.match("C:\\windows\\system32\\hosts", "C:/Windows/System32/*")).toBe(true)
expect(Wildcard.match("c:/windows/system32/hosts", "C:\\Windows\\System32\\*")).toBe(true)
} else {
// Unix paths are case-sensitive
expect(Wildcard.match("/users/test/file", "/Users/test/*")).toBe(false)
}
})