Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
NOTION_ACCESS_TOKEN=secret_sup3rdup3rs3cr37h3sh
NOTION_CLIMB_DATABASE_ID=r4nd0mh45hfr0mth3n0t10nw3bs17e
NOTION_GEAR_DATABASE_ID=r4nd0mh45hfr0mth3n0t10nw3bs17e3
NOTION_PEAK_DATABASE_ID=r4nd0mh45hfr0mth3n0t10nw3bs17e7
NOTION_PHOTO_DATABASE_ID=r4nd0mh45hfr0mth3n0t10nw3bs17e2
NOTION_TOKEN=secret_sup3rdup3rs3cr37h3sh
NOTION_CLIMBS_DB_ID=r4nd0mh45hfr0mth3n0t10nw3bs17e
NOTION_GEAR_DB_ID=r4nd0mh45hfr0mth3n0t10nw3bs17e3
NOTION_PEAKS_DB_ID=r4nd0mh45hfr0mth3n0t10nw3bs17e7
NOTION_PHOTOS_DB_ID=r4nd0mh45hfr0mth3n0t10nw3bs17e2

# Cron secret for manual triggers
CRON_SECRET=generate_with_openssl_rand_hex_32

# Photos API configuration
ALLOWED_WIDTHS=200,400,800,1600

# Cloudflare (optional, for R2 sync)
CLOUDFLARE_ACCOUNT_ID=your_account_id
CLOUDFLARE_API_TOKEN=your_api_token
R2_ACCESS_KEY_ID=your_r2_access_key_id
R2_SECRET_ACCESS_KEY=your_r2_secret_access_key
67 changes: 67 additions & 0 deletions .github/workflows/cleanup-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Cleanup Preview

on:
pull_request:
branches: [main]
types: [closed]

env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install wrangler
run: npm install -g wrangler

- name: Generate preview name from PR title
id: slug
run: |
TITLE="${{ github.event.pull_request.title }}"
WORD=$(echo "$TITLE" | sed -E 's/^(fix|feat|chore|docs|refactor|test|ci|style|perf|build|revert|rewrite|add|update|remove|delete)(\([^)]*\))?[:!]?\s*//i' | awk '{print $1}' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g' | cut -c1-12)
[ -z "$WORD" ] && WORD="pr${{ github.event.pull_request.number }}"
echo "name=climb-log-${WORD}-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT

- name: Delete preview worker
run: |
npx wrangler delete --name "${{ steps.slug.outputs.name }}" --force
continue-on-error: true

- name: Update bot comment
uses: actions/github-script@v7
with:
script: |
const marker = '<!-- climb-log-preview-bot -->';
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});

const existing = comments.find(c => c.body && c.body.includes(marker));

if (existing) {
const verb = context.payload.pull_request.merged ? 'merged' : 'closed';
const body = `${marker}\n🧹 Preview cleaned up — PR ${verb}!\n\n_The preview worker has been deleted._`;

await github.rest.issues.updateComment({
comment_id: existing.id,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
}
249 changes: 249 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
name: Deploy

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
pull-requests: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install dependencies
run: npm install

- name: Build
run: npm run build
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_CLIMBS_DB_ID: ${{ secrets.NOTION_CLIMBS_DB_ID }}
NOTION_PEAKS_DB_ID: ${{ secrets.NOTION_PEAKS_DB_ID }}
NOTION_GEAR_DB_ID: ${{ secrets.NOTION_GEAR_DB_ID }}
NOTION_PHOTOS_DB_ID: ${{ secrets.NOTION_PHOTOS_DB_ID }}

# Preview deployment for PRs
- name: Create Preview Deployment
if: github.event_name == 'pull_request'
id: preview-deployment
uses: actions/github-script@v7
with:
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.pull_request.head.sha,
environment: 'preview',
auto_merge: false,
required_contexts: [],
description: `Preview deployment for PR #${context.payload.pull_request.number}`
});
return deployment.data.id;
result-encoding: string

- name: generate preview name from pr title
if: github.event_name == 'pull_request'
id: slug
run: |
TITLE="${{ github.event.pull_request.title }}"
WORD=$(echo "$TITLE" | sed -E 's/^(fix|feat|chore|docs|refactor|test|ci|style|perf|build|revert|rewrite|add|update|remove|delete)(\([^)]*\))?[\:!]?\s*//i' | awk '{print $1}' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g' | cut -c1-12)
[ -z "$WORD" ] && WORD="pr${{ github.event.pull_request.number }}"
echo "name=climb-log-${WORD}-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT

- name: generate preview config
if: github.event_name == 'pull_request'
run: |
# Astro builds worker output to dist/server/ with static assets in dist/client/
# We need main pointing to the worker entry and assets to the client directory
# NOTE: Secrets are set via `wrangler secret put`, NOT in vars (which are plaintext)
jq --arg name "${{ steps.slug.outputs.name }}" '
.name = $name
| .main = "./dist/server/entry.mjs"
| .assets.directory = "./dist/client"
| .d1_databases[0].database_id = "c0e56b31-fbfd-46f9-992e-af369d9c2cb9"
| .d1_databases[0].database_name = "climb-log-db-preview"
| .vars.NOTION_CLIMBS_DB_ID = "${{ secrets.NOTION_CLIMBS_DB_ID }}"
| .vars.NOTION_PEAKS_DB_ID = "${{ secrets.NOTION_PEAKS_DB_ID }}"
| .vars.NOTION_GEAR_DB_ID = "${{ secrets.NOTION_GEAR_DB_ID }}"
| .vars.NOTION_PHOTOS_DB_ID = "${{ secrets.NOTION_PHOTOS_DB_ID }}"
| del(.env)
' wrangler.jsonc > wrangler-preview.jsonc

- name: Deploy to Preview
if: github.event_name == 'pull_request'
id: deploy-preview
run: |
START_TIME=$(date +%s)
set +e
npx wrangler deploy --config wrangler-preview.jsonc 2>&1
EXIT_CODE=$?
set -e
END_TIME=$(date +%s)
DEPLOY_TIME=$((END_TIME - START_TIME))
echo "deploy_time=${DEPLOY_TIME}s" >> $GITHUB_OUTPUT
echo "preview_url=https://${{ steps.slug.outputs.name }}.kylieski.workers.dev" >> $GITHUB_OUTPUT
exit $EXIT_CODE

- name: Set preview secrets
if: github.event_name == 'pull_request'
run: |
# Set secrets via wrangler secret put (not plaintext vars)
echo "${{ secrets.NOTION_TOKEN }}" | npx wrangler secret put NOTION_TOKEN --config wrangler-preview.jsonc
echo "${{ secrets.CRON_SECRET }}" | npx wrangler secret put CRON_SECRET --config wrangler-preview.jsonc

- name: apply migrations to preview db
if: github.event_name == 'pull_request'
run: |
npx wrangler d1 migrations apply climb-log-db-preview --remote --config wrangler-preview.jsonc
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

- name: seed preview db
if: github.event_name == 'pull_request'
run: |
npx tsx db/seed.ts
npx wrangler d1 execute climb-log-db-preview --remote --file=db/seed.sql
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

- name: Update Preview Deployment Status
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const deploymentId = ${{ steps.preview-deployment.outputs.result }};
const previewUrl = '${{ steps.deploy-preview.outputs.preview_url }}';

await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deploymentId,
state: 'success',
environment_url: previewUrl,
description: 'Preview deployment successful'
});

- name: comment preview url
if: github.event_name == 'pull_request' && !startsWith(github.head_ref, 'blog/') && !startsWith(github.head_ref, 'post/')
uses: actions/github-script@v7
with:
script: |
const marker = '<!-- climb-log-preview-bot -->';
const isNew = context.payload.action === 'opened';
const verb = isNew ? 'deployed' : 'updated';
const emoji = isNew ? '🚀' : '⛰️';
const dbUrl = 'https://dash.cloudflare.com/bb52c0e5f3a93fc72779915d7dc6982b/workers/d1/databases/c0e56b31-fbfd-46f9-992e-af369d9c2cb9';
const previewUrl = '${{ steps.deploy-preview.outputs.preview_url }}';
const deployTime = '${{ steps.deploy-preview.outputs.deploy_time }}';
const commitSha = '${{ github.event.pull_request.head.sha }}'.substring(0, 7);

const body = `${marker}\n${emoji} Preview ${verb}!\n\n| | |\n|------|------|\n| 🔗 Preview | ${previewUrl} |\n| 📊 Database | [climb-log-db-preview](${dbUrl}) |\n| ⏱️ Deploy Time | ${deployTime} |\n| 📝 Commit | \`${commitSha}\` |\n\n_This preview updates automatically with each push._`;

const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});

const existing = comments.find(c => c.body && c.body.includes(marker));

if (existing) {
await github.rest.issues.updateComment({
comment_id: existing.id,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
} else {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
}

# Production deployment on push to main
- name: Create Production Deployment
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: prod-deployment
uses: actions/github-script@v7
with:
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha,
environment: 'production',
auto_merge: false,
required_contexts: [],
description: 'Production deployment'
});
return deployment.data.id;
result-encoding: string

- name: Deploy to Production
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: deploy-prod
run: |
npx wrangler deploy
echo "prod_url=https://climb-log.kylieski.workers.dev" >> $GITHUB_OUTPUT

- name: Update Production Deployment Status
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/github-script@v7
with:
script: |
const deploymentId = ${{ steps.prod-deployment.outputs.result }};

await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deploymentId,
state: 'success',
environment_url: 'https://climb-log.kylieski.workers.dev',
description: 'Production deployment successful'
});

- name: Handle Deployment Failure
if: failure()
uses: actions/github-script@v7
with:
script: |
let deploymentId;
if (context.eventName === 'pull_request') {
deploymentId = '${{ steps.preview-deployment.outputs.result }}';
} else {
deploymentId = '${{ steps.prod-deployment.outputs.result }}';
}

if (deploymentId && deploymentId !== '') {
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: parseInt(deploymentId),
state: 'failure',
description: 'Deployment failed'
});
}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ yarn-error.log*
/build
.next/
.vercel
/dist/

# astro
.astro/

# wrangler / cloudflare
.wrangler/

# misc
.DS_Store
Expand Down
Loading
Loading