From 0e84ceb935f6096f691e9f1b14fd12c4dc2f4181 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 11 Jul 2026 23:11:51 -0400 Subject: [PATCH 1/2] feat(sea): build single executable application and upload to release Add experimental standalone executables built with Node.js SEA while retaining the npm package as the primary, fully supported distribution. Build each executable on its native GitHub-hosted runner and attach versioned archives to the GitHub release created by Release Please. --- Implementation - Use Node.js 25.9.0 for reproducible SEA builds and keep the normal npm build on the Node version declared by `.nvmrc`. - Build one ESM entry with `vite.sea.config.mts`, disabling code splitting, bundling application dependencies, and leaving `@napi-rs/keyring` external. - Load the native keyring dependency lazily so unsupported OAuth credential storage does not prevent a standalone executable from starting. - Generate executables with `sea-config.json` and `node --build-sea`. - Share setup, build, platform-specific generation, macOS ad-hoc signing, and smoke tests through `.github/actions/build-sea/action.yml`. - Run one Linux x64 SEA smoke build in `.github/workflows/build.yml` to catch packaging regressions before release. - Build release executables on native runners for Linux x64/ARM64, macOS Intel/ARM64, and Windows x64. - Compare the version reported by each executable with the Release Please tag after removing its leading `v`, then verify `--help`. - Archive Unix executables as `.tar.gz` and Windows executables as `.zip` using separate native-shell steps, then upload them with `gh release upload`. - Name release assets `code-ollama-v--.` while keeping the contained executable named `code-ollama` or `code-ollama.exe`. --- Limitations - OAuth authentication for MCP servers is unsupported in standalone executables because the platform-native keyring addon is not embedded. - Document this limitation in the README and direct OAuth users to the npm package. - Keep standalone executables experimental while Node SEA remains under active development. --- .github/actions/build-sea/action.yml | 68 ++++++++++++++++++++++++++++ .github/workflows/build.yml | 15 +++++- .github/workflows/release-please.yml | 60 ++++++++++++++++++++++++ .gitignore | 1 + README.md | 7 +++ sea-config.json | 6 +++ vite.config.mts | 20 ++++---- vite.sea.config.mts | 28 ++++++++++++ 8 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 .github/actions/build-sea/action.yml create mode 100644 sea-config.json create mode 100644 vite.sea.config.mts diff --git a/.github/actions/build-sea/action.yml b/.github/actions/build-sea/action.yml new file mode 100644 index 00000000..5574a2c8 --- /dev/null +++ b/.github/actions/build-sea/action.yml @@ -0,0 +1,68 @@ +name: Build SEA +description: Build and verify a Node.js single executable application + +inputs: + executable: + description: Executable filename written to the release directory + required: true + expected-version: + description: Expected package version or release tag + required: false + default: '' + +runs: + using: composite + steps: + - name: Use Node.js + uses: actions/setup-node@v6 + with: + cache: npm + node-version: 25.9.0 + + - name: Install dependencies + shell: bash + run: npm ci --prefer-offline + + - name: Build SEA bundle + shell: bash + run: npm run build -- --config vite.sea.config.mts + + - name: Create release directory + shell: bash + run: mkdir -p release + + - name: Build executable + if: runner.os != 'Windows' + shell: bash + run: node --build-sea sea-config.json + + - name: Build executable on Windows + if: runner.os == 'Windows' + shell: pwsh + run: | + $config = Get-Content sea-config.json | ConvertFrom-Json + $config.output = "release/${{ inputs.executable }}" + $config | ConvertTo-Json | Set-Content sea-config.windows.json + node --build-sea sea-config.windows.json + + - name: Sign executable on macOS + if: runner.os == 'macOS' + shell: bash + run: codesign --sign - --force "release/${{ inputs.executable }}" + + - name: Verify executable + shell: bash + run: | + actual=$(release/${{ inputs.executable }} --version) + expected=${EXPECTED_VERSION#v} + if [ -z "$expected" ]; then + expected=$(node -p "require('./package.json').version") + fi + actual_version=${actual%% *} + actual_version=${actual_version#code-ollama/} + + printf '%s\n' "$actual" + test "$actual_version" = "$expected" + release/${{ inputs.executable }} --help + env: + EXPECTED_VERSION: ${{ inputs.expected-version }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e15fd1fe..a262ca4d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,10 +2,10 @@ name: build on: [push, pull_request] permissions: - contents: write + contents: read jobs: - build: + build-package: runs-on: ubuntu-latest steps: - name: Checkout repository @@ -22,3 +22,14 @@ jobs: - name: Build package run: npm run build + + build-sea: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Build SEA + uses: ./.github/actions/build-sea + with: + executable: code-ollama diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 7755f570..ad330b12 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -9,6 +9,7 @@ jobs: runs-on: ubuntu-latest outputs: release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} permissions: contents: write pull-requests: write @@ -43,3 +44,62 @@ jobs: - name: Publish run: npm publish --provenance --access public + + build: + runs-on: ${{ matrix.os }} + needs: release + if: ${{ needs.release.outputs.release_created }} + permissions: + contents: write + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: linux-x64 + executable: code-ollama + - os: ubuntu-24.04-arm + target: linux-arm64 + executable: code-ollama + - os: macos-15-intel + target: darwin-x64 + executable: code-ollama + - os: macos-latest + target: darwin-arm64 + executable: code-ollama + - os: windows-latest + target: windows-x64 + executable: code-ollama.exe + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Build SEA + uses: ./.github/actions/build-sea + with: + executable: ${{ matrix.executable }} + expected-version: ${{ needs.release.outputs.tag_name }} + + - name: Archive and upload executable + if: runner.os != 'Windows' + run: | + archive="release/code-ollama-${TAG_NAME}-${TARGET}.tar.gz" + tar -czf "$archive" -C release "${{ matrix.executable }}" + gh release upload "$TAG_NAME" "$archive" --clobber + env: + GH_TOKEN: ${{ github.token }} + TAG_NAME: ${{ needs.release.outputs.tag_name }} + TARGET: ${{ matrix.target }} + + - name: Archive and upload executable on Windows + if: runner.os == 'Windows' + shell: pwsh + run: | + $archive = "release/code-ollama-$env:TAG_NAME-$env:TARGET.zip" + Compress-Archive -Path "release/${{ matrix.executable }}" -DestinationPath $archive + gh release upload $env:TAG_NAME $archive --clobber + env: + GH_TOKEN: ${{ github.token }} + TAG_NAME: ${{ needs.release.outputs.tag_name }} + TARGET: ${{ matrix.target }} diff --git a/.gitignore b/.gitignore index 5437c3c4..0633986f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ jspm_packages # Build files dist/ docs/ +release/ # Vim swap files *.swp diff --git a/README.md b/README.md index 67043eb8..cb611ff8 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,13 @@ Install the [CLI](https://www.npmjs.com/package/code-ollama) globally: npm install --global code-ollama ``` +## Download + +Standalone executables for Linux, macOS, and Windows are also available from [GitHub Releases](https://github.com/ai-action/code-ollama/releases). Extract the archive for your operating system and architecture, then run `code-ollama` (or `code-ollama.exe` on Windows). + +> [!WARNING] +> OAuth authentication for MCP servers is not supported in standalone executables. Install the npm package when OAuth-backed MCP servers are required. + ## Usage ### TUI diff --git a/sea-config.json b/sea-config.json new file mode 100644 index 00000000..2f59263c --- /dev/null +++ b/sea-config.json @@ -0,0 +1,6 @@ +{ + "main": "dist/sea/cli.js", + "mainFormat": "module", + "output": "release/code-ollama", + "disableExperimentalSEAWarning": true +} diff --git a/vite.config.mts b/vite.config.mts index 50ca6e14..7cffc4d4 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -3,17 +3,19 @@ import { resolve } from 'node:path'; import { defineConfig } from 'vitest/config'; +export const alias = { + '@': resolve(__dirname, 'src'), + '@modelcontextprotocol/sdk/client/auth': + '@modelcontextprotocol/sdk/client/auth.js', + '@modelcontextprotocol/sdk/client/stdio': + '@modelcontextprotocol/sdk/client/stdio.js', + '@modelcontextprotocol/sdk/client/streamableHttp': + '@modelcontextprotocol/sdk/client/streamableHttp.js', +}; + export default defineConfig({ resolve: { - alias: { - '@': resolve(__dirname, 'src'), - '@modelcontextprotocol/sdk/client/auth': - '@modelcontextprotocol/sdk/client/auth.js', - '@modelcontextprotocol/sdk/client/stdio': - '@modelcontextprotocol/sdk/client/stdio.js', - '@modelcontextprotocol/sdk/client/streamableHttp': - '@modelcontextprotocol/sdk/client/streamableHttp.js', - }, + alias, }, build: { diff --git a/vite.sea.config.mts b/vite.sea.config.mts new file mode 100644 index 00000000..8031f441 --- /dev/null +++ b/vite.sea.config.mts @@ -0,0 +1,28 @@ +import { defineConfig } from 'vite'; + +import { alias } from './vite.config.mts'; + +export default defineConfig({ + resolve: { + alias, + }, + + ssr: { + noExternal: true, + }, + + build: { + emptyOutDir: true, + outDir: 'dist/sea', + ssr: 'src/cli.ts', + target: 'node25', + rolldownOptions: { + external: ['@napi-rs/keyring'], + output: { + codeSplitting: false, + entryFileNames: 'cli.js', + format: 'esm', + }, + }, + }, +}); From b1565a2d3b880d90e7261c62ad1589a6c2af4f3e Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 11 Jul 2026 23:20:03 -0400 Subject: [PATCH 2/2] ci(github): remove expected-version from the shared action and release workflow The action now always: 1. Reads the expected version from `package.json`. 2. Extracts the version from the executable output. 3. Requires an exact match. 4. Verifies `--help`. --- .github/actions/build-sea/action.yml | 11 +---------- .github/workflows/release-please.yml | 1 - 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/actions/build-sea/action.yml b/.github/actions/build-sea/action.yml index 5574a2c8..dbd8b52e 100644 --- a/.github/actions/build-sea/action.yml +++ b/.github/actions/build-sea/action.yml @@ -5,10 +5,6 @@ inputs: executable: description: Executable filename written to the release directory required: true - expected-version: - description: Expected package version or release tag - required: false - default: '' runs: using: composite @@ -54,15 +50,10 @@ runs: shell: bash run: | actual=$(release/${{ inputs.executable }} --version) - expected=${EXPECTED_VERSION#v} - if [ -z "$expected" ]; then - expected=$(node -p "require('./package.json').version") - fi + expected=$(node -p "require('./package.json').version") actual_version=${actual%% *} actual_version=${actual_version#code-ollama/} printf '%s\n' "$actual" test "$actual_version" = "$expected" release/${{ inputs.executable }} --help - env: - EXPECTED_VERSION: ${{ inputs.expected-version }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index ad330b12..e73f3ec9 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -79,7 +79,6 @@ jobs: uses: ./.github/actions/build-sea with: executable: ${{ matrix.executable }} - expected-version: ${{ needs.release.outputs.tag_name }} - name: Archive and upload executable if: runner.os != 'Windows'