# yaml-language-server: $schema=https://json-schema.org/draft-07/schema# name: _build concurrency: group: build cancel-in-progress: false on: workflow_call: jobs: check: runs-on: ubuntu-latest permissions: actions: read contents: read outputs: should-build: ${{ steps.check.outputs.should-build }} artifact-run-id: ${{ steps.check.outputs.artifact-run-id }} artifact-id: ${{ steps.check.outputs.artifact-id }} build-sha: ${{ steps.check.outputs.build-sha }} repo-name: ${{ steps.meta.outputs.repo-name }} repo-path: ${{ steps.meta.outputs.repo-path }} image-tar: ${{ steps.check.outputs.image-tar }} prod: ${{ steps.meta.outputs.prod }} tag: ${{ steps.check.outputs.tag }} should-deploy: ${{ steps.check.outputs.should-deploy }} steps: - name: Checkout the repo uses: actions/checkout@v4 with: fetch-depth: 0 - name: Determine build metadata id: meta uses: ./.github/actions/determine-build-metadata with: repository: ${{ github.repository }} github-ref: ${{ github.ref }} - name: Check if build/deploy needed id: check env: GH_TOKEN: ${{ github.token }} run: | set -eu -o pipefail SHOULD_BUILD=false SHOULD_DEPLOY=false SHORT_SHA="${{ github.sha }}" SHORT_SHA="${SHORT_SHA:0:7}" IMAGE_TAR="${{ steps.meta.outputs.repo-name }}-${{github.sha}}.tar" VERSION="$(git tag --points-at ${{ github.sha }})" IS_VERSION_TAGGED="$([[ "$VERSION" =~ ^v ]] && echo "true" || echo "false")" BRANCH_NAME="$([[ "${{ github.ref }}" =~ ^refs/tags/v ]] && echo "main" || echo "${{ github.ref }}" | sed 's/refs\/heads\///')" COMMIT_MESSAGE="$(git log --format=%s -n 1 ${{ github.sha }})" if [[ "${{ steps.meta.outputs.prod }}" == "true" ]]; then TAG="${{ steps.meta.outputs.repo-name }}:sha-${SHORT_SHA},${{ steps.meta.outputs.repo-name }}:latest" else TAG="${{ steps.meta.outputs.repo-name }}-dev:sha-${SHORT_SHA}" fi if [[ "$BRANCH_NAME" == "staging" ]]; then echo "🔨 Build is on staging - will also deploy to staging" SHOULD_DEPLOY="true" elif [[ "$BRANCH_NAME" == "main" ]]; then if [[ ! "$COMMIT_MESSAGE" =~ ^chore:\ Release ]] || [[ "$IS_VERSION_TAGGED" == "false" ]]; then echo "❌ Skipping build for main: $COMMIT_MESSAGE. Not a version release. Missing tag or proper commit message for release." exit 1 fi echo "🏷️ Version tagged build on main: $VERSION - will also deploy to production" SHOULD_DEPLOY="true" fi echo "🔍 Checking for in progress builds with git SHA ${{github.sha}}" IN_PROGRESS_BUILDS="" if ! IN_PROGRESS_BUILDS=$(gh api repos/${{github.repository}}/actions/runs --jq '.workflow_runs[] | select(.head_sha == "${{github.sha}}" and .status == "in_progress" and .name == "Build/Deploy" and .id != ${{ github.run_id }}) | .id' 2>/dev/null); then echo "❌ Failed to check for in-progress builds via GitHub API" echo "::error::GitHub API call failed while checking for in-progress builds" exit 1 fi IN_PROGRESS_BUILDS=$(echo "$IN_PROGRESS_BUILDS" | head -1) if [[ -n "$IN_PROGRESS_BUILDS" ]]; then echo "❌ Found in-progress build for SHA ${{github.sha}}: $IN_PROGRESS_BUILDS - wait for existing build to complete before triggering a new one or rerunning this workflow" exit 1 fi echo "🔍 Checking for existing build artifacts with git SHA ${{github.sha}}" # Check for existing artifacts with this SHA across all workflow runs ARTIFACT_INFO="" if ! ARTIFACT_INFO=$(gh api repos/${{github.repository}}/actions/artifacts --jq '.artifacts[] | select(.name | endswith("-${{github.sha}}.tar")) | {id, name, workflow_run} | @json' 2>/dev/null); then echo "❌ Failed to check for existing artifacts via GitHub API" echo "::error::GitHub API call failed while checking for existing artifacts" exit 1 fi ARTIFACT_INFO=$(echo "$ARTIFACT_INFO" | head -1) if [[ -n "$ARTIFACT_INFO" ]]; then ARTIFACT_ID=$(echo "$ARTIFACT_INFO" | jq -r '.id') ARTIFACT_NAME=$(echo "$ARTIFACT_INFO" | jq -r '.name') RUN_ID=$(echo "$ARTIFACT_INFO" | jq -r '.workflow_run.id') echo "✅ Found existing artifact $ARTIFACT_NAME from run: $RUN_ID" echo "artifact-id=$ARTIFACT_ID" >> $GITHUB_OUTPUT echo "artifact-id -> $ARTIFACT_ID" else RUN_ID="${{ github.run_id }}" SHOULD_BUILD="true" echo "🔨 No existing build artifact found; will build..." fi echo "should-build=$SHOULD_BUILD" >> $GITHUB_OUTPUT echo "should-build -> $SHOULD_BUILD" echo "should-deploy=$SHOULD_DEPLOY" >> $GITHUB_OUTPUT echo "should-deploy -> $SHOULD_DEPLOY" echo "artifact-run-id=$RUN_ID" >> $GITHUB_OUTPUT echo "artifact-run-id -> $RUN_ID" echo "build-sha=${{github.sha}}" >> $GITHUB_OUTPUT echo "build-sha -> ${{github.sha}}" echo "image-tar=$IMAGE_TAR" >> $GITHUB_OUTPUT echo "image-tar -> $IMAGE_TAR" echo "tag=$TAG" >> $GITHUB_OUTPUT echo "tag -> $TAG" build: needs: check if: ${{ needs.check.outputs.should-build == 'true' }} runs-on: ubuntu-latest outputs: artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }} steps: - name: Checkout the repo uses: actions/checkout@v4 with: fetch-depth: 0 - name: Ensure scripts are executable run: chmod +x deploy/scripts/*.sh - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate cache keys id: cache-keys run: | # Generate cache key based on package-lock.json for dependencies DEPS_HASH=$(sha256sum package-lock.json | cut -d' ' -f1 | cut -c1-8) echo "deps-hash=${DEPS_HASH}" >> $GITHUB_OUTPUT echo "deps-hash -> $DEPS_HASH" # Generate cache key based on source files and paths SOURCE_PATHS="app/ server/ public/ nuxt.config.ts tsconfig.json tailwind.config.js package.json" SRC_HASH=$(find $SOURCE_PATHS -type f 2>/dev/null -exec sha256sum {} + | sha256sum | cut -d' ' -f1 | cut -c1-8 || echo "fallback-$(date +%s)") echo "src-hash=${SRC_HASH}" >> $GITHUB_OUTPUT echo "src-hash -> $SRC_HASH" - name: Cache Docker layers uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-deps-${{ steps.cache-keys.outputs.deps-hash }}-src-${{ steps.cache-keys.outputs.src-hash }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx-deps-${{ steps.cache-keys.outputs.deps-hash }}-src-${{ steps.cache-keys.outputs.src-hash }}- ${{ runner.os }}-buildx-deps-${{ steps.cache-keys.outputs.deps-hash }}- ${{ runner.os }}-buildx- - name: Extract version for Docker build id: extract_version run: | if [[ "${{ github.ref }}" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)(-.*)?$ ]]; then VERSION="${BASH_REMATCH[1]}" if [[ -n "${BASH_REMATCH[2]}" ]]; then VERSION="${VERSION}${BASH_REMATCH[2]}" fi echo "🏷️ Using git tag version: ${VERSION}" else VERSION=$(node -p "require('./package.json').version || '0.0.0'") SHA="${{ github.sha }}" GIT_SHA_SHORT="${SHA:0:7}" VERSION="${VERSION}+${GIT_SHA_SHORT}" echo "📦 Using package.json + SHA version: ${VERSION}" fi echo "VERSION=${VERSION}" >> $GITHUB_ENV - name: Build container uses: docker/build-push-action@v6 with: outputs: type=docker,dest=${{ runner.temp }}/${{ needs.check.outputs.image-tar }} tags: ${{ needs.check.outputs.tag }} build-args: | VERSION=${{ env.VERSION }} context: . target: runtime cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max platforms: linux/amd64 - name: Rotate cache run: | rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache - name: Upload container as artifact id: upload-artifact uses: actions/upload-artifact@v4 with: name: ${{ needs.check.outputs.image-tar }} path: ${{ runner.temp }}/${{ needs.check.outputs.image-tar }} if-no-files-found: error retention-days: 30 compression-level: 0 - name: Notify successful build run: | echo "🎉 Build completed successfully!" echo "📋 Summary:" echo " - Source: ${{ github.ref }}" echo " - Status: ✅ Build Successful" echo " - Next: Stage this build manually or wait for the next scheduled staging." deploy: needs: [check, build] if: ${{ always() && needs.check.outputs.should-deploy == 'true' }} permissions: actions: read contents: read uses: ./.github/workflows/deploy.yml with: artifact-run-id: ${{ needs.check.outputs.artifact-run-id }} artifact-id: ${{ needs.check.outputs.should-build == 'true' && needs.build.outputs.artifact-id || needs.check.outputs.artifact-id }} build-sha: ${{ needs.check.outputs.build-sha }} repo-name: ${{ needs.check.outputs.repo-name }} repo-path: ${{ needs.check.outputs.repo-path }} image-tar: ${{ needs.check.outputs.image-tar }} prod: ${{ needs.check.outputs.prod == 'true' }} tag: ${{ needs.check.outputs.tag }} secrets: inherit