Phase 1 of exobrain architecture. Ships TS MCP server as a static
binary so users on machines without Node can run KeiSeiKit (USB /
flashdrive / air-gapped scenarios).
.github/workflows/release.yml (+62 LOC) — new build-mcp-binary job:
- 5-target matrix: darwin arm64/x64, linux arm64/x64, windows x64
- bun build --compile, linux arm64 continue-on-error (ARM runners
less reliable)
- Artifact kei-mcp-server-<os>-<arch>[.exe] + sha256
- release job now needs [build-release, build-mcp-binary]
install/lib-rust.sh (+50 LOC) — have_prebuilt_mcp_server() +
report_mcp_server_binary_status(); KEI_SKIP_MCP_BUILD=1 env
flag skips bun/npm install when a prebuilt binary is present.
File 165 LOC (<200 limit).
_ts_packages/packages/mcp-server/package.json — scripts.build:native
+ 5 per-target aliases (macos-arm, macos-x64, linux-x64,
linux-arm, win-x64) for local dev.
_ts_packages/packages/mcp-server/BUILD.md (NEW, 52 LOC) — local
compile guide per platform + Gatekeeper/code-sign notes +
cites bun docs [VERIFIED: https://bun.sh/docs/bundler/executables].
README.md pre-built-binaries section gains 'MCP server binary'
subsection (download, chmod +x, xattr -d com.apple.quarantine for
macOS, UAC note for Windows).
CHANGELOG.md [Unreleased] bullet added.
Output size: ~90 MB per binary (bundled bun runtime). Acceptable
trade for zero-dep USB distribution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
297 lines
10 KiB
YAML
297 lines
10 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
build-release:
|
|
name: Build ${{ matrix.target }}
|
|
runs-on: ${{ matrix.os }}
|
|
continue-on-error: ${{ matrix.experimental }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
experimental: false
|
|
- os: ubuntu-latest
|
|
target: aarch64-unknown-linux-gnu
|
|
experimental: true
|
|
- os: macos-latest
|
|
target: x86_64-apple-darwin
|
|
experimental: false
|
|
- os: macos-latest
|
|
target: aarch64-apple-darwin
|
|
experimental: false
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: _primitives/_rust
|
|
|
|
- name: Install aarch64 cross-linker (Linux only)
|
|
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y gcc-aarch64-linux-gnu
|
|
mkdir -p .cargo
|
|
printf '[target.aarch64-unknown-linux-gnu]\nlinker = "aarch64-linux-gnu-gcc"\n' \
|
|
> _primitives/_rust/.cargo/config.toml
|
|
|
|
- name: Build workspace (release)
|
|
working-directory: _primitives/_rust
|
|
run: cargo build --workspace --release --target ${{ matrix.target }}
|
|
|
|
- name: Package binaries
|
|
id: package
|
|
working-directory: _primitives/_rust/target/${{ matrix.target }}/release
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
# Collect every Cargo-built executable (Linux + macOS: no ext, mode +x).
|
|
# Portable across GNU + BSD find: iterate, test executability in shell.
|
|
BINS=()
|
|
for f in *; do
|
|
[ -f "$f" ] || continue
|
|
case "$f" in
|
|
*.d|*.rlib|*.rmeta|*.so|*.dylib|*.dSYM) continue ;;
|
|
esac
|
|
if [ -x "$f" ]; then
|
|
BINS+=("$f")
|
|
fi
|
|
done
|
|
if [ "${#BINS[@]}" -eq 0 ]; then
|
|
echo "::error::no release binaries produced for ${{ matrix.target }}"
|
|
exit 1
|
|
fi
|
|
echo "Binaries found: ${BINS[*]}"
|
|
ARCHIVE="keisei-${{ matrix.target }}.tar.gz"
|
|
tar czf "$GITHUB_WORKSPACE/$ARCHIVE" "${BINS[@]}"
|
|
cd "$GITHUB_WORKSPACE"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$ARCHIVE" > "$ARCHIVE.sha256"
|
|
else
|
|
shasum -a 256 "$ARCHIVE" > "$ARCHIVE.sha256"
|
|
fi
|
|
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Upload artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: binaries-${{ matrix.target }}
|
|
path: |
|
|
keisei-${{ matrix.target }}.tar.gz
|
|
keisei-${{ matrix.target }}.tar.gz.sha256
|
|
if-no-files-found: error
|
|
|
|
# v0.18 Phase 1 (exobrain): compile @keisei/mcp-server to a single static
|
|
# binary for 5 platforms via `bun build --compile`. Runs in parallel with
|
|
# build-release; the release job below `needs:` both. Linux arm64 is kept
|
|
# `continue-on-error` because the ubuntu arm runner pool is newer and
|
|
# occasionally flaky — a missing linux-arm64 asset must NOT block release.
|
|
build-mcp-binary:
|
|
name: Build mcp-server ${{ matrix.target.platform }}-${{ matrix.target.arch }}
|
|
runs-on: ${{ matrix.target.runner }}
|
|
continue-on-error: ${{ matrix.target.arch == 'arm64' && matrix.target.platform == 'linux' }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
target:
|
|
- { platform: linux, arch: x64, runner: ubuntu-latest, bun_target: bun-linux-x64, ext: '' }
|
|
- { platform: linux, arch: arm64, runner: ubuntu-24.04-arm, bun_target: bun-linux-arm64, ext: '' }
|
|
- { platform: darwin, arch: x64, runner: macos-13, bun_target: bun-darwin-x64, ext: '' }
|
|
- { platform: darwin, arch: arm64, runner: macos-latest, bun_target: bun-darwin-arm64, ext: '' }
|
|
- { platform: windows, arch: x64, runner: windows-latest, bun_target: bun-windows-x64, ext: '.exe' }
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: latest
|
|
|
|
- name: Install mcp-server deps
|
|
shell: bash
|
|
working-directory: _ts_packages/packages/mcp-server
|
|
run: bun install --frozen-lockfile || bun install
|
|
|
|
- name: Compile single-binary
|
|
shell: bash
|
|
env:
|
|
BIN_NAME: kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}${{ matrix.target.ext }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
bun build \
|
|
--compile \
|
|
--target=${{ matrix.target.bun_target }} \
|
|
_ts_packages/packages/mcp-server/src/index.ts \
|
|
--outfile "dist/${BIN_NAME}"
|
|
ls -la "dist/${BIN_NAME}"
|
|
|
|
- name: Compute sha256
|
|
shell: bash
|
|
env:
|
|
BIN_NAME: kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}${{ matrix.target.ext }}
|
|
run: |
|
|
set -euo pipefail
|
|
cd dist
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "${BIN_NAME}" > "${BIN_NAME}.sha256"
|
|
else
|
|
shasum -a 256 "${BIN_NAME}" > "${BIN_NAME}.sha256"
|
|
fi
|
|
cat "${BIN_NAME}.sha256"
|
|
|
|
- name: Upload artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}
|
|
path: |
|
|
dist/kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}${{ matrix.target.ext }}
|
|
dist/kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}${{ matrix.target.ext }}.sha256
|
|
if-no-files-found: error
|
|
|
|
release:
|
|
name: Publish GitHub Release
|
|
needs: [build-release, build-mcp-binary]
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: _primitives/_rust
|
|
|
|
- name: Build kei-changelog
|
|
working-directory: _primitives/_rust
|
|
run: cargo build --release -p kei-changelog
|
|
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
path: dist/
|
|
|
|
- name: Flatten artifacts
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p release-assets
|
|
# Rust tarballs + sha256 sums from build-release matrix.
|
|
# MCP-server bare binaries (+ .exe on windows) + sha256 sums from
|
|
# build-mcp-binary matrix. Bare binaries need a stable name to stay
|
|
# USB-drive-droppable, so no archive — we ship them raw alongside
|
|
# the tarballs.
|
|
find dist -type f \( \
|
|
-name '*.tar.gz' \
|
|
-o -name '*.sha256' \
|
|
-o -name 'kei-mcp-server-*' \
|
|
\) -exec mv {} release-assets/ \;
|
|
ls -la release-assets
|
|
|
|
- name: Generate release notes (kei-changelog)
|
|
id: notes
|
|
run: |
|
|
set -euo pipefail
|
|
TAG="${GITHUB_REF_NAME}"
|
|
PREV="$(git tag --sort=-creatordate | grep -v "^${TAG}$" | head -n1 || true)"
|
|
echo "Current tag: ${TAG}"
|
|
echo "Previous tag: ${PREV:-<none>}"
|
|
if [ -n "${PREV}" ]; then
|
|
NOTES="$(./_primitives/_rust/target/release/kei-changelog \
|
|
--from "${PREV}" --to "${TAG}" --version "${TAG}")"
|
|
else
|
|
NOTES="$(./_primitives/_rust/target/release/kei-changelog \
|
|
--to "${TAG}" --version "${TAG}")"
|
|
fi
|
|
if [ -z "${NOTES}" ]; then
|
|
NOTES="Release ${TAG}. No conventional-commit entries found in range."
|
|
fi
|
|
{
|
|
echo 'notes<<KEISEI_NOTES_EOF'
|
|
echo "${NOTES}"
|
|
echo 'KEISEI_NOTES_EOF'
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Publish GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
name: ${{ github.ref_name }}
|
|
tag_name: ${{ github.ref_name }}
|
|
body: ${{ steps.notes.outputs.notes }}
|
|
files: |
|
|
release-assets/*.tar.gz
|
|
release-assets/*.sha256
|
|
release-assets/kei-mcp-server-*
|
|
fail_on_unmatched_files: false
|
|
|
|
npm-publish:
|
|
name: Publish npm packages (optional)
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
# Graceful skip: if NPM_TOKEN secret is not configured, the first step
|
|
# reports "skipped" and exits 0 — Rust-binary release above still succeeds.
|
|
steps:
|
|
- name: Check NPM_TOKEN presence
|
|
id: have_token
|
|
env:
|
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
run: |
|
|
if [ -n "${NPM_TOKEN:-}" ]; then
|
|
echo "present=1" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "present=0" >> "$GITHUB_OUTPUT"
|
|
echo "::notice::NPM_TOKEN not set — skipping npm publish gracefully"
|
|
fi
|
|
|
|
- uses: actions/checkout@v4
|
|
if: steps.have_token.outputs.present == '1'
|
|
|
|
- uses: actions/setup-node@v4
|
|
if: steps.have_token.outputs.present == '1'
|
|
with:
|
|
node-version: '20'
|
|
registry-url: 'https://registry.npmjs.org'
|
|
|
|
- name: Install deps
|
|
if: steps.have_token.outputs.present == '1'
|
|
working-directory: _ts_packages
|
|
run: npm ci
|
|
|
|
- name: Build workspaces
|
|
if: steps.have_token.outputs.present == '1'
|
|
working-directory: _ts_packages
|
|
run: npm run build --workspaces --if-present
|
|
|
|
- name: Publish each package
|
|
if: steps.have_token.outputs.present == '1'
|
|
working-directory: _ts_packages
|
|
env:
|
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
for pkg in packages/*/; do
|
|
if [ -f "$pkg/package.json" ]; then
|
|
echo "::group::publish $pkg"
|
|
( cd "$pkg" && npm publish --access public ) \
|
|
|| echo "::warning::publish failed for $pkg (continuing)"
|
|
echo "::endgroup::"
|
|
fi
|
|
done
|