From a5930e4c704566a84c75709c7771a84b5326c82c Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Wed, 15 Apr 2026 19:37:34 -0400 Subject: [PATCH 1/3] add stubs to client only libs --- apps/dashboard/package.json | 2 +- apps/dashboard/vite.config.ts | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 0c2c8f2..3f8cc3f 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -7,7 +7,7 @@ }, "scripts": { "predev": "node ../../scripts/link-worktree-dev-vars.mjs", - "dev": "vite dev --port 3000", + "dev": "NODE_OPTIONS='--max-old-space-size=8192' vite dev --port 3000", "build": "vite build", "preview": "vite preview", "test": "vitest run", diff --git a/apps/dashboard/vite.config.ts b/apps/dashboard/vite.config.ts index 3f7af76..b287004 100644 --- a/apps/dashboard/vite.config.ts +++ b/apps/dashboard/vite.config.ts @@ -120,11 +120,80 @@ export default {};`; }; } +// Stub out client-only packages in the SSR environment to reduce the workerd +// V8 heap footprint. These libraries are only used for client-side rendering +// (animations, number transitions, diff viewers, devtools) and are never +// needed during server-side rendering. Without these stubs, workerd can hit +// its ~1.4 GB V8 heap limit and OOM on memory-constrained systems (e.g. WSL2). +function clientOnlySSRStubs(): import("vite").Plugin { + const CLIENT_ONLY_PACKAGES = [ + "motion", + "@number-flow/react", + "@pierre/diffs", + "@tanstack/react-devtools", + "@tanstack/react-router-devtools", + ]; + + const STUB_PREFIX = "\0client-stub:"; + + const MOTION_STUB = ` +const noop = () => {}; +const noopObj = { get: noop, set: noop, on: noop, stop: noop, destroy: noop }; +const handler = { get: (_, prop) => typeof prop === "symbol" ? undefined : prop }; +export const motion = new Proxy({}, handler); +export const AnimatePresence = ({ children }) => children; +export const useMotionValue = () => noopObj; +export const useTransform = () => noopObj; +export const animate = () => ({ stop: noop }); +export default {};`; + + const STUBS: Record = { + motion: MOTION_STUB, + "@number-flow/react": `export default function NumberFlow({ value }) { return String(value ?? ""); }`, + "@pierre/diffs": `export const registerCustomTheme = () => {}; export default {};`, + "@pierre/diffs/react": `const noop = () => null; export default noop;`, + "@tanstack/react-devtools": `export function TanStackDevtools() { return null; }`, + "@tanstack/react-router-devtools": `export function TanStackRouterDevtoolsPanel() { return null; }`, + }; + + function isClientOnly(source: string) { + return CLIENT_ONLY_PACKAGES.some( + (pkg) => source === pkg || source.startsWith(`${pkg}/`), + ); + } + + function getStub(source: string) { + if (STUBS[source]) return STUBS[source]; + for (const [pkg, stub] of Object.entries(STUBS)) { + if (source.startsWith(`${pkg}/`)) return stub; + } + return `export default {};`; + } + + return { + name: "client-only-ssr-stubs", + enforce: "pre", + resolveId: { + handler(source) { + if (this.environment?.name === "ssr" && isClientOnly(source)) { + return `${STUB_PREFIX}${source}`; + } + }, + }, + load(id) { + if (id.startsWith(STUB_PREFIX)) { + return getStub(id.slice(STUB_PREFIX.length)); + } + }, + }; +} + const config = defineConfig(({ command }) => ({ server: command === "serve" ? getTunnelServerConfig() : undefined, plugins: [ devtools(), shikiSSRStub(), + clientOnlySSRStubs(), cloudflare({ viteEnvironment: { name: "ssr" }, ...worktreePersistState, From 9e4ad04ffde36ac3a91cba4b9cd57be755f88bed Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Wed, 15 Apr 2026 20:21:46 -0400 Subject: [PATCH 2/3] patch miniflare to pass heap args into v8 --- package.json | 5 ++++- patches/miniflare@4.20260409.0.patch | 12 ++++++++++++ pnpm-lock.yaml | 11 ++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 patches/miniflare@4.20260409.0.patch diff --git a/package.json b/package.json index 0c4419a..d6ed42e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,9 @@ "lightningcss", "sharp", "workerd" - ] + ], + "patchedDependencies": { + "miniflare@4.20260409.0": "patches/miniflare@4.20260409.0.patch" + } } } diff --git a/patches/miniflare@4.20260409.0.patch b/patches/miniflare@4.20260409.0.patch new file mode 100644 index 0000000..eff4685 --- /dev/null +++ b/patches/miniflare@4.20260409.0.patch @@ -0,0 +1,12 @@ +diff --git a/dist/src/index.js b/dist/src/index.js +index 4f02a86f29bdb38533512e4744cfc384b29a3327..83b7ec764f3f0e0776cf955b5e0c15d1db859416 100644 +--- a/dist/src/index.js ++++ b/dist/src/index.js +@@ -87406,6 +87406,7 @@ var Miniflare = class _Miniflare { + services: servicesArray, + sockets, + extensions, ++ v8Flags: ["--max-heap-size=4096"], + structuredLogging: this.#structuredWorkerdLogs + }; + } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be8a24b..841991b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + miniflare@4.20260409.0: + hash: 61d4e1e1d19037db6a859a4cf0924920d09bfb6ed5101cc328bfb2e9e790fb68 + path: patches/miniflare@4.20260409.0.patch + importers: .: @@ -5253,7 +5258,7 @@ snapshots: '@cloudflare/vite-plugin@1.31.2(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260409.1)(wrangler@4.81.1(@cloudflare/workers-types@4.20260413.1))': dependencies: '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260409.1) - miniflare: 4.20260409.0 + miniflare: 4.20260409.0(patch_hash=61d4e1e1d19037db6a859a4cf0924920d09bfb6ed5101cc328bfb2e9e790fb68) unenv: 2.0.0-rc.24 vite: 7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3) wrangler: 4.81.1(@cloudflare/workers-types@4.20260413.1) @@ -8676,7 +8681,7 @@ snapshots: mimic-function@5.0.1: {} - miniflare@4.20260409.0: + miniflare@4.20260409.0(patch_hash=61d4e1e1d19037db6a859a4cf0924920d09bfb6ed5101cc328bfb2e9e790fb68): dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.34.5 @@ -9553,7 +9558,7 @@ snapshots: '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260409.1) blake3-wasm: 2.1.5 esbuild: 0.27.3 - miniflare: 4.20260409.0 + miniflare: 4.20260409.0(patch_hash=61d4e1e1d19037db6a859a4cf0924920d09bfb6ed5101cc328bfb2e9e790fb68) path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 workerd: 1.20260409.1 From 8e7140dbc3c239a784e52f4467027ceb3d08bc15 Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Wed, 15 Apr 2026 21:05:56 -0400 Subject: [PATCH 3/3] docs --- apps/dashboard/.dev.vars.example | 13 +++++++++---- apps/dashboard/README.md | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/dashboard/.dev.vars.example b/apps/dashboard/.dev.vars.example index b1d4674..bb62919 100644 --- a/apps/dashboard/.dev.vars.example +++ b/apps/dashboard/.dev.vars.example @@ -33,7 +33,7 @@ GITHUB_CLIENT_SECRET= # How to get these: # 1. Go to https://github.com/settings/apps → New GitHub App # 2. Set "Callback URL" to http://localhost:3000/api/github/app/callback -# 3. Set "Setup URL" to http://localhost:3000/?show-org-setup=true +# 3. Set "Setup URL" to http://localhost:3000/setup # 4. Check "Redirect on update" # 5. Leave "Request user authorization (OAuth) during installation" UNCHECKED # 6. Grant the permissions listed in the README @@ -55,7 +55,8 @@ GITHUB_APP_ID= # GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----\n" # # Quick conversion from the downloaded .pem file: -# awk 'NF {printf "%s\\n", $0}' /path/to/key.pem +# macOS / Linux: awk 'NF {printf "%s\\n", $0}' /path/to/key.pem +# PowerShell: (Get-Content key.pem -Raw) -replace "`r`n","\n" -replace "`n","\n" # # GitHub downloads PKCS#1 keys ("BEGIN RSA PRIVATE KEY"); DiffKit auto-converts # to PKCS#8 at runtime — no manual conversion needed. @@ -70,7 +71,9 @@ GITHUB_APP_SLUG= # Secret used to verify webhook payloads from GitHub (HMAC-SHA256). # Set the same value in your GitHub App settings under "Webhook secret". # -# Generate one with: openssl rand -hex 20 +# Generate one with: +# macOS / Linux: openssl rand -hex 20 +# PowerShell: -join ((1..20) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) }) GITHUB_WEBHOOK_SECRET= # ----------------------------------------------------------------------------- @@ -79,7 +82,9 @@ GITHUB_WEBHOOK_SECRET= # Random secret used by Better Auth to encrypt session cookies. # Must be at least 32 characters. # -# Generate one with: openssl rand -base64 32 +# Generate one with: +# macOS / Linux: openssl rand -base64 32 +# PowerShell: [Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Max 256 })) BETTER_AUTH_SECRET= # Base URL where DiffKit is running. Used by Better Auth for redirects. diff --git a/apps/dashboard/README.md b/apps/dashboard/README.md index 9d346b9..1d89672 100644 --- a/apps/dashboard/README.md +++ b/apps/dashboard/README.md @@ -22,7 +22,7 @@ OAuth App: GitHub App: - Callback URL: `http://localhost:3000/api/github/app/callback` -- Setup URL: `http://localhost:3000/?show-org-setup=true` +- Setup URL: `http://localhost:3000/setup` - Enable **Redirect on update** - Leave **Request user authorization (OAuth) during installation** unchecked - Environment variables: `GITHUB_APP_CLIENT_ID`, `GITHUB_APP_CLIENT_SECRET`, `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, `GITHUB_APP_SLUG`, `GITHUB_WEBHOOK_SECRET`