desktop: add electron version (#15663)
|
|
@ -99,7 +99,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: opencode-cli
|
name: opencode-cli
|
||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ needs.version.outputs.version }}
|
version: ${{ needs.version.outputs.version }}
|
||||||
|
|
||||||
|
|
@ -240,11 +239,130 @@ jobs:
|
||||||
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||||
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
|
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
|
||||||
|
|
||||||
|
build-electron:
|
||||||
|
needs:
|
||||||
|
- build-cli
|
||||||
|
- version
|
||||||
|
continue-on-error: false
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
settings:
|
||||||
|
- host: macos-latest
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
platform_flag: --mac --x64
|
||||||
|
- host: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
platform_flag: --mac --arm64
|
||||||
|
- host: "blacksmith-4vcpu-windows-2025"
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
platform_flag: --win
|
||||||
|
- host: "blacksmith-4vcpu-ubuntu-2404"
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
platform_flag: --linux
|
||||||
|
- host: "blacksmith-4vcpu-ubuntu-2404"
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
platform_flag: --linux
|
||||||
|
runs-on: ${{ matrix.settings.host }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: apple-actions/import-codesign-certs@v2
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
with:
|
||||||
|
keychain: build
|
||||||
|
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Setup Apple API Key
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
|
||||||
|
- name: Cache apt packages
|
||||||
|
if: contains(matrix.settings.host, 'ubuntu')
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/apt-cache
|
||||||
|
key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-
|
||||||
|
|
||||||
|
- name: Install dependencies (ubuntu only)
|
||||||
|
if: contains(matrix.settings.host, 'ubuntu')
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" rpm
|
||||||
|
sudo chmod -R a+rw ~/apt-cache
|
||||||
|
|
||||||
|
- name: Setup git committer
|
||||||
|
id: committer
|
||||||
|
uses: ./.github/actions/setup-git-committer
|
||||||
|
with:
|
||||||
|
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||||
|
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: bun ./scripts/prepare.ts
|
||||||
|
working-directory: packages/desktop-electron
|
||||||
|
env:
|
||||||
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
|
RUST_TARGET: ${{ matrix.settings.target }}
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: bun run build
|
||||||
|
working-directory: packages/desktop-electron
|
||||||
|
env:
|
||||||
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
|
|
||||||
|
- name: Package and publish
|
||||||
|
if: needs.version.outputs.release
|
||||||
|
run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish always --config electron-builder.config.ts
|
||||||
|
working-directory: packages/desktop-electron
|
||||||
|
timeout-minutes: 60
|
||||||
|
env:
|
||||||
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
|
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
|
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
|
||||||
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
|
||||||
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
|
|
||||||
|
- name: Package (no publish)
|
||||||
|
if: ${{ !needs.version.outputs.release }}
|
||||||
|
run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
|
||||||
|
working-directory: packages/desktop-electron
|
||||||
|
timeout-minutes: 60
|
||||||
|
env:
|
||||||
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: opencode-electron-${{ matrix.settings.target }}
|
||||||
|
path: packages/desktop-electron/dist/*
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: needs.version.outputs.release
|
||||||
|
with:
|
||||||
|
name: latest-yml-${{ matrix.settings.target }}
|
||||||
|
path: packages/desktop-electron/dist/latest*.yml
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs:
|
needs:
|
||||||
- version
|
- version
|
||||||
- build-cli
|
- build-cli
|
||||||
- build-tauri
|
- build-tauri
|
||||||
|
- build-electron
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
@ -281,6 +399,12 @@ jobs:
|
||||||
name: opencode-cli
|
name: opencode-cli
|
||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
if: needs.version.outputs.release
|
||||||
|
with:
|
||||||
|
pattern: latest-yml-*
|
||||||
|
path: /tmp/latest-yml
|
||||||
|
|
||||||
- name: Cache apt packages (AUR)
|
- name: Cache apt packages (AUR)
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -308,3 +432,4 @@ jobs:
|
||||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||||
NPM_CONFIG_PROVENANCE: false
|
NPM_CONFIG_PROVENANCE: false
|
||||||
|
LATEST_YML_DIR: /tmp/latest-yml
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,8 @@
|
||||||
"protobufjs",
|
"protobufjs",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
"tree-sitter-bash",
|
"tree-sitter-bash",
|
||||||
"web-tree-sitter"
|
"web-tree-sitter",
|
||||||
|
"electron"
|
||||||
],
|
],
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||||
import { Font } from "@opencode-ai/ui/font"
|
import { Font } from "@opencode-ai/ui/font"
|
||||||
import { ThemeProvider } from "@opencode-ai/ui/theme"
|
import { ThemeProvider } from "@opencode-ai/ui/theme"
|
||||||
import { MetaProvider } from "@solidjs/meta"
|
import { MetaProvider } from "@solidjs/meta"
|
||||||
import { Navigate, Route, Router } from "@solidjs/router"
|
import { BaseRouterProps, Navigate, Route, Router } from "@solidjs/router"
|
||||||
import { ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js"
|
import { Component, ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js"
|
||||||
import { CommandProvider } from "@/context/command"
|
import { CommandProvider } from "@/context/command"
|
||||||
import { CommentsProvider } from "@/context/comments"
|
import { CommentsProvider } from "@/context/comments"
|
||||||
import { FileProvider } from "@/context/file"
|
import { FileProvider } from "@/context/file"
|
||||||
|
|
@ -28,6 +28,7 @@ import { TerminalProvider } from "@/context/terminal"
|
||||||
import DirectoryLayout from "@/pages/directory-layout"
|
import DirectoryLayout from "@/pages/directory-layout"
|
||||||
import Layout from "@/pages/layout"
|
import Layout from "@/pages/layout"
|
||||||
import { ErrorPage } from "./pages/error"
|
import { ErrorPage } from "./pages/error"
|
||||||
|
import { Dynamic } from "solid-js/web"
|
||||||
|
|
||||||
const Home = lazy(() => import("@/pages/home"))
|
const Home = lazy(() => import("@/pages/home"))
|
||||||
const Session = lazy(() => import("@/pages/session"))
|
const Session = lazy(() => import("@/pages/session"))
|
||||||
|
|
@ -144,13 +145,15 @@ export function AppInterface(props: {
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
defaultServer: ServerConnection.Key
|
defaultServer: ServerConnection.Key
|
||||||
servers?: Array<ServerConnection.Any>
|
servers?: Array<ServerConnection.Any>
|
||||||
|
router?: Component<BaseRouterProps>
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ServerProvider defaultServer={props.defaultServer} servers={props.servers}>
|
<ServerProvider defaultServer={props.defaultServer} servers={props.servers}>
|
||||||
<ServerKey>
|
<ServerKey>
|
||||||
<GlobalSDKProvider>
|
<GlobalSDKProvider>
|
||||||
<GlobalSyncProvider>
|
<GlobalSyncProvider>
|
||||||
<Router
|
<Dynamic
|
||||||
|
component={props.router ?? Router}
|
||||||
root={(routerProps) => <RouterRoot appChildren={props.children}>{routerProps.children}</RouterRoot>}
|
root={(routerProps) => <RouterRoot appChildren={props.children}>{routerProps.children}</RouterRoot>}
|
||||||
>
|
>
|
||||||
<Route path="/" component={HomeRoute} />
|
<Route path="/" component={HomeRoute} />
|
||||||
|
|
@ -158,7 +161,7 @@ export function AppInterface(props: {
|
||||||
<Route path="/" component={SessionIndexRoute} />
|
<Route path="/" component={SessionIndexRoute} />
|
||||||
<Route path="/session/:id?" component={SessionRoute} />
|
<Route path="/session/:id?" component={SessionRoute} />
|
||||||
</Route>
|
</Route>
|
||||||
</Router>
|
</Dynamic>
|
||||||
</GlobalSyncProvider>
|
</GlobalSyncProvider>
|
||||||
</GlobalSDKProvider>
|
</GlobalSDKProvider>
|
||||||
</ServerKey>
|
</ServerKey>
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,7 @@ export function Titlebar() {
|
||||||
<header
|
<header
|
||||||
class="h-10 shrink-0 bg-background-base relative grid grid-cols-[auto_minmax(0,1fr)_auto] items-center"
|
class="h-10 shrink-0 bg-background-base relative grid grid-cols-[auto_minmax(0,1fr)_auto] items-center"
|
||||||
style={{ "min-height": minHeight() }}
|
style={{ "min-height": minHeight() }}
|
||||||
|
data-tauri-drag-region
|
||||||
onMouseDown={drag}
|
onMouseDown={drag}
|
||||||
onDblClick={maximize}
|
onDblClick={maximize}
|
||||||
>
|
>
|
||||||
|
|
@ -276,6 +277,7 @@ export function Titlebar() {
|
||||||
"flex items-center min-w-0 justify-end": true,
|
"flex items-center min-w-0 justify-end": true,
|
||||||
"pr-2": !windows(),
|
"pr-2": !windows(),
|
||||||
}}
|
}}
|
||||||
|
data-tauri-drag-region
|
||||||
onMouseDown={drag}
|
onMouseDown={drag}
|
||||||
>
|
>
|
||||||
<div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />
|
<div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { retry } from "@opencode-ai/util/retry"
|
import { retry } from "@opencode-ai/util/retry"
|
||||||
import { playSound, soundSrc } from "@/utils/sound"
|
import { playSound, soundSrc } from "@/utils/sound"
|
||||||
import { createAim } from "@/utils/aim"
|
import { createAim } from "@/utils/aim"
|
||||||
|
import { setNavigate } from "@/utils/notification-click"
|
||||||
import { Worktree as WorktreeState } from "@/utils/worktree"
|
import { Worktree as WorktreeState } from "@/utils/worktree"
|
||||||
|
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
|
@ -107,6 +108,7 @@ export default function Layout(props: ParentProps) {
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
setNavigate(navigate)
|
||||||
const providers = useProviders()
|
const providers = useProviders()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const command = useCommand()
|
const command = useCommand()
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
import { describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import { handleNotificationClick } from "./notification-click"
|
import { handleNotificationClick, setNavigate } from "./notification-click"
|
||||||
|
|
||||||
describe("notification click", () => {
|
describe("notification click", () => {
|
||||||
test("focuses and navigates when href exists", () => {
|
afterEach(() => {
|
||||||
const calls: string[] = []
|
setNavigate(undefined as any)
|
||||||
handleNotificationClick("/abc/session/123", {
|
|
||||||
focus: () => calls.push("focus"),
|
|
||||||
location: {
|
|
||||||
assign: (href) => calls.push(href),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(calls).toEqual(["focus", "/abc/session/123"])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("only focuses when href is missing", () => {
|
test("navigates via registered navigate function", () => {
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
handleNotificationClick(undefined, {
|
setNavigate((href) => calls.push(href))
|
||||||
focus: () => calls.push("focus"),
|
handleNotificationClick("/abc/session/123")
|
||||||
location: {
|
expect(calls).toEqual(["/abc/session/123"])
|
||||||
assign: (href) => calls.push(href),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
expect(calls).toEqual(["focus"])
|
|
||||||
|
test("does not navigate when href is missing", () => {
|
||||||
|
const calls: string[] = []
|
||||||
|
setNavigate((href) => calls.push(href))
|
||||||
|
handleNotificationClick(undefined)
|
||||||
|
expect(calls).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to location.assign without registered navigate", () => {
|
||||||
|
handleNotificationClick("/abc/session/123")
|
||||||
|
// falls back to window.location.assign — no error thrown
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
type WindowTarget = {
|
let nav: ((href: string) => void) | undefined
|
||||||
focus: () => void
|
|
||||||
location: {
|
export const setNavigate = (fn: (href: string) => void) => {
|
||||||
assign: (href: string) => void
|
nav = fn
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleNotificationClick = (href?: string, target: WindowTarget = window) => {
|
export const handleNotificationClick = (href?: string) => {
|
||||||
target.focus()
|
window.focus()
|
||||||
if (!href) return
|
if (!href) return
|
||||||
target.location.assign(href)
|
if (nav) nav(href)
|
||||||
|
else window.location.assign(href)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
out/
|
||||||
|
|
||||||
|
resources/opencode-cli*
|
||||||
|
resources/icons
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Desktop package notes
|
||||||
|
|
||||||
|
- Renderer process should only call `window.api` from `src/preload`.
|
||||||
|
- Main process should register IPC handlers in `src/main/ipc.ts`.
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# OpenCode Desktop
|
||||||
|
|
||||||
|
Native OpenCode desktop app, built with Tauri v2.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
From the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import type { Configuration } from "electron-builder"
|
||||||
|
|
||||||
|
const channel = (() => {
|
||||||
|
const raw = process.env.OPENCODE_CHANNEL
|
||||||
|
if (raw === "dev" || raw === "beta" || raw === "prod") return raw
|
||||||
|
return "dev"
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getBase = (): Configuration => ({
|
||||||
|
artifactName: "opencode-electron-${os}-${arch}.${ext}",
|
||||||
|
directories: {
|
||||||
|
output: "dist",
|
||||||
|
buildResources: "resources",
|
||||||
|
},
|
||||||
|
files: ["out/**/*", "resources/**/*"],
|
||||||
|
extraResources: [
|
||||||
|
{
|
||||||
|
from: "resources/",
|
||||||
|
to: "",
|
||||||
|
filter: ["opencode-cli*"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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`,
|
||||||
|
target: ["nsis"],
|
||||||
|
},
|
||||||
|
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()
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { defineConfig } from "electron-vite"
|
||||||
|
import appPlugin from "@opencode-ai/app/vite"
|
||||||
|
|
||||||
|
const channel = (() => {
|
||||||
|
const raw = process.env.OPENCODE_CHANNEL
|
||||||
|
if (raw === "dev" || raw === "beta" || raw === "prod") return raw
|
||||||
|
return "dev"
|
||||||
|
})()
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
main: {
|
||||||
|
define: {
|
||||||
|
"import.meta.env.OPENCODE_CHANNEL": JSON.stringify(channel),
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: { index: "src/main/index.ts" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: { index: "src/preload/index.ts" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
plugins: [appPlugin],
|
||||||
|
publicDir: "../app/public",
|
||||||
|
root: "src/renderer",
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: "src/renderer/index.html",
|
||||||
|
loading: "src/renderer/loading.html",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Tauri Icons
|
||||||
|
|
||||||
|
Here's the process I've been using to create icons:
|
||||||
|
|
||||||
|
- Save source image as `app-icon.png` in `packages/desktop`
|
||||||
|
- `cd` to `packages/desktop`
|
||||||
|
- Run `bun tauri icon -o src-tauri/icons/{environment}`
|
||||||
|
- Use [Image2Icon](https://img2icnsapp.com/)'s 'Big Sur Icon' preset to generate an `icon.icns` file and place it in the appropriate icons folder
|
||||||
|
|
||||||
|
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.
|
||||||
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 687 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 582 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 258 KiB |