mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 08:33:26 +00:00
feat(desktop): support local server builds (#41486)
This commit is contained in:
parent
055a380e66
commit
1190ef3818
5 changed files with 116 additions and 17 deletions
|
|
@ -11,8 +11,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"typecheck": "tsgo -b",
|
||||
"predev": "bun ./scripts/predev.ts",
|
||||
"dev": "electron-vite dev",
|
||||
"dev": "bun ./scripts/dev.ts",
|
||||
"prebuild": "bun ./scripts/prebuild.ts",
|
||||
"build": "electron-vite build",
|
||||
"preview": "electron-vite preview",
|
||||
|
|
|
|||
56
packages/desktop/scripts/dev.ts
Normal file
56
packages/desktop/scripts/dev.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { $ } from "bun"
|
||||
import { homedir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
import { buildCliToResources, downloadCliToResources, windowsify } from "./utils"
|
||||
|
||||
type ServerSource = { type: "build" } | { type: "download"; version: string }
|
||||
type DevOptions = { server: ServerSource; electron: string[] }
|
||||
|
||||
async function main() {
|
||||
const options = selectOptions()
|
||||
await prepareDesktop()
|
||||
await prepareServer(options.server)
|
||||
await startDesktop(options.electron)
|
||||
}
|
||||
|
||||
async function prepareDesktop() {
|
||||
await $`bun run install-electron`
|
||||
await $`bun ./scripts/copy-icons.ts ${process.env.OPENCODE_CHANNEL ?? "dev"}`
|
||||
}
|
||||
|
||||
function selectOptions(): DevOptions {
|
||||
const args = process.argv.slice(2)
|
||||
const build = args.indexOf("--build-server")
|
||||
const download = args.indexOf("--download-server")
|
||||
if (build >= 0 && download >= 0) {
|
||||
throw new Error("--build-server and --download-server cannot be used together")
|
||||
}
|
||||
if (download >= 0 && !args[download + 1]) throw new Error("--download-server requires a version")
|
||||
const consumed = new Set([build, download, download >= 0 ? download + 1 : -1])
|
||||
return {
|
||||
server: download >= 0 ? { type: "download", version: args[download + 1] } : { type: "build" },
|
||||
electron: args.filter((_, index) => !consumed.has(index)),
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareServer(source: ServerSource) {
|
||||
const destination = windowsify("resources/opencode-cli-dev")
|
||||
if (source.type === "download") return downloadCliToResources(source.version, destination)
|
||||
return buildCliToResources(destination, developmentStateHome())
|
||||
}
|
||||
|
||||
function developmentStateHome() {
|
||||
const appData = (() => {
|
||||
if (process.platform === "darwin") return join(homedir(), "Library", "Application Support")
|
||||
if (process.platform === "win32") return process.env.APPDATA ?? join(homedir(), "AppData", "Roaming")
|
||||
return process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config")
|
||||
})()
|
||||
return join(appData, "ai.opencode.desktop.dev")
|
||||
}
|
||||
|
||||
async function startDesktop(args: string[]) {
|
||||
process.env.OPENCODE_DESKTOP_ISOLATED_SERVER = "1"
|
||||
await $`electron-vite dev ${args}`
|
||||
}
|
||||
|
||||
await main()
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { $ } from "bun"
|
||||
import { downloadCliToResources } from "./utils"
|
||||
|
||||
await $`bun run install-electron`
|
||||
|
||||
await $`bun ./scripts/copy-icons.ts ${process.env.OPENCODE_CHANNEL ?? "dev"}`
|
||||
|
||||
await downloadCliToResources()
|
||||
|
|
@ -69,10 +69,9 @@ export function getCurrentCli(target = CLI_TARGET ?? nativeTarget()) {
|
|||
return binaryConfig
|
||||
}
|
||||
|
||||
export async function downloadCliToResources(version = CLI_VERSION) {
|
||||
export async function downloadCliToResources(version = CLI_VERSION, dest = windowsify("resources/opencode-cli")) {
|
||||
const cli = getCurrentCli()
|
||||
const directory = await mkdtemp(join(tmpdir(), "opencode-cli-"))
|
||||
const dest = windowsify("resources/opencode-cli")
|
||||
try {
|
||||
await $`bun install --no-save --cwd ${directory} ${`${cli.package}@${version}`} ${`--os=${cli.os}`} ${`--cpu=${cli.cpu}`}`
|
||||
await copyFile(
|
||||
|
|
@ -82,13 +81,45 @@ export async function downloadCliToResources(version = CLI_VERSION) {
|
|||
} finally {
|
||||
await rm(directory, { recursive: true, force: true })
|
||||
}
|
||||
await prepareCli(dest)
|
||||
|
||||
console.log(`Copied ${cli.package}@${version} to ${dest}`)
|
||||
}
|
||||
|
||||
export async function buildCliToResources(dest = windowsify("resources/opencode-cli"), stateHome?: string) {
|
||||
const directory = await mkdtemp(join(tmpdir(), "opencode-cli-"))
|
||||
const target = `cli-${process.platform === "win32" ? "windows" : process.platform}-${process.arch}`
|
||||
try {
|
||||
await $`bun ${join(import.meta.dirname, "../../cli/script/build.ts")} --single --skip-install --outdir=${directory}`.env(
|
||||
{
|
||||
...process.env,
|
||||
OPENCODE_VERSION: `0.0.0-local-${Date.now()}`,
|
||||
},
|
||||
)
|
||||
if (stateHome && (await Bun.file(dest).exists())) {
|
||||
const child = Bun.spawn([dest, "service", "stop"], {
|
||||
env: { ...process.env, XDG_STATE_HOME: stateHome },
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
const exitCode = await child.exited
|
||||
if (exitCode !== 0) throw new Error(`Failed to stop development service: ${exitCode}`)
|
||||
}
|
||||
await copyFile(join(directory, target, "bin", windowsify("opencode2")), dest)
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true })
|
||||
}
|
||||
await prepareCli(dest)
|
||||
|
||||
console.log(`Built local CLI at ${dest}`)
|
||||
}
|
||||
|
||||
async function prepareCli(dest: string) {
|
||||
if (process.platform !== "win32") await chmod(dest, 0o755)
|
||||
if (process.platform === "win32" && process.env.GITHUB_ACTIONS === "true") {
|
||||
await $`pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File ../../script/sign-windows.ps1 ${dest}`
|
||||
}
|
||||
if (process.platform === "darwin") await $`codesign --force --sign - ${dest}`
|
||||
|
||||
console.log(`Copied ${cli.package}@${version} to ${dest}`)
|
||||
}
|
||||
|
||||
export function windowsify(path: string) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Service } from "@opencode-ai/client/service"
|
||||
import { execFile } from "node:child_process"
|
||||
import { existsSync } from "node:fs"
|
||||
import { chmod, copyFile, mkdir, rename, rm } from "node:fs/promises"
|
||||
import { chmod, copyFile, mkdir, readdir, rename, rm } from "node:fs/promises"
|
||||
import { dirname, join } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import { promisify } from "node:util"
|
||||
|
|
@ -16,12 +16,14 @@ type Logger = {
|
|||
}
|
||||
|
||||
export async function startBackgroundCli(logger: Logger) {
|
||||
const isolated = !app.isPackaged && process.env.OPENCODE_DESKTOP_ISOLATED_SERVER === "1"
|
||||
const bundled = app.isPackaged
|
||||
? join(process.resourcesPath, executableName())
|
||||
: join(root, "../../resources", executableName())
|
||||
: join(root, "../../resources", isolated ? developmentExecutableName() : executableName())
|
||||
logger.log("v2 CLI executable resolved", { bundled, packaged: app.isPackaged })
|
||||
const version = parseVersion(await run(bundled, ["--version"], logger))
|
||||
const binary = app.isPackaged ? await installCli(bundled, version, logger) : bundled
|
||||
const binary = app.isPackaged || isolated ? await installCli(bundled, version, logger) : bundled
|
||||
if (isolated) process.env.XDG_STATE_HOME = app.getPath("userData")
|
||||
const service = await Service.ensure({
|
||||
version,
|
||||
command: [binary, "serve", "--service"],
|
||||
|
|
@ -33,6 +35,7 @@ export async function startBackgroundCli(logger: Logger) {
|
|||
version,
|
||||
...endpoint(service.url),
|
||||
})
|
||||
if (isolated) await cleanCliStages(binary, logger)
|
||||
return {
|
||||
url: service.url,
|
||||
username: service.auth.username,
|
||||
|
|
@ -40,6 +43,20 @@ export async function startBackgroundCli(logger: Logger) {
|
|||
}
|
||||
}
|
||||
|
||||
async function cleanCliStages(binary: string, logger: Logger) {
|
||||
const current = dirname(binary)
|
||||
const root = dirname(current)
|
||||
await Promise.all(
|
||||
(await readdir(root, { withFileTypes: true }))
|
||||
.filter((entry) => entry.isDirectory() && join(root, entry.name) !== current)
|
||||
.map((entry) =>
|
||||
rm(join(root, entry.name), { recursive: true, force: true }).catch((error) =>
|
||||
logger.error("failed to clean staged v2 CLI", { path: join(root, entry.name), error }),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
async function installCli(source: string, version: string, logger: Logger) {
|
||||
const directory = join(app.getPath("userData"), "cli", version.replace(/[^a-zA-Z0-9._-]/g, "-"))
|
||||
const destination = join(directory, executableName())
|
||||
|
|
@ -98,3 +115,7 @@ function endpoint(url: string | undefined) {
|
|||
function executableName() {
|
||||
return process.platform === "win32" ? "opencode-cli.exe" : "opencode-cli"
|
||||
}
|
||||
|
||||
function developmentExecutableName() {
|
||||
return process.platform === "win32" ? "opencode-cli-dev.exe" : "opencode-cli-dev"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue