fix(ci): automate version sync across all packages on release #691
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: ["**"] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| PNPM_VERSION: "10.6.1" | |
| NODE_VERSION: "22" | |
| PYTHON_VERSION: "3.12" | |
| jobs: | |
| # ── TypeScript ──────────────────────────────────────────────────────────── | |
| ts-typecheck: | |
| name: TS / typecheck | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - name: Build TS packages in dependency order | |
| run: | | |
| pnpm --filter @maschina/db build | |
| pnpm --filter @maschina/crypto build | |
| pnpm --filter @maschina/auth build | |
| pnpm --filter @maschina/cache build | |
| pnpm --filter @maschina/plans build | |
| pnpm --filter @maschina/events build | |
| pnpm --filter @maschina/nats build | |
| pnpm --filter @maschina/jobs build | |
| pnpm --filter @maschina/model build | |
| pnpm --filter @maschina/telemetry build | |
| pnpm --filter @maschina/usage build | |
| pnpm --filter @maschina/billing build | |
| pnpm --filter @maschina/push build | |
| pnpm --filter @maschina/notifications build | |
| pnpm --filter @maschina/validation build | |
| pnpm --filter @maschina/email build | |
| pnpm --filter @maschina/webhooks build | |
| pnpm --filter @maschina/search build | |
| pnpm --filter @maschina/compliance build | |
| pnpm --filter @maschina/storage build | |
| pnpm --filter @maschina/flags build | |
| pnpm --filter @maschina/analytics build | |
| pnpm --filter @maschina/chain build | |
| pnpm --filter @maschina/connectors build | |
| pnpm --filter @maschina/marketplace build | |
| - run: pnpm typecheck | |
| ts-lint: | |
| name: TS / lint (Biome) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm exec biome check packages/ services/ --no-errors-on-unmatched | |
| ts-test: | |
| name: TS / test (Vitest) | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_USER: maschina | |
| POSTGRES_PASSWORD: maschina | |
| POSTGRES_DB: maschina_test | |
| ports: ["5432:5432"] | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| redis: | |
| image: redis:7-alpine | |
| ports: ["6379:6379"] | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| env: | |
| DATABASE_URL: postgresql://maschina:maschina@localhost:5432/maschina_test | |
| REDIS_URL: redis://localhost:6379 | |
| NATS_URL: nats://localhost:4222 | |
| JWT_SECRET: ci-test-secret-minimum-32-chars-longvalue | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Service containers can't pass command args — start NATS manually with JetStream. | |
| - name: Start NATS with JetStream | |
| run: docker run -d --name nats -p 4222:4222 nats:2-alpine -js | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - name: Build TS packages in dependency order | |
| run: | | |
| pnpm --filter @maschina/db build | |
| pnpm --filter @maschina/crypto build | |
| pnpm --filter @maschina/auth build | |
| pnpm --filter @maschina/cache build | |
| pnpm --filter @maschina/plans build | |
| pnpm --filter @maschina/events build | |
| pnpm --filter @maschina/nats build | |
| pnpm --filter @maschina/jobs build | |
| pnpm --filter @maschina/model build | |
| pnpm --filter @maschina/telemetry build | |
| pnpm --filter @maschina/usage build | |
| pnpm --filter @maschina/billing build | |
| pnpm --filter @maschina/push build | |
| pnpm --filter @maschina/notifications build | |
| pnpm --filter @maschina/validation build | |
| pnpm --filter @maschina/email build | |
| pnpm --filter @maschina/webhooks build | |
| pnpm --filter @maschina/search build | |
| pnpm --filter @maschina/compliance build | |
| pnpm --filter @maschina/storage build | |
| pnpm --filter @maschina/flags build | |
| pnpm --filter @maschina/analytics build | |
| pnpm --filter @maschina/connectors build | |
| pnpm --filter @maschina/marketplace build | |
| - name: Run migrations | |
| run: pnpm db:migrate | |
| - run: pnpm exec vitest run --coverage | |
| # ── Rust ────────────────────────────────────────────────────────────────── | |
| # Scoped to server-side crates only. Tauri + CLI binaries are built and | |
| # cross-compiled in release.yml with their own platform runners. | |
| rust-fmt: | |
| name: Rust / fmt | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: "1.88" | |
| components: rustfmt | |
| - run: cargo fmt -p maschina-gateway -p maschina-daemon -p maschina-realtime -p maschina-node -- --check | |
| rust-clippy: | |
| name: Rust / clippy | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: "1.88" | |
| components: clippy | |
| - uses: Swatinem/rust-cache@v2 | |
| - run: sudo apt-get install -y pkg-config libssl-dev | |
| - run: > | |
| cargo clippy | |
| -p maschina-gateway | |
| -p maschina-daemon | |
| -p maschina-realtime | |
| -p maschina-node | |
| -- -D warnings | |
| rust-test: | |
| name: Rust / test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: "1.88" | |
| - uses: Swatinem/rust-cache@v2 | |
| - run: sudo apt-get install -y pkg-config libssl-dev | |
| - run: cargo test -p maschina-gateway -p maschina-daemon -p maschina-realtime -p maschina-node | |
| rust-build: | |
| name: Rust / build (release) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: "1.88" | |
| - uses: Swatinem/rust-cache@v2 | |
| - run: sudo apt-get install -y pkg-config libssl-dev | |
| - run: > | |
| cargo build --release | |
| -p maschina-gateway | |
| -p maschina-daemon | |
| -p maschina-realtime | |
| -p maschina-node | |
| # ── Python ──────────────────────────────────────────────────────────────── | |
| python-lint: | |
| name: Python / lint (Ruff) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - run: pip install ruff | |
| - run: ruff check packages/runtime packages/agents packages/risk packages/ml services/runtime services/worker | |
| - run: ruff format --check packages/runtime packages/agents packages/risk packages/ml services/runtime services/worker | |
| python-test: | |
| name: Python / test (Pytest) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install uv | |
| run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH | |
| - name: Install packages | |
| run: | | |
| uv pip install -e packages/runtime --system | |
| uv pip install -e packages/ml --no-deps --system | |
| uv pip install numpy scikit-learn scipy pandas tqdm --system | |
| uv pip install -e packages/agents --system | |
| uv pip install -e packages/risk --system | |
| uv pip install -e "packages/sdk/python[dev]" --system | |
| uv pip install -e "services/runtime[dev]" --system | |
| uv pip install -e services/worker --system | |
| uv pip install pytest pytest-asyncio pytest-mock numpy scikit-learn --system | |
| - run: pytest packages/runtime/tests packages/agents/tests packages/risk/tests packages/ml/tests packages/sdk/python/tests services/runtime/tests services/worker/tests -v | |
| # ── Docker ──────────────────────────────────────────────────────────────── | |
| docker-build: | |
| name: Docker / build (${{ matrix.service }}) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - service: api | |
| dockerfile: services/api/Dockerfile | |
| context: . | |
| - service: gateway | |
| dockerfile: services/gateway/Dockerfile | |
| context: services/gateway | |
| - service: daemon | |
| dockerfile: services/daemon/Dockerfile | |
| context: services/daemon | |
| - service: realtime | |
| dockerfile: services/realtime/Dockerfile | |
| context: services/realtime | |
| - service: runtime | |
| dockerfile: services/runtime/Dockerfile | |
| context: . | |
| - service: worker | |
| dockerfile: services/worker/Dockerfile | |
| context: . | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-buildx-action@v3 | |
| - name: Set lowercase repo owner | |
| run: echo "REPO_OWNER=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV | |
| - name: Log in to GHCR | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build ${{ matrix.service }} | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ${{ matrix.context }} | |
| file: ${{ matrix.dockerfile }} | |
| push: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} | |
| tags: | | |
| ghcr.io/${{ env.REPO_OWNER }}/maschina-${{ matrix.service }}:latest | |
| ghcr.io/${{ env.REPO_OWNER }}/maschina-${{ matrix.service }}:${{ github.sha }} | |
| cache-from: type=gha,scope=docker-${{ matrix.service }} | |
| cache-to: type=gha,mode=max,scope=docker-${{ matrix.service }} | |
| # ── Gate ────────────────────────────────────────────────────────────────── | |
| ci-pass: | |
| name: CI passed | |
| runs-on: ubuntu-latest | |
| needs: | |
| - ts-typecheck | |
| - ts-lint | |
| - ts-test | |
| - rust-fmt | |
| - rust-clippy | |
| - rust-test | |
| - rust-build | |
| - python-lint | |
| - python-test | |
| - docker-build | |
| if: always() | |
| steps: | |
| - name: Verify all required jobs passed | |
| id: gate | |
| run: | | |
| results='${{ toJSON(needs) }}' | |
| failed=$(echo "$results" | jq '[.[].result] | map(select(. == "failure" or . == "cancelled")) | length') | |
| if [ "$failed" -gt "0" ]; then | |
| echo "status=failed" >> $GITHUB_OUTPUT | |
| echo "One or more required CI jobs failed." | |
| exit 1 | |
| fi | |
| echo "status=passed" >> $GITHUB_OUTPUT | |
| echo "All CI checks passed." | |
| - name: Notify Discord | |
| if: always() && github.event_name == 'pull_request' | |
| env: | |
| DISCORD_WEBHOOK: ${{ secrets.DISCORD_CI_WEBHOOK }} | |
| STATUS: ${{ steps.gate.outputs.status }} | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| PR_URL: ${{ github.event.pull_request.html_url }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| ACTOR: ${{ github.actor }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| [ -z "$DISCORD_WEBHOOK" ] && exit 0 | |
| if [ "$STATUS" = "passed" ]; then | |
| COLOR=5763719 | |
| LABEL="passed" | |
| else | |
| COLOR=15548997 | |
| LABEL="failed" | |
| fi | |
| SHORT_SHA="${SHA:0:7}" | |
| curl -s -X POST "$DISCORD_WEBHOOK" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"embeds\": [{ | |
| \"title\": \"CI ${LABEL} — PR #${PR_NUMBER}\", | |
| \"url\": \"${PR_URL}\", | |
| \"description\": \"${PR_TITLE}\", | |
| \"color\": ${COLOR}, | |
| \"fields\": [ | |
| { \"name\": \"By\", \"value\": \"${ACTOR}\", \"inline\": true }, | |
| { \"name\": \"Commit\", \"value\": \"\`${SHORT_SHA}\`\", \"inline\": true } | |
| ] | |
| }] | |
| }" |