Skip to content

fix(deps): update all non-major dependencies #165

fix(deps): update all non-major dependencies

fix(deps): update all non-major dependencies #165

Workflow file for this run

name: Preview
on:
pull_request:
branches: [main]
concurrency:
group: preview-${{ github.ref }}
cancel-in-progress: true
jobs:
preview-api:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: "package.json"
- uses: pnpm/action-setup@v4
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
- uses: actions/cache@v5
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run preview tests
run: pnpm --filter @append/api test:preview
- name: Apply D1 migrations to preview
working-directory: packages/api
run: pnpm exec wrangler d1 migrations apply append-db-preview --remote --config wrangler.jsonc --env preview
- name: Install Doppler CLI
uses: dopplerhq/cli-action@v3
- name: Sync secrets from Doppler to Cloudflare
working-directory: packages/api
env:
DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN_PREVIEW_APPEND }}
run: |
# Sync all secrets from Doppler to Cloudflare Worker (preview environment)
# This makes Doppler the canonical source of truth for secrets
set -euo pipefail
set +e
doppler secrets --json | \
jq -c 'with_entries(.value = .value.computed) | del(.DOPPLER_PROJECT, .DOPPLER_CONFIG, .DOPPLER_ENVIRONMENT, .DOPPLER_ENVIRONMENT_SLUG, .DOPPLER_PROJECT_NAME, .DOPPLER_CONFIG_NAME)' | \
pnpm exec wrangler secret bulk --env preview --config wrangler.jsonc
SYNC_EXIT_CODE=$?
set -e
if [ $SYNC_EXIT_CODE -ne 0 ]; then
echo "::warning::Secret sync failed (preview Worker may not exist yet). Deploying once to create the Worker, then retrying secret sync."
# Note: This intermediate deployment creates the Worker without E2E_AUTH_EMAIL.
# This transient state is acceptable for preview - the secret is set in the next step
# and validated before the final deployment. No public traffic reaches this intermediate state.
pnpm exec wrangler deploy --env preview --config wrangler.jsonc
doppler secrets --json | \
jq -c 'with_entries(.value = .value.computed) | del(.DOPPLER_PROJECT, .DOPPLER_CONFIG, .DOPPLER_ENVIRONMENT, .DOPPLER_ENVIRONMENT_SLUG, .DOPPLER_PROJECT_NAME, .DOPPLER_CONFIG_NAME)' | \
pnpm exec wrangler secret bulk --env preview --config wrangler.jsonc
fi
- name: Set PR-specific E2E auth email
working-directory: packages/api
run: |
# Set E2E_AUTH_EMAIL with PR-specific suffix for environment isolation
# This is the canonical place where preview E2E_AUTH_EMAIL is set.
# This matches the wildcard pattern in ALLOWED_EMAIL (e2e-bot+*@append.test)
set -euo pipefail
set +e
echo "e2e-bot+pr-${{ github.event.pull_request.number }}@append.test" | \
pnpm exec wrangler secret put E2E_AUTH_EMAIL --env preview --config wrangler.jsonc
PUT_EXIT_CODE=$?
set -e
if [ $PUT_EXIT_CODE -ne 0 ]; then
echo "::error::Failed to set E2E_AUTH_EMAIL secret for preview Worker."
exit 1
fi
- name: Verify required Worker secrets exist (no values)
working-directory: packages/api
run: |
set -euo pipefail
SECRETS_JSON="$(pnpm exec wrangler secret list --format json --env preview --config wrangler.jsonc)"
export SECRETS_JSON
python3 - <<'PY'
import json
import os
import sys
required = [
"GOOGLE_CLIENT_ID",
"GOOGLE_CLIENT_SECRET",
"BETTER_AUTH_SECRET",
"E2E_AUTH_SECRET",
"E2E_AUTH_EMAIL",
]
# At least one allowlist mechanism must be configured
allowlist = ["ALLOWED_SUB", "ALLOWED_EMAIL"]
secrets = json.loads(os.environ["SECRETS_JSON"])
names = {s.get("name") for s in secrets if isinstance(s, dict)}
missing = [name for name in required if name not in names]
if missing:
print(f"::error::Missing Worker secrets (names only): {', '.join(missing)}")
sys.exit(1)
# Check that at least one allowlist secret exists
allowlist_present = [name for name in allowlist if name in names]
if not allowlist_present:
print(f"::error::Missing allowlist configuration: at least one of {', '.join(allowlist)} must be present")
sys.exit(1)
print("✓ Required Worker secrets present")
print(f"✓ Allowlist configured: {', '.join(allowlist_present)}")
PY
- name: Deploy Worker API Preview
id: deploy
working-directory: packages/api
run: |
set +e # Don't exit on error yet
DEPLOYMENT_OUTPUT=$(pnpm exec wrangler deploy --env preview --config wrangler.jsonc 2>&1)
EXIT_CODE=$?
echo "$DEPLOYMENT_OUTPUT"
if [ $EXIT_CODE -ne 0 ]; then
echo "::error::Deployment failed with exit code $EXIT_CODE"
exit $EXIT_CODE
fi
# Extract deployment URL from output (strip ANSI codes first)
# shellcheck disable=SC2001
DEPLOYMENT_URL=$(echo "$DEPLOYMENT_OUTPUT" | sed 's/\x1b\[[0-9;]*m//g' | grep -oP 'https://[^\s]+\.workers\.dev' | head -1)
echo "deployment-url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT"
preview-web:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: "package.json"
- uses: pnpm/action-setup@v4
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
- uses: actions/cache@v5
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build web
working-directory: packages/web
env:
VITE_API_URL: https://append-api-preview.tindotdev.workers.dev
run: pnpm run build
- name: Deploy web preview to Pages
id: deploy
working-directory: packages/web
env:
BRANCH_NAME: ${{ github.head_ref }}
run: |
# Deploy to preview environment and capture URL
DEPLOY_OUTPUT=$(pnpm exec wrangler pages deploy dist --project-name "append-web" --branch "$BRANCH_NAME" 2>&1)
echo "$DEPLOY_OUTPUT"
# Extract URLs (strip ANSI codes first)
# shellcheck disable=SC2001
CLEAN_OUTPUT=$(echo "$DEPLOY_OUTPUT" | sed 's/\x1b\[[0-9;]*m//g')
# Get the alias URL (branch-specific) if available, otherwise get the primary URL
ALIAS_URL=$(echo "$CLEAN_OUTPUT" | grep -oP 'Deployment alias URL: \Khttps://[^\s]+\.pages\.dev' | head -1)
PRIMARY_URL=$(echo "$CLEAN_OUTPUT" | grep -oP 'Deployment complete.*\Khttps://[^\s]+\.pages\.dev' | head -1)
# Use alias URL if available, otherwise use primary
DEPLOYMENT_URL="${ALIAS_URL:-$PRIMARY_URL}"
if [ -z "$DEPLOYMENT_URL" ]; then
echo "::warning::Could not extract deployment URL from wrangler output"
fi
# Use ::set-output to avoid secret masking issues
echo "deployment-url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT"
e2e-tests:
runs-on: ubuntu-latest
needs: [preview-api, preview-web]
if: needs.preview-api.result == 'success' && needs.preview-web.result == 'success'
timeout-minutes: 10
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: "package.json"
- uses: pnpm/action-setup@v4
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
- uses: actions/cache@v5
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
working-directory: packages/web
run: npx playwright install --with-deps chromium
- name: Validate E2E secrets
run: |
if [ -z "${{ secrets.E2E_AUTH_SECRET }}" ]; then
echo "::error::E2E_AUTH_SECRET is not configured in GitHub Actions secrets."
echo "To set it up:"
echo " 1. Generate a 32+ character random secret: openssl rand -base64 32"
echo " 2. Add it to GitHub Actions secrets as E2E_AUTH_SECRET"
echo " 3. Add the same secret to Doppler: doppler secrets set E2E_AUTH_SECRET --project apps --config prv_append"
echo " (The Doppler sync step will deploy it to Cloudflare)"
exit 1
fi
echo "✓ E2E_AUTH_SECRET is configured"
- name: Run Playwright E2E tests
working-directory: packages/web
env:
E2E_AUTH_SECRET: ${{ secrets.E2E_AUTH_SECRET }}
PLAYWRIGHT_BASE_URL: ${{ needs.preview-web.outputs.deployment-url }}
PLAYWRIGHT_API_URL: ${{ needs.preview-api.outputs.deployment-url }}
run: pnpm test:e2e
- name: Upload Playwright report
uses: actions/upload-artifact@v6
if: always()
with:
name: playwright-report
path: packages/web/playwright-report/
retention-days: 14
comment-preview-urls:
runs-on: ubuntu-latest
needs: [preview-api, preview-web, e2e-tests]
if: always() && (needs.preview-api.result == 'success' || needs.preview-web.result == 'success')
permissions:
pull-requests: write
steps:
- name: Comment PR with preview URLs
uses: actions/github-script@v8
with:
script: |
const apiUrl = '${{ needs.preview-api.outputs.deployment-url }}';
const webUrl = '${{ needs.preview-web.outputs.deployment-url }}';
const e2eResult = '${{ needs.e2e-tests.result }}';
let body = '## 🚀 Preview Deployments\n\n';
if (apiUrl) {
body += `**API**: ${apiUrl}\n`;
} else {
body += '**API**: ❌ Deployment failed\n';
}
if (webUrl) {
body += `**Web**: ${webUrl}\n`;
} else {
body += '**Web**: ❌ Deployment failed\n';
}
body += '\n### E2E Tests\n';
if (e2eResult === 'success') {
body += '✅ Passed\n';
} else if (e2eResult === 'failure') {
body += '❌ Failed (see artifacts for report)\n';
} else if (e2eResult === 'skipped') {
body += '⏭️ Skipped\n';
} else {
body += `⚠️ ${e2eResult}\n`;
}
// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('🚀 Preview Deployments')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});
}