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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .repos/alchemy-effect/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alchemy/test/**
137 changes: 137 additions & 0 deletions .repos/alchemy-effect/.github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Deploy Website

on:
workflow_dispatch:
push:
branches:
- main
paths:
- "website/**"
- "packages/alchemy/**"
- "bun.lock"
- ".github/workflows/deploy.yml"
pull_request:
types:
- opened
- reopened
- synchronize
- closed
paths:
- "website/**"
- "packages/alchemy/**"
- "bun.lock"
- ".github/workflows/deploy.yml"

concurrency:
group: deploy-website-${{ github.ref }}
cancel-in-progress: false

env:
STAGE: ${{ github.event_name == 'pull_request' && format('pr-{0}',
github.event.number) || (github.ref == 'refs/heads/main' && 'prod' ||
github.ref_name) }}

jobs:
deploy:
if: ${{ github.event.action != 'closed' }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# Mint a token for the alchemy-version-bot GitHub App so the PR
# preview comment (created via GitHub.Comment in alchemy.run.ts)
# posts under the bot's identity instead of `github-actions[bot]`.
# Same App as release.yml — its installation scopes already cover
# PR comments.
- name: Generate bot token
id: bot-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.ALCHEMY_VERSION_BOT_ID }}
private-key: ${{ secrets.ALCHEMY_VERSION_BOT_PRIVATE_KEY }}

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0

- name: Install dependencies
run: bun install

# Build the website ahead of `alchemy deploy`. The
# Cloudflare.StaticSite resource declares the same `bun run build`
# in its config and runs it as part of the create lifecycle, but
# on update plans alchemy reads `dist/` during plan.diff before
# the embedded Build resource gets a chance to (re)run, causing
# a NotFound on a fresh CI checkout. Building here makes the
# workflow correct under both create and update plans.
- name: Build website
working-directory: website
run: bun run build

- name: Deploy
working-directory: website
run: bun alchemy deploy --stage ${{ env.STAGE }} --yes
env:
# Alchemy's Cloudflare auth provider accepts either a scoped
# API token (CLOUDFLARE_API_TOKEN) or the legacy global API
# key paired with email (CLOUDFLARE_API_KEY +
# CLOUDFLARE_EMAIL). We use the latter here so CI matches
# the credentials in Doppler / .env, where only the admin
# global key is provisioned.
CLOUDFLARE_API_KEY: ${{ secrets.ADMIN_CLOUDFLARE_API_KEY }}
CLOUDFLARE_EMAIL: ${{ secrets.ADMIN_CLOUDFLARE_EMAIL }}
CLOUDFLARE_ACCOUNT_ID: ${{ env.STAGE == 'prod' &&
secrets.PROD_CLOUDFLARE_ACCOUNT_ID ||
secrets.TEST_CLOUDFLARE_ACCOUNT_ID }}
PULL_REQUEST: ${{ github.event.number }}
# On `pull_request` events GitHub Actions' built-in
# `GITHUB_SHA` is the synthetic merge commit (PR head merged
# into base) — that SHA never shows up in the PR's git log.
# `GITHUB_SHA` is also a reserved env var, so a step-level
# override is silently ignored. Pass the PR head SHA under
# a non-reserved name and read it from alchemy.run.ts.
BUILD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
GITHUB_TOKEN: ${{ steps.bot-token.outputs.token }}

cleanup:
runs-on: ubuntu-latest
if:
${{ github.event_name == 'pull_request' && github.event.action == 'closed'
}}
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Generate bot token
id: bot-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.ALCHEMY_VERSION_BOT_ID }}
private-key: ${{ secrets.ALCHEMY_VERSION_BOT_PRIVATE_KEY }}

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0

- name: Install dependencies
run: bun install

- name: Safety Check
run: |-
if [ "${{ env.STAGE }}" = "prod" ]; then
echo "ERROR: Cannot destroy prod environment in cleanup job"
exit 1
fi

- name: Destroy Preview Environment
working-directory: website
run: bun alchemy destroy --stage ${{ env.STAGE }} --yes
env:
CLOUDFLARE_API_KEY: ${{ secrets.ADMIN_CLOUDFLARE_API_KEY }}
CLOUDFLARE_EMAIL: ${{ secrets.ADMIN_CLOUDFLARE_EMAIL }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.TEST_CLOUDFLARE_ACCOUNT_ID }}
PULL_REQUEST: ${{ github.event.number }}
GITHUB_TOKEN: ${{ steps.bot-token.outputs.token }}
180 changes: 180 additions & 0 deletions .repos/alchemy-effect/.github/workflows/pr-package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
name: pr-package

# Publishes alchemy + better-auth + pr-package tarballs to the pr-package
# service at pkg.ing on every push to main and every PR sync. On PR close,
# deletes every tag we ever assigned for that PR so the underlying tarballs
# become orphaned and the bucket cleans them up.

on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened, closed]

permissions:
contents: read
pull-requests: write

env:
PR_PACKAGE_HOST: pkg.ing
NODE_VERSION: "24"

jobs:
# ── Publish on push-to-main and PR sync. ──────────────────────────────────
publish:
if: github.event_name == 'push' || (github.event_name == 'pull_request' &&
github.event.action != 'closed')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ env.NODE_VERSION }}

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest

- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-

- run: bun install

# Build everything: tsdown bundles alchemy (bin/ + cli lib) and tsc -b
# populates lib/ for the layer packages (better-auth, pr-package).
# Their tarballs ship both src/ and lib/, and consumers in non-bun
# runtimes resolve via the `import` condition → ./lib/index.js.
- run: bun run build:packages

- name: Compute tags
id: tags
env:
EVENT: ${{ github.event_name }}
# `head_ref` for PRs (source branch), `ref_name` for push (e.g. "main").
BRANCH: ${{ github.event_name == 'pull_request' && github.head_ref ||
github.ref_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
# PR builds run on the merge commit by default; tag the actual head sha
# so consumers can pin to a specific PR commit.
SHA: ${{ github.event_name == 'pull_request' &&
github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
short="${SHA:0:7}"
long="$SHA"
tags=("$short" "$long" "$BRANCH")
if [ "$EVENT" = "pull_request" ]; then
tags+=("pr-${PR_NUMBER}")
fi
json=$(printf '%s\n' "${tags[@]}" | jq -R . | jq -s -c .)
echo "tags=$json" >> "$GITHUB_OUTPUT"
echo "short=$short" >> "$GITHUB_OUTPUT"
echo "Tags: $json"

# PR builds expire after 1 week; pushes to main omit the header and
# fall back to the worker's defaultTtl (3 weeks).
- name: Publish alchemy
env:
TOKEN: ${{ secrets.PR_PACKAGE_TOKEN }}
TAGS: ${{ steps.tags.outputs.tags }}
TTL: ${{ github.event_name == 'pull_request' && '1 week' || '' }}
working-directory: packages/alchemy
run: |
set -euo pipefail
rm -f *.tgz
bun pm pack --destination .
tgz=$(ls *.tgz)
echo "Publishing $tgz with tags $TAGS ttl=${TTL:-default}"
curl -fsSL --show-error -X PUT \
"https://${PR_PACKAGE_HOST}/projects/alchemy/packages" \
-H "Authorization: Bearer ${TOKEN}" \
-H "X-Tags: ${TAGS}" \
${TTL:+-H "X-TTL: ${TTL}"} \
-H "Content-Type: application/gzip" \
--data-binary "@${tgz}"

- name: Publish better-auth
env:
TOKEN: ${{ secrets.PR_PACKAGE_TOKEN }}
TAGS: ${{ steps.tags.outputs.tags }}
TTL: ${{ github.event_name == 'pull_request' && '1 week' || '' }}
working-directory: packages/better-auth
run: |
set -euo pipefail
rm -f *.tgz
bun pm pack --destination .
tgz=$(ls *.tgz)
echo "Publishing $tgz with tags $TAGS ttl=${TTL:-default}"
curl -fsSL --show-error -X PUT \
"https://${PR_PACKAGE_HOST}/projects/@alchemy.run/better-auth/packages" \
-H "Authorization: Bearer ${TOKEN}" \
-H "X-Tags: ${TAGS}" \
${TTL:+-H "X-TTL: ${TTL}"} \
-H "Content-Type: application/gzip" \
--data-binary "@${tgz}"

- name: Publish pr-package
env:
TOKEN: ${{ secrets.PR_PACKAGE_TOKEN }}
TAGS: ${{ steps.tags.outputs.tags }}
TTL: ${{ github.event_name == 'pull_request' && '1 week' || '' }}
working-directory: packages/pr-package
run: |
set -euo pipefail
rm -f *.tgz
bun pm pack --destination .
tgz=$(ls *.tgz)
echo "Publishing $tgz with tags $TAGS ttl=${TTL:-default}"
curl -fsSL --show-error -X PUT \
"https://${PR_PACKAGE_HOST}/projects/@alchemy.run/pr-package/packages" \
-H "Authorization: Bearer ${TOKEN}" \
-H "X-Tags: ${TAGS}" \
${TTL:+-H "X-TTL: ${TTL}"} \
-H "Content-Type: application/gzip" \
--data-binary "@${tgz}"

# PR-only: leave a sticky comment with the install URLs pinned to this
# commit. Uses pkg.ing (the canonical short host), which serves both
# the pretty alias paths and the underlying /projects/:project/tags/:tag
# API directly.
- name: Generate bot token
if: github.event_name == 'pull_request'
id: bot-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.ALCHEMY_VERSION_BOT_ID }}
private-key: ${{ secrets.ALCHEMY_VERSION_BOT_PRIVATE_KEY }}

- name: Comment on PR
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ steps.bot-token.outputs.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
SHORT_SHA: ${{ steps.tags.outputs.short }}
run: |
set -euo pipefail
MARKER="<!-- pr-package-comment -->"
# Use `bun add <name>@<url>` instead of `bun add <url>`. Without an
# explicit name, bun's resolver hits a DependencyLoop bug when the
# consumer already has the same package name resolved from npm
# (oven-sh/bun#5789, oven-sh/bun#17946). The aliased form bypasses
# the buggy reconciliation path and works on fresh and existing
# projects alike.
BODY=$(printf '%s\n\nInstall the packages built from this commit:\n\n**alchemy**\n```sh\nbun add alchemy@https://pkg.ing/alchemy/%s\n```\n\n**@alchemy.run/better-auth**\n```sh\nbun add @alchemy.run/better-auth@https://pkg.ing/@alchemy.run/better-auth/%s\n```\n\n**@alchemy.run/pr-package**\n```sh\nbun add @alchemy.run/pr-package@https://pkg.ing/@alchemy.run/pr-package/%s\n```\n' "$MARKER" "$SHORT_SHA" "$SHORT_SHA" "$SHORT_SHA")

existing=$(gh api --paginate \
"repos/${REPO}/issues/${PR_NUMBER}/comments" \
--jq ".[] | select(.user.login == \"alchemy-version-bot[bot]\") | select(.body | startswith(\"${MARKER}\")) | .id" \
| head -1)

if [ -n "$existing" ]; then
gh api -X PATCH "repos/${REPO}/issues/comments/${existing}" -f body="$BODY"
else
gh api -X POST "repos/${REPO}/issues/${PR_NUMBER}/comments" -f body="$BODY"
fi
Loading
Loading