Skip to content

chore(release): v0.12.0 (#1327) #122

chore(release): v0.12.0 (#1327)

chore(release): v0.12.0 (#1327) #122

Workflow file for this run

name: Release
on:
push:
tags:
- "v*"
concurrency:
group: release
cancel-in-progress: false
env:
GH_REPO: AltimateAI/altimate-code
jobs:
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2
with:
bun-version: "1.3.14"
- name: Cache Bun dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-
- name: Configure git for tests
run: |
git config --global user.name "CI"
git config --global user.email "ci@test.local"
- name: Install dependencies
run: bun install
- name: Typecheck
run: bun turbo typecheck
- name: Run release-critical tests
run: bun test --timeout 30000 test/branding/ test/install/
working-directory: packages/opencode
build:
name: Build (${{ matrix.name }})
# Runs in PARALLEL with test — publish waits for both
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
# Each target builds ONE binary in parallel (~5 min each)
# Index matches allTargets array in build.ts. Targets without a
# @altimateai/altimate-core NAPI prebuild (linux-*-musl, win32-arm64)
# are intentionally excluded — see allTargets in build.ts.
- { index: 0, name: "linux-arm64" }
- { index: 1, name: "linux-x64" }
- { index: 2, name: "linux-x64-baseline" }
- { index: 3, name: "darwin-arm64" }
- { index: 4, name: "darwin-x64" }
- { index: 5, name: "darwin-x64-baseline" }
- { index: 6, name: "win32-x64" }
- { index: 7, name: "win32-x64-baseline" }
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2
with:
bun-version: "1.3.14"
- name: Cache Bun dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-
- name: Install dependencies
run: bun install
- name: Build ${{ matrix.name }}
run: bun run packages/opencode/script/build.ts --target-index=${{ matrix.index }}
env:
OPENCODE_VERSION: ${{ github.ref_name }}
# altimate_change — derive channel from the tag: a prerelease tag (e.g. v0.9.0-beta.1)
# publishes to the npm `beta` dist-tag so existing `latest` users are NOT auto-upgraded;
# a plain tag (v0.9.0) goes to `latest`. Prevents a beta tag from bricking the whole user base.
OPENCODE_CHANNEL: ${{ contains(github.ref_name, '-') && 'beta' || 'latest' }}
OPENCODE_RELEASE: "1"
# altimate_change — embed the operator-controlled Base endpoint without publishing it in source
ALTIMATE_BASE_GATEWAY_URL: ${{ vars.ALTIMATE_BASE_GATEWAY_URL }}
GH_REPO: ${{ env.GH_REPO }}
# altimate_change — MODELS_DEV_API_JSON is deliberately NOT set here.
# Pointing it at test/tool/fixtures/models-api.json (as ci.yml does, where
# a hermetic build is correct) made every shipped binary embed that fixture
# as its bundled models.dev catalog — newest entry 2026-03-30. Release
# builds fetch models.dev live, matching upstream's publish.yml. build.ts
# validates the payload and fails the build if it is unusable.
# Smoke-test: verify the compiled binary actually starts.
# Only possible for native linux-x64 builds on the ubuntu runner.
# This catches missing externals (e.g. @altimateai/altimate-core)
# that compile fine but crash at runtime.
- name: Smoke test binary
if: matrix.name == 'linux-x64'
id: smoke-test
run: |
# Resolve to an absolute path before we cd away from the workspace.
# Test `altimate-code` — the binary the platform package actually ships
# (the redundant `altimate` copy is dropped from the npm package after the
# release archive is built, to stay under npm's tarball size limit).
BINARY=$(find "$(pwd)/packages/opencode/dist" -name altimate-code -type f | head -1)
if [ -z "$BINARY" ]; then
echo "::error::No binary found in dist/"
exit 1
fi
chmod +x "$BINARY"
# altimate_change — share the resolved path with the cold-start step below so a dist-layout
# change can't make this step pass while the other fails on a stale hardcoded path.
echo "binary=$BINARY" >> "$GITHUB_OUTPUT"
# Run with NO pre-set NODE_PATH AND from a directory with no
# node_modules anywhere upward. Bun's compiled binary would
# otherwise walk the workspace tree for node_modules and resolve
# altimate-core that way — the test would pass for the wrong
# reason if the staged shim ever silently misses.
cd "${RUNNER_TEMP:-/tmp}"
env -u NODE_PATH "$BINARY" --version
echo "Smoke test passed: standalone binary starts hermetically"
# altimate_change start — --version and PURE-mode tests never exercise ordinary config installs.
- name: Cold-start config regression (compiled, non-PURE)
if: matrix.name == 'linux-x64'
working-directory: packages/opencode
env:
# Reuse the path the smoke test just resolved via `find` instead of a second hardcoded
# copy of it — a dist-layout change would otherwise break one of these two steps silently.
OPENCODE_TEST_CLI: ${{ steps.smoke-test.outputs.binary }}
run: bun test test/cli/serve/fresh-start.test.ts --timeout 90000
# altimate_change end
- name: Upload build artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dist-${{ matrix.name }}
path: packages/opencode/dist/
compression-level: 1
# ---------------------------------------------------------------------------
# Verdaccio sanity suite — tests the real `npm install -g` flow BEFORE
# publishing to npm. Catches broken symlinks, missing files, postinstall
# failures, and dependency resolution issues that smoke tests miss.
# ---------------------------------------------------------------------------
sanity-verdaccio:
name: Sanity (Verdaccio)
needs: build
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2
with:
bun-version: "1.3.14"
- name: Cache Bun dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-
- name: Install dependencies
run: bun install
- name: Download linux-x64 build artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist-linux-x64
path: packages/opencode/dist/
- name: Build dbt-tools
run: bun run build
working-directory: packages/dbt-tools
- name: Run Verdaccio sanity suite
run: |
docker compose -f test/sanity/docker-compose.verdaccio.yml up \
--build --abort-on-container-exit --exit-code-from sanity
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
publish-npm:
name: Publish to npm
# altimate_change — gate publishing on the test job. Previously publish-npm only
# needed [build, sanity-verdaccio], so npm could publish while typecheck/tests
# were red (found in the 2026-07-22 release retro).
needs: [test, build, sanity-verdaccio]
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
# altimate_change — validate the tag BEFORE any publish work. Tag-format
# validation previously lived in github-release, which runs AFTER publish-npm,
# so a malformed tag could publish to npm and only fail afterwards.
- name: Validate release tag
run: |
# Strict SemVer: no leading zeros in numeric identifiers, no empty
# prerelease identifiers (rejects v01.2.3, v1.2.3-01, v1.2.3-a..b).
SEMVER_ID='(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)'
if ! echo "$CURRENT_TAG" | grep -qE "^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-${SEMVER_ID}(\.${SEMVER_ID})*)?$"; then
echo "::error::Invalid tag format: $CURRENT_TAG — refusing to publish"
exit 1
fi
# Ask origin directly what the tag points to — the event payload and
# the checkout's local tag ref are both stale if the tag was force-moved
# between the push event and this job. An unforced `git fetch` would
# refuse to clobber the local tag, so ls-remote is the authority.
LS=$(git ls-remote origin "refs/tags/$CURRENT_TAG" "refs/tags/$CURRENT_TAG^{}") || {
echo "::error::Could not query origin for tag $CURRENT_TAG"
exit 1
}
# Annotated tags list a peeled ^{} line pointing at the commit; prefer it.
TAG_SHA=$(echo "$LS" | grep '\^{}' | cut -f1 | head -1)
[ -z "$TAG_SHA" ] && TAG_SHA=$(echo "$LS" | cut -f1 | head -1)
if [ -z "$TAG_SHA" ]; then
echo "::error::Tag $CURRENT_TAG no longer exists on origin — refusing to publish"
exit 1
fi
HEAD_SHA=$(git rev-parse HEAD)
if [ "$TAG_SHA" != "$HEAD_SHA" ]; then
echo "::error::Tag $CURRENT_TAG points at $TAG_SHA on origin but workflow checked out $HEAD_SHA (tag moved since the push event?)"
exit 1
fi
VERSION="${CURRENT_TAG#v}"
if ! grep -q "\[$VERSION\]" CHANGELOG.md && [ "${CURRENT_TAG#*-}" = "$CURRENT_TAG" ]; then
echo "::error::CHANGELOG.md has no entry for $VERSION — stable releases require a changelog entry"
exit 1
fi
echo "Tag validation passed: $CURRENT_TAG @ $TAG_SHA"
env:
CURRENT_TAG: ${{ github.ref_name }}
- uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2
with:
bun-version: "1.3.14"
- name: Cache Bun dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-
- name: Install dependencies
run: bun install
- name: Build dbt-tools (bundled with CLI)
run: bun run build
working-directory: packages/dbt-tools
- name: Smoke test dbt-tools bundle
run: |
# Verify node_python_bridge.py was copied into dist
if [ ! -f packages/dbt-tools/dist/node_python_bridge.py ]; then
echo "::error::node_python_bridge.py missing from dbt-tools dist"
exit 1
fi
# Verify no hardcoded absolute path in __dirname (catches any CI runner OS)
if grep -qE 'var __dirname\s*=\s*"(/|[A-Za-z]:\\)' packages/dbt-tools/dist/index.js; then
echo "::error::dbt-tools bundle contains hardcoded absolute path in __dirname"
exit 1
fi
# Verify __dirname was patched to runtime resolution
if ! grep -q 'import.meta.dirname' packages/dbt-tools/dist/index.js; then
echo "::error::dbt-tools bundle missing import.meta.dirname patch"
exit 1
fi
echo "dbt-tools smoke test passed"
- name: Free disk space for artifact download + npm publish
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /usr/local/share/boost
df -h /
- name: Download all build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: "dist-*"
path: packages/opencode/dist/
merge-multiple: true
- name: Configure npm auth
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "$RUNNER_TEMP/.npmrc"
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# TODO: Uncomment when AUR publishing is enabled (see publish.ts for setup steps)
# - name: Configure SSH for AUR
# if: ${{ !contains(github.ref_name, '-') }}
# run: |
# mkdir -p ~/.ssh
# echo "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
# chmod 600 ~/.ssh/aur
# ssh-keyscan -t ed25519 aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
# cat >> ~/.ssh/config << 'EOF'
# Host aur.archlinux.org
# IdentityFile ~/.ssh/aur
# User aur
# EOF
# env:
# AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
# Smoke-test a linux-x64 binary from the downloaded artifacts before publishing.
# This is the last gate before npm publish — catches runtime crashes that
# compile-time checks miss (e.g. missing NAPI externals like v0.5.10).
- name: Pre-publish smoke test
run: |
# Resolve to an absolute path before we cd away from the workspace.
BINARY=$(find "$(pwd)/packages/opencode/dist" -path '*altimate-code-linux-x64/bin/altimate-code' -type f | head -1)
if [ -z "$BINARY" ]; then
echo "::error::No linux-x64 binary found in artifacts — cannot verify release"
exit 1
else
chmod +x "$BINARY"
# No NODE_PATH AND hermetic cwd: see the build-time smoke test
# comment for why this matters. The binary must start without
# walking the workspace for node_modules.
cd "${RUNNER_TEMP:-/tmp}"
# altimate_change — assert the EXACT version, not just "it starts".
# The 2026-07-22 release retro found a smoke test once validated a
# stale binary (0.7.3) while releasing 0.9.2.
REPORTED=$(env -u NODE_PATH "$BINARY" --version)
EXPECTED="${CURRENT_TAG#v}"
if [ "$REPORTED" != "$EXPECTED" ]; then
echo "::error::Binary reports version '$REPORTED' but tag says '$EXPECTED'"
exit 1
fi
echo "Pre-publish smoke test passed ($REPORTED)"
fi
env:
CURRENT_TAG: ${{ github.ref_name }}
- name: Publish to npm
run: bun run packages/opencode/script/publish.ts
env:
OPENCODE_VERSION: ${{ github.ref_name }}
# altimate_change — derive channel from the tag: a prerelease tag (e.g. v0.9.0-beta.1)
# publishes to the npm `beta` dist-tag so existing `latest` users are NOT auto-upgraded;
# a plain tag (v0.9.0) goes to `latest`. Prevents a beta tag from bricking the whole user base.
OPENCODE_CHANNEL: ${{ contains(github.ref_name, '-') && 'beta' || 'latest' }}
OPENCODE_RELEASE: "1"
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/.npmrc
GH_REPO: ${{ env.GH_REPO }}
GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
# Python engine (publish-engine) job removed — engine eliminated.
# All methods now run natively in TypeScript.
github-release:
name: Create GitHub Release
needs: [build, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Generate release notes
id: notes
run: |
# Validate tag format to prevent injection
if ! echo "$CURRENT_TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?$'; then
echo "::error::Invalid tag format: $CURRENT_TAG"
exit 1
fi
# Get the previous tag.
# altimate_change — pick the newest STABLE tag that is an ANCESTOR of this
# commit AND strictly LOWER than the current tag. Excluding only equality is
# not enough: if upstream history is ever merged, fork-inherited tags (e.g.
# v1.18.3) would beat the real previous release for a v0.9.x target and the
# compare range would silently omit history.
PREV_TAG=""
for t in $(git tag --merged HEAD --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$'); do
if [ "$t" != "$CURRENT_TAG" ] && \
[ "$(printf '%s\n%s\n' "$t" "$CURRENT_TAG" | sort -V | head -1)" = "$t" ]; then
PREV_TAG="$t"
break
fi
done
# Generate changelog from commits between tags
echo "## What's Changed" > notes.md
echo "" >> notes.md
if [ -n "$PREV_TAG" ]; then
# Categorize commits
echo "### Features" >> notes.md
git log "${PREV_TAG}".."${CURRENT_TAG}" --pretty=format:"- %s (%h)" --grep="^feat" >> notes.md || true
echo "" >> notes.md
echo "" >> notes.md
echo "### Bug Fixes" >> notes.md
git log "${PREV_TAG}".."${CURRENT_TAG}" --pretty=format:"- %s (%h)" --grep="^fix" >> notes.md || true
echo "" >> notes.md
echo "" >> notes.md
echo "### Other Changes" >> notes.md
git log "${PREV_TAG}".."${CURRENT_TAG}" --pretty=format:"- %s (%h)" --invert-grep --grep="^feat" --grep="^fix" >> notes.md || true
echo "" >> notes.md
else
echo "Initial release" >> notes.md
fi
echo "" >> notes.md
echo "### Install" >> notes.md
echo '```bash' >> notes.md
echo "npm install -g @altimateai/altimate-code@${CURRENT_TAG#v}" >> notes.md
echo "# or" >> notes.md
echo "brew install AltimateAI/tap/altimate-code" >> notes.md
echo '```' >> notes.md
echo "" >> notes.md
echo "**Full Changelog**: https://github.com/${GH_REPO}/compare/${PREV_TAG}...${CURRENT_TAG}" >> notes.md
env:
GH_REPO: ${{ env.GH_REPO }}
CURRENT_TAG: ${{ github.ref_name }}
- name: Download all build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: "dist-*"
path: packages/opencode/dist/
merge-multiple: true
- name: Generate checksums
# Single checksums.txt (sha256sum format: "<hash> <bare-filename>") shipped
# as a release asset. The curl and PowerShell installers fetch it and verify
# the downloaded archive before extracting.
working-directory: packages/opencode/dist
run: sha256sum *.tar.gz *.zip > checksums.txt
- name: Create GitHub Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
body_path: notes.md
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
files: |
packages/opencode/dist/*.tar.gz
packages/opencode/dist/*.zip
packages/opencode/dist/checksums.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}