molecule-core is a public repo — GHA-hosted minutes are free. The self-hosted Mac mini was only in play to dodge GHA rate limits (memory feedback_selfhosted_runner), but for these specific workflows it came with real costs: - Docker-push workflows emulated linux/amd64 from arm64 via QEMU — every canvas + platform image build ran ~2-3x slower than native. - Six PRs worth of keychain-avoidance hacks in publish-* because `docker login` on macOS writes to osxkeychain unconditionally, and the Mac mini's launchd user-agent keychain is locked. - Homebrew pin-down environment variables (HOMEBREW_NO_*) sprinkled everywhere to work around the shared /opt/homebrew symlink mess on the runner. - Setup-python@v5 couldn't write to /Users/runner, so ci.yml python-lint resorted to a hand-rolled Homebrew python3.11 dance. - Single runner → fan-out contention; CodeQL's 45-min analysis fought the canvas publish for the one slot. Changes across the 7 workflows: - runs-on: [self-hosted, macos, arm64] → ubuntu-latest (every job) - publish-canvas-image + publish-workspace-server-image: drop the hand-rolled auths-map step + QEMU setup + buildx v4 → docker/login-action@v3 + setup-buildx@v3. Linux + amd64 target = native build. - canary-verify + promote-latest: replace `brew install crane` + HOMEBREW_NO_* incantations with imjasonh/setup-crane@v0.4. - codeql.yml: drop `brew install jq` — jq is preinstalled on ubuntu-latest. - ci.yml shellcheck: drop the self-hosted existence check — shellcheck is preinstalled via apt. - ci.yml python-lint: replace the Homebrew python3.11 path dance with actions/setup-python@v5 (which works fine on GHA-hosted), add requirements.txt caching while we're there. - Remove stale comments referencing "the self-hosted runner", "Mac mini", keychain, osxkeychain etc. The self-hosted Mac mini remains in service for private-repo workflows only. Memory feedback_selfhosted_runner updated to reflect the public-repo scope carve-out. Net -96 lines across the 7 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
199 lines
7.8 KiB
YAML
199 lines
7.8 KiB
YAML
name: CI
|
||
|
||
on:
|
||
push:
|
||
branches: [main, staging]
|
||
pull_request:
|
||
branches: [main, staging]
|
||
|
||
# Cancel in-progress CI runs when a new commit arrives on the same ref.
|
||
# This prevents stale runs from queuing behind each other.
|
||
concurrency:
|
||
group: ci-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
# Detect which paths changed so downstream jobs can skip when only
|
||
# docs/markdown files were modified.
|
||
changes:
|
||
name: Detect changes
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
platform: ${{ steps.check.outputs.platform }}
|
||
canvas: ${{ steps.check.outputs.canvas }}
|
||
python: ${{ steps.check.outputs.python }}
|
||
scripts: ${{ steps.check.outputs.scripts }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
- id: check
|
||
run: |
|
||
# For PR events: diff against the base branch (not HEAD~1 of the branch,
|
||
# which may be unrelated after force-pushes). When a push updates a PR,
|
||
# both pull_request and push events fire — prefer the PR base so that
|
||
# the diff is always computed against the actual merge base, not the
|
||
# previous SHA on the branch which may be on a different history line.
|
||
BASE="${GITHUB_BASE_REF:-${{ github.event.before }}}"
|
||
# GITHUB_BASE_REF is set by GitHub for PR events (the base branch name).
|
||
# For pull_request events we use the stored base.sha; for push events
|
||
# (or when base.sha is unavailable) fall back to github.event.before.
|
||
if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "${{ github.event.pull_request.base.sha }}" ]; then
|
||
BASE="${{ github.event.pull_request.base.sha }}"
|
||
fi
|
||
# Fallback: if BASE is empty or all zeros (new branch), run everything
|
||
if [ -z "$BASE" ] || echo "$BASE" | grep -qE '^0+$'; then
|
||
echo "platform=true" >> "$GITHUB_OUTPUT"
|
||
echo "canvas=true" >> "$GITHUB_OUTPUT"
|
||
echo "python=true" >> "$GITHUB_OUTPUT"
|
||
echo "scripts=true" >> "$GITHUB_OUTPUT"
|
||
exit 0
|
||
fi
|
||
DIFF=$(git diff --name-only "$BASE" HEAD 2>/dev/null || echo ".github/workflows/ci.yml")
|
||
echo "platform=$(echo "$DIFF" | grep -qE '^workspace-server/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||
echo "canvas=$(echo "$DIFF" | grep -qE '^canvas/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||
echo "python=$(echo "$DIFF" | grep -qE '^workspace/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||
echo "scripts=$(echo "$DIFF" | grep -qE '^tests/e2e/|^scripts/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||
|
||
platform-build:
|
||
name: Platform (Go)
|
||
needs: changes
|
||
if: needs.changes.outputs.platform == 'true'
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: workspace-server
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-go@v5
|
||
with:
|
||
go-version: 'stable'
|
||
- run: go mod download
|
||
- run: go build ./cmd/server
|
||
# CLI (molecli) moved to standalone repo: github.com/Molecule-AI/molecule-cli
|
||
- run: go vet ./...
|
||
- name: Run golangci-lint
|
||
uses: golangci/golangci-lint-action@v9
|
||
with:
|
||
version: latest
|
||
working-directory: workspace-server
|
||
args: --timeout 3m
|
||
continue-on-error: true # Warn but don't block until codebase is clean
|
||
- name: Run tests with race detection and coverage
|
||
run: go test -race -coverprofile=coverage.out ./...
|
||
- name: Check coverage baseline
|
||
run: |
|
||
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
|
||
echo "Total coverage: ${COVERAGE}%"
|
||
THRESHOLD=25
|
||
awk "BEGIN{if ($COVERAGE < $THRESHOLD) exit 1}" || {
|
||
echo "::error::Coverage ${COVERAGE}% is below the ${THRESHOLD}% threshold"
|
||
exit 1
|
||
}
|
||
|
||
canvas-build:
|
||
name: Canvas (Next.js)
|
||
needs: changes
|
||
if: needs.changes.outputs.canvas == 'true'
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: canvas
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '22'
|
||
- run: rm -f package-lock.json && npm install
|
||
- run: npm run build
|
||
- name: Run tests
|
||
run: npx vitest run
|
||
|
||
# MCP Server + SDK removed from CI — now in standalone repos:
|
||
# - github.com/Molecule-AI/molecule-mcp-server (npm CI)
|
||
# - github.com/Molecule-AI/molecule-sdk-python (PyPI CI)
|
||
|
||
# e2e-api job moved to .github/workflows/e2e-api.yml (issue #458).
|
||
# It now has workflow-level concurrency (cancel-in-progress: false) so
|
||
# new pushes queue the E2E run rather than cancelling it at the run level.
|
||
|
||
shellcheck:
|
||
name: Shellcheck (E2E scripts)
|
||
needs: changes
|
||
if: needs.changes.outputs.scripts == 'true'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- name: Run shellcheck on tests/e2e/*.sh
|
||
# shellcheck is pre-installed on ubuntu-latest runners (via apt).
|
||
run: |
|
||
find tests/e2e -type f -name '*.sh' -print0 \
|
||
| xargs -0 shellcheck --severity=warning
|
||
|
||
canvas-deploy-reminder:
|
||
name: Canvas Deploy Reminder
|
||
runs-on: ubuntu-latest
|
||
needs: [changes, canvas-build]
|
||
# Only fires on direct pushes to main (i.e. after staging→main promotion).
|
||
if: needs.changes.outputs.canvas == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||
permissions:
|
||
# Required to post commit comments via the GitHub API.
|
||
contents: write
|
||
steps:
|
||
- name: Post deploy reminder as commit comment
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
COMMIT_SHA: ${{ github.sha }}
|
||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
run: |
|
||
# Write body to a temp file — avoids backtick escaping in shell.
|
||
cat > /tmp/deploy-reminder.md << 'BODY'
|
||
## Canvas build passed ✅ — deploy required
|
||
|
||
The `publish-canvas-image` workflow is now building a fresh Docker image
|
||
(`ghcr.io/molecule-ai/canvas:latest`) in the background.
|
||
|
||
Once it completes (~3–5 min), apply on the host machine with:
|
||
```bash
|
||
cd <runner-workspace>
|
||
git pull origin main
|
||
docker compose pull canvas && docker compose up -d canvas
|
||
```
|
||
|
||
If you need to rebuild from local source instead (e.g. testing unreleased
|
||
changes or a new `NEXT_PUBLIC_*` URL), use:
|
||
```bash
|
||
docker compose build canvas && docker compose up -d canvas
|
||
```
|
||
BODY
|
||
printf '\n> Posted automatically by CI · commit `%s` · [build log](%s)\n' \
|
||
"$COMMIT_SHA" "$RUN_URL" >> /tmp/deploy-reminder.md
|
||
|
||
gh api \
|
||
--method POST \
|
||
"repos/${{ github.repository }}/commits/${{ github.sha }}/comments" \
|
||
--field "body=@/tmp/deploy-reminder.md"
|
||
|
||
python-lint:
|
||
name: Python Lint & Test
|
||
needs: changes
|
||
if: needs.changes.outputs.python == 'true'
|
||
runs-on: ubuntu-latest
|
||
env:
|
||
WORKSPACE_ID: test
|
||
defaults:
|
||
run:
|
||
working-directory: workspace
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-python@v5
|
||
with:
|
||
python-version: '3.11'
|
||
cache: pip
|
||
cache-dependency-path: workspace/requirements.txt
|
||
- run: pip install -r requirements.txt pytest pytest-asyncio pytest-cov
|
||
- run: python -m pytest --tb=short -q --cov=. --cov-report=term-missing
|
||
|
||
# SDK + plugin validation moved to standalone repo:
|
||
# github.com/Molecule-AI/molecule-sdk-python
|