mirror of
https://github.com/simstudioai/sim.git
synced 2026-08-30 17:05:18 +08:00
ca77908e20
* fix(ci): set up Node 22 for every job that runs bun install isolated-vm's install script now runs on install (#5935), and upstream only publishes prebuilds for Node 22 (ABI 127) and Node 24 (ABI 137). Jobs without an explicit setup-node inherit the runner default, Node 20, where prebuild-install finds nothing and falls back to node-gyp — which crashes on Node 20 with "webidl.util.markAsUncloneable is not a function", failing bun install --frozen-lockfile outright. This broke Create GitHub Release on main. - add setup-node 22 to ci.yml create-release and deploy-trigger-dev - add setup-node 22 to migrations.yml migrate - bump publish-cli.yml from the EOL Node 18 to 22, matching publish-ts-sdk.yml All eight jobs that run bun install now pin Node 22, matching the repo's declared engines.node >= 22.19.0. * fix(ci): skip lifecycle scripts on CI installs No CI job needs a compiled native module. isolated-vm landed in December 2025 and .npmrc blocked all lifecycle scripts until #5935, so CI ran green for ~7 months with it never built: next.config.ts and trigger.config.ts both external it, every test mocks it, and the only real require lives in isolated-vm-worker.cjs, which no CI job spawns. All three Dockerfiles already install with --ignore-scripts and rebuild it by hand. Building it in CI therefore buys nothing and couples every job to prebuild availability for the pinned Node. Upstream ships prebuilds for two ABIs only (Node 22/24), so the next setup-node bump would resurface the same opaque node-gyp failure that broke Create GitHub Release on main. - pass --ignore-scripts to all 8 CI bun install invocations - retarget the Setup Node comments at engines.node >= 22.19.0, which is the standalone reason for the pin now that scripts no longer run * fix(ci): drop redundant setup-node from bun-only jobs Once lifecycle scripts are skipped, nothing in create-release, migrate, or deploy-trigger-dev invokes node: they run bun run scripts/create-single-release.ts, bun run db:push plus bun run scripts/migrate.ts, and bunx trigger.dev deploy. All three were green without setup-node for months — the isolated-vm install script was the only thing that ever needed it, and --ignore-scripts covers that. setup-node stays where it is load-bearing: publish-cli and publish-ts-sdk need it for npm publish and its registry-url auth, and test-build/docs-embeddings already had it. publish-cli keeps the Node 18 -> 22 bump: that setup-node is required, and 18 has been EOL since April 2025, so it now matches publish-ts-sdk.
593 lines
25 KiB
YAML
593 lines
25 KiB
YAML
name: CI
|
||
|
||
# Runner provider toggle, read from the CI_PROVIDER repo variable:
|
||
#
|
||
# gh variable set CI_PROVIDER --body github # fall back to GitHub-hosted
|
||
# gh variable delete CI_PROVIDER # back to Blacksmith (default)
|
||
#
|
||
# A repo variable, not a committed value: during a Blacksmith outage there is no
|
||
# working CI to merge a switchover through. Only unset/'blacksmith' selects
|
||
# Blacksmith; anything unrecognized selects GitHub so a typo can't queue jobs
|
||
# against the provider you're escaping. Every runs-on and both composite actions
|
||
# share this predicate and must change together.
|
||
#
|
||
# GitHub mode is break-glass, not a peer — cold layers, slower runs. The app image
|
||
# is the one job on a paid larger runner: next build needs ~32 GB and OOM-kills
|
||
# (exit 137) on the free 16 GB runners at any heap ceiling.
|
||
|
||
on:
|
||
push:
|
||
branches: [main, staging, dev]
|
||
pull_request:
|
||
branches: [main, staging, dev]
|
||
# Docs content and markdown don't affect the app build or images; push
|
||
# runs stay unfiltered because they feed the deploy pipeline.
|
||
paths-ignore:
|
||
- 'apps/docs/content/**'
|
||
- '**/*.md'
|
||
|
||
concurrency:
|
||
group: ci-${{ github.ref }}
|
||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||
|
||
permissions:
|
||
contents: read
|
||
|
||
jobs:
|
||
test-build:
|
||
name: Test and Build
|
||
if: github.ref != 'refs/heads/dev' || github.event_name == 'pull_request'
|
||
uses: ./.github/workflows/test-build.yml
|
||
secrets: inherit
|
||
|
||
# Detect if this is a version release commit (e.g., "v0.5.24: ...")
|
||
# Smallest runner on purpose: a few seconds of pure shell over the commit
|
||
# message, no checkout and no install.
|
||
detect-version:
|
||
name: Detect Version
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
||
timeout-minutes: 5
|
||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/dev')
|
||
outputs:
|
||
version: ${{ steps.extract.outputs.version }}
|
||
is_release: ${{ steps.extract.outputs.is_release }}
|
||
steps:
|
||
- name: Extract version from commit message
|
||
id: extract
|
||
env:
|
||
COMMIT_MSG: ${{ github.event.head_commit.message }}
|
||
run: |
|
||
# Only tag versions on main branch
|
||
if [ "$GITHUB_REF" = "refs/heads/main" ] && [[ "$COMMIT_MSG" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+): ]]; then
|
||
VERSION="${BASH_REMATCH[1]}"
|
||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||
echo "is_release=true" >> $GITHUB_OUTPUT
|
||
echo "✅ Detected release commit: ${VERSION}"
|
||
else
|
||
echo "version=" >> $GITHUB_OUTPUT
|
||
echo "is_release=false" >> $GITHUB_OUTPUT
|
||
echo "ℹ️ Not a release commit"
|
||
fi
|
||
|
||
# Run database migrations before images are promoted: the ECR latest/staging
|
||
# tag push triggers CodePipeline, so migrating first guarantees the schema is
|
||
# in place before the new app version deploys (replaces the removed ECS
|
||
# migration sidecar)
|
||
migrate:
|
||
name: Migrate DB
|
||
needs: [test-build]
|
||
# Explicit need results instead of the implicit success(): a skipped job
|
||
# anywhere in the transitive needs chain silently fails implicit success()
|
||
# and cascade-skips the deploy chain (migrate -> promote-images ->
|
||
# CodeDeploy) — this bit us on 2026-07-23. State requirements explicitly.
|
||
if: >-
|
||
!cancelled() &&
|
||
needs.test-build.result == 'success' &&
|
||
github.event_name == 'push' &&
|
||
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
||
uses: ./.github/workflows/migrations.yml
|
||
with:
|
||
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
|
||
secrets: inherit
|
||
|
||
# Same ordering for dev (schema push before the dev image lands in ECR)
|
||
migrate-dev:
|
||
name: Migrate Dev DB
|
||
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
|
||
uses: ./.github/workflows/migrations.yml
|
||
with:
|
||
environment: dev
|
||
secrets: inherit
|
||
|
||
# Dev: build all 3 images for ECR only (no GHCR, no ARM64)
|
||
build-dev:
|
||
name: Build Dev ECR
|
||
needs: [detect-version, migrate-dev]
|
||
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.bs_runner || matrix.gh_runner }}
|
||
timeout-minutes: 30
|
||
permissions:
|
||
contents: read
|
||
id-token: write
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
# Only the app image needs the paid 8-core/32 GB runner: next build
|
||
# exhausts the free 16 GB one (exit 137). The others build in <5 min.
|
||
# bs_runner mirrors that per-image sizing on Blacksmith — a single
|
||
# pinned tier put every image on 8 vCPU, where the non-app builds idle
|
||
# at 12-15% CPU and under 10% memory.
|
||
- dockerfile: ./docker/app.Dockerfile
|
||
ecr_repo_secret: ECR_APP
|
||
gh_runner: linux-x64-8-core
|
||
bs_runner: blacksmith-8vcpu-ubuntu-2404
|
||
- dockerfile: ./docker/db.Dockerfile
|
||
ecr_repo_secret: ECR_MIGRATIONS
|
||
gh_runner: ubuntu-latest
|
||
bs_runner: blacksmith-2vcpu-ubuntu-2404
|
||
- dockerfile: ./docker/realtime.Dockerfile
|
||
ecr_repo_secret: ECR_REALTIME
|
||
gh_runner: ubuntu-latest
|
||
bs_runner: blacksmith-4vcpu-ubuntu-2404
|
||
- dockerfile: ./docker/pii.Dockerfile
|
||
ecr_repo_secret: ECR_PII
|
||
gh_runner: ubuntu-latest
|
||
bs_runner: blacksmith-4vcpu-ubuntu-2404
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||
|
||
- name: Configure AWS credentials
|
||
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6
|
||
with:
|
||
role-to-assume: ${{ secrets.DEV_AWS_ROLE_TO_ASSUME }}
|
||
aws-region: ${{ secrets.DEV_AWS_REGION }}
|
||
|
||
- name: Login to Amazon ECR
|
||
id: login-ecr
|
||
uses: aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||
with:
|
||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Resolve ECR repo name
|
||
id: ecr-repo
|
||
run: echo "name=$ECR_REPO" >> $GITHUB_OUTPUT
|
||
env:
|
||
ECR_REPO: ${{ matrix.ecr_repo_secret == 'ECR_APP' && secrets.ECR_APP || matrix.ecr_repo_secret == 'ECR_MIGRATIONS' && secrets.ECR_MIGRATIONS || matrix.ecr_repo_secret == 'ECR_REALTIME' && secrets.ECR_REALTIME || matrix.ecr_repo_secret == 'ECR_PII' && secrets.ECR_PII || '' }}
|
||
|
||
- name: Build and push
|
||
uses: ./.github/actions/docker-build
|
||
with:
|
||
provider: ${{ vars.CI_PROVIDER }}
|
||
file: ${{ matrix.dockerfile }}
|
||
platforms: linux/amd64
|
||
tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:dev
|
||
|
||
# Dev: deploy Trigger.dev background tasks to the preview "dev-sim" branch.
|
||
# Gated after migrate-dev for the same reason as build-dev — the new task
|
||
# code runs against the dev DB, so the schema must be pushed first.
|
||
deploy-trigger-dev:
|
||
name: Deploy Trigger.dev (Dev)
|
||
needs: [migrate-dev]
|
||
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
||
timeout-minutes: 15
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||
|
||
- name: Setup Bun
|
||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||
with:
|
||
bun-version: 1.3.13
|
||
|
||
- name: Cache Bun dependencies
|
||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||
with:
|
||
path: |
|
||
~/.bun/install/cache
|
||
node_modules
|
||
**/node_modules
|
||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-bun-
|
||
|
||
- name: Install dependencies
|
||
run: bun install --frozen-lockfile --ignore-scripts
|
||
|
||
- name: Deploy to Trigger.dev
|
||
working-directory: ./apps/sim
|
||
env:
|
||
TRIGGER_ACCESS_TOKEN: ${{ secrets.DEV_TRIGGER_ACCESS_TOKEN }}
|
||
TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }}
|
||
run: |
|
||
if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then
|
||
echo "ERROR: DEV_TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2
|
||
exit 1
|
||
fi
|
||
bunx trigger.dev@4.4.3 deploy --env preview --branch dev-sim
|
||
|
||
# Main/staging: build AMD64 images and push sha-tagged images to ECR + GHCR.
|
||
# Runs in parallel with tests — only immutable sha tags are pushed here, and
|
||
# the CodePipeline EventBridge triggers filter on exactly the
|
||
# latest/staging/dev ECR tags, so nothing deploys and no mutable tag moves
|
||
# until promote-images / create-ghcr-manifests retag after the gate.
|
||
build-amd64:
|
||
name: Build AMD64
|
||
if: >-
|
||
github.event_name == 'push' &&
|
||
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.bs_runner || matrix.gh_runner }}
|
||
timeout-minutes: 30
|
||
permissions:
|
||
contents: read
|
||
packages: write
|
||
id-token: write
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- dockerfile: ./docker/app.Dockerfile
|
||
ghcr_image: ghcr.io/simstudioai/simstudio
|
||
ecr_repo_secret: ECR_APP
|
||
gh_runner: linux-x64-8-core
|
||
bs_runner: blacksmith-8vcpu-ubuntu-2404
|
||
- dockerfile: ./docker/db.Dockerfile
|
||
ghcr_image: ghcr.io/simstudioai/migrations
|
||
ecr_repo_secret: ECR_MIGRATIONS
|
||
gh_runner: ubuntu-latest
|
||
bs_runner: blacksmith-2vcpu-ubuntu-2404
|
||
- dockerfile: ./docker/realtime.Dockerfile
|
||
ghcr_image: ghcr.io/simstudioai/realtime
|
||
ecr_repo_secret: ECR_REALTIME
|
||
gh_runner: ubuntu-latest
|
||
bs_runner: blacksmith-4vcpu-ubuntu-2404
|
||
- dockerfile: ./docker/pii.Dockerfile
|
||
ghcr_image: ghcr.io/simstudioai/pii
|
||
ecr_repo_secret: ECR_PII
|
||
gh_runner: ubuntu-latest
|
||
bs_runner: blacksmith-4vcpu-ubuntu-2404
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||
|
||
- name: Configure AWS credentials
|
||
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6
|
||
with:
|
||
role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }}
|
||
aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }}
|
||
|
||
- name: Login to Amazon ECR
|
||
id: login-ecr
|
||
uses: aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||
with:
|
||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Login to GHCR
|
||
if: github.ref == 'refs/heads/main'
|
||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||
with:
|
||
registry: ghcr.io
|
||
username: ${{ github.repository_owner }}
|
||
password: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Resolve ECR repo name
|
||
id: ecr-repo
|
||
run: echo "name=$ECR_REPO" >> $GITHUB_OUTPUT
|
||
env:
|
||
ECR_REPO: ${{ matrix.ecr_repo_secret == 'ECR_APP' && secrets.ECR_APP || matrix.ecr_repo_secret == 'ECR_MIGRATIONS' && secrets.ECR_MIGRATIONS || matrix.ecr_repo_secret == 'ECR_REALTIME' && secrets.ECR_REALTIME || matrix.ecr_repo_secret == 'ECR_PII' && secrets.ECR_PII || '' }}
|
||
|
||
# Only sha tags here — the ECR deploy tags (latest/staging) are applied
|
||
# by promote-images and the GHCR latest-amd64/version tags by
|
||
# create-ghcr-manifests, both after tests and migrations pass.
|
||
- name: Generate tags
|
||
id: meta
|
||
run: |
|
||
ECR_REGISTRY="${{ steps.login-ecr.outputs.registry }}"
|
||
ECR_REPO="${{ steps.ecr-repo.outputs.name }}"
|
||
GHCR_IMAGE="${{ matrix.ghcr_image }}"
|
||
|
||
TAGS="${ECR_REGISTRY}/${ECR_REPO}:${{ github.sha }}"
|
||
|
||
if [ "${{ github.ref }}" = "refs/heads/main" ] && [ -n "$GHCR_IMAGE" ]; then
|
||
TAGS="${TAGS},${GHCR_IMAGE}:${{ github.sha }}-amd64"
|
||
fi
|
||
|
||
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||
|
||
- name: Build and push images
|
||
uses: ./.github/actions/docker-build
|
||
with:
|
||
provider: ${{ vars.CI_PROVIDER }}
|
||
file: ${{ matrix.dockerfile }}
|
||
platforms: linux/amd64
|
||
tags: ${{ steps.meta.outputs.tags }}
|
||
|
||
# Promote the sha-tagged ECR images to the deploy tags once tests and
|
||
# migrations pass. Pushing the ECR latest/staging tag is what triggers
|
||
# CodePipeline, so this seconds-long manifest retag is the deploy gate —
|
||
# the image builds themselves run in parallel with the tests. A single job
|
||
# (not a matrix) so all four sha manifests are verified before any tag
|
||
# moves; a missing image can't produce a partial mixed-version deploy.
|
||
promote-images:
|
||
name: Promote Images
|
||
needs: [migrate, build-amd64]
|
||
# Explicit results: see migrate's comment.
|
||
if: >-
|
||
!cancelled() &&
|
||
needs.migrate.result == 'success' &&
|
||
needs.build-amd64.result == 'success' &&
|
||
github.event_name == 'push' &&
|
||
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
||
timeout-minutes: 10
|
||
permissions:
|
||
contents: read
|
||
id-token: write
|
||
steps:
|
||
- name: Configure AWS credentials
|
||
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6
|
||
with:
|
||
role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }}
|
||
aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }}
|
||
|
||
- name: Login to Amazon ECR
|
||
id: login-ecr
|
||
uses: aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2
|
||
|
||
# Deploy-tag moves must be monotonic: a re-run of an old run must never
|
||
# retag latest/staging back to stale code. A superseded first-attempt
|
||
# run still promotes — the ci-<ref> concurrency group executes runs
|
||
# serially in commit order, so an ancestor of head is a forward deploy.
|
||
- name: Guard against stale promotion
|
||
id: guard
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
run: |
|
||
STATUS="$(gh api "repos/${{ github.repository }}/compare/${{ github.sha }}...${GITHUB_REF_NAME}" --jq '.status' || echo "unknown")"
|
||
if [ "$STATUS" = "identical" ] || { [ "$STATUS" = "ahead" ] && [ "${{ github.run_attempt }}" = "1" ]; }; then
|
||
echo "fresh=true" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "::warning::Skipping promotion of ${{ github.sha }} (branch compare: ${STATUS}, attempt ${{ github.run_attempt }}). Moving the deploy tags here could deploy stale code; push a revert commit to roll back instead."
|
||
echo "fresh=false" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
- name: Promote images to deploy tags
|
||
if: steps.guard.outputs.fresh == 'true'
|
||
env:
|
||
ECR_REPOS: >-
|
||
${{ secrets.ECR_APP }}
|
||
${{ secrets.ECR_MIGRATIONS }}
|
||
${{ secrets.ECR_REALTIME }}
|
||
${{ secrets.ECR_PII }}
|
||
run: |
|
||
REGISTRY="${{ steps.login-ecr.outputs.registry }}"
|
||
|
||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||
ECR_TAG="latest"
|
||
else
|
||
ECR_TAG="staging"
|
||
fi
|
||
|
||
# Verify every sha image exists before moving any deploy tag, so a
|
||
# missing/expired image aborts the whole promotion up front.
|
||
for repo in $ECR_REPOS; do
|
||
echo "🔍 Verifying ${repo}:${{ github.sha }}"
|
||
docker buildx imagetools inspect "${REGISTRY}/${repo}:${{ github.sha }}" > /dev/null
|
||
done
|
||
|
||
for repo in $ECR_REPOS; do
|
||
echo "🚀 Promoting ${repo}:${{ github.sha }} to ${ECR_TAG}"
|
||
docker buildx imagetools create \
|
||
-t "${REGISTRY}/${repo}:${ECR_TAG}" \
|
||
"${REGISTRY}/${repo}:${{ github.sha }}"
|
||
done
|
||
|
||
# Build ARM64 images for GHCR (main branch only, runs in parallel with
|
||
# tests). Pushes only the immutable sha tag — latest-arm64/version-arm64
|
||
# are applied by create-ghcr-manifests after the gate, so a failing run
|
||
# never moves a documented tag.
|
||
build-ghcr-arm64:
|
||
name: Build ARM64 (GHCR Only)
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.bs_runner || matrix.gh_runner }}
|
||
timeout-minutes: 30
|
||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||
permissions:
|
||
contents: read
|
||
packages: write
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
# Non-app images sit at 4 vCPU rather than the finer x64 split: the ARM
|
||
# sizing data is job-level (8 -> 4 for the whole matrix), not per-image,
|
||
# and this job only runs on push to main — an unprovisioned label would
|
||
# hang a release in `queued` rather than fail a PR.
|
||
include:
|
||
- dockerfile: ./docker/app.Dockerfile
|
||
image: ghcr.io/simstudioai/simstudio
|
||
gh_runner: linux-arm64-8-core
|
||
bs_runner: blacksmith-8vcpu-ubuntu-2404-arm
|
||
- dockerfile: ./docker/db.Dockerfile
|
||
image: ghcr.io/simstudioai/migrations
|
||
gh_runner: ubuntu-24.04-arm
|
||
bs_runner: blacksmith-4vcpu-ubuntu-2404-arm
|
||
- dockerfile: ./docker/realtime.Dockerfile
|
||
image: ghcr.io/simstudioai/realtime
|
||
gh_runner: ubuntu-24.04-arm
|
||
bs_runner: blacksmith-4vcpu-ubuntu-2404-arm
|
||
- dockerfile: ./docker/pii.Dockerfile
|
||
image: ghcr.io/simstudioai/pii
|
||
gh_runner: ubuntu-24.04-arm
|
||
bs_runner: blacksmith-4vcpu-ubuntu-2404-arm
|
||
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||
|
||
- name: Login to GHCR
|
||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||
with:
|
||
registry: ghcr.io
|
||
username: ${{ github.repository_owner }}
|
||
password: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Build and push ARM64 to GHCR
|
||
uses: ./.github/actions/docker-build
|
||
with:
|
||
provider: ${{ vars.CI_PROVIDER }}
|
||
file: ${{ matrix.dockerfile }}
|
||
platforms: linux/arm64
|
||
tags: ${{ matrix.image }}:${{ github.sha }}-arm64
|
||
|
||
# Publish all mutable GHCR tags (latest, latest-amd64/arm64, version tags)
|
||
# and the multi-arch manifests from the immutable sha tags — only on main,
|
||
# after the deploy gate (promote-images) and the ARM64 build both pass.
|
||
create-ghcr-manifests:
|
||
name: Create GHCR Manifests
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
||
timeout-minutes: 10
|
||
needs: [promote-images, build-ghcr-arm64, detect-version]
|
||
# Explicit results: see migrate's comment.
|
||
if: >-
|
||
!cancelled() &&
|
||
needs.promote-images.result == 'success' &&
|
||
needs.build-ghcr-arm64.result == 'success' &&
|
||
needs.detect-version.result == 'success' &&
|
||
github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||
permissions:
|
||
contents: read
|
||
packages: write
|
||
strategy:
|
||
matrix:
|
||
include:
|
||
- image: ghcr.io/simstudioai/simstudio
|
||
- image: ghcr.io/simstudioai/migrations
|
||
- image: ghcr.io/simstudioai/realtime
|
||
- image: ghcr.io/simstudioai/pii
|
||
|
||
steps:
|
||
- name: Login to GHCR
|
||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||
with:
|
||
registry: ghcr.io
|
||
username: ${{ github.repository_owner }}
|
||
password: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
# Same monotonic guard as promote-images, applied to the public latest
|
||
# tags only — immutable sha and version tags are always published.
|
||
- name: Guard against stale latest tags
|
||
id: guard
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
run: |
|
||
STATUS="$(gh api "repos/${{ github.repository }}/compare/${{ github.sha }}...${GITHUB_REF_NAME}" --jq '.status' || echo "unknown")"
|
||
if [ "$STATUS" = "identical" ] || { [ "$STATUS" = "ahead" ] && [ "${{ github.run_attempt }}" = "1" ]; }; then
|
||
echo "fresh=true" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "::warning::Publishing immutable tags for ${{ github.sha }} but skipping the latest tags (branch compare: ${STATUS}, attempt ${{ github.run_attempt }})."
|
||
echo "fresh=false" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
- name: Publish tags and manifests
|
||
run: |
|
||
IMAGE="${{ matrix.image }}"
|
||
SHA="${{ github.sha }}"
|
||
|
||
# Multi-arch manifest from the immutable per-arch sha tags
|
||
docker buildx imagetools create -t "${IMAGE}:${SHA}" \
|
||
"${IMAGE}:${SHA}-amd64" "${IMAGE}:${SHA}-arm64"
|
||
|
||
if [ "${{ needs.detect-version.outputs.is_release }}" = "true" ]; then
|
||
VERSION="${{ needs.detect-version.outputs.version }}"
|
||
echo "📦 Publishing version tags: ${VERSION}"
|
||
docker buildx imagetools create -t "${IMAGE}:${VERSION}-amd64" "${IMAGE}:${SHA}-amd64"
|
||
docker buildx imagetools create -t "${IMAGE}:${VERSION}-arm64" "${IMAGE}:${SHA}-arm64"
|
||
docker buildx imagetools create -t "${IMAGE}:${VERSION}" \
|
||
"${IMAGE}:${SHA}-amd64" "${IMAGE}:${SHA}-arm64"
|
||
fi
|
||
|
||
if [ "${{ steps.guard.outputs.fresh }}" = "true" ]; then
|
||
docker buildx imagetools create -t "${IMAGE}:latest-amd64" "${IMAGE}:${SHA}-amd64"
|
||
docker buildx imagetools create -t "${IMAGE}:latest-arm64" "${IMAGE}:${SHA}-arm64"
|
||
docker buildx imagetools create -t "${IMAGE}:latest" \
|
||
"${IMAGE}:${SHA}-amd64" "${IMAGE}:${SHA}-arm64"
|
||
fi
|
||
|
||
# Check if docs changed
|
||
# Smallest runner on purpose: a depth-2 checkout plus a path filter, no
|
||
# install and no build.
|
||
check-docs-changes:
|
||
name: Check Docs Changes
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
||
timeout-minutes: 5
|
||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||
outputs:
|
||
docs_changed: ${{ steps.filter.outputs.docs }}
|
||
steps:
|
||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||
with:
|
||
fetch-depth: 2 # Need at least 2 commits to detect changes
|
||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4
|
||
id: filter
|
||
with:
|
||
filters: |
|
||
docs:
|
||
- 'apps/docs/content/docs/en/**'
|
||
- 'apps/sim/scripts/process-docs.ts'
|
||
- 'apps/sim/lib/chunkers/**'
|
||
|
||
# Process docs embeddings (only when docs change, after images are promoted)
|
||
process-docs:
|
||
name: Process Docs
|
||
needs: [promote-images, check-docs-changes]
|
||
# Explicit results: see migrate's comment.
|
||
if: >-
|
||
!cancelled() &&
|
||
needs.promote-images.result == 'success' &&
|
||
needs.check-docs-changes.result == 'success' &&
|
||
needs.check-docs-changes.outputs.docs_changed == 'true'
|
||
uses: ./.github/workflows/docs-embeddings.yml
|
||
secrets: inherit
|
||
|
||
# Create GitHub Release (only for version commits on main, after all builds complete)
|
||
create-release:
|
||
name: Create GitHub Release
|
||
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
||
timeout-minutes: 10
|
||
needs: [create-ghcr-manifests, detect-version]
|
||
# Explicit results: see migrate's comment.
|
||
if: >-
|
||
!cancelled() &&
|
||
needs.create-ghcr-manifests.result == 'success' &&
|
||
needs.detect-version.result == 'success' &&
|
||
needs.detect-version.outputs.is_release == 'true'
|
||
permissions:
|
||
contents: write
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Setup Bun
|
||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||
with:
|
||
bun-version: 1.3.13
|
||
|
||
- name: Install dependencies
|
||
run: bun install --frozen-lockfile --ignore-scripts
|
||
|
||
- name: Create release
|
||
env:
|
||
GH_PAT: ${{ secrets.GITHUB_TOKEN }}
|
||
run: bun run scripts/create-single-release.ts ${{ needs.detect-version.outputs.version }}
|