Deploy #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy | |
| on: | |
| workflow_run: | |
| workflows: ["CI"] | |
| types: [completed] | |
| branches: [main] | |
| jobs: | |
| deploy-backend: | |
| name: Deploy Backend to Railway | |
| runs-on: ubuntu-latest | |
| # Only deploy if the CI run that triggered this workflow actually passed. | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Verify Railway secrets | |
| run: | | |
| test -n "$RAILWAY_API_TOKEN" || (echo "Missing RAILWAY_API_TOKEN GitHub repository secret" && exit 1) | |
| test -n "$RAILWAY_PROJECT_ID" || (echo "Missing RAILWAY_PROJECT_ID GitHub repository secret" && exit 1) | |
| test -n "$RAILWAY_SERVICE_ID" || (echo "Missing RAILWAY_SERVICE_ID GitHub repository secret" && exit 1) | |
| env: | |
| RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }} | |
| RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }} | |
| RAILWAY_SERVICE_ID: ${{ secrets.RAILWAY_SERVICE_ID }} | |
| - name: Install Railway CLI | |
| run: npm install -g @railway/cli@5.2.0 | |
| # Deploy from the repo root (not backend/) because the Railway service | |
| # is already configured with Root Directory=/backend, so Railway applies | |
| # its own configured root when given the full repo as build context. | |
| - name: Deploy to Railway | |
| run: railway up --project "$RAILWAY_PROJECT_ID" --service "$RAILWAY_SERVICE_ID" --environment production --ci | |
| env: | |
| RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }} | |
| RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }} | |
| RAILWAY_SERVICE_ID: ${{ secrets.RAILWAY_SERVICE_ID }} | |
| - name: Verify production CORS | |
| run: | | |
| for attempt in {1..6}; do | |
| headers="$(mktemp)" | |
| status="$( | |
| curl -sS -o /dev/null -D "$headers" -w "%{http_code}" \ | |
| -X OPTIONS "https://api.simpleinvoice.khangtran.dev/auth/login" \ | |
| -H "Origin: https://simpleinvoice.khangtran.dev" \ | |
| -H "Access-Control-Request-Method: POST" \ | |
| -H "Access-Control-Request-Headers: content-type" | |
| )" | |
| if [ "$status" = "204" ] && grep -qi '^access-control-allow-origin: https://simpleinvoice.khangtran.dev' "$headers"; then | |
| exit 0 | |
| fi | |
| echo "Production CORS check failed on attempt $attempt; retrying..." | |
| cat "$headers" | |
| sleep 10 | |
| done | |
| echo "Production API did not allow https://simpleinvoice.khangtran.dev after deploy." | |
| exit 1 | |
| deploy-frontend: | |
| name: Deploy Frontend to Cloudflare Pages | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| defaults: | |
| run: | |
| working-directory: frontend | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build | |
| run: npm run build | |
| env: | |
| VITE_API_BASE_URL: https://api.simpleinvoice.khangtran.dev | |
| - name: Verify Cloudflare secrets | |
| run: | | |
| test -n "$CLOUDFLARE_ACCOUNT_ID" || (echo "Missing CLOUDFLARE_ACCOUNT_ID GitHub repository secret" && exit 1) | |
| test -n "$CLOUDFLARE_API_TOKEN" || (echo "Missing CLOUDFLARE_API_TOKEN GitHub repository secret" && exit 1) | |
| env: | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| # Cloudflare Pages Direct Upload: we build dist/ ourselves and push it | |
| # straight to Cloudflare. No Cloudflare Git integration is used, and | |
| # this is not `wrangler deploy` (that's for Workers, not Pages). | |
| - name: Deploy to Cloudflare Pages | |
| run: npx wrangler pages deploy dist --project-name simple-invoice --branch main | |
| env: | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} |