Skip to content

fix: replace unsupported lucide brand icons #75

fix: replace unsupported lucide brand icons

fix: replace unsupported lucide brand icons #75

Workflow file for this run

name: Deploy Web
on:
push:
branches: [main, develop]
paths:
- "apps/web/**"
- "packages/**"
- "bun.lock"
- "package.json"
- ".github/workflows/deploy-web.yaml"
pull_request:
types: [opened, reopened, synchronize, closed]
paths:
- "apps/web/**"
- "packages/**"
- "bun.lock"
- "package.json"
- ".github/workflows/deploy-web.yaml"
workflow_dispatch:
inputs:
stage:
description: "Stage to deploy (e.g. production, staging, pr-42)"
required: true
type: string
concurrency:
group: deploy-web-${{ github.ref }}
cancel-in-progress: false
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
NEON_API_KEY: ${{ secrets.NEON_API_KEY }}
jobs:
stage:
runs-on: ubuntu-latest
outputs:
stage: ${{ steps.resolve.outputs.stage }}
action: ${{ steps.resolve.outputs.action }}
steps:
- id: resolve
run: |
set -euo pipefail
event="${{ github.event_name }}"
if [[ "$event" == "workflow_dispatch" ]]; then
stage="${{ inputs.stage }}"
action="deploy"
elif [[ "$event" == "push" ]]; then
case "${{ github.ref_name }}" in
main) stage="production" ;;
develop) stage="staging" ;;
*) echo "Unexpected ref ${{ github.ref_name }}" >&2; exit 1 ;;
esac
action="deploy"
elif [[ "$event" == "pull_request" ]]; then
stage="pr-${{ github.event.pull_request.number }}"
if [[ "${{ github.event.action }}" == "closed" ]]; then
action="destroy"
else
action="deploy"
fi
fi
echo "stage=$stage" >> "$GITHUB_OUTPUT"
echo "action=$action" >> "$GITHUB_OUTPUT"
echo "Resolved: $action stage=$stage"
deploy:
needs: stage
if: needs.stage.outputs.action == 'deploy'
runs-on: ubuntu-latest
environment:
name: ${{ needs.stage.outputs.stage }}
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
# Persist alchemy's LocalState (.alchemy/state/<stack>/<stage>/) across
# runners so the destroy job on PR close can find the resources to tear down.
# alchemy currently ships only a filesystem state backend; without this
# cache, every runner starts blind and orphans Workers + Neon projects.
- name: Restore alchemy state
uses: actions/cache@v4
with:
path: apps/web/.alchemy
key: alchemy-state-web-${{ needs.stage.outputs.stage }}
# Invalidate Cloudflare.Vite's content-hash memo on every push.
#
# `Cloudflare.Vite` hashes the Worker's working directory (`apps/web/`)
# to decide whether a rebuild + upload is needed. That scope does *not*
# include workspace dependencies under `packages/**` (`@stackpanel/api`,
# `@stackpanel/auth`, `@stackpanel/db`, …), so a push-to-`main` that
# only touches those packages hashes identically to the last deploy and
# alchemy short-circuits with "no change" — silently skipping the Vite
# build and the Worker upload. The apex then stays frozen on whichever
# commit last happened to touch `apps/web/`.
#
# Stamping a commit SHA (+ refs) file inside `apps/web/` forces a fresh
# memo hash on every run, so production tracks `main` even for diffs
# that live entirely in `packages/**`. The file must NOT be gitignored
# (the default memo `exclude` is the merged gitignore rules, which
# would otherwise filter it out). It's written only in CI — runners
# are ephemeral, so there's nothing to clean up — and contains no
# secrets.
- name: Stamp build info for memo invalidation
working-directory: apps/web
run: |
set -euo pipefail
cat > .build-info <<EOF
sha=${{ github.sha }}
ref=${{ github.ref }}
event=${{ github.event_name }}
run_id=${{ github.run_id }}
EOF
echo "--- .build-info ---"
cat .build-info
- name: Deploy
id: deploy
working-directory: apps/web
env:
STAGE: ${{ needs.stage.outputs.stage }}
# SOPS AGE key used by `loadAppEnv` (sops-age) to decrypt the
# generated per-app payloads. `production` uses the prod key;
# everything else (staging, pr-*, dev) uses the dev key.
# All stages encrypt with the github_actions age recipient
# (SECRETS_AGE_KEY_DEV's pubkey). The previous prod/dev split
# was stale — SECRETS_AGE_KEY_PROD's pubkey isn't on the prod
# payloads, so production deploys failed at decrypt time.
SOPS_AGE_KEY: ${{ secrets.SECRETS_AGE_KEY_DEV }}
run: |
set -euo pipefail
bunx alchemy deploy --stage ${{ needs.stage.outputs.stage }} --yes
- name: Comment preview URL on PR
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: preview-web
message: |
Preview deployed to `${{ needs.stage.outputs.stage }}` — https://${{ needs.stage.outputs.stage }}.stackpanel.com
destroy:
needs: stage
if: needs.stage.outputs.action == 'destroy'
runs-on: ubuntu-latest
permissions:
actions: write # gh cache delete
contents: read
pull-requests: write # sticky comment
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
# Restore-only — we never want the destroy job's post-step to write the
# (now-empty) state back to the cache.
- name: Restore alchemy state
uses: actions/cache/restore@v4
with:
path: apps/web/.alchemy
key: alchemy-state-web-${{ needs.stage.outputs.stage }}
- name: Destroy PR preview
working-directory: apps/web
env:
STAGE: ${{ needs.stage.outputs.stage }}
# PR previews always use the dev key.
SOPS_AGE_KEY: ${{ secrets.SECRETS_AGE_KEY_DEV }}
run: |
set -euo pipefail
bunx alchemy destroy --stage ${{ needs.stage.outputs.stage }} --yes
- name: Delete cached alchemy state
if: always()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh cache delete "alchemy-state-web-${{ needs.stage.outputs.stage }}" \
--repo "${{ github.repository }}" || true
- name: Mark preview comment as torn down
uses: marocchino/sticky-pull-request-comment@v2
with:
header: preview-web
message: |
Preview `${{ needs.stage.outputs.stage }}` has been destroyed.