diff --git a/.github/actions/build-sea/action.yml b/.github/actions/build-sea/action.yml new file mode 100644 index 00000000..dbd8b52e --- /dev/null +++ b/.github/actions/build-sea/action.yml @@ -0,0 +1,59 @@ +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 + +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=$(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 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..e73f3ec9 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,61 @@ 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 }} + + - 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', + }, + }, + }, +});