diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 9d29724d86a4..ad3fd6daf873 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -58,10 +58,18 @@ runs: # Workaround for patched peer variants # e.g. ./patches/ for standard-openapi # https://github.com/oven-sh/bun/issues/28147 - if [ "$RUNNER_OS" = "Windows" ]; then - bun install --linker hoisted ${{ inputs.install-flags }} - else - bun install ${{ inputs.install-flags }} + install_dependencies() { + if [ "$RUNNER_OS" = "Windows" ]; then + bun install --linker hoisted ${{ inputs.install-flags }} + else + bun install ${{ inputs.install-flags }} + fi + } + + if ! install_dependencies; then + echo "Bun install failed; clearing the runner-local Bun cache and retrying" + rm -rf "$(bun pm cache)" + install_dependencies fi shell: bash diff --git a/.github/workflows/publish-fork-release.yml b/.github/workflows/publish-fork-release.yml new file mode 100644 index 000000000000..553e006c0065 --- /dev/null +++ b/.github/workflows/publish-fork-release.yml @@ -0,0 +1,70 @@ +name: publish-fork-release + +on: + workflow_dispatch: + inputs: + version: + description: Release version without the v prefix + required: true + default: 1.18.23 + source_run_id: + description: Actions run ID containing the completed desktop artifacts + required: true + default: "33138463284" + +permissions: + actions: read + contents: write + +jobs: + publish: + runs-on: ubuntu-24.04 + env: + OPENCODE_VERSION: ${{ inputs.version }} + SOURCE_RUN_ID: ${{ inputs.source_run_id }} + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + pattern: desktop-* + path: /tmp/desktop + merge-multiple: true + run-id: ${{ inputs.source_run_id }} + github-token: ${{ github.token }} + + - name: Upload desktop installers and publish release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + if [[ ! "$SOURCE_RUN_ID" =~ ^[0-9]+$ ]]; then + echo "source_run_id must be numeric" + exit 1 + fi + if [[ "$(gh release view "v${OPENCODE_VERSION}" --json isDraft --jq .isDraft --repo "${GITHUB_REPOSITORY}")" != "true" ]]; then + echo "Release v${OPENCODE_VERSION} is missing or already published" + exit 1 + fi + mapfile -d '' files < <(find /tmp/desktop -mindepth 1 -maxdepth 1 -type f \( \ + -name 'opencode-desktop-*.AppImage' -o \ + -name 'opencode-desktop-*.blockmap' -o \ + -name 'opencode-desktop-*.deb' -o \ + -name 'opencode-desktop-*.dmg' -o \ + -name 'opencode-desktop-*.exe' -o \ + -name 'opencode-desktop-*.rpm' -o \ + -name 'opencode-desktop-*.tar.gz' -o \ + -name 'opencode-desktop-*.zip' -o \ + -name 'latest.json' -o \ + -name 'latest*.yml' \ + \) -print0) + if (( ${#files[@]} == 0 )); then + echo "No desktop release assets found" + exit 1 + fi + printf 'Uploading %s\n' "${files[@]}" + gh release upload "v${OPENCODE_VERSION}" "${files[@]}" \ + --clobber \ + --repo "${GITHUB_REPOSITORY}" + gh release edit "v${OPENCODE_VERSION}" \ + --draft=false \ + --latest \ + --repo "${GITHUB_REPOSITORY}" diff --git a/.github/workflows/release-fork.yml b/.github/workflows/release-fork.yml new file mode 100644 index 000000000000..9e566968170d --- /dev/null +++ b/.github/workflows/release-fork.yml @@ -0,0 +1,180 @@ +name: release-fork + +on: + workflow_dispatch: + inputs: + version: + description: "Release version without the v prefix" + required: true + default: "1.18.23" + type: string + +permissions: + contents: write + +concurrency: + group: release-fork-${{ inputs.version }} + cancel-in-progress: false + +env: + OPENCODE_VERSION: ${{ inputs.version }} + OPENCODE_CHANNEL: prod + +jobs: + create-release: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v4.3.0 + with: + fetch-depth: 0 + + - name: Create draft release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + if gh release view "v${OPENCODE_VERSION}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then + if [ "$(gh release view "v${OPENCODE_VERSION}" --repo "${GITHUB_REPOSITORY}" --json isDraft --jq .isDraft)" != "true" ]; then + echo "Published release v${OPENCODE_VERSION} already exists" + exit 1 + fi + echo "Reusing existing draft release v${OPENCODE_VERSION}" + exit 0 + fi + gh release create "v${OPENCODE_VERSION}" \ + --draft \ + --generate-notes \ + --target "${GITHUB_SHA}" \ + --title "v${OPENCODE_VERSION}" \ + --repo "${GITHUB_REPOSITORY}" + + build-cli: + needs: create-release + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v4.3.0 + + - uses: ./.github/actions/setup-bun + + - name: Build CLI archives + env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + OPENCODE_RELEASE: "true" + run: | + ./packages/opencode/script/build.ts + ./packages/cli/script/build.ts + + build-desktop: + needs: create-release + strategy: + fail-fast: false + matrix: + settings: + - host: macos-26-intel + target: x86_64-apple-darwin + platform_flag: --mac --x64 + bun_install_flags: --os=darwin --cpu=x64 + - host: macos-26 + target: aarch64-apple-darwin + platform_flag: --mac --arm64 + bun_install_flags: --os=darwin --cpu=arm64 + - host: windows-2025 + target: aarch64-pc-windows-msvc + platform_flag: --win --arm64 + - host: windows-2025 + target: x86_64-pc-windows-msvc + platform_flag: --win + bun_install_flags: --os=win32 --cpu=x64 + - host: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + platform_flag: --linux + - host: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + platform_flag: --linux --arm64 + runs-on: ${{ matrix.settings.host }} + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v4.3.0 + + - uses: ./.github/actions/setup-bun + with: + install-flags: ${{ matrix.settings.bun_install_flags }} + + - name: Install Linux packaging tools + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends rpm + + - name: Prepare desktop build + working-directory: packages/desktop + run: bun ./scripts/prepare.ts + env: + OPENCODE_VERSION: ${{ env.OPENCODE_VERSION }} + OPENCODE_CHANNEL: prod + RUST_TARGET: ${{ matrix.settings.target }} + + - name: Build desktop application + working-directory: packages/desktop + run: bun run build + env: + NODE_OPTIONS: --max-old-space-size=4096 + OPENCODE_VERSION: ${{ env.OPENCODE_VERSION }} + OPENCODE_CHANNEL: prod + RUST_TARGET: ${{ matrix.settings.target }} + OPENCODE_UNSIGNED_BUILD: "true" + + - name: Package desktop installer + working-directory: packages/desktop + run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts + env: + GITHUB_ACTIONS: "false" + CSC_IDENTITY_AUTO_DISCOVERY: "false" + OPENCODE_CHANNEL: prod + OPENCODE_UNSIGNED_BUILD: "true" + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: desktop-${{ matrix.settings.target }} + path: packages/desktop/dist/* + + publish-release: + needs: + - build-cli + - build-desktop + runs-on: ubuntu-24.04 + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + pattern: desktop-* + path: /tmp/desktop + merge-multiple: true + + - name: Upload desktop installers and publish release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + mapfile -d '' files < <(find /tmp/desktop -mindepth 1 -maxdepth 1 -type f \( \ + -name 'opencode-desktop-*.AppImage' -o \ + -name 'opencode-desktop-*.blockmap' -o \ + -name 'opencode-desktop-*.deb' -o \ + -name 'opencode-desktop-*.dmg' -o \ + -name 'opencode-desktop-*.exe' -o \ + -name 'opencode-desktop-*.rpm' -o \ + -name 'opencode-desktop-*.tar.gz' -o \ + -name 'opencode-desktop-*.zip' -o \ + -name 'latest.json' -o \ + -name 'latest*.yml' \ + \) -print0) + if (( ${#files[@]} == 0 )); then + echo "No desktop release assets found" + exit 1 + fi + gh release upload "v${OPENCODE_VERSION}" "${files[@]}" \ + --clobber \ + --repo "${GITHUB_REPOSITORY}" + gh release edit "v${OPENCODE_VERSION}" \ + --draft=false \ + --latest \ + --repo "${GITHUB_REPOSITORY}" diff --git a/packages/desktop/electron-builder.config.ts b/packages/desktop/electron-builder.config.ts index 508c0df5e914..cac1ba2987f9 100644 --- a/packages/desktop/electron-builder.config.ts +++ b/packages/desktop/electron-builder.config.ts @@ -9,6 +9,7 @@ const execFileAsync = promisify(execFile) const packageDir = path.dirname(fileURLToPath(import.meta.url)) const rootDir = path.resolve(packageDir, "../..") const signScript = path.join(rootDir, "script", "sign-windows.ps1") +const unsignedBuild = process.env.OPENCODE_UNSIGNED_BUILD === "true" // The Electron 42 packaging update briefly installed Linux launchers/icons under // "opencode-desktop". Keep that hidden desktop entry around so existing GNOME/KDE // pins still resolve after the canonical app id changes back to ai.opencode.desktop. @@ -19,6 +20,7 @@ const metainfoFpm = (appId: string) => `${path.join(packageDir, "resources", `${appId}.metainfo.xml`)}=/usr/share/metainfo/${appId}.metainfo.xml` async function signWindows(configuration: { path: string }) { + if (unsignedBuild) return if (process.platform !== "win32") return if (process.env.GITHUB_ACTIONS !== "true") return @@ -75,15 +77,15 @@ const getBase = (appId: string): Configuration => ({ mac: { category: "public.app-category.developer-tools", icon: `resources/icons/icon.icns`, - hardenedRuntime: true, + hardenedRuntime: !unsignedBuild, gatekeeperAssess: false, entitlements: "resources/entitlements.plist", entitlementsInherit: "resources/entitlements.plist", - notarize: true, + notarize: !unsignedBuild, target: ["dmg", "zip"], }, dmg: { - sign: true, + sign: !unsignedBuild, }, protocols: { name: "OpenCode", diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index fe7f4a22f75f..5443cd88c587 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -165,6 +165,7 @@ export function Prompt(props: PromptProps) { const stash = usePromptStash() const keymap = useOpencodeKeymap() const agentShortcut = useCommandShortcut("agent.cycle") + const variantShortcut = useCommandShortcut("variant.cycle") const paletteShortcut = useCommandShortcut("command.palette.show") const renderer = useRenderer() const exit = useExit() @@ -1675,6 +1676,11 @@ export function Prompt(props: PromptProps) { + 0 && variantShortcut()}> + + {variantShortcut()} thinking + + {paletteShortcut()} commands