Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ce42058
feat(ci): add GitHub Actions workflow for deploying CMS and Web appli…
jhb-dev Dec 22, 2025
0d006a7
fix(ci): use Vercel remote build instead of local build
jhb-dev Dec 22, 2025
bf43b04
fix(ci): set VERCEL_PROJECT_ID as env variable
jhb-dev Dec 22, 2025
0675d24
fix(ci): remove working-directory, use Vercel project root settings
jhb-dev Dec 22, 2025
6b09e43
refactor(ci): remove unnecessary ready check step
jhb-dev Dec 22, 2025
4528bf4
docs(ci): add description to deploy workflow
jhb-dev Dec 22, 2025
1fd6122
feat(ci): add preview deployments and concurrency control
jhb-dev Dec 22, 2025
ba84ea4
test(web): add preview deployment banner and console logs
jhb-dev Dec 22, 2025
a570d60
docs(ci): add prerequisites and TODO for bypass secret migration
jhb-dev Dec 22, 2025
d98901d
feat(ci): add Vercel inspect links to PR comment
jhb-dev Dec 22, 2025
00e243f
refactor(ci): simplify and speed up deployment workflow
jhb-dev Dec 22, 2025
a6e1930
fix(ci): improve URL parsing from Vercel output
jhb-dev Dec 22, 2025
67397ff
refactor(ci): simplify by using vercel stdout directly, remove inspec…
jhb-dev Dec 22, 2025
6e25acc
feat(ci): add change detection and skip tags support
jhb-dev Dec 22, 2025
7cb676b
fix(ci): handle empty CMS_URL and fix PROD_FLAG expression
jhb-dev Dec 22, 2025
844beef
test(cms): add dummy sitemap entry to test CMS-only deployment
jhb-dev Dec 22, 2025
b741321
fix(ci): install Vercel CLI globally to avoid npx download
jhb-dev Dec 22, 2025
3ff8c22
feat: add Vercel automation bypass for protected CMS previews
jhb-dev Dec 22, 2025
2d1465e
chore: remove test code from cms and web
jhb-dev Dec 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Deployment workflow for the JHB Software website.
#
# This workflow deploys the CMS before the Web frontend to ensure the frontend
# always builds against the latest CMS schema and content. The Web frontend
# fetches data from the CMS at build time, so the CMS must be deployed first.
#
# Production: Triggered on push to main
# Preview: Triggered on pull requests, uses CMS preview URL for Web build
#
# Disable automatic Vercel deployments for both projects to avoid race conditions.

name: Deploy

on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
deploy_cms:
description: 'Deploy CMS'
required: false
default: true
type: boolean
deploy_web:
description: 'Deploy Web'
required: false
default: true
type: boolean
environment:
description: 'Environment'
required: false
default: 'preview'
type: choice
options:
- preview
- production

concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true

env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
IS_PRODUCTION: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event.inputs.environment == 'production' }}

jobs:
deploy-cms:
name: Deploy CMS
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.deploy_cms == 'true' }}
outputs:
deployment_url: ${{ steps.deploy.outputs.deployment_url }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install Vercel CLI
run: npm install -g vercel@latest

- name: Deploy to Vercel (Production)
id: deploy-prod
if: ${{ env.IS_PRODUCTION == 'true' }}
run: |
DEPLOYMENT_URL=$(vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }})
echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
echo "CMS deployed to: $DEPLOYMENT_URL"
env:
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_CMS_PROJECT_ID }}

- name: Deploy to Vercel (Preview)
id: deploy-preview
if: ${{ env.IS_PRODUCTION != 'true' }}
run: |
DEPLOYMENT_URL=$(vercel deploy --yes --token=${{ secrets.VERCEL_TOKEN }})
echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
echo "CMS preview deployed to: $DEPLOYMENT_URL"
env:
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_CMS_PROJECT_ID }}

- name: Set deployment URL output
id: deploy
run: |
if [ "${{ env.IS_PRODUCTION }}" == "true" ]; then
echo "deployment_url=${{ steps.deploy-prod.outputs.deployment_url }}" >> $GITHUB_OUTPUT
else
echo "deployment_url=${{ steps.deploy-preview.outputs.deployment_url }}" >> $GITHUB_OUTPUT
fi

deploy-web:
name: Deploy Web
runs-on: ubuntu-latest
needs: deploy-cms
if: ${{ always() && (github.event_name != 'workflow_dispatch' || github.event.inputs.deploy_web == 'true') && (needs.deploy-cms.result == 'success' || needs.deploy-cms.result == 'skipped') }}
outputs:
deployment_url: ${{ steps.deploy.outputs.deployment_url }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install Vercel CLI
run: npm install -g vercel@latest

- name: Deploy to Vercel (Production)
id: deploy-prod
if: ${{ env.IS_PRODUCTION == 'true' }}
run: |
DEPLOYMENT_URL=$(vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }})
echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
echo "Web deployed to: $DEPLOYMENT_URL"
env:
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_WEB_PROJECT_ID }}

- name: Deploy to Vercel (Preview)
id: deploy-preview
if: ${{ env.IS_PRODUCTION != 'true' }}
run: |
DEPLOYMENT_URL=$(vercel deploy --yes --token=${{ secrets.VERCEL_TOKEN }} --build-env CMS_URL=${{ needs.deploy-cms.outputs.deployment_url }})
echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
echo "Web preview deployed to: $DEPLOYMENT_URL"
env:
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_WEB_PROJECT_ID }}

- name: Set deployment URL output
id: deploy
run: |
if [ "${{ env.IS_PRODUCTION }}" == "true" ]; then
echo "deployment_url=${{ steps.deploy-prod.outputs.deployment_url }}" >> $GITHUB_OUTPUT
else
echo "deployment_url=${{ steps.deploy-preview.outputs.deployment_url }}" >> $GITHUB_OUTPUT
fi

- name: Deployment Summary
run: |
if [ "${{ env.IS_PRODUCTION }}" == "true" ]; then
echo "## Production Deployment Complete" >> $GITHUB_STEP_SUMMARY
else
echo "## Preview Deployment Complete" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **CMS**: ${{ needs.deploy-cms.outputs.deployment_url || 'Skipped' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Web**: ${{ steps.deploy.outputs.deployment_url }}" >> $GITHUB_STEP_SUMMARY

comment-preview:
name: Comment Preview URLs
runs-on: ubuntu-latest
needs: [deploy-cms, deploy-web]
if: ${{ github.event_name == 'pull_request' && needs.deploy-web.result == 'success' }}
steps:
- name: Find existing comment
uses: peter-evans/find-comment@v3
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: '## Preview Deployment'

- name: Create or update comment
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
edit-mode: replace
body: |
## Preview Deployment

| Project | URL |
|---------|-----|
| CMS | ${{ needs.deploy-cms.outputs.deployment_url }} |
| Web | ${{ needs.deploy-web.outputs.deployment_url }} |

The Web preview uses the CMS preview URL for content fetching.
12 changes: 12 additions & 0 deletions web/src/layout/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import CacheClearButton from '@/components/CacheClearButton.astro'
import PostHog from '@/components/PostHog.astro'
import { websiteConfig } from '@/config'
import '@fontsource-variable/montserrat'
import { CMS_URL } from 'astro:env/client'
import { VERCEL_ENV } from 'astro:env/server'
import { ClientRouter } from 'astro:transitions'
import type { SeoMetadata } from 'cms/src/payload-types'
Expand All @@ -24,6 +25,10 @@ const { preview } = Astro.locals.globalState

const enableAnalytics = VERCEL_ENV === 'production' && !preview
const showCacheButton = import.meta.env.DEV && !preview
const isPreviewDeployment = VERCEL_ENV === 'preview'

console.log('[Preview Test] CMS_URL:', CMS_URL)
console.log('[Preview Test] VERCEL_ENV:', VERCEL_ENV)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug console.log statements will run in production

The console.log statements labeled [Preview Test] are unconditional and will execute on every page render in all environments, including production. Unlike the isPreviewDeployment banner which is conditional, these logs always run. This appears to be temporary debugging code that was included for testing purposes but will pollute server logs with CMS_URL and VERCEL_ENV values on every page load.

Fix in Cursor Fix in Web

---

<!doctype html>
Expand Down Expand Up @@ -59,6 +64,13 @@ const showCacheButton = import.meta.env.DEV && !preview
"url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32' fill='none' stroke='rgb(15 23 42 / 0.025)'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e\")",
}}
>
{
isPreviewDeployment && (
<div class="bg-amber-500 text-black text-center py-2 text-sm font-medium">
Preview Deployment - CMS: {CMS_URL}
</div>
)
}
<Header alternatePaths={meta.alternatePaths} />
<main class="py-32">
<slot />
Expand Down
Loading