133 lines
5 KiB
TypeScript
133 lines
5 KiB
TypeScript
import { expect, test } from "bun:test"
|
|
import { mkdir, mkdtemp, rm } from "node:fs/promises"
|
|
import { join, relative, resolve } from "node:path"
|
|
import { pathToFileURL } from "node:url"
|
|
|
|
const directory = resolve(import.meta.dir, "../..")
|
|
|
|
test("built Node entrypoint imports and exposes browser registration in Node", async () => {
|
|
await buildClient()
|
|
const output = await Bun.file(join(directory, "dist/node/index.js")).text()
|
|
expect(output).not.toMatch(/(?:from\s+|import\s*)["']\.\.?\//)
|
|
|
|
const temporary = await mkdtemp(join(import.meta.dir, ".node-package-"))
|
|
try {
|
|
await Bun.write(join(temporary, "index.mjs"), output)
|
|
await stageWorkspaceDependencies(temporary)
|
|
const child = Bun.spawn(
|
|
["node", "--input-type=module", "-e", nodeScenario(pathToFileURL(join(temporary, "index.mjs")).href)],
|
|
{ cwd: temporary, stdout: "pipe", stderr: "pipe" },
|
|
)
|
|
const [exitCode, stdout, stderr] = await Promise.all([
|
|
child.exited,
|
|
new Response(child.stdout).text(),
|
|
new Response(child.stderr).text(),
|
|
])
|
|
if (exitCode !== 0) throw new Error(stderr || stdout)
|
|
expect(stdout.trim()).toBe("ok")
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true })
|
|
}
|
|
}, 60_000)
|
|
|
|
async function buildClient() {
|
|
const child = Bun.spawn([process.execPath, "run", "build"], {
|
|
cwd: directory,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
})
|
|
const [exitCode, stdout, stderr] = await Promise.all([
|
|
child.exited,
|
|
new Response(child.stdout).text(),
|
|
new Response(child.stderr).text(),
|
|
])
|
|
if (exitCode !== 0) throw new Error(stdout + stderr)
|
|
}
|
|
|
|
async function stageWorkspaceDependencies(temporary: string) {
|
|
const schema = join(temporary, "node_modules/@opencode-ai/schema")
|
|
const protocol = join(temporary, "node_modules/@opencode-ai/protocol")
|
|
await Promise.all([mkdir(schema, { recursive: true }), mkdir(protocol, { recursive: true })])
|
|
|
|
const schemaEntry = join(temporary, "schema.ts")
|
|
const protocolEntry = join(temporary, "protocol.ts")
|
|
await Promise.all([
|
|
Bun.write(
|
|
schemaEntry,
|
|
[
|
|
`export { Browser } from ${JSON.stringify(importPath(temporary, resolve(directory, "../schema/src/browser.ts")))}`,
|
|
`export { BrowserControl } from ${JSON.stringify(importPath(temporary, resolve(directory, "../schema/src/browser-control.ts")))}`,
|
|
`export { BrowserTunnel } from ${JSON.stringify(importPath(temporary, resolve(directory, "../schema/src/browser-tunnel.ts")))}`,
|
|
`export { Session } from ${JSON.stringify(importPath(temporary, resolve(directory, "../schema/src/session.ts")))}`,
|
|
].join("\n"),
|
|
),
|
|
Bun.write(
|
|
protocolEntry,
|
|
[
|
|
`export { BrowserControlProtocol } from ${JSON.stringify(importPath(temporary, resolve(directory, "../protocol/src/browser-control.ts")))}`,
|
|
`export { BrowserTunnelProtocol } from ${JSON.stringify(importPath(temporary, resolve(directory, "../protocol/src/browser-tunnel.ts")))}`,
|
|
].join("\n"),
|
|
),
|
|
])
|
|
const [schemaBuild, protocolBuild] = await Promise.all([
|
|
Bun.build({
|
|
entrypoints: [schemaEntry],
|
|
outdir: schema,
|
|
naming: "index.js",
|
|
target: "node",
|
|
format: "esm",
|
|
packages: "bundle",
|
|
}),
|
|
Bun.build({
|
|
entrypoints: [protocolEntry],
|
|
outdir: protocol,
|
|
naming: "index.js",
|
|
target: "node",
|
|
format: "esm",
|
|
packages: "bundle",
|
|
}),
|
|
])
|
|
if (!schemaBuild.success) throw new Error(schemaBuild.logs.map((log) => log.message).join("\n"))
|
|
if (!protocolBuild.success) throw new Error(protocolBuild.logs.map((log) => log.message).join("\n"))
|
|
await Promise.all([
|
|
Bun.write(
|
|
join(schema, "package.json"),
|
|
JSON.stringify({
|
|
type: "module",
|
|
exports: {
|
|
"./browser": "./index.js",
|
|
"./browser-control": "./index.js",
|
|
"./browser-tunnel": "./index.js",
|
|
"./session": "./index.js",
|
|
},
|
|
}),
|
|
),
|
|
Bun.write(
|
|
join(protocol, "package.json"),
|
|
JSON.stringify({
|
|
type: "module",
|
|
exports: {
|
|
"./browser-control": "./index.js",
|
|
"./browser-tunnel": "./index.js",
|
|
},
|
|
}),
|
|
),
|
|
])
|
|
}
|
|
|
|
function importPath(from: string, to: string) {
|
|
const path = relative(from, to).replaceAll("\\", "/")
|
|
return path.startsWith(".") ? path : `./${path}`
|
|
}
|
|
|
|
function nodeScenario(moduleURL: string) {
|
|
return `const sdk = await import(${JSON.stringify(moduleURL)})
|
|
if (typeof sdk.OpenCode.make !== "function") throw new Error("Missing OpenCode.make")
|
|
if (typeof sdk.BrowserDriver.define !== "function") throw new Error("Missing BrowserDriver.define")
|
|
if (typeof sdk.BrowserDriver.chromium !== "function") throw new Error("Missing BrowserDriver.chromium")
|
|
if (typeof sdk.BrowserDriverError !== "function") throw new Error("Missing BrowserDriverError")
|
|
if (!sdk.Browser.State) throw new Error("Missing canonical Browser export")
|
|
const client = sdk.OpenCode.make({ baseUrl: "http://127.0.0.1:1" })
|
|
if (typeof client.browser.register !== "function") throw new Error("Missing browser.register")
|
|
console.log("ok")`
|
|
}
|