Deploy #5
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: | |
| # Gate on CI passing — this workflow only fires after lint/typecheck/format/test succeeds | |
| workflows: ["Lint/Typecheck/Format/Test"] | |
| types: [completed] | |
| # Only watch these branches — PRs from feature branches don't trigger this | |
| branches: [develop, main] | |
| # Required for google-github-actions/auth to request an OIDC token from GitHub | |
| permissions: | |
| id-token: write | |
| contents: read | |
| jobs: | |
| deploy-dev: | |
| # Three conditions must all be true: | |
| # 1. CI passed (not cancelled or failed) | |
| # 2. This was a push (merge), not a PR CI run | |
| # 3. The push was to develop specifically | |
| if: > | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.head_branch == 'develop' | |
| runs-on: ubuntu-latest | |
| # Scopes WIF_PROVIDER and WIF_SERVICE_ACCOUNT vars to the dev environment | |
| environment: dev | |
| steps: | |
| # Check out the exact commit that triggered CI — not just the branch HEAD | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version-file: ".nvmrc" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| # Exchange GitHub's OIDC token for a short-lived OAuth 2.0 access token | |
| # via Workload Identity Federation. token_format: access_token is required | |
| # because clasp cannot parse the external_account credential file that WIF | |
| # writes by default — this gives clasp a concrete token it can use directly. | |
| - name: Authenticate to Google Cloud | |
| id: auth | |
| uses: google-github-actions/auth@v3 | |
| with: | |
| workload_identity_provider: ${{ vars.WIF_PROVIDER }} | |
| service_account: ${{ vars.WIF_SERVICE_ACCOUNT }} | |
| token_format: 'access_token' | |
| access_token_scopes: 'https://www.googleapis.com/auth/script.projects,https://www.googleapis.com/auth/drive.file,https://www.googleapis.com/auth/script.deployments' | |
| # Write the access token to ~/.clasprc.json where clasp reads credentials. | |
| # expiry_date is set far in the future to prevent clasp attempting a refresh | |
| # (there is no refresh token — the 1-hour access token is sufficient for a push). | |
| # ACCESS_TOKEN is passed via env to avoid shell interpolation exposure. | |
| - name: Write clasp credentials | |
| env: | |
| ACCESS_TOKEN: ${{ steps.auth.outputs.access_token }} | |
| run: | | |
| python3 -c " | |
| import json, os | |
| json.dump({'tokens': {'default': {'access_token': os.environ['ACCESS_TOKEN'], 'token_type': 'Bearer', 'expiry_date': 9999999999999}}}, open(os.path.expanduser('~/.clasprc.json'), 'w')) | |
| " | |
| # Build, copy dev clasp config, and push. Clasp reads credentials from | |
| # ~/.clasprc.json written in the previous step. | |
| - name: Deploy to dev | |
| run: npm run build && cp .clasp.dev.json .clasp.json && npx clasp push | |
| deploy-prod: | |
| # Same gate logic, but only fires on pushes to main | |
| if: > | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.head_branch == 'main' | |
| runs-on: ubuntu-latest | |
| environment: prod | |
| steps: | |
| # Check out the exact commit that triggered CI — not just the branch HEAD | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version-file: ".nvmrc" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| # Exchange GitHub's OIDC token for a short-lived OAuth 2.0 access token | |
| # via Workload Identity Federation. token_format: access_token is required | |
| # because clasp cannot parse the external_account credential file that WIF | |
| # writes by default — this gives clasp a concrete token it can use directly. | |
| - name: Authenticate to Google Cloud | |
| id: auth | |
| uses: google-github-actions/auth@v3 | |
| with: | |
| workload_identity_provider: ${{ vars.WIF_PROVIDER }} | |
| service_account: ${{ vars.WIF_SERVICE_ACCOUNT }} | |
| token_format: 'access_token' | |
| access_token_scopes: 'https://www.googleapis.com/auth/script.projects,https://www.googleapis.com/auth/drive.file,https://www.googleapis.com/auth/script.deployments' | |
| # Write the access token to ~/.clasprc.json where clasp reads credentials. | |
| # expiry_date is set far in the future to prevent clasp attempting a refresh | |
| # (there is no refresh token — the 1-hour access token is sufficient for a push). | |
| # ACCESS_TOKEN is passed via env to avoid shell interpolation exposure. | |
| - name: Write clasp credentials | |
| env: | |
| ACCESS_TOKEN: ${{ steps.auth.outputs.access_token }} | |
| run: | | |
| python3 -c " | |
| import json, os | |
| json.dump({'tokens': {'default': {'access_token': os.environ['ACCESS_TOKEN'], 'token_type': 'Bearer', 'expiry_date': 9999999999999}}}, open(os.path.expanduser('~/.clasprc.json'), 'w')) | |
| " | |
| # Build, copy prod clasp config, and push. Clasp reads credentials from | |
| # ~/.clasprc.json written in the previous step. | |
| - name: Deploy to prod | |
| run: npm run build && cp .clasp.prod.json .clasp.json && npx clasp push |