void 0.1.6 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_PROMPT.md +15 -0
- package/README.md +62 -123
- package/dist/auth-BdsJ0Aff.d.mts +43 -0
- package/dist/auth-cmd-Dx8oPKZC.mjs +43 -0
- package/dist/auth-migrations-BAtAck2g.mjs +117 -0
- package/dist/better-auth-shared-C9_GHSkR.d.mts +71 -0
- package/dist/better-auth-shared-CdYmQGry.mjs +163 -0
- package/dist/cache-W82I8ihI.mjs +47 -0
- package/dist/cancel-deploy-BOBTqqh0.mjs +59 -0
- package/dist/cf-access-Dee5cXxL.mjs +22 -0
- package/dist/chunk-DJd-R1mw.mjs +34 -0
- package/dist/cli/cli.d.mts +1 -0
- package/dist/cli/cli.mjs +1807 -0
- package/dist/client-snXOjrp1.mjs +565 -0
- package/dist/collect-CjeZgz5D.mjs +55 -0
- package/dist/config-BIa9HwVX.mjs +573 -0
- package/dist/config-BzM9Dy7T.mjs +37 -0
- package/dist/config-CvHtTM0q.mjs +30 -0
- package/dist/create-project-BIA15W7z.mjs +90 -0
- package/dist/db-DsRoMcfN.mjs +895 -0
- package/dist/defer-DcxEsVH1.mjs +49 -0
- package/dist/delete-DAP6yDc7.mjs +64 -0
- package/dist/deploy-BPKblFx6.mjs +2424 -0
- package/dist/discover-B7FkXBLB.mjs +40 -0
- package/dist/dist-DUyXJLkq.mjs +2667 -0
- package/dist/dist-Dayj3gCK.mjs +1287 -0
- package/dist/domain-BGofcQ6I.mjs +79 -0
- package/dist/dotenv-DwO4ti0Z.mjs +173 -0
- package/dist/drizzle-NnudE_UN.mjs +232 -0
- package/dist/env-CyG3tvU0.mjs +301 -0
- package/dist/env-helpers-Dr9Y7RnE.d.mts +52 -0
- package/dist/env-raw-BDL4TvdN.mjs +32 -0
- package/dist/env-types-DknSA4SO.mjs +64 -0
- package/dist/env-validation-DJKjR_8q.mjs +163 -0
- package/dist/fetch-error-BQ8sZ5Nd.mjs +266 -0
- package/dist/fetch-error-CVZ5CGA-.d.mts +20 -0
- package/dist/gen-U0Ktr4Zd.mjs +761 -0
- package/dist/handler-B0ds0OHJ.d.mts +269 -0
- package/dist/head-P-egrtFE.d.mts +45 -0
- package/dist/headers-DCXc7mDs.mjs +279 -0
- package/dist/index.d.mts +32 -0
- package/dist/index.mjs +4695 -0
- package/dist/init-C7wS5iGP.mjs +2625 -0
- package/dist/link-p2R6NbgN.mjs +49 -0
- package/dist/list-Bfel-QLc.mjs +113 -0
- package/dist/log-DXdqnmhF.mjs +26 -0
- package/dist/login-CkcXUiIu.mjs +72 -0
- package/dist/logs-DmkrRvx6.mjs +98 -0
- package/dist/magic-string.es-D6g9UnIy.mjs +1011 -0
- package/dist/mcp-CaQzfeUi.mjs +373 -0
- package/dist/node-DDfXj10V.mjs +54 -0
- package/dist/output-BwlcIYSR.mjs +139 -0
- package/dist/pages/client.d.mts +198 -0
- package/dist/pages/client.mjs +980 -0
- package/dist/pages/head-client.d.mts +15 -0
- package/dist/pages/head-client.mjs +90 -0
- package/dist/pages/head.d.mts +2 -0
- package/dist/pages/head.mjs +112 -0
- package/dist/pages/index.d.mts +38 -0
- package/dist/pages/index.mjs +76 -0
- package/dist/pages/islands-plugin.d.mts +50 -0
- package/dist/pages/islands-plugin.mjs +195 -0
- package/dist/pages/prefetch.d.mts +31 -0
- package/dist/pages/prefetch.mjs +90 -0
- package/dist/pages/protocol.d.mts +3 -0
- package/dist/pages/protocol.mjs +193 -0
- package/dist/pages/serialize.d.mts +10 -0
- package/dist/pages/serialize.mjs +14 -0
- package/dist/pathe.M-eThtNZ-D-kmWkCS.mjs +150 -0
- package/dist/plugin-inference-oZ6Ybu2_.mjs +2447 -0
- package/dist/prepare-BAtWufvm.mjs +99 -0
- package/dist/preset-D4I73kT4.mjs +221 -0
- package/dist/project-TqORyHn8.mjs +72 -0
- package/dist/project-cmd-B7lQp3F3.mjs +67 -0
- package/dist/project-slug-CKam8lF9.mjs +11 -0
- package/dist/project-tsconfig-DfkESbDL.mjs +63 -0
- package/dist/protocol-BWzXs2A2.d.mts +34 -0
- package/dist/providers-B3aMxWzP.mjs +67 -0
- package/dist/resolve-project-Br5BR03U.mjs +29 -0
- package/dist/rollback-gyC59l7U.mjs +92 -0
- package/dist/route-types-DReF1gUY.mjs +255 -0
- package/dist/routes-stub.d.mts +55 -0
- package/dist/routes-stub.mjs +1 -0
- package/dist/runner-6Ep3fNQu.mjs +123 -0
- package/dist/runner-pg-D0wWHYnr.mjs +57 -0
- package/dist/runtime/ai.d.mts +127 -0
- package/dist/runtime/ai.mjs +348 -0
- package/dist/runtime/auth-client-react.d.mts +8 -0
- package/dist/runtime/auth-client-react.mjs +6 -0
- package/dist/runtime/auth-client-solid.d.mts +8 -0
- package/dist/runtime/auth-client-solid.mjs +6 -0
- package/dist/runtime/auth-client-svelte.d.mts +8 -0
- package/dist/runtime/auth-client-svelte.mjs +6 -0
- package/dist/runtime/auth-client-vue.d.mts +8 -0
- package/dist/runtime/auth-client-vue.mjs +6 -0
- package/dist/runtime/auth-client.d.mts +8 -0
- package/dist/runtime/auth-client.mjs +6 -0
- package/dist/runtime/auth.d.mts +2 -0
- package/dist/runtime/auth.mjs +22 -0
- package/dist/runtime/better-auth-pg.d.mts +11 -0
- package/dist/runtime/better-auth-pg.mjs +51 -0
- package/dist/runtime/better-auth.d.mts +11 -0
- package/dist/runtime/better-auth.mjs +33 -0
- package/dist/runtime/client.d.mts +6 -0
- package/dist/runtime/client.mjs +5 -0
- package/dist/runtime/db-pg.d.mts +2 -0
- package/dist/runtime/db-pg.mjs +1 -0
- package/dist/runtime/db.d.mts +17 -0
- package/dist/runtime/db.mjs +30 -0
- package/dist/runtime/drizzle-arktype.d.mts +1 -0
- package/dist/runtime/drizzle-arktype.mjs +2 -0
- package/dist/runtime/drizzle-valibot.d.mts +1 -0
- package/dist/runtime/drizzle-valibot.mjs +2 -0
- package/dist/runtime/drizzle-zod.d.mts +1 -0
- package/dist/runtime/drizzle-zod.mjs +2 -0
- package/dist/runtime/env-helpers.d.mts +2 -0
- package/dist/runtime/env-helpers.mjs +173 -0
- package/dist/runtime/env-public-client.d.mts +22 -0
- package/dist/runtime/env-public-client.mjs +54 -0
- package/dist/runtime/env-public.d.mts +143 -0
- package/dist/runtime/env-public.mjs +366 -0
- package/dist/runtime/env.d.mts +13 -0
- package/dist/runtime/env.mjs +51 -0
- package/dist/runtime/fetch-stream.d.mts +51 -0
- package/dist/runtime/fetch-stream.mjs +81 -0
- package/dist/runtime/fetch.d.mts +59 -0
- package/dist/runtime/fetch.mjs +18 -0
- package/dist/runtime/handler.d.mts +3 -0
- package/dist/runtime/handler.mjs +85 -0
- package/dist/runtime/isr.d.mts +26 -0
- package/dist/runtime/isr.mjs +43 -0
- package/dist/runtime/kv.d.mts +48 -0
- package/dist/runtime/kv.mjs +106 -0
- package/dist/runtime/log.d.mts +24 -0
- package/dist/runtime/log.mjs +31 -0
- package/dist/runtime/migration-handler-pg.d.mts +6 -0
- package/dist/runtime/migration-handler-pg.mjs +85 -0
- package/dist/runtime/migration-handler.d.mts +19 -0
- package/dist/runtime/migration-handler.mjs +92 -0
- package/dist/runtime/queues.d.mts +7 -0
- package/dist/runtime/queues.mjs +8 -0
- package/dist/runtime/remote/binding-handler.d.mts +15 -0
- package/dist/runtime/remote/binding-handler.mjs +208 -0
- package/dist/runtime/remote/index.d.mts +8 -0
- package/dist/runtime/remote/index.mjs +461 -0
- package/dist/runtime/response.d.mts +14 -0
- package/dist/runtime/response.mjs +30 -0
- package/dist/runtime/sandbox.d.mts +17 -0
- package/dist/runtime/sandbox.mjs +19 -0
- package/dist/runtime/schema-d1.d.mts +1 -0
- package/dist/runtime/schema-d1.mjs +2 -0
- package/dist/runtime/schema-pg.d.mts +1 -0
- package/dist/runtime/schema-pg.mjs +2 -0
- package/dist/runtime/seed.d.mts +30 -0
- package/dist/runtime/seed.mjs +6 -0
- package/dist/runtime/storage.d.mts +7 -0
- package/dist/runtime/storage.mjs +14 -0
- package/dist/runtime/validator.d.mts +2 -0
- package/dist/runtime/validator.mjs +72 -0
- package/dist/runtime/ws-server.d.mts +26 -0
- package/dist/runtime/ws-server.mjs +296 -0
- package/dist/runtime/ws.d.mts +123 -0
- package/dist/runtime/ws.mjs +103 -0
- package/dist/scan-Ba4hFwlH.mjs +324 -0
- package/dist/scan-C6HMEIdW.mjs +318 -0
- package/dist/secret-CeRSukgM.mjs +109 -0
- package/dist/skills-ipldjlKE.mjs +62 -0
- package/dist/standard-schema-9CRjx-uR.d.mts +42 -0
- package/dist/subcommand-prompt-BKjuNAPb.mjs +349 -0
- package/dist/sveltekit.d.mts +20 -0
- package/dist/sveltekit.mjs +61 -0
- package/dist/types-mHOEwpW4.d.mts +57 -0
- package/dist/validate-CaMavMxu.mjs +146 -0
- package/dist/yarn-pnp-BFqMV_bl.mjs +196 -0
- package/getting-started-prompt.txt +26 -0
- package/package.json +322 -30
- package/schema.json +364 -0
- package/skills/migrate-vite-cloudflare-to-void/SKILL.md +175 -0
- package/skills/void/SKILL.md +75 -0
- package/skills/void/command/void.md +7 -0
- package/skills/void/docs/guide/ai.md +235 -0
- package/skills/void/docs/guide/app-types.md +103 -0
- package/skills/void/docs/guide/auth.md +257 -0
- package/skills/void/docs/guide/database/d1.md +106 -0
- package/skills/void/docs/guide/database/postgresql.md +106 -0
- package/skills/void/docs/guide/database.md +418 -0
- package/skills/void/docs/guide/deployment.md +98 -0
- package/skills/void/docs/guide/edge/headers.md +79 -0
- package/skills/void/docs/guide/edge/prerendering.md +83 -0
- package/skills/void/docs/guide/edge/redirects.md +116 -0
- package/skills/void/docs/guide/edge/revalidation.md +131 -0
- package/skills/void/docs/guide/edge/rewrites.md +354 -0
- package/skills/void/docs/guide/edge/static-assets.md +72 -0
- package/skills/void/docs/guide/env-vars.md +298 -0
- package/skills/void/docs/guide/index.md +80 -0
- package/skills/void/docs/guide/jobs.md +91 -0
- package/skills/void/docs/guide/kv.md +107 -0
- package/skills/void/docs/guide/pages-routing/actions-and-forms.md +419 -0
- package/skills/void/docs/guide/pages-routing/head.md +130 -0
- package/skills/void/docs/guide/pages-routing/islands.md +405 -0
- package/skills/void/docs/guide/pages-routing/layouts.md +362 -0
- package/skills/void/docs/guide/pages-routing/loaders.md +267 -0
- package/skills/void/docs/guide/pages-routing/markdown.md +625 -0
- package/skills/void/docs/guide/pages-routing/overview.md +236 -0
- package/skills/void/docs/guide/pages-routing/view-transitions.md +140 -0
- package/skills/void/docs/guide/queues.md +140 -0
- package/skills/void/docs/guide/quickstart.md +233 -0
- package/skills/void/docs/guide/remote-dev.md +67 -0
- package/skills/void/docs/guide/sandboxes.md +82 -0
- package/skills/void/docs/guide/server-routing.md +246 -0
- package/skills/void/docs/guide/ssg.md +74 -0
- package/skills/void/docs/guide/ssr.md +105 -0
- package/skills/void/docs/guide/storage.md +67 -0
- package/skills/void/docs/guide/type-safety.md +179 -0
- package/skills/void/docs/guide/typed-fetch.md +113 -0
- package/skills/void/docs/guide/websockets.md +190 -0
- package/skills/void/docs/index.md +48 -0
- package/skills/void/docs/integrations/agents.md +84 -0
- package/skills/void/docs/integrations/cloudflare.md +284 -0
- package/skills/void/docs/integrations/frameworks/analog.md +182 -0
- package/skills/void/docs/integrations/frameworks/astro.md +197 -0
- package/skills/void/docs/integrations/frameworks/nuxt.md +164 -0
- package/skills/void/docs/integrations/frameworks/overview.md +136 -0
- package/skills/void/docs/integrations/frameworks/react-router.md +137 -0
- package/skills/void/docs/integrations/frameworks/sveltekit.md +191 -0
- package/skills/void/docs/integrations/frameworks/tanstack-start.md +140 -0
- package/skills/void/docs/integrations/hono.md +97 -0
- package/skills/void/docs/integrations/nodejs-bun-deno.md +210 -0
- package/skills/void/docs/node_modules/@iconify/vue/README.md +408 -0
- package/skills/void/docs/node_modules/@iconify/vue/offline/readme.md +5 -0
- package/skills/void/docs/node_modules/@voidzero-dev/vitepress-theme/README.md +103 -0
- package/skills/void/docs/node_modules/oxc-minify/README.md +78 -0
- package/skills/void/docs/node_modules/reka-ui/README.md +80 -0
- package/skills/void/docs/node_modules/vitepress/README.md +28 -0
- package/skills/void/docs/node_modules/vitepress/template/api-examples.md +49 -0
- package/skills/void/docs/node_modules/vitepress/template/index.md +28 -0
- package/skills/void/docs/node_modules/vitepress/template/markdown-examples.md +85 -0
- package/skills/void/docs/node_modules/vitepress-plugin-group-icons/README.md +101 -0
- package/skills/void/docs/node_modules/void/AGENTS.md +204 -0
- package/skills/void/docs/node_modules/void/AGENT_PROMPT.md +15 -0
- package/skills/void/docs/node_modules/void/README.md +89 -0
- package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/CHANGELOG.md +591 -0
- package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/README.md +375 -0
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/sandbox/README.md +174 -0
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/vite-plugin/README.md +37 -0
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/workers-types/README.md +135 -0
- package/skills/void/docs/node_modules/void/node_modules/@electric-sql/pglite/README.md +189 -0
- package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/CHANGELOG.md +143 -0
- package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/README.md +1272 -0
- package/skills/void/docs/node_modules/void/node_modules/@napi-rs/keyring/README.md +19 -0
- package/skills/void/docs/node_modules/void/node_modules/@types/better-sqlite3/README.md +15 -0
- package/skills/void/docs/node_modules/void/node_modules/@types/node/README.md +15 -0
- package/skills/void/docs/node_modules/void/node_modules/@types/pg/README.md +15 -0
- package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/README.md +22 -0
- package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/vendor/vscode-jsonrpc/README.md +69 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/README.md +152 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/engine-javascript/README.md +9 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/transformers/README.md +9 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@types/node/README.md +15 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/CHANGELOG.md +24 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/README.md +565 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-exit/README.md +124 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-anchor/README.md +600 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-attrs/README.md +386 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-container/README.md +95 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-emoji/README.md +101 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/pathe/README.md +73 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/shiki/README.md +15 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/tinyglobby/README.md +25 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/tsdown/README.md +55 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite/LICENSE.md +2230 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite/README.md +20 -0
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vue/README.md +58 -0
- package/skills/void/docs/node_modules/void/node_modules/arktype/README.md +165 -0
- package/skills/void/docs/node_modules/void/node_modules/better-auth/LICENSE.md +20 -0
- package/skills/void/docs/node_modules/void/node_modules/better-auth/README.md +32 -0
- package/skills/void/docs/node_modules/void/node_modules/better-sqlite3/README.md +99 -0
- package/skills/void/docs/node_modules/void/node_modules/blake3-jit/README.md +108 -0
- package/skills/void/docs/node_modules/void/node_modules/drizzle-arktype/README.md +51 -0
- package/skills/void/docs/node_modules/void/node_modules/drizzle-kit/README.md +79 -0
- package/skills/void/docs/node_modules/void/node_modules/drizzle-orm/README.md +44 -0
- package/skills/void/docs/node_modules/void/node_modules/drizzle-valibot/README.md +51 -0
- package/skills/void/docs/node_modules/void/node_modules/drizzle-zod/README.md +65 -0
- package/skills/void/docs/node_modules/void/node_modules/es-module-lexer/README.md +390 -0
- package/skills/void/docs/node_modules/void/node_modules/estree-walker/README.md +48 -0
- package/skills/void/docs/node_modules/void/node_modules/hono/README.md +85 -0
- package/skills/void/docs/node_modules/void/node_modules/ignore/README.md +452 -0
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/CHANGELOG.md +76 -0
- package/{LICENSE → skills/void/docs/node_modules/void/node_modules/jsonc-parser/LICENSE.md} +21 -21
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/README.md +364 -0
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/SECURITY.md +41 -0
- package/skills/void/docs/node_modules/void/node_modules/magic-string/README.md +325 -0
- package/skills/void/docs/node_modules/void/node_modules/ofetch/README.md +398 -0
- package/skills/void/docs/node_modules/void/node_modules/pathe/README.md +73 -0
- package/skills/void/docs/node_modules/void/node_modules/pg/README.md +95 -0
- package/skills/void/docs/node_modules/void/node_modules/pglite-server/LICENSE.md +21 -0
- package/skills/void/docs/node_modules/void/node_modules/pglite-server/README.md +135 -0
- package/skills/void/docs/node_modules/void/node_modules/picocolors/README.md +21 -0
- package/skills/void/docs/node_modules/void/node_modules/tinyglobby/README.md +25 -0
- package/skills/void/docs/node_modules/void/node_modules/tsdown/README.md +55 -0
- package/skills/void/docs/node_modules/void/node_modules/valibot/LICENSE.md +9 -0
- package/skills/void/docs/node_modules/void/node_modules/valibot/README.md +94 -0
- package/skills/void/docs/node_modules/void/node_modules/vite/LICENSE.md +2230 -0
- package/skills/void/docs/node_modules/void/node_modules/vite/README.md +20 -0
- package/skills/void/docs/node_modules/void/node_modules/wrangler/README.md +63 -0
- package/skills/void/docs/node_modules/void/node_modules/zod/README.md +191 -0
- package/skills/void/docs/node_modules/void/skills/migrate-vite-cloudflare-to-void/SKILL.md +175 -0
- package/skills/void/docs/node_modules/void/skills/void/SKILL.md +75 -0
- package/skills/void/docs/node_modules/void/skills/void/command/void.md +7 -0
- package/skills/void/docs/reference/api.md +917 -0
- package/skills/void/docs/reference/cli.md +561 -0
- package/skills/void/docs/reference/config.md +408 -0
- package/skills/void/docs/reference/resource-inference.md +149 -0
- package/skills/void/docs/reference/structure.md +176 -0
- package/.npmignore +0 -29
- package/.travis.yml +0 -9
- package/favicon.ico +0 -0
- package/index.js +0 -14
- package/lib/Job.js +0 -150
- package/lib/Void.js +0 -99
- package/lib/scan.js +0 -19
- package/test/credentials.js +0 -20
- package/test/job.js +0 -64
- package/test/static/dir1/test6.html +0 -0
- package/test/static/dir2/test7.html +0 -0
- package/test/static/dir2/test8.html +0 -0
- package/test/static/dir2/test9.html +0 -0
- package/test/static/test1.html +0 -0
- package/test/static/test2.html +0 -0
- package/test/static/test3.html +0 -0
- package/test/void.js +0 -31
- /package/{test/static/dir1/test4.html → skills/void/docs/integrations/auth-providers.md} +0 -0
- /package/{test/static/dir1/test5.html → skills/void/docs/integrations/payment-processors.md} +0 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4695 @@
|
|
|
1
|
+
import { r as __toESM } from "./chunk-DJd-R1mw.mjs";
|
|
2
|
+
import { a as join, n as dirname, o as relative } from "./pathe.M-eThtNZ-D-kmWkCS.mjs";
|
|
3
|
+
import { u as require_picocolors } from "./output-BwlcIYSR.mjs";
|
|
4
|
+
import { n as loadVoidAuthConfig, t as findVoidAuthConfig } from "./config-CvHtTM0q.mjs";
|
|
5
|
+
import { r as readProjectConfig } from "./project-TqORyHn8.mjs";
|
|
6
|
+
import { l as isStagingMode, s as getToken } from "./client-snXOjrp1.mjs";
|
|
7
|
+
import { _ as resolveSandboxConfig, a as prerenderPagesNode, c as filterLoadedEnv, d as ensureWranglerCompatibilityDate, f as ensureWranglerNodejsAlsFlag, g as isSandboxEnabled, h as SANDBOX_MIGRATION_TAG, i as prerenderPages, l as resolveRemoteMode, m as syncWranglerBindings, n as resolveDistDir, o as DEFAULT_PROXY_URL, p as readWranglerCompat, r as resolveWorkerDirName, s as STAGING_PROXY_URL, u as stripInternalEnvKeys } from "./deploy-BPKblFx6.mjs";
|
|
8
|
+
import { c as getDatabaseDialect, f as readConfig, l as isNodeTarget, p as resolveBindingNames } from "./config-BIa9HwVX.mjs";
|
|
9
|
+
import { i as scanJobsSync, n as scanWebSocketRoutesSync, r as scanQueuesSync, t as scanRoutes } from "./scan-C6HMEIdW.mjs";
|
|
10
|
+
import { a as SSR_SERVER_ENTRY_RELATIVE_PATHS, c as validateSsrEntry, d as isIdentifier, f as isLiteral, h as walk, i as SSR_CLIENT_ENTRY_RELATIVE_PATHS, l as inferBindingsFromSource, n as detectFramework, p as isMemberExpression, r as inferProjectBindings, s as validateSsrEntries, t as FRAMEWORK_SCAN_DIRS } from "./plugin-inference-oZ6Ybu2_.mjs";
|
|
11
|
+
import { i as voidWarn, n as voidError, r as voidLog, t as setShowTimestamp } from "./log-DXdqnmhF.mjs";
|
|
12
|
+
import { r as parseDotenvFile } from "./dotenv-DwO4ti0Z.mjs";
|
|
13
|
+
import { i as generateDbDts, n as generateQueueTypes, r as DRIZZLE_OPERATORS, t as generateRouteTypes } from "./route-types-DReF1gUY.mjs";
|
|
14
|
+
import { n as ensureProjectTsconfig } from "./project-tsconfig-DfkESbDL.mjs";
|
|
15
|
+
import { t as discoverSchema } from "./discover-B7FkXBLB.mjs";
|
|
16
|
+
import { t as MagicString } from "./magic-string.es-D6g9UnIy.mjs";
|
|
17
|
+
import { n as generateEnvTypes, t as findEnvFile } from "./env-types-DknSA4SO.mjs";
|
|
18
|
+
import { a as loadUserEnvFile } from "./env-validation-DJKjR_8q.mjs";
|
|
19
|
+
import { i as resolveLayoutIds } from "./scan-Ba4hFwlH.mjs";
|
|
20
|
+
import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-DCXc7mDs.mjs";
|
|
21
|
+
import { n as generateVoidAuthDrizzleSchema, t as generateVoidAuthDatabaseModule } from "./drizzle-NnudE_UN.mjs";
|
|
22
|
+
import { t as INTERNAL_BINDING_PREFIXES } from "./env-raw-BDL4TvdN.mjs";
|
|
23
|
+
import { n as defer } from "./defer-DcxEsVH1.mjs";
|
|
24
|
+
import { VoidAssetRewriteError, defineHandler, defineHead, defineMiddleware, defineQueue, defineRender, defineScheduled } from "./runtime/handler.mjs";
|
|
25
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, writeFileSync, writeSync } from "node:fs";
|
|
26
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
27
|
+
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
28
|
+
import { loadEnv, normalizePath, parseSync } from "vite";
|
|
29
|
+
import { homedir } from "node:os";
|
|
30
|
+
//#region src/plugin-dev-triggers-hint.ts
|
|
31
|
+
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
32
|
+
/**
|
|
33
|
+
* Prints a one-shot hint on dev server start when the project has crons
|
|
34
|
+
* or queues, explaining how to trigger them locally. Both default Void mode
|
|
35
|
+
* and framework mode (SvelteKit, Nuxt, Analog, Astro, TanStack Start, React
|
|
36
|
+
* Router, vinext) expose `/__void/scheduled` and `/__void/queue`.
|
|
37
|
+
*/
|
|
38
|
+
function devTriggersHintPlugin(root, options) {
|
|
39
|
+
const { devTriggerToken } = options;
|
|
40
|
+
return {
|
|
41
|
+
name: "void:dev-triggers-hint",
|
|
42
|
+
apply: "serve",
|
|
43
|
+
configureServer(server) {
|
|
44
|
+
const jobs = scanJobsSync(root);
|
|
45
|
+
const queues = scanQueuesSync(root);
|
|
46
|
+
if (jobs.length === 0 && queues.length === 0) return;
|
|
47
|
+
const afterReady = (fn) => {
|
|
48
|
+
if (server.httpServer) server.httpServer.once("listening", () => setTimeout(fn, 100));
|
|
49
|
+
else fn();
|
|
50
|
+
};
|
|
51
|
+
afterReady(() => {
|
|
52
|
+
const address = server.httpServer?.address();
|
|
53
|
+
const port = typeof address === "object" && address ? address.port : null;
|
|
54
|
+
if (port == null) return;
|
|
55
|
+
const baseUrl = `http://localhost:${port}`;
|
|
56
|
+
const summary = [];
|
|
57
|
+
if (jobs.length > 0) summary.push(`${jobs.length} cron${jobs.length === 1 ? "" : "s"}`);
|
|
58
|
+
if (queues.length > 0) summary.push(`${queues.length} queue${queues.length === 1 ? "" : "s"}`);
|
|
59
|
+
console.log();
|
|
60
|
+
voidLog(`Detected ${summary.join(" and ")}. Trigger locally:`);
|
|
61
|
+
const configuredProxyToken = parseDotenvFile(join(root, ".dev.vars"), { expand: false }).__VOID_PROXY_TOKEN;
|
|
62
|
+
const authHeader = configuredProxyToken ? `x-void-internal: ${configuredProxyToken}` : `x-void-dev-trigger: ${devTriggerToken}`;
|
|
63
|
+
if (jobs.length > 0) {
|
|
64
|
+
const sample = jobs[0].crons[0];
|
|
65
|
+
console.log(` \x1b[2mcurl -X POST ${baseUrl}/__void/scheduled \\\n -H "${authHeader}" \\\n -d '{"cron":"${sample}","scheduledTime":'"$(date +%s000)"'}'\x1b[22m`);
|
|
66
|
+
}
|
|
67
|
+
if (queues.length > 0) {
|
|
68
|
+
const sample = queues[0].name;
|
|
69
|
+
console.log(` \x1b[2mcurl -X POST ${baseUrl}/__void/queue \\\n -H "${authHeader}" \\\n -d '{"queue":"${sample}","messages":[{"id":"1","timestamp":'"$(date +%s000)"',"body":{},"attempts":1}]}'\x1b[22m`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/plugin-drizzle.ts
|
|
77
|
+
const drizzleD1Resolved = fileURLToPath(import.meta.resolve("drizzle-orm/d1"));
|
|
78
|
+
const drizzleOrmResolved = fileURLToPath(import.meta.resolve("drizzle-orm"));
|
|
79
|
+
const envModuleResolved = fileURLToPath(import.meta.resolve("#self/_env"));
|
|
80
|
+
const drizzleNodePgResolved = fileURLToPath(import.meta.resolve("drizzle-orm/node-postgres"));
|
|
81
|
+
const pgResolved = fileURLToPath(import.meta.resolve("pg"));
|
|
82
|
+
const VIRTUAL_DB_ID = "\0virtual:void-db";
|
|
83
|
+
const VIRTUAL_SCHEMA_DB_ID = "\0virtual:void-db-schema";
|
|
84
|
+
/**
|
|
85
|
+
* Vite plugin for Drizzle integration.
|
|
86
|
+
*
|
|
87
|
+
* - Intercepts `void/db` imports in SSR/worker environments and serves
|
|
88
|
+
* a virtual module that creates a Drizzle DB instance, using the user's
|
|
89
|
+
* schema when present.
|
|
90
|
+
* - Generates `.void/db.d.ts` with proper Drizzle types.
|
|
91
|
+
* - Sets up `@schema` Vite alias for convenient imports when a schema exists.
|
|
92
|
+
* - Updates `.void/tsconfig.json` with TypeScript paths.
|
|
93
|
+
*/
|
|
94
|
+
function drizzlePlugin(root, hasRouteTypes, dialect = "sqlite", options = {}) {
|
|
95
|
+
const outDir = join(root, ".void");
|
|
96
|
+
const schema = discoverSchema(root);
|
|
97
|
+
const hasDbRuntime = schema != null || options.forceVirtualDb === true;
|
|
98
|
+
ensureProjectTsconfig(root, hasRouteTypes, hasDbRuntime, schema != null, { redirectDbModule: options.redirectDbTypePaths });
|
|
99
|
+
if (hasDbRuntime) generateDbDts(outDir, schema ?? null, dialect);
|
|
100
|
+
let isBuild = false;
|
|
101
|
+
return {
|
|
102
|
+
name: "void:drizzle",
|
|
103
|
+
enforce: "pre",
|
|
104
|
+
config(_, env) {
|
|
105
|
+
isBuild = env.command === "build";
|
|
106
|
+
if (!schema) return;
|
|
107
|
+
return { resolve: { alias: { "@schema": schema.path } } };
|
|
108
|
+
},
|
|
109
|
+
resolveId: {
|
|
110
|
+
filter: { id: /^(void\/db|@schema|pg$)/ },
|
|
111
|
+
handler(id, importer) {
|
|
112
|
+
if (id === "pg" && isBuild && dialect === "postgresql" && importer === VIRTUAL_DB_ID) return pgResolved;
|
|
113
|
+
if (id === "void/db" && this.environment?.name !== "client") {
|
|
114
|
+
if (!hasDbRuntime) return;
|
|
115
|
+
if (schema && importer) {
|
|
116
|
+
const normalizedImporter = normalizePath(importer);
|
|
117
|
+
const normalizedSchemaPath = normalizePath(schema.path);
|
|
118
|
+
if (schema.type === "file" && normalizedImporter === normalizedSchemaPath) return VIRTUAL_SCHEMA_DB_ID;
|
|
119
|
+
if (schema.type === "directory" && normalizedImporter.startsWith(normalizedSchemaPath)) return VIRTUAL_SCHEMA_DB_ID;
|
|
120
|
+
}
|
|
121
|
+
return VIRTUAL_DB_ID;
|
|
122
|
+
}
|
|
123
|
+
if (schema && (id === "@schema" || id.startsWith("@schema/"))) {
|
|
124
|
+
if (id === "@schema") return schema.path;
|
|
125
|
+
return join(schema.path, id.slice(8));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
load: {
|
|
130
|
+
filter: { id: /^\0virtual:void-db(?:-schema)?$/ },
|
|
131
|
+
handler(id) {
|
|
132
|
+
if (id === VIRTUAL_SCHEMA_DB_ID) return `
|
|
133
|
+
export { ${DRIZZLE_OPERATORS} } from ${JSON.stringify(drizzleOrmResolved)};
|
|
134
|
+
export const db = {};
|
|
135
|
+
export function createDb() {
|
|
136
|
+
throw new Error("db: createDb() is unavailable when importing void/db from a schema file.");
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
139
|
+
const schemaImport = schema ? `import * as schema from ${JSON.stringify(schema.path)};` : "";
|
|
140
|
+
const drizzleConfig = schema ? ", { schema }" : "";
|
|
141
|
+
if (dialect === "postgresql") return `
|
|
142
|
+
import { drizzle } from ${JSON.stringify(drizzleNodePgResolved)};
|
|
143
|
+
import pg from "pg";
|
|
144
|
+
import { getRuntimeBinding } from ${JSON.stringify(envModuleResolved)};
|
|
145
|
+
${schemaImport}
|
|
146
|
+
export { ${DRIZZLE_OPERATORS} } from ${JSON.stringify(drizzleOrmResolved)};
|
|
147
|
+
|
|
148
|
+
let _prodInstance;
|
|
149
|
+
|
|
150
|
+
function getConnectionString() {
|
|
151
|
+
const url = getRuntimeBinding("DATABASE_URL");
|
|
152
|
+
if (url) return url;
|
|
153
|
+
const hd = getRuntimeBinding("HYPERDRIVE");
|
|
154
|
+
if (hd?.connectionString) return hd.connectionString;
|
|
155
|
+
throw new Error("db: PostgreSQL is not configured. Add DATABASE_URL to .env.local for local dev, or configure Hyperdrive for production.");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getInstance() {
|
|
159
|
+
// Local dev (DATABASE_URL): create a fresh Pool per query chain.
|
|
160
|
+
// workerd kills TCP sockets between requests, so pg.Pool's cached
|
|
161
|
+
// connections go stale and hang. Fresh Pool = fresh connection every time.
|
|
162
|
+
if (getRuntimeBinding("DATABASE_URL")) {
|
|
163
|
+
return drizzle(new pg.Pool({ connectionString: getRuntimeBinding("DATABASE_URL"), max: 1 })${drizzleConfig});
|
|
164
|
+
}
|
|
165
|
+
// Production (Hyperdrive): cache the instance — Hyperdrive handles pooling.
|
|
166
|
+
_prodInstance ??= drizzle(getConnectionString()${drizzleConfig});
|
|
167
|
+
return _prodInstance;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const db = new Proxy({}, {
|
|
171
|
+
get(_, prop) {
|
|
172
|
+
return Reflect.get(getInstance(), prop);
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
export function createDb(connectionString) {
|
|
177
|
+
return drizzle(connectionString${drizzleConfig});
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
return `
|
|
181
|
+
import { drizzle } from ${JSON.stringify(drizzleD1Resolved)};
|
|
182
|
+
import { requireRuntimeBinding } from ${JSON.stringify(envModuleResolved)};
|
|
183
|
+
${schemaImport}
|
|
184
|
+
export { ${DRIZZLE_OPERATORS} } from ${JSON.stringify(drizzleOrmResolved)};
|
|
185
|
+
|
|
186
|
+
let _instance;
|
|
187
|
+
|
|
188
|
+
export const db = new Proxy({}, {
|
|
189
|
+
get(_, prop) {
|
|
190
|
+
_instance ??= drizzle(requireRuntimeBinding("DB")${drizzleConfig});
|
|
191
|
+
return Reflect.get(_instance, prop);
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export function createDb(d1) {
|
|
196
|
+
return drizzle(d1${drizzleConfig});
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
configureServer(server) {
|
|
202
|
+
if (!schema) return;
|
|
203
|
+
const migrationsDir = join(root, "db", "migrations");
|
|
204
|
+
if (!(existsSync(migrationsDir) && readdirSync(migrationsDir).some((f) => f.endsWith(".sql")))) voidWarn("Schema found but no migrations in db/migrations/.\n Run `void db push` to apply to local DB, or\n `void db generate` to create migration files.");
|
|
205
|
+
const watchPath = schema.path;
|
|
206
|
+
const regenerate = (path) => {
|
|
207
|
+
if (path === watchPath || schema.type === "directory" && path.startsWith(watchPath)) generateDbDts(outDir, schema, dialect);
|
|
208
|
+
};
|
|
209
|
+
server.watcher.on("change", regenerate);
|
|
210
|
+
server.watcher.on("add", regenerate);
|
|
211
|
+
server.watcher.on("unlink", regenerate);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/plugin-env-client-fold.ts
|
|
217
|
+
let cachedModule$1 = null;
|
|
218
|
+
async function loadEnvPublic$1() {
|
|
219
|
+
if (cachedModule$1) return cachedModule$1;
|
|
220
|
+
const mod = await import("./runtime/env-public.mjs");
|
|
221
|
+
cachedModule$1 = mod;
|
|
222
|
+
return mod;
|
|
223
|
+
}
|
|
224
|
+
const SKIP_EXT$1 = /\.(?:css|s[ac]ss|less|styl|json|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|otf|eot|wasm|mp[34]|webm|md|mdx)(?:\?|$)/;
|
|
225
|
+
const SCRIPT_LANG_RE$1 = /[?&]lang\.([a-z0-9]+)/i;
|
|
226
|
+
function mentionsVoidEnv$1(code) {
|
|
227
|
+
return code.includes("void/env");
|
|
228
|
+
}
|
|
229
|
+
function extractEnvBindings$1(program) {
|
|
230
|
+
const direct = /* @__PURE__ */ new Set();
|
|
231
|
+
const namespace = /* @__PURE__ */ new Set();
|
|
232
|
+
let found = false;
|
|
233
|
+
for (const node of program.body ?? []) {
|
|
234
|
+
if (node.type !== "ImportDeclaration") continue;
|
|
235
|
+
if (node.source.value !== "void/env") continue;
|
|
236
|
+
found = true;
|
|
237
|
+
for (const spec of node.specifiers ?? []) if (spec.type === "ImportSpecifier") {
|
|
238
|
+
const importedName = spec.imported;
|
|
239
|
+
if (!isIdentifier(importedName)) continue;
|
|
240
|
+
if (importedName.name === "env") direct.add(spec.local.name);
|
|
241
|
+
} else if (spec.type === "ImportNamespaceSpecifier") namespace.add(spec.local.name);
|
|
242
|
+
}
|
|
243
|
+
if (!found || direct.size === 0 && namespace.size === 0) return null;
|
|
244
|
+
return {
|
|
245
|
+
directNames: direct,
|
|
246
|
+
namespaceNames: namespace
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Walk the AST and collect every static `env.FOO` / `env["FOO"]` (or
|
|
251
|
+
* `<ns>.env.FOO`) read. Dynamic keys (`env[variable]`) are intentionally
|
|
252
|
+
* ignored — the Proxy still serves them at runtime, and we can't prove
|
|
253
|
+
* they'd be folded the same way Vite folds `import.meta.env.FOO`.
|
|
254
|
+
*/
|
|
255
|
+
function findFoldHits(program, bindings) {
|
|
256
|
+
const hits = [];
|
|
257
|
+
walk(program, { enter(node) {
|
|
258
|
+
const current = node;
|
|
259
|
+
if (!isMemberExpression(current)) return;
|
|
260
|
+
if (isIdentifier(current.object) && bindings.directNames.has(current.object.name)) {
|
|
261
|
+
pushHit(current, hits);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const obj = current.object;
|
|
265
|
+
if (isMemberExpression(obj) && !obj.computed && isIdentifier(obj.object) && bindings.namespaceNames.has(obj.object.name) && isIdentifier(obj.property) && obj.property.name === "env") pushHit(current, hits);
|
|
266
|
+
} });
|
|
267
|
+
return hits;
|
|
268
|
+
}
|
|
269
|
+
function pushHit(node, hits) {
|
|
270
|
+
if (!node.computed && isIdentifier(node.property)) {
|
|
271
|
+
hits.push({
|
|
272
|
+
start: node.start,
|
|
273
|
+
end: node.end,
|
|
274
|
+
property: node.property.name
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (node.computed && isLiteral(node.property) && typeof node.property.value === "string") {
|
|
279
|
+
hits.push({
|
|
280
|
+
start: node.start,
|
|
281
|
+
end: node.end,
|
|
282
|
+
property: node.property.value
|
|
283
|
+
});
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/** Default Vite `envPrefix`. */
|
|
288
|
+
const DEFAULT_ENV_PREFIXES$1 = ["VITE_"];
|
|
289
|
+
/**
|
|
290
|
+
* Build-time constant folding for client-only `env.FOO` reads.
|
|
291
|
+
*
|
|
292
|
+
* Purpose: match what Vite already does for `import.meta.env.FOO` so that
|
|
293
|
+
* branches like `if (env.MODE === "production") { ... }` get dead-code
|
|
294
|
+
* eliminated in production client bundles. The runtime `env` Proxy on the
|
|
295
|
+
* client already resolves keys via `import.meta.env`, so *semantics* don't
|
|
296
|
+
* change — we just move the lookup from runtime to build time, which
|
|
297
|
+
* unlocks DCE and bundler minification of guarded blocks.
|
|
298
|
+
*
|
|
299
|
+
* Safety properties:
|
|
300
|
+
* • Runs only when `this.environment.name === 'client'`. Server / SSR /
|
|
301
|
+
* worker builds keep the full runtime Proxy (secrets, runtime-injected
|
|
302
|
+
* vars, HMR remain live).
|
|
303
|
+
* • Only folds keys that match the configured `envPrefix` — the exact
|
|
304
|
+
* set Vite is already willing to ship to the browser bundle. A
|
|
305
|
+
* server-only key is never folded (and the env-client-guard plugin
|
|
306
|
+
* would have errored out earlier anyway).
|
|
307
|
+
* • Only folds static member access: `env.FOO` and `env["FOO"]`.
|
|
308
|
+
* Dynamic access, destructuring, and reassignment fall through to the
|
|
309
|
+
* runtime Proxy.
|
|
310
|
+
* • Only folds keys with a known build-time value (loaded .env* merged
|
|
311
|
+
* with schema `.default(...)` entries). Unknown keys are left as-is
|
|
312
|
+
* and the Proxy handles them at runtime — no "undefined" shoved in.
|
|
313
|
+
* • Build-only (`apply: 'build'`). Dev runs through the Proxy so HMR
|
|
314
|
+
* and editing `.env.local` take effect without a full rebuild.
|
|
315
|
+
*/
|
|
316
|
+
function envClientFoldPlugin() {
|
|
317
|
+
let envPrefixes = DEFAULT_ENV_PREFIXES$1;
|
|
318
|
+
let loadedEnv = {};
|
|
319
|
+
const schemaDefaults = {};
|
|
320
|
+
/** The merged view the transform consults for each key. */
|
|
321
|
+
function resolveValue(key) {
|
|
322
|
+
if (key in loadedEnv) return loadedEnv[key];
|
|
323
|
+
if (key in schemaDefaults) return schemaDefaults[key];
|
|
324
|
+
}
|
|
325
|
+
function isExposedToClient(key) {
|
|
326
|
+
return envPrefixes.some((p) => key.startsWith(p));
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
name: "void:env-client-fold",
|
|
330
|
+
apply: "build",
|
|
331
|
+
enforce: "post",
|
|
332
|
+
async configResolved(config) {
|
|
333
|
+
const prefix = config.envPrefix;
|
|
334
|
+
if (typeof prefix === "string") envPrefixes = [prefix];
|
|
335
|
+
else if (Array.isArray(prefix) && prefix.length > 0) envPrefixes = prefix;
|
|
336
|
+
else envPrefixes = DEFAULT_ENV_PREFIXES$1;
|
|
337
|
+
loadedEnv = { ...config.env ?? {} };
|
|
338
|
+
try {
|
|
339
|
+
const mod = await loadEnvPublic$1();
|
|
340
|
+
if (mod._hasRegisteredSchema()) {
|
|
341
|
+
const defaults = mod._getSchemaDefaults();
|
|
342
|
+
for (const [key, value] of Object.entries(defaults)) if (isExposedToClient(key) && !(key in loadedEnv)) schemaDefaults[key] = value;
|
|
343
|
+
}
|
|
344
|
+
} catch {}
|
|
345
|
+
},
|
|
346
|
+
async transform(code, id) {
|
|
347
|
+
if (this.environment?.name !== "client") return;
|
|
348
|
+
if (id.includes("/node_modules/")) return;
|
|
349
|
+
if (SKIP_EXT$1.test(id)) return;
|
|
350
|
+
const langMatch = SCRIPT_LANG_RE$1.exec(id);
|
|
351
|
+
const isScriptQuery = langMatch !== null && /^(?:m?[jt]sx?|cjs)$/i.test(langMatch[1]);
|
|
352
|
+
const isPlainScript = /\.(?:[mc]?[jt]sx?)(?:\?|$)/.test(id);
|
|
353
|
+
const isSfc = /\.(?:svelte|vue|astro)(?:\?|$)/.test(id);
|
|
354
|
+
if (!isScriptQuery && !isPlainScript && !isSfc) return;
|
|
355
|
+
if (!mentionsVoidEnv$1(code)) return;
|
|
356
|
+
if (Object.keys(loadedEnv).length === 0 && Object.keys(schemaDefaults).length === 0) return;
|
|
357
|
+
let parsed;
|
|
358
|
+
try {
|
|
359
|
+
parsed = parseSync(id, code);
|
|
360
|
+
} catch {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const program = parsed.program;
|
|
364
|
+
const bindings = extractEnvBindings$1(program);
|
|
365
|
+
if (!bindings) return;
|
|
366
|
+
const hits = findFoldHits(program, bindings);
|
|
367
|
+
if (hits.length === 0) return;
|
|
368
|
+
let ms = null;
|
|
369
|
+
for (const hit of hits) {
|
|
370
|
+
if (!isExposedToClient(hit.property)) continue;
|
|
371
|
+
const value = resolveValue(hit.property);
|
|
372
|
+
if (value === void 0) continue;
|
|
373
|
+
if (!ms) ms = new MagicString(code);
|
|
374
|
+
ms.overwrite(hit.start, hit.end, JSON.stringify(value));
|
|
375
|
+
}
|
|
376
|
+
if (!ms) return;
|
|
377
|
+
return {
|
|
378
|
+
code: ms.toString(),
|
|
379
|
+
map: ms.generateMap({ hires: "boundary" })
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/plugin-env-client-guard.ts
|
|
386
|
+
let cachedModule = null;
|
|
387
|
+
async function loadEnvPublic() {
|
|
388
|
+
if (cachedModule) return cachedModule;
|
|
389
|
+
const mod = await import("./runtime/env-public.mjs");
|
|
390
|
+
cachedModule = mod;
|
|
391
|
+
return mod;
|
|
392
|
+
}
|
|
393
|
+
const SKIP_EXT = /\.(?:css|s[ac]ss|less|styl|json|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|otf|eot|wasm|mp[34]|webm|md|mdx)(?:\?|$)/;
|
|
394
|
+
const SCRIPT_LANG_RE = /[?&]lang\.([a-z0-9]+)/i;
|
|
395
|
+
/**
|
|
396
|
+
* Quick check: does the source mention `void/env` at all? If not, skip the
|
|
397
|
+
* full AST walk. Catches static imports, dynamic imports, and edge cases
|
|
398
|
+
* like comments — false positives here just mean we parse a file we didn't
|
|
399
|
+
* have to, never that we miss a real reference.
|
|
400
|
+
*/
|
|
401
|
+
function mentionsVoidEnv(code) {
|
|
402
|
+
return code.includes("void/env");
|
|
403
|
+
}
|
|
404
|
+
function extractEnvBindings(program) {
|
|
405
|
+
const direct = /* @__PURE__ */ new Set();
|
|
406
|
+
const namespace = /* @__PURE__ */ new Set();
|
|
407
|
+
let found = false;
|
|
408
|
+
for (const node of program.body ?? []) {
|
|
409
|
+
if (node.type !== "ImportDeclaration") continue;
|
|
410
|
+
if (node.source.value !== "void/env") continue;
|
|
411
|
+
found = true;
|
|
412
|
+
for (const spec of node.specifiers ?? []) if (spec.type === "ImportSpecifier") {
|
|
413
|
+
const importedName = spec.imported;
|
|
414
|
+
if (!isIdentifier(importedName)) continue;
|
|
415
|
+
if (importedName.name === "env") direct.add(spec.local.name);
|
|
416
|
+
} else if (spec.type === "ImportNamespaceSpecifier") namespace.add(spec.local.name);
|
|
417
|
+
else if (spec.type === "ImportDefaultSpecifier") {}
|
|
418
|
+
}
|
|
419
|
+
if (!found || direct.size === 0 && namespace.size === 0) return null;
|
|
420
|
+
return {
|
|
421
|
+
directNames: direct,
|
|
422
|
+
namespaceNames: namespace
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function buildOffsetIndex(code) {
|
|
426
|
+
const lineStarts = [0];
|
|
427
|
+
for (let i = 0; i < code.length; i++) if (code.charCodeAt(i) === 10) lineStarts.push(i + 1);
|
|
428
|
+
return { lineStarts };
|
|
429
|
+
}
|
|
430
|
+
function offsetToLineCol(index, offset) {
|
|
431
|
+
const arr = index.lineStarts;
|
|
432
|
+
let lo = 0;
|
|
433
|
+
let hi = arr.length - 1;
|
|
434
|
+
while (lo < hi) {
|
|
435
|
+
const mid = lo + hi + 1 >>> 1;
|
|
436
|
+
if (arr[mid] <= offset) lo = mid;
|
|
437
|
+
else hi = mid - 1;
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
line: lo + 1,
|
|
441
|
+
column: offset - arr[lo] + 1
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Walk the AST collecting every `env.X` (or `<ns>.env.X`) read where `env`
|
|
446
|
+
* is bound to the `void/env` proxy. Computed access produces a warning hit
|
|
447
|
+
* with `property === null`; everything else carries the property name.
|
|
448
|
+
*/
|
|
449
|
+
function findHits(program, bindings) {
|
|
450
|
+
const hits = [];
|
|
451
|
+
walk(program, { enter(node) {
|
|
452
|
+
const current = node;
|
|
453
|
+
if (!isMemberExpression(current)) return;
|
|
454
|
+
if (isIdentifier(current.object) && bindings.directNames.has(current.object.name)) {
|
|
455
|
+
recordHit(current, hits);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const obj = current.object;
|
|
459
|
+
if (isMemberExpression(obj) && !obj.computed && isIdentifier(obj.object) && bindings.namespaceNames.has(obj.object.name) && isIdentifier(obj.property) && obj.property.name === "env") recordHit(current, hits);
|
|
460
|
+
} });
|
|
461
|
+
return hits;
|
|
462
|
+
}
|
|
463
|
+
function recordHit(node, hits) {
|
|
464
|
+
if (node.computed) {
|
|
465
|
+
hits.push({
|
|
466
|
+
kind: "warn",
|
|
467
|
+
property: null,
|
|
468
|
+
start: node.start
|
|
469
|
+
});
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (!isIdentifier(node.property)) return;
|
|
473
|
+
hits.push({
|
|
474
|
+
kind: "error",
|
|
475
|
+
property: node.property.name,
|
|
476
|
+
start: node.start
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Vite plugin that fails the client build when a server-only env key
|
|
481
|
+
* (anything in `defineEnv` not prefixed with `VITE_`) is referenced from a
|
|
482
|
+
* module that ends up in the client bundle. The runtime proxy in
|
|
483
|
+
* `env-public-client.ts` already throws for the same class of bug at
|
|
484
|
+
* request time — this plugin moves the diagnosis forward to compile time
|
|
485
|
+
* so it shows up in `pnpm build` and the dev overlay, not in production
|
|
486
|
+
* after an admin clicks the offending route.
|
|
487
|
+
*
|
|
488
|
+
* No-op when no `env.ts` is registered. Server / SSR / worker environments
|
|
489
|
+
* are skipped — only the `client` environment is checked.
|
|
490
|
+
*/
|
|
491
|
+
/** Default value matches Vite's default `envPrefix`. */
|
|
492
|
+
const DEFAULT_ENV_PREFIXES = ["VITE_"];
|
|
493
|
+
function envClientGuardPlugin(root) {
|
|
494
|
+
let envPrefixes = DEFAULT_ENV_PREFIXES;
|
|
495
|
+
return {
|
|
496
|
+
name: "void:env-client-guard",
|
|
497
|
+
enforce: "post",
|
|
498
|
+
configResolved(config) {
|
|
499
|
+
const prefix = config.envPrefix;
|
|
500
|
+
if (typeof prefix === "string") envPrefixes = [prefix];
|
|
501
|
+
else if (Array.isArray(prefix) && prefix.length > 0) envPrefixes = prefix;
|
|
502
|
+
},
|
|
503
|
+
async transform(code, id) {
|
|
504
|
+
if (this.environment?.name !== "client") return;
|
|
505
|
+
if (id.includes("/node_modules/")) return;
|
|
506
|
+
if (SKIP_EXT.test(id)) return;
|
|
507
|
+
const langMatch = SCRIPT_LANG_RE.exec(id);
|
|
508
|
+
const isScriptQuery = langMatch !== null && /^(?:m?[jt]sx?|cjs)$/i.test(langMatch[1]);
|
|
509
|
+
const isPlainScript = /\.(?:[mc]?[jt]sx?)(?:\?|$)/.test(id);
|
|
510
|
+
const isSfc = /\.(?:svelte|vue|astro)(?:\?|$)/.test(id);
|
|
511
|
+
if (!isScriptQuery && !isPlainScript && !isSfc) return;
|
|
512
|
+
if (!mentionsVoidEnv(code)) return;
|
|
513
|
+
const { _hasRegisteredSchema, _getRegisteredSchema } = await loadEnvPublic();
|
|
514
|
+
if (!_hasRegisteredSchema()) return;
|
|
515
|
+
const schema = _getRegisteredSchema();
|
|
516
|
+
if (!schema || Object.keys(schema).length === 0) return;
|
|
517
|
+
let parsed;
|
|
518
|
+
try {
|
|
519
|
+
parsed = parseSync(id, code);
|
|
520
|
+
} catch {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const program = parsed.program;
|
|
524
|
+
const bindings = extractEnvBindings(program);
|
|
525
|
+
if (!bindings) return;
|
|
526
|
+
const hits = findHits(program, bindings);
|
|
527
|
+
if (hits.length === 0) return;
|
|
528
|
+
const offsets = buildOffsetIndex(code);
|
|
529
|
+
const relPath = relative(root, id.split("?")[0]) || id;
|
|
530
|
+
for (const hit of hits) {
|
|
531
|
+
const { line, column } = offsetToLineCol(offsets, hit.start);
|
|
532
|
+
if (hit.kind === "warn") {
|
|
533
|
+
this.warn({
|
|
534
|
+
message: `Computed access to "env[…]" at ${relPath}:${line}:${column} cannot be statically verified. If the key being read is server-only, this will throw at runtime in the browser. Prefer literal property access (env.VITE_FOO) so the build can check the split.`,
|
|
535
|
+
loc: {
|
|
536
|
+
line,
|
|
537
|
+
column,
|
|
538
|
+
file: id
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const key = hit.property;
|
|
544
|
+
if (!(key in schema)) continue;
|
|
545
|
+
if (envPrefixes.some((p) => key.startsWith(p))) continue;
|
|
546
|
+
const prefixHint = envPrefixes.length === 1 ? `${envPrefixes[0]}* prefix` : `one of these prefixes: ${envPrefixes.join(", ")}`;
|
|
547
|
+
this.error({
|
|
548
|
+
message: `Server-only env "${key}" referenced from client code at ${relPath}:${line}:${column}.\nIf this should be available in the browser, rename it with the ${prefixHint} in env.ts.\nIf it must stay server-only, remove this reference and read the value via a server route or loader.`,
|
|
549
|
+
loc: {
|
|
550
|
+
line,
|
|
551
|
+
column,
|
|
552
|
+
file: id
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/plugin-env-schema.ts
|
|
561
|
+
let cachedSchemaModule = null;
|
|
562
|
+
async function loadSchemaModule() {
|
|
563
|
+
if (cachedSchemaModule) return cachedSchemaModule;
|
|
564
|
+
const mod = await import("./runtime/env-public.mjs");
|
|
565
|
+
cachedSchemaModule = mod;
|
|
566
|
+
return mod;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Vite plugin that auto-discovers `env.ts` at the project root and:
|
|
570
|
+
* - imports it (registering the user's schema via `defineEnv`)
|
|
571
|
+
* - generates `.void/env.d.ts` so `env.X` and `c.env.X` are typed
|
|
572
|
+
* - validates the merged dev env (.env files + process.env) and warns
|
|
573
|
+
* on missing/invalid required keys
|
|
574
|
+
* - regenerates types + re-validates when env.ts changes (HMR)
|
|
575
|
+
*
|
|
576
|
+
* Hard errors are reserved for `void deploy`; dev keeps moving with warnings
|
|
577
|
+
* so unrelated work isn't blocked by a stale .env.local.
|
|
578
|
+
*/
|
|
579
|
+
/** Patterns Vite's `define` typically uses for env-shaped constants. */
|
|
580
|
+
const DEFINE_ENV_PATTERN = /^(?:globalThis\.)?(?:process\.env|import\.meta\.env)\.([A-Z_][A-Z0-9_]*)$/;
|
|
581
|
+
function envSchemaPlugin(root, options = {}) {
|
|
582
|
+
const outDir = join(root, ".void");
|
|
583
|
+
let envFile = null;
|
|
584
|
+
let viteMode = "development";
|
|
585
|
+
let envDir = root;
|
|
586
|
+
async function emitDefaults() {
|
|
587
|
+
if (!envFile || !options.onDefaults) return;
|
|
588
|
+
const { _hasRegisteredSchema, _getSchemaDefaults } = await loadSchemaModule();
|
|
589
|
+
if (!_hasRegisteredSchema()) return;
|
|
590
|
+
options.onDefaults(_getSchemaDefaults());
|
|
591
|
+
}
|
|
592
|
+
function regenerateTypes() {
|
|
593
|
+
if (!envFile) return;
|
|
594
|
+
mkdirSync(outDir, { recursive: true });
|
|
595
|
+
const { dts } = generateEnvTypes(envFile, outDir);
|
|
596
|
+
writeFileSync(join(outDir, "env.d.ts"), dts);
|
|
597
|
+
}
|
|
598
|
+
async function importUserEnv() {
|
|
599
|
+
if (!envFile) return;
|
|
600
|
+
try {
|
|
601
|
+
await loadUserEnvFile(envFile);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
voidWarn(err.message);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async function warnOnDefineOverlap(define) {
|
|
607
|
+
const { _hasRegisteredSchema, _getRegisteredSchema } = await loadSchemaModule();
|
|
608
|
+
if (!_hasRegisteredSchema()) return;
|
|
609
|
+
const schema = _getRegisteredSchema();
|
|
610
|
+
if (!schema) return;
|
|
611
|
+
const overlaps = [];
|
|
612
|
+
for (const defineKey of Object.keys(define)) {
|
|
613
|
+
const match = DEFINE_ENV_PATTERN.exec(defineKey);
|
|
614
|
+
if (!match) continue;
|
|
615
|
+
const name = match[1];
|
|
616
|
+
if (name in schema) overlaps.push({
|
|
617
|
+
defineKey,
|
|
618
|
+
schemaKey: name
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
if (overlaps.length === 0) return;
|
|
622
|
+
const lines = ["Vite `define` is shadowing schema env keys:"];
|
|
623
|
+
for (const { defineKey, schemaKey } of overlaps) lines.push(` • "${defineKey}" → schema key "${schemaKey}". The compiled client code receives the define value, but the typed env.X proxy and validation still source from the schema. Prefer one or the other for any given key.`);
|
|
624
|
+
voidWarn(lines.join("\n"));
|
|
625
|
+
}
|
|
626
|
+
async function validateAndWarn() {
|
|
627
|
+
if (!envFile) return;
|
|
628
|
+
const { _hasRegisteredSchema, validateAllEnv } = await loadSchemaModule();
|
|
629
|
+
if (!_hasRegisteredSchema()) return;
|
|
630
|
+
const { loadEnv } = await import("vite");
|
|
631
|
+
const report = await validateAllEnv(filterLoadedEnv(loadEnv(viteMode, envDir, "")));
|
|
632
|
+
if (report.valid) return;
|
|
633
|
+
const lines = ["Env validation issues:"];
|
|
634
|
+
for (const key of report.missing) lines.push(` • ${key} is required but missing — add it to .env.local or set in your shell`);
|
|
635
|
+
for (const { key, messages } of report.invalid) lines.push(` • ${key}: ${messages.join("; ")}`);
|
|
636
|
+
voidWarn(lines.join("\n"));
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
name: "void:env-schema",
|
|
640
|
+
async config() {
|
|
641
|
+
envFile = findEnvFile(root);
|
|
642
|
+
if (!envFile) return;
|
|
643
|
+
regenerateTypes();
|
|
644
|
+
await importUserEnv();
|
|
645
|
+
await emitDefaults();
|
|
646
|
+
},
|
|
647
|
+
async configResolved(config) {
|
|
648
|
+
viteMode = config.mode;
|
|
649
|
+
envDir = typeof config.envDir === "string" ? config.envDir : root;
|
|
650
|
+
if (envFile && config.define) await warnOnDefineOverlap(config.define);
|
|
651
|
+
},
|
|
652
|
+
async configureServer(server) {
|
|
653
|
+
if (!envFile) return;
|
|
654
|
+
await importUserEnv();
|
|
655
|
+
const run = () => {
|
|
656
|
+
validateAndWarn();
|
|
657
|
+
};
|
|
658
|
+
if (server.httpServer) server.httpServer.once("listening", () => setTimeout(run, 100));
|
|
659
|
+
else run();
|
|
660
|
+
server.watcher.add(envFile);
|
|
661
|
+
server.watcher.on("change", async (file) => {
|
|
662
|
+
if (file === envFile) {
|
|
663
|
+
voidLog("env.ts changed — regenerating types");
|
|
664
|
+
regenerateTypes();
|
|
665
|
+
await importUserEnv();
|
|
666
|
+
await emitDefaults();
|
|
667
|
+
await validateAndWarn();
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
},
|
|
671
|
+
async buildStart() {
|
|
672
|
+
if (envFile) await importUserEnv();
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
//#endregion
|
|
677
|
+
//#region src/plugin-framework-triggers.ts
|
|
678
|
+
/** Absolute path of the generated dispatch file. Stable across the dev process. */
|
|
679
|
+
function frameworkTriggersDispatchPath(root) {
|
|
680
|
+
return join(root, ".void", "dev-triggers.ts");
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Synchronously emit `<root>/.void/dev-triggers.ts`. We expose this directly
|
|
684
|
+
* (in addition to the Vite plugin below) so callers like the Class A entry
|
|
685
|
+
* wrapper can guarantee the file exists at the moment they reference it,
|
|
686
|
+
* without depending on `configResolved` ordering.
|
|
687
|
+
*/
|
|
688
|
+
function writeFrameworkTriggersDispatch(root, devTriggerToken) {
|
|
689
|
+
const jobs = scanJobsSync(root);
|
|
690
|
+
const queues = scanQueuesSync(root);
|
|
691
|
+
const target = frameworkTriggersDispatchPath(root);
|
|
692
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
693
|
+
writeFileSync(target, generateModule(root, jobs, queues, devTriggerToken));
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Writes a real file at `<root>/.void/dev-triggers.ts` exporting
|
|
697
|
+
* `handleDevTrigger(request, env, ctx)`. We use a real file (not a Vite
|
|
698
|
+
* virtual module) because Nitro and Astro have their own server bundlers
|
|
699
|
+
* that don't go through Vite's plugin pipeline. The framework injection
|
|
700
|
+
* code imports this path at runtime to dispatch cron and queue handlers
|
|
701
|
+
* with the framework adapter's real bindings.
|
|
702
|
+
*/
|
|
703
|
+
function frameworkTriggersPlugin(root, options) {
|
|
704
|
+
const writeDispatch = () => writeFrameworkTriggersDispatch(root, options.devTriggerToken);
|
|
705
|
+
const dispatchPath = frameworkTriggersDispatchPath(root);
|
|
706
|
+
return {
|
|
707
|
+
name: "void:framework-triggers",
|
|
708
|
+
apply: "serve",
|
|
709
|
+
configResolved() {
|
|
710
|
+
writeDispatch();
|
|
711
|
+
},
|
|
712
|
+
configureServer(server) {
|
|
713
|
+
const cronsPrefix = normalizePath(join(root, "crons"));
|
|
714
|
+
const queuesPrefix = normalizePath(join(root, "queues"));
|
|
715
|
+
const isInteresting = (file) => {
|
|
716
|
+
const normalized = normalizePath(file);
|
|
717
|
+
return normalized.startsWith(cronsPrefix) || normalized.startsWith(queuesPrefix);
|
|
718
|
+
};
|
|
719
|
+
const onChange = (file) => {
|
|
720
|
+
if (!isInteresting(file)) return;
|
|
721
|
+
writeDispatch();
|
|
722
|
+
for (const env of Object.values(server.environments)) {
|
|
723
|
+
const mod = env.moduleGraph.getModuleById(dispatchPath);
|
|
724
|
+
if (mod) env.moduleGraph.invalidateModule(mod);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
server.watcher.on("add", onChange);
|
|
728
|
+
server.watcher.on("change", onChange);
|
|
729
|
+
server.watcher.on("unlink", onChange);
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
function generateModule(root, jobs, queues, devTriggerToken) {
|
|
734
|
+
const jobImports = jobs.map((j, i) => `import __job${i} from ${JSON.stringify(join(root, "crons", j.filePath))};`).join("\n");
|
|
735
|
+
const queueImports = queues.map((q, i) => `import __queue${i} from ${JSON.stringify(join(root, "queues", q.filePath))};`).join("\n");
|
|
736
|
+
const jobTable = jobs.map((j, i) => ` { crons: ${JSON.stringify(j.crons)}, handler: __job${i} }`).join(",\n");
|
|
737
|
+
const queueTable = queues.map((q, i) => ` { name: ${JSON.stringify(q.name)}, handler: __queue${i} }`).join(",\n");
|
|
738
|
+
return `// @ts-nocheck — Auto-generated by Void in dev. Do NOT edit. Regenerated on every config load.
|
|
739
|
+
${jobImports}
|
|
740
|
+
${queueImports}
|
|
741
|
+
|
|
742
|
+
const __DEV_TRIGGER_TOKEN = ${JSON.stringify(devTriggerToken)};
|
|
743
|
+
|
|
744
|
+
const __jobs = [
|
|
745
|
+
${jobTable}
|
|
746
|
+
];
|
|
747
|
+
|
|
748
|
+
const __queues = [
|
|
749
|
+
${queueTable}
|
|
750
|
+
];
|
|
751
|
+
|
|
752
|
+
function __json(body, status = 200) {
|
|
753
|
+
return new Response(JSON.stringify(body), {
|
|
754
|
+
status,
|
|
755
|
+
headers: { "Content-Type": "application/json" },
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function __authorize(request, env) {
|
|
760
|
+
const internal = request.headers.get("x-void-internal");
|
|
761
|
+
const dev = request.headers.get("x-void-dev-trigger");
|
|
762
|
+
const expected = env?.__VOID_PROXY_TOKEN;
|
|
763
|
+
if (expected) return internal === expected;
|
|
764
|
+
return dev === __DEV_TRIGGER_TOKEN;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function __wrapMessage(m, decisions) {
|
|
768
|
+
let decided = false;
|
|
769
|
+
return {
|
|
770
|
+
id: m.id,
|
|
771
|
+
timestamp: new Date(m.timestamp),
|
|
772
|
+
body: m.body,
|
|
773
|
+
attempts: m.attempts,
|
|
774
|
+
ack() {
|
|
775
|
+
if (!decided) {
|
|
776
|
+
decided = true;
|
|
777
|
+
decisions.set(m.id, { action: "ack" });
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
retry(opts) {
|
|
781
|
+
if (!decided) {
|
|
782
|
+
decided = true;
|
|
783
|
+
decisions.set(m.id, {
|
|
784
|
+
action: "retry",
|
|
785
|
+
...(opts?.delaySeconds != null ? { delaySeconds: opts.delaySeconds } : {}),
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
async function __dispatchScheduled(request, env, ctx) {
|
|
793
|
+
const { cron, scheduledTime } = await request.json();
|
|
794
|
+
const job = __jobs.find((j) => j.crons.includes(cron));
|
|
795
|
+
if (!job) {
|
|
796
|
+
return __json({ error: \`No cron handler matches "\${cron}"\` }, 404);
|
|
797
|
+
}
|
|
798
|
+
const controller = { cron, scheduledTime, noRetry() {} };
|
|
799
|
+
await job.handler(controller, env, ctx ?? { waitUntil() {}, passThroughOnException() {} });
|
|
800
|
+
return __json({ ok: true });
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
async function __dispatchQueue(request, env) {
|
|
804
|
+
const payload = await request.json();
|
|
805
|
+
// Exact match only — payload.queue is the user-typed queue name from the
|
|
806
|
+
// dev curl. The prod platform-side dispatch suffix-matches against
|
|
807
|
+
// project-prefixed queue names, but that isn't relevant here and would
|
|
808
|
+
// cause false positives between e.g. "events" and "user-events".
|
|
809
|
+
const q = __queues.find((entry) => entry.name === payload.queue);
|
|
810
|
+
if (!q) {
|
|
811
|
+
return __json({ error: \`No queue handler matches "\${payload.queue}"\` }, 404);
|
|
812
|
+
}
|
|
813
|
+
const decisions = new Map();
|
|
814
|
+
const messages = (payload.messages ?? []).map((m) => __wrapMessage(m, decisions));
|
|
815
|
+
const batch = {
|
|
816
|
+
queue: payload.queue,
|
|
817
|
+
messages,
|
|
818
|
+
ackAll() {
|
|
819
|
+
for (const m of messages) m.ack();
|
|
820
|
+
},
|
|
821
|
+
retryAll(opts) {
|
|
822
|
+
for (const m of messages) m.retry(opts);
|
|
823
|
+
},
|
|
824
|
+
};
|
|
825
|
+
try {
|
|
826
|
+
await q.handler(batch, env);
|
|
827
|
+
} catch (err) {
|
|
828
|
+
console.error("[void] queue handler threw:", err);
|
|
829
|
+
// CF queues retry the entire batch when a consumer throws, regardless
|
|
830
|
+
// of any ack/retry calls made before the throw — drop partial decisions
|
|
831
|
+
// so dev-mode response semantics match prod.
|
|
832
|
+
return __json({ ok: false });
|
|
833
|
+
}
|
|
834
|
+
return __json({
|
|
835
|
+
ok: true,
|
|
836
|
+
...(decisions.size > 0 ? { decisions: Object.fromEntries(decisions) } : {}),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* If \`request\` is a /__void/scheduled or /__void/queue trigger, validate auth,
|
|
842
|
+
* dispatch, and return a \`Response\`. Otherwise return null so the caller can
|
|
843
|
+
* forward to the framework's normal request handling.
|
|
844
|
+
*/
|
|
845
|
+
export async function handleDevTrigger(request, env, ctx) {
|
|
846
|
+
if (request.method !== "POST") return null;
|
|
847
|
+
const url = new URL(request.url);
|
|
848
|
+
if (url.pathname !== "/__void/scheduled" && url.pathname !== "/__void/queue") {
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
if (!__authorize(request, env)) {
|
|
852
|
+
return __json({ error: "unauthorized" }, 401);
|
|
853
|
+
}
|
|
854
|
+
if (url.pathname === "/__void/scheduled") {
|
|
855
|
+
return __dispatchScheduled(request, env, ctx);
|
|
856
|
+
}
|
|
857
|
+
return __dispatchQueue(request, env);
|
|
858
|
+
}
|
|
859
|
+
`;
|
|
860
|
+
}
|
|
861
|
+
const NITRO_MIDDLEWARE_FILENAME = "__void-dev-trigger.ts";
|
|
862
|
+
function nitroMiddlewareSource(dispatchPath) {
|
|
863
|
+
return `// @ts-nocheck — Auto-generated by Void in dev. Do NOT edit. Removed when the dev server stops.
|
|
864
|
+
import { handleDevTrigger } from ${JSON.stringify(dispatchPath)};
|
|
865
|
+
|
|
866
|
+
export default async function voidDevTrigger(event) {
|
|
867
|
+
const req = event.node.req;
|
|
868
|
+
if (req.method !== 'POST') return;
|
|
869
|
+
const path = req.url || '';
|
|
870
|
+
if (!path.startsWith('/__void/scheduled') && !path.startsWith('/__void/queue')) return;
|
|
871
|
+
|
|
872
|
+
const chunks = [];
|
|
873
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
874
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
875
|
+
|
|
876
|
+
const headers = new Headers();
|
|
877
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
878
|
+
if (typeof v === 'string') headers.set(k, v);
|
|
879
|
+
else if (Array.isArray(v)) headers.set(k, v.join(', '));
|
|
880
|
+
}
|
|
881
|
+
const webReq = new Request(\`http://localhost\${path}\`, { method: 'POST', headers, body });
|
|
882
|
+
|
|
883
|
+
const cloudflare = event.context?.cloudflare;
|
|
884
|
+
const response = await handleDevTrigger(webReq, cloudflare?.env ?? {}, cloudflare?.context);
|
|
885
|
+
if (!response) return;
|
|
886
|
+
|
|
887
|
+
const res = event.node.res;
|
|
888
|
+
res.statusCode = response.status;
|
|
889
|
+
for (const [k, v] of response.headers) res.setHeader(k, v);
|
|
890
|
+
res.end(await response.text());
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
`;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Writes a tiny Nitro server middleware to the user's project so
|
|
897
|
+
* \`/__void/scheduled\` and \`/__void/queue\` are intercepted inside Nitro's
|
|
898
|
+
* request pipeline (where \`event.context.cloudflare?.env\` is populated).
|
|
899
|
+
*
|
|
900
|
+
* Nitro discovers middleware from \`<serverDir>/middleware/*.ts\`. Nuxt scans
|
|
901
|
+
* \`server/\`, Analog scans \`src/server/\`, so the caller specifies which.
|
|
902
|
+
* To keep the generated file out of git we append a \`.gitignore\` entry,
|
|
903
|
+
* and the file is removed when the dev server stops.
|
|
904
|
+
*/
|
|
905
|
+
function nitroDevTriggerInjectPlugin(root, options) {
|
|
906
|
+
const middlewareRelative = `${options.serverDir}/middleware/${NITRO_MIDDLEWARE_FILENAME}`;
|
|
907
|
+
const middlewarePath = join(root, middlewareRelative);
|
|
908
|
+
const gitignoreLine = middlewareRelative;
|
|
909
|
+
const dispatchPath = frameworkTriggersDispatchPath(root);
|
|
910
|
+
const ensureFile = () => {
|
|
911
|
+
mkdirSync(dirname(middlewarePath), { recursive: true });
|
|
912
|
+
writeFileSync(middlewarePath, nitroMiddlewareSource(dispatchPath));
|
|
913
|
+
};
|
|
914
|
+
const ensureGitignore = () => {
|
|
915
|
+
const gitignorePath = join(root, ".gitignore");
|
|
916
|
+
let contents = "";
|
|
917
|
+
if (existsSync(gitignorePath)) {
|
|
918
|
+
contents = readFileSync(gitignorePath, "utf-8");
|
|
919
|
+
if (contents.split("\n").some((line) => line.trim() === gitignoreLine)) return;
|
|
920
|
+
}
|
|
921
|
+
const sep = contents.length === 0 || contents.endsWith("\n") ? "" : "\n";
|
|
922
|
+
writeFileSync(gitignorePath, `${contents}${sep}${gitignoreLine}\n`);
|
|
923
|
+
};
|
|
924
|
+
const cleanup = () => {
|
|
925
|
+
rmSync(middlewarePath, { force: true });
|
|
926
|
+
};
|
|
927
|
+
return {
|
|
928
|
+
name: "void:nitro-dev-trigger-inject",
|
|
929
|
+
apply: "serve",
|
|
930
|
+
configureServer(server) {
|
|
931
|
+
ensureFile();
|
|
932
|
+
ensureGitignore();
|
|
933
|
+
server.httpServer?.once("close", cleanup);
|
|
934
|
+
const onExit = () => cleanup();
|
|
935
|
+
process.once("exit", onExit);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
const ANALOG_ROUTE_DIR_RELATIVE = "src/server/routes/api/__void";
|
|
940
|
+
const ANALOG_TRIGGERS = [{
|
|
941
|
+
user: "scheduled",
|
|
942
|
+
file: "cron-trigger"
|
|
943
|
+
}, {
|
|
944
|
+
user: "queue",
|
|
945
|
+
file: "queue-trigger"
|
|
946
|
+
}];
|
|
947
|
+
function analogRouteSource(dispatchPath, userTrigger) {
|
|
948
|
+
return `// @ts-nocheck — Auto-generated by Void in dev. Do NOT edit. Removed when the dev server stops.
|
|
949
|
+
import { defineEventHandler } from 'h3';
|
|
950
|
+
import { handleDevTrigger } from ${JSON.stringify(dispatchPath)};
|
|
951
|
+
|
|
952
|
+
export default defineEventHandler(async (event) => {
|
|
953
|
+
const req = event.node.req;
|
|
954
|
+
const chunks = [];
|
|
955
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
956
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
957
|
+
|
|
958
|
+
const headers = new Headers();
|
|
959
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
960
|
+
if (typeof v === 'string') headers.set(k, v);
|
|
961
|
+
else if (Array.isArray(v)) headers.set(k, v.join(', '));
|
|
962
|
+
}
|
|
963
|
+
const webReq = new Request('http://localhost/__void/${userTrigger}', {
|
|
964
|
+
method: 'POST',
|
|
965
|
+
headers,
|
|
966
|
+
body,
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
const cloudflare = event.context?.cloudflare;
|
|
970
|
+
const response = await handleDevTrigger(webReq, cloudflare?.env ?? {}, cloudflare?.context);
|
|
971
|
+
if (!response) return;
|
|
972
|
+
|
|
973
|
+
const res = event.node.res;
|
|
974
|
+
res.statusCode = response.status;
|
|
975
|
+
for (const [k, v] of response.headers) res.setHeader(k, v);
|
|
976
|
+
res.end(await response.text());
|
|
977
|
+
return true;
|
|
978
|
+
});
|
|
979
|
+
`;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Analog-specific injection. Analog's @analogjs/vite-plugin-nitro registers
|
|
983
|
+
* user "server middleware" on Vite's connect pipeline (deliberately, to keep
|
|
984
|
+
* some routes out of Nitro), so Nitro plugins like nitro-cloudflare-dev never
|
|
985
|
+
* run for it and `event.context.cloudflare` is empty there.
|
|
986
|
+
*
|
|
987
|
+
* Workaround: put the actual handler at \`src/server/routes/api/__void/[trigger].ts\`
|
|
988
|
+
* — Nitro \*does\* run plugins for routes — and proxy \`/__void/{scheduled,queue}\`
|
|
989
|
+
* to \`/api/__void/{scheduled,queue}\` from a small Vite middleware so the
|
|
990
|
+
* user-facing URL stays consistent with the other frameworks.
|
|
991
|
+
*/
|
|
992
|
+
function analogDevTriggerInjectPlugin(root) {
|
|
993
|
+
const routeDir = join(root, ANALOG_ROUTE_DIR_RELATIVE);
|
|
994
|
+
const routePaths = ANALOG_TRIGGERS.map((t) => ({
|
|
995
|
+
...t,
|
|
996
|
+
path: join(routeDir, `${t.file}.ts`)
|
|
997
|
+
}));
|
|
998
|
+
const gitignoreLines = ANALOG_TRIGGERS.map((t) => `${ANALOG_ROUTE_DIR_RELATIVE}/${t.file}.ts`);
|
|
999
|
+
const dispatchPath = frameworkTriggersDispatchPath(root);
|
|
1000
|
+
const ensureFiles = () => {
|
|
1001
|
+
mkdirSync(routeDir, { recursive: true });
|
|
1002
|
+
for (const { user, path } of routePaths) writeFileSync(path, analogRouteSource(dispatchPath, user));
|
|
1003
|
+
};
|
|
1004
|
+
const ensureGitignore = () => {
|
|
1005
|
+
const gitignorePath = join(root, ".gitignore");
|
|
1006
|
+
let contents = "";
|
|
1007
|
+
if (existsSync(gitignorePath)) contents = readFileSync(gitignorePath, "utf-8");
|
|
1008
|
+
const existing = new Set(contents.split("\n").map((l) => l.trim()));
|
|
1009
|
+
const missing = gitignoreLines.filter((l) => !existing.has(l));
|
|
1010
|
+
if (missing.length === 0) return;
|
|
1011
|
+
const sep = contents.length === 0 || contents.endsWith("\n") ? "" : "\n";
|
|
1012
|
+
writeFileSync(gitignorePath, `${contents}${sep}${missing.join("\n")}\n`);
|
|
1013
|
+
};
|
|
1014
|
+
const cleanup = () => {
|
|
1015
|
+
for (const { path } of routePaths) rmSync(path, { force: true });
|
|
1016
|
+
};
|
|
1017
|
+
return {
|
|
1018
|
+
name: "void:analog-dev-trigger-inject",
|
|
1019
|
+
apply: "serve",
|
|
1020
|
+
configureServer(server) {
|
|
1021
|
+
ensureFiles();
|
|
1022
|
+
ensureGitignore();
|
|
1023
|
+
const proxy = async (req, res, next) => {
|
|
1024
|
+
if (req.method !== "POST") {
|
|
1025
|
+
next();
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const path = req.url ?? "";
|
|
1029
|
+
const match = ANALOG_TRIGGERS.find((t) => path === `/__void/${t.user}`);
|
|
1030
|
+
if (!match) {
|
|
1031
|
+
next();
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
try {
|
|
1035
|
+
const chunks = [];
|
|
1036
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
1037
|
+
const body = Buffer.concat(chunks);
|
|
1038
|
+
const headers = new Headers();
|
|
1039
|
+
for (const [k, v] of Object.entries(req.headers)) if (typeof v === "string") headers.set(k, v);
|
|
1040
|
+
else if (Array.isArray(v)) headers.set(k, v.join(", "));
|
|
1041
|
+
const host = req.headers.host ?? "localhost";
|
|
1042
|
+
const upstream = await fetch(`http://${host}/api/__void/${match.file}`, {
|
|
1043
|
+
method: "POST",
|
|
1044
|
+
headers,
|
|
1045
|
+
body
|
|
1046
|
+
});
|
|
1047
|
+
res.statusCode = upstream.status;
|
|
1048
|
+
upstream.headers.forEach((v, k) => res.setHeader(k, v));
|
|
1049
|
+
res.end(Buffer.from(await upstream.arrayBuffer()));
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
next(err);
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
server.middlewares.use(proxy);
|
|
1055
|
+
server.httpServer?.once("close", cleanup);
|
|
1056
|
+
const onExit = () => cleanup();
|
|
1057
|
+
process.once("exit", onExit);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
const ASTRO_MIDDLEWARE_RELATIVE = "src/middleware.ts";
|
|
1062
|
+
function astroMiddlewareSource(dispatchPath) {
|
|
1063
|
+
return `// @ts-nocheck — Auto-generated by Void in dev. Do NOT edit. Removed when the dev server stops.
|
|
1064
|
+
import { defineMiddleware } from 'astro:middleware';
|
|
1065
|
+
import { env } from 'cloudflare:workers';
|
|
1066
|
+
import { handleDevTrigger } from ${JSON.stringify(dispatchPath)};
|
|
1067
|
+
|
|
1068
|
+
export const onRequest = defineMiddleware(async (context, next) => {
|
|
1069
|
+
if (context.request.method !== 'POST') return next();
|
|
1070
|
+
const { pathname } = context.url;
|
|
1071
|
+
if (pathname !== '/__void/scheduled' && pathname !== '/__void/queue') return next();
|
|
1072
|
+
|
|
1073
|
+
const response = await handleDevTrigger(context.request, env, context.locals.cfContext);
|
|
1074
|
+
return response ?? next();
|
|
1075
|
+
});
|
|
1076
|
+
`;
|
|
1077
|
+
}
|
|
1078
|
+
const ASTRO_GITIGNORE_LINE = "src/middleware.ts";
|
|
1079
|
+
/**
|
|
1080
|
+
* Writes a tiny Astro middleware to the user's project so
|
|
1081
|
+
* \`/__void/scheduled\` and \`/__void/queue\` are intercepted inside Astro's
|
|
1082
|
+
* request pipeline (where \`context.locals.runtime.env\` is populated by
|
|
1083
|
+
* \`@astrojs/cloudflare\`).
|
|
1084
|
+
*
|
|
1085
|
+
* Astro middleware lives at a single discoverable entry, so if the user
|
|
1086
|
+
* already has \`src/middleware.ts\` we don't overwrite it — instead we log a
|
|
1087
|
+
* one-shot snippet they can paste into their existing middleware.
|
|
1088
|
+
*
|
|
1089
|
+
* If we wrote the file we remove it on dev-server close so the user's
|
|
1090
|
+
* source tree stays clean.
|
|
1091
|
+
*/
|
|
1092
|
+
function astroDevTriggerInjectPlugin(root) {
|
|
1093
|
+
const middlewarePath = join(root, ASTRO_MIDDLEWARE_RELATIVE);
|
|
1094
|
+
const middlewareDirIndexPath = join(root, "src", "middleware", "index.ts");
|
|
1095
|
+
let wroteFile = false;
|
|
1096
|
+
const ensureFile = () => {
|
|
1097
|
+
if (existsSync(middlewarePath) || existsSync(middlewareDirIndexPath)) {
|
|
1098
|
+
voidWarn("Astro middleware already exists. To enable /__void/scheduled and /__void/queue,\n add the Void dev-trigger middleware to your existing onRequest sequence.\n See https://void.cloud/guide/jobs#local-development.");
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
mkdirSync(dirname(middlewarePath), { recursive: true });
|
|
1102
|
+
writeFileSync(middlewarePath, astroMiddlewareSource(frameworkTriggersDispatchPath(root)));
|
|
1103
|
+
wroteFile = true;
|
|
1104
|
+
};
|
|
1105
|
+
const ensureGitignore = () => {
|
|
1106
|
+
if (!wroteFile) return;
|
|
1107
|
+
const gitignorePath = join(root, ".gitignore");
|
|
1108
|
+
let contents = "";
|
|
1109
|
+
if (existsSync(gitignorePath)) {
|
|
1110
|
+
contents = readFileSync(gitignorePath, "utf-8");
|
|
1111
|
+
if (contents.split("\n").some((line) => line.trim() === ASTRO_GITIGNORE_LINE)) return;
|
|
1112
|
+
}
|
|
1113
|
+
const sep = contents.length === 0 || contents.endsWith("\n") ? "" : "\n";
|
|
1114
|
+
writeFileSync(gitignorePath, `${contents}${sep}${ASTRO_GITIGNORE_LINE}\n`);
|
|
1115
|
+
};
|
|
1116
|
+
const cleanup = () => {
|
|
1117
|
+
if (wroteFile) rmSync(middlewarePath, { force: true });
|
|
1118
|
+
};
|
|
1119
|
+
return {
|
|
1120
|
+
name: "void:astro-dev-trigger-inject",
|
|
1121
|
+
apply: "serve",
|
|
1122
|
+
configureServer(server) {
|
|
1123
|
+
ensureFile();
|
|
1124
|
+
ensureGitignore();
|
|
1125
|
+
server.httpServer?.once("close", cleanup);
|
|
1126
|
+
const onExit = () => cleanup();
|
|
1127
|
+
process.once("exit", onExit);
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
//#endregion
|
|
1132
|
+
//#region src/plugin-migration.ts
|
|
1133
|
+
function readDatabaseUrl(root) {
|
|
1134
|
+
const envPath = join(root, ".env.local");
|
|
1135
|
+
if (!existsSync(envPath)) return;
|
|
1136
|
+
const content = readFileSync(envPath, "utf-8");
|
|
1137
|
+
for (const line of content.split("\n")) {
|
|
1138
|
+
const trimmed = line.trim();
|
|
1139
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1140
|
+
const eqIndex = trimmed.indexOf("=");
|
|
1141
|
+
if (eqIndex === -1) continue;
|
|
1142
|
+
if (trimmed.slice(0, eqIndex).trim() !== "DATABASE_URL") continue;
|
|
1143
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
1144
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1145
|
+
return value;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Vite plugin that runs local migrations on dev server startup.
|
|
1150
|
+
* For SQLite/D1: applies migrations to the local miniflare database.
|
|
1151
|
+
* For PostgreSQL: connects via DATABASE_URL from .env.local.
|
|
1152
|
+
*/
|
|
1153
|
+
function migrationPlugin(root, dialect, databaseId = "local", persistRoot = join(root, ".void"), auth) {
|
|
1154
|
+
return {
|
|
1155
|
+
name: "void:migrations",
|
|
1156
|
+
apply: "serve",
|
|
1157
|
+
async configureServer(server) {
|
|
1158
|
+
const migrationsDir = join(root, "db", "migrations");
|
|
1159
|
+
const hasSqlMigrations = existsSync(migrationsDir);
|
|
1160
|
+
if (!hasSqlMigrations && !auth?.enabled) return;
|
|
1161
|
+
try {
|
|
1162
|
+
if (hasSqlMigrations && dialect === "postgresql") {
|
|
1163
|
+
const url = readDatabaseUrl(root);
|
|
1164
|
+
if (!url) {
|
|
1165
|
+
const warn = () => {
|
|
1166
|
+
console.log();
|
|
1167
|
+
voidError("DATABASE_URL not found in .env.local — skipping PostgreSQL migrations.");
|
|
1168
|
+
};
|
|
1169
|
+
if (server.httpServer) server.httpServer.once("listening", () => setTimeout(warn, 100));
|
|
1170
|
+
else warn();
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
const { runPgMigrations, getPgAppliedMigrations } = await import("./runner-pg-D0wWHYnr.mjs");
|
|
1174
|
+
const { collectMigrations } = await import("./collect-CjeZgz5D.mjs").then((n) => n.n);
|
|
1175
|
+
const { validateMigrations } = await import("./validate-CaMavMxu.mjs").then((n) => n.i);
|
|
1176
|
+
const allMigrations = collectMigrations(migrationsDir);
|
|
1177
|
+
const pgApplied = await getPgAppliedMigrations(url);
|
|
1178
|
+
const pgAppliedSet = new Set(pgApplied);
|
|
1179
|
+
const pgPending = allMigrations.filter((m) => !pgAppliedSet.has(m.name));
|
|
1180
|
+
if (pgPending.length > 0) try {
|
|
1181
|
+
validateMigrations(pgPending);
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
voidWarn(err instanceof Error ? err.message : String(err));
|
|
1184
|
+
}
|
|
1185
|
+
const applied = await runPgMigrations(migrationsDir, url);
|
|
1186
|
+
if (applied.length > 0) voidLog(`Applied ${applied.length} migration(s): ${applied.join(", ")}`);
|
|
1187
|
+
} else if (hasSqlMigrations) {
|
|
1188
|
+
const { runMigrations, getMiniflareDatabasePath, getAppliedMigrations } = await import("./runner-6Ep3fNQu.mjs");
|
|
1189
|
+
const { collectMigrations } = await import("./collect-CjeZgz5D.mjs").then((n) => n.n);
|
|
1190
|
+
const { validateMigrations } = await import("./validate-CaMavMxu.mjs").then((n) => n.i);
|
|
1191
|
+
const dbPath = getMiniflareDatabasePath(persistRoot, databaseId);
|
|
1192
|
+
const allMigrations = collectMigrations(migrationsDir);
|
|
1193
|
+
const appliedRows = await getAppliedMigrations(dbPath);
|
|
1194
|
+
const appliedSet = new Set(appliedRows.map((r) => r.name));
|
|
1195
|
+
const pending = allMigrations.filter((m) => !appliedSet.has(m.name));
|
|
1196
|
+
if (pending.length > 0) try {
|
|
1197
|
+
validateMigrations(pending);
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
voidWarn(err instanceof Error ? err.message : String(err));
|
|
1200
|
+
}
|
|
1201
|
+
const applied = await runMigrations(migrationsDir, dbPath);
|
|
1202
|
+
if (applied.length > 0) voidLog(`Applied ${applied.length} migration(s): ${applied.join(", ")}`);
|
|
1203
|
+
}
|
|
1204
|
+
if (auth?.enabled) {
|
|
1205
|
+
const { runLocalVoidAuthMigrations } = await import("./node-DDfXj10V.mjs");
|
|
1206
|
+
await runLocalVoidAuthMigrations(root, dialect, persistRoot, databaseId, auth.configPath);
|
|
1207
|
+
voidLog("Applied Better Auth migrations");
|
|
1208
|
+
}
|
|
1209
|
+
} catch (err) {
|
|
1210
|
+
voidError(`Migration error: ${err}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
const CF_ONLY_REGEX = new RegExp(`^(${[
|
|
1216
|
+
"void/db",
|
|
1217
|
+
"void/kv",
|
|
1218
|
+
"void/auth",
|
|
1219
|
+
"void/storage",
|
|
1220
|
+
"void/ai",
|
|
1221
|
+
"void/sandbox",
|
|
1222
|
+
"void/env"
|
|
1223
|
+
].map((s) => s.replace("/", "\\/")).join("|")})$`);
|
|
1224
|
+
/**
|
|
1225
|
+
* Bridge Node.js HTTP to Web API and serve via a Hono app.
|
|
1226
|
+
* Used by both dev server and preview server middleware.
|
|
1227
|
+
*/
|
|
1228
|
+
function honoMiddleware(getApp) {
|
|
1229
|
+
return async (req, res, next) => {
|
|
1230
|
+
try {
|
|
1231
|
+
if (!req.url || req.url.startsWith("/@") || req.url.startsWith("/__") || req.url.startsWith("/node_modules/")) {
|
|
1232
|
+
next();
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
const app = await getApp();
|
|
1236
|
+
if (!app?.fetch) {
|
|
1237
|
+
next();
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
|
|
1241
|
+
const headers = new Headers();
|
|
1242
|
+
for (const [key, value] of Object.entries(req.headers)) if (value) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
1243
|
+
const hasBody = req.method !== "GET" && req.method !== "HEAD";
|
|
1244
|
+
const request = new Request(url.toString(), {
|
|
1245
|
+
method: req.method,
|
|
1246
|
+
headers,
|
|
1247
|
+
body: hasBody ? req : void 0,
|
|
1248
|
+
duplex: hasBody ? "half" : void 0
|
|
1249
|
+
});
|
|
1250
|
+
const response = await app.fetch(request);
|
|
1251
|
+
if (response.status === 404) {
|
|
1252
|
+
next();
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
res.statusCode = response.status;
|
|
1256
|
+
response.headers.forEach((value, key) => {
|
|
1257
|
+
res.setHeader(key, value);
|
|
1258
|
+
});
|
|
1259
|
+
if (response.body) {
|
|
1260
|
+
const reader = response.body.getReader();
|
|
1261
|
+
while (true) {
|
|
1262
|
+
const { done, value } = await reader.read();
|
|
1263
|
+
if (done) break;
|
|
1264
|
+
res.write(value);
|
|
1265
|
+
}
|
|
1266
|
+
res.end();
|
|
1267
|
+
} else res.end();
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
next(err);
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Vite plugin for non-Cloudflare deploy targets (Node.js, Bun, Deno).
|
|
1275
|
+
*
|
|
1276
|
+
* Replaces `@cloudflare/vite-plugin` in the plugin composition. Provides:
|
|
1277
|
+
* 1. Binding guard — compile-time errors for CF-only void/* imports
|
|
1278
|
+
* 2. Dev server — Vite SSR middleware to run Hono in Node.js
|
|
1279
|
+
* 3. Preview server — serves from the built SSR entry
|
|
1280
|
+
* 4. Build — dual-environment build (client + SSR) for pages apps,
|
|
1281
|
+
* single SSR build for API-only apps
|
|
1282
|
+
*/
|
|
1283
|
+
function nodeTargetPlugin(target) {
|
|
1284
|
+
return [{
|
|
1285
|
+
name: "void:node-target",
|
|
1286
|
+
enforce: "pre",
|
|
1287
|
+
resolveId: {
|
|
1288
|
+
filter: { id: CF_ONLY_REGEX },
|
|
1289
|
+
handler(id) {
|
|
1290
|
+
throw new Error(`target: '${id}' is not available with target '${target}'. CF bindings require target: "cloudflare".`);
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
config(_, { command }) {
|
|
1294
|
+
if (command !== "build") return;
|
|
1295
|
+
const root = process.cwd();
|
|
1296
|
+
const voidDir = join(root, ".void");
|
|
1297
|
+
const ssrBuildOptions = {
|
|
1298
|
+
outDir: "dist/ssr",
|
|
1299
|
+
rollupOptions: {
|
|
1300
|
+
input: {
|
|
1301
|
+
app: join(voidDir, "app.ts"),
|
|
1302
|
+
index: join(voidDir, "index.ts")
|
|
1303
|
+
},
|
|
1304
|
+
external: [
|
|
1305
|
+
/^node:/,
|
|
1306
|
+
"@hono/node-server",
|
|
1307
|
+
"@hono/node-server/serve-static"
|
|
1308
|
+
]
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
if (existsSync(join(root, "pages"))) return {
|
|
1312
|
+
build: { outDir: "dist/client" },
|
|
1313
|
+
environments: { ssr: { build: ssrBuildOptions } },
|
|
1314
|
+
builder: {}
|
|
1315
|
+
};
|
|
1316
|
+
return { build: {
|
|
1317
|
+
ssr: true,
|
|
1318
|
+
...ssrBuildOptions
|
|
1319
|
+
} };
|
|
1320
|
+
},
|
|
1321
|
+
async buildApp(builder) {
|
|
1322
|
+
await builder.build(builder.environments.ssr);
|
|
1323
|
+
await builder.build(builder.environments.client);
|
|
1324
|
+
},
|
|
1325
|
+
configureServer(server) {
|
|
1326
|
+
server.middlewares.use(honoMiddleware(async () => {
|
|
1327
|
+
return (await server.ssrLoadModule("virtual:void-routes")).default;
|
|
1328
|
+
}));
|
|
1329
|
+
},
|
|
1330
|
+
configurePreviewServer(server) {
|
|
1331
|
+
const entryPath = join(process.cwd(), "dist", "ssr", "app.js");
|
|
1332
|
+
let appPromise = null;
|
|
1333
|
+
server.middlewares.use(honoMiddleware(() => {
|
|
1334
|
+
if (!appPromise) appPromise = import(
|
|
1335
|
+
/* @vite-ignore */
|
|
1336
|
+
pathToFileURL(entryPath).href
|
|
1337
|
+
).then((m) => m.default, () => null);
|
|
1338
|
+
return appPromise;
|
|
1339
|
+
}));
|
|
1340
|
+
}
|
|
1341
|
+
}];
|
|
1342
|
+
}
|
|
1343
|
+
//#endregion
|
|
1344
|
+
//#region src/plugin-prerender.ts
|
|
1345
|
+
/**
|
|
1346
|
+
* Vite plugin that prerenders pages at build time when `output: "static"`.
|
|
1347
|
+
*
|
|
1348
|
+
* Runs in the `closeBundle` hook so prerender logs appear after Vite's
|
|
1349
|
+
* asset summary. Gated on the "client" environment so it only runs once.
|
|
1350
|
+
*/
|
|
1351
|
+
function prerenderPlugin(root, config) {
|
|
1352
|
+
return {
|
|
1353
|
+
name: "void:prerender",
|
|
1354
|
+
apply: "build",
|
|
1355
|
+
async closeBundle() {
|
|
1356
|
+
if (config.output !== "static") return;
|
|
1357
|
+
if (this.environment?.name !== "client") return;
|
|
1358
|
+
setShowTimestamp(false);
|
|
1359
|
+
const distDir = resolveDistDir(root);
|
|
1360
|
+
const clientDir = join(distDir, "client");
|
|
1361
|
+
let result;
|
|
1362
|
+
if (isNodeTarget(config.target)) result = await prerenderPagesNode({
|
|
1363
|
+
root,
|
|
1364
|
+
ssrDir: join(distDir, "ssr"),
|
|
1365
|
+
clientDir
|
|
1366
|
+
});
|
|
1367
|
+
else {
|
|
1368
|
+
const persistDir = join(root, ".void");
|
|
1369
|
+
result = await prerenderPages({
|
|
1370
|
+
root,
|
|
1371
|
+
workerDir: join(distDir, resolveWorkerDirName(distDir)),
|
|
1372
|
+
clientDir,
|
|
1373
|
+
persistDir,
|
|
1374
|
+
remoteMode: resolveRemoteMode(config.remote),
|
|
1375
|
+
projectId: process.env.VOID_DEPLOY_PROJECT_ID || void 0
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
if (result.paths.length === 0) {
|
|
1379
|
+
voidLog("No pages to prerender");
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
if (result.failed.length > 0) voidWarn(`${result.failed.length} page(s) failed to prerender`);
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
//#endregion
|
|
1387
|
+
//#region src/plugin-restart.ts
|
|
1388
|
+
const BINDING_HINT_RESOLVED = "\0virtual:void/binding-hint";
|
|
1389
|
+
const RESTARTING_HTML = `<!DOCTYPE html>
|
|
1390
|
+
<html lang="en">
|
|
1391
|
+
<head>
|
|
1392
|
+
<meta charset="UTF-8">
|
|
1393
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1394
|
+
<title>Restarting</title>
|
|
1395
|
+
<style>
|
|
1396
|
+
body{margin:0;height:100vh;display:flex;align-items:center;justify-content:center;background:#1a1a2e;color:#e0def4;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
1397
|
+
.tag{color:#c4a7e7;font-weight:700}
|
|
1398
|
+
@keyframes d{0%,80%{opacity:0}40%{opacity:1}}
|
|
1399
|
+
.dots span{opacity:0;animation:d 1.4s infinite}
|
|
1400
|
+
.dots span:nth-child(2){animation-delay:.2s}
|
|
1401
|
+
.dots span:nth-child(3){animation-delay:.4s}
|
|
1402
|
+
</style>
|
|
1403
|
+
</head>
|
|
1404
|
+
<body>
|
|
1405
|
+
<div><span class="tag">[void]</span> Restarting dev server<span class="dots"><span>.</span><span>.</span><span>.</span></span></div>
|
|
1406
|
+
<script>
|
|
1407
|
+
fetch("/__void/restart",{method:"POST"}).catch(function(){});
|
|
1408
|
+
setTimeout(function p(){
|
|
1409
|
+
fetch("/__void/ping").then(function(r){
|
|
1410
|
+
if(r.ok)location.href=new URLSearchParams(location.search).get("return")||"/";
|
|
1411
|
+
else setTimeout(p,150);
|
|
1412
|
+
}).catch(function(){setTimeout(p,150)});
|
|
1413
|
+
},300);
|
|
1414
|
+
<\/script>
|
|
1415
|
+
</body>
|
|
1416
|
+
</html>`;
|
|
1417
|
+
const CLIENT_MODULE = `if (import.meta.hot) {
|
|
1418
|
+
import.meta.hot.on("void:new-binding", (d) => {
|
|
1419
|
+
if (document.getElementById("void-binding-hint")) return;
|
|
1420
|
+
const e = document.createElement("div");
|
|
1421
|
+
e.id = "void-binding-hint";
|
|
1422
|
+
e.style.cssText = "position:fixed;top:16px;right:16px;z-index:99999;padding:12px 16px;background:rgba(26,26,46,0.95);color:#e0def4;font:13px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;border-radius:8px;border:1px solid rgba(255,255,255,0.08);box-shadow:0 8px 32px rgba(0,0,0,0.4);backdrop-filter:blur(8px);cursor:pointer;max-width:300px;transform:translateX(calc(100% + 16px));transition:transform .3s ease";
|
|
1423
|
+
e.innerHTML = "<strong style=\\"color:#c4a7e7\\">[void]</strong> " + d.message + " Click to restart.";
|
|
1424
|
+
e.onclick = () => { location.href = "/__void/restarting?return=" + encodeURIComponent(location.pathname + location.search); };
|
|
1425
|
+
document.body.appendChild(e);
|
|
1426
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
1427
|
+
e.style.transform = "translateX(0)";
|
|
1428
|
+
}));
|
|
1429
|
+
});
|
|
1430
|
+
}`;
|
|
1431
|
+
/**
|
|
1432
|
+
* Vite plugin that provides controlled dev-server restart via the browser.
|
|
1433
|
+
*
|
|
1434
|
+
* Serves three endpoints:
|
|
1435
|
+
* - `/__void/restarting` — standalone loading page (no Vite HMR client)
|
|
1436
|
+
* - `/__void/restart` — POST triggers `server.restart()` after a short delay
|
|
1437
|
+
* - `/__void/ping` — health check; returns 503 on the old server instance
|
|
1438
|
+
* (after restart is triggered) and 200 on the new one
|
|
1439
|
+
*
|
|
1440
|
+
* Also injects a virtual module (`virtual:void/binding-hint`) that listens for
|
|
1441
|
+
* `void:new-binding` HMR events and shows a toast with a click-to-restart action.
|
|
1442
|
+
*/
|
|
1443
|
+
function restartPlugin() {
|
|
1444
|
+
return {
|
|
1445
|
+
name: "void:restart",
|
|
1446
|
+
apply: "serve",
|
|
1447
|
+
resolveId: {
|
|
1448
|
+
filter: { id: /^virtual:void\/binding-hint$/ },
|
|
1449
|
+
handler() {
|
|
1450
|
+
return BINDING_HINT_RESOLVED;
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
load: {
|
|
1454
|
+
filter: { id: /^\0virtual:void\/binding-hint$/ },
|
|
1455
|
+
handler() {
|
|
1456
|
+
return CLIENT_MODULE;
|
|
1457
|
+
}
|
|
1458
|
+
},
|
|
1459
|
+
configureServer(server) {
|
|
1460
|
+
let restartPending = false;
|
|
1461
|
+
server.middlewares.use("/__void/ping", (_req, res) => {
|
|
1462
|
+
res.writeHead(restartPending ? 503 : 200);
|
|
1463
|
+
res.end();
|
|
1464
|
+
});
|
|
1465
|
+
server.middlewares.use("/__void/restart", (req, res) => {
|
|
1466
|
+
if (req.method !== "POST") {
|
|
1467
|
+
res.writeHead(405);
|
|
1468
|
+
res.end();
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
restartPending = true;
|
|
1472
|
+
res.writeHead(200);
|
|
1473
|
+
res.end();
|
|
1474
|
+
setTimeout(() => server.restart(), 50);
|
|
1475
|
+
});
|
|
1476
|
+
server.middlewares.use("/__void/restarting", (_req, res) => {
|
|
1477
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1478
|
+
res.end(RESTARTING_HTML);
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
//#endregion
|
|
1484
|
+
//#region src/router/compile.ts
|
|
1485
|
+
/**
|
|
1486
|
+
* Script tag injected into dev HTML to load the binding-hint virtual module.
|
|
1487
|
+
* The virtual module (served by restartPlugin) listens for `void:new-binding`
|
|
1488
|
+
* HMR events and shows a toast with a click-to-restart action.
|
|
1489
|
+
*/
|
|
1490
|
+
const BINDING_HINT_SCRIPT = `<script type="module" src="/@id/virtual:void/binding-hint"><\/script>`;
|
|
1491
|
+
const DEFAULT_ERROR_PAGE_FONT_STACK = "system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif";
|
|
1492
|
+
function pushDefaultErrorPageHelpers(lines, options) {
|
|
1493
|
+
const dev = options.dev === true;
|
|
1494
|
+
lines.push(`const __VOID_ERROR_PAGE_FONT_STACK = ${JSON.stringify(DEFAULT_ERROR_PAGE_FONT_STACK)};`);
|
|
1495
|
+
lines.push(`const __VOID_DEV = ${JSON.stringify(dev)};`);
|
|
1496
|
+
lines.push(`const __VOID_PROJECT_ROOT = ${JSON.stringify(options.projectRoot)};`);
|
|
1497
|
+
lines.push(`const __VOID_HOME_DIR = ${JSON.stringify(options.homeDir)};`);
|
|
1498
|
+
lines.push(`function __voidEscapeHtml(value) {`);
|
|
1499
|
+
lines.push(` return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);`);
|
|
1500
|
+
lines.push(`}`);
|
|
1501
|
+
lines.push("");
|
|
1502
|
+
lines.push(`function __voidShouldServeHtmlErrorPage(request) {`);
|
|
1503
|
+
lines.push(` if (request.method !== "GET" && request.method !== "HEAD") return false;`);
|
|
1504
|
+
lines.push(` const url = new URL(request.url);`);
|
|
1505
|
+
lines.push(` if (url.pathname === "/api" || url.pathname.startsWith("/api/")) return false;`);
|
|
1506
|
+
lines.push(` if (/\\/[^/]+\\.[^/]+$/.test(url.pathname)) return false;`);
|
|
1507
|
+
lines.push(` const destination = request.headers.get("sec-fetch-dest");`);
|
|
1508
|
+
lines.push(` if (destination && destination !== "document" && destination !== "empty") return false;`);
|
|
1509
|
+
lines.push(` const accept = request.headers.get("accept");`);
|
|
1510
|
+
lines.push(` return !accept || accept.includes("text/html") || accept.includes("*/*");`);
|
|
1511
|
+
lines.push(`}`);
|
|
1512
|
+
lines.push("");
|
|
1513
|
+
lines.push(`function __voidNormalizeFsPath(value) {`);
|
|
1514
|
+
lines.push(` return typeof value === "string" ? value.replaceAll("\\\\", "/") : "";`);
|
|
1515
|
+
lines.push(`}`);
|
|
1516
|
+
lines.push("");
|
|
1517
|
+
lines.push(`function __voidIsAbsolutePath(value) {`);
|
|
1518
|
+
lines.push(` return value.startsWith("/") || /^[A-Za-z]:\\//.test(value);`);
|
|
1519
|
+
lines.push(`}`);
|
|
1520
|
+
lines.push("");
|
|
1521
|
+
lines.push(`function __voidDisplayPath(value) {`);
|
|
1522
|
+
lines.push(` const path = __voidNormalizeFsPath(value);`);
|
|
1523
|
+
lines.push(` const homeDir = __voidNormalizeFsPath(__VOID_HOME_DIR);`);
|
|
1524
|
+
lines.push(` const projectRoot = __voidNormalizeFsPath(__VOID_PROJECT_ROOT);`);
|
|
1525
|
+
lines.push(` if (!path) return "";`);
|
|
1526
|
+
lines.push(` if (projectRoot && (path === projectRoot || path.startsWith(projectRoot + "/"))) {`);
|
|
1527
|
+
lines.push(` const suffix = path.slice(projectRoot.length).replace(/^\\/?/, "");`);
|
|
1528
|
+
lines.push(` return suffix ? "./" + suffix : "./";`);
|
|
1529
|
+
lines.push(` }`);
|
|
1530
|
+
lines.push(` if (homeDir && (path === homeDir || path.startsWith(homeDir + "/"))) {`);
|
|
1531
|
+
lines.push(` const suffix = path.slice(homeDir.length).replace(/^\\/?/, "");`);
|
|
1532
|
+
lines.push(` return suffix ? "~/" + suffix : "~/";`);
|
|
1533
|
+
lines.push(` }`);
|
|
1534
|
+
lines.push(` return path;`);
|
|
1535
|
+
lines.push(`}`);
|
|
1536
|
+
lines.push("");
|
|
1537
|
+
lines.push(`function __voidEditorHref(value, line, column) {`);
|
|
1538
|
+
lines.push(` const path = __voidNormalizeFsPath(value);`);
|
|
1539
|
+
lines.push(` if (!__voidIsAbsolutePath(path)) return "";`);
|
|
1540
|
+
lines.push(` return "vscode://file/" + encodeURI(path).replaceAll("#", "%23").replaceAll("?", "%3F") + ":" + String(line) + ":" + String(column);`);
|
|
1541
|
+
lines.push(`}`);
|
|
1542
|
+
lines.push("");
|
|
1543
|
+
lines.push(`function __voidCreateStackFrame(functionName, file, line, column, raw) {`);
|
|
1544
|
+
lines.push(` const normalizedFile = __voidNormalizeFsPath(file);`);
|
|
1545
|
+
lines.push(` const lineNumber = Number(line);`);
|
|
1546
|
+
lines.push(` const columnNumber = Number(column);`);
|
|
1547
|
+
lines.push(` if (!normalizedFile || !Number.isFinite(lineNumber) || lineNumber <= 0) return null;`);
|
|
1548
|
+
lines.push(` const safeColumn = Number.isFinite(columnNumber) && columnNumber > 0 ? columnNumber : 1;`);
|
|
1549
|
+
lines.push(` return {`);
|
|
1550
|
+
lines.push(` functionName: functionName || "",`);
|
|
1551
|
+
lines.push(` file: normalizedFile,`);
|
|
1552
|
+
lines.push(` line: lineNumber,`);
|
|
1553
|
+
lines.push(` column: safeColumn,`);
|
|
1554
|
+
lines.push(` displayPath: __voidDisplayPath(normalizedFile),`);
|
|
1555
|
+
lines.push(` href: __voidEditorHref(normalizedFile, lineNumber, safeColumn),`);
|
|
1556
|
+
lines.push(` raw: raw || "",`);
|
|
1557
|
+
lines.push(` };`);
|
|
1558
|
+
lines.push(`}`);
|
|
1559
|
+
lines.push("");
|
|
1560
|
+
lines.push(`function __voidParseFrameLocation(value) {`);
|
|
1561
|
+
lines.push(` const match = value.match(/^(.*):(\\d+):(\\d+)$/);`);
|
|
1562
|
+
lines.push(` if (!match) return null;`);
|
|
1563
|
+
lines.push(` return __voidCreateStackFrame("", match[1], Number(match[2]), Number(match[3]), value);`);
|
|
1564
|
+
lines.push(`}`);
|
|
1565
|
+
lines.push("");
|
|
1566
|
+
lines.push(`function __voidParseStackFrames(stack) {`);
|
|
1567
|
+
lines.push(` if (!stack) return [];`);
|
|
1568
|
+
lines.push(` const frames = [];`);
|
|
1569
|
+
lines.push(` for (const rawLine of stack.split(/\\n+/)) {`);
|
|
1570
|
+
lines.push(` const line = rawLine.trim();`);
|
|
1571
|
+
lines.push(` if (!line) continue;`);
|
|
1572
|
+
lines.push(` let frame = null;`);
|
|
1573
|
+
lines.push(` let match = line.match(/^at\\s+(?:async\\s+)?(.+?)\\s+\\((.+)\\)$/);`);
|
|
1574
|
+
lines.push(` if (match) {`);
|
|
1575
|
+
lines.push(` const location = __voidParseFrameLocation(match[2]);`);
|
|
1576
|
+
lines.push(` frame = location ? { ...location, functionName: match[1] } : null;`);
|
|
1577
|
+
lines.push(` } else {`);
|
|
1578
|
+
lines.push(` match = line.match(/^at\\s+(.+)$/);`);
|
|
1579
|
+
lines.push(` if (match) {`);
|
|
1580
|
+
lines.push(` frame = __voidParseFrameLocation(match[1]);`);
|
|
1581
|
+
lines.push(` } else {`);
|
|
1582
|
+
lines.push(` match = line.match(/^(.*?)@(.+)$/);`);
|
|
1583
|
+
lines.push(` if (match) {`);
|
|
1584
|
+
lines.push(` const location = __voidParseFrameLocation(match[2]);`);
|
|
1585
|
+
lines.push(` frame = location ? { ...location, functionName: match[1] } : null;`);
|
|
1586
|
+
lines.push(` }`);
|
|
1587
|
+
lines.push(` }`);
|
|
1588
|
+
lines.push(` }`);
|
|
1589
|
+
lines.push(` if (frame) frames.push({ ...frame, raw: line });`);
|
|
1590
|
+
lines.push(` }`);
|
|
1591
|
+
lines.push(` return frames;`);
|
|
1592
|
+
lines.push(`}`);
|
|
1593
|
+
lines.push("");
|
|
1594
|
+
lines.push(`function __voidGetStackFrames(error) {`);
|
|
1595
|
+
lines.push(` if (!(error instanceof Error)) return [];`);
|
|
1596
|
+
lines.push(` if (!error.stack && typeof Error.captureStackTrace === "function") {`);
|
|
1597
|
+
lines.push(` try {`);
|
|
1598
|
+
lines.push(` Error.captureStackTrace(error, __voidGetStackFrames);`);
|
|
1599
|
+
lines.push(` } catch {`);
|
|
1600
|
+
lines.push(` // Ignore runtimes that reject captureStackTrace on thrown errors.`);
|
|
1601
|
+
lines.push(` }`);
|
|
1602
|
+
lines.push(` }`);
|
|
1603
|
+
lines.push(` const originalPrepareStackTrace = Error.prepareStackTrace;`);
|
|
1604
|
+
lines.push(` try {`);
|
|
1605
|
+
lines.push(` Error.prepareStackTrace = (_, callSites) => callSites;`);
|
|
1606
|
+
lines.push(` const structured = error.stack;`);
|
|
1607
|
+
lines.push(` if (Array.isArray(structured)) {`);
|
|
1608
|
+
lines.push(` const frames = [];`);
|
|
1609
|
+
lines.push(` for (const callSite of structured) {`);
|
|
1610
|
+
lines.push(` try {`);
|
|
1611
|
+
lines.push(` const fileName = callSite.getFileName?.() || callSite.getScriptNameOrSourceURL?.();`);
|
|
1612
|
+
lines.push(` const line = callSite.getLineNumber?.();`);
|
|
1613
|
+
lines.push(` const column = callSite.getColumnNumber?.();`);
|
|
1614
|
+
lines.push(` const functionName = callSite.getFunctionName?.() || callSite.getMethodName?.() || "";`);
|
|
1615
|
+
lines.push(` const frame = __voidCreateStackFrame(functionName, fileName || "", line, column, String(callSite));`);
|
|
1616
|
+
lines.push(` if (frame) frames.push(frame);`);
|
|
1617
|
+
lines.push(` } catch {`);
|
|
1618
|
+
lines.push(` // Ignore malformed CallSite entries and continue rendering.`);
|
|
1619
|
+
lines.push(` }`);
|
|
1620
|
+
lines.push(` }`);
|
|
1621
|
+
lines.push(` if (frames.length > 0) return frames;`);
|
|
1622
|
+
lines.push(` }`);
|
|
1623
|
+
lines.push(` } catch {`);
|
|
1624
|
+
lines.push(` // Fall through to string parsing.`);
|
|
1625
|
+
lines.push(` } finally {`);
|
|
1626
|
+
lines.push(` Error.prepareStackTrace = originalPrepareStackTrace;`);
|
|
1627
|
+
lines.push(` }`);
|
|
1628
|
+
lines.push(` return __voidParseStackFrames(typeof error.stack === "string" ? error.stack : "");`);
|
|
1629
|
+
lines.push(`}`);
|
|
1630
|
+
lines.push("");
|
|
1631
|
+
lines.push(`function __voidDescribeThrownValue(value) {`);
|
|
1632
|
+
lines.push(` if (value instanceof Error) {`);
|
|
1633
|
+
lines.push(` return {`);
|
|
1634
|
+
lines.push(` name: value.name || "Error",`);
|
|
1635
|
+
lines.push(` message: value.message || "",`);
|
|
1636
|
+
lines.push(` stack: typeof value.stack === "string" ? value.stack : "",`);
|
|
1637
|
+
lines.push(` frames: __voidGetStackFrames(value),`);
|
|
1638
|
+
lines.push(` };`);
|
|
1639
|
+
lines.push(` }`);
|
|
1640
|
+
lines.push(` if (typeof value === "string") {`);
|
|
1641
|
+
lines.push(` return { name: "Thrown value", message: value, stack: "", frames: [] };`);
|
|
1642
|
+
lines.push(` }`);
|
|
1643
|
+
lines.push(` let message = "";`);
|
|
1644
|
+
lines.push(` try {`);
|
|
1645
|
+
lines.push(` if (typeof value === "object" && value !== null) {`);
|
|
1646
|
+
lines.push(` message = JSON.stringify(value, null, 2) ?? String(value);`);
|
|
1647
|
+
lines.push(` } else {`);
|
|
1648
|
+
lines.push(` message = String(value);`);
|
|
1649
|
+
lines.push(` }`);
|
|
1650
|
+
lines.push(` } catch {`);
|
|
1651
|
+
lines.push(` message = String(value);`);
|
|
1652
|
+
lines.push(` }`);
|
|
1653
|
+
lines.push(` return { name: "Thrown value", message, stack: "", frames: [] };`);
|
|
1654
|
+
lines.push(`}`);
|
|
1655
|
+
lines.push("");
|
|
1656
|
+
lines.push(`function __voidRenderStack(entry) {`);
|
|
1657
|
+
lines.push(` if (Array.isArray(entry.frames) && entry.frames.length > 0) {`);
|
|
1658
|
+
lines.push(` const items = entry.frames.map((frame) => {`);
|
|
1659
|
+
lines.push(` const name = frame.functionName || "(anonymous)";`);
|
|
1660
|
+
lines.push(` const location = frame.displayPath + ":" + frame.line + ":" + frame.column;`);
|
|
1661
|
+
lines.push(` const body = \`<span class="__void-error-frame-function">\${__voidEscapeHtml(name)}</span><span class="__void-error-frame-location">\${__voidEscapeHtml(location)}</span>\`;`);
|
|
1662
|
+
lines.push(` if (frame.href) {`);
|
|
1663
|
+
lines.push(` return \`<a class="__void-error-frame" href="\${__voidEscapeHtml(frame.href)}">\${body}</a>\`;`);
|
|
1664
|
+
lines.push(` }`);
|
|
1665
|
+
lines.push(` return \`<div class="__void-error-frame is-static">\${body}</div>\`;`);
|
|
1666
|
+
lines.push(` }).join("");`);
|
|
1667
|
+
lines.push(` return \`<div class="__void-error-stack"><div class="__void-error-stack-label">Stack trace</div><div class="__void-error-frames">\${items}</div></div>\`;`);
|
|
1668
|
+
lines.push(` }`);
|
|
1669
|
+
lines.push(` if (!entry.stack) return "";`);
|
|
1670
|
+
lines.push(` return \`<div class="__void-error-stack"><div class="__void-error-stack-label">Stack trace</div><pre>\${__voidEscapeHtml(entry.stack)}</pre></div>\`;`);
|
|
1671
|
+
lines.push(`}`);
|
|
1672
|
+
lines.push("");
|
|
1673
|
+
lines.push(`function __voidCollectErrorChain(error) {`);
|
|
1674
|
+
lines.push(` const chain = [];`);
|
|
1675
|
+
lines.push(` const seen = new Set();`);
|
|
1676
|
+
lines.push(` let current = error;`);
|
|
1677
|
+
lines.push(` let depth = 0;`);
|
|
1678
|
+
lines.push(` while (current != null && depth < 8 && !seen.has(current)) {`);
|
|
1679
|
+
lines.push(` seen.add(current);`);
|
|
1680
|
+
lines.push(` const entry = __voidDescribeThrownValue(current);`);
|
|
1681
|
+
lines.push(` chain.push({`);
|
|
1682
|
+
lines.push(` label: depth === 0 ? "Error" : "Cause " + depth,`);
|
|
1683
|
+
lines.push(` ...entry,`);
|
|
1684
|
+
lines.push(` });`);
|
|
1685
|
+
lines.push(` current = current instanceof Error ? current.cause : undefined;`);
|
|
1686
|
+
lines.push(` depth += 1;`);
|
|
1687
|
+
lines.push(` }`);
|
|
1688
|
+
lines.push(` if (current != null) {`);
|
|
1689
|
+
lines.push(` chain.push({`);
|
|
1690
|
+
lines.push(` label: "Cause " + depth,`);
|
|
1691
|
+
lines.push(` name: "Truncated",`);
|
|
1692
|
+
lines.push(` message: "Additional nested causes were omitted.",`);
|
|
1693
|
+
lines.push(` stack: "",`);
|
|
1694
|
+
lines.push(` frames: [],`);
|
|
1695
|
+
lines.push(` });`);
|
|
1696
|
+
lines.push(` }`);
|
|
1697
|
+
lines.push(` if (chain.length === 0) {`);
|
|
1698
|
+
lines.push(` chain.push({`);
|
|
1699
|
+
lines.push(` label: "Error",`);
|
|
1700
|
+
lines.push(` name: "Unknown error",`);
|
|
1701
|
+
lines.push(` message: "No error details were available.",`);
|
|
1702
|
+
lines.push(` stack: "",`);
|
|
1703
|
+
lines.push(` frames: [],`);
|
|
1704
|
+
lines.push(` });`);
|
|
1705
|
+
lines.push(` }`);
|
|
1706
|
+
lines.push(` return chain;`);
|
|
1707
|
+
lines.push(`}`);
|
|
1708
|
+
lines.push("");
|
|
1709
|
+
lines.push(`function __voidRenderDevErrorOverlay(request, status, title, error) {`);
|
|
1710
|
+
lines.push(` const chain = __voidCollectErrorChain(error);`);
|
|
1711
|
+
lines.push(` const errorCountLabel = chain.length + (chain.length === 1 ? " Error" : " Errors");`);
|
|
1712
|
+
lines.push(` const sections = chain.map((entry, index) => {`);
|
|
1713
|
+
lines.push(` const heading = entry.name || (index === 0 ? title : "Cause");`);
|
|
1714
|
+
lines.push(` const message = entry.message || title;`);
|
|
1715
|
+
lines.push(` const stack = __voidRenderStack(entry);`);
|
|
1716
|
+
lines.push(` if (index === 0) {`);
|
|
1717
|
+
lines.push(` return \`<section class="__void-error-section is-root"><p class="__void-error-message">\${__voidEscapeHtml(message)}</p>\${stack}</section>\`;`);
|
|
1718
|
+
lines.push(` }`);
|
|
1719
|
+
lines.push(` return \`<section class="__void-error-section"><div class="__void-error-kicker">\${__voidEscapeHtml(entry.label)}</div><h2>\${__voidEscapeHtml(heading)}</h2><p>\${__voidEscapeHtml(message)}</p>\${stack}</section>\`;`);
|
|
1720
|
+
lines.push(` }).join("");`);
|
|
1721
|
+
lines.push(` return \`<style>`);
|
|
1722
|
+
lines.push(` .__void-error-overlay {`);
|
|
1723
|
+
lines.push(` position: fixed;`);
|
|
1724
|
+
lines.push(` inset: 0;`);
|
|
1725
|
+
lines.push(` z-index: 2147483647;`);
|
|
1726
|
+
lines.push(` pointer-events: none;`);
|
|
1727
|
+
lines.push(` font-family: \${__VOID_ERROR_PAGE_FONT_STACK};`);
|
|
1728
|
+
lines.push(` color: #f5f5f5;`);
|
|
1729
|
+
lines.push(` }`);
|
|
1730
|
+
lines.push(` .__void-error-backdrop {`);
|
|
1731
|
+
lines.push(` position: absolute;`);
|
|
1732
|
+
lines.push(` inset: 0;`);
|
|
1733
|
+
lines.push(` background: rgba(10, 10, 10, 0.52);`);
|
|
1734
|
+
lines.push(` backdrop-filter: blur(4px);`);
|
|
1735
|
+
lines.push(` transition: opacity 180ms ease;`);
|
|
1736
|
+
lines.push(` }`);
|
|
1737
|
+
lines.push(` .__void-error-panel {`);
|
|
1738
|
+
lines.push(` position: absolute;`);
|
|
1739
|
+
lines.push(` inset: auto auto 24px 24px;`);
|
|
1740
|
+
lines.push(` inline-size: min(760px, calc(100vw - 48px));`);
|
|
1741
|
+
lines.push(` max-block-size: min(80dvh, 920px);`);
|
|
1742
|
+
lines.push(` overflow: auto;`);
|
|
1743
|
+
lines.push(` pointer-events: auto;`);
|
|
1744
|
+
lines.push(` border-radius: 32px;`);
|
|
1745
|
+
lines.push(` corner-shape: squircle;`);
|
|
1746
|
+
lines.push(` border: 1px solid rgba(255, 255, 255, 0.14);`);
|
|
1747
|
+
lines.push(` background: linear-gradient(180deg, rgba(24, 24, 27, 0.98), rgba(9, 9, 11, 0.98));`);
|
|
1748
|
+
lines.push(` box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45);`);
|
|
1749
|
+
lines.push(` padding: 12px 16px;`);
|
|
1750
|
+
lines.push(` transition: transform 180ms ease, opacity 180ms ease;`);
|
|
1751
|
+
lines.push(` transform-origin: bottom left;`);
|
|
1752
|
+
lines.push(` }`);
|
|
1753
|
+
lines.push(` .__void-error-close {`);
|
|
1754
|
+
lines.push(` position: absolute;`);
|
|
1755
|
+
lines.push(` inset: 12px 12px auto auto;`);
|
|
1756
|
+
lines.push(` inline-size: 36px;`);
|
|
1757
|
+
lines.push(` block-size: 36px;`);
|
|
1758
|
+
lines.push(` border: 0;`);
|
|
1759
|
+
lines.push(` border-radius: 999px;`);
|
|
1760
|
+
lines.push(` background: rgba(255, 255, 255, 0.08);`);
|
|
1761
|
+
lines.push(` color: #fafafa;`);
|
|
1762
|
+
lines.push(` cursor: pointer;`);
|
|
1763
|
+
lines.push(` display: grid;`);
|
|
1764
|
+
lines.push(` place-items: center;`);
|
|
1765
|
+
lines.push(` padding: 0;`);
|
|
1766
|
+
lines.push(` }`);
|
|
1767
|
+
lines.push(` .__void-error-close:hover {`);
|
|
1768
|
+
lines.push(` background: rgba(255, 255, 255, 0.14);`);
|
|
1769
|
+
lines.push(` }`);
|
|
1770
|
+
lines.push(` .__void-error-close svg {`);
|
|
1771
|
+
lines.push(` inline-size: 16px;`);
|
|
1772
|
+
lines.push(` block-size: 16px;`);
|
|
1773
|
+
lines.push(` display: block;`);
|
|
1774
|
+
lines.push(` }`);
|
|
1775
|
+
lines.push(` .__void-error-minimized {`);
|
|
1776
|
+
lines.push(` position: absolute;`);
|
|
1777
|
+
lines.push(` inset: auto auto 24px 24px;`);
|
|
1778
|
+
lines.push(` pointer-events: none;`);
|
|
1779
|
+
lines.push(` opacity: 0;`);
|
|
1780
|
+
lines.push(` transform: scale(0.92);`);
|
|
1781
|
+
lines.push(` transform-origin: bottom left;`);
|
|
1782
|
+
lines.push(` border: 1px solid rgba(248, 113, 113, 0.35);`);
|
|
1783
|
+
lines.push(` border-radius: 32px;`);
|
|
1784
|
+
lines.push(` corner-shape: squircle;`);
|
|
1785
|
+
lines.push(` background: rgba(24, 24, 27, 0.96);`);
|
|
1786
|
+
lines.push(` color: #fca5a5;`);
|
|
1787
|
+
lines.push(` padding: 12px 8px;`);
|
|
1788
|
+
lines.push(` box-shadow: 0 18px 36px rgba(0, 0, 0, 0.28);`);
|
|
1789
|
+
lines.push(` cursor: pointer;`);
|
|
1790
|
+
lines.push(` font: 700 13px/1 \${__VOID_ERROR_PAGE_FONT_STACK};`);
|
|
1791
|
+
lines.push(` letter-spacing: 0.04em;`);
|
|
1792
|
+
lines.push(` text-transform: uppercase;`);
|
|
1793
|
+
lines.push(` transition: transform 180ms ease, opacity 180ms ease;`);
|
|
1794
|
+
lines.push(` }`);
|
|
1795
|
+
lines.push(` .__void-error-header {`);
|
|
1796
|
+
lines.push(` display: flex;`);
|
|
1797
|
+
lines.push(` flex-direction: column;`);
|
|
1798
|
+
lines.push(` gap: 6px;`);
|
|
1799
|
+
lines.push(` margin-block-end: 0;`);
|
|
1800
|
+
lines.push(` padding-right: 52px;`);
|
|
1801
|
+
lines.push(` }`);
|
|
1802
|
+
lines.push(` .__void-error-status {`);
|
|
1803
|
+
lines.push(` margin: 0;`);
|
|
1804
|
+
lines.push(` color: #fca5a5;`);
|
|
1805
|
+
lines.push(` font-size: 12px;`);
|
|
1806
|
+
lines.push(` font-weight: 700;`);
|
|
1807
|
+
lines.push(` letter-spacing: 0.12em;`);
|
|
1808
|
+
lines.push(` text-transform: uppercase;`);
|
|
1809
|
+
lines.push(` }`);
|
|
1810
|
+
lines.push(` .__void-error-section {`);
|
|
1811
|
+
lines.push(` border-top: 1px solid rgba(255, 255, 255, 0.08);`);
|
|
1812
|
+
lines.push(` padding-top: 16px;`);
|
|
1813
|
+
lines.push(` margin-top: 16px;`);
|
|
1814
|
+
lines.push(` }`);
|
|
1815
|
+
lines.push(` .__void-error-section.is-root {`);
|
|
1816
|
+
lines.push(` border-top: 0;`);
|
|
1817
|
+
lines.push(` padding-top: 0;`);
|
|
1818
|
+
lines.push(` margin-top: 0;`);
|
|
1819
|
+
lines.push(` }`);
|
|
1820
|
+
lines.push(` .__void-error-message {`);
|
|
1821
|
+
lines.push(` margin: 0;`);
|
|
1822
|
+
lines.push(` color: #fff;`);
|
|
1823
|
+
lines.push(` font-size: 1.7rem;`);
|
|
1824
|
+
lines.push(` line-height: 1.15;`);
|
|
1825
|
+
lines.push(` letter-spacing: -0.03em;`);
|
|
1826
|
+
lines.push(` white-space: pre-wrap;`);
|
|
1827
|
+
lines.push(` word-break: break-word;`);
|
|
1828
|
+
lines.push(` }`);
|
|
1829
|
+
lines.push(` .__void-error-kicker {`);
|
|
1830
|
+
lines.push(` margin-bottom: 6px;`);
|
|
1831
|
+
lines.push(` color: #fda4af;`);
|
|
1832
|
+
lines.push(` font-size: 12px;`);
|
|
1833
|
+
lines.push(` font-weight: 700;`);
|
|
1834
|
+
lines.push(` letter-spacing: 0.08em;`);
|
|
1835
|
+
lines.push(` text-transform: uppercase;`);
|
|
1836
|
+
lines.push(` }`);
|
|
1837
|
+
lines.push(` .__void-error-section h2 {`);
|
|
1838
|
+
lines.push(` margin: 0 0 8px;`);
|
|
1839
|
+
lines.push(` font-size: 1.05rem;`);
|
|
1840
|
+
lines.push(` color: #fff;`);
|
|
1841
|
+
lines.push(` }`);
|
|
1842
|
+
lines.push(` .__void-error-section p {`);
|
|
1843
|
+
lines.push(` margin: 0;`);
|
|
1844
|
+
lines.push(` color: #e4e4e7;`);
|
|
1845
|
+
lines.push(` white-space: pre-wrap;`);
|
|
1846
|
+
lines.push(` word-break: break-word;`);
|
|
1847
|
+
lines.push(` }`);
|
|
1848
|
+
lines.push(` .__void-error-stack {`);
|
|
1849
|
+
lines.push(` margin-top: 18px;`);
|
|
1850
|
+
lines.push(` border: 1px solid rgba(255, 255, 255, 0.08);`);
|
|
1851
|
+
lines.push(` border-radius: 32px;`);
|
|
1852
|
+
lines.push(` background: rgba(255, 255, 255, 0.03);`);
|
|
1853
|
+
lines.push(` corner-shape: squircle;`);
|
|
1854
|
+
lines.push(` overflow: hidden;`);
|
|
1855
|
+
lines.push(` }`);
|
|
1856
|
+
lines.push(` .__void-error-stack-label {`);
|
|
1857
|
+
lines.push(` padding: 12px 14px 10px;`);
|
|
1858
|
+
lines.push(` color: #f4f4f5;`);
|
|
1859
|
+
lines.push(` font-size: 12px;`);
|
|
1860
|
+
lines.push(` font-weight: 700;`);
|
|
1861
|
+
lines.push(` letter-spacing: 0.08em;`);
|
|
1862
|
+
lines.push(` text-transform: uppercase;`);
|
|
1863
|
+
lines.push(` border-bottom: 1px solid rgba(255, 255, 255, 0.08);`);
|
|
1864
|
+
lines.push(` }`);
|
|
1865
|
+
lines.push(` .__void-error-frames {`);
|
|
1866
|
+
lines.push(` display: flex;`);
|
|
1867
|
+
lines.push(` flex-direction: column;`);
|
|
1868
|
+
lines.push(` }`);
|
|
1869
|
+
lines.push(` .__void-error-frame {`);
|
|
1870
|
+
lines.push(` display: flex;`);
|
|
1871
|
+
lines.push(` align-items: baseline;`);
|
|
1872
|
+
lines.push(` justify-content: space-between;`);
|
|
1873
|
+
lines.push(` gap: 16px;`);
|
|
1874
|
+
lines.push(` padding: 12px 14px;`);
|
|
1875
|
+
lines.push(` color: inherit;`);
|
|
1876
|
+
lines.push(` text-decoration: none;`);
|
|
1877
|
+
lines.push(` border-top: 1px solid rgba(255, 255, 255, 0.06);`);
|
|
1878
|
+
lines.push(` }`);
|
|
1879
|
+
lines.push(` .__void-error-frame:first-child {`);
|
|
1880
|
+
lines.push(` border-top: 0;`);
|
|
1881
|
+
lines.push(` }`);
|
|
1882
|
+
lines.push(` .__void-error-frame:hover {`);
|
|
1883
|
+
lines.push(` background: rgba(255, 255, 255, 0.05);`);
|
|
1884
|
+
lines.push(` }`);
|
|
1885
|
+
lines.push(` .__void-error-frame.is-static {`);
|
|
1886
|
+
lines.push(` cursor: default;`);
|
|
1887
|
+
lines.push(` }`);
|
|
1888
|
+
lines.push(` .__void-error-frame-function {`);
|
|
1889
|
+
lines.push(` flex: 1 1 auto;`);
|
|
1890
|
+
lines.push(` min-width: 0;`);
|
|
1891
|
+
lines.push(` color: #fafafa;`);
|
|
1892
|
+
lines.push(` font: 600 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;`);
|
|
1893
|
+
lines.push(` white-space: nowrap;`);
|
|
1894
|
+
lines.push(` overflow: hidden;`);
|
|
1895
|
+
lines.push(` text-overflow: ellipsis;`);
|
|
1896
|
+
lines.push(` }`);
|
|
1897
|
+
lines.push(` .__void-error-frame-location {`);
|
|
1898
|
+
lines.push(` flex: 0 1 52%;`);
|
|
1899
|
+
lines.push(` min-width: 0;`);
|
|
1900
|
+
lines.push(` color: #fda4af;`);
|
|
1901
|
+
lines.push(` font: 500 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;`);
|
|
1902
|
+
lines.push(` text-align: right;`);
|
|
1903
|
+
lines.push(` white-space: nowrap;`);
|
|
1904
|
+
lines.push(` overflow: hidden;`);
|
|
1905
|
+
lines.push(` text-overflow: ellipsis;`);
|
|
1906
|
+
lines.push(` }`);
|
|
1907
|
+
lines.push(` .__void-error-section pre {`);
|
|
1908
|
+
lines.push(` margin: 0;`);
|
|
1909
|
+
lines.push(` padding: 12px 14px;`);
|
|
1910
|
+
lines.push(` overflow: auto;`);
|
|
1911
|
+
lines.push(` color: #e4e4e7;`);
|
|
1912
|
+
lines.push(` font: 500 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;`);
|
|
1913
|
+
lines.push(` white-space: pre-wrap;`);
|
|
1914
|
+
lines.push(` word-break: break-word;`);
|
|
1915
|
+
lines.push(` }`);
|
|
1916
|
+
lines.push(` .__void-error-overlay.is-minimized .__void-error-backdrop {`);
|
|
1917
|
+
lines.push(` opacity: 0;`);
|
|
1918
|
+
lines.push(` pointer-events: none;`);
|
|
1919
|
+
lines.push(` }`);
|
|
1920
|
+
lines.push(` .__void-error-overlay.is-minimized .__void-error-panel {`);
|
|
1921
|
+
lines.push(` opacity: 0;`);
|
|
1922
|
+
lines.push(` pointer-events: none;`);
|
|
1923
|
+
lines.push(` transform: scale(0.92);`);
|
|
1924
|
+
lines.push(` }`);
|
|
1925
|
+
lines.push(` .__void-error-overlay.is-minimized .__void-error-minimized {`);
|
|
1926
|
+
lines.push(` opacity: 1;`);
|
|
1927
|
+
lines.push(` pointer-events: auto;`);
|
|
1928
|
+
lines.push(` transform: scale(1);`);
|
|
1929
|
+
lines.push(` }`);
|
|
1930
|
+
lines.push(` @media (max-width: 720px) {`);
|
|
1931
|
+
lines.push(` .__void-error-panel {`);
|
|
1932
|
+
lines.push(` inset: auto 12px 12px 12px;`);
|
|
1933
|
+
lines.push(` inline-size: auto;`);
|
|
1934
|
+
lines.push(` max-block-size: min(70dvh, 920px);`);
|
|
1935
|
+
lines.push(` padding: 16px;`);
|
|
1936
|
+
lines.push(` }`);
|
|
1937
|
+
lines.push(` .__void-error-minimized {`);
|
|
1938
|
+
lines.push(` inset: auto auto 12px 12px;`);
|
|
1939
|
+
lines.push(` }`);
|
|
1940
|
+
lines.push(` .__void-error-frame {`);
|
|
1941
|
+
lines.push(` flex-direction: column;`);
|
|
1942
|
+
lines.push(` align-items: flex-start;`);
|
|
1943
|
+
lines.push(` }`);
|
|
1944
|
+
lines.push(` .__void-error-frame-location {`);
|
|
1945
|
+
lines.push(` text-align: left;`);
|
|
1946
|
+
lines.push(` }`);
|
|
1947
|
+
lines.push(` }`);
|
|
1948
|
+
lines.push(` </style>`);
|
|
1949
|
+
lines.push(`<aside class="__void-error-overlay" data-void-error-overlay role="dialog" aria-label="Void dev error overlay"><div class="__void-error-backdrop" data-void-error-backdrop></div><section class="__void-error-panel"><button type="button" class="__void-error-close" data-void-error-close aria-label="Minimize error overlay"><svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M3.757 3.757 12.243 12.243M12.243 3.757 3.757 12.243" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/></svg></button><header class="__void-error-header"><p class="__void-error-status">\${__voidEscapeHtml(String(status) + ": " + title)}</p></header>\${sections}</section><button type="button" class="__void-error-minimized" data-void-error-toggle aria-label="Show dev error overlay" aria-expanded="true">\${__voidEscapeHtml(errorCountLabel)}</button><script>(function(){const script=document.currentScript;if(!(script instanceof HTMLScriptElement))return;const root=script.closest("[data-void-error-overlay]");if(!(root instanceof HTMLElement))return;const backdrop=root.querySelector("[data-void-error-backdrop]");const closeButton=root.querySelector("[data-void-error-close]");const toggle=root.querySelector("[data-void-error-toggle]");const setMinimized=(minimized)=>{root.classList.toggle("is-minimized", minimized);if(toggle instanceof HTMLElement)toggle.setAttribute("aria-expanded", minimized ? "false" : "true");};if(backdrop instanceof HTMLElement)backdrop.addEventListener("click",()=>setMinimized(true));if(closeButton instanceof HTMLElement)closeButton.addEventListener("click",()=>setMinimized(true));if(toggle instanceof HTMLElement)toggle.addEventListener("click",()=>setMinimized(false));})();<\/script></aside>\`;`);
|
|
1950
|
+
lines.push(`}`);
|
|
1951
|
+
lines.push("");
|
|
1952
|
+
lines.push(`function __voidInjectDevErrorOverlay(html, overlay) {`);
|
|
1953
|
+
lines.push(` return /<\\/body\\s*>/i.test(html) ? html.replace(/<\\/body\\s*>/i, overlay + "</body>") : html + overlay;`);
|
|
1954
|
+
lines.push(`}`);
|
|
1955
|
+
lines.push("");
|
|
1956
|
+
lines.push(`function __voidRenderErrorPage(request, status, title, error) {`);
|
|
1957
|
+
lines.push(` const heading = status + ": " + title;`);
|
|
1958
|
+
lines.push(` const prefix = status + ": ";`);
|
|
1959
|
+
lines.push(` const overlay = __VOID_DEV && status === 500 && error ? __voidRenderDevErrorOverlay(request, status, title, error) : "";`);
|
|
1960
|
+
lines.push(` return \`<!doctype html>`);
|
|
1961
|
+
lines.push(`<html lang="en">`);
|
|
1962
|
+
lines.push(` <head>`);
|
|
1963
|
+
lines.push(` <meta charset="utf-8">`);
|
|
1964
|
+
lines.push(` <meta name="viewport" content="width=device-width, initial-scale=1">`);
|
|
1965
|
+
lines.push(` <title>\${__voidEscapeHtml(heading)}</title>`);
|
|
1966
|
+
lines.push(` <style>`);
|
|
1967
|
+
lines.push(` :root {`);
|
|
1968
|
+
lines.push(` color-scheme: light dark;`);
|
|
1969
|
+
lines.push(` font-family: \${__VOID_ERROR_PAGE_FONT_STACK};`);
|
|
1970
|
+
lines.push(` background: #f5f5f5;`);
|
|
1971
|
+
lines.push(` color: #171717;`);
|
|
1972
|
+
lines.push(` }`);
|
|
1973
|
+
lines.push(` * { box-sizing: border-box; }`);
|
|
1974
|
+
lines.push(` body {`);
|
|
1975
|
+
lines.push(` margin: 0;`);
|
|
1976
|
+
lines.push(` min-block-size: 100dvh;`);
|
|
1977
|
+
lines.push(` display: grid;`);
|
|
1978
|
+
lines.push(` place-items: center;`);
|
|
1979
|
+
lines.push(` padding: 24px;`);
|
|
1980
|
+
lines.push(` background: radial-gradient(circle at top, rgba(255, 255, 255, 0.96), rgba(245, 245, 245, 0.96) 52%), linear-gradient(180deg, #fafafa 0%, #f1f1f1 100%);`);
|
|
1981
|
+
lines.push(` font: 500 16px/1.5 \${__VOID_ERROR_PAGE_FONT_STACK};`);
|
|
1982
|
+
lines.push(` }`);
|
|
1983
|
+
lines.push(` main {`);
|
|
1984
|
+
lines.push(` inline-size: min(100%, 560px);`);
|
|
1985
|
+
lines.push(` padding: 32px 36px;`);
|
|
1986
|
+
lines.push(` border: 1px solid #d4d4d8;`);
|
|
1987
|
+
lines.push(` border-radius: 32px;`);
|
|
1988
|
+
lines.push(` corner-shape: squircle;`);
|
|
1989
|
+
lines.push(` background: rgba(255, 255, 255, 0.88);`);
|
|
1990
|
+
lines.push(` text-align: center;`);
|
|
1991
|
+
lines.push(` backdrop-filter: blur(12px);`);
|
|
1992
|
+
lines.push(` }`);
|
|
1993
|
+
lines.push(` h1 {`);
|
|
1994
|
+
lines.push(` margin: 0;`);
|
|
1995
|
+
lines.push(` font: 400 clamp(1.5rem, 1.2rem + 1vw, 2rem) / 1.1 \${__VOID_ERROR_PAGE_FONT_STACK};`);
|
|
1996
|
+
lines.push(` letter-spacing: -0.03em;`);
|
|
1997
|
+
lines.push(` }`);
|
|
1998
|
+
lines.push(` strong {`);
|
|
1999
|
+
lines.push(` font-weight: 600;`);
|
|
2000
|
+
lines.push(` }`);
|
|
2001
|
+
lines.push(` @media (prefers-color-scheme: dark) {`);
|
|
2002
|
+
lines.push(` :root {`);
|
|
2003
|
+
lines.push(` background: #0a0a0a;`);
|
|
2004
|
+
lines.push(` color: #f5f5f5;`);
|
|
2005
|
+
lines.push(` }`);
|
|
2006
|
+
lines.push(` body {`);
|
|
2007
|
+
lines.push(` background: radial-gradient(circle at top, rgba(38, 38, 38, 0.96), rgba(10, 10, 10, 0.96) 52%), linear-gradient(180deg, #171717 0%, #0a0a0a 100%);`);
|
|
2008
|
+
lines.push(` }`);
|
|
2009
|
+
lines.push(` main {`);
|
|
2010
|
+
lines.push(` border-color: #27272a;`);
|
|
2011
|
+
lines.push(` background: rgba(23, 23, 23, 0.88);`);
|
|
2012
|
+
lines.push(` }`);
|
|
2013
|
+
lines.push(` }`);
|
|
2014
|
+
lines.push(` </style>`);
|
|
2015
|
+
lines.push(` </head>`);
|
|
2016
|
+
lines.push(` <body>`);
|
|
2017
|
+
lines.push(` <main>`);
|
|
2018
|
+
lines.push(` <h1><strong>\${__voidEscapeHtml(prefix)}</strong>\${__voidEscapeHtml(title)}</h1>`);
|
|
2019
|
+
lines.push(` </main>`);
|
|
2020
|
+
lines.push(` \${overlay}`);
|
|
2021
|
+
lines.push(` </body>`);
|
|
2022
|
+
lines.push(`</html>\`;`);
|
|
2023
|
+
lines.push(`}`);
|
|
2024
|
+
lines.push("");
|
|
2025
|
+
lines.push(`function __voidCreateErrorResponse(request, status, title, error) {`);
|
|
2026
|
+
lines.push(` if (!__voidShouldServeHtmlErrorPage(request)) {`);
|
|
2027
|
+
lines.push(` return new Response(title, { status });`);
|
|
2028
|
+
lines.push(` }`);
|
|
2029
|
+
lines.push(` return new Response(request.method === "HEAD" ? null : __voidRenderErrorPage(request, status, title, error), {`);
|
|
2030
|
+
lines.push(` status,`);
|
|
2031
|
+
lines.push(` headers: { "content-type": "text/html; charset=utf-8" },`);
|
|
2032
|
+
lines.push(` });`);
|
|
2033
|
+
lines.push(`}`);
|
|
2034
|
+
lines.push("");
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Generate the code for the virtual route table module (virtual:void-routes).
|
|
2038
|
+
* This module creates a Hono app with:
|
|
2039
|
+
* - Middleware registered via app.use()
|
|
2040
|
+
* - Routes registered with lazy-loaded handlers and auto-conversion
|
|
2041
|
+
*/
|
|
2042
|
+
function compileRouteTable(scan, routesDir, middlewareDir, jobsDir, queuesDir, ssr, options, pages = null) {
|
|
2043
|
+
const lines = [];
|
|
2044
|
+
const auth = options.auth;
|
|
2045
|
+
const webSockets = scan.websockets ?? [];
|
|
2046
|
+
lines.push(`import { AsyncLocalStorage } from "node:async_hooks";`);
|
|
2047
|
+
lines.push(`import { Hono } from ${JSON.stringify(options.honoPath)};`);
|
|
2048
|
+
lines.push(`import { convertReturnValue } from ${JSON.stringify(options.responsePath)};`);
|
|
2049
|
+
lines.push(`import { VoidAssetRewriteError as __VoidAssetRewriteError } from ${JSON.stringify(options.handlerPath)};`);
|
|
2050
|
+
if (options.nodeTarget) lines.push(`const withRuntimeEnv = (_env, fn) => fn();`);
|
|
2051
|
+
else lines.push(`import { withRuntimeEnv } from ${JSON.stringify(options.runtimeEnvPath)};`);
|
|
2052
|
+
if (auth) {
|
|
2053
|
+
const authImports = [
|
|
2054
|
+
"runVoidAuthMiddleware",
|
|
2055
|
+
"handleVoidAuthRequest",
|
|
2056
|
+
"runVoidAuthMigrations"
|
|
2057
|
+
];
|
|
2058
|
+
if (webSockets.length > 0) authImports.push("resolveVoidAuthState as __resolveVoidAuthStateRuntime");
|
|
2059
|
+
lines.push(`import { ${authImports.join(", ")} } from ${JSON.stringify(auth.authRuntimePath)};`);
|
|
2060
|
+
if (auth.authDatabasePath) lines.push(`import { __voidAuthDatabase } from ${JSON.stringify(auth.authDatabasePath)};`);
|
|
2061
|
+
if (auth.authSchemaPath) lines.push(`import { migrationSchema as __voidAuthMigrationSchema } from ${JSON.stringify(auth.authSchemaPath)};`);
|
|
2062
|
+
if (auth.authConfigPath) lines.push(`import __voidAuthConfig from ${JSON.stringify(auth.authConfigPath)};`);
|
|
2063
|
+
}
|
|
2064
|
+
if (webSockets.length > 0 && options.webSocketRuntimePath) lines.push(`import { createWebSocketDurableObject as __createWebSocketDurableObject, handleWebSocketUpgrade as __handleWebSocketUpgrade, forwardWebSocketRequest as __forwardWebSocketRequest } from ${JSON.stringify(options.webSocketRuntimePath)};`);
|
|
2065
|
+
lines.push("");
|
|
2066
|
+
for (let i = 0; i < scan.middleware.length; i++) {
|
|
2067
|
+
const mw = scan.middleware[i];
|
|
2068
|
+
lines.push(`import middleware${i} from ${JSON.stringify(`${middlewareDir}/${mw.filePath}`)};`);
|
|
2069
|
+
}
|
|
2070
|
+
for (let i = 0; i < webSockets.length; i++) {
|
|
2071
|
+
const route = webSockets[i];
|
|
2072
|
+
lines.push(`import websocket${i} from ${JSON.stringify(`${routesDir}/${route.filePath}`)};`);
|
|
2073
|
+
}
|
|
2074
|
+
lines.push("");
|
|
2075
|
+
if (auth) {
|
|
2076
|
+
lines.push(`const __voidAuthOptions = { dialect: ${JSON.stringify(auth.dialect)}, providers: ${JSON.stringify(auth.providers)}, userConfig: ${auth.authConfigPath ? "__voidAuthConfig" : "undefined"} };`);
|
|
2077
|
+
if (webSockets.length > 0) lines.push(`const __resolveVoidAuthState = (request, env, runtime) => __resolveVoidAuthStateRuntime(request, env, runtime${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
|
|
2078
|
+
lines.push("");
|
|
2079
|
+
}
|
|
2080
|
+
lines.push(`const app = new Hono();`);
|
|
2081
|
+
lines.push(`export const requestContext = new AsyncLocalStorage();`);
|
|
2082
|
+
lines.push("const __voidOriginalUrls = new WeakMap();");
|
|
2083
|
+
lines.push("const __voidFallbackHandled = new WeakSet();");
|
|
2084
|
+
const ASSET_EXTENSIONS = [
|
|
2085
|
+
"png",
|
|
2086
|
+
"jpg",
|
|
2087
|
+
"jpeg",
|
|
2088
|
+
"gif",
|
|
2089
|
+
"webp",
|
|
2090
|
+
"avif",
|
|
2091
|
+
"svg",
|
|
2092
|
+
"ico",
|
|
2093
|
+
"css",
|
|
2094
|
+
"js",
|
|
2095
|
+
"mjs",
|
|
2096
|
+
"cjs",
|
|
2097
|
+
"woff",
|
|
2098
|
+
"woff2",
|
|
2099
|
+
"ttf",
|
|
2100
|
+
"otf",
|
|
2101
|
+
"eot",
|
|
2102
|
+
"mp4",
|
|
2103
|
+
"webm",
|
|
2104
|
+
"mp3",
|
|
2105
|
+
"wav",
|
|
2106
|
+
"pdf",
|
|
2107
|
+
"txt",
|
|
2108
|
+
"xml",
|
|
2109
|
+
"json",
|
|
2110
|
+
"wasm",
|
|
2111
|
+
"map"
|
|
2112
|
+
];
|
|
2113
|
+
const routePatterns = [];
|
|
2114
|
+
for (const r of scan.routes) routePatterns.push(r.pattern);
|
|
2115
|
+
if (pages && scan.pages.pages.length > 0) for (const p of scan.pages.pages) routePatterns.push(p.pattern);
|
|
2116
|
+
const usedExts = /* @__PURE__ */ new Set();
|
|
2117
|
+
for (const p of routePatterns) {
|
|
2118
|
+
const m = /\.([A-Za-z0-9]+)$/.exec(p);
|
|
2119
|
+
if (m) usedExts.add(m[1].toLowerCase());
|
|
2120
|
+
}
|
|
2121
|
+
const blockedExts = ASSET_EXTENSIONS.filter((e) => !usedExts.has(e));
|
|
2122
|
+
if (blockedExts.length > 0) lines.push(`const __voidAssetRewriteExtRE = /\\.(${blockedExts.join("|")})$/i;`);
|
|
2123
|
+
else lines.push("const __voidAssetRewriteExtRE = /(?!)/;");
|
|
2124
|
+
lines.push(`const __VOID_DEV_ROUTING = ${JSON.stringify(options.dev === true)};`);
|
|
2125
|
+
lines.push("function __voidAnnotateRouting(response, trace) {");
|
|
2126
|
+
lines.push(" if (!__VOID_DEV_ROUTING || !response) return response;");
|
|
2127
|
+
lines.push(" const headers = new Headers(response.headers);");
|
|
2128
|
+
lines.push(" headers.set(\"X-Void-Routing\", trace);");
|
|
2129
|
+
lines.push(" return new Response(response.body, { status: response.status, statusText: response.statusText, headers });");
|
|
2130
|
+
lines.push("}");
|
|
2131
|
+
lines.push("");
|
|
2132
|
+
lines.push("function __voidShouldMarkNoMatch(request) {");
|
|
2133
|
+
lines.push(" const url = new URL(request.url);");
|
|
2134
|
+
lines.push(" if (request.method !== \"GET\" && request.method !== \"HEAD\") return false;");
|
|
2135
|
+
lines.push(" if (url.pathname === \"/api\" || url.pathname.startsWith(\"/api/\")) return false;");
|
|
2136
|
+
lines.push(" if (/\\/[^/]+\\.[^/]+$/.test(url.pathname)) return false;");
|
|
2137
|
+
lines.push(" return true;");
|
|
2138
|
+
lines.push("}");
|
|
2139
|
+
lines.push("function __voidMarkNoMatch(request, response) {");
|
|
2140
|
+
lines.push(" if (!response || response.status !== 404 || !__voidShouldMarkNoMatch(request)) return response;");
|
|
2141
|
+
lines.push(" const headers = new Headers(response.headers);");
|
|
2142
|
+
lines.push(" headers.set(\"X-Void-No-Match\", \"1\");");
|
|
2143
|
+
lines.push(" return new Response(response.body, { status: response.status, statusText: response.statusText, headers });");
|
|
2144
|
+
lines.push("}");
|
|
2145
|
+
lines.push("");
|
|
2146
|
+
ensureMergeQueryHelperEmitted(lines);
|
|
2147
|
+
lines.push("app.use(async (c, next) => {");
|
|
2148
|
+
if (!options.nodeTarget && options.dev !== true) {
|
|
2149
|
+
lines.push(" if (!__voidOriginalUrls.has(c.req.raw)) {");
|
|
2150
|
+
lines.push(" const __h = c.req.raw.headers.get(\"X-Void-Original-URL\");");
|
|
2151
|
+
lines.push(" if (__h != null) {");
|
|
2152
|
+
lines.push(" try { __voidOriginalUrls.set(c.req.raw, new URL(__h)); } catch {}");
|
|
2153
|
+
lines.push(" }");
|
|
2154
|
+
lines.push(" }");
|
|
2155
|
+
}
|
|
2156
|
+
lines.push(" c.rewrite = (path) => {");
|
|
2157
|
+
lines.push(" if (!path.startsWith(\"/\") || path.startsWith(\"//\")) {");
|
|
2158
|
+
lines.push(" throw new Error(\"c.rewrite() only accepts internal, path-absolute destinations — they must start with a single \\\"/\\\" (a leading \\\"//\\\" parses as protocol-relative and would proxy cross-origin); use fetch() for external URLs\");");
|
|
2159
|
+
lines.push(" }");
|
|
2160
|
+
lines.push(" const __qIdx = path.indexOf(\"?\");");
|
|
2161
|
+
lines.push(" const __hIdx = path.indexOf(\"#\");");
|
|
2162
|
+
lines.push(" const __firstDelim = __qIdx === -1 ? __hIdx : __hIdx === -1 ? __qIdx : Math.min(__qIdx, __hIdx);");
|
|
2163
|
+
lines.push(" const __assetCheckPath = __firstDelim === -1 ? path : path.slice(0, __firstDelim);");
|
|
2164
|
+
lines.push(" if (__voidAssetRewriteExtRE.test(__assetCheckPath)) {");
|
|
2165
|
+
lines.push(" throw new __VoidAssetRewriteError(path);");
|
|
2166
|
+
lines.push(" }");
|
|
2167
|
+
lines.push(" const __pathOnly = __firstDelim === -1 ? path : path.slice(0, __firstDelim);");
|
|
2168
|
+
lines.push(" const __searchOnly = __qIdx === -1 ? \"\" : path.slice(__qIdx, __hIdx === -1 ? path.length : __hIdx);");
|
|
2169
|
+
lines.push(" const rewriteUrl = new URL(c.req.url);");
|
|
2170
|
+
lines.push(" rewriteUrl.pathname = __pathOnly;");
|
|
2171
|
+
lines.push(" if (__searchOnly) rewriteUrl.search = __searchOnly;");
|
|
2172
|
+
lines.push(" const rewriteReq = new Request(rewriteUrl.toString(), c.req.raw);");
|
|
2173
|
+
lines.push(" const __prior = __voidOriginalUrls.get(c.req.raw);");
|
|
2174
|
+
lines.push(" __voidOriginalUrls.set(rewriteReq, __prior ?? new URL(c.req.url));");
|
|
2175
|
+
if (options.nodeTarget) lines.push(" const __rewritePromise = app.fetch(rewriteReq, c.env);");
|
|
2176
|
+
else lines.push(" const __rewritePromise = app.fetch(rewriteReq, c.env, c.executionCtx);");
|
|
2177
|
+
lines.push(" if (!__VOID_DEV_ROUTING) return __rewritePromise;");
|
|
2178
|
+
lines.push(" return __rewritePromise.then((res) => __voidAnnotateRouting(res, \"c.rewrite \\u2192 \" + path + \" (middleware)\"));");
|
|
2179
|
+
lines.push(" };");
|
|
2180
|
+
lines.push(" const __origRedirect = c.redirect.bind(c);");
|
|
2181
|
+
lines.push(" c.redirect = (location, status) => {");
|
|
2182
|
+
lines.push(" if (typeof location === \"string\" && location.startsWith(\"/\") && !location.startsWith(\"//\")) {");
|
|
2183
|
+
lines.push(" const search = new URL(c.req.raw.url).search;");
|
|
2184
|
+
lines.push(" return __origRedirect(__mergeQueryIntoLocation(location, search), status);");
|
|
2185
|
+
lines.push(" }");
|
|
2186
|
+
lines.push(" return __origRedirect(location, status);");
|
|
2187
|
+
lines.push(" };");
|
|
2188
|
+
lines.push(" c.originalUrl = () => __voidOriginalUrls.get(c.req.raw) ?? null;");
|
|
2189
|
+
lines.push(" c.isRewritten = () => __voidOriginalUrls.has(c.req.raw);");
|
|
2190
|
+
lines.push(" await next();");
|
|
2191
|
+
lines.push("});");
|
|
2192
|
+
lines.push("");
|
|
2193
|
+
lines.push("app.use(async (c, next) => {");
|
|
2194
|
+
if (options.nodeTarget) lines.push(" await requestContext.run({ request: c.req.raw }, next);");
|
|
2195
|
+
else lines.push(" await requestContext.run({ request: c.req.raw, executionCtx: c.executionCtx }, next);");
|
|
2196
|
+
lines.push("});");
|
|
2197
|
+
lines.push("");
|
|
2198
|
+
const emitEdgeRules = options.nodeTarget || options.dev === true;
|
|
2199
|
+
if (emitEdgeRules && options.redirectRules?.length) compileRoutingRulesMiddleware(lines, options.redirectRules, options.dev === true, options.nodeTarget === true);
|
|
2200
|
+
if (emitEdgeRules && options.headerRules?.length) compileHeaderMiddleware(lines, options.headerRules);
|
|
2201
|
+
if (auth) {
|
|
2202
|
+
lines.push("app.use(async (c, next) => {");
|
|
2203
|
+
lines.push(` return runVoidAuthMiddleware(c, next, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
|
|
2204
|
+
lines.push("});");
|
|
2205
|
+
lines.push("");
|
|
2206
|
+
}
|
|
2207
|
+
for (let i = 0; i < scan.middleware.length; i++) lines.push(`app.use(middleware${i});`);
|
|
2208
|
+
if (scan.middleware.length > 0) lines.push("");
|
|
2209
|
+
if (auth) {
|
|
2210
|
+
lines.push("app.all(\"/api/auth\", async (c) => {");
|
|
2211
|
+
lines.push(` return handleVoidAuthRequest(c, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
|
|
2212
|
+
lines.push("});");
|
|
2213
|
+
lines.push("");
|
|
2214
|
+
lines.push("app.all(\"/api/auth/*\", async (c) => {");
|
|
2215
|
+
lines.push(` return handleVoidAuthRequest(c, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
|
|
2216
|
+
lines.push("});");
|
|
2217
|
+
lines.push("");
|
|
2218
|
+
}
|
|
2219
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
2220
|
+
for (const route of scan.routes) {
|
|
2221
|
+
const filePath = `${routesDir}/${route.filePath}`;
|
|
2222
|
+
const varName = `_mod_${route.filePath.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
2223
|
+
const loaderName = `_load_${route.filePath.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
2224
|
+
if (!seenFiles.has(route.filePath)) {
|
|
2225
|
+
seenFiles.add(route.filePath);
|
|
2226
|
+
if (options.dev !== true) lines.push(`let ${varName};`);
|
|
2227
|
+
lines.push(`async function ${loaderName}() {`);
|
|
2228
|
+
if (options.dev === true) lines.push(` return import(${JSON.stringify(filePath)});`);
|
|
2229
|
+
else {
|
|
2230
|
+
lines.push(` if (!${varName}) ${varName} = await import(${JSON.stringify(filePath)});`);
|
|
2231
|
+
lines.push(` return ${varName};`);
|
|
2232
|
+
}
|
|
2233
|
+
lines.push(`}`);
|
|
2234
|
+
}
|
|
2235
|
+
if (route.methods.length === 0) {
|
|
2236
|
+
lines.push(`app.all(${JSON.stringify(route.pattern)}, async (c) => {`);
|
|
2237
|
+
lines.push(` const mod = await ${loaderName}();`);
|
|
2238
|
+
lines.push(` return convertReturnValue(await mod.default(c));`);
|
|
2239
|
+
lines.push(`});`);
|
|
2240
|
+
} else for (const method of route.methods) {
|
|
2241
|
+
const honoMethod = method.toLowerCase();
|
|
2242
|
+
lines.push(`app.${honoMethod}(${JSON.stringify(route.pattern)}, async (c) => {`);
|
|
2243
|
+
lines.push(` const mod = await ${loaderName}();`);
|
|
2244
|
+
lines.push(` return convertReturnValue(await mod.${method}(c));`);
|
|
2245
|
+
lines.push(`});`);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
lines.push("");
|
|
2249
|
+
if (webSockets.length > 0) {
|
|
2250
|
+
for (let i = 0; i < webSockets.length; i++) {
|
|
2251
|
+
const route = webSockets[i];
|
|
2252
|
+
lines.push(`export const ${route.className} = __createWebSocketDurableObject(websocket${i}, ${JSON.stringify({
|
|
2253
|
+
pattern: route.pattern,
|
|
2254
|
+
params: route.params,
|
|
2255
|
+
className: route.className,
|
|
2256
|
+
bindingName: route.bindingName
|
|
2257
|
+
})}, ${auth ? "__voidAuthOptions" : "undefined"}, ${auth ? "__resolveVoidAuthState" : "undefined"});`);
|
|
2258
|
+
}
|
|
2259
|
+
lines.push("");
|
|
2260
|
+
lines.push(`const __voidWebSocketRoutes = ${JSON.stringify(webSockets.map((route) => ({
|
|
2261
|
+
pattern: route.pattern,
|
|
2262
|
+
params: route.params,
|
|
2263
|
+
className: route.className,
|
|
2264
|
+
bindingName: route.bindingName
|
|
2265
|
+
})))};`);
|
|
2266
|
+
lines.push("export async function __voidHandleWebSocket(request, env) {");
|
|
2267
|
+
lines.push(" return __handleWebSocketUpgrade(request, env, __voidWebSocketRoutes);");
|
|
2268
|
+
lines.push("}");
|
|
2269
|
+
lines.push("");
|
|
2270
|
+
for (let i = 0; i < webSockets.length; i++) {
|
|
2271
|
+
const route = webSockets[i];
|
|
2272
|
+
lines.push(`app.all(${JSON.stringify(route.pattern)}, async (c) => {`);
|
|
2273
|
+
lines.push(` return __forwardWebSocketRequest(c.req.raw, c.env, __voidWebSocketRoutes[${i}]);`);
|
|
2274
|
+
lines.push("});");
|
|
2275
|
+
}
|
|
2276
|
+
lines.push("");
|
|
2277
|
+
} else {
|
|
2278
|
+
lines.push("export async function __voidHandleWebSocket() { return null; }");
|
|
2279
|
+
lines.push("");
|
|
2280
|
+
}
|
|
2281
|
+
if (pages && scan.pages.pages.length > 0) compilePageRoutes(lines, scan, pages, options);
|
|
2282
|
+
compilePrerenderCollector(lines, scan, ssr, options);
|
|
2283
|
+
if (ssr) {
|
|
2284
|
+
const clientEntryUrl = `/${ssr.clientEntryRelativePath}`;
|
|
2285
|
+
lines.push(`const SSR_CLIENT_ENTRY_ID = ${JSON.stringify(ssr.clientEntryRelativePath)};`);
|
|
2286
|
+
lines.push(`const SSR_CLIENT_ENTRY_URL = ${JSON.stringify(clientEntryUrl)};`);
|
|
2287
|
+
lines.push(`let cachedSsrManifest = null;`);
|
|
2288
|
+
lines.push("");
|
|
2289
|
+
lines.push(`function escapeHtml(value) {`);
|
|
2290
|
+
lines.push(` return String(value).replace(/"/g, """);`);
|
|
2291
|
+
lines.push(`}`);
|
|
2292
|
+
lines.push("");
|
|
2293
|
+
lines.push(`function getDevClientTags() {`);
|
|
2294
|
+
lines.push(` const preloadTags = [\`<script type="module" src="/@vite/client"><\/script>\`];`);
|
|
2295
|
+
lines.push(` preloadTags.push(\`${BINDING_HINT_SCRIPT}\`);`);
|
|
2296
|
+
if (options.reactRefresh) lines.push(` preloadTags.push(\`<script type="module">
|
|
2297
|
+
import RefreshRuntime from "/@react-refresh";
|
|
2298
|
+
RefreshRuntime.injectIntoGlobalHook(window);
|
|
2299
|
+
window.$RefreshReg$ = () => {};
|
|
2300
|
+
window.$RefreshSig$ = () => (type) => type;
|
|
2301
|
+
window.__vite_plugin_react_preamble_installed__ = true;
|
|
2302
|
+
<\/script>\`);`);
|
|
2303
|
+
lines.push(` const bodyTag = \`<script type="module" src="\${escapeHtml(SSR_CLIENT_ENTRY_URL)}"><\/script>\`;`);
|
|
2304
|
+
lines.push(` return { css: "", preloads: preloadTags.join("\\n"), body: bodyTag };`);
|
|
2305
|
+
lines.push(`}`);
|
|
2306
|
+
lines.push("");
|
|
2307
|
+
lines.push(`function collectManifestFiles(manifest, entryId) {`);
|
|
2308
|
+
lines.push(` const seen = new Set();`);
|
|
2309
|
+
lines.push(` const toVisit = [entryId];`);
|
|
2310
|
+
lines.push(` while (toVisit.length > 0) {`);
|
|
2311
|
+
lines.push(` const currentId = toVisit.pop();`);
|
|
2312
|
+
lines.push(` if (seen.has(currentId)) continue;`);
|
|
2313
|
+
lines.push(` seen.add(currentId);`);
|
|
2314
|
+
lines.push(` const chunk = manifest[currentId];`);
|
|
2315
|
+
lines.push(` if (!chunk || !Array.isArray(chunk.imports)) continue;`);
|
|
2316
|
+
lines.push(` for (const importedId of chunk.imports) toVisit.push(importedId);`);
|
|
2317
|
+
lines.push(` }`);
|
|
2318
|
+
lines.push(` return Array.from(seen);`);
|
|
2319
|
+
lines.push(`}`);
|
|
2320
|
+
lines.push("");
|
|
2321
|
+
lines.push(`async function loadSsrManifest(request, env) {`);
|
|
2322
|
+
lines.push(` if (cachedSsrManifest) return cachedSsrManifest;`);
|
|
2323
|
+
lines.push(` if (typeof globalThis.__VITE_MANIFEST__ === "object" && globalThis.__VITE_MANIFEST__) {`);
|
|
2324
|
+
lines.push(` cachedSsrManifest = globalThis.__VITE_MANIFEST__;`);
|
|
2325
|
+
lines.push(` return cachedSsrManifest;`);
|
|
2326
|
+
lines.push(` }`);
|
|
2327
|
+
lines.push(` const assets = env?.ASSETS;`);
|
|
2328
|
+
lines.push(` if (!assets || typeof assets.fetch !== "function") return null;`);
|
|
2329
|
+
lines.push(` const manifestUrl = new URL("/.vite/manifest.json", request.url);`);
|
|
2330
|
+
lines.push(` const response = await assets.fetch(new Request(manifestUrl.toString()));`);
|
|
2331
|
+
lines.push(` if (!response.ok) return null;`);
|
|
2332
|
+
lines.push(` cachedSsrManifest = await response.json();`);
|
|
2333
|
+
lines.push(` return cachedSsrManifest;`);
|
|
2334
|
+
lines.push(`}`);
|
|
2335
|
+
lines.push("");
|
|
2336
|
+
lines.push(`async function getClientAssetTags(request, env) {`);
|
|
2337
|
+
lines.push(` const manifest = await loadSsrManifest(request, env);`);
|
|
2338
|
+
lines.push(` if (!manifest || typeof manifest !== "object") {`);
|
|
2339
|
+
lines.push(` if (!import.meta.env.DEV) return { css: "", preloads: "", body: "" };`);
|
|
2340
|
+
lines.push(` return getDevClientTags();`);
|
|
2341
|
+
lines.push(` }`);
|
|
2342
|
+
lines.push(` const entry = manifest[SSR_CLIENT_ENTRY_ID];`);
|
|
2343
|
+
lines.push(` if (!entry || typeof entry !== "object" || typeof entry.file !== "string") {`);
|
|
2344
|
+
lines.push(` return { css: "", preloads: "", body: "" };`);
|
|
2345
|
+
lines.push(` }`);
|
|
2346
|
+
lines.push(` const ids = collectManifestFiles(manifest, SSR_CLIENT_ENTRY_ID);`);
|
|
2347
|
+
lines.push(` const cssFiles = new Set();`);
|
|
2348
|
+
lines.push(` const preloadFiles = new Set();`);
|
|
2349
|
+
lines.push(` for (const id of ids) {`);
|
|
2350
|
+
lines.push(` const chunk = manifest[id];`);
|
|
2351
|
+
lines.push(` if (!chunk || typeof chunk !== "object") continue;`);
|
|
2352
|
+
lines.push(` if (Array.isArray(chunk.css)) {`);
|
|
2353
|
+
lines.push(` for (const cssFile of chunk.css) cssFiles.add(cssFile);`);
|
|
2354
|
+
lines.push(` }`);
|
|
2355
|
+
lines.push(` if (id !== SSR_CLIENT_ENTRY_ID && typeof chunk.file === "string") {`);
|
|
2356
|
+
lines.push(` preloadFiles.add(chunk.file);`);
|
|
2357
|
+
lines.push(` }`);
|
|
2358
|
+
lines.push(` }`);
|
|
2359
|
+
lines.push(` const cssTags = [];`);
|
|
2360
|
+
lines.push(` for (const cssFile of cssFiles) {`);
|
|
2361
|
+
lines.push(` cssTags.push(\`<link rel="stylesheet" href="/\${escapeHtml(cssFile)}">\`);`);
|
|
2362
|
+
lines.push(` }`);
|
|
2363
|
+
lines.push(` const preloadTags = [];`);
|
|
2364
|
+
lines.push(` for (const preloadFile of preloadFiles) {`);
|
|
2365
|
+
lines.push(` preloadTags.push(\`<link rel="modulepreload" href="/\${escapeHtml(preloadFile)}">\`);`);
|
|
2366
|
+
lines.push(` }`);
|
|
2367
|
+
lines.push(` const bodyTag = \`<script type="module" src="/\${escapeHtml(entry.file)}"><\/script>\`;`);
|
|
2368
|
+
lines.push(` return { css: cssTags.join("\\n"), preloads: preloadTags.join("\\n"), body: bodyTag };`);
|
|
2369
|
+
lines.push(`}`);
|
|
2370
|
+
lines.push("");
|
|
2371
|
+
lines.push(`function shouldHandleSsrRequest(c) {`);
|
|
2372
|
+
lines.push(` const path = c.req.path;`);
|
|
2373
|
+
lines.push(` if (path === "/api" || path.startsWith("/api/")) return false;`);
|
|
2374
|
+
lines.push(` if (/\\/[^/]+\\.[^/]+$/.test(path)) return false;`);
|
|
2375
|
+
lines.push(` const destination = c.req.header("sec-fetch-dest");`);
|
|
2376
|
+
lines.push(` if (destination && destination !== "document" && destination !== "empty") return false;`);
|
|
2377
|
+
lines.push(` return true;`);
|
|
2378
|
+
lines.push(`}`);
|
|
2379
|
+
lines.push("");
|
|
2380
|
+
lines.push(`async function fetchFromAssets(c) {`);
|
|
2381
|
+
lines.push(` const assets = c.env?.ASSETS;`);
|
|
2382
|
+
lines.push(` if (!assets || typeof assets.fetch !== "function") {`);
|
|
2383
|
+
lines.push(` return __voidMarkNoMatch(c.req.raw, new Response("Not Found", { status: 404 }));`);
|
|
2384
|
+
lines.push(` }`);
|
|
2385
|
+
lines.push(` const response = await assets.fetch(c.req.raw);`);
|
|
2386
|
+
lines.push(` return response.status === 404 ? __voidMarkNoMatch(c.req.raw, new Response("Not Found", { status: 404 })) : response;`);
|
|
2387
|
+
lines.push(`}`);
|
|
2388
|
+
lines.push("");
|
|
2389
|
+
lines.push(`async function renderSsrResponse(c, mod) {`);
|
|
2390
|
+
lines.push(` const render = typeof mod.render === "function" ? mod.render : mod.default;`);
|
|
2391
|
+
lines.push(` if (typeof render !== "function") {`);
|
|
2392
|
+
lines.push(` throw new Error("ssr: Invalid entry. Export render(c, assetTags).");`);
|
|
2393
|
+
lines.push(` }`);
|
|
2394
|
+
lines.push(` const assetTags = await getClientAssetTags(c.req.raw, c.env);`);
|
|
2395
|
+
lines.push(` return render(c, assetTags);`);
|
|
2396
|
+
lines.push(`}`);
|
|
2397
|
+
lines.push("");
|
|
2398
|
+
lines.push(`app.notFound(async (c) => {`);
|
|
2399
|
+
if (options.nodeTarget && options.fallbackRules?.length) emitFallbackRulesCheck(lines, options.fallbackRules, options.dev === true, " ", options.nodeTarget === true);
|
|
2400
|
+
lines.push(` if (!shouldHandleSsrRequest(c)) {`);
|
|
2401
|
+
if (!options.nodeTarget && options.dev === true && options.fallbackRules?.length) {
|
|
2402
|
+
lines.push(` const __assetRes = await fetchFromAssets(c);`);
|
|
2403
|
+
lines.push(` if (__assetRes.status !== 404) return __assetRes;`);
|
|
2404
|
+
emitFallbackRulesCheck(lines, options.fallbackRules, options.dev === true, " ", false);
|
|
2405
|
+
lines.push(` return __assetRes;`);
|
|
2406
|
+
} else lines.push(` return fetchFromAssets(c);`);
|
|
2407
|
+
lines.push(` }`);
|
|
2408
|
+
lines.push(` const mod = await import(${JSON.stringify(ssr.serverEntryPath)});`);
|
|
2409
|
+
lines.push(` return renderSsrResponse(c, mod);`);
|
|
2410
|
+
lines.push(`});`);
|
|
2411
|
+
lines.push("");
|
|
2412
|
+
} else if ((options.nodeTarget || options.dev === true) && options.fallbackRules?.length && !(pages && scan.pages.pages.length > 0)) {
|
|
2413
|
+
lines.push(`app.notFound(async (c) => {`);
|
|
2414
|
+
if (!options.nodeTarget && options.dev === true) {
|
|
2415
|
+
lines.push(` const assets = c.env?.ASSETS;`);
|
|
2416
|
+
lines.push(` if (assets && typeof assets.fetch === "function") {`);
|
|
2417
|
+
lines.push(` const __assetRes = await assets.fetch(c.req.raw);`);
|
|
2418
|
+
lines.push(` if (__assetRes.status !== 404) return __assetRes;`);
|
|
2419
|
+
lines.push(` }`);
|
|
2420
|
+
}
|
|
2421
|
+
emitFallbackRulesCheck(lines, options.fallbackRules, options.dev === true, " ", options.nodeTarget === true);
|
|
2422
|
+
lines.push(` return __voidMarkNoMatch(c.req.raw, new Response("Not Found", { status: 404 }));`);
|
|
2423
|
+
lines.push(`});`);
|
|
2424
|
+
lines.push("");
|
|
2425
|
+
}
|
|
2426
|
+
if (scan.jobs.length > 0) {
|
|
2427
|
+
for (let i = 0; i < scan.jobs.length; i++) {
|
|
2428
|
+
const job = scan.jobs[i];
|
|
2429
|
+
lines.push(`const job${i}Crons = ${JSON.stringify(job.crons)};`);
|
|
2430
|
+
}
|
|
2431
|
+
lines.push("");
|
|
2432
|
+
lines.push("export async function scheduled(controller, env, ctx) {");
|
|
2433
|
+
lines.push(" switch (controller.cron) {");
|
|
2434
|
+
for (let i = 0; i < scan.jobs.length; i++) {
|
|
2435
|
+
const job = scan.jobs[i];
|
|
2436
|
+
const filePath = JSON.stringify(`${jobsDir}/${job.filePath}`);
|
|
2437
|
+
for (let j = 0; j < job.crons.length; j++) if (j < job.crons.length - 1) lines.push(` case job${i}Crons[${j}]:`);
|
|
2438
|
+
else lines.push(` case job${i}Crons[${j}]: {`);
|
|
2439
|
+
lines.push(` const mod = await import(${filePath});`);
|
|
2440
|
+
lines.push(" if (typeof mod.default === 'function') {");
|
|
2441
|
+
lines.push(" await mod.default(controller, env, ctx);");
|
|
2442
|
+
lines.push(" }");
|
|
2443
|
+
lines.push(" return;");
|
|
2444
|
+
lines.push(" }");
|
|
2445
|
+
}
|
|
2446
|
+
lines.push(" default:");
|
|
2447
|
+
lines.push(" return;");
|
|
2448
|
+
lines.push(" }");
|
|
2449
|
+
lines.push("}");
|
|
2450
|
+
lines.push("");
|
|
2451
|
+
} else {
|
|
2452
|
+
lines.push("export async function scheduled() {}");
|
|
2453
|
+
lines.push("");
|
|
2454
|
+
}
|
|
2455
|
+
if (scan.jobs.length > 0) {
|
|
2456
|
+
lines.push("const __scheduledHandler = async (c) => {");
|
|
2457
|
+
lines.push(" const __token = c.req.header(\"x-void-internal\");");
|
|
2458
|
+
lines.push(" const __expected = __getRawEnv()?.__VOID_PROXY_TOKEN;");
|
|
2459
|
+
lines.push(" const __devToken = c.req.header(\"x-void-dev-trigger\");");
|
|
2460
|
+
lines.push(" if (__expected ? __token !== __expected : import.meta.env.DEV ? __devToken !== __VOID_DEV_TRIGGER_TOKEN : __token !== __VOID_BUILD_KEY) {");
|
|
2461
|
+
lines.push(" return c.json({ error: \"unauthorized\" }, 401);");
|
|
2462
|
+
lines.push(" }");
|
|
2463
|
+
lines.push(" const { cron, scheduledTime } = await c.req.json();");
|
|
2464
|
+
lines.push(" const controller = { cron, scheduledTime, noRetry() {} };");
|
|
2465
|
+
lines.push(" await scheduled(controller, c.env, c.executionCtx);");
|
|
2466
|
+
lines.push(" return c.json({ ok: true });");
|
|
2467
|
+
lines.push("};");
|
|
2468
|
+
lines.push("app.post(\"/__void/scheduled\", __scheduledHandler);");
|
|
2469
|
+
lines.push("");
|
|
2470
|
+
}
|
|
2471
|
+
if (scan.queues.length > 0) {
|
|
2472
|
+
for (let i = 0; i < scan.queues.length; i++) lines.push(`const queue${i}Name = ${JSON.stringify(scan.queues[i].name)};`);
|
|
2473
|
+
lines.push("");
|
|
2474
|
+
lines.push("export async function __processQueue(payload, env) {");
|
|
2475
|
+
lines.push(" const queueSuffix = payload.queue;");
|
|
2476
|
+
lines.push(" const __decisions = new Map();");
|
|
2477
|
+
lines.push(" function __wrapMsg(m) {");
|
|
2478
|
+
lines.push(" let decided = false;");
|
|
2479
|
+
lines.push(" return {");
|
|
2480
|
+
lines.push(" id: m.id, timestamp: new Date(m.timestamp), body: m.body, attempts: m.attempts,");
|
|
2481
|
+
lines.push(" ack() { if (!decided) { decided = true; __decisions.set(m.id, { action: \"ack\" }); } },");
|
|
2482
|
+
lines.push(" retry(opts) { if (!decided) { decided = true; __decisions.set(m.id, { action: \"retry\", ...(opts?.delaySeconds != null ? { delaySeconds: opts.delaySeconds } : {}) }); } },");
|
|
2483
|
+
lines.push(" };");
|
|
2484
|
+
lines.push(" }");
|
|
2485
|
+
lines.push(" const __msgs = payload.messages.map(__wrapMsg);");
|
|
2486
|
+
lines.push(" const batch = {");
|
|
2487
|
+
lines.push(" queue: queueSuffix,");
|
|
2488
|
+
lines.push(" messages: __msgs,");
|
|
2489
|
+
lines.push(" ackAll() { for (const m of __msgs) m.ack(); },");
|
|
2490
|
+
lines.push(" retryAll(opts) { for (const m of __msgs) m.retry(opts); },");
|
|
2491
|
+
lines.push(" };");
|
|
2492
|
+
lines.push(" let __ok = true;");
|
|
2493
|
+
lines.push(" try {");
|
|
2494
|
+
for (let i = 0; i < scan.queues.length; i++) {
|
|
2495
|
+
const q = scan.queues[i];
|
|
2496
|
+
const cond = i === 0 ? "if" : "} else if";
|
|
2497
|
+
lines.push(` ${cond} (queueSuffix === queue${i}Name || queueSuffix.endsWith("-" + queue${i}Name)) {`);
|
|
2498
|
+
lines.push(` const mod = await import(${JSON.stringify(`${queuesDir}/${q.filePath}`)});`);
|
|
2499
|
+
lines.push(" if (typeof mod.default === 'function') await mod.default(batch, env);");
|
|
2500
|
+
}
|
|
2501
|
+
lines.push(" }");
|
|
2502
|
+
lines.push(" } catch { __ok = false; }");
|
|
2503
|
+
lines.push(" return { ok: __ok, ...(__decisions.size > 0 ? { decisions: Object.fromEntries(__decisions) } : {}) };");
|
|
2504
|
+
lines.push("}");
|
|
2505
|
+
lines.push("");
|
|
2506
|
+
lines.push("const __queueHandler = async (c) => {");
|
|
2507
|
+
lines.push(" const __token = c.req.header(\"x-void-internal\");");
|
|
2508
|
+
lines.push(" const __expected = __getRawEnv()?.__VOID_PROXY_TOKEN;");
|
|
2509
|
+
lines.push(" const __devToken = c.req.header(\"x-void-dev-trigger\");");
|
|
2510
|
+
lines.push(" if (__expected ? __token !== __expected : import.meta.env.DEV ? __devToken !== __VOID_DEV_TRIGGER_TOKEN : __token !== __VOID_BUILD_KEY) {");
|
|
2511
|
+
lines.push(" return c.json({ error: \"unauthorized\" }, 401);");
|
|
2512
|
+
lines.push(" }");
|
|
2513
|
+
lines.push(" const payload = await c.req.json();");
|
|
2514
|
+
lines.push(" const result = await __processQueue(payload, c.env);");
|
|
2515
|
+
lines.push(" return c.json(result);");
|
|
2516
|
+
lines.push("};");
|
|
2517
|
+
lines.push("app.post(\"/__void/queue\", __queueHandler);");
|
|
2518
|
+
lines.push("");
|
|
2519
|
+
}
|
|
2520
|
+
if (options.migrationHandlerPath && (options.migrations?.length || auth)) {
|
|
2521
|
+
lines.push(`import { createMigrationHandler as __createMigrationHandler } from ${JSON.stringify(options.migrationHandlerPath)};`);
|
|
2522
|
+
lines.push(`const __migrations = ${JSON.stringify(options.migrations)};`);
|
|
2523
|
+
lines.push(`const __migrationDialect = ${JSON.stringify(options.migrationDialect ?? "sqlite")};`);
|
|
2524
|
+
lines.push("app.post(\"/__void/migrate\", async (c) => {");
|
|
2525
|
+
lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
|
|
2526
|
+
lines.push(" __getRawEnv(),");
|
|
2527
|
+
lines.push(" __migrations,");
|
|
2528
|
+
lines.push(" __migrationDialect,");
|
|
2529
|
+
if (auth) lines.push(` () => runVoidAuthMigrations(new Request(c.req.url), __getRawEnv(), __voidAuthOptions${auth.authSchemaPath ? ", __voidAuthMigrationSchema" : ""}),`);
|
|
2530
|
+
else lines.push(" undefined,");
|
|
2531
|
+
lines.push(" );");
|
|
2532
|
+
lines.push(" return __voidMigrationHandler(c.req.raw);");
|
|
2533
|
+
lines.push("});");
|
|
2534
|
+
lines.push("");
|
|
2535
|
+
}
|
|
2536
|
+
if (options.bindingHandlerPath) {
|
|
2537
|
+
lines.push(`import { createBindingHandler as __createBindingHandler } from ${JSON.stringify(options.bindingHandlerPath)};`);
|
|
2538
|
+
lines.push("let __voidBindingApp;");
|
|
2539
|
+
lines.push("app.all(\"/__void/*\", async (c) => {");
|
|
2540
|
+
lines.push(" if (!__voidBindingApp) __voidBindingApp = __createBindingHandler(__getRawEnv());");
|
|
2541
|
+
lines.push(" const url = new URL(c.req.url);");
|
|
2542
|
+
lines.push(" url.pathname = url.pathname.slice(7);");
|
|
2543
|
+
lines.push(" return __voidBindingApp.fetch(new Request(url.toString(), c.req.raw));");
|
|
2544
|
+
lines.push("});");
|
|
2545
|
+
lines.push("");
|
|
2546
|
+
}
|
|
2547
|
+
lines.push("const __rawEnvStore = new AsyncLocalStorage();");
|
|
2548
|
+
lines.push("export function __withRawEnv(env, fn) { return __rawEnvStore.run(env, fn); }");
|
|
2549
|
+
lines.push("function __getRawEnv() { return __rawEnvStore.getStore(); }");
|
|
2550
|
+
lines.push(`export const __VOID_BUILD_KEY = ${JSON.stringify(crypto.randomUUID())};`);
|
|
2551
|
+
lines.push(`export const __VOID_DEV_TRIGGER_TOKEN = ${JSON.stringify(options.devTriggerToken ?? crypto.randomUUID())};`);
|
|
2552
|
+
lines.push("");
|
|
2553
|
+
lines.push(`export default app;`);
|
|
2554
|
+
return lines.join("\n");
|
|
2555
|
+
}
|
|
2556
|
+
function compilePrerenderCollector(lines, scan, ssr, options) {
|
|
2557
|
+
const staticPaths = scan.pages.pages.filter((page) => page.prerender && page.params.length === 0 && !page.catchAll).map((page) => page.pattern);
|
|
2558
|
+
const dynamicPages = scan.pages.pages.filter((page) => page.prerender && page.hasGetPrerenderPaths && (page.params.length > 0 || page.catchAll));
|
|
2559
|
+
const explicitPaths = options.explicitPrerenderPaths ?? [];
|
|
2560
|
+
lines.push(`function __voidBuildPrerenderPath(pattern, params) {`);
|
|
2561
|
+
lines.push(` let path = pattern;`);
|
|
2562
|
+
lines.push(` for (const [key, value] of Object.entries(params)) {`);
|
|
2563
|
+
lines.push(` path = path.replace(":" + key + "{.+}", value);`);
|
|
2564
|
+
lines.push(` path = path.replace(":" + key, value);`);
|
|
2565
|
+
lines.push(` }`);
|
|
2566
|
+
lines.push(` return path;`);
|
|
2567
|
+
lines.push(`}`);
|
|
2568
|
+
lines.push("");
|
|
2569
|
+
lines.push(`export async function __voidCollectPrerenderPaths(request, env, ctx) {`);
|
|
2570
|
+
lines.push(` return withRuntimeEnv(env, async () => {`);
|
|
2571
|
+
if (options.nodeTarget) lines.push(` return requestContext.run({ request }, async () => {`);
|
|
2572
|
+
else lines.push(` return requestContext.run({ request, executionCtx: ctx }, async () => {`);
|
|
2573
|
+
lines.push(` const paths = [];`);
|
|
2574
|
+
for (const path of staticPaths) lines.push(` paths.push(${JSON.stringify(path)});`);
|
|
2575
|
+
for (const page of dynamicPages) {
|
|
2576
|
+
const loaderName = `_pload_${page.componentId.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
2577
|
+
lines.push(` {`);
|
|
2578
|
+
lines.push(` const mod = await ${loaderName}();`);
|
|
2579
|
+
lines.push(` if (typeof mod.getPrerenderPaths === "function") {`);
|
|
2580
|
+
lines.push(` const paramSets = await mod.getPrerenderPaths();`);
|
|
2581
|
+
lines.push(` if (Array.isArray(paramSets)) {`);
|
|
2582
|
+
lines.push(` for (const params of paramSets) {`);
|
|
2583
|
+
lines.push(` paths.push(__voidBuildPrerenderPath(${JSON.stringify(page.pattern)}, params));`);
|
|
2584
|
+
lines.push(` }`);
|
|
2585
|
+
lines.push(` }`);
|
|
2586
|
+
lines.push(` }`);
|
|
2587
|
+
lines.push(` }`);
|
|
2588
|
+
}
|
|
2589
|
+
if (ssr) {
|
|
2590
|
+
lines.push(` {`);
|
|
2591
|
+
lines.push(` const mod = await import(${JSON.stringify(ssr.serverEntryPath)});`);
|
|
2592
|
+
lines.push(` if (typeof mod.getPrerenderPaths === "function") {`);
|
|
2593
|
+
lines.push(` const ssrPaths = await mod.getPrerenderPaths();`);
|
|
2594
|
+
lines.push(` if (Array.isArray(ssrPaths)) {`);
|
|
2595
|
+
lines.push(` for (const path of ssrPaths) {`);
|
|
2596
|
+
lines.push(` if (typeof path === "string") paths.push(path);`);
|
|
2597
|
+
lines.push(` }`);
|
|
2598
|
+
lines.push(` }`);
|
|
2599
|
+
lines.push(` }`);
|
|
2600
|
+
lines.push(` }`);
|
|
2601
|
+
}
|
|
2602
|
+
for (const path of explicitPaths) lines.push(` paths.push(${JSON.stringify(path)});`);
|
|
2603
|
+
lines.push(` return [...new Set(paths)];`);
|
|
2604
|
+
lines.push(` });`);
|
|
2605
|
+
lines.push(` });`);
|
|
2606
|
+
lines.push(`}`);
|
|
2607
|
+
lines.push("");
|
|
2608
|
+
}
|
|
2609
|
+
function compilePageRoutes(lines, scan, pages, options) {
|
|
2610
|
+
lines.push(`import { handlePageGet, handlePageMutation } from ${JSON.stringify(pages.protocolPath)};`);
|
|
2611
|
+
lines.push(`import { renderHeadToString, renderHtmlAttrs, renderBodyAttrs } from ${JSON.stringify(pages.headPath)};`);
|
|
2612
|
+
lines.push(`import { renderPage } from "virtual:void-pages-renderer";`);
|
|
2613
|
+
const hasIslandPages = scan.pages.pages.some((p) => p.island);
|
|
2614
|
+
if (hasIslandPages) lines.push(`import { renderIslandPage } from "virtual:void-islands-renderer";`);
|
|
2615
|
+
lines.push("");
|
|
2616
|
+
lines.push(`const __voidHeadConfig = ${JSON.stringify(pages.headConfig ?? null)};`);
|
|
2617
|
+
lines.push("");
|
|
2618
|
+
pushDefaultErrorPageHelpers(lines, options);
|
|
2619
|
+
const clientEntryId = pages.clientEntryId;
|
|
2620
|
+
lines.push(`const PAGES_CLIENT_ENTRY_ID = ${JSON.stringify(clientEntryId)};`);
|
|
2621
|
+
lines.push(`const PAGES_CLIENT_ENTRY_URL = ${JSON.stringify("/@id/__x00__" + clientEntryId)};`);
|
|
2622
|
+
lines.push(`let cachedPagesManifest = null;`);
|
|
2623
|
+
lines.push("");
|
|
2624
|
+
lines.push(`function escapeHtmlAttr(value) {`);
|
|
2625
|
+
lines.push(` return String(value).replace(/"/g, """);`);
|
|
2626
|
+
lines.push(`}`);
|
|
2627
|
+
lines.push("");
|
|
2628
|
+
lines.push(`function getPagesDevClientTags() {`);
|
|
2629
|
+
lines.push(` const preloads = \`<script type="module" src="/@vite/client"><\/script>\\n${BINDING_HINT_SCRIPT}\`;`);
|
|
2630
|
+
lines.push(` const bodyTag = \`<script type="module" src="\${escapeHtmlAttr(PAGES_CLIENT_ENTRY_URL)}"><\/script>\`;`);
|
|
2631
|
+
lines.push(` return { css: "", preloads, body: bodyTag };`);
|
|
2632
|
+
lines.push(`}`);
|
|
2633
|
+
lines.push("");
|
|
2634
|
+
lines.push(`function collectPagesManifestFiles(manifest, entryId) {`);
|
|
2635
|
+
lines.push(` const seen = new Set();`);
|
|
2636
|
+
lines.push(` const toVisit = [entryId];`);
|
|
2637
|
+
lines.push(` while (toVisit.length > 0) {`);
|
|
2638
|
+
lines.push(` const currentId = toVisit.pop();`);
|
|
2639
|
+
lines.push(` if (seen.has(currentId)) continue;`);
|
|
2640
|
+
lines.push(` seen.add(currentId);`);
|
|
2641
|
+
lines.push(` const chunk = manifest[currentId];`);
|
|
2642
|
+
lines.push(` if (!chunk || !Array.isArray(chunk.imports)) continue;`);
|
|
2643
|
+
lines.push(` for (const importedId of chunk.imports) toVisit.push(importedId);`);
|
|
2644
|
+
lines.push(` }`);
|
|
2645
|
+
lines.push(` return Array.from(seen);`);
|
|
2646
|
+
lines.push(`}`);
|
|
2647
|
+
lines.push("");
|
|
2648
|
+
const relPagesDir = pages.pagesDirRelative;
|
|
2649
|
+
const pageCssKeys = {};
|
|
2650
|
+
for (const page of scan.pages.pages) {
|
|
2651
|
+
const keys = [`${relPagesDir}/${page.componentPath}`];
|
|
2652
|
+
const layoutIds = resolveLayoutIds(page, scan.pages.layouts, scan.pages.namedLayouts);
|
|
2653
|
+
for (const layoutId of layoutIds) {
|
|
2654
|
+
const layout = scan.pages.layouts.find((l) => (l.directory ? `${l.directory}/layout` : "layout") === layoutId);
|
|
2655
|
+
if (layout) {
|
|
2656
|
+
keys.push(`${relPagesDir}/${layout.filePath}`);
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
const named = scan.pages.namedLayouts.find((nl) => {
|
|
2660
|
+
return (nl.directory ? `${nl.directory}/_layouts/${nl.name}` : `_layouts/${nl.name}`) === layoutId;
|
|
2661
|
+
});
|
|
2662
|
+
if (named) keys.push(`${relPagesDir}/${named.filePath}`);
|
|
2663
|
+
}
|
|
2664
|
+
pageCssKeys[page.componentId] = keys;
|
|
2665
|
+
}
|
|
2666
|
+
lines.push(`const PAGE_CSS_KEYS = ${JSON.stringify(pageCssKeys)};`);
|
|
2667
|
+
lines.push("");
|
|
2668
|
+
lines.push(`function collectRouteCss(manifest, entryId, pageKeys) {`);
|
|
2669
|
+
lines.push(` const cssFiles = new Set();`);
|
|
2670
|
+
lines.push(` const visited = new Set();`);
|
|
2671
|
+
lines.push(` const toVisit = [entryId];`);
|
|
2672
|
+
lines.push(` while (toVisit.length > 0) {`);
|
|
2673
|
+
lines.push(` const id = toVisit.pop();`);
|
|
2674
|
+
lines.push(` if (visited.has(id)) continue;`);
|
|
2675
|
+
lines.push(` visited.add(id);`);
|
|
2676
|
+
lines.push(` const chunk = manifest[id];`);
|
|
2677
|
+
lines.push(` if (!chunk) continue;`);
|
|
2678
|
+
lines.push(` if (Array.isArray(chunk.css)) {`);
|
|
2679
|
+
lines.push(` for (const f of chunk.css) cssFiles.add(f);`);
|
|
2680
|
+
lines.push(` }`);
|
|
2681
|
+
lines.push(` if (Array.isArray(chunk.imports)) {`);
|
|
2682
|
+
lines.push(` for (const i of chunk.imports) toVisit.push(i);`);
|
|
2683
|
+
lines.push(` }`);
|
|
2684
|
+
lines.push(` }`);
|
|
2685
|
+
lines.push(` for (const key of pageKeys) {`);
|
|
2686
|
+
lines.push(` const stack = [key];`);
|
|
2687
|
+
lines.push(` while (stack.length > 0) {`);
|
|
2688
|
+
lines.push(` const id = stack.pop();`);
|
|
2689
|
+
lines.push(` if (visited.has(id)) continue;`);
|
|
2690
|
+
lines.push(` visited.add(id);`);
|
|
2691
|
+
lines.push(` const chunk = manifest[id];`);
|
|
2692
|
+
lines.push(` if (!chunk) continue;`);
|
|
2693
|
+
lines.push(` if (Array.isArray(chunk.css)) {`);
|
|
2694
|
+
lines.push(` for (const f of chunk.css) cssFiles.add(f);`);
|
|
2695
|
+
lines.push(` }`);
|
|
2696
|
+
lines.push(` if (Array.isArray(chunk.imports)) {`);
|
|
2697
|
+
lines.push(` for (const i of chunk.imports) stack.push(i);`);
|
|
2698
|
+
lines.push(` }`);
|
|
2699
|
+
lines.push(` }`);
|
|
2700
|
+
lines.push(` }`);
|
|
2701
|
+
lines.push(` return cssFiles;`);
|
|
2702
|
+
lines.push(`}`);
|
|
2703
|
+
lines.push("");
|
|
2704
|
+
lines.push(`async function loadPagesManifest(request, env) {`);
|
|
2705
|
+
lines.push(` if (cachedPagesManifest) return cachedPagesManifest;`);
|
|
2706
|
+
lines.push(` if (typeof globalThis.__VITE_MANIFEST__ === "object" && globalThis.__VITE_MANIFEST__) {`);
|
|
2707
|
+
lines.push(` cachedPagesManifest = globalThis.__VITE_MANIFEST__;`);
|
|
2708
|
+
lines.push(` return cachedPagesManifest;`);
|
|
2709
|
+
lines.push(` }`);
|
|
2710
|
+
lines.push(` const assets = env?.ASSETS;`);
|
|
2711
|
+
lines.push(` if (!assets || typeof assets.fetch !== "function") return null;`);
|
|
2712
|
+
lines.push(` const manifestUrl = new URL("/.vite/manifest.json", request.url);`);
|
|
2713
|
+
lines.push(` const response = await assets.fetch(new Request(manifestUrl.toString()));`);
|
|
2714
|
+
lines.push(` if (!response.ok) return null;`);
|
|
2715
|
+
lines.push(` cachedPagesManifest = await response.json();`);
|
|
2716
|
+
lines.push(` return cachedPagesManifest;`);
|
|
2717
|
+
lines.push(`}`);
|
|
2718
|
+
lines.push("");
|
|
2719
|
+
lines.push(`async function getPagesAssetTags(request, env, componentId) {`);
|
|
2720
|
+
lines.push(` const manifest = await loadPagesManifest(request, env);`);
|
|
2721
|
+
lines.push(` if (!manifest || typeof manifest !== "object") {`);
|
|
2722
|
+
lines.push(` if (!import.meta.env.DEV) return { css: "", preloads: "", body: "" };`);
|
|
2723
|
+
lines.push(` return getPagesDevClientTags();`);
|
|
2724
|
+
lines.push(` }`);
|
|
2725
|
+
lines.push(` const entry = manifest[PAGES_CLIENT_ENTRY_ID];`);
|
|
2726
|
+
lines.push(` if (!entry || typeof entry !== "object" || typeof entry.file !== "string") {`);
|
|
2727
|
+
lines.push(` return { css: "", preloads: "", body: "" };`);
|
|
2728
|
+
lines.push(` }`);
|
|
2729
|
+
lines.push(` const ids = collectPagesManifestFiles(manifest, PAGES_CLIENT_ENTRY_ID);`);
|
|
2730
|
+
lines.push(` const pageKeys = componentId ? (PAGE_CSS_KEYS[componentId] || []) : [];`);
|
|
2731
|
+
lines.push(` const cssFiles = collectRouteCss(manifest, PAGES_CLIENT_ENTRY_ID, pageKeys);`);
|
|
2732
|
+
lines.push(` const preloadFiles = new Set();`);
|
|
2733
|
+
lines.push(` for (const id of ids) {`);
|
|
2734
|
+
lines.push(` const chunk = manifest[id];`);
|
|
2735
|
+
lines.push(` if (!chunk || typeof chunk !== "object") continue;`);
|
|
2736
|
+
lines.push(` if (id !== PAGES_CLIENT_ENTRY_ID && typeof chunk.file === "string") {`);
|
|
2737
|
+
lines.push(` preloadFiles.add(chunk.file);`);
|
|
2738
|
+
lines.push(` }`);
|
|
2739
|
+
lines.push(` }`);
|
|
2740
|
+
lines.push(` const cssTags = [];`);
|
|
2741
|
+
lines.push(` for (const cssFile of cssFiles) {`);
|
|
2742
|
+
lines.push(` cssTags.push(\`<link rel="stylesheet" href="/\${escapeHtmlAttr(cssFile)}">\`);`);
|
|
2743
|
+
lines.push(` }`);
|
|
2744
|
+
lines.push(` const preloadTags = [];`);
|
|
2745
|
+
lines.push(` for (const preloadFile of preloadFiles) {`);
|
|
2746
|
+
lines.push(` preloadTags.push(\`<link rel="modulepreload" href="/\${escapeHtmlAttr(preloadFile)}">\`);`);
|
|
2747
|
+
lines.push(` }`);
|
|
2748
|
+
lines.push(` const bodyTag = \`<script type="module" src="/\${escapeHtmlAttr(entry.file)}"><\/script>\`;`);
|
|
2749
|
+
lines.push(` return { css: cssTags.join("\\n"), preloads: preloadTags.join("\\n"), body: bodyTag };`);
|
|
2750
|
+
lines.push(`}`);
|
|
2751
|
+
lines.push("");
|
|
2752
|
+
if (hasIslandPages && pages.islandClientEntryId) {
|
|
2753
|
+
const islandEntryId = pages.islandClientEntryId;
|
|
2754
|
+
lines.push(`const ISLANDS_CLIENT_ENTRY_ID = ${JSON.stringify(islandEntryId)};`);
|
|
2755
|
+
lines.push(`const ISLANDS_CLIENT_ENTRY_URL = ${JSON.stringify("/@id/__x00__" + islandEntryId)};`);
|
|
2756
|
+
if (pages.mdClientEntryId) {
|
|
2757
|
+
lines.push(`const MD_CLIENT_ENTRY_ID = ${JSON.stringify(pages.mdClientEntryId)};`);
|
|
2758
|
+
lines.push(`const MD_CLIENT_ENTRY_URL = ${JSON.stringify("/@id/__x00__" + pages.mdClientEntryId)};`);
|
|
2759
|
+
}
|
|
2760
|
+
lines.push("");
|
|
2761
|
+
lines.push(`function getIslandsDevClientTags() {`);
|
|
2762
|
+
lines.push(` const preloads = \`<script type="module" src="/@vite/client"><\/script>\\n${BINDING_HINT_SCRIPT}\`;`);
|
|
2763
|
+
lines.push(` let bodyTag = \`<script type="module" src="\${escapeHtmlAttr(ISLANDS_CLIENT_ENTRY_URL)}"><\/script>\`;`);
|
|
2764
|
+
if (pages.mdClientEntryId) lines.push(` bodyTag += \`\\n<script type="module" src="\${escapeHtmlAttr(MD_CLIENT_ENTRY_URL)}"><\/script>\`;`);
|
|
2765
|
+
lines.push(` return { css: "", preloads, body: bodyTag };`);
|
|
2766
|
+
lines.push(`}`);
|
|
2767
|
+
lines.push("");
|
|
2768
|
+
lines.push(`async function getIslandsAssetTags(request, env, componentId) {`);
|
|
2769
|
+
lines.push(` const manifest = await loadPagesManifest(request, env);`);
|
|
2770
|
+
lines.push(` if (!manifest || typeof manifest !== "object") {`);
|
|
2771
|
+
lines.push(` if (!import.meta.env.DEV) return { css: "", preloads: "", body: "" };`);
|
|
2772
|
+
lines.push(` return getIslandsDevClientTags();`);
|
|
2773
|
+
lines.push(` }`);
|
|
2774
|
+
lines.push(` const entry = manifest[ISLANDS_CLIENT_ENTRY_ID];`);
|
|
2775
|
+
lines.push(` if (!entry || typeof entry !== "object" || typeof entry.file !== "string") {`);
|
|
2776
|
+
lines.push(` return { css: "", preloads: "", body: "" };`);
|
|
2777
|
+
lines.push(` }`);
|
|
2778
|
+
lines.push(` const ids = collectPagesManifestFiles(manifest, ISLANDS_CLIENT_ENTRY_ID);`);
|
|
2779
|
+
if (pages.mdClientEntryId) {
|
|
2780
|
+
lines.push(` const mdEntry = manifest[MD_CLIENT_ENTRY_ID];`);
|
|
2781
|
+
lines.push(` if (mdEntry && typeof mdEntry.file === "string") {`);
|
|
2782
|
+
lines.push(` const mdIds = collectPagesManifestFiles(manifest, MD_CLIENT_ENTRY_ID);`);
|
|
2783
|
+
lines.push(` for (const mdId of mdIds) ids.push(mdId);`);
|
|
2784
|
+
lines.push(` }`);
|
|
2785
|
+
}
|
|
2786
|
+
lines.push(` const pageKeys = componentId ? (PAGE_CSS_KEYS[componentId] || []) : [];`);
|
|
2787
|
+
lines.push(` const cssFiles = collectRouteCss(manifest, ISLANDS_CLIENT_ENTRY_ID, pageKeys);`);
|
|
2788
|
+
if (pages.mdClientEntryId) {
|
|
2789
|
+
lines.push(` if (mdEntry && typeof mdEntry.file === "string") {`);
|
|
2790
|
+
lines.push(` const mdKeys = [];`);
|
|
2791
|
+
lines.push(` const mdCss = collectRouteCss(manifest, MD_CLIENT_ENTRY_ID, mdKeys);`);
|
|
2792
|
+
lines.push(` for (const f of mdCss) cssFiles.add(f);`);
|
|
2793
|
+
lines.push(` }`);
|
|
2794
|
+
}
|
|
2795
|
+
lines.push(` const preloadFiles = new Set();`);
|
|
2796
|
+
lines.push(` for (const id of ids) {`);
|
|
2797
|
+
lines.push(` const chunk = manifest[id];`);
|
|
2798
|
+
lines.push(` if (!chunk || typeof chunk !== "object") continue;`);
|
|
2799
|
+
const excludeFromPreload = pages.mdClientEntryId ? `id !== ISLANDS_CLIENT_ENTRY_ID && id !== MD_CLIENT_ENTRY_ID` : `id !== ISLANDS_CLIENT_ENTRY_ID`;
|
|
2800
|
+
lines.push(` if (${excludeFromPreload} && typeof chunk.file === "string") {`);
|
|
2801
|
+
lines.push(` preloadFiles.add(chunk.file);`);
|
|
2802
|
+
lines.push(` }`);
|
|
2803
|
+
lines.push(` }`);
|
|
2804
|
+
lines.push(` const cssTags = [];`);
|
|
2805
|
+
lines.push(` for (const cssFile of cssFiles) {`);
|
|
2806
|
+
lines.push(` cssTags.push(\`<link rel="stylesheet" href="/\${escapeHtmlAttr(cssFile)}">\`);`);
|
|
2807
|
+
lines.push(` }`);
|
|
2808
|
+
lines.push(` const preloadTags = [];`);
|
|
2809
|
+
lines.push(` for (const preloadFile of preloadFiles) {`);
|
|
2810
|
+
lines.push(` preloadTags.push(\`<link rel="modulepreload" href="/\${escapeHtmlAttr(preloadFile)}">\`);`);
|
|
2811
|
+
lines.push(` }`);
|
|
2812
|
+
lines.push(` let bodyTag = \`<script type="module" src="/\${escapeHtmlAttr(entry.file)}"><\/script>\`;`);
|
|
2813
|
+
if (pages.mdClientEntryId) {
|
|
2814
|
+
lines.push(` if (mdEntry && typeof mdEntry.file === "string") {`);
|
|
2815
|
+
lines.push(` bodyTag += \`\\n<script type="module" src="/\${escapeHtmlAttr(mdEntry.file)}"><\/script>\`;`);
|
|
2816
|
+
lines.push(` }`);
|
|
2817
|
+
}
|
|
2818
|
+
lines.push(` return { css: cssTags.join("\\n"), preloads: preloadTags.join("\\n"), body: bodyTag };`);
|
|
2819
|
+
lines.push(`}`);
|
|
2820
|
+
lines.push("");
|
|
2821
|
+
}
|
|
2822
|
+
const seenServerFiles = /* @__PURE__ */ new Set();
|
|
2823
|
+
for (const page of scan.pages.pages) {
|
|
2824
|
+
const safeId = page.componentId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
2825
|
+
let serverImportPath = null;
|
|
2826
|
+
if (page.serverPath) serverImportPath = `${pages.pagesDir}/${page.serverPath}`;
|
|
2827
|
+
if (serverImportPath && !seenServerFiles.has(serverImportPath)) {
|
|
2828
|
+
seenServerFiles.add(serverImportPath);
|
|
2829
|
+
const varName = `_pmod_${safeId}`;
|
|
2830
|
+
const loaderName = `_pload_${safeId}`;
|
|
2831
|
+
lines.push(`let ${varName};`);
|
|
2832
|
+
lines.push(`async function ${loaderName}() {`);
|
|
2833
|
+
lines.push(` if (!${varName}) ${varName} = await import(${JSON.stringify(serverImportPath)});`);
|
|
2834
|
+
lines.push(` return ${varName};`);
|
|
2835
|
+
lines.push(`}`);
|
|
2836
|
+
}
|
|
2837
|
+
let mdHeadRef = null;
|
|
2838
|
+
if (!serverImportPath && page.metadata && (page.metadata.title || page.metadata.description)) {
|
|
2839
|
+
const headObj = {};
|
|
2840
|
+
if (page.metadata.title) headObj.title = page.metadata.title;
|
|
2841
|
+
if (page.metadata.description) headObj.meta = [{
|
|
2842
|
+
name: "description",
|
|
2843
|
+
content: page.metadata.description
|
|
2844
|
+
}];
|
|
2845
|
+
const headVarName = `__mdHead_${safeId}`;
|
|
2846
|
+
lines.push(`const ${headVarName} = () => (${JSON.stringify(headObj)});`);
|
|
2847
|
+
mdHeadRef = headVarName;
|
|
2848
|
+
}
|
|
2849
|
+
const renderer = page.island ? "renderIslandPage" : "renderPage";
|
|
2850
|
+
const assetTags = page.island && pages.islandClientEntryId ? "getIslandsAssetTags" : "getPagesAssetTags";
|
|
2851
|
+
const islandFlag = page.island ? ", true" : "";
|
|
2852
|
+
lines.push(`app.get(${JSON.stringify(page.pattern)}, async (c) => {`);
|
|
2853
|
+
if (serverImportPath) {
|
|
2854
|
+
const loaderName = `_pload_${safeId}`;
|
|
2855
|
+
lines.push(` const mod = await ${loaderName}();`);
|
|
2856
|
+
lines.push(` return handlePageGet(mod.loader, mod.head, c, ${JSON.stringify(page.componentId)}, ${renderer}, ${assetTags}, __voidHeadConfig${islandFlag});`);
|
|
2857
|
+
} else {
|
|
2858
|
+
const headArg = mdHeadRef ?? "undefined";
|
|
2859
|
+
lines.push(` return handlePageGet(undefined, ${headArg}, c, ${JSON.stringify(page.componentId)}, ${renderer}, ${assetTags}, __voidHeadConfig${islandFlag});`);
|
|
2860
|
+
}
|
|
2861
|
+
lines.push(`});`);
|
|
2862
|
+
if (page.methods.includes("action") || page.methods.includes("actions")) {
|
|
2863
|
+
const loaderName = `_pload_${safeId}`;
|
|
2864
|
+
lines.push(`app.on(["POST", "PUT", "PATCH", "DELETE"], ${JSON.stringify(page.pattern)}, async (c) => {`);
|
|
2865
|
+
lines.push(` const mod = await ${loaderName}();`);
|
|
2866
|
+
if (page.methods.includes("actions")) {
|
|
2867
|
+
lines.push(` const actionName = new URL(c.req.url).searchParams.keys().next().value;`);
|
|
2868
|
+
lines.push(` const handler = actionName ? mod.actions?.[actionName] : mod.actions?.default;`);
|
|
2869
|
+
lines.push(` if (!handler) return new Response("Unknown action" + (actionName ? ": " + actionName : ""), { status: 404 });`);
|
|
2870
|
+
} else lines.push(` const handler = mod.action;`);
|
|
2871
|
+
lines.push(` return handlePageMutation(handler, mod.loader, mod.head, c, ${JSON.stringify(page.componentId)}, ${renderer}, ${assetTags}, __voidHeadConfig${islandFlag});`);
|
|
2872
|
+
lines.push(`});`);
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
lines.push("");
|
|
2876
|
+
lines.push(`async function fetchPagesAsset(c) {`);
|
|
2877
|
+
lines.push(` if (c.req.method !== "GET" && c.req.method !== "HEAD") {`);
|
|
2878
|
+
lines.push(` return new Response("Not Found", { status: 404 });`);
|
|
2879
|
+
lines.push(` }`);
|
|
2880
|
+
lines.push(` const assets = c.env?.ASSETS;`);
|
|
2881
|
+
lines.push(` if (!assets || typeof assets.fetch !== "function") {`);
|
|
2882
|
+
lines.push(` return __voidMarkNoMatch(c.req.raw, new Response("Not Found", { status: 404 }));`);
|
|
2883
|
+
lines.push(` }`);
|
|
2884
|
+
lines.push(` return assets.fetch(c.req.raw);`);
|
|
2885
|
+
lines.push(`}`);
|
|
2886
|
+
lines.push("");
|
|
2887
|
+
lines.push(`async function serveCustomErrorPage(c, status, fallbackText, error) {`);
|
|
2888
|
+
lines.push(` try {`);
|
|
2889
|
+
lines.push(` const assets = c.env?.ASSETS;`);
|
|
2890
|
+
lines.push(` if (assets && typeof assets.fetch === "function") {`);
|
|
2891
|
+
lines.push(` const errReq = new Request(new URL("/" + status + ".html", c.req.url));`);
|
|
2892
|
+
lines.push(` const errRes = await assets.fetch(errReq);`);
|
|
2893
|
+
lines.push(` if (errRes.ok) {`);
|
|
2894
|
+
lines.push(` if (__VOID_DEV && status === 500 && error && __voidShouldServeHtmlErrorPage(c.req.raw)) {`);
|
|
2895
|
+
lines.push(` const overlay = __voidRenderDevErrorOverlay(c.req.raw, status, fallbackText, error);`);
|
|
2896
|
+
lines.push(` const html = await errRes.text();`);
|
|
2897
|
+
lines.push(` return new Response(c.req.method === "HEAD" ? null : __voidInjectDevErrorOverlay(html, overlay), {`);
|
|
2898
|
+
lines.push(` status,`);
|
|
2899
|
+
lines.push(` headers: { "content-type": "text/html; charset=utf-8" },`);
|
|
2900
|
+
lines.push(` });`);
|
|
2901
|
+
lines.push(` }`);
|
|
2902
|
+
lines.push(` return new Response(errRes.body, { status, headers: { "content-type": "text/html; charset=utf-8" } });`);
|
|
2903
|
+
lines.push(` }`);
|
|
2904
|
+
lines.push(` }`);
|
|
2905
|
+
lines.push(` } catch {}`);
|
|
2906
|
+
lines.push(` return __voidCreateErrorResponse(c.req.raw, status, fallbackText, error);`);
|
|
2907
|
+
lines.push(`}`);
|
|
2908
|
+
lines.push("");
|
|
2909
|
+
lines.push(`app.onError((err, c) => {`);
|
|
2910
|
+
lines.push(` if (err && typeof err === "object" && "getResponse" in err) return err.getResponse();`);
|
|
2911
|
+
lines.push(` console.error(err);`);
|
|
2912
|
+
lines.push(` return serveCustomErrorPage(c, 500, "Internal Server Error", err);`);
|
|
2913
|
+
lines.push(`});`);
|
|
2914
|
+
lines.push("");
|
|
2915
|
+
lines.push(`app.notFound(async (c) => {`);
|
|
2916
|
+
lines.push(` const path = c.req.path;`);
|
|
2917
|
+
lines.push(` if (path === "/api" || path.startsWith("/api/")) {`);
|
|
2918
|
+
lines.push(` return c.text("Not Found", 404);`);
|
|
2919
|
+
lines.push(` }`);
|
|
2920
|
+
lines.push(` if (c.req.method !== "GET" && c.req.method !== "HEAD") {`);
|
|
2921
|
+
lines.push(` return c.text("Not Found", 404);`);
|
|
2922
|
+
lines.push(` }`);
|
|
2923
|
+
lines.push(` const assetRes = await fetchPagesAsset(c);`);
|
|
2924
|
+
lines.push(` if (assetRes.status !== 404) return assetRes;`);
|
|
2925
|
+
if ((options.nodeTarget || options.dev === true) && options.fallbackRules?.length) emitFallbackRulesCheck(lines, options.fallbackRules, options.dev === true, " ", options.nodeTarget === true);
|
|
2926
|
+
lines.push(` if (path.includes("/.")) {`);
|
|
2927
|
+
lines.push(` return serveCustomErrorPage(c, 404, "Not Found");`);
|
|
2928
|
+
lines.push(` }`);
|
|
2929
|
+
lines.push(` const lastSeg = path.substring(path.lastIndexOf("/") + 1);`);
|
|
2930
|
+
lines.push(` if (lastSeg.includes(".")) {`);
|
|
2931
|
+
lines.push(` return serveCustomErrorPage(c, 404, "Not Found");`);
|
|
2932
|
+
lines.push(` }`);
|
|
2933
|
+
lines.push(` if (path !== "/" && path.endsWith("/")) {`);
|
|
2934
|
+
lines.push(` const url = new URL(c.req.url);`);
|
|
2935
|
+
lines.push(` url.pathname = path.slice(0, -1);`);
|
|
2936
|
+
lines.push(` return Response.redirect(url.href, 301);`);
|
|
2937
|
+
lines.push(` }`);
|
|
2938
|
+
lines.push(` return __voidMarkNoMatch(c.req.raw, await serveCustomErrorPage(c, 404, "Not Found"));`);
|
|
2939
|
+
lines.push(`});`);
|
|
2940
|
+
lines.push("");
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Generate the Worker entry module code.
|
|
2944
|
+
* Re-exports the Hono app from the virtual module with an asset-serving
|
|
2945
|
+
* fallthrough: when the app returns 404, the entry tries env.ASSETS.fetch()
|
|
2946
|
+
* and adds proper Cache-Control headers (immutable for Vite-hashed /assets/*).
|
|
2947
|
+
*
|
|
2948
|
+
* When `hasQueues` is true, also exports a `queue()` handler that
|
|
2949
|
+
* serializes the native MessageBatch and dispatches it to the
|
|
2950
|
+
* `/__void/queue` Hono route. In local dev Miniflare delivers batches
|
|
2951
|
+
* natively to this export; in production the relay worker does it.
|
|
2952
|
+
*
|
|
2953
|
+
* When `remotePath` is provided, wraps the fetch handler to detect
|
|
2954
|
+
* remote mode (`__VOID_REMOTE`) and replace local bindings with
|
|
2955
|
+
* proxy-backed versions.
|
|
2956
|
+
*/
|
|
2957
|
+
function compileWorkerEntry(hasQueues = false, remote, assetsDir = "assets", webSocketClasses = [], runtimeEnvPath = "void/_env", envSchemaModule, sandbox, serveSpaFallback = false) {
|
|
2958
|
+
const remotePath = remote?.remotePath;
|
|
2959
|
+
const hasSandbox = sandbox === true || typeof sandbox === "object";
|
|
2960
|
+
const sandboxClassName = typeof sandbox === "object" ? sandbox.className ?? "Sandbox" : "Sandbox";
|
|
2961
|
+
const sandboxBindingName = typeof sandbox === "object" ? sandbox.bindingName ?? "SANDBOX" : "SANDBOX";
|
|
2962
|
+
const sandboxRuntimePath = typeof sandbox === "object" ? sandbox.runtimePath ?? "void/sandbox" : "void/sandbox";
|
|
2963
|
+
const sandboxImport = hasSandbox ? `import { Sandbox as __VoidSandbox } from "@cloudflare/sandbox";\nimport { __setSandboxBindingName } from ${JSON.stringify(sandboxRuntimePath)};\n` : "";
|
|
2964
|
+
const sandboxExport = hasSandbox ? sandboxClassName === "Sandbox" ? `export { __VoidSandbox as Sandbox };\n__setSandboxBindingName(${JSON.stringify(sandboxBindingName)});\n` : `export class ${sandboxClassName} extends __VoidSandbox {}\n__setSandboxBindingName(${JSON.stringify(sandboxBindingName)});\n` : "";
|
|
2965
|
+
const runtimeEnvImport = `import { withRuntimeEnv } from ${JSON.stringify(runtimeEnvPath)};\n`;
|
|
2966
|
+
const remoteImport = remote ? `import { createRemoteEnv } from ${JSON.stringify(remote.remotePath)};\n` : "";
|
|
2967
|
+
const envSchemaImport = envSchemaModule ? `import ${JSON.stringify(envSchemaModule.importPath)};\nimport { _validateEnvOnce } from ${JSON.stringify(envSchemaModule.helperPath)};\n` : "";
|
|
2968
|
+
const envValidateCall = envSchemaModule ? " await _validateEnvOnce(env);\n" : "";
|
|
2969
|
+
const prefixCheck = INTERNAL_BINDING_PREFIXES.map((p) => `prop.startsWith(${JSON.stringify(p)})`).join(" || ");
|
|
2970
|
+
const filterBindingsHelper = `
|
|
2971
|
+
function __filterInternalBindings(env) {
|
|
2972
|
+
return new Proxy(env, {
|
|
2973
|
+
get(target, prop, receiver) {
|
|
2974
|
+
if (typeof prop === "string" && (${prefixCheck})) return undefined;
|
|
2975
|
+
return Reflect.get(target, prop, receiver);
|
|
2976
|
+
},
|
|
2977
|
+
has(target, prop) {
|
|
2978
|
+
if (typeof prop === "string" && (${prefixCheck})) return false;
|
|
2979
|
+
return Reflect.has(target, prop);
|
|
2980
|
+
},
|
|
2981
|
+
ownKeys(target) {
|
|
2982
|
+
return Reflect.ownKeys(target).filter(
|
|
2983
|
+
(key) => typeof key !== "string" || (${INTERNAL_BINDING_PREFIXES.map((p) => `!key.startsWith(${JSON.stringify(p)})`).join(" && ")})
|
|
2984
|
+
);
|
|
2985
|
+
},
|
|
2986
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
2987
|
+
if (typeof prop === "string" && (${prefixCheck})) return undefined;
|
|
2988
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
2989
|
+
},
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
`;
|
|
2993
|
+
const spaFallbackCode = serveSpaFallback ? ` // SPA fallback: when routes/middleware make assetConfig.not_found_handling
|
|
2994
|
+
// = 'none', the asset binding returns 404 instead of falling back to
|
|
2995
|
+
// index.html itself. Do that fallback here, after user routes/middleware
|
|
2996
|
+
// have run, so auth gates and OAuth callbacks see the request first.
|
|
2997
|
+
// Pages and SSR apps leave this disabled so intentional HTML 404s stay 404.
|
|
2998
|
+
const accept = request.headers.get("accept") ?? "";
|
|
2999
|
+
if (accept.includes("text/html")) {
|
|
3000
|
+
const indexUrl = new URL("/index.html", request.url);
|
|
3001
|
+
const indexAsset = await env.ASSETS.fetch(new Request(indexUrl, { method: request.method }));
|
|
3002
|
+
if (indexAsset.status === 200) {
|
|
3003
|
+
const res = new Response(indexAsset.body, {
|
|
3004
|
+
status: 200,
|
|
3005
|
+
headers: indexAsset.headers,
|
|
3006
|
+
});
|
|
3007
|
+
res.headers.set("Cache-Control", "no-store");
|
|
3008
|
+
res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
3009
|
+
return res;
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
return response;` : ` return response;`;
|
|
3013
|
+
const assetHelper = `
|
|
3014
|
+
async function serveWithAssets(request, env, response) {
|
|
3015
|
+
if (response.status !== 404 || !env.ASSETS || (request.method !== "GET" && request.method !== "HEAD")) return response;
|
|
3016
|
+
const asset = await env.ASSETS.fetch(new Request(request.url, { method: request.method, headers: request.headers }));
|
|
3017
|
+
if (asset.status !== 404) {
|
|
3018
|
+
const res = new Response(asset.body, asset);
|
|
3019
|
+
if (new URL(request.url).pathname.startsWith(${JSON.stringify("/" + assetsDir + "/")})) {
|
|
3020
|
+
res.headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
3021
|
+
} else {
|
|
3022
|
+
res.headers.set("Cache-Control", "no-store");
|
|
3023
|
+
}
|
|
3024
|
+
return res;
|
|
3025
|
+
}
|
|
3026
|
+
${spaFallbackCode}
|
|
3027
|
+
}
|
|
3028
|
+
`;
|
|
3029
|
+
const fetchHandler = remotePath ? `async (request, env, ctx) => {
|
|
3030
|
+
${envValidateCall} const ws = await __voidHandleWebSocket(request, env);
|
|
3031
|
+
if (ws) return ws;
|
|
3032
|
+
if (env.__VOID_REMOTE === "1") env = createRemoteEnv(env);
|
|
3033
|
+
return __withRawEnv(env, () => {
|
|
3034
|
+
const userEnv = __filterInternalBindings(env);
|
|
3035
|
+
return withRuntimeEnv(env, async () => {
|
|
3036
|
+
const internal = await __handleInternal(request, env, ctx);
|
|
3037
|
+
if (internal) return internal;
|
|
3038
|
+
return serveWithAssets(request, env, await app.fetch(request, userEnv, ctx));
|
|
3039
|
+
});
|
|
3040
|
+
});
|
|
3041
|
+
}` : `async (request, env, ctx) => {
|
|
3042
|
+
${envValidateCall} const ws = await __voidHandleWebSocket(request, env);
|
|
3043
|
+
if (ws) return ws;
|
|
3044
|
+
return __withRawEnv(env, () => {
|
|
3045
|
+
const userEnv = __filterInternalBindings(env);
|
|
3046
|
+
return withRuntimeEnv(env, async () => {
|
|
3047
|
+
const internal = await __handleInternal(request, env, ctx);
|
|
3048
|
+
if (internal) return internal;
|
|
3049
|
+
return serveWithAssets(request, env, await app.fetch(request, userEnv, ctx));
|
|
3050
|
+
});
|
|
3051
|
+
});
|
|
3052
|
+
}`;
|
|
3053
|
+
const internalRouteHelper = `async function __handleInternal(request, env, ctx) {
|
|
3054
|
+
const url = new URL(request.url);
|
|
3055
|
+
// Readiness probe — platform polls this to confirm the worker is live after WfP upload.
|
|
3056
|
+
// Handled here (before app.fetch) so user middleware cannot block it.
|
|
3057
|
+
if (request.method === "GET" && url.pathname === "/__void/ready") {
|
|
3058
|
+
return Response.json({ ready: true });
|
|
3059
|
+
}
|
|
3060
|
+
if (request.method !== "POST" || url.pathname !== "/__void/prerender-paths") return null;
|
|
3061
|
+
const __expected = env.__VOID_PROXY_TOKEN;
|
|
3062
|
+
if (__expected) {
|
|
3063
|
+
const __token = request.headers.get("x-void-internal");
|
|
3064
|
+
if (!__token || __token !== __expected) {
|
|
3065
|
+
return Response.json({ error: "unauthorized" }, { status: 401 });
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
const paths = await __voidCollectPrerenderPaths(request, env, ctx);
|
|
3069
|
+
return Response.json({ paths });
|
|
3070
|
+
}`;
|
|
3071
|
+
const scheduledHandler = remotePath ? `async function handleScheduled(controller, env, ctx) {
|
|
3072
|
+
${envValidateCall} if (env.__VOID_REMOTE === "1") env = createRemoteEnv(env);
|
|
3073
|
+
const userEnv = __filterInternalBindings(env);
|
|
3074
|
+
return withRuntimeEnv(env, () => scheduled(controller, userEnv, ctx));
|
|
3075
|
+
}` : `async function handleScheduled(controller, env, ctx) {
|
|
3076
|
+
${envValidateCall} const userEnv = __filterInternalBindings(env);
|
|
3077
|
+
return withRuntimeEnv(env, () => scheduled(controller, userEnv, ctx));
|
|
3078
|
+
}`;
|
|
3079
|
+
if (hasQueues) return `${sandboxImport}import app, { scheduled, __voidCollectPrerenderPaths, __voidHandleWebSocket, __withRawEnv, __processQueue${webSocketClasses.length > 0 ? `, ${webSocketClasses.join(", ")}` : ""} } from 'virtual:void-routes';
|
|
3080
|
+
${runtimeEnvImport}${envSchemaImport}${remoteImport}${filterBindingsHelper}${assetHelper}
|
|
3081
|
+
${internalRouteHelper}
|
|
3082
|
+
${scheduledHandler}
|
|
3083
|
+
async function queue(batch, env, ctx) {
|
|
3084
|
+
${envValidateCall}${remotePath ? ` if (env.__VOID_REMOTE === "1") env = createRemoteEnv(env);\n` : ""} const userEnv = __filterInternalBindings(env);
|
|
3085
|
+
const messages = [...batch.messages].map(m => ({
|
|
3086
|
+
id: m.id, timestamp: m.timestamp.toISOString(),
|
|
3087
|
+
body: m.body, attempts: m.attempts,
|
|
3088
|
+
}));
|
|
3089
|
+
const payload = { queue: batch.queue, messages };
|
|
3090
|
+
const __qr = await __withRawEnv(env, () => withRuntimeEnv(env, () => __processQueue(payload, userEnv)));
|
|
3091
|
+
if (__qr.decisions) {
|
|
3092
|
+
const __msgMap = new Map();
|
|
3093
|
+
for (const m of batch.messages) __msgMap.set(m.id, m);
|
|
3094
|
+
for (const [id, d] of Object.entries(__qr.decisions)) {
|
|
3095
|
+
const m = __msgMap.get(id);
|
|
3096
|
+
if (!m) continue;
|
|
3097
|
+
if (d.action === "ack") m.ack();
|
|
3098
|
+
else if (d.action === "retry") m.retry(d.delaySeconds != null ? { delaySeconds: d.delaySeconds } : undefined);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (!__qr.ok) throw new Error("queue: Handler failed.");
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
${webSocketClasses.map((name) => `export { ${name} };`).join("\n")}
|
|
3105
|
+
${sandboxExport}
|
|
3106
|
+
export default { fetch: ${fetchHandler}, scheduled: handleScheduled, queue };`;
|
|
3107
|
+
return `${sandboxImport}import app, { scheduled, __voidCollectPrerenderPaths, __voidHandleWebSocket, __withRawEnv${webSocketClasses.length > 0 ? `, ${webSocketClasses.join(", ")}` : ""} } from 'virtual:void-routes';
|
|
3108
|
+
${runtimeEnvImport}${envSchemaImport}${remoteImport}${filterBindingsHelper}${assetHelper}
|
|
3109
|
+
${internalRouteHelper}
|
|
3110
|
+
${scheduledHandler}
|
|
3111
|
+
${webSocketClasses.map((name) => `export { ${name} };`).join("\n")}
|
|
3112
|
+
${sandboxExport}
|
|
3113
|
+
export default { fetch: ${fetchHandler}, scheduled: handleScheduled };`;
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Generate the app module — exports the Hono app with static asset serving.
|
|
3117
|
+
* This is the entry imported by `vite preview` and can be used programmatically.
|
|
3118
|
+
*
|
|
3119
|
+
* When `envSchemaModule` is provided, splices boot-time env validation so it
|
|
3120
|
+
* runs on the first request (including the prerender child process spawned
|
|
3121
|
+
* by `prerenderPagesNode`). Mirrors `compileWorkerEntry`: a side-effect import
|
|
3122
|
+
* registers the schema, and `_validateEnvOnce` throws `EnvValidationError` on
|
|
3123
|
+
* bad values. Env source on Node/Bun is `process.env`; on Deno it's
|
|
3124
|
+
* `Deno.env.toObject()`.
|
|
3125
|
+
*/
|
|
3126
|
+
function compileNodeApp(target, honoPath, envSchemaModule) {
|
|
3127
|
+
const staticImport = target === "bun" ? `import { serveStatic } from "hono/bun";` : target === "deno" ? `import { serveStatic } from "hono/deno";` : `import { serveStatic } from "@hono/node-server/serve-static";`;
|
|
3128
|
+
const envSchemaImport = envSchemaModule ? `import ${JSON.stringify(envSchemaModule.importPath)};\nimport { _validateEnvOnce } from ${JSON.stringify(envSchemaModule.helperPath)};\n` : "";
|
|
3129
|
+
const envMiddleware = envSchemaModule ? `app.use("*", async (c, next) => { await _validateEnvOnce(${target === "deno" ? "Deno.env.toObject()" : "process.env"}); return next(); });\n` : "";
|
|
3130
|
+
return `${staticImport}
|
|
3131
|
+
import { Hono } from ${JSON.stringify(honoPath)};
|
|
3132
|
+
import innerApp, { __voidCollectPrerenderPaths } from "virtual:void-routes";
|
|
3133
|
+
${envSchemaImport}
|
|
3134
|
+
const app = new Hono();
|
|
3135
|
+
${envMiddleware}app.post("/__void/prerender-paths", async (c) => {
|
|
3136
|
+
const __expected = c.env?.__VOID_PROXY_TOKEN;
|
|
3137
|
+
if (__expected) {
|
|
3138
|
+
const __token = c.req.header("x-void-internal");
|
|
3139
|
+
if (!__token || __token !== __expected) return c.json({ error: "unauthorized" }, 401);
|
|
3140
|
+
}
|
|
3141
|
+
const paths = await __voidCollectPrerenderPaths(c.req.raw);
|
|
3142
|
+
return c.json({ paths });
|
|
3143
|
+
});
|
|
3144
|
+
app.use("/assets/*", serveStatic({ root: "./client" }));
|
|
3145
|
+
app.all("*", (c) => innerApp.fetch(c.req.raw));
|
|
3146
|
+
export default app;`;
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* Generate the server entry — imports the app and starts an HTTP server.
|
|
3150
|
+
* This is what users run with `node dist/ssr/index.js`.
|
|
3151
|
+
*/
|
|
3152
|
+
function compileNodeServer(target) {
|
|
3153
|
+
if (target === "bun") return `import app from "./app.js";
|
|
3154
|
+
|
|
3155
|
+
const port = Number(process.env.PORT || 3000);
|
|
3156
|
+
Bun.serve({ fetch: app.fetch, port });
|
|
3157
|
+
console.log("Listening on http://localhost:" + port);`;
|
|
3158
|
+
if (target === "deno") return `import app from "./app.js";
|
|
3159
|
+
|
|
3160
|
+
const port = Number(Deno.env.get("PORT") || 3000);
|
|
3161
|
+
Deno.serve({ port }, app.fetch);`;
|
|
3162
|
+
return `import { serve } from "@hono/node-server";
|
|
3163
|
+
import app from "./app.js";
|
|
3164
|
+
|
|
3165
|
+
const port = Number(process.env.PORT || 3000);
|
|
3166
|
+
serve({ fetch: app.fetch, port }, (info) => {
|
|
3167
|
+
console.log("Listening on http://localhost:" + info.port);
|
|
3168
|
+
});`;
|
|
3169
|
+
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Generate the server-side fetch client module for non-CF targets.
|
|
3172
|
+
* Same as compileWorkerClient but without cloudflare:workers dependency.
|
|
3173
|
+
* Calls app.fetch() directly (no HTTP round-trip) with ALS context forwarding.
|
|
3174
|
+
*/
|
|
3175
|
+
function compileBrowserClient(authClientPath, fetchPath, fetchStreamPath) {
|
|
3176
|
+
return `export { fetch, FetchError } from ${JSON.stringify(fetchPath)};
|
|
3177
|
+
export { fetchStream, StreamError } from ${JSON.stringify(fetchStreamPath)};
|
|
3178
|
+
export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};`;
|
|
3179
|
+
}
|
|
3180
|
+
function compileNodeClient(authClientPath) {
|
|
3181
|
+
return `import app, { requestContext } from "virtual:void-routes";
|
|
3182
|
+
export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};
|
|
3183
|
+
|
|
3184
|
+
export class FetchError extends Error {
|
|
3185
|
+
constructor(status, statusText, data) {
|
|
3186
|
+
super(\`fetch: Request failed with status \${status}.\`);
|
|
3187
|
+
this.name = "FetchError";
|
|
3188
|
+
this.status = status;
|
|
3189
|
+
this.statusText = statusText;
|
|
3190
|
+
this.data = data;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
export async function fetch(path, options) {
|
|
3195
|
+
let url = path;
|
|
3196
|
+
if (options?.params) {
|
|
3197
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
3198
|
+
url = url.replace(":" + key, encodeURIComponent(value));
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
if (options?.query) {
|
|
3203
|
+
const qs = new URLSearchParams(options.query).toString();
|
|
3204
|
+
if (qs) url += "?" + qs;
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
const method = options?.method ?? "GET";
|
|
3208
|
+
const headers = new Headers(options?.headers);
|
|
3209
|
+
|
|
3210
|
+
const store = requestContext.getStore();
|
|
3211
|
+
if (store?.request) {
|
|
3212
|
+
const outerReq = store.request;
|
|
3213
|
+
if (!headers.has("cookie")) {
|
|
3214
|
+
const cookie = outerReq.headers.get("cookie");
|
|
3215
|
+
if (cookie) headers.set("cookie", cookie);
|
|
3216
|
+
}
|
|
3217
|
+
if (!headers.has("authorization")) {
|
|
3218
|
+
const auth = outerReq.headers.get("authorization");
|
|
3219
|
+
if (auth) headers.set("authorization", auth);
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
if (options?.body != null && !headers.has("content-type")) {
|
|
3224
|
+
headers.set("content-type", "application/json");
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
const request = new Request("http://localhost" + url, {
|
|
3228
|
+
method,
|
|
3229
|
+
headers,
|
|
3230
|
+
body: options?.body != null ? JSON.stringify(options.body) : undefined,
|
|
3231
|
+
});
|
|
3232
|
+
|
|
3233
|
+
const response = await app.fetch(request);
|
|
3234
|
+
|
|
3235
|
+
if (response.status === 204) return undefined;
|
|
3236
|
+
|
|
3237
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
3238
|
+
const data = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
3239
|
+
|
|
3240
|
+
if (!response.ok) {
|
|
3241
|
+
throw new FetchError(response.status, response.statusText, data);
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
return data;
|
|
3245
|
+
}`;
|
|
3246
|
+
}
|
|
3247
|
+
/**
|
|
3248
|
+
* Emit inline `__compilePattern()` and `__matchPattern()` — pattern compilation
|
|
3249
|
+
* with named captures, matching the dispatch worker's behavior in
|
|
3250
|
+
* `packages/dispatch/src/pattern.ts`.
|
|
3251
|
+
*
|
|
3252
|
+
* Syntax:
|
|
3253
|
+
* :name — single path segment (captured as `name`)
|
|
3254
|
+
* :name* — zero or more segments (captured as `name`)
|
|
3255
|
+
* * — zero or more segments (captured as `splat`, legacy)
|
|
3256
|
+
*
|
|
3257
|
+
* When a pattern contains multiple bare `*`, only the first captures as
|
|
3258
|
+
* `splat`; later occurrences emit a non-capturing `.*` so the constructed
|
|
3259
|
+
* `RegExp` doesn't reject "duplicate group name" at request time. `:name*`
|
|
3260
|
+
* uses a distinct group name and never clashes with `*`. `__matchPattern`
|
|
3261
|
+
* additionally swallows any compile-time `RegExp` error and returns `false`
|
|
3262
|
+
* so a single malformed rule cannot 500 unrelated requests on CF dev.
|
|
3263
|
+
*/
|
|
3264
|
+
function emitCompilePattern(lines) {
|
|
3265
|
+
lines.push("function __compilePattern(source) {");
|
|
3266
|
+
lines.push(" const names = [];");
|
|
3267
|
+
lines.push(" let legacySplatCaptured = false;");
|
|
3268
|
+
lines.push(" let pattern = \"\";");
|
|
3269
|
+
lines.push(" let i = 0;");
|
|
3270
|
+
lines.push(" while (i < source.length) {");
|
|
3271
|
+
lines.push(" const ch = source[i];");
|
|
3272
|
+
lines.push(" if (ch === \":\") {");
|
|
3273
|
+
lines.push(" let j = i + 1;");
|
|
3274
|
+
lines.push(" while (j < source.length && /[A-Za-z0-9_]/.test(source[j])) j++;");
|
|
3275
|
+
lines.push(" const name = source.slice(i + 1, j);");
|
|
3276
|
+
lines.push(" if (!name) { pattern += \"\\\\:\"; i++; continue; }");
|
|
3277
|
+
lines.push(" if (source[j] === \"*\") {");
|
|
3278
|
+
lines.push(" names.push(name);");
|
|
3279
|
+
lines.push(" pattern += `(?<${name}>.*)`;");
|
|
3280
|
+
lines.push(" i = j + 1;");
|
|
3281
|
+
lines.push(" } else {");
|
|
3282
|
+
lines.push(" names.push(name);");
|
|
3283
|
+
lines.push(" pattern += `(?<${name}>[^/]+)`;");
|
|
3284
|
+
lines.push(" i = j;");
|
|
3285
|
+
lines.push(" }");
|
|
3286
|
+
lines.push(" } else if (ch === \"*\") {");
|
|
3287
|
+
lines.push(" if (legacySplatCaptured) {");
|
|
3288
|
+
lines.push(" pattern += \".*\";");
|
|
3289
|
+
lines.push(" } else {");
|
|
3290
|
+
lines.push(" names.push(\"splat\");");
|
|
3291
|
+
lines.push(" pattern += \"(?<splat>.*)\";");
|
|
3292
|
+
lines.push(" legacySplatCaptured = true;");
|
|
3293
|
+
lines.push(" }");
|
|
3294
|
+
lines.push(" i++;");
|
|
3295
|
+
lines.push(" } else {");
|
|
3296
|
+
lines.push(" if (/[.+?^${}()|[\\]\\\\]/.test(ch)) pattern += \"\\\\\";");
|
|
3297
|
+
lines.push(" pattern += ch;");
|
|
3298
|
+
lines.push(" i++;");
|
|
3299
|
+
lines.push(" }");
|
|
3300
|
+
lines.push(" }");
|
|
3301
|
+
lines.push(" return { regex: new RegExp(`^${pattern}$`), names };");
|
|
3302
|
+
lines.push("}");
|
|
3303
|
+
lines.push("const __matchPatternWarned = new Set();");
|
|
3304
|
+
lines.push("function __matchPattern(pattern, pathname) {");
|
|
3305
|
+
lines.push(" try {");
|
|
3306
|
+
lines.push(" return __compilePattern(pattern).regex.test(pathname);");
|
|
3307
|
+
lines.push(" } catch (err) {");
|
|
3308
|
+
lines.push(" if (!__matchPatternWarned.has(pattern)) {");
|
|
3309
|
+
lines.push(" __matchPatternWarned.add(pattern);");
|
|
3310
|
+
lines.push(" console.warn(`[void] failed to compile route pattern ${JSON.stringify(pattern)}: ${err && err.message ? err.message : err}`);");
|
|
3311
|
+
lines.push(" }");
|
|
3312
|
+
lines.push(" return false;");
|
|
3313
|
+
lines.push(" }");
|
|
3314
|
+
lines.push("}");
|
|
3315
|
+
lines.push("function __substituteParams(destination, params) {");
|
|
3316
|
+
lines.push(" return destination.replace(/:([A-Za-z0-9_]+)\\*?/g, (whole, name) => {");
|
|
3317
|
+
lines.push(" return params[name] != null ? params[name] : whole;");
|
|
3318
|
+
lines.push(" });");
|
|
3319
|
+
lines.push("}");
|
|
3320
|
+
lines.push("");
|
|
3321
|
+
}
|
|
3322
|
+
function ensureCompilePatternEmitted(lines) {
|
|
3323
|
+
if (!lines.some((l) => l.includes("function __compilePattern"))) emitCompilePattern(lines);
|
|
3324
|
+
}
|
|
3325
|
+
/**
|
|
3326
|
+
* Emit the `__mergeQueryIntoLocation` helper. Shared between the static
|
|
3327
|
+
* routing-rules middleware (for `_redirects` entries) and the dynamic
|
|
3328
|
+
* `c.redirect` wrapper, so it's emitted unconditionally above the rewrite
|
|
3329
|
+
* middleware. If the destination already carries a param, the destination
|
|
3330
|
+
* wins — matching the static-redirect semantics.
|
|
3331
|
+
*/
|
|
3332
|
+
function ensureMergeQueryHelperEmitted(lines) {
|
|
3333
|
+
if (lines.some((l) => l.includes("function __mergeQueryIntoLocation"))) return;
|
|
3334
|
+
lines.push("function __mergeQueryIntoLocation(destination, search) {");
|
|
3335
|
+
lines.push(" if (!search || search === \"?\") return destination;");
|
|
3336
|
+
lines.push(" const fragIdx = destination.indexOf(\"#\");");
|
|
3337
|
+
lines.push(" const base = fragIdx === -1 ? destination : destination.slice(0, fragIdx);");
|
|
3338
|
+
lines.push(" const fragment = fragIdx === -1 ? \"\" : destination.slice(fragIdx);");
|
|
3339
|
+
lines.push(" const q = base.indexOf(\"?\");");
|
|
3340
|
+
lines.push(" if (q === -1) return base + search + fragment;");
|
|
3341
|
+
lines.push(" if (q === base.length - 1) return base.slice(0, q) + fragment;");
|
|
3342
|
+
lines.push(" const dp = new URLSearchParams(base.slice(q + 1));");
|
|
3343
|
+
lines.push(" for (const [k, v] of new URLSearchParams(search)) {");
|
|
3344
|
+
lines.push(" if (!dp.has(k)) dp.append(k, v);");
|
|
3345
|
+
lines.push(" }");
|
|
3346
|
+
lines.push(" return base.slice(0, q) + \"?\" + dp.toString() + fragment;");
|
|
3347
|
+
lines.push("}");
|
|
3348
|
+
lines.push("");
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Shared body for routing-rule evaluation: match rules in order (last wins),
|
|
3352
|
+
* substitute captures into destination, handle rewrite (200) or redirect (3xx).
|
|
3353
|
+
* Used by both the pre-pipeline routing middleware and the post-pipeline
|
|
3354
|
+
* fallback (notFound) middleware.
|
|
3355
|
+
*/
|
|
3356
|
+
function emitApplyRoutingRules(lines, rulesVar, indent, nodeTarget, markFallbackHandled = false) {
|
|
3357
|
+
lines.push(`${indent}let matched;`);
|
|
3358
|
+
lines.push(`${indent}for (const rule of ${rulesVar}) {`);
|
|
3359
|
+
lines.push(`${indent} if (rule.host && rule.host !== requestHost) continue;`);
|
|
3360
|
+
lines.push(`${indent} let regex;`);
|
|
3361
|
+
lines.push(`${indent} try {`);
|
|
3362
|
+
lines.push(`${indent} ({ regex } = __compilePattern(rule.source));`);
|
|
3363
|
+
lines.push(`${indent} } catch (err) {`);
|
|
3364
|
+
lines.push(`${indent} if (!__matchPatternWarned.has(rule.source)) {`);
|
|
3365
|
+
lines.push(`${indent} __matchPatternWarned.add(rule.source);`);
|
|
3366
|
+
lines.push(`${indent} console.warn(\`[void] skipping routing rule with invalid source \${JSON.stringify(rule.source)}: \${err && err.message ? err.message : err}\`);`);
|
|
3367
|
+
lines.push(`${indent} }`);
|
|
3368
|
+
lines.push(`${indent} continue;`);
|
|
3369
|
+
lines.push(`${indent} }`);
|
|
3370
|
+
lines.push(`${indent} const m = regex.exec(pathname);`);
|
|
3371
|
+
lines.push(`${indent} if (m) { matched = { rule, params: m.groups || {} }; break; }`);
|
|
3372
|
+
lines.push(`${indent}}`);
|
|
3373
|
+
lines.push(`${indent}if (matched) {`);
|
|
3374
|
+
lines.push(`${indent} const dest = __substituteParams(matched.rule.destination, matched.params);`);
|
|
3375
|
+
lines.push(`${indent} if (matched.rule.status === 200) {`);
|
|
3376
|
+
lines.push(`${indent} const __qIdx = dest.indexOf("?");`);
|
|
3377
|
+
lines.push(`${indent} const __pathOnly = __qIdx === -1 ? dest : dest.slice(0, __qIdx);`);
|
|
3378
|
+
lines.push(`${indent} const __searchOnly = __qIdx === -1 ? "" : dest.slice(__qIdx);`);
|
|
3379
|
+
lines.push(`${indent} const rewriteUrl = new URL(c.req.url);`);
|
|
3380
|
+
lines.push(`${indent} rewriteUrl.pathname = __pathOnly;`);
|
|
3381
|
+
lines.push(`${indent} if (__searchOnly) rewriteUrl.search = __searchOnly;`);
|
|
3382
|
+
lines.push(`${indent} const rewriteReq = new Request(rewriteUrl.toString(), c.req.raw);`);
|
|
3383
|
+
lines.push(`${indent} const __prior = __voidOriginalUrls.get(c.req.raw);`);
|
|
3384
|
+
lines.push(`${indent} __voidOriginalUrls.set(rewriteReq, __prior ?? new URL(c.req.url));`);
|
|
3385
|
+
if (markFallbackHandled) lines.push(`${indent} __voidFallbackHandled.add(rewriteReq);`);
|
|
3386
|
+
if (nodeTarget) lines.push(`${indent} const __r = await app.fetch(rewriteReq, c.env);`);
|
|
3387
|
+
else lines.push(`${indent} const __r = await app.fetch(rewriteReq, c.env, c.executionCtx);`);
|
|
3388
|
+
lines.push(`${indent} if (!__VOID_DEV_ROUTING) return __r;`);
|
|
3389
|
+
lines.push(`${indent} const __kind = matched.rule.force === true ? "rewrite" : "fallback";`);
|
|
3390
|
+
lines.push(`${indent} const __trace = __kind + "[" + matched.rule.source + "] \\u2192 " + dest + " (" + (matched.rule.origin || "unknown") + ")";`);
|
|
3391
|
+
lines.push(`${indent} return __voidAnnotateRouting(__r, __trace);`);
|
|
3392
|
+
lines.push(`${indent} }`);
|
|
3393
|
+
lines.push(`${indent} const location = __mergeQueryIntoLocation(dest, new URL(c.req.url).search);`);
|
|
3394
|
+
lines.push(`${indent} const __redir = c.redirect(location, matched.rule.status);`);
|
|
3395
|
+
lines.push(`${indent} if (!__VOID_DEV_ROUTING) return __redir;`);
|
|
3396
|
+
lines.push(`${indent} const __rtrace = "redirect[" + matched.rule.source + "] \\u2192 " + dest + " " + matched.rule.status + " (" + (matched.rule.origin || "unknown") + ")";`);
|
|
3397
|
+
lines.push(`${indent} return __voidAnnotateRouting(__redir, __rtrace);`);
|
|
3398
|
+
lines.push(`${indent}}`);
|
|
3399
|
+
}
|
|
3400
|
+
/**
|
|
3401
|
+
* Strip dev-only `origin` metadata from a rule list before embedding in the
|
|
3402
|
+
* generated worker code. Keeps production bundles free of authoring hints.
|
|
3403
|
+
*
|
|
3404
|
+
* Keep in sync with `stripDevOnlyRuleFields` in `cli/package.ts`. This helper
|
|
3405
|
+
* intentionally preserves `force` because the Node/Bun/Deno-target codegen
|
|
3406
|
+
* reads it at runtime to label the `X-Void-Routing` trace header as either
|
|
3407
|
+
* `rewrite[...]` (force) or `fallback[...]`. The CLI version strips both
|
|
3408
|
+
* fields because the deploy manifest never transmits either to the dispatch
|
|
3409
|
+
* worker (phase is implied by which bucket the rule ships in).
|
|
3410
|
+
*/
|
|
3411
|
+
function stripOriginForProd(rules) {
|
|
3412
|
+
return rules.map(({ origin: _origin, ...rest }) => rest);
|
|
3413
|
+
}
|
|
3414
|
+
/**
|
|
3415
|
+
* Emit routing rules middleware — handles both redirects (3xx) and rewrites (200).
|
|
3416
|
+
* Last match wins. Rewrites re-dispatch with a guard header to prevent loops.
|
|
3417
|
+
* Redirects preserve the request's query string in the Location header.
|
|
3418
|
+
*
|
|
3419
|
+
* When `dev === true` the per-rule `origin` metadata is preserved in the
|
|
3420
|
+
* compiled rule table and a matched rule's trace is emitted on the
|
|
3421
|
+
* `X-Void-Routing` response header. In production the origin field is
|
|
3422
|
+
* stripped and no trace header is emitted.
|
|
3423
|
+
*/
|
|
3424
|
+
function compileRoutingRulesMiddleware(lines, rules, dev, nodeTarget) {
|
|
3425
|
+
ensureCompilePatternEmitted(lines);
|
|
3426
|
+
lines.push("// Routing rules: redirects + rewrites (applied before route handlers)");
|
|
3427
|
+
lines.push(`const __routingRules = ${JSON.stringify(dev ? rules : stripOriginForProd(rules))};`);
|
|
3428
|
+
lines.push("app.use(\"*\", async (c, next) => {");
|
|
3429
|
+
lines.push(" if (__voidOriginalUrls.has(c.req.raw)) return next();");
|
|
3430
|
+
lines.push(" const __reqUrl = new URL(c.req.url);");
|
|
3431
|
+
lines.push(" const pathname = __reqUrl.pathname;");
|
|
3432
|
+
lines.push(" const requestHost = __reqUrl.hostname;");
|
|
3433
|
+
emitApplyRoutingRules(lines, "__routingRules", " ", nodeTarget);
|
|
3434
|
+
if (dev) {
|
|
3435
|
+
lines.push(" await next();");
|
|
3436
|
+
lines.push(" if (__VOID_DEV_ROUTING && c.res && !c.res.headers.has(\"X-Void-Routing\")) {");
|
|
3437
|
+
lines.push(" try { c.res.headers.set(\"X-Void-Routing\", \"pass-through\"); } catch {}");
|
|
3438
|
+
lines.push(" }");
|
|
3439
|
+
} else lines.push(" return next();");
|
|
3440
|
+
lines.push("});");
|
|
3441
|
+
lines.push("");
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Emit a fallback-rule check inside Hono's `notFound` hook. Fires only when
|
|
3445
|
+
* the pipeline didn't match a route — the "asset-miss / no route matched"
|
|
3446
|
+
* phase. Re-dispatches through `app.fetch` on a rule match so the re-dispatched
|
|
3447
|
+
* request runs through the full middleware + route stack with
|
|
3448
|
+
* `X-Void-Original-URL` set; falls through to the caller's existing notFound
|
|
3449
|
+
* body when no rule matches.
|
|
3450
|
+
*
|
|
3451
|
+
* Indent `indent` so the emitted statements nest cleanly within the caller's
|
|
3452
|
+
* block. Expects `__matchPattern`, `__voidOriginalUrls`, `__mergeQueryIntoLocation`,
|
|
3453
|
+
* `__substituteParams`, and `__voidAnnotateRouting` to already be in scope —
|
|
3454
|
+
* which they are, because `compileRoutingRulesMiddleware` runs earlier in
|
|
3455
|
+
* compile order and emits every one of them.
|
|
3456
|
+
*/
|
|
3457
|
+
function emitFallbackRulesCheck(lines, rules, dev, indent, nodeTarget) {
|
|
3458
|
+
ensureCompilePatternEmitted(lines);
|
|
3459
|
+
const tableMarker = "// Fallback rules (applied on no-match, post-route)";
|
|
3460
|
+
if (!lines.includes(tableMarker)) {
|
|
3461
|
+
lines.push(tableMarker);
|
|
3462
|
+
lines.push(`const __fallbackRules = ${JSON.stringify(dev ? rules : stripOriginForProd(rules))};`);
|
|
3463
|
+
lines.push("");
|
|
3464
|
+
}
|
|
3465
|
+
lines.push(`${indent}if (!__voidFallbackHandled.has(c.req.raw)) {`);
|
|
3466
|
+
lines.push(`${indent} const __reqUrl = new URL(c.req.url);`);
|
|
3467
|
+
lines.push(`${indent} const pathname = __reqUrl.pathname;`);
|
|
3468
|
+
lines.push(`${indent} const requestHost = __reqUrl.hostname;`);
|
|
3469
|
+
emitApplyRoutingRules(lines, "__fallbackRules", `${indent} `, nodeTarget, true);
|
|
3470
|
+
lines.push(`${indent}}`);
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* Emit header middleware — applies matching header rules to every response.
|
|
3474
|
+
* All matching rules merge; last match wins for same header name.
|
|
3475
|
+
*/
|
|
3476
|
+
function compileHeaderMiddleware(lines, rules) {
|
|
3477
|
+
ensureCompilePatternEmitted(lines);
|
|
3478
|
+
lines.push("// Header rules (applied to responses)");
|
|
3479
|
+
lines.push(`const __headerRules = ${JSON.stringify(rules)};`);
|
|
3480
|
+
lines.push("app.use(\"*\", async (c, next) => {");
|
|
3481
|
+
lines.push(" await next();");
|
|
3482
|
+
lines.push(" const pathname = new URL(c.req.url).pathname;");
|
|
3483
|
+
lines.push(" const matched = [];");
|
|
3484
|
+
lines.push(" for (const rule of __headerRules) {");
|
|
3485
|
+
lines.push(" if (__matchPattern(rule.pattern, pathname)) matched.push(...rule.headers);");
|
|
3486
|
+
lines.push(" }");
|
|
3487
|
+
lines.push(" for (const [name, value] of matched) {");
|
|
3488
|
+
lines.push(" c.res.headers.set(name, value);");
|
|
3489
|
+
lines.push(" }");
|
|
3490
|
+
lines.push("});");
|
|
3491
|
+
lines.push("");
|
|
3492
|
+
}
|
|
3493
|
+
function compileWorkerClient(authClientPath) {
|
|
3494
|
+
return `import app, { requestContext } from "virtual:void-routes";
|
|
3495
|
+
import { env } from "cloudflare:workers";
|
|
3496
|
+
export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};
|
|
3497
|
+
|
|
3498
|
+
export class FetchError extends Error {
|
|
3499
|
+
constructor(status, statusText, data) {
|
|
3500
|
+
super(\`fetch: Request failed with status \${status}.\`);
|
|
3501
|
+
this.name = "FetchError";
|
|
3502
|
+
this.status = status;
|
|
3503
|
+
this.statusText = statusText;
|
|
3504
|
+
this.data = data;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
export async function fetch(path, options) {
|
|
3509
|
+
let url = path;
|
|
3510
|
+
if (options?.params) {
|
|
3511
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
3512
|
+
url = url.replace(":" + key, encodeURIComponent(value));
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
if (options?.query) {
|
|
3517
|
+
const qs = new URLSearchParams(options.query).toString();
|
|
3518
|
+
if (qs) url += "?" + qs;
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
const method = options?.method ?? "GET";
|
|
3522
|
+
const headers = new Headers(options?.headers);
|
|
3523
|
+
|
|
3524
|
+
// Auto-forward cookie and authorization from the outer request via ALS
|
|
3525
|
+
const store = requestContext.getStore();
|
|
3526
|
+
if (store?.request) {
|
|
3527
|
+
const outerReq = store.request;
|
|
3528
|
+
if (!headers.has("cookie")) {
|
|
3529
|
+
const cookie = outerReq.headers.get("cookie");
|
|
3530
|
+
if (cookie) headers.set("cookie", cookie);
|
|
3531
|
+
}
|
|
3532
|
+
if (!headers.has("authorization")) {
|
|
3533
|
+
const auth = outerReq.headers.get("authorization");
|
|
3534
|
+
if (auth) headers.set("authorization", auth);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
if (options?.body != null && !headers.has("content-type")) {
|
|
3539
|
+
headers.set("content-type", "application/json");
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
const request = new Request("http://worker.local" + url, {
|
|
3543
|
+
method,
|
|
3544
|
+
headers,
|
|
3545
|
+
body: options?.body != null ? JSON.stringify(options.body) : undefined,
|
|
3546
|
+
});
|
|
3547
|
+
|
|
3548
|
+
// Use real executionCtx from ALS if available, otherwise provide a no-op fallback
|
|
3549
|
+
const executionCtx = store?.executionCtx ?? { waitUntil() {}, passThroughOnException() {} };
|
|
3550
|
+
const response = await app.fetch(request, env, executionCtx);
|
|
3551
|
+
|
|
3552
|
+
if (response.status === 204) return undefined;
|
|
3553
|
+
|
|
3554
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
3555
|
+
const data = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
3556
|
+
|
|
3557
|
+
if (!response.ok) {
|
|
3558
|
+
throw new FetchError(response.status, response.statusText, data);
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
return data;
|
|
3562
|
+
}
|
|
3563
|
+
`;
|
|
3564
|
+
}
|
|
3565
|
+
//#endregion
|
|
3566
|
+
//#region src/plugin-routing.ts
|
|
3567
|
+
const VIRTUAL_ROUTES_ID = "virtual:void-routes";
|
|
3568
|
+
const VIRTUAL_CLIENT_WORKER_ID = "virtual:void-client-worker";
|
|
3569
|
+
const VIRTUAL_CLIENT_BROWSER_ID = "virtual:void-client-browser";
|
|
3570
|
+
const RESOLVE_ID_FILTER = /^(virtual:void-(routes|client-worker|client-browser)|void(\/client|\/queues)?)$/;
|
|
3571
|
+
const LOAD_ID_FILTER = /^virtual:void-(routes|client-worker|client-browser)$/;
|
|
3572
|
+
function routingPlugin(root, options) {
|
|
3573
|
+
let scan = null;
|
|
3574
|
+
let ssr = null;
|
|
3575
|
+
let authConfigPath = null;
|
|
3576
|
+
const routesDir = join(root, "routes");
|
|
3577
|
+
const middlewareDir = join(root, "middleware");
|
|
3578
|
+
const jobsDir = join(root, "crons");
|
|
3579
|
+
const queuesDir = join(root, "queues");
|
|
3580
|
+
const pagesDir = join(root, "pages");
|
|
3581
|
+
const srcDir = join(root, "src");
|
|
3582
|
+
const authConfigDir = root;
|
|
3583
|
+
const ssrEntryAbsPaths = new Set([...SSR_SERVER_ENTRY_RELATIVE_PATHS, ...SSR_CLIENT_ENTRY_RELATIVE_PATHS].map((path) => join(root, path)));
|
|
3584
|
+
function writeRouteTypes() {
|
|
3585
|
+
if (!scan) return;
|
|
3586
|
+
const { dts } = generateRouteTypes(scan, routesDir, outDir, pagesDir);
|
|
3587
|
+
writeFileSync(join(outDir, "routes.d.ts"), dts);
|
|
3588
|
+
}
|
|
3589
|
+
function writeQueueTypes() {
|
|
3590
|
+
if (!scan) return;
|
|
3591
|
+
const dts = generateQueueTypes(scan.queues, queuesDir, outDir);
|
|
3592
|
+
writeFileSync(join(outDir, "queues.d.ts"), dts);
|
|
3593
|
+
}
|
|
3594
|
+
async function rescan() {
|
|
3595
|
+
scan = await scanRoutes(root, { output: options.output });
|
|
3596
|
+
authConfigPath = findVoidAuthConfig(root);
|
|
3597
|
+
const entries = validateSsrEntries(root);
|
|
3598
|
+
ssr = entries ? {
|
|
3599
|
+
serverEntryPath: entries.server.absolutePath,
|
|
3600
|
+
clientEntryRelativePath: entries.client.relativePath
|
|
3601
|
+
} : null;
|
|
3602
|
+
}
|
|
3603
|
+
const resolveFile = (specifier) => fileURLToPath(import.meta.resolve(specifier));
|
|
3604
|
+
const handlerFile = resolveFile("#self/handler");
|
|
3605
|
+
const runtimeDir = dirname(resolveFile("#self/client"));
|
|
3606
|
+
const fetchPath = resolveFile("#self/runtime/fetch");
|
|
3607
|
+
const fetchStreamPath = resolveFile("#self/runtime/fetch-stream");
|
|
3608
|
+
const sandboxRuntimePath = resolveFile("#self/sandbox");
|
|
3609
|
+
const drizzleAdapterPath = resolveFile("better-auth/adapters/drizzle");
|
|
3610
|
+
let authClientPath = join(runtimeDir, "auth-client.mjs");
|
|
3611
|
+
const remote = options.remoteMode ? {
|
|
3612
|
+
remotePath: resolveFile("#self/remote"),
|
|
3613
|
+
envPath: resolveFile("#self/_env")
|
|
3614
|
+
} : void 0;
|
|
3615
|
+
function getEnvSchemaModule() {
|
|
3616
|
+
const envFile = findEnvFile(root);
|
|
3617
|
+
if (!envFile) return;
|
|
3618
|
+
return {
|
|
3619
|
+
importPath: envFile,
|
|
3620
|
+
helperPath: resolveFile("#self/env")
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
const compileOptions = {
|
|
3624
|
+
honoPath: resolveFile("hono"),
|
|
3625
|
+
responsePath: resolveFile("#self/response"),
|
|
3626
|
+
handlerPath: resolveFile("#self/handler"),
|
|
3627
|
+
runtimeEnvPath: resolveFile("#self/_env"),
|
|
3628
|
+
projectRoot: root,
|
|
3629
|
+
homeDir: homedir(),
|
|
3630
|
+
webSocketRuntimePath: resolveFile("#self/runtime/ws-server"),
|
|
3631
|
+
reactRefresh: false,
|
|
3632
|
+
dev: false,
|
|
3633
|
+
devTriggerToken: options.devTriggerToken,
|
|
3634
|
+
auth: null,
|
|
3635
|
+
nodeTarget: !!options.target,
|
|
3636
|
+
explicitPrerenderPaths: options.prerenderPaths,
|
|
3637
|
+
bindingHandlerPath: options.target ? void 0 : resolveFile("#self/remote/binding-handler")
|
|
3638
|
+
};
|
|
3639
|
+
let publicDir = join(root, "public");
|
|
3640
|
+
function reloadFileRules() {
|
|
3641
|
+
const merged = mergeRoutingRules({
|
|
3642
|
+
dir: publicDir,
|
|
3643
|
+
routing: options.routing
|
|
3644
|
+
});
|
|
3645
|
+
lintDuplicateSources(merged.redirectRules, "redirects/rewrites (pre-asset)");
|
|
3646
|
+
lintDuplicateSources(merged.fallbackRules, "fallbacks (post-asset)");
|
|
3647
|
+
lintDestinationSplats(merged.redirectRules, "redirects/rewrites (pre-asset)");
|
|
3648
|
+
lintDestinationSplats(merged.fallbackRules, "fallbacks (post-asset)");
|
|
3649
|
+
compileOptions.headerRules = merged.headerRules.length > 0 ? merged.headerRules : void 0;
|
|
3650
|
+
compileOptions.redirectRules = merged.redirectRules.length > 0 ? merged.redirectRules : void 0;
|
|
3651
|
+
compileOptions.fallbackRules = merged.fallbackRules;
|
|
3652
|
+
}
|
|
3653
|
+
reloadFileRules();
|
|
3654
|
+
function resolveAuthCompileOptions() {
|
|
3655
|
+
if (!options.isAuthEnabled() && !authConfigPath) {
|
|
3656
|
+
compileOptions.auth = null;
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
const config = readConfig(root);
|
|
3660
|
+
const dialect = getDatabaseDialect(config);
|
|
3661
|
+
compileOptions.auth = {
|
|
3662
|
+
authRuntimePath: dialect === "postgresql" ? join(runtimeDir, "better-auth-pg.mjs") : join(runtimeDir, "better-auth.mjs"),
|
|
3663
|
+
authConfigPath,
|
|
3664
|
+
authDatabasePath: null,
|
|
3665
|
+
authSchemaPath: null,
|
|
3666
|
+
dialect,
|
|
3667
|
+
providers: config.auth?.providers ?? null
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
async function writeAuthDatabaseFiles() {
|
|
3671
|
+
if (!compileOptions.auth) return;
|
|
3672
|
+
const authRuntime = {
|
|
3673
|
+
dialect: compileOptions.auth.dialect,
|
|
3674
|
+
providers: compileOptions.auth.providers,
|
|
3675
|
+
userConfig: await loadVoidAuthConfig(compileOptions.auth.authConfigPath)
|
|
3676
|
+
};
|
|
3677
|
+
const env = loadEnv(resolvedConfig?.mode ?? "development", root, "");
|
|
3678
|
+
const schemaPath = join(outDir, "better-auth-schema.ts");
|
|
3679
|
+
const databasePath = join(outDir, "better-auth-db.ts");
|
|
3680
|
+
writeFileSync(schemaPath, generateVoidAuthDrizzleSchema(authRuntime, env));
|
|
3681
|
+
writeFileSync(databasePath, generateVoidAuthDatabaseModule({
|
|
3682
|
+
dialect: compileOptions.auth.dialect,
|
|
3683
|
+
schemaImportPath: schemaPath,
|
|
3684
|
+
drizzleAdapterPath
|
|
3685
|
+
}));
|
|
3686
|
+
compileOptions.auth.authSchemaPath = schemaPath;
|
|
3687
|
+
compileOptions.auth.authDatabasePath = databasePath;
|
|
3688
|
+
}
|
|
3689
|
+
let resolvedConfig = null;
|
|
3690
|
+
function resolveAuthClientPath(config) {
|
|
3691
|
+
if (config.plugins.some((plugin) => plugin.name === "void-react:pages")) return join(runtimeDir, "auth-client-react.mjs");
|
|
3692
|
+
if (config.plugins.some((plugin) => plugin.name === "void-vue:pages")) return join(runtimeDir, "auth-client-vue.mjs");
|
|
3693
|
+
if (config.plugins.some((plugin) => plugin.name === "void-svelte:pages")) return join(runtimeDir, "auth-client-svelte.mjs");
|
|
3694
|
+
if (config.plugins.some((plugin) => plugin.name === "void-solid:pages")) return join(runtimeDir, "auth-client-solid.mjs");
|
|
3695
|
+
return join(runtimeDir, "auth-client.mjs");
|
|
3696
|
+
}
|
|
3697
|
+
function isPotentialAuthConfigPath(path) {
|
|
3698
|
+
if (!path.startsWith(authConfigDir + "/")) return false;
|
|
3699
|
+
const rel = path.slice(authConfigDir.length + 1);
|
|
3700
|
+
return /^auth\.(c|m)?[jt]s$/.test(rel);
|
|
3701
|
+
}
|
|
3702
|
+
function getPagesCompileOptions() {
|
|
3703
|
+
if (!scan || scan.pages.pages.length === 0) return null;
|
|
3704
|
+
const hasIslandPages = scan.pages.pages.some((p) => p.island);
|
|
3705
|
+
const mdPlugin = resolvedConfig?.plugins?.find((p) => p.name === "void-md");
|
|
3706
|
+
const mdClientEntryId = hasIslandPages && mdPlugin?.api?.clientEntryId ? mdPlugin.api.clientEntryId : void 0;
|
|
3707
|
+
return {
|
|
3708
|
+
pagesDir,
|
|
3709
|
+
pagesDirRelative: relative(root, pagesDir).replaceAll("\\", "/"),
|
|
3710
|
+
protocolPath: resolveFile("#self/pages-protocol"),
|
|
3711
|
+
headPath: resolveFile("#self/pages-head"),
|
|
3712
|
+
clientEntryId: "virtual:void-pages-client",
|
|
3713
|
+
islandClientEntryId: hasIslandPages ? "virtual:void-islands-client" : void 0,
|
|
3714
|
+
mdClientEntryId,
|
|
3715
|
+
headConfig: options.headConfig
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
function shouldServeSpaFallback() {
|
|
3719
|
+
return !!scan && scan.pages.pages.length === 0 && !ssr && (scan.routes.length > 0 || (scan.websockets?.length ?? 0) > 0 || scan.middleware.length > 0 || compileOptions.auth !== null);
|
|
3720
|
+
}
|
|
3721
|
+
const outDir = join(root, ".void");
|
|
3722
|
+
mkdirSync(outDir, { recursive: true });
|
|
3723
|
+
if (options.target) {
|
|
3724
|
+
writeFileSync(join(outDir, "app.ts"), compileNodeApp(options.target, compileOptions.honoPath, getEnvSchemaModule()));
|
|
3725
|
+
writeFileSync(join(outDir, "index.ts"), compileNodeServer(options.target));
|
|
3726
|
+
} else writeFileSync(join(outDir, "entry.ts"), compileWorkerEntry(false, remote, void 0, void 0, compileOptions.runtimeEnvPath, getEnvSchemaModule(), options.hasSandbox ? {
|
|
3727
|
+
bindingName: options.sandboxBindingName,
|
|
3728
|
+
className: options.sandboxClassName,
|
|
3729
|
+
runtimePath: sandboxRuntimePath
|
|
3730
|
+
} : false));
|
|
3731
|
+
return {
|
|
3732
|
+
name: "void:routing",
|
|
3733
|
+
enforce: "pre",
|
|
3734
|
+
configResolved(config) {
|
|
3735
|
+
resolvedConfig = config;
|
|
3736
|
+
authClientPath = resolveAuthClientPath(config);
|
|
3737
|
+
compileOptions.dev = config.command === "serve";
|
|
3738
|
+
compileOptions.reactRefresh = config.plugins.some((plugin) => plugin.name === "vite:react-babel" || plugin.name === "vite:react-refresh" || plugin.name === "vite:react-swc");
|
|
3739
|
+
const resolvedPublicDir = config.publicDir && config.publicDir.length > 0 ? config.publicDir : null;
|
|
3740
|
+
if (resolvedPublicDir !== publicDir) {
|
|
3741
|
+
publicDir = resolvedPublicDir;
|
|
3742
|
+
reloadFileRules();
|
|
3743
|
+
}
|
|
3744
|
+
},
|
|
3745
|
+
config(_, env) {
|
|
3746
|
+
if (env.command === "serve" && options.isAuthEnabled()) return { resolve: { alias: [
|
|
3747
|
+
"client",
|
|
3748
|
+
"react",
|
|
3749
|
+
"vue",
|
|
3750
|
+
"svelte",
|
|
3751
|
+
"solid"
|
|
3752
|
+
].flatMap((sub) => {
|
|
3753
|
+
try {
|
|
3754
|
+
return [{
|
|
3755
|
+
find: `better-auth/${sub}`,
|
|
3756
|
+
replacement: resolveFile(`better-auth/${sub}`)
|
|
3757
|
+
}];
|
|
3758
|
+
} catch {
|
|
3759
|
+
return [];
|
|
3760
|
+
}
|
|
3761
|
+
}) } };
|
|
3762
|
+
const entries = validateSsrEntries(root);
|
|
3763
|
+
if (!entries || env.command !== "build") return;
|
|
3764
|
+
return { build: {
|
|
3765
|
+
manifest: true,
|
|
3766
|
+
rollupOptions: { input: entries.client.absolutePath }
|
|
3767
|
+
} };
|
|
3768
|
+
},
|
|
3769
|
+
async buildStart() {
|
|
3770
|
+
await rescan();
|
|
3771
|
+
if (options.target && (scan?.websockets?.length ?? 0) > 0) throw new Error("routing: WebSocket routes require the default Cloudflare target.");
|
|
3772
|
+
resolveAuthCompileOptions();
|
|
3773
|
+
await writeAuthDatabaseFiles();
|
|
3774
|
+
writeRouteTypes();
|
|
3775
|
+
writeQueueTypes();
|
|
3776
|
+
if (!options.target) {
|
|
3777
|
+
const migrationsDir = join(root, "db", "migrations");
|
|
3778
|
+
if (existsSync(migrationsDir) || compileOptions.auth) {
|
|
3779
|
+
const { collectMigrations } = await import("./collect-CjeZgz5D.mjs").then((n) => n.n);
|
|
3780
|
+
const migrations = existsSync(migrationsDir) ? collectMigrations(migrationsDir) : [];
|
|
3781
|
+
const dialect = getDatabaseDialect(readConfig(root));
|
|
3782
|
+
compileOptions.migrationHandlerPath = resolveFile(dialect === "postgresql" ? "#self/runtime/migration-handler-pg" : "#self/runtime/migration-handler");
|
|
3783
|
+
compileOptions.migrations = migrations;
|
|
3784
|
+
compileOptions.migrationDialect = dialect;
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
if (options.target) {
|
|
3788
|
+
writeFileSync(join(outDir, "app.ts"), compileNodeApp(options.target, compileOptions.honoPath, getEnvSchemaModule()));
|
|
3789
|
+
writeFileSync(join(outDir, "index.ts"), compileNodeServer(options.target));
|
|
3790
|
+
} else writeFileSync(join(outDir, "entry.ts"), compileWorkerEntry(scan.queues.length > 0, remote, resolvedConfig?.build.assetsDir, (scan.websockets ?? []).map((route) => route.className), compileOptions.runtimeEnvPath, getEnvSchemaModule(), options.hasSandbox ? {
|
|
3791
|
+
bindingName: options.sandboxBindingName,
|
|
3792
|
+
className: options.sandboxClassName,
|
|
3793
|
+
runtimePath: sandboxRuntimePath
|
|
3794
|
+
} : false, shouldServeSpaFallback()));
|
|
3795
|
+
},
|
|
3796
|
+
resolveId: {
|
|
3797
|
+
filter: { id: RESOLVE_ID_FILTER },
|
|
3798
|
+
handler(id) {
|
|
3799
|
+
if (id === VIRTUAL_ROUTES_ID) return VIRTUAL_ROUTES_ID;
|
|
3800
|
+
if (id === VIRTUAL_CLIENT_WORKER_ID) return VIRTUAL_CLIENT_WORKER_ID;
|
|
3801
|
+
if (id === VIRTUAL_CLIENT_BROWSER_ID) return VIRTUAL_CLIENT_BROWSER_ID;
|
|
3802
|
+
if (id === "void") return handlerFile;
|
|
3803
|
+
if (id === "void/queues") return resolveFile("#self/queues");
|
|
3804
|
+
if (id === "void/client") return this.environment?.name === "client" ? VIRTUAL_CLIENT_BROWSER_ID : VIRTUAL_CLIENT_WORKER_ID;
|
|
3805
|
+
}
|
|
3806
|
+
},
|
|
3807
|
+
load: {
|
|
3808
|
+
filter: { id: LOAD_ID_FILTER },
|
|
3809
|
+
async handler(id) {
|
|
3810
|
+
if (id === VIRTUAL_CLIENT_BROWSER_ID) return compileBrowserClient(authClientPath, fetchPath, fetchStreamPath);
|
|
3811
|
+
if (id === VIRTUAL_CLIENT_WORKER_ID) {
|
|
3812
|
+
if (options.target) return compileNodeClient(authClientPath);
|
|
3813
|
+
return compileWorkerClient(authClientPath);
|
|
3814
|
+
}
|
|
3815
|
+
if (id !== VIRTUAL_ROUTES_ID) return;
|
|
3816
|
+
if (!scan) await rescan();
|
|
3817
|
+
return compileRouteTable(scan, routesDir, middlewareDir, jobsDir, queuesDir, ssr, compileOptions, getPagesCompileOptions());
|
|
3818
|
+
}
|
|
3819
|
+
},
|
|
3820
|
+
configureServer(devServer) {
|
|
3821
|
+
const watcher = devServer.watcher;
|
|
3822
|
+
const handleFileChange = async (path, event) => {
|
|
3823
|
+
if (!path.startsWith(routesDir) && !path.startsWith(middlewareDir) && !path.startsWith(jobsDir) && !path.startsWith(queuesDir) && !path.startsWith(pagesDir) && !path.startsWith(srcDir) && !isPotentialAuthConfigPath(path)) return;
|
|
3824
|
+
if (path.startsWith(srcDir) && !ssrEntryAbsPaths.has(path)) return;
|
|
3825
|
+
await rescan();
|
|
3826
|
+
resolveAuthCompileOptions();
|
|
3827
|
+
await writeAuthDatabaseFiles();
|
|
3828
|
+
writeRouteTypes();
|
|
3829
|
+
writeQueueTypes();
|
|
3830
|
+
if (options.target) {
|
|
3831
|
+
writeFileSync(join(outDir, "app.ts"), compileNodeApp(options.target, compileOptions.honoPath, getEnvSchemaModule()));
|
|
3832
|
+
writeFileSync(join(outDir, "index.ts"), compileNodeServer(options.target));
|
|
3833
|
+
} else writeFileSync(join(outDir, "entry.ts"), compileWorkerEntry(scan.queues.length > 0, remote, resolvedConfig?.build.assetsDir, (scan.websockets ?? []).map((route) => route.className), compileOptions.runtimeEnvPath, getEnvSchemaModule(), options.hasSandbox ? {
|
|
3834
|
+
bindingName: options.sandboxBindingName,
|
|
3835
|
+
className: options.sandboxClassName,
|
|
3836
|
+
runtimePath: sandboxRuntimePath
|
|
3837
|
+
} : false, shouldServeSpaFallback()));
|
|
3838
|
+
const structural = event !== "change";
|
|
3839
|
+
for (const env of Object.values(devServer.environments)) {
|
|
3840
|
+
const mod = env.moduleGraph.getModuleById(VIRTUAL_ROUTES_ID);
|
|
3841
|
+
if (mod) {
|
|
3842
|
+
env.moduleGraph.invalidateModule(mod);
|
|
3843
|
+
if (structural) env.hot.send({ type: "full-reload" });
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
if (/\.island\./.test(path) || path.endsWith(".md")) devServer.environments.client?.hot.send({ type: "full-reload" });
|
|
3847
|
+
};
|
|
3848
|
+
watcher.on("add", (path) => handleFileChange(path, "add"));
|
|
3849
|
+
watcher.on("change", (path) => handleFileChange(path, "change"));
|
|
3850
|
+
watcher.on("unlink", (path) => handleFileChange(path, "unlink"));
|
|
3851
|
+
if (publicDir) {
|
|
3852
|
+
const redirectsPath = join(publicDir, "_redirects");
|
|
3853
|
+
const headersPath = join(publicDir, "_headers");
|
|
3854
|
+
const handleRulesFileChange = (path) => {
|
|
3855
|
+
if (path !== redirectsPath && path !== headersPath) return;
|
|
3856
|
+
reloadFileRules();
|
|
3857
|
+
for (const env of Object.values(devServer.environments)) {
|
|
3858
|
+
const mod = env.moduleGraph.getModuleById(VIRTUAL_ROUTES_ID);
|
|
3859
|
+
if (mod) {
|
|
3860
|
+
env.moduleGraph.invalidateModule(mod);
|
|
3861
|
+
env.hot.send({ type: "full-reload" });
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
};
|
|
3865
|
+
watcher.on("add", handleRulesFileChange);
|
|
3866
|
+
watcher.on("change", handleRulesFileChange);
|
|
3867
|
+
watcher.on("unlink", handleRulesFileChange);
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
};
|
|
3871
|
+
}
|
|
3872
|
+
//#endregion
|
|
3873
|
+
//#region src/index.ts
|
|
3874
|
+
/**
|
|
3875
|
+
* Persist the dev-trigger token at `<root>/.void/dev-trigger-token` so
|
|
3876
|
+
* every voidPlugin() invocation against the same project reads the same
|
|
3877
|
+
* value. This matters because `@cloudflare/vite-plugin` spawns a
|
|
3878
|
+
* worker-environment child Vite that re-imports vite.config.ts and calls
|
|
3879
|
+
* voidPlugin() again — without persistence the parent and child would
|
|
3880
|
+
* generate different UUIDs, the dispatch file would embed one and the
|
|
3881
|
+
* hint plugin would print the other, and curls would land on
|
|
3882
|
+
* `unauthorized`.
|
|
3883
|
+
*
|
|
3884
|
+
* The file persists across dev sessions by design — it lives under `.void/`
|
|
3885
|
+
* (gitignored), and `cleanupWrapper` in `void deploy` explicitly preserves
|
|
3886
|
+
* it across its own wipe so saved curl scripts keep working between deploys.
|
|
3887
|
+
* The token is local to the project and only honored by a worker bound to
|
|
3888
|
+
* localhost; treat it like the rest of `.void/` — disposable scratch state,
|
|
3889
|
+
* safe to remove manually if desired.
|
|
3890
|
+
*/
|
|
3891
|
+
/**
|
|
3892
|
+
* Best-effort detection of `vite build` (and `vite preview`) so we can skip
|
|
3893
|
+
* dev-only side effects — writing the dev-trigger token and the dispatch
|
|
3894
|
+
* file — when no dev server will run. Vite's CLI passes the subcommand as
|
|
3895
|
+
* `process.argv[2]`; programmatic API users (and `vite` with no subcommand)
|
|
3896
|
+
* default to `dev`, which we detect as the absence of a build-shaped first
|
|
3897
|
+
* positional. This is intentionally permissive: when in doubt, we behave as
|
|
3898
|
+
* if we're in dev so first-request correctness is preserved.
|
|
3899
|
+
*/
|
|
3900
|
+
function isViteBuildOrPreview() {
|
|
3901
|
+
const subcommand = process.argv[2];
|
|
3902
|
+
return subcommand === "build" || subcommand === "preview";
|
|
3903
|
+
}
|
|
3904
|
+
function getOrCreateDevTriggerToken(root) {
|
|
3905
|
+
const tokenPath = join(root, ".void", "dev-trigger-token");
|
|
3906
|
+
const readExisting = () => {
|
|
3907
|
+
try {
|
|
3908
|
+
const existing = readFileSync(tokenPath, "utf-8").trim();
|
|
3909
|
+
return existing.length > 0 ? existing : void 0;
|
|
3910
|
+
} catch {
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
const existing = readExisting();
|
|
3915
|
+
if (existing) return existing;
|
|
3916
|
+
mkdirSync(join(root, ".void"), { recursive: true });
|
|
3917
|
+
const fresh = crypto.randomUUID();
|
|
3918
|
+
try {
|
|
3919
|
+
const fd = openSync(tokenPath, "wx");
|
|
3920
|
+
try {
|
|
3921
|
+
writeSync(fd, fresh);
|
|
3922
|
+
} finally {
|
|
3923
|
+
closeSync(fd);
|
|
3924
|
+
}
|
|
3925
|
+
return fresh;
|
|
3926
|
+
} catch (err) {
|
|
3927
|
+
if (err.code !== "EEXIST") throw err;
|
|
3928
|
+
const winner = readExisting();
|
|
3929
|
+
if (winner) return winner;
|
|
3930
|
+
return fresh;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
/**
|
|
3934
|
+
* Warn when the project's `package.json` does not declare `"type": "module"`.
|
|
3935
|
+
*
|
|
3936
|
+
* Vite's default `configLoader: "bundle"` bundles `vite.config.ts`,
|
|
3937
|
+
* externalizes deps, and then loads the bundle. When `package.json` is non-ESM
|
|
3938
|
+
* the bundle is written as `.js` and loaded via CJS `require()`, which throws
|
|
3939
|
+
* `ERR_REQUIRE_ASYNC_MODULE` if any externalized dep's ESM graph contains
|
|
3940
|
+
* top-level await. `@cloudflare/vite-plugin` (which voidPlugin pulls in) has
|
|
3941
|
+
* exactly such a TLA, so non-ESM consumers hit this on every Vite invocation.
|
|
3942
|
+
*
|
|
3943
|
+
* Runs once per Vite process (factory time), single sync read.
|
|
3944
|
+
*/
|
|
3945
|
+
function warnIfPackageJsonIsNotEsm(root) {
|
|
3946
|
+
const pkgPath = join(root, "package.json");
|
|
3947
|
+
if (!existsSync(pkgPath)) return;
|
|
3948
|
+
let pkg;
|
|
3949
|
+
try {
|
|
3950
|
+
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3951
|
+
} catch {
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
if (pkg.type === "module") return;
|
|
3955
|
+
voidWarn("package.json is missing `\"type\": \"module\"`. Vite's default config loader will fail with ERR_REQUIRE_ASYNC_MODULE on transitive top-level await (e.g. @cloudflare/vite-plugin). Add `\"type\": \"module\"` to package.json, or run vite build with `--configLoader runner`.");
|
|
3956
|
+
}
|
|
3957
|
+
/**
|
|
3958
|
+
* Resolve the server entry module for a Class A framework. We always wrap
|
|
3959
|
+
* the framework's own entry with a tiny shim that intercepts
|
|
3960
|
+
* `/__void/{scheduled,queue}` in dev so cron and queue handlers run with
|
|
3961
|
+
* the worker's full CF bindings. The interception block is gated on
|
|
3962
|
+
* `import.meta.env.DEV` and the dispatch module is loaded via dynamic
|
|
3963
|
+
* `import()` so the entire dev path (including user cron imports) is
|
|
3964
|
+
* dead-code-eliminated in `vite build`. Production deploys go through
|
|
3965
|
+
* `cli/wrapper.ts` instead.
|
|
3966
|
+
*/
|
|
3967
|
+
function getFrameworkEntry(framework, root, devTriggerToken) {
|
|
3968
|
+
const isBuild = isViteBuildOrPreview();
|
|
3969
|
+
if (!isBuild) writeFrameworkTriggersDispatch(root, devTriggerToken);
|
|
3970
|
+
return generateClassATriggerWrapper(root, framework, getOriginalFrameworkEntry(framework, root), isBuild);
|
|
3971
|
+
}
|
|
3972
|
+
/** The framework's own server entry, before our trigger wrapper. */
|
|
3973
|
+
function getOriginalFrameworkEntry(framework, root) {
|
|
3974
|
+
switch (framework) {
|
|
3975
|
+
case "tanstack-start": return "@tanstack/react-start/server-entry";
|
|
3976
|
+
case "react-router": return generateReactRouterEntry(root);
|
|
3977
|
+
case "vinext-app": return "vinext/server/app-router-entry";
|
|
3978
|
+
case "vinext-pages": return generateVinextPagesEntry(root);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
/**
|
|
3982
|
+
* Generate `<root>/.void/<framework>-trigger-wrapper.ts`. This file becomes
|
|
3983
|
+
* `workerConfig.main` so the dev miniflare/workerd evaluates *our* fetch
|
|
3984
|
+
* first, dispatches `/__void/{scheduled,queue}` if it matches, and otherwise
|
|
3985
|
+
* delegates to the framework's original entry.
|
|
3986
|
+
*
|
|
3987
|
+
* We emit two shapes of the wrapper depending on the Vite command:
|
|
3988
|
+
*
|
|
3989
|
+
* - Serve: dispatch is dynamically imported and gets a chance to handle
|
|
3990
|
+
* `/__void/{scheduled,queue}` before falling through to the framework.
|
|
3991
|
+
* - Build: dispatch is omitted entirely. The wrapper just delegates fetch.
|
|
3992
|
+
*
|
|
3993
|
+
* We can't simply gate the dynamic import on `import.meta.env.DEV` and
|
|
3994
|
+
* trust DCE: rolldown emits chunks for dynamic imports during chunk
|
|
3995
|
+
* splitting, *before* constant folding removes the dead branch, so the
|
|
3996
|
+
* dispatch file (and its transitive user-cron/queue imports + dev token)
|
|
3997
|
+
* would still ship in the production bundle.
|
|
3998
|
+
*/
|
|
3999
|
+
function generateClassATriggerWrapper(root, framework, original, isBuild) {
|
|
4000
|
+
const outDir = join(root, ".void");
|
|
4001
|
+
mkdirSync(outDir, { recursive: true });
|
|
4002
|
+
const wrapperPath = join(outDir, `${framework}-trigger-wrapper.ts`);
|
|
4003
|
+
writeFileSync(wrapperPath, isBuild ? `// @ts-nocheck — Auto-generated by Void for vite build. Do NOT edit.
|
|
4004
|
+
import original from ${JSON.stringify(original)};
|
|
4005
|
+
|
|
4006
|
+
export default {
|
|
4007
|
+
...original,
|
|
4008
|
+
async fetch(request, env, ctx) {
|
|
4009
|
+
return original.fetch(request, env, ctx);
|
|
4010
|
+
},
|
|
4011
|
+
};
|
|
4012
|
+
` : `// @ts-nocheck — Auto-generated by Void in dev. Do NOT edit.
|
|
4013
|
+
import original from ${JSON.stringify(original)};
|
|
4014
|
+
|
|
4015
|
+
export default {
|
|
4016
|
+
...original,
|
|
4017
|
+
async fetch(request, env, ctx) {
|
|
4018
|
+
const { handleDevTrigger } = await import(${JSON.stringify(frameworkTriggersDispatchPath(root))});
|
|
4019
|
+
const response = await handleDevTrigger(request, env, ctx);
|
|
4020
|
+
if (response) return response;
|
|
4021
|
+
return original.fetch(request, env, ctx);
|
|
4022
|
+
},
|
|
4023
|
+
};
|
|
4024
|
+
`);
|
|
4025
|
+
return wrapperPath;
|
|
4026
|
+
}
|
|
4027
|
+
/** Generate a worker entry for vinext Pages Router projects. */
|
|
4028
|
+
function generateVinextPagesEntry(root) {
|
|
4029
|
+
const outDir = join(root, ".void");
|
|
4030
|
+
mkdirSync(outDir, { recursive: true });
|
|
4031
|
+
const entryPath = join(outDir, "vinext-pages-entry.ts");
|
|
4032
|
+
writeFileSync(entryPath, `// Auto-generated by Void — vinext Pages Router worker entry
|
|
4033
|
+
// @ts-expect-error virtual module resolved by vinext at build time
|
|
4034
|
+
import { renderPage, handleApiRoute } from "virtual:vinext-server-entry";
|
|
4035
|
+
|
|
4036
|
+
export default {
|
|
4037
|
+
async fetch(request: Request): Promise<Response> {
|
|
4038
|
+
const url = new URL(request.url);
|
|
4039
|
+
const pathname = url.pathname;
|
|
4040
|
+
if (pathname.replaceAll("\\\\", "/").startsWith("//")) {
|
|
4041
|
+
return new Response("404 Not Found", { status: 404 });
|
|
4042
|
+
}
|
|
4043
|
+
const urlWithQuery = pathname + url.search;
|
|
4044
|
+
if (pathname.startsWith("/api/") || pathname === "/api") {
|
|
4045
|
+
return await handleApiRoute(request, urlWithQuery);
|
|
4046
|
+
}
|
|
4047
|
+
return await renderPage(request, urlWithQuery, null);
|
|
4048
|
+
},
|
|
4049
|
+
};
|
|
4050
|
+
`);
|
|
4051
|
+
return entryPath;
|
|
4052
|
+
}
|
|
4053
|
+
/** Generate a worker entry for React Router v7 projects. */
|
|
4054
|
+
function generateReactRouterEntry(root) {
|
|
4055
|
+
const outDir = join(root, ".void");
|
|
4056
|
+
mkdirSync(outDir, { recursive: true });
|
|
4057
|
+
const entryPath = join(outDir, "react-router-entry.ts");
|
|
4058
|
+
writeFileSync(entryPath, `// Auto-generated by Void — React Router v7 worker entry
|
|
4059
|
+
import { createRequestHandler } from "react-router";
|
|
4060
|
+
|
|
4061
|
+
const requestHandler = createRequestHandler(
|
|
4062
|
+
// @ts-expect-error virtual module resolved by @react-router/dev at build time
|
|
4063
|
+
() => import("virtual:react-router/server-build"),
|
|
4064
|
+
import.meta.env.MODE,
|
|
4065
|
+
);
|
|
4066
|
+
|
|
4067
|
+
export default {
|
|
4068
|
+
async fetch(request: Request): Promise<Response> {
|
|
4069
|
+
return requestHandler(request);
|
|
4070
|
+
},
|
|
4071
|
+
};
|
|
4072
|
+
`);
|
|
4073
|
+
return entryPath;
|
|
4074
|
+
}
|
|
4075
|
+
/** Check whether a binding name already exists in a resolved bindings array. */
|
|
4076
|
+
function hasBinding(list, name) {
|
|
4077
|
+
return Array.isArray(list) && list.some((b) => b.binding === name);
|
|
4078
|
+
}
|
|
4079
|
+
const CLOUDFLARE_BUILTIN_IMPORT = /^cloudflare:/;
|
|
4080
|
+
const SANDBOX_SDK_IMPORT = "@cloudflare/sandbox";
|
|
4081
|
+
const SANDBOX_SDK_IMPORT_FILTER = /^@cloudflare\/sandbox$/;
|
|
4082
|
+
const DEFAULT_WORKER_ENVIRONMENT_NAME = "void_worker";
|
|
4083
|
+
const DEFAULT_WORKER_OUT_DIR = "dist/ssr";
|
|
4084
|
+
function resolveSandboxSdkEntry() {
|
|
4085
|
+
return fileURLToPath(import.meta.resolve(SANDBOX_SDK_IMPORT));
|
|
4086
|
+
}
|
|
4087
|
+
function createCloudflareBuiltinsExternalPlugin() {
|
|
4088
|
+
return {
|
|
4089
|
+
name: "void:cloudflare-builtins-external",
|
|
4090
|
+
config(_, env) {
|
|
4091
|
+
if (env.command !== "build") return;
|
|
4092
|
+
return { build: { rollupOptions: { external: [CLOUDFLARE_BUILTIN_IMPORT] } } };
|
|
4093
|
+
}
|
|
4094
|
+
};
|
|
4095
|
+
}
|
|
4096
|
+
function createDefaultWorkerEnvironmentPlugin() {
|
|
4097
|
+
return {
|
|
4098
|
+
name: "void:default-worker-environment",
|
|
4099
|
+
config(config) {
|
|
4100
|
+
const currentEnvironment = config.environments?.[DEFAULT_WORKER_ENVIRONMENT_NAME];
|
|
4101
|
+
return { environments: { [DEFAULT_WORKER_ENVIRONMENT_NAME]: {
|
|
4102
|
+
...currentEnvironment,
|
|
4103
|
+
build: {
|
|
4104
|
+
...currentEnvironment?.build,
|
|
4105
|
+
outDir: currentEnvironment?.build?.outDir ?? DEFAULT_WORKER_OUT_DIR
|
|
4106
|
+
}
|
|
4107
|
+
} } };
|
|
4108
|
+
}
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
function createSandboxSdkResolvePlugin() {
|
|
4112
|
+
let resolved;
|
|
4113
|
+
return {
|
|
4114
|
+
name: "void:sandbox-sdk-resolve",
|
|
4115
|
+
enforce: "pre",
|
|
4116
|
+
resolveId: {
|
|
4117
|
+
filter: { id: SANDBOX_SDK_IMPORT_FILTER },
|
|
4118
|
+
handler(id) {
|
|
4119
|
+
if (id !== SANDBOX_SDK_IMPORT) return;
|
|
4120
|
+
resolved ??= resolveSandboxSdkEntry();
|
|
4121
|
+
return resolved;
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
/**
|
|
4127
|
+
* Read Void auth credentials for local dev AI proxy access.
|
|
4128
|
+
* Returns a discriminated union so callers can distinguish
|
|
4129
|
+
* "no token" from "no project" for targeted onboarding guidance.
|
|
4130
|
+
*/
|
|
4131
|
+
function readVoidDevCredentials(root) {
|
|
4132
|
+
const token = getToken(root);
|
|
4133
|
+
if (!token) return { status: "no-token" };
|
|
4134
|
+
const staging = isStagingMode(root) || void 0;
|
|
4135
|
+
const project = readProjectConfig(root);
|
|
4136
|
+
if (!project) return {
|
|
4137
|
+
status: "no-project",
|
|
4138
|
+
token,
|
|
4139
|
+
staging
|
|
4140
|
+
};
|
|
4141
|
+
return {
|
|
4142
|
+
status: "ok",
|
|
4143
|
+
token,
|
|
4144
|
+
projectId: project.projectId,
|
|
4145
|
+
staging
|
|
4146
|
+
};
|
|
4147
|
+
}
|
|
4148
|
+
const DEFAULT_API_URL = "https://api.void.cloud";
|
|
4149
|
+
const STAGING_API_URL = "https://api.staging.void.cloud";
|
|
4150
|
+
/**
|
|
4151
|
+
* Interactive AI setup prompt shown during dev server startup when the user
|
|
4152
|
+
* has a token but no linked project. Runs as fire-and-forget so it doesn't
|
|
4153
|
+
* block the server. On completion, writes project.json and restarts the server.
|
|
4154
|
+
*/
|
|
4155
|
+
async function runAiSetupPrompt(root, token, staging, server) {
|
|
4156
|
+
const { select, text, isCancel, log } = await import("./dist-Dayj3gCK.mjs").then((n) => n.p);
|
|
4157
|
+
const { PlatformClient } = await import("./client-snXOjrp1.mjs").then((n) => n.r);
|
|
4158
|
+
const { writeProjectConfig } = await import("./project-TqORyHn8.mjs").then((n) => n.n);
|
|
4159
|
+
const { inferDefaultSlug } = await import("./resolve-project-Br5BR03U.mjs").then((n) => n.i);
|
|
4160
|
+
const client = new PlatformClient(token, staging ? STAGING_API_URL : DEFAULT_API_URL);
|
|
4161
|
+
let projects;
|
|
4162
|
+
try {
|
|
4163
|
+
projects = await client.listProjects();
|
|
4164
|
+
} catch {
|
|
4165
|
+
voidWarn("Failed to fetch projects. Run `void project link` to link a project manually.");
|
|
4166
|
+
return;
|
|
4167
|
+
}
|
|
4168
|
+
const options = [{
|
|
4169
|
+
value: "__create__",
|
|
4170
|
+
label: "Create new project"
|
|
4171
|
+
}, ...projects.map((p) => ({
|
|
4172
|
+
value: p.id,
|
|
4173
|
+
label: p.slug
|
|
4174
|
+
}))];
|
|
4175
|
+
console.log();
|
|
4176
|
+
log.info("[void] AI requires a linked project. Let's set one up:");
|
|
4177
|
+
const result = await select({
|
|
4178
|
+
message: "Select a project:",
|
|
4179
|
+
options
|
|
4180
|
+
});
|
|
4181
|
+
if (isCancel(result)) return;
|
|
4182
|
+
let projectId;
|
|
4183
|
+
let slug;
|
|
4184
|
+
if (result === "__create__") {
|
|
4185
|
+
const defaultSlug = inferDefaultSlug(root);
|
|
4186
|
+
const { validateProjectSlug } = await import("./project-slug-CKam8lF9.mjs").then((n) => n.t);
|
|
4187
|
+
const slugResult = await text({
|
|
4188
|
+
message: "Choose a slug for your new Void project:",
|
|
4189
|
+
placeholder: "my-app",
|
|
4190
|
+
initialValue: defaultSlug,
|
|
4191
|
+
validate: validateProjectSlug
|
|
4192
|
+
});
|
|
4193
|
+
if (isCancel(slugResult)) return;
|
|
4194
|
+
try {
|
|
4195
|
+
const created = await client.createProject(slugResult.trim());
|
|
4196
|
+
projectId = created.id;
|
|
4197
|
+
slug = created.slug;
|
|
4198
|
+
} catch (err) {
|
|
4199
|
+
voidError(`project: Failed to create project: '${err instanceof Error ? err.message : String(err)}'.`);
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
log.success(`Project created: ${import_picocolors.default.blue(slug)}`);
|
|
4203
|
+
} else {
|
|
4204
|
+
const project = projects.find((p) => p.id === result);
|
|
4205
|
+
projectId = project.id;
|
|
4206
|
+
slug = project.slug;
|
|
4207
|
+
}
|
|
4208
|
+
writeProjectConfig(root, {
|
|
4209
|
+
projectId,
|
|
4210
|
+
slug
|
|
4211
|
+
});
|
|
4212
|
+
log.success(`Linked to ${import_picocolors.default.blue(slug)}`);
|
|
4213
|
+
await server.restart();
|
|
4214
|
+
}
|
|
4215
|
+
/**
|
|
4216
|
+
* Void plugin — the main entry point.
|
|
4217
|
+
*
|
|
4218
|
+
* Composes:
|
|
4219
|
+
* 1. Routing plugin (file-based routing from routes/ and middleware/)
|
|
4220
|
+
* 2. Migration plugin (auto-apply .sql migrations on dev startup)
|
|
4221
|
+
* 3. @cloudflare/vite-plugin (workerd runtime, HMR, builds)
|
|
4222
|
+
*
|
|
4223
|
+
* Binding inference runs synchronously here so results are available
|
|
4224
|
+
* before the Cloudflare plugin initializes.
|
|
4225
|
+
*
|
|
4226
|
+
* If the user has a `wrangler.jsonc` / `wrangler.json` in their project root,
|
|
4227
|
+
* the Cloudflare plugin reads it and provides the resolved config to our
|
|
4228
|
+
* config function. Bindings already defined there (with real IDs for
|
|
4229
|
+
* production deployment) are preserved — we only add inferred bindings
|
|
4230
|
+
* that are missing. This allows `vite build && wrangler deploy` to work
|
|
4231
|
+
* for deploying directly to Cloudflare without the Void platform.
|
|
4232
|
+
*/
|
|
4233
|
+
function voidPlugin(options) {
|
|
4234
|
+
const root = process.cwd();
|
|
4235
|
+
warnIfPackageJsonIsNotEsm(root);
|
|
4236
|
+
const devTriggerToken = isViteBuildOrPreview() ? crypto.randomUUID() : getOrCreateDevTriggerToken(root);
|
|
4237
|
+
const config = readConfig(root);
|
|
4238
|
+
const dialect = getDatabaseDialect(config);
|
|
4239
|
+
const detected = detectFramework(root);
|
|
4240
|
+
const framework = detected?.class === "a" ? detected.name : void 0;
|
|
4241
|
+
const scanDirs = config.inference?.scanDirs ?? (detected ? FRAMEWORK_SCAN_DIRS : void 0);
|
|
4242
|
+
const inferenceOptions = config.inference?.bindings ? { bindings: config.inference.bindings } : void 0;
|
|
4243
|
+
const inferredBindings = inferProjectBindings(root, inferenceOptions, scanDirs);
|
|
4244
|
+
const bindings = isSandboxEnabled(config) ? {
|
|
4245
|
+
...inferredBindings,
|
|
4246
|
+
needsSandbox: true
|
|
4247
|
+
} : inferredBindings;
|
|
4248
|
+
const sandboxConfig = bindings.needsSandbox ? resolveSandboxConfig(config, root) : void 0;
|
|
4249
|
+
const authConfigPath = findVoidAuthConfig(root);
|
|
4250
|
+
const authEnabled = bindings.needsAuth || authConfigPath !== null || config.auth !== void 0;
|
|
4251
|
+
if (detected && authEnabled) throw new Error("auth: Void-managed Better Auth is only supported in Void apps. Use Better Auth's official integration for this framework instead.");
|
|
4252
|
+
if (detected && bindings.needsSandbox) throw new Error("sandbox: Cloudflare Sandboxes are currently supported in Void apps only.");
|
|
4253
|
+
const STUB_PREFIX = "\0void-client-stub:";
|
|
4254
|
+
const SERVER_ONLY_STUBS = {
|
|
4255
|
+
"void/db": "export const db = {}; export const createDb = () => ({});",
|
|
4256
|
+
"void/kv": "export const kv = {}; export const createKV = () => ({});",
|
|
4257
|
+
"void/ai": "export const ai = {};",
|
|
4258
|
+
"void/queues": "export const queues = {};",
|
|
4259
|
+
"void/storage": "export const storage = {}; export const createStorage = () => ({});",
|
|
4260
|
+
"void/isr": "export const revalidate = async () => {};",
|
|
4261
|
+
"void/sandbox": "export const sandbox = {}; export const getSandbox = () => ({});"
|
|
4262
|
+
};
|
|
4263
|
+
const STUB_RESOLVE_FILTER = new RegExp(`^(${Object.keys(SERVER_ONLY_STUBS).join("|").replaceAll("/", "\\/")})$`);
|
|
4264
|
+
const STUB_LOAD_FILTER = new RegExp(`^\\0void-client-stub:`);
|
|
4265
|
+
const clientStubsPlugin = {
|
|
4266
|
+
name: "void:client-stubs",
|
|
4267
|
+
enforce: "pre",
|
|
4268
|
+
resolveId: {
|
|
4269
|
+
filter: { id: STUB_RESOLVE_FILTER },
|
|
4270
|
+
handler(id) {
|
|
4271
|
+
if (this.environment?.name === "client") return STUB_PREFIX + id;
|
|
4272
|
+
}
|
|
4273
|
+
},
|
|
4274
|
+
load: {
|
|
4275
|
+
filter: { id: STUB_LOAD_FILTER },
|
|
4276
|
+
handler(id) {
|
|
4277
|
+
return SERVER_ONLY_STUBS[id.slice(18)] ?? "export default {};";
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
};
|
|
4281
|
+
const queues = framework ? [] : scanQueuesSync(root);
|
|
4282
|
+
const websocketRoutes = framework ? [] : scanWebSocketRoutesSync(root);
|
|
4283
|
+
if (detected && (detected.class === "b" || detected.class === "c")) {
|
|
4284
|
+
const bindingNames = resolveBindingNames(config.inference?.bindings);
|
|
4285
|
+
const frameworkPersistRoot = join(root, ".wrangler", "state");
|
|
4286
|
+
const syncFrameworkCompat = () => {
|
|
4287
|
+
if (ensureWranglerCompatibilityDate(root, readWranglerCompat(root, config).compatibilityDate)) voidLog("Synced compatibility_date to wrangler config");
|
|
4288
|
+
};
|
|
4289
|
+
return [
|
|
4290
|
+
createCloudflareBuiltinsExternalPlugin(),
|
|
4291
|
+
{
|
|
4292
|
+
name: "void:wrangler-compat",
|
|
4293
|
+
enforce: "pre",
|
|
4294
|
+
config(_, env) {
|
|
4295
|
+
syncFrameworkCompat();
|
|
4296
|
+
if (env.command !== "serve") return;
|
|
4297
|
+
if (ensureWranglerNodejsAlsFlag(root)) voidLog("Synced nodejs_als to wrangler config");
|
|
4298
|
+
}
|
|
4299
|
+
},
|
|
4300
|
+
{
|
|
4301
|
+
name: "void:wrangler-sync",
|
|
4302
|
+
configResolved(resolvedConfig) {
|
|
4303
|
+
syncFrameworkCompat();
|
|
4304
|
+
if (resolvedConfig.command !== "serve") return;
|
|
4305
|
+
if (syncWranglerBindings(root, bindings, {
|
|
4306
|
+
bindingNames,
|
|
4307
|
+
websockets: websocketRoutes,
|
|
4308
|
+
sandbox: sandboxConfig
|
|
4309
|
+
})) voidLog("Synced bindings to wrangler.jsonc");
|
|
4310
|
+
}
|
|
4311
|
+
},
|
|
4312
|
+
{
|
|
4313
|
+
name: "void:sveltekit-hooks-inject",
|
|
4314
|
+
apply: "serve",
|
|
4315
|
+
transform(code, id) {
|
|
4316
|
+
if (detected.class !== "b") return;
|
|
4317
|
+
if (!id.includes(".svelte-kit/generated/server/internal")) return;
|
|
4318
|
+
if (!code.includes("export async function get_hooks")) return;
|
|
4319
|
+
const dispatchPath = JSON.stringify(frameworkTriggersDispatchPath(root));
|
|
4320
|
+
return code.replace("export async function get_hooks() {", `export async function get_hooks() {
|
|
4321
|
+
const { withRuntimeEnv } = await import("void/_env");
|
|
4322
|
+
const { handleDevTrigger } = await import(${dispatchPath});`).replace(/return\s*\{/, "const __origHandle = handle ?? (({ event, resolve }) => resolve(event));\n handle = ({ event, resolve }) => withRuntimeEnv(event.platform?.env, async () => {\n const __triggerResp = await handleDevTrigger(event.request, event.platform?.env, undefined);\n if (__triggerResp) return __triggerResp;\n return __origHandle({ event, resolve });\n });\n return {");
|
|
4323
|
+
}
|
|
4324
|
+
},
|
|
4325
|
+
clientStubsPlugin,
|
|
4326
|
+
envSchemaPlugin(root),
|
|
4327
|
+
envClientGuardPlugin(root),
|
|
4328
|
+
envClientFoldPlugin(),
|
|
4329
|
+
drizzlePlugin(root, false, dialect, { forceVirtualDb: authEnabled }),
|
|
4330
|
+
migrationPlugin(root, dialect, "local", frameworkPersistRoot, { enabled: false }),
|
|
4331
|
+
devTriggersHintPlugin(root, {
|
|
4332
|
+
framework: true,
|
|
4333
|
+
devTriggerToken
|
|
4334
|
+
}),
|
|
4335
|
+
frameworkTriggersPlugin(root, { devTriggerToken }),
|
|
4336
|
+
...detected.name === "nuxt" ? [nitroDevTriggerInjectPlugin(root, { serverDir: "server" })] : [],
|
|
4337
|
+
...detected.name === "analog" ? [analogDevTriggerInjectPlugin(root)] : [],
|
|
4338
|
+
...detected.name === "astro" ? [astroDevTriggerInjectPlugin(root)] : []
|
|
4339
|
+
];
|
|
4340
|
+
}
|
|
4341
|
+
const remoteMode = resolveRemoteMode(config.remote);
|
|
4342
|
+
const workerConfig = {};
|
|
4343
|
+
let viteCommand = "build";
|
|
4344
|
+
const wranglerCompat = isNodeTarget(config.target) ? void 0 : readWranglerCompat(root, config);
|
|
4345
|
+
if (wranglerCompat) {
|
|
4346
|
+
workerConfig.compatibility_date = wranglerCompat.compatibilityDate;
|
|
4347
|
+
if (wranglerCompat.compatibilityFlags) workerConfig.compatibility_flags = wranglerCompat.compatibilityFlags;
|
|
4348
|
+
}
|
|
4349
|
+
const inspectorPortEnv = process.env.VOID_INSPECTOR_PORT;
|
|
4350
|
+
const inspectorPort = inspectorPortEnv === "false" ? false : typeof inspectorPortEnv === "string" && inspectorPortEnv.length > 0 ? Number.parseInt(inspectorPortEnv, 10) : void 0;
|
|
4351
|
+
const inspectorPortOption = inspectorPort === false ? { inspectorPort: false } : typeof inspectorPort === "number" ? { inspectorPort } : {};
|
|
4352
|
+
const outDir = join(root, ".void");
|
|
4353
|
+
mkdirSync(outDir, { recursive: true });
|
|
4354
|
+
function mergeBindings(resolved) {
|
|
4355
|
+
const result = { ...workerConfig };
|
|
4356
|
+
const d1 = [];
|
|
4357
|
+
if ((bindings.needsD1 || authEnabled && dialect === "sqlite") && !hasBinding(resolved.d1_databases, "DB")) d1.push({
|
|
4358
|
+
binding: "DB",
|
|
4359
|
+
database_name: "default",
|
|
4360
|
+
database_id: "local"
|
|
4361
|
+
});
|
|
4362
|
+
if (d1.length > 0) result.d1_databases = [...resolved.d1_databases ?? [], ...d1];
|
|
4363
|
+
const kv = [];
|
|
4364
|
+
if (bindings.needsKV && !hasBinding(resolved.kv_namespaces, "KV")) kv.push({
|
|
4365
|
+
binding: "KV",
|
|
4366
|
+
id: "local"
|
|
4367
|
+
});
|
|
4368
|
+
if (kv.length > 0) result.kv_namespaces = [...resolved.kv_namespaces ?? [], ...kv];
|
|
4369
|
+
if (bindings.needsR2 && !hasBinding(resolved.r2_buckets, "STORAGE")) result.r2_buckets = [...resolved.r2_buckets ?? [], {
|
|
4370
|
+
binding: "STORAGE",
|
|
4371
|
+
bucket_name: "default"
|
|
4372
|
+
}];
|
|
4373
|
+
if (queues.length > 0) result.queues = {
|
|
4374
|
+
producers: queues.map((q) => ({
|
|
4375
|
+
queue: q.name,
|
|
4376
|
+
binding: q.bindingName
|
|
4377
|
+
})),
|
|
4378
|
+
consumers: queues.map((q) => ({
|
|
4379
|
+
queue: q.name,
|
|
4380
|
+
...q.maxBatchSize != null && { max_batch_size: q.maxBatchSize },
|
|
4381
|
+
...q.maxBatchTimeout != null && { max_batch_timeout: q.maxBatchTimeout },
|
|
4382
|
+
...q.maxRetries != null && { max_retries: q.maxRetries },
|
|
4383
|
+
...q.retryDelay != null && { retry_delay: q.retryDelay }
|
|
4384
|
+
}))
|
|
4385
|
+
};
|
|
4386
|
+
if (websocketRoutes.length > 0) {
|
|
4387
|
+
const existingDurableObjectBindings = resolved.durable_objects?.bindings ?? [];
|
|
4388
|
+
const missingDurableObjectBindings = websocketRoutes.filter((route) => !existingDurableObjectBindings.some((binding) => binding.name === route.bindingName)).map((route) => ({
|
|
4389
|
+
name: route.bindingName,
|
|
4390
|
+
class_name: route.className
|
|
4391
|
+
}));
|
|
4392
|
+
if (missingDurableObjectBindings.length > 0) result.durable_objects = { bindings: [...existingDurableObjectBindings, ...missingDurableObjectBindings] };
|
|
4393
|
+
if (!Array.isArray(resolved.migrations)) result.migrations = [{
|
|
4394
|
+
tag: "void-ws-v1",
|
|
4395
|
+
new_classes: websocketRoutes.map((route) => route.className)
|
|
4396
|
+
}];
|
|
4397
|
+
}
|
|
4398
|
+
if (bindings.needsSandbox && sandboxConfig) {
|
|
4399
|
+
const existingDurableObjectBindings = (result.durable_objects ?? resolved.durable_objects)?.bindings ?? [];
|
|
4400
|
+
if (!existingDurableObjectBindings.some((binding) => binding.name === sandboxConfig.binding)) result.durable_objects = { bindings: [...existingDurableObjectBindings, {
|
|
4401
|
+
name: sandboxConfig.binding,
|
|
4402
|
+
class_name: sandboxConfig.className
|
|
4403
|
+
}] };
|
|
4404
|
+
const existingContainers = result.containers ?? resolved.containers ?? [];
|
|
4405
|
+
if (!existingContainers.some((container) => container.class_name === sandboxConfig.className)) result.containers = [...existingContainers, {
|
|
4406
|
+
name: sandboxConfig.containerName,
|
|
4407
|
+
class_name: sandboxConfig.className,
|
|
4408
|
+
image: sandboxConfig.image,
|
|
4409
|
+
...sandboxConfig.imageBuildContext && { image_build_context: sandboxConfig.imageBuildContext },
|
|
4410
|
+
...sandboxConfig.instanceType && { instance_type: sandboxConfig.instanceType },
|
|
4411
|
+
...sandboxConfig.maxInstances != null && { max_instances: sandboxConfig.maxInstances }
|
|
4412
|
+
}];
|
|
4413
|
+
const currentMigrations = result.migrations ?? resolved.migrations;
|
|
4414
|
+
const existingMigrations = Array.isArray(currentMigrations) ? currentMigrations : [];
|
|
4415
|
+
if (!existingMigrations.some((migration) => migration.tag === "void-sandbox-v1" || migration.new_sqlite_classes?.includes(sandboxConfig.className))) result.migrations = [...existingMigrations, {
|
|
4416
|
+
tag: SANDBOX_MIGRATION_TAG,
|
|
4417
|
+
new_sqlite_classes: [sandboxConfig.className]
|
|
4418
|
+
}];
|
|
4419
|
+
}
|
|
4420
|
+
if (viteCommand === "serve" && bindings.needsAI) {
|
|
4421
|
+
const credentials = readVoidDevCredentials(root);
|
|
4422
|
+
if (credentials.status === "ok") {
|
|
4423
|
+
const vars = result.vars ?? {};
|
|
4424
|
+
vars.__VOID_TOKEN = credentials.token;
|
|
4425
|
+
vars.__VOID_PROJECT_ID = credentials.projectId;
|
|
4426
|
+
if (credentials.staging) vars.__VOID_STAGING = "1";
|
|
4427
|
+
result.vars = vars;
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
if (viteCommand === "serve" && remoteMode && (bindings.needsD1 || bindings.needsKV || bindings.needsR2)) {
|
|
4431
|
+
const credentials = readVoidDevCredentials(root);
|
|
4432
|
+
if (credentials.status === "ok") {
|
|
4433
|
+
const vars = result.vars ?? {};
|
|
4434
|
+
vars.__VOID_PROXY_URL = credentials.staging ? STAGING_PROXY_URL : DEFAULT_PROXY_URL;
|
|
4435
|
+
vars.__VOID_TOKEN = credentials.token;
|
|
4436
|
+
vars.__VOID_PROJECT_ID = credentials.projectId;
|
|
4437
|
+
vars.__VOID_REMOTE = "1";
|
|
4438
|
+
result.vars = vars;
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
return result;
|
|
4442
|
+
}
|
|
4443
|
+
function applySchemaDefaultsToWorkerVars(defaults) {
|
|
4444
|
+
const vars = workerConfig.vars ?? {};
|
|
4445
|
+
for (const [key, value] of Object.entries(defaults)) if (vars[key] === void 0) vars[key] = value;
|
|
4446
|
+
workerConfig.vars = vars;
|
|
4447
|
+
}
|
|
4448
|
+
const envPlugin = {
|
|
4449
|
+
name: "void:env",
|
|
4450
|
+
config(userConfig, { mode, command }) {
|
|
4451
|
+
viteCommand = command;
|
|
4452
|
+
const ignored = ["**/.void/**"];
|
|
4453
|
+
const { sanitized: env, stripped } = stripInternalEnvKeys(filterLoadedEnv(loadEnv(mode, typeof userConfig.envDir === "string" && userConfig.envDir.length > 0 ? join(root, userConfig.envDir) : root, "")));
|
|
4454
|
+
if (stripped.length > 0) voidWarn(`Ignoring internal env keys from .env files: ${stripped.join(", ")}. These are reserved for Void runtime use.`);
|
|
4455
|
+
workerConfig.vars = {
|
|
4456
|
+
...workerConfig.vars,
|
|
4457
|
+
...env
|
|
4458
|
+
};
|
|
4459
|
+
const assets = workerConfig.assets;
|
|
4460
|
+
if (command === "serve" && assets) delete assets.run_worker_first;
|
|
4461
|
+
const watchConfig = { server: { watch: { ignored } } };
|
|
4462
|
+
if (!isNodeTarget(config.target) && config.output !== "static") {
|
|
4463
|
+
const envNames = framework === "vinext-app" ? ["rsc", "ssr"] : ["ssr"];
|
|
4464
|
+
return {
|
|
4465
|
+
...watchConfig,
|
|
4466
|
+
environments: Object.fromEntries(envNames.map((n) => [n, { build: { minify: true } }]))
|
|
4467
|
+
};
|
|
4468
|
+
}
|
|
4469
|
+
return watchConfig;
|
|
4470
|
+
},
|
|
4471
|
+
configureServer(server) {
|
|
4472
|
+
const afterReady = (fn) => {
|
|
4473
|
+
if (server.httpServer) server.httpServer.once("listening", () => setTimeout(fn, 100));
|
|
4474
|
+
else fn();
|
|
4475
|
+
};
|
|
4476
|
+
if (remoteMode && (bindings.needsD1 || bindings.needsKV || bindings.needsR2)) {
|
|
4477
|
+
const credentials = readVoidDevCredentials(root);
|
|
4478
|
+
if (credentials.status === "ok") {
|
|
4479
|
+
const lines = ["\x1B[36mRemote bindings active\x1B[39m"];
|
|
4480
|
+
if (bindings.needsD1) lines.push(" DB \x1B[2m→ remote D1\x1B[22m");
|
|
4481
|
+
if (bindings.needsKV) lines.push(" KV \x1B[2m→ remote KV\x1B[22m");
|
|
4482
|
+
if (bindings.needsR2) lines.push(" STORAGE \x1B[2m→ remote R2\x1B[22m");
|
|
4483
|
+
afterReady(() => {
|
|
4484
|
+
console.log();
|
|
4485
|
+
voidLog(lines.join("\n "));
|
|
4486
|
+
});
|
|
4487
|
+
} else if (credentials.status === "no-token") afterReady(() => {
|
|
4488
|
+
console.log();
|
|
4489
|
+
voidWarn("Remote mode requires authentication. Run `void auth login`, then restart.");
|
|
4490
|
+
voidWarn("Falling back to local bindings.");
|
|
4491
|
+
});
|
|
4492
|
+
else afterReady(() => {
|
|
4493
|
+
console.log();
|
|
4494
|
+
voidWarn("Remote mode requires a linked project. Run `void project link` first.");
|
|
4495
|
+
voidWarn("Falling back to local bindings.");
|
|
4496
|
+
});
|
|
4497
|
+
}
|
|
4498
|
+
if (bindings.needsAI) {
|
|
4499
|
+
const credentials = readVoidDevCredentials(root);
|
|
4500
|
+
if (credentials.status === "no-token") afterReady(() => {
|
|
4501
|
+
console.log();
|
|
4502
|
+
voidWarn("AI requires authentication. Run `void auth login`, then restart the dev server.");
|
|
4503
|
+
});
|
|
4504
|
+
else if (credentials.status === "no-project") if (process.stdin.isTTY) afterReady(() => {
|
|
4505
|
+
console.log();
|
|
4506
|
+
runAiSetupPrompt(root, credentials.token, credentials.staging, server);
|
|
4507
|
+
});
|
|
4508
|
+
else afterReady(() => {
|
|
4509
|
+
console.log();
|
|
4510
|
+
voidWarn("AI requires a linked project. Run `void project link` to set one up.");
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
};
|
|
4515
|
+
const autoDetectBindings = !inferenceOptions;
|
|
4516
|
+
const autoDetectAuth = !authEnabled;
|
|
4517
|
+
const bindingWatchPlugin = {
|
|
4518
|
+
name: "void:binding-watch",
|
|
4519
|
+
apply: "serve",
|
|
4520
|
+
configureServer(server) {
|
|
4521
|
+
if (!autoDetectBindings && !autoDetectAuth) return;
|
|
4522
|
+
const watchDirs = [...new Set([...scanDirs ?? [
|
|
4523
|
+
"routes",
|
|
4524
|
+
"middleware",
|
|
4525
|
+
"queues",
|
|
4526
|
+
"pages",
|
|
4527
|
+
"crons"
|
|
4528
|
+
], "src"])].map((d) => join(root, d));
|
|
4529
|
+
let hintShown = false;
|
|
4530
|
+
const check = (path) => {
|
|
4531
|
+
if (hintShown) return;
|
|
4532
|
+
if (!watchDirs.some((d) => path.startsWith(d))) return;
|
|
4533
|
+
try {
|
|
4534
|
+
const fileBindings = inferBindingsFromSource(readFileSync(path, "utf-8"), path.split("/").pop());
|
|
4535
|
+
const newBindings = [];
|
|
4536
|
+
if (autoDetectBindings) {
|
|
4537
|
+
if (fileBindings.needsD1 && !bindings.needsD1) newBindings.push("D1 (void/db)");
|
|
4538
|
+
if (fileBindings.needsKV && !bindings.needsKV) newBindings.push("KV (void/kv)");
|
|
4539
|
+
if (fileBindings.needsR2 && !bindings.needsR2) newBindings.push("R2 (void/storage)");
|
|
4540
|
+
if (fileBindings.needsAI && !bindings.needsAI) newBindings.push("AI (void/ai)");
|
|
4541
|
+
if (fileBindings.needsSandbox && !bindings.needsSandbox) newBindings.push("Sandbox (void/sandbox)");
|
|
4542
|
+
}
|
|
4543
|
+
if (autoDetectAuth && fileBindings.needsAuth) newBindings.push("Auth (void/auth)");
|
|
4544
|
+
if (newBindings.length > 0) {
|
|
4545
|
+
hintShown = true;
|
|
4546
|
+
const detected = newBindings.join(", ");
|
|
4547
|
+
setTimeout(() => {
|
|
4548
|
+
voidLog(`New ${detected} binding detected. Restart to apply.`);
|
|
4549
|
+
}, 300);
|
|
4550
|
+
server.hot.send({
|
|
4551
|
+
type: "custom",
|
|
4552
|
+
event: "void:new-binding",
|
|
4553
|
+
data: { message: `New ${detected} binding detected.` }
|
|
4554
|
+
});
|
|
4555
|
+
}
|
|
4556
|
+
} catch {}
|
|
4557
|
+
};
|
|
4558
|
+
const attach = () => {
|
|
4559
|
+
server.watcher.on("add", check);
|
|
4560
|
+
server.watcher.on("change", check);
|
|
4561
|
+
};
|
|
4562
|
+
if (server.httpServer) server.httpServer.once("listening", attach);
|
|
4563
|
+
else attach();
|
|
4564
|
+
}
|
|
4565
|
+
};
|
|
4566
|
+
if (framework) {
|
|
4567
|
+
workerConfig.main = getFrameworkEntry(framework, root, devTriggerToken);
|
|
4568
|
+
const existingFlags = workerConfig.compatibility_flags ?? [];
|
|
4569
|
+
if (!existingFlags.includes("nodejs_compat")) workerConfig.compatibility_flags = [...existingFlags, "nodejs_compat"];
|
|
4570
|
+
const viteEnvironment = framework === "vinext-app" ? {
|
|
4571
|
+
name: "rsc",
|
|
4572
|
+
childEnvironments: ["ssr"]
|
|
4573
|
+
} : { name: "ssr" };
|
|
4574
|
+
const redirectDbTypePaths = framework !== "vinext-app" && framework !== "vinext-pages";
|
|
4575
|
+
const vinextCompat = framework === "vinext-app" || framework === "vinext-pages" ? {
|
|
4576
|
+
name: "void:vinext-compat",
|
|
4577
|
+
enforce: "pre",
|
|
4578
|
+
config(userConfig) {
|
|
4579
|
+
const plugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
|
|
4580
|
+
for (const p of plugins) if (p && typeof p === "object" && !Array.isArray(p) && "name" in p && p.name === "vite-tsconfig-paths") {
|
|
4581
|
+
const plugin = p;
|
|
4582
|
+
for (const key of Object.keys(plugin)) if (key !== "name" && typeof plugin[key] === "function") plugin[key] = () => {};
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
} : void 0;
|
|
4586
|
+
return [
|
|
4587
|
+
createCloudflareBuiltinsExternalPlugin(),
|
|
4588
|
+
restartPlugin(),
|
|
4589
|
+
bindingWatchPlugin,
|
|
4590
|
+
envPlugin,
|
|
4591
|
+
envSchemaPlugin(root, { onDefaults: applySchemaDefaultsToWorkerVars }),
|
|
4592
|
+
envClientGuardPlugin(root),
|
|
4593
|
+
envClientFoldPlugin(),
|
|
4594
|
+
clientStubsPlugin,
|
|
4595
|
+
drizzlePlugin(root, false, getDatabaseDialect(config), {
|
|
4596
|
+
forceVirtualDb: authEnabled,
|
|
4597
|
+
redirectDbTypePaths
|
|
4598
|
+
}),
|
|
4599
|
+
migrationPlugin(root, getDatabaseDialect(config)),
|
|
4600
|
+
...vinextCompat ? [vinextCompat] : [],
|
|
4601
|
+
frameworkTriggersPlugin(root, { devTriggerToken }),
|
|
4602
|
+
devTriggersHintPlugin(root, {
|
|
4603
|
+
framework: true,
|
|
4604
|
+
devTriggerToken
|
|
4605
|
+
}),
|
|
4606
|
+
...cloudflare({
|
|
4607
|
+
...inspectorPortOption,
|
|
4608
|
+
persistState: { path: options?.persistTo ?? outDir },
|
|
4609
|
+
auxiliaryWorkers: options?.auxiliaryWorkers,
|
|
4610
|
+
viteEnvironment,
|
|
4611
|
+
config: mergeBindings
|
|
4612
|
+
})
|
|
4613
|
+
];
|
|
4614
|
+
}
|
|
4615
|
+
if (isNodeTarget(config.target)) {
|
|
4616
|
+
if (authEnabled) throw new Error("auth: Void-managed Better Auth is only supported in Cloudflare Void apps. Use Better Auth directly for this target instead.");
|
|
4617
|
+
if (config.inference?.bindings) voidWarn("\"inference.bindings\" in void.json is ignored with target: \"" + config.target + "\"");
|
|
4618
|
+
if (config.worker) voidWarn("\"worker\" in void.json is ignored with target: \"" + config.target + "\"");
|
|
4619
|
+
if (config.remote) voidWarn("\"remote\" in void.json is ignored with target: \"" + config.target + "\"");
|
|
4620
|
+
if (bindings.needsSandbox) throw new Error(`sandbox: Cloudflare Sandboxes require target: "cloudflare". They are not available with target: '${config.target}'.`);
|
|
4621
|
+
if (config.routing?.revalidate) voidWarn("\"routing.revalidate\" in void.json is ignored with target: \"" + config.target + "\" (edge caching is CF-only)");
|
|
4622
|
+
return [
|
|
4623
|
+
restartPlugin(),
|
|
4624
|
+
envPlugin,
|
|
4625
|
+
envSchemaPlugin(root),
|
|
4626
|
+
routingPlugin(root, {
|
|
4627
|
+
isAuthEnabled: () => false,
|
|
4628
|
+
devTriggerToken,
|
|
4629
|
+
headConfig: config.head,
|
|
4630
|
+
output: config.output,
|
|
4631
|
+
prerenderPaths: config.routing?.prerender,
|
|
4632
|
+
target: config.target,
|
|
4633
|
+
routing: config.routing,
|
|
4634
|
+
hasSandbox: false
|
|
4635
|
+
}),
|
|
4636
|
+
prerenderPlugin(root, config),
|
|
4637
|
+
...nodeTargetPlugin(config.target)
|
|
4638
|
+
];
|
|
4639
|
+
}
|
|
4640
|
+
const existingFlags = workerConfig.compatibility_flags ?? [];
|
|
4641
|
+
if ((dialect === "postgresql" || bindings.needsSandbox) && !existingFlags.includes("nodejs_compat")) workerConfig.compatibility_flags = [...existingFlags.filter((f) => f !== "nodejs_als"), "nodejs_compat"];
|
|
4642
|
+
else if (!existingFlags.includes("nodejs_als") && !existingFlags.includes("nodejs_compat")) workerConfig.compatibility_flags = [...existingFlags, "nodejs_als"];
|
|
4643
|
+
const jobs = scanJobsSync(root);
|
|
4644
|
+
const ssrEntry = validateSsrEntry(root);
|
|
4645
|
+
const hasPages = existsSync(join(root, "pages"));
|
|
4646
|
+
workerConfig.main = join(root, ".void", "entry.ts");
|
|
4647
|
+
if (jobs.length > 0) workerConfig.triggers = { crons: jobs.flatMap((job) => job.crons) };
|
|
4648
|
+
if (ssrEntry || hasPages) workerConfig.assets = {
|
|
4649
|
+
binding: "ASSETS",
|
|
4650
|
+
run_worker_first: ["/**"]
|
|
4651
|
+
};
|
|
4652
|
+
return [
|
|
4653
|
+
createCloudflareBuiltinsExternalPlugin(),
|
|
4654
|
+
createDefaultWorkerEnvironmentPlugin(),
|
|
4655
|
+
restartPlugin(),
|
|
4656
|
+
bindingWatchPlugin,
|
|
4657
|
+
envPlugin,
|
|
4658
|
+
envSchemaPlugin(root, { onDefaults: applySchemaDefaultsToWorkerVars }),
|
|
4659
|
+
envClientGuardPlugin(root),
|
|
4660
|
+
envClientFoldPlugin(),
|
|
4661
|
+
clientStubsPlugin,
|
|
4662
|
+
...bindings.needsSandbox ? [createSandboxSdkResolvePlugin()] : [],
|
|
4663
|
+
routingPlugin(root, {
|
|
4664
|
+
isAuthEnabled: () => authEnabled,
|
|
4665
|
+
devTriggerToken,
|
|
4666
|
+
headConfig: config.head,
|
|
4667
|
+
output: config.output,
|
|
4668
|
+
prerenderPaths: config.routing?.prerender,
|
|
4669
|
+
remoteMode,
|
|
4670
|
+
routing: config.routing,
|
|
4671
|
+
hasSandbox: bindings.needsSandbox,
|
|
4672
|
+
sandboxBindingName: sandboxConfig?.binding,
|
|
4673
|
+
sandboxClassName: sandboxConfig?.className
|
|
4674
|
+
}),
|
|
4675
|
+
drizzlePlugin(root, true, dialect, { forceVirtualDb: authEnabled }),
|
|
4676
|
+
migrationPlugin(root, dialect, "local", join(root, ".void"), {
|
|
4677
|
+
enabled: authEnabled,
|
|
4678
|
+
configPath: authConfigPath
|
|
4679
|
+
}),
|
|
4680
|
+
prerenderPlugin(root, config),
|
|
4681
|
+
devTriggersHintPlugin(root, {
|
|
4682
|
+
framework: false,
|
|
4683
|
+
devTriggerToken
|
|
4684
|
+
}),
|
|
4685
|
+
...cloudflare({
|
|
4686
|
+
...inspectorPortOption,
|
|
4687
|
+
persistState: { path: options?.persistTo ?? outDir },
|
|
4688
|
+
auxiliaryWorkers: options?.auxiliaryWorkers,
|
|
4689
|
+
viteEnvironment: { name: DEFAULT_WORKER_ENVIRONMENT_NAME },
|
|
4690
|
+
config: mergeBindings
|
|
4691
|
+
})
|
|
4692
|
+
];
|
|
4693
|
+
}
|
|
4694
|
+
//#endregion
|
|
4695
|
+
export { VoidAssetRewriteError, defer, defineHandler, defineHead, defineMiddleware, defineQueue, defineRender, defineScheduled, voidPlugin };
|