refactor(desktop): consolidate desktop-electron into desktop package (#25822)

This commit is contained in:
Brendan Allan
2026-05-05 13:43:36 +08:00
committed by GitHub
parent 6f7d63e9ce
commit b4147c8d08
441 changed files with 115 additions and 13441 deletions

View File

@@ -22,3 +22,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
out/
resources/opencode-cli*
resources/icons

View File

@@ -1,4 +1,4 @@
# Desktop package notes
- Never call `invoke` manually in this package.
- Use the generated bindings in `packages/desktop/src/bindings.ts` for core commands/events.
- Renderer process should only call `window.api` from `src/preload`.
- Main process should register IPC handlers in `src/main/ipc.ts`.

View File

@@ -2,10 +2,6 @@
Native OpenCode desktop app, built with Tauri v2.
## Prerequisites
Building the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.
## Development
From the repo root:
@@ -15,18 +11,22 @@ bun install
bun run --cwd packages/desktop tauri dev
```
This starts the Vite dev server on http://localhost:1420 and opens the native window.
If you only want the web dev server (no native shell):
```bash
bun run --cwd packages/desktop dev
```
## Build
To create a production `dist/` and build the native app bundle:
```bash
bun run --cwd packages/desktop tauri build
```
## Troubleshooting
## Prerequisites
### Rust compiler not found
If you see errors about Rust not being found, install it via [rustup](https://rustup.rs/):
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.

View File

@@ -0,0 +1,116 @@
import { execFile } from "node:child_process"
import path from "node:path"
import { fileURLToPath } from "node:url"
import { promisify } from "node:util"
import type { Configuration } from "electron-builder"
const execFileAsync = promisify(execFile)
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..")
const signScript = path.join(rootDir, "script", "sign-windows.ps1")
async function signWindows(configuration: { path: string }) {
if (process.platform !== "win32") return
if (process.env.GITHUB_ACTIONS !== "true") return
await execFileAsync(
"pwsh",
["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", signScript, configuration.path],
{ cwd: rootDir },
)
}
const channel = (() => {
const raw = process.env.OPENCODE_CHANNEL
if (raw === "dev" || raw === "beta" || raw === "prod") return raw
return "dev"
})()
const getBase = (): Configuration => ({
artifactName: "opencode-desktop-${os}-${arch}.${ext}",
directories: {
output: "dist",
buildResources: "resources",
},
files: ["out/**/*", "resources/**/*"],
extraResources: [
{
from: "native/",
to: "native/",
filter: ["index.js", "index.d.ts", "build/Release/mac_window.node", "swift-build/**"],
},
],
mac: {
category: "public.app-category.developer-tools",
icon: `resources/icons/icon.icns`,
hardenedRuntime: true,
gatekeeperAssess: false,
entitlements: "resources/entitlements.plist",
entitlementsInherit: "resources/entitlements.plist",
notarize: true,
target: ["dmg", "zip"],
},
dmg: {
sign: true,
},
protocols: {
name: "OpenCode",
schemes: ["opencode"],
},
win: {
icon: `resources/icons/icon.ico`,
signtoolOptions: {
sign: signWindows,
},
target: ["nsis"],
verifyUpdateCodeSignature: false,
},
nsis: {
oneClick: false,
allowToChangeInstallationDirectory: true,
installerIcon: `resources/icons/icon.ico`,
installerHeaderIcon: `resources/icons/icon.ico`,
},
linux: {
icon: `resources/icons`,
category: "Development",
target: ["AppImage", "deb", "rpm"],
},
})
function getConfig() {
const base = getBase()
switch (channel) {
case "dev": {
return {
...base,
appId: "ai.opencode.desktop.dev",
productName: "OpenCode Dev",
rpm: { packageName: "opencode-dev" },
}
}
case "beta": {
return {
...base,
appId: "ai.opencode.desktop.beta",
productName: "OpenCode Beta",
protocols: { name: "OpenCode Beta", schemes: ["opencode"] },
publish: { provider: "github", owner: "anomalyco", repo: "opencode-beta", channel: "latest" },
rpm: { packageName: "opencode-beta" },
}
}
case "prod": {
return {
...base,
appId: "ai.opencode.desktop",
productName: "OpenCode",
protocols: { name: "OpenCode", schemes: ["opencode"] },
publish: { provider: "github", owner: "anomalyco", repo: "opencode", channel: "latest" },
rpm: { packageName: "opencode" },
}
}
}
}
export default getConfig()

View File

@@ -0,0 +1,98 @@
import { sentryVitePlugin } from "@sentry/vite-plugin"
import { defineConfig } from "electron-vite"
import appPlugin from "@opencode-ai/app/vite"
import * as fs from "node:fs/promises"
const channel = (() => {
const raw = process.env.OPENCODE_CHANNEL
if (raw === "dev" || raw === "beta" || raw === "prod") return raw
return "dev"
})()
const OPENCODE_SERVER_DIST = "../opencode/dist/node"
const nodePtyPkg = `@lydell/node-pty-${process.platform}-${process.arch}`
const sentry =
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT
? sentryVitePlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
telemetry: false,
release: {
name: process.env.SENTRY_RELEASE ?? process.env.VITE_SENTRY_RELEASE,
},
sourcemaps: {
assets: "./out/renderer/**",
filesToDeleteAfterUpload: "./out/renderer/**/*.map",
},
})
: false
export default defineConfig({
main: {
define: {
"import.meta.env.OPENCODE_CHANNEL": JSON.stringify(channel),
},
build: {
rollupOptions: {
input: { index: "src/main/index.ts" },
},
externalizeDeps: { include: [nodePtyPkg] },
},
plugins: [
{
name: "opencode:node-pty-narrower",
enforce: "pre",
resolveId(s) {
if (s === "@lydell/node-pty") return nodePtyPkg
},
},
{
name: "opencode:virtual-server-module",
enforce: "pre",
resolveId(id) {
if (id === "virtual:opencode-server") return this.resolve(`${OPENCODE_SERVER_DIST}/node.js`)
},
},
{
name: "opencode:copy-server-assets",
async writeBundle() {
for (const l of await fs.readdir(OPENCODE_SERVER_DIST)) {
if (!l.endsWith(".wasm")) continue
await fs.writeFile(`./out/main/chunks/${l}`, await fs.readFile(`${OPENCODE_SERVER_DIST}/${l}`))
}
},
},
],
},
preload: {
build: {
rollupOptions: {
input: { index: "src/preload/index.ts" },
output: {
format: "cjs",
entryFileNames: "[name].js",
},
},
},
},
renderer: {
plugins: [appPlugin, sentry],
publicDir: "../../../app/public",
root: "src/renderer",
define: {
"import.meta.env.VITE_OPENCODE_CHANNEL": JSON.stringify(channel),
},
build: {
sourcemap: true,
rollupOptions: {
input: {
main: "src/renderer/index.html",
loading: "src/renderer/loading.html",
},
},
},
},
})

View File

@@ -9,3 +9,6 @@ Here's the process I've been using to create icons:
The Image2Icon step is necessary as the `icon.icns` generated by `app-icon.png` does not apply the shadow/padding expected by macOS,
so app icons appear larger than expected.
For unpackaged Electron on macOS, `app.dock.setIcon()` should use a PNG. Keep `dock.png` in each channel folder synced with the
extracted `icon_128x128@2x.png` from that channel's `icon.icns` so the dev Dock icon matches the packaged app inset.

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

Before

Width:  |  Height:  |  Size: 687 B

After

Width:  |  Height:  |  Size: 687 B

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 582 KiB

After

Width:  |  Height:  |  Size: 582 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 258 KiB

View File

Before

Width:  |  Height:  |  Size: 955 B

After

Width:  |  Height:  |  Size: 955 B

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Some files were not shown because too many files have changed in this diff Show More