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
67 changes: 67 additions & 0 deletions .github/workflows/dashboard-deploy-production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Dashboard — production deploy

on:
push:
branches: [main]
paths:
- "apps/dashboard/**"
- "scripts/generate-wrangler-dashboard-config.mjs"
- "scripts/run-d1-migrations.mjs"
- ".github/workflows/dashboard-deploy-production.yml"
workflow_dispatch:

concurrency:
group: dashboard-production
cancel-in-progress: false

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Generate Wrangler config
run: node scripts/generate-wrangler-dashboard-config.mjs apps/dashboard/wrangler.deploy.json
env:
WRANGLER_COMPATIBILITY_DATE: ${{ vars.WRANGLER_COMPATIBILITY_DATE }}
DASHBOARD_PROD_WORKER_NAME: ${{ secrets.DASHBOARD_PROD_WORKER_NAME }}
DASHBOARD_PROD_D1_DATABASE_NAME: ${{ secrets.DASHBOARD_PROD_D1_DATABASE_NAME }}
DASHBOARD_PROD_D1_DATABASE_ID: ${{ secrets.DASHBOARD_PROD_D1_DATABASE_ID }}
DASHBOARD_PROD_KV_ID: ${{ secrets.DASHBOARD_PROD_KV_ID }}
DASHBOARD_PROD_KV_PREVIEW_ID: ${{ secrets.DASHBOARD_PROD_KV_PREVIEW_ID }}
DASHBOARD_PROD_R2_BUCKET: ${{ secrets.DASHBOARD_PROD_R2_BUCKET }}
DASHBOARD_PROD_R2_PREVIEW_BUCKET: ${{ secrets.DASHBOARD_PROD_R2_PREVIEW_BUCKET }}

# @cloudflare/vite-plugin resolves `wrangler.jsonc` during `vite build` (deploy:ci).
- name: Stage Wrangler config for Vite
working-directory: apps/dashboard
run: cp wrangler.deploy.json wrangler.jsonc

- name: Apply D1 migrations (remote)
working-directory: apps/dashboard
env:
WRANGLER_CONFIG: wrangler.deploy.json
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: node --experimental-strip-types ../../scripts/run-d1-migrations.mjs DB --remote

- name: Build and deploy Worker
working-directory: apps/dashboard
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: pnpm run deploy:ci
2 changes: 1 addition & 1 deletion .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"*.{js,jsx,ts,tsx,json,jsonc,css,scss,md,mdx}": [
"*.{js,jsx,ts,mts,tsx,json,jsonc,css,scss,md,mdx}": [
"biome check --write --no-errors-on-unmatched"
]
}
5 changes: 5 additions & 0 deletions apps/dashboard/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ dist-ssr
.vinxi
__unconfig*
todos.json

# Local Wrangler config (copy from wrangler.jsonc.example) and CI-generated deploy configs
wrangler.jsonc
wrangler.dev.jsonc
wrangler.deploy.json
4 changes: 2 additions & 2 deletions apps/dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ Copy `.dev.vars.example` to `.dev.vars` and fill in the real values. GitHub comm

## Cloudflare Wrangler Configuration

The live `wrangler.jsonc` is ignored by git, so copy `wrangler.jsonc.example` to `wrangler.jsonc` before you deploy or connect to Cloudflare resources locally.
Copy `wrangler.jsonc.example` to **`wrangler.dev.jsonc`** (gitignored). `pnpm dev` and local D1 migrations use that file so local bindings stay separate from deploy config. For a local production build or `wrangler deploy` without `--config`, you can also maintain **`wrangler.jsonc`** (gitignored).

Fill in the Cloudflare-specific values in `wrangler.jsonc`:
Fill in the Cloudflare-specific values:

- `account_id`
- `d1_databases[].database_id`
Expand Down
7 changes: 4 additions & 3 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
"check": "biome check",
"check-types": "tsc --noEmit",
"migrate": "pnpm run migrate:local",
"migrate:local": "node ../../scripts/run-d1-migrations.mjs DB --local",
"migrate:remote": "node ../../scripts/run-d1-migrations.mjs DB --remote",
"deploy": "pnpm run build && wrangler deploy"
"migrate:local": "node --experimental-strip-types ../../scripts/run-d1-migrations.mjs DB --local",
"migrate:remote": "node --experimental-strip-types ../../scripts/run-d1-migrations.mjs DB --remote",
"deploy": "pnpm run build && wrangler deploy",
"deploy:ci": "pnpm run build && wrangler deploy --config wrangler.deploy.json"
},
"dependencies": {
"@cloudflare/vite-plugin": "^1.31.2",
Expand Down
6 changes: 5 additions & 1 deletion apps/dashboard/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"extends": "@diffkit/typescript-config/start-app.json",
"include": ["**/*.ts", "**/*.tsx"],
"include": [
"**/*.ts",
"**/*.tsx",
"../../scripts/resolve-wrangler-config-path.mts"
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
Expand Down
45 changes: 28 additions & 17 deletions apps/dashboard/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { existsSync, readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { cloudflare } from "@cloudflare/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import { devtools } from "@tanstack/devtools-vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { resolveWranglerConfigPath } from "../../scripts/resolve-wrangler-config-path.mts";
import {
getSharedWranglerStatePath,
isWorktreeCheckout,
} from "../../scripts/shared-worktree-paths.mjs";

const dashboardRoot = new URL(".", import.meta.url);
const dashboardRootDir = fileURLToPath(dashboardRoot);
const worktreePersistState = isWorktreeCheckout(dashboardRoot)
? { persistState: { path: getSharedWranglerStatePath(dashboardRoot) } }
: {};
Expand Down Expand Up @@ -148,22 +151,30 @@ export default {};`;
const useCloudflareRemoteBindings =
process.env.CI !== "true" && process.env.VITEST !== "true";

const config = defineConfig(({ command }) => ({
server: command === "serve" ? getTunnelServerConfig() : undefined,
plugins: [
devtools(),
shikiSSRStub(),
betterAuthDialectStub(),
cloudflare({
viteEnvironment: { name: "ssr" },
...worktreePersistState,
remoteBindings: useCloudflareRemoteBindings,
}),
tsconfigPaths({ projects: ["./tsconfig.json"] }),
tailwindcss(),
tanstackStart(),
viteReact(),
],
}));
const config = defineConfig(({ command, mode }) => {
const wranglerConfigPath = resolveWranglerConfigPath({
command,
mode,
rootDir: dashboardRootDir,
});
return {
server: command === "serve" ? getTunnelServerConfig() : undefined,
plugins: [
devtools(),
shikiSSRStub(),
betterAuthDialectStub(),
cloudflare({
viteEnvironment: { name: "ssr" },
...worktreePersistState,
remoteBindings: useCloudflareRemoteBindings,
...(wranglerConfigPath ? { configPath: wranglerConfigPath } : {}),
}),
tsconfigPaths({ projects: ["./tsconfig.json"] }),
tailwindcss(),
tanstackStart(),
viteReact(),
],
};
});

export default config;
56 changes: 0 additions & 56 deletions apps/dashboard/wrangler.jsonc

This file was deleted.

20 changes: 20 additions & 0 deletions apps/dashboard/wrangler.jsonc.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
// Local setup:
// cp wrangler.jsonc.example wrangler.dev.jsonc
// Vite dev / Vitest load `wrangler.dev.jsonc` (gitignored). Fill in placeholders there.
//
// Optional — only if you run `vite build` or `wrangler deploy` without `--config`:
// cp wrangler.jsonc.example wrangler.jsonc
//
// GitHub Actions (production) secrets — map into generated `wrangler.deploy.json`:
// CLOUDFLARE_API_TOKEN
// CLOUDFLARE_ACCOUNT_ID
// DASHBOARD_PROD_WORKER_NAME
// DASHBOARD_PROD_D1_DATABASE_NAME
// DASHBOARD_PROD_D1_DATABASE_ID
// DASHBOARD_PROD_KV_ID
// DASHBOARD_PROD_KV_PREVIEW_ID (optional; defaults to DASHBOARD_PROD_KV_ID)
// DASHBOARD_PROD_R2_BUCKET
// DASHBOARD_PROD_R2_PREVIEW_BUCKET
//
// Optional repository variables:
// WRANGLER_COMPATIBILITY_DATE
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "diffkit",
Expand Down
82 changes: 82 additions & 0 deletions scripts/generate-wrangler-dashboard-config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { writeFileSync } from "node:fs";
import { resolve } from "node:path";

const [, , outArg] = process.argv;

if (!outArg) {
console.error(
"Usage: node scripts/generate-wrangler-dashboard-config.mjs <outFile>"
);
process.exit(1);
}

const outFile = resolve(outArg);
const compatibilityDate =
process.env.WRANGLER_COMPATIBILITY_DATE || "2025-09-02";

const name = process.env.DASHBOARD_PROD_WORKER_NAME;
const d1Name = process.env.DASHBOARD_PROD_D1_DATABASE_NAME;
const d1Id = process.env.DASHBOARD_PROD_D1_DATABASE_ID;
const kvId = process.env.DASHBOARD_PROD_KV_ID;
const kvPreviewId =
process.env.DASHBOARD_PROD_KV_PREVIEW_ID || process.env.DASHBOARD_PROD_KV_ID;
const r2Bucket = process.env.DASHBOARD_PROD_R2_BUCKET;
const r2Preview = process.env.DASHBOARD_PROD_R2_PREVIEW_BUCKET;

for (const [key, val] of Object.entries({
DASHBOARD_PROD_WORKER_NAME: name,
DASHBOARD_PROD_D1_DATABASE_NAME: d1Name,
DASHBOARD_PROD_D1_DATABASE_ID: d1Id,
DASHBOARD_PROD_KV_ID: kvId,
DASHBOARD_PROD_R2_BUCKET: r2Bucket,
DASHBOARD_PROD_R2_PREVIEW_BUCKET: r2Preview,
})) {
if (!val) {
console.error(`Missing required environment variable: ${key}`);
process.exit(1);
}
}

const config = {
$schema: "node_modules/wrangler/config-schema.json",
compatibility_date: compatibilityDate,
compatibility_flags: [
"nodejs_compat",
"no_handle_cross_request_promise_resolution",
],
main: "src/entry-worker.ts",
observability: {
logs: { enabled: true, invocation_logs: true },
traces: { enabled: false },
},
durable_objects: {
bindings: [{ name: "SIGNAL_RELAY", class_name: "SignalRelay" }],
},
migrations: [{ tag: "v1", new_classes: ["SignalRelay"] }],
name,
d1_databases: [
{
binding: "DB",
database_name: d1Name,
database_id: d1Id,
migrations_dir: "drizzle",
},
],
kv_namespaces: [
{
binding: "GITHUB_CACHE_KV",
id: kvId,
preview_id: kvPreviewId,
},
],
r2_buckets: [
{
binding: "COMMENT_MEDIA",
bucket_name: r2Bucket,
preview_bucket_name: r2Preview,
remote: true,
},
],
};

writeFileSync(outFile, `${JSON.stringify(config, null, 2)}\n`, "utf8");
Loading
Loading