feat: web build target via Emscripten + automated Vercel deployment #1
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 Web (Vercel) | |
| on: | |
| # Production: only pushes to main. | |
| push: | |
| branches: [main] | |
| paths: | |
| - "src/**" | |
| - "assets/**" | |
| - "web/**" | |
| - "vendor/**" | |
| - "CMakeLists.txt" | |
| - ".github/workflows/deploy-web.yml" | |
| # Preview: every PR targeting main gets a throwaway preview URL — try it | |
| # before merging. Same-repo PRs have access to secrets; forked PRs don't | |
| # (the deploy step will simply skip then). | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - "src/**" | |
| - "assets/**" | |
| - "web/**" | |
| - "vendor/**" | |
| - "CMakeLists.txt" | |
| - ".github/workflows/deploy-web.yml" | |
| # Manual: run from any branch via the Actions tab once this workflow has | |
| # been merged to the default branch at least once. | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: "Deployment target (preview by default; pick production to promote)" | |
| type: choice | |
| options: [preview, production] | |
| default: preview | |
| concurrency: | |
| group: deploy-web-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | |
| VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | |
| jobs: | |
| deploy: | |
| name: Build & Deploy to Vercel | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: ${{ github.event_name == 'push' && 'production' || (github.event_name == 'workflow_dispatch' && inputs.environment) || 'preview' }} | |
| url: ${{ steps.deploy.outputs.url }} | |
| # Triggers: | |
| # push → production (main only) | |
| # pull_request → preview (try it before merging) | |
| # workflow_dispatch → manual, runs from any branch you pick | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 1 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y cmake ninja-build pkg-config | |
| - name: Setup Emscripten SDK | |
| uses: mymindstorm/setup-emsdk@v14 | |
| with: | |
| version: latest | |
| actions-cache-folder: emsdk-cache | |
| - name: ccache | |
| uses: hendrikmuhs/ccache-action@v1.2 | |
| with: | |
| key: emscripten-web | |
| - name: Cache build-web directory | |
| uses: actions/cache@v5 | |
| with: | |
| path: build-web | |
| key: build-web-${{ hashFiles('CMakeLists.txt', 'src/**') }} | |
| restore-keys: | | |
| build-web- | |
| - name: Configure (Emscripten) | |
| run: > | |
| emcmake cmake -S . -B build-web | |
| -G Ninja | |
| -DCMAKE_BUILD_TYPE=Release | |
| -DCMAKE_COLOR_DIAGNOSTICS=ON | |
| -DCMAKE_POLICY_VERSION_MINIMUM=3.5 | |
| - name: Build web bundle | |
| run: cmake --build build-web --parallel | |
| - name: Verify build output | |
| run: | | |
| test -f web-dist/index.html | |
| test -f web-dist/index.js | |
| test -f web-dist/index.wasm | |
| ls -lh web-dist/ | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 20 | |
| - name: Install Vercel CLI | |
| run: npm install --global vercel@latest | |
| - name: Determine environment | |
| id: env | |
| run: | | |
| # Production only when: | |
| # - push event to main, OR | |
| # - workflow_dispatch with environment=production | |
| # Everything else (pull_request, manual preview) → preview. | |
| target=preview | |
| prod_flag= | |
| if [ "${{ github.event_name }}" = "push" ]; then | |
| target=production | |
| prod_flag=--prod | |
| elif [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "production" ]; then | |
| target=production | |
| prod_flag=--prod | |
| fi | |
| echo "target=$target" >> "$GITHUB_OUTPUT" | |
| echo "prod_flag=$prod_flag" >> "$GITHUB_OUTPUT" | |
| - name: Pull Vercel project settings | |
| run: vercel pull --yes --environment=${{ steps.env.outputs.target }} --token=${{ secrets.VERCEL_TOKEN }} | |
| - name: Assemble Vercel prebuilt output | |
| run: | | |
| rm -rf .vercel/output | |
| mkdir -p .vercel/output/static | |
| cp -r web-dist/. .vercel/output/static/ | |
| cat > .vercel/output/config.json <<'EOF' | |
| { | |
| "version": 3, | |
| "routes": [ | |
| { | |
| "src": "/(.*\\.wasm)", | |
| "headers": { "content-type": "application/wasm" }, | |
| "continue": true | |
| }, | |
| { | |
| "src": "/(.*\\.data)", | |
| "headers": { "content-type": "application/octet-stream" }, | |
| "continue": true | |
| }, | |
| { | |
| "src": "/", | |
| "headers": { | |
| "cross-origin-opener-policy": "same-origin", | |
| "cross-origin-embedder-policy": "require-corp" | |
| }, | |
| "continue": true | |
| } | |
| ], | |
| "overrides": { | |
| "index.html": { "path": "" } | |
| } | |
| } | |
| EOF | |
| - name: Deploy to Vercel | |
| id: deploy | |
| run: | | |
| URL=$(vercel deploy --prebuilt ${{ steps.env.outputs.prod_flag }} --token=${{ secrets.VERCEL_TOKEN }}) | |
| echo "url=$URL" >> "$GITHUB_OUTPUT" | |
| echo "Deployed to: $URL" | |
| - name: Job summary | |
| run: | | |
| { | |
| echo "## Vercel deployment" | |
| echo "" | |
| echo "- **Environment:** ${{ steps.env.outputs.target }}" | |
| echo "- **URL:** ${{ steps.deploy.outputs.url }}" | |
| } >> "$GITHUB_STEP_SUMMARY" |