Skip to content

v0.7.0 - Docker Files Published #72

v0.7.0 - Docker Files Published

v0.7.0 - Docker Files Published #72

name: Deploy to Cloudflare Workers
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: "Version tag (e.g., v1.0.0)"
required: true
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
PNPM_VERSION: 10.19.0
NODE_VERSION: "20"
# Only run for app releases (v*), not package releases (@tuvixrss/*)
# Package releases like @tuvixrss/tricorder@x.y.z should not trigger deployment
jobs:
check-release-type:
name: Check if this is an app release
runs-on: ubuntu-latest
outputs:
should-deploy: ${{ steps.check.outputs.should-deploy }}
steps:
- name: Check release tag format
id: check
run: |
TAG="${{ github.event.release.tag_name || github.event.inputs.version }}"
if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "should-deploy=true" >> $GITHUB_OUTPUT
echo "✅ App release detected: $TAG"
elif [[ "$TAG" =~ ^@tuvixrss/ ]]; then
echo "should-deploy=false" >> $GITHUB_OUTPUT
echo "⏭️ Package release detected: $TAG - skipping deployment"
else
echo "should-deploy=true" >> $GITHUB_OUTPUT
echo "⚠️ Unknown tag format: $TAG - proceeding with deployment"
fi
deploy-api:
name: Deploy API to Cloudflare Workers
runs-on: ubuntu-latest
needs: [check-release-type]
if: needs.check-release-type.outputs.should-deploy == 'true'
environment:
name: production
env:
# Set from secret for conditional step execution
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.event.release.tag_name || github.event.inputs.version || github.ref }}
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Build Tricorder
run: pnpm run build:tricorder
- name: Type check API
run: pnpm run type-check:api
- name: Run API tests
run: pnpm run test:api
- name: Build API
run: pnpm run build:api
- name: Create wrangler.toml from example
uses: ./.github/actions/substitute-d1-database-id
with:
d1-database-id: ${{ secrets.D1_DATABASE_ID }}
- name: Get worker name
id: worker-name
uses: ./.github/actions/get-worker-name
- name: Get release version
id: release-version
run: |
RELEASE_VERSION="${{ github.event.release.tag_name || github.event.inputs.version || github.sha }}"
echo "version=$RELEASE_VERSION" >> $GITHUB_OUTPUT
echo "Release version: $RELEASE_VERSION"
- name: Set Sentry Release (Backend)
if: env.SENTRY_DSN != ''
run: |
RELEASE_VERSION="${{ steps.release-version.outputs.version }}"
cd packages/api
echo "$RELEASE_VERSION" | pnpm exec wrangler secret put SENTRY_RELEASE --name "${{ steps.worker-name.outputs.name }}"
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Deploy to Cloudflare Workers
id: deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: packages/api
command: deploy
packageManager: pnpm
- name: Output API deployment URL
if: success()
run: |
API_URL="https://${{ steps.worker-name.outputs.name }}.workers.dev"
echo "API_URL=$API_URL" >> $GITHUB_OUTPUT
echo "## API Deployment" >> $GITHUB_STEP_SUMMARY
echo "- **URL:** $API_URL" >> $GITHUB_STEP_SUMMARY
- name: Prepare D1 migrations
if: success()
working-directory: packages/api
run: |
# Copy migrations from drizzle/ to migrations/ (Wrangler expects migrations/ folder)
# This matches the logic in scripts/migrate-d1.sh lines 56-57
mkdir -p migrations
cp drizzle/*.sql migrations/ 2>/dev/null || true
if [ ! "$(ls -A migrations 2>/dev/null)" ]; then
echo "::error::No migration files found in drizzle/ directory"
exit 1
fi
echo "✅ Prepared $(ls migrations/*.sql | wc -l) migration file(s)"
- name: Verify D1 database access
if: success()
working-directory: packages/api
run: |
echo "🔍 Verifying D1 database access..."
# Verify we can access the database (this will fail early if permissions are wrong)
pnpm exec wrangler d1 migrations list tuvix --remote || {
echo "::error::Failed to access D1 database. Check:"
echo " 1. API token has 'Account.Cloudflare D1:Edit' permission"
echo " 2. Account ID matches the database's account"
echo " 3. Database ID is correct for this account"
exit 1
}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
D1_DATABASE_ID: ${{ secrets.D1_DATABASE_ID }}
- name: Run database migrations
if: success()
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: packages/api
command: d1 migrations apply tuvix --remote
packageManager: pnpm
env:
D1_DATABASE_ID: ${{ secrets.D1_DATABASE_ID }}
- name: Cleanup migrations folder
if: always()
working-directory: packages/api
run: |
rm -rf migrations
echo "✅ Cleaned up migrations folder"
deploy-app:
name: Deploy App to Cloudflare Pages
runs-on: ubuntu-latest
needs: [check-release-type, deploy-api]
if: needs.check-release-type.outputs.should-deploy == 'true'
environment:
name: production
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.event.release.tag_name || github.event.inputs.version || github.ref }}
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Type check App
run: pnpm run type-check:app
- name: Run App tests
run: pnpm run test:app
- name: Get release version
id: release-version
run: |
RELEASE_VERSION="${{ github.event.release.tag_name || github.event.inputs.version || github.sha }}"
echo "version=$RELEASE_VERSION" >> $GITHUB_OUTPUT
echo "Release version: $RELEASE_VERSION"
- name: Verify Sentry Configuration
id: verify-sentry
env:
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
run: |
echo "🔍 Checking VITE_SENTRY_DSN secret..."
echo "Secret exists check: ${{ secrets.VITE_SENTRY_DSN != '' }}"
echo "Secret length: ${#VITE_SENTRY_DSN}"
echo "Secret starts with: ${VITE_SENTRY_DSN:0:20}..."
if [ -z "$VITE_SENTRY_DSN" ] || [ "$VITE_SENTRY_DSN" = "" ]; then
echo "⚠️ WARNING: VITE_SENTRY_DSN secret is not set or is empty in GitHub Secrets"
echo " Frontend Sentry will not be initialized."
echo " Set it in: Settings → Secrets and variables → Actions → VITE_SENTRY_DSN"
echo "has_dsn=false" >> $GITHUB_OUTPUT
else
echo "✅ VITE_SENTRY_DSN is configured (length: ${#VITE_SENTRY_DSN} chars)"
echo "has_dsn=true" >> $GITHUB_OUTPUT
# Store the DSN for the build step (masked for security)
echo "dsn_length=${#VITE_SENTRY_DSN}" >> $GITHUB_OUTPUT
fi
- name: Build App
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}
VITE_APP_VERSION: ${{ steps.release-version.outputs.version }}
VITE_SENTRY_ENVIRONMENT: ${{ secrets.VITE_SENTRY_ENVIRONMENT || 'production' }}
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
run: |
echo "🔍 Build-time Sentry DSN check:"
echo "VITE_SENTRY_DSN type: $(if [ -z "$VITE_SENTRY_DSN" ]; then echo 'unset'; else echo 'set'; fi)"
echo "VITE_SENTRY_DSN length: ${#VITE_SENTRY_DSN}"
echo "VITE_SENTRY_DSN starts with: ${VITE_SENTRY_DSN:0:20}..."
echo "Verification step result: ${{ steps.verify-sentry.outputs.has_dsn }}"
# Verify the DSN is actually set and not empty
if [ -z "$VITE_SENTRY_DSN" ] || [ "$VITE_SENTRY_DSN" = "" ]; then
echo "❌ ERROR: VITE_SENTRY_DSN is empty or unset during build!"
echo " This will cause Sentry to not initialize in the frontend."
echo " Check that the secret is set correctly in GitHub Secrets."
exit 1
fi
echo "✅ Building with Sentry DSN configured (length: ${#VITE_SENTRY_DSN} chars)"
pnpm run build:app
- name: Verify Production Pages Project Name
run: |
echo "🔍 Verifying Pages project configuration..."
echo "Project Name: ${{ secrets.CLOUDFLARE_PAGES_PROJECT_NAME }}"
echo "Environment: production"
echo "⚠️ IMPORTANT: Ensure this project name matches your PRODUCTION Pages project"
echo " If this shows a dev project name, update the secret in:"
echo " Settings → Environments → production → Secrets → CLOUDFLARE_PAGES_PROJECT_NAME"
- name: Deploy to Cloudflare Pages
id: deploy-app
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy packages/app/dist --project-name=${{ secrets.CLOUDFLARE_PAGES_PROJECT_NAME }} --branch=main
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
packageManager: pnpm
- name: Output App deployment URL
if: success()
run: |
APP_URL="https://${{ secrets.CLOUDFLARE_PAGES_PROJECT_NAME }}.pages.dev"
echo "APP_URL=$APP_URL" >> $GITHUB_OUTPUT
echo "## App Deployment" >> $GITHUB_STEP_SUMMARY
echo "- **URL:** $APP_URL" >> $GITHUB_STEP_SUMMARY
notify:
name: Notify Deployment Status
runs-on: ubuntu-latest
needs: [check-release-type, deploy-api, deploy-app]
if: always() && needs.check-release-type.outputs.should-deploy == 'true'
environment:
name: production
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Create wrangler.toml from example
uses: ./.github/actions/substitute-d1-database-id
with:
d1-database-id: ${{ secrets.D1_DATABASE_ID }}
- name: Get worker name
id: worker-name
uses: ./.github/actions/get-worker-name
- name: Deployment Summary
run: |
echo "## 🚀 Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Deployed version:** \`${{ github.event.release.tag_name || github.event.inputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Deployment Status" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Service | Status | URL |" >> $GITHUB_STEP_SUMMARY
echo "|---------|--------|-----|" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.deploy-api.result }}" == "success" ]; then
echo "| API | ✅ Success | https://${{ steps.worker-name.outputs.name }}.workers.dev |" >> $GITHUB_STEP_SUMMARY
else
echo "| API | ❌ Failed | - |" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.deploy-app.result }}" == "success" ]; then
echo "| App | ✅ Success | https://${{ secrets.CLOUDFLARE_PAGES_PROJECT_NAME }}.pages.dev |" >> $GITHUB_STEP_SUMMARY
else
echo "| App | ❌ Failed | - |" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Quick Links" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.deploy-api.result }}" == "success" ]; then
echo "- [API Worker](https://${{ steps.worker-name.outputs.name }}.workers.dev)" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.deploy-app.result }}" == "success" ]; then
echo "- [App Pages](https://${{ secrets.CLOUDFLARE_PAGES_PROJECT_NAME }}.pages.dev)" >> $GITHUB_STEP_SUMMARY
fi