diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml new file mode 100644 index 00000000..4ca30ba1 --- /dev/null +++ b/.github/workflows/deploy-stage.yml @@ -0,0 +1,212 @@ +name: Stage Deployment + +on: + push: + branches: [main] + +jobs: + detect-changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + web: ${{ steps.filter.outputs.web }} + admin: ${{ steps.filter.outputs.admin }} + shared: ${{ steps.filter.outputs.shared }} + root: ${{ steps.filter.outputs.root }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check changed files + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + web: + - 'apps/web/**' + admin: + - 'apps/admin/**' + shared: + - 'packages/**' + root: + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - 'turbo.json' + - '.github/workflows/**' + + deploy-web-stage: + name: Deploy Web (Stage) + runs-on: ubuntu-latest + needs: detect-changes + if: needs.detect-changes.outputs.web == 'true' || needs.detect-changes.outputs.shared == 'true' || needs.detect-changes.outputs.root == 'true' + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_STAGE }} + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Vercel CLI + run: pnpm add --global vercel@latest + + - name: Turbo cached build (web) + run: pnpm turbo run build --filter=@solid-connect/web + + - name: Prepare Vercel project metadata (web) + run: | + rm -rf .vercel + mkdir -p .vercel-ci + working-directory: apps/web + + - name: Pull Vercel environment (web stage) + run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee .vercel-ci/pull.log + working-directory: apps/web + + - name: Build prebuilt artifacts (web stage) + run: vercel build --yes --target=preview --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee .vercel-ci/build.log + working-directory: apps/web + + - name: Verify prebuilt artifacts (web) + run: | + test -f .vercel/output/config.json + test -d .vercel/output/functions + working-directory: apps/web + + - name: Deploy prebuilt artifacts (web stage) + id: deploy_web + run: | + set -euo pipefail + vercel deploy --prebuilt --target=preview --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee .vercel-ci/deploy.log + URL=$(grep -Eo 'https://[^ ]+\.vercel\.app' .vercel-ci/deploy.log | head -n 1) + if [ -z "$URL" ]; then + echo "Failed to parse deployment URL from Vercel output" >&2 + exit 1 + fi + echo "url=$URL" >> "$GITHUB_OUTPUT" + { + echo "## Web Stage Deployment" + echo "- Status: success" + echo "- URL: $URL" + } >> "$GITHUB_STEP_SUMMARY" + working-directory: apps/web + + - name: Append failure summary (web stage) + if: failure() + run: | + { + echo "## Web Stage Deployment Failed" + echo "- Inspect artifact: web-stage-vercel-logs" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload Vercel logs (web stage) + if: always() + uses: actions/upload-artifact@v4 + with: + name: web-stage-vercel-logs + path: apps/web/.vercel-ci/*.log + if-no-files-found: ignore + + deploy-admin-stage: + name: Deploy Admin (Stage) + runs-on: ubuntu-latest + needs: detect-changes + if: needs.detect-changes.outputs.admin == 'true' || needs.detect-changes.outputs.shared == 'true' || needs.detect-changes.outputs.root == 'true' + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_ADMIN_STAGE }} + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Vercel CLI + run: pnpm add --global vercel@latest + + - name: Turbo cached build (admin) + run: pnpm turbo run build --filter=@solid-connect/admin + + - name: Prepare Vercel project metadata (admin) + run: | + rm -rf .vercel + mkdir -p .vercel-ci + working-directory: apps/admin + + - name: Pull Vercel environment (admin stage) + run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee .vercel-ci/pull.log + working-directory: apps/admin + + - name: Build prebuilt artifacts (admin stage) + run: vercel build --yes --target=preview --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee .vercel-ci/build.log + working-directory: apps/admin + + - name: Verify prebuilt artifacts (admin) + run: | + test -f .vercel/output/config.json + test -d .vercel/output/functions + working-directory: apps/admin + + - name: Deploy prebuilt artifacts (admin stage) + id: deploy_admin + run: | + set -euo pipefail + vercel deploy --prebuilt --target=preview --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee .vercel-ci/deploy.log + URL=$(grep -Eo 'https://[^ ]+\.vercel\.app' .vercel-ci/deploy.log | head -n 1) + if [ -z "$URL" ]; then + echo "Failed to parse deployment URL from Vercel output" >&2 + exit 1 + fi + echo "url=$URL" >> "$GITHUB_OUTPUT" + { + echo "## Admin Stage Deployment" + echo "- Status: success" + echo "- URL: $URL" + } >> "$GITHUB_STEP_SUMMARY" + working-directory: apps/admin + + - name: Append failure summary (admin stage) + if: failure() + run: | + { + echo "## Admin Stage Deployment Failed" + echo "- Inspect artifact: admin-stage-vercel-logs" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload Vercel logs (admin stage) + if: always() + uses: actions/upload-artifact@v4 + with: + name: admin-stage-vercel-logs + path: apps/admin/.vercel-ci/*.log + if-no-files-found: ignore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cdae4556..295a78b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,8 @@ -name: Build and Vercel Production Deployment +name: Promote Main to Release permissions: contents: write -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - VERCEL_ENV: production - on: workflow_dispatch: @@ -25,8 +20,8 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Create Release - id: create_release uses: ncipollo/release-action@v1 with: tag: "v${{ needs.generate_tag.outputs.version }}" @@ -34,40 +29,63 @@ jobs: body: "Automated release created for build v${{ needs.generate_tag.outputs.version }}" token: ${{ secrets.GITHUB_TOKEN }} - deploy_production: + promote_release_branch: + name: Promote main -> release runs-on: ubuntu-latest needs: create_release - env: - VERSION_TAG: ${{ needs.generate_tag.outputs.version }} steps: - - uses: actions/checkout@v3 - - - name: Install pnpm - uses: pnpm/action-setup@v3 + - name: Checkout repository + uses: actions/checkout@v4 with: - version: 9 + fetch-depth: 0 - - name: Install Vercel CLI - run: pnpm add --global vercel@latest + - name: Promote main branch to release branch + run: | + set -euo pipefail - - name: Remove existing .vercel directory - run: rm -rf .vercel + git fetch origin main + git fetch origin release || true - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=${{ env.VERCEL_ENV }} --token=${{ secrets.VERCEL_TOKEN }} + MAIN_SHA=$(git rev-parse origin/main) - - name: Build Project Artifacts - run: | - vercel build \ - --yes \ - --target=${{ env.VERCEL_ENV }} \ - --build-env NEXT_PUBLIC_WEB_URL=https://www.solid-connection.com \ - --build-env NEXT_PUBLIC_API_SERVER_URL=https://api.solid-connection.com \ - --build-env NEXT_PUBLIC_KAKAO_JS_KEY=b285223d3e57a6820552018b93805658 \ - --token=${{ secrets.VERCEL_TOKEN }} + if git show-ref --verify --quiet refs/remotes/origin/release; then + RELEASE_SHA=$(git rev-parse origin/release) + else + RELEASE_SHA="" + fi + + if [ -z "$RELEASE_SHA" ]; then + git push origin origin/main:refs/heads/release + { + echo "## Release Promotion" + echo "- Status: success (release branch created)" + echo "- Promoted main SHA: $MAIN_SHA" + echo "- Target branch: release" + echo "- Note: Vercel production deploy is triggered by release branch update" + } >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + + if [ "$MAIN_SHA" = "$RELEASE_SHA" ]; then + { + echo "## Release Promotion" + echo "- Status: skipped (release is already up to date)" + echo "- main: $MAIN_SHA" + } >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + + if ! git merge-base --is-ancestor origin/release origin/main; then + echo "release branch is not an ancestor of main. Resolve release history before promotion." >&2 + exit 1 + fi - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --prebuilt --target=${{ env.VERCEL_ENV }} --token=${{ secrets.VERCEL_TOKEN }} + git push origin origin/main:refs/heads/release - - name: Output Tag Version - run: echo "Deployment completed for version $VERSION_TAG" + { + echo "## Release Promotion" + echo "- Status: success" + echo "- Promoted main SHA: $MAIN_SHA" + echo "- Target branch: release" + echo "- Note: Vercel production deploy is triggered by release branch update" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/turbo.json b/turbo.json index f2f03b7e..49171f7d 100644 --- a/turbo.json +++ b/turbo.json @@ -4,7 +4,7 @@ "tasks": { "build": { "dependsOn": ["^build"], - "outputs": ["dist/**", ".output/**"], + "outputs": ["dist/**", ".output/**", ".vercel/output/**"], "env": ["NODE_ENV", "NEXT_PUBLIC_*"] }, "@solid-connect/web#build": {