Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fuzzy-taxis-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"opencode-drive": patch
---

Launch current OpenCode V2 development checkouts through their native simulation integration.
26 changes: 19 additions & 7 deletions packages/drive/src/instance/dev.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mkdir, rm, symlink } from "node:fs/promises"
import { join, resolve } from "node:path"
import { pathToFileURL } from "node:url"
import * as Effect from "effect/Effect"
import { instanceError } from "./error.js"

Expand All @@ -13,8 +14,9 @@ export const prepareDev = Effect.fn("OpenCodeInstance.prepareDev")(function* (
) {
const root = resolve(directory)
const entrypoint = join(root, "packages", "cli", "src", "index.ts")
const launcher = join(artifacts, "opencode-dev.ts")
const solid = join(root, "packages", "tui", "node_modules", "@opentui", "solid")
yield* Effect.tryPromise({
const standalone = yield* Effect.tryPromise({
try: async () => {
if (!(await Bun.file(entrypoint).exists()))
throw new Error(`OpenCode development entrypoint not found: ${entrypoint}`)
Expand All @@ -26,13 +28,23 @@ export const prepareDev = Effect.fn("OpenCodeInstance.prepareDev")(function* (
})
await rm(preload, { recursive: true, force: true })
await symlink(solid, preload, "dir")
await Bun.write(
launcher,
`if (process.argv[2] === "serve") delete process.env.BUN_OPTIONS\nawait import(${JSON.stringify(pathToFileURL(entrypoint).href)})\n`,
)
return Bun.file(join(root, "packages", "cli", "src", "services", "standalone.ts")).exists()
},
catch: (cause) => instanceError("prepare development checkout", cause),
})
return [
process.execPath,
"--conditions=browser",
"--preload=@opentui/solid/preload",
entrypoint,
]
const bunOptions = ["--conditions=browser", "--preload=@opentui/solid/preload"]
return {
command: [
process.execPath,
"--conditions=browser",
`--preload=${join(solid, "scripts", "preload.js")}`,
launcher,
],
bunOptions,
standalone,
}
})
28 changes: 25 additions & 3 deletions packages/drive/src/instance/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,26 @@ export const make = Effect.fn("OpenCodeInstance.make")(function* (
}).pipe(
Effect.mapError((cause) => instanceError("prepare project", cause)),
)
const dev = options.dev !== undefined
? yield* prepareDev(artifacts, options.dev)
: undefined
if (dev?.standalone && options.scripted) {
const port = yield* freePort
yield* Effect.tryPromise({
try: () =>
Bun.write(
join(files, ".opencode", "service-local.json"),
`${JSON.stringify({ port }, undefined, 2)}\n`,
),
catch: (cause) => instanceError("configure development service", cause),
})
}
const environment = stripGitEnvironment({
...process.env,
...options.env,
BUN_OPTIONS: dev === undefined
? options.env?.BUN_OPTIONS ?? process.env.BUN_OPTIONS
: [options.env?.BUN_OPTIONS ?? process.env.BUN_OPTIONS, ...dev.bunOptions].filter(Boolean).join(" "),
OPENCODE_SIMULATE: "1",
OPENCODE_DRIVE_SCRIPTED: options.scripted ? "1" : undefined,
DRIVE_REGISTRY_DIR: drive,
Expand All @@ -156,8 +173,8 @@ export const make = Effect.fn("OpenCodeInstance.make")(function* (
XDG_DATA_HOME: logs,
XDG_STATE_HOME: join(artifacts, "home", ".local", "state"),
})
const command = options.dev !== undefined
? yield* prepareDev(artifacts, options.dev)
const command = dev !== undefined
? dev.command
: options.command?.length
? [...options.command]
: ["opencode2"]
Expand Down Expand Up @@ -231,7 +248,12 @@ export const make = Effect.fn("OpenCodeInstance.make")(function* (
) {
yield* writeManifest(options.name, endpoints, recording, options.viewport)
options.log?.("launching OpenCode")
return yield* spawn(options.name, command, "opencode", options.visible ?? false)
return yield* spawn(
options.name,
dev?.standalone && !options.visible ? [...command, "--standalone"] : command,
"opencode",
options.visible ?? false,
)
})

if (!options.scripted) {
Expand Down
57 changes: 57 additions & 0 deletions packages/drive/test/instance/dev.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { afterEach, expect, test } from "vitest"
import { mkdir, mkdtemp, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { pathToFileURL } from "node:url"
import * as Effect from "effect/Effect"
import { prepareDev } from "../../src/instance/dev.js"

const directories: string[] = []

afterEach(async () => {
await Promise.all(directories.splice(0).map((path) => rm(path, { recursive: true, force: true })))
})

test("detects standalone V2 development checkouts without changing the base command", async () => {
const root = await mkdtemp(join(tmpdir(), "opencode drive dev "))
const artifacts = await mkdtemp(join(tmpdir(), "opencode-drive-artifacts-"))
directories.push(root, artifacts)
await mkdir(join(root, "packages", "cli", "src", "services"), { recursive: true })
await mkdir(join(root, "packages", "tui", "node_modules", "@opentui", "solid", "scripts"), { recursive: true })
await Promise.all([
Bun.write(join(root, "packages", "cli", "src", "index.ts"), ""),
Bun.write(join(root, "packages", "cli", "src", "services", "standalone.ts"), ""),
Bun.write(
join(root, "packages", "tui", "node_modules", "@opentui", "solid", "package.json"),
JSON.stringify({ exports: { "./preload": "./scripts/preload.js" } }),
),
Bun.write(join(root, "packages", "tui", "node_modules", "@opentui", "solid", "scripts", "preload.js"), ""),
])

const result = await Effect.runPromise(prepareDev(artifacts, root))

expect(result.standalone).toBe(true)
expect(result.command.at(-1)).toBe(join(artifacts, "opencode-dev.ts"))
expect(result.command).toContain(
`--preload=${join(root, "packages", "tui", "node_modules", "@opentui", "solid", "scripts", "preload.js")}`,
)
expect(result.bunOptions).toEqual(["--conditions=browser", "--preload=@opentui/solid/preload"])
expect(await Bun.file(join(artifacts, "opencode-dev.ts")).text()).toContain(
`await import(${JSON.stringify(pathToFileURL(join(root, "packages", "cli", "src", "index.ts")).href)})`,
)

const output = join(artifacts, "server-environment.json")
await Bun.write(
join(root, "packages", "cli", "src", "index.ts"),
`await Bun.write(${JSON.stringify(output)}, JSON.stringify({ args: process.argv.slice(2), bunOptions: process.env.BUN_OPTIONS }))\n`,
)
const server = Bun.spawn([...result.command, "serve", "--service"], {
cwd: artifacts,
env: { ...process.env, BUN_OPTIONS: result.bunOptions.join(" ") },
stderr: "pipe",
})
const [status, stderr] = await Promise.all([server.exited, new Response(server.stderr).text()])
expect(stderr).toBe("")
expect(status).toBe(0)
expect(await Bun.file(output).json()).toEqual({ args: ["serve", "--service"] })
})