Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ba6231b
feat(repo): deploy the site from one command
systemfsoftware-maker Oct 6, 2026
f16479b
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
91a262e
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
64e7664
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
e7a62a1
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
a88a620
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
a8f0f5e
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
fec437a
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
5d97489
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
de168f5
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
15dc522
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
69eadec
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
d71d019
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
38d569a
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
8f75bfb
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
4d82eac
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
6ce8253
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
96345ee
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
761e5b3
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
942b206
chore(repo): merge lake1/nix-sandbox into lake1/deploy
systemfsoftware-maker Oct 7, 2026
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ applications — both are workspace globs in `pnpm-workspace.yaml`. Turbo declar

## CI

- This repo is a template and holds no credentials; CI proves everything locally. Gate: review of the workflow diff; a job that needs a secret carries `if: ${{ !github.event.repository.is_template }}`.
- Every job runs on GitHub-hosted `ubuntu-latest` runners. This repository is public, and the org's self-hosted fleet runner group admits only private repositories (fork safety), so a `[self-hosted, systemfsoftware-runner, *]` job here queues forever. Gate: review of the workflow diff.
- Heavy suites split across parallel hosted jobs instead of a larger runner: Linux `check:ci` runs as one job per part (format, lint, typecheck, test, dist), and mutation on `main` runs one job per package. Gate: review of the workflow diff.

Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,25 @@ pnpm build
pnpm check:ci
```

### 4. Run It Locally

```bash
pnpm dev # the whole app at http://localhost:1337, Alchemy's local emulation, no cloud
pnpm journeys # the end-to-end journeys in a real browser against pnpm dev
```

### 5. Deploy

Your copy deploys to your own Cloudflare account; the template holds no credentials. Set `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`, and optionally `SITE_DOMAIN` (a hostname in a zone on that account) to serve production there instead of on `workers.dev`:

```bash
pnpm run deploy # stage prod
ALCHEMY_STAGE=pr-12 pnpm run deploy # a preview stage
ALCHEMY_STAGE=pr-12 pnpm run destroy
```

`pnpm run deploy`, not `pnpm deploy`: the bare form is pnpm's own built-in command.

---

## 🚦 Verification Gates
Expand Down
30 changes: 23 additions & 7 deletions apps/site/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import * as Alchemy from 'alchemy'
import * as Cloudflare from 'alchemy/Cloudflare'
import * as Effect from 'effect/Effect'
import * as Layer from 'effect/Layer'

import { siteWorker } from './site-worker.ts'

export const Site = Cloudflare.Website.Vite('Site', {
main: siteWorker.main,
compatibility: { date: siteWorker.compatibilityDate, flags: [...siteWorker.compatibilityFlags] },
dev: { port: 1337, strictPort: true },
})
export const isCloudStage = (stage: string): boolean => stage === 'prod' || /^pr-\d+$/.test(stage)

const productionDomain = (stage: string): { readonly domain?: string } => {
const domain = process.env['SITE_DOMAIN']
return stage === 'prod' && domain !== undefined && domain !== '' ? { domain } : {}
}

export const Site = Cloudflare.Website.Vite(
'Site',
Effect.map(Alchemy.Stage, (stage) => ({
main: siteWorker.main,
compatibility: { date: siteWorker.compatibilityDate, flags: [...siteWorker.compatibilityFlags] },
dev: { port: 1337, strictPort: true },
...productionDomain(stage),
})),
)

const stateOfStage = Layer.unwrap(
Effect.map(Alchemy.Stage, (stage) => isCloudStage(stage) ? Cloudflare.state() : Alchemy.localState()),
)

export default Alchemy.Stack(
'Endgame',
{ providers: Cloudflare.providers(), state: Alchemy.localState() },
{ providers: Cloudflare.providers(), state: stateOfStage },
Effect.gen(function*() {
const site = yield* Site
return { url: site.url }
return { url: site.url, workerName: site.workerName }
}),
)
18 changes: 18 additions & 0 deletions bin/cloud
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Run a Cloudflare-facing command (an alchemy deploy, destroy or state read)
# inside the sandbox. This is the one place the Cloudflare credentials enter a
# sandbox, and the one list of hosts such a command may reach: Cloudflare's
# API, and the account's workers.dev hosts, where alchemy's state store
# answers. Telemetry stays off, so nothing else needs to be reachable.
set -euo pipefail

export DO_NOT_TRACK=1
exec sandbox \
--allow-host api.cloudflare.com \
--allow-host '*.workers.dev' \
--pass-env CLOUDFLARE_API_TOKEN \
--pass-env CLOUDFLARE_ACCOUNT_ID \
--pass-env SITE_DOMAIN \
--pass-env DO_NOT_TRACK \
--pass-env CI \
-- "$@"
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"check:ci": "s=0; pnpm format:check || s=1; pnpm check:sfs-sources || s=1; TURBO_CONCURRENCY=${TURBO_CONCURRENCY:-100%} pnpm gate:tasks || s=1; pnpm gate:dist || s=1; pnpm test:sandbox || s=1; exit $s",
"check:sfs-sources": "sandbox --allow-host jsr.io -- ./scripts/check-sfs-sources.ts",
"clean": "sandbox -- turbo clean",
"deploy": "./bin/cloud pnpm --filter @endgame/site exec alchemy deploy --stage \"${ALCHEMY_STAGE:-prod}\" --no-input --yes",
"destroy": "./bin/cloud pnpm --filter @endgame/site exec alchemy destroy --stage \"${ALCHEMY_STAGE:?set ALCHEMY_STAGE, e.g. pr-<N>}\" --no-input --yes",
"format": "./bin/dprint fmt",
"format:check": "./bin/dprint check",
"gate:dist": "sandbox -- turbo --concurrency=${TURBO_CONCURRENCY:-100%} build",
Expand Down
Loading