mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
313 lines
10 KiB
YAML
313 lines
10 KiB
YAML
name: Deploy
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- 'apps/marketing/**'
|
|
- 'apps/portal/**'
|
|
- 'apps/paste-service/**'
|
|
- 'apps/waitlist-service/**'
|
|
- 'packages/**'
|
|
workflow_dispatch:
|
|
inputs:
|
|
target:
|
|
description: 'Deploy target'
|
|
required: true
|
|
default: 'all'
|
|
type: choice
|
|
options:
|
|
- all
|
|
- marketing
|
|
- portal
|
|
- paste
|
|
- waitlist
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
# Deploy remains automatic, but only after the exact commit has passed the
|
|
# repository's full Test workflow. The workflows start together on a main
|
|
# push; this job waits for Test instead of allowing deployment to race it.
|
|
verify:
|
|
name: Require successful Test run
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
steps:
|
|
- name: Wait for Test on this commit
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
COMMIT_SHA: ${{ github.sha }}
|
|
shell: bash
|
|
run: |
|
|
deadline=$((SECONDS + 2400))
|
|
previous_state=""
|
|
|
|
while (( SECONDS < deadline )); do
|
|
run_json="$(gh api --method GET \
|
|
"repos/${REPOSITORY}/actions/workflows/test.yml/runs" \
|
|
-f head_sha="$COMMIT_SHA" \
|
|
-f per_page=100)"
|
|
|
|
# Manual Test runs are an explicit recovery path when the original
|
|
# push run was skipped or cannot be recovered. Ignore PR runs and
|
|
# retain the exact-SHA requirement enforced by the API query.
|
|
run="$(jq -c '
|
|
[.workflow_runs[]
|
|
| select(
|
|
.head_sha == env.COMMIT_SHA and
|
|
(.event == "push" or .event == "workflow_dispatch")
|
|
)]
|
|
| sort_by(.created_at)
|
|
| last // {}
|
|
' <<<"$run_json")"
|
|
|
|
trigger="$(jq -r '.event // ""' <<<"$run")"
|
|
status="$(jq -r '.status // "missing"' <<<"$run")"
|
|
conclusion="$(jq -r '.conclusion // ""' <<<"$run")"
|
|
run_url="$(jq -r '.html_url // ""' <<<"$run")"
|
|
state="${trigger}:${status}:${conclusion}"
|
|
|
|
if [[ "$state" != "$previous_state" ]]; then
|
|
if [[ -n "$run_url" ]]; then
|
|
echo "Test workflow for ${COMMIT_SHA}: ${state} (${run_url})"
|
|
else
|
|
echo "Test workflow for ${COMMIT_SHA}: ${state}"
|
|
fi
|
|
previous_state="$state"
|
|
fi
|
|
|
|
if [[ "$status" == "completed" ]]; then
|
|
if [[ "$conclusion" == "success" ]]; then
|
|
exit 0
|
|
fi
|
|
echo "Test workflow did not succeed for ${COMMIT_SHA}: ${conclusion} (${run_url})" >&2
|
|
exit 1
|
|
fi
|
|
|
|
sleep 15
|
|
done
|
|
|
|
echo "Timed out waiting for the Test workflow on ${COMMIT_SHA}." >&2
|
|
exit 1
|
|
|
|
detect-changes:
|
|
needs: verify
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
marketing: ${{ steps.changes.outputs.marketing }}
|
|
portal: ${{ steps.changes.outputs.portal }}
|
|
paste: ${{ steps.changes.outputs.paste }}
|
|
waitlist: ${{ steps.changes.outputs.waitlist }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Check for changes
|
|
id: changes
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
TARGET: ${{ inputs.target }}
|
|
BEFORE_SHA: ${{ github.event.before }}
|
|
CURRENT_SHA: ${{ github.sha }}
|
|
run: |
|
|
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
|
|
if [[ "$TARGET" == "all" || "$TARGET" == "marketing" ]]; then
|
|
echo "marketing=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "marketing=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
if [[ "$TARGET" == "all" || "$TARGET" == "portal" ]]; then
|
|
echo "portal=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "portal=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
if [[ "$TARGET" == "all" || "$TARGET" == "paste" ]]; then
|
|
echo "paste=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "paste=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
if [[ "$TARGET" == "all" || "$TARGET" == "waitlist" ]]; then
|
|
echo "waitlist=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "waitlist=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
else
|
|
# For push events, check what changed
|
|
git fetch origin "$BEFORE_SHA" --depth=1 2>/dev/null || true
|
|
|
|
MARKETING_CHANGED=$(git diff --name-only "$BEFORE_SHA" "$CURRENT_SHA" 2>/dev/null | grep -E '^(apps/marketing/|packages/)' || true)
|
|
PORTAL_CHANGED=$(git diff --name-only "$BEFORE_SHA" "$CURRENT_SHA" 2>/dev/null | grep -E '^(apps/portal/|packages/)' || true)
|
|
PASTE_CHANGED=$(git diff --name-only "$BEFORE_SHA" "$CURRENT_SHA" 2>/dev/null | grep -E '^apps/paste-service/' || true)
|
|
WAITLIST_CHANGED=$(git diff --name-only "$BEFORE_SHA" "$CURRENT_SHA" 2>/dev/null | grep -E '^apps/waitlist-service/' || true)
|
|
|
|
if [[ -n "$MARKETING_CHANGED" ]]; then
|
|
echo "marketing=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "marketing=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
if [[ -n "$PORTAL_CHANGED" ]]; then
|
|
echo "portal=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "portal=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
if [[ -n "$PASTE_CHANGED" ]]; then
|
|
echo "paste=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "paste=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
if [[ -n "$WAITLIST_CHANGED" ]]; then
|
|
echo "waitlist=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "waitlist=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
fi
|
|
|
|
deploy-marketing:
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.marketing == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
environment: production
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
|
with:
|
|
bun-version: 1.3.14
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Build marketing
|
|
run: bun run build:marketing
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Configure AWS credentials
|
|
uses: aws-actions/configure-aws-credentials@cbe3b392738ccf3f987d68400dafcf4b0624a56c # v6.2.4
|
|
with:
|
|
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
|
aws-region: us-east-1
|
|
|
|
- name: Deploy to S3
|
|
run: aws s3 sync apps/marketing/dist/ s3://plannotator-marketing/ --delete
|
|
|
|
- name: Invalidate CloudFront
|
|
run: |
|
|
aws cloudfront create-invalidation \
|
|
--distribution-id E284ON0A27O2H6 \
|
|
--paths "/*"
|
|
|
|
# NOTE: the /install.sh, /install.ps1, and /install.cmd paths on
|
|
# plannotator.ai are served from the dedicated plannotator-install-scripts
|
|
# bucket, NOT the marketing bucket. That bucket is deliberately NOT writable
|
|
# by the CI deploy role, so there is no auto-sync job here: after any change
|
|
# to scripts/install.*, sync BY HAND with local credentials (aws s3 cp each
|
|
# script with its content-type, then a CloudFront invalidation for the three
|
|
# paths on E284ON0A27O2H6) and verify the served md5s match the repo. See the
|
|
# release runbook's post-release checklist for the exact commands.
|
|
deploy-portal:
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.portal == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
environment: production
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
|
with:
|
|
bun-version: 1.3.14
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Build portal
|
|
run: bun run build:portal
|
|
|
|
- name: Configure AWS credentials
|
|
uses: aws-actions/configure-aws-credentials@cbe3b392738ccf3f987d68400dafcf4b0624a56c # v6.2.4
|
|
with:
|
|
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
|
aws-region: us-east-1
|
|
|
|
- name: Deploy to S3
|
|
run: aws s3 sync apps/portal/dist/ s3://plannotator-portal/ --delete
|
|
|
|
- name: Invalidate CloudFront
|
|
run: |
|
|
aws cloudfront create-invalidation \
|
|
--distribution-id EP0KB9EFUWYXR \
|
|
--paths "/*"
|
|
|
|
deploy-paste:
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.paste == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
environment: production
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
|
with:
|
|
bun-version: 1.3.14
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Deploy to Cloudflare
|
|
working-directory: apps/paste-service
|
|
run: npx wrangler deploy
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
|
|
deploy-waitlist:
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.waitlist == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
environment: production
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
|
with:
|
|
bun-version: 1.3.14
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Deploy to Cloudflare
|
|
working-directory: apps/waitlist-service
|
|
run: npx wrangler deploy
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|