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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Bug report
about: Create a report to help us improve
title: Bug report
labels: bug
assignees: ''
assignees: ""
---

**Describe the bug** A clear and concise description of what the bug is.
Expand Down
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
name: Feature request
about: Suggest an idea or voice a concern
title: 'Feature request: '
title: "Feature request: "
labels: enhancement
assignees: ''
assignees: ""
---

**Is your feature request related to a problem? Please describe.** A clear and
Expand Down
40 changes: 0 additions & 40 deletions .github/workflows/deploy.yml

This file was deleted.

4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ coverage/
cov.lcov
.idea
# Ignore fresh build directory
_fresh/
_fresh/
# Claude Code working files
.claude/
96 changes: 96 additions & 0 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Deploying to Modern Deno Deploy

This project ships via **modern Deno Deploy**
([console.deno.com](https://console.deno.com)) using GitHub integration — there
is **no GitHub Actions deploy job**. Deno Deploy handles the build itself when
commits land on the connected branch.

> **Deno Deploy Classic shuts down 2026-07-20.** This repo has been migrated off
> `deployctl@v1`. The classic project on `dash.deno.com` is not
> auto-transferred; follow the runbook below before that date.

## Project settings to configure in console.deno.com

| Setting | Value |
| --------------- | -------------------------------------------- |
| Build command | `deno task build` |
| Install command | _(none — Deno fetches imports during build)_ |
| Entrypoint | `main.ts` |
| Root directory | `.` |

The optional `deploy` block in [`deno.json`](deno.json) (`project: "denodevs"`,
`entrypoint: "./main.ts"`, …) is the source of truth for include/exclude; the
dashboard can read these as defaults.

## Environment variables

These must be configured in console.deno.com — the application reads them at
module load (`utils/config.ts`) and will fail to boot if any required value is
missing.

### Required at **runtime** (production)

| Variable | Purpose |
| ------------------------------------------- | --------------------------------------------------------------- |
| `SITE_BASE_URL` | Canonical site URL used for OAuth redirects, signed links, etc. |
| `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` | GitHub OAuth app for developer sign-in. |
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | Google OAuth app for developer sign-in. |
| `RESEND_API_KEY` | Resend API key for transactional email. |
| `ADMIN_USERNAME` / `ADMIN_PASSWORD` | HTTP Basic credentials for `/admin/*`. |
| `USE_SECURE_COOKIES` | `true` in production (required for `Secure` flag). |
| `CLICKY_SITE_ID` | Clicky analytics site ID. |

### Required at **build time**

`utils/config.ts` runs `assertExists` on several env vars at module load, and
the build evaluates that module. Set these in the Deno Deploy project's **Build
Environment** with placeholder values (the real secrets bind at runtime):

```
SITE_BASE_URL = http://localhost:8000
GITHUB_CLIENT_ID = build
GITHUB_CLIENT_SECRET = build
GOOGLE_CLIENT_ID = build
GOOGLE_CLIENT_SECRET = build
CLICKY_SITE_ID = build
```

(Use the same set the old `.github/workflows/deploy.yml` was passing — they were
always placeholders, never real secrets.)

## Modern Deploy gotchas to know about

- **2-region cap** on modern Deploy (vs Classic's 6). Pick the regions closest
to the user base.
- **Deno Deploy Classic Queues are unsupported** in modern Deploy. This app uses
no `Deno.cron` and no queues, so we're unaffected — but don't add either
expecting it to work.
- **KV data is not automatically migrated.** See the cutover runbook below.

## Cutover runbook (one-time)

Execute these steps against the live environment after this branch lands.

1. Sign into [console.deno.com](https://console.deno.com) with the account that
owns the Classic project.
2. Create a new **organization**.
3. Create a new **project**, connect the `denodevs.com` GitHub repo, point at
the production branch.
4. Configure the build command (`deno task build`), entrypoint (`main.ts`), and
the env vars above.
5. Trigger a deploy from a **staging branch** first. You'll need to temporarily
add the preview `*.deno.net` URL to the GitHub and Google OAuth apps' allowed
callback URLs to test sign-in end-to-end.
6. **KV data migration**: email `support@deno.com` to request a KV copy from the
Classic project to the new one. There is no self-serve path. Do this
**before** flipping DNS, and verify a few keys (`developers`,
`employers_by_email`, `developer_sessions`) post-copy.
7. Once the new deploy passes a smoke test on its `.deno.net` URL, add the
production custom domain in console.deno.com, set the `_acme-challenge`
CNAME, then update the apex/CNAME DNS to point at modern Deploy. DNS
propagation can take up to 48h.
8. During the cutover window, keep **both** OAuth callback URLs (Classic and
modern) registered with GitHub and Google. Remove the Classic URLs after DNS
settles.
9. Smoke test once more on the production domain. Once green, **archive** (don't
delete) the Classic project. Classic shuts down 2026-07-20 regardless.
58 changes: 40 additions & 18 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,48 +1,70 @@
{
"compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" },
"compilerOptions": {
"lib": [
"dom",
"dom.asynciterable",
"dom.iterable",
"deno.ns",
"deno.unstable"
],
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"exclude": ["cov/", "**/_fresh/*"],
"fmt": { "exclude": ["static/styles.gen.css", "styles/open-props"] },
"fmt": {
"exclude": ["static/styles.gen.css", "static/*.svg", "styles/open-props"]
},
"imports": {
"@/": "./",
"$fresh/": "https://deno.land/x/fresh@1.7.3/",
"$gfm": "https://deno.land/x/gfm@0.2.4/mod.ts",
"@std/assert": "jsr:@std/assert@^1.0",
"@std/collections": "jsr:@std/collections@^1.1",
"@std/datetime": "jsr:@std/datetime@^0.225",
"@std/dotenv": "jsr:@std/dotenv@^0.225",
"@std/http": "jsr:@std/http@^1.1",
"@std/ulid": "jsr:@std/ulid@^1.0",
"basic_auth": "https://deno.land/x/basic_auth@v1.1.1/mod.ts",
"browserslist": "npm:browserslist@^4.24.4",
"case": "https://deno.land/x/case@2.1.1/mod.ts",
"chart.js": "https://esm.sh/stable/chart.js@4.3.0/auto?target=es2022",
"countries": "https://deno.land/x/countries@v1.1.2/mod.ts",
"fast-blurhash": "npm:fast-blurhash@^1.1.4",
"gravatar": "npm:gravatar@^1.8.2",
"kv_insights": "https://deno.land/x/deno_kv_insights@v0.8.4-beta/mod.ts",
"kv_insights/": "https://deno.land/x/deno_kv_insights@v0.8.4-beta/",
"kv_oauth": "https://deno.land/x/deno_kv_oauth@v0.10.0/mod.ts",
"kv_oauth/": "https://deno.land/x/deno_kv_oauth@v0.10.0/",
"kv_oauth": "jsr:@deno/kv-oauth@^0.11",
"lightningcss": "npm:lightningcss@^1.29.1",
"marked": "npm:marked@^15.0.6",
"preact": "https://esm.sh/preact@10.22.0",
"preact/": "https://esm.sh/preact@10.22.0/",
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.2",
"@preact/signals": "https://esm.sh/*@preact/signals@1.2.2",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1",
"preact": "npm:preact@^10.29.1",
"@preact/signals": "npm:@preact/signals@^2.9.0",
"react": "https://esm.sh/preact@10.19.2/compat",
"react-dom": "https://esm.sh/preact@10.19.2/compat",
"react/jsx-runtime": "https://esm.sh/preact@10.19.2/compat",
"std/": "https://deno.land/std@0.208.0/",
"stripe": "./stripe.ts"
"stripe": "./stripe.ts",
"fresh": "jsr:@fresh/core@^2.3.3"
},
"lint": {
"rules": {
"tags": ["fresh", "recommended"],
"exclude": ["react-no-danger"]
}
},
"nodeModulesDir": "none",
"deploy": {
"project": "denodevs",
"entrypoint": "./main.ts",
"include": ["./"],
"exclude": ["cov/", "_fresh/", ".github/"]
},
"lint": { "rules": { "tags": ["fresh", "recommended"] } },
"lock": false,
"pkgx": "deno.land^2.1.1 github.com/evilmartians/lefthook^1.5.0",
"tasks": {
"db:dump": "deno run --allow-read --allow-env tools/dump_kv.ts",
"db:seed": "deno run --allow-read --allow-env --allow-net tools/seed_submissions.ts",
"db:reset": "deno run --allow-read --allow-env tools/reset_kv.ts",
"start": "deno run -A --env --watch=styles/,routes/,utils/ dev.ts",
"start": "deno run -A --env --watch=styles/,routes/,utils/ --watch-exclude=_fresh/ dev.ts",
"test": "KV_PATH=:memory: SITE_BASE_URL=http://localhost:8000 CLICKY_SITE_ID=site_id GITHUB_CLIENT_ID=test GITHUB_CLIENT_SECRET=test GOOGLE_CLIENT_ID=test GOOGLE_CLIENT_SECRET=test deno test -A --coverage",
"ok": "deno fmt --check && deno lint && deno check main.ts && deno task test",
"cov": "deno coverage coverage --lcov --exclude='.tsx' --output=cov.lcov",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts",
"preview": "deno serve -A _fresh/server.js",
"update:fresh": "deno run -A -r https://fresh.deno.dev/update ."
},
"unstable": ["kv"]
Expand Down
Loading
Loading