void 0.24.0 → 0.25.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.
Files changed (137) hide show
  1. package/README.md +19 -106
  2. package/dist/{account-cmd-vJhh5lrb.mjs → account-cmd-DcR1ywt8.mjs} +5 -5
  3. package/dist/{auth-CQkSDS50.mjs → auth-7wuZ5ob8.mjs} +3 -3
  4. package/dist/{auth-DX3JURcX.mjs → auth-CdgZ9uag.mjs} +1 -1
  5. package/dist/{auth-link-D75t9wO2.mjs → auth-link-Z6E5DYvM.mjs} +5 -6
  6. package/dist/{auth-router-ClfhL1J7.mjs → auth-router-BdM4avI8.mjs} +7 -7
  7. package/dist/{build-cmd-zMi1iFnS.mjs → build-cmd-CEE7D-hM.mjs} +6 -7
  8. package/dist/{cache-ZtQmyxVV.mjs → cache-TcZnBLtH.mjs} +6 -7
  9. package/dist/{cancel-deploy-Box0kkEV.mjs → cancel-deploy-CYoZmcmR.mjs} +4 -6
  10. package/dist/{cf-access-BGe5sWXF.mjs → cf-access-AHKBzmuu.mjs} +1 -1
  11. package/dist/cf-build-output-ChY6-XWP.mjs +2 -0
  12. package/dist/cli/cli.mjs +45 -45
  13. package/dist/cli/cloudflare-operation-process.mjs +2 -2
  14. package/dist/cli/env-schema-probe.mjs +2 -2
  15. package/dist/client-B4Me-9HE.mjs +2 -0
  16. package/dist/{client-DKAsFA4X.mjs → client-RsPo8wsP.mjs} +103 -25
  17. package/dist/{cloudflare-cmd-CmY8dGkd.mjs → cloudflare-cmd-bhwCxAN6.mjs} +2 -2
  18. package/dist/{cloudflare-connect-IiR3ZieC.mjs → cloudflare-connect-CuhRgk3k.mjs} +2 -2
  19. package/dist/{cloudflare-operations-BCdXSsmo.mjs → cloudflare-operations-BhbK6yTq.mjs} +1 -1
  20. package/dist/{cloudflare-operations-DzH-A966.mjs → cloudflare-operations-CjaV4lZi.mjs} +2 -2
  21. package/dist/{connect-B-usTnlF.mjs → connect-DCvUmPXY.mjs} +12 -9
  22. package/dist/{create-project-FWbf5Olo.mjs → create-project-C734I-Mu.mjs} +1 -1
  23. package/dist/{create-project-CYOOAYcn.mjs → create-project-eAPgYq35.mjs} +6 -6
  24. package/dist/{db-Ck2eZ2iB.mjs → db-C07GEdKF.mjs} +25 -28
  25. package/dist/{delete-sK56qtzZ.mjs → delete-pKMVsrpJ.mjs} +5 -6
  26. package/dist/deploy-BDu1vhSH.mjs +2 -0
  27. package/dist/{deploy-gCVaiJbS.mjs → deploy-DfsAe-T0.mjs} +9212 -8902
  28. package/dist/{dev-inbox-P0u4tM8Y.mjs → dev-inbox-D64ZKUod.mjs} +5 -6
  29. package/dist/{domain-DFfQE8jg.mjs → domain-D3uA9ize.mjs} +8 -8
  30. package/dist/{email-CdnhUHvJ.mjs → email-z3o2cwjH.mjs} +7 -7
  31. package/dist/{env-DmGdHi5i.mjs → env-3Q8-_2IH.mjs} +6 -8
  32. package/dist/{env-validation-zZOCcQVZ.mjs → env-validation-cZBvGWaP.mjs} +1 -1
  33. package/dist/gen-ById9r2j.mjs +2 -0
  34. package/dist/{gen-CshAkukR.mjs → gen-NjwoG9FK.mjs} +2 -4
  35. package/dist/{github-cmd-DftuEvIt.mjs → github-cmd-DquUq-b1.mjs} +7 -16
  36. package/dist/help-DPGBwomV.mjs +2 -0
  37. package/dist/{help-Dotbf6iz.mjs → help-DRZXC4fj.mjs} +3 -3
  38. package/dist/index.d.mts +18 -14
  39. package/dist/index.mjs +2166 -2096
  40. package/dist/{info-B97bTX9N.mjs → info-BmA3_IFZ.mjs} +1 -3
  41. package/dist/{init-BTQEZZGJ.mjs → init-DT7yuuyl.mjs} +16 -18
  42. package/dist/{link-B46sYH0K.mjs → link-CXOlqh2F.mjs} +6 -7
  43. package/dist/{list-BnPrIsU_.mjs → list-eEWVJEPn.mjs} +8 -9
  44. package/dist/{local-d1-2CMnpuW_.mjs → local-d1-Df4XpwhW.mjs} +1 -1
  45. package/dist/login-C98-sw79.mjs +2 -0
  46. package/dist/{login-BuiMyUrx.mjs → login-pWQHYVu2.mjs} +9 -7
  47. package/dist/{logs-CZG9d-ic.mjs → logs-DnxVX7TR.mjs} +7 -8
  48. package/dist/migrate-CEu2X5DB.mjs +2 -0
  49. package/dist/{migrate-q4_njtaW.mjs → migrate-CV1on-yJ.mjs} +2 -4
  50. package/dist/{operator-args-B3BTmSoi.mjs → operator-args-CAVcmwUt.mjs} +1 -1
  51. package/dist/{operator-auth-7erYc6Lt.mjs → operator-auth-CkmTE3rt.mjs} +2 -2
  52. package/dist/{operator-client-CpHjTd0t.mjs → operator-client-LmwjxT6q.mjs} +2 -2
  53. package/dist/{operator-cmd-DGqPJUD3.mjs → operator-cmd-d-IEKgNk.mjs} +6 -6
  54. package/dist/{output-CbM85T4k.mjs → output-BBsUwdyT.mjs} +5 -5
  55. package/dist/pages/client.d.mts +3 -0
  56. package/dist/pages/client.mjs +120 -29
  57. package/dist/{platform-args-3k8X9-H_.mjs → platform-args-Bzf8JPc2.mjs} +1 -1
  58. package/dist/platform-args-Cq6EjQro.mjs +2 -0
  59. package/dist/{platform-auth-config-DDrxUy4m.mjs → platform-auth-config-0V0lA3pF.mjs} +7 -7
  60. package/dist/{platform-auth-protection-BLadnnct.mjs → platform-auth-protection-DFeJ-JkK.mjs} +8 -8
  61. package/dist/{platform-auth-recovery-CmCzX2Fl.mjs → platform-auth-recovery-ICy4muq6.mjs} +7 -7
  62. package/dist/{platform-cmd-pMTRhvYs.mjs → platform-cmd-BPySFOew.mjs} +1 -1
  63. package/dist/{platform-cmd-DMDEc9uS.mjs → platform-cmd-D7vSWMvV.mjs} +37 -27
  64. package/dist/platform-discovery-DDDUvnqp.mjs +27 -0
  65. package/dist/{platform-domain-Di6EYrQl.mjs → platform-domain-CUdY064e.mjs} +4 -4
  66. package/dist/{platform-lifecycle-BFdSi9Qt.mjs → platform-lifecycle-BaUdTRPU.mjs} +1 -1
  67. package/dist/{platform-lifecycle-BBoNzojY.mjs → platform-lifecycle-Dm09JNkn.mjs} +3901 -3651
  68. package/dist/{platform-management-CWOQOWXm.mjs → platform-management-BSEc_ZeG.mjs} +47 -10
  69. package/dist/platform-management-Bp6aoH2C.mjs +2 -0
  70. package/dist/{platform-plans-config-Vl9BP3H4.mjs → platform-plans-config-B0pOVQyW.mjs} +3 -3
  71. package/dist/{platform-recovery-BJnQJEtQ.mjs → platform-recovery-WOZ95Uze.mjs} +3 -3
  72. package/dist/{prepare-CBLT3CHH.mjs → prepare-39EvOhI6.mjs} +1 -3
  73. package/dist/{project-DKG5Ra8p.mjs → project-BkbfDs0V.mjs} +1 -1
  74. package/dist/{project-DsblYifa.mjs → project-IOjBU8R6.mjs} +1 -1
  75. package/dist/{project-cmd-DxnXstAq.mjs → project-cmd-Bs5A392E.mjs} +21 -22
  76. package/dist/{project-team-BmHcaALJ.mjs → project-team-ohAWWaew.mjs} +8 -9
  77. package/dist/{project-token-GionZlBI.mjs → project-token-CP01Jna6.mjs} +6 -9
  78. package/dist/{project-zero-trust-JLCjVQA7.mjs → project-zero-trust-BzkVgx35.mjs} +5 -6
  79. package/dist/{prompts-BNUoTiaf.mjs → prompts-6OSVA7DJ.mjs} +58 -15
  80. package/dist/{prompts-2TGE6oXx.mjs → prompts-ZAuJlyCj.mjs} +1 -1
  81. package/dist/{provision-CVku1JFU.mjs → provision-BEv0k-qz.mjs} +1 -1
  82. package/dist/{provision-CaoxO_U7.mjs → provision-Btj1jppf.mjs} +5 -7
  83. package/dist/{requests-BOjIVAeF.mjs → requests-B3PBuGlX.mjs} +5 -6
  84. package/dist/{rollback-CwR3pLj3.mjs → rollback-DDub9GsO.mjs} +9 -10
  85. package/dist/runtime/email/testing.mjs +1 -1
  86. package/dist/runtime/email.mjs +35 -17
  87. package/dist/runtime/routing.mjs +1 -1
  88. package/dist/{secret-ByWJWBUG.mjs → secret-3c72lY1U.mjs} +15 -16
  89. package/dist/{skills-C1PlclPt.mjs → skills-BPQUGClu.mjs} +1 -1
  90. package/dist/{subcommand-prompt-klFCfcor.mjs → subcommand-prompt-Dhom39bd.mjs} +2 -2
  91. package/dist/sveltekit.d.mts +1 -1
  92. package/dist/sveltekit.mjs +4 -9
  93. package/dist/{wrangler-7K-bW_DL.mjs → wrangler-DhUerFkW.mjs} +10 -15
  94. package/package.json +21 -21
  95. package/sandbox.Dockerfile +1 -1
  96. package/skills/void/docs/guide/ai.md +24 -86
  97. package/skills/void/docs/guide/auth.md +0 -2
  98. package/skills/void/docs/guide/database/d1.md +7 -7
  99. package/skills/void/docs/guide/deployment.md +58 -124
  100. package/skills/void/docs/guide/env-vars.md +1 -5
  101. package/skills/void/docs/guide/index.md +19 -18
  102. package/skills/void/docs/guide/kv.md +0 -2
  103. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +49 -47
  104. package/skills/void/docs/guide/pages-routing/overview.md +2 -2
  105. package/skills/void/docs/guide/platform/administration/email.md +0 -2
  106. package/skills/void/docs/guide/platform/administration/operations.md +11 -22
  107. package/skills/void/docs/guide/platform/administration/plans.md +1 -5
  108. package/skills/void/docs/guide/platform/administration/zero-trust.md +1 -4
  109. package/skills/void/docs/guide/platform/development/schema-ci.md +7 -1
  110. package/skills/void/docs/guide/platform/installation/maintenance.md +30 -10
  111. package/skills/void/docs/guide/platform/installation/prerequisites.md +79 -7
  112. package/skills/void/docs/guide/quickstart.md +37 -80
  113. package/skills/void/docs/guide/sandboxes.md +36 -46
  114. package/skills/void/docs/guide/self-hosted-platform.md +20 -0
  115. package/skills/void/docs/guide/websockets.md +0 -2
  116. package/skills/void/docs/integrations/frameworks/overview.md +2 -2
  117. package/skills/void/docs/integrations/frameworks/sveltekit.md +40 -25
  118. package/skills/void/docs/integrations/nodejs-bun-deno.md +7 -2
  119. package/skills/void/docs/reference/api/resources.md +27 -0
  120. package/skills/void/docs/reference/api.md +1 -1
  121. package/skills/void/docs/reference/cli/deploy.md +1 -1
  122. package/skills/void/docs/reference/cli/platform-operations.md +4 -8
  123. package/skills/void/docs/reference/cli/platform.md +5 -0
  124. package/skills/void/docs/reference/resource-inference.md +11 -8
  125. package/dist/cf-build-output-_0HNWysu.mjs +0 -2
  126. package/dist/client-B9UBI03V.mjs +0 -2
  127. package/dist/deploy-irP5q0-x.mjs +0 -2
  128. package/dist/gen-DvQTWzDU.mjs +0 -2
  129. package/dist/help-mabuIV98.mjs +0 -2
  130. package/dist/login-DIy9Sb-U.mjs +0 -2
  131. package/dist/migrate-BCiJ8w0f.mjs +0 -2
  132. package/dist/platform-args-DdnM6WIf.mjs +0 -2
  133. package/dist/platform-management-DG47jys8.mjs +0 -2
  134. package/skills/void/docs/guide/env-migration.md +0 -109
  135. package/dist/{cf-build-output-BPqOT964.mjs → cf-build-output-U8o1Gl6X.mjs} +1 -1
  136. package/dist/{env-validation-C_8tPdhs.mjs → env-validation-vBSQ8gw5.mjs} +1 -1
  137. package/dist/{platform-registry-DkPPXfp_.mjs → platform-registry-KfNEF84O.mjs} +1 -1
package/dist/index.mjs CHANGED
@@ -12,97 +12,42 @@ import { a as validateSsrEntries, n as SSR_SERVER_ENTRY_RELATIVE_PATHS, o as val
12
12
  import { r as hasEmailHandlers } from "./email--tEtMoj_.mjs";
13
13
  import { i as resolveLayoutIds } from "./scan-C7okrLyM.mjs";
14
14
  import { a as hasSandboxSqliteMigration, c as resolveSandboxConfig, i as assertSandboxNamespaceConfig, l as sandboxContainerConfig, n as DEFAULT_SANDBOX_CLASS_NAME, r as SANDBOX_MIGRATION_TAG } from "./config-C_XRIPx2.mjs";
15
- import { Ct as hasDocumentRoutes, G as resolveWorkerDirName, St as hasApiRoutes, Tt as resolveAssetRoutingPolicy, V as resolveDistDir, ct as selectNativeVersionMetadataBinding, ft as collectPages, ht as SEND_EMAIL_BINDING, lt as prerenderPages, mt as hasRevalidate, ot as generateQueueResolverSource, pt as collectRevalidate, st as CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV, ut as prerenderPagesNode } from "./deploy-gCVaiJbS.mjs";
16
- import { n as scopedAccessCredentials } from "./cf-access-TWNzXltp.mjs";
17
- import { r as ensurePlatformProxyAccess } from "./cf-access-BGe5sWXF.mjs";
18
- import { n as readDeploymentPlatform, r as readProjectConfig } from "./project-DKG5Ra8p.mjs";
19
- import { a as getApiUrl, c as getToken, o as getProxyUrl } from "./auth-CQkSDS50.mjs";
20
- import { a as restoreCloudflarePreviewPointer, r as materializeCloudflareConfig, s as stashCloudflareDeployPointer } from "./cloudflare-config-Bktvwtpf.mjs";
21
- import { D as syncWranglerDurableObjects, E as syncWranglerBindings, O as syncWranglerWebSockets, T as resolveLiveBindingConfig, _ as readExistingLiveBindingConfig, a as LIVE_MIGRATION_TAG, b as readWranglerCompat, d as ensureWranglerCompatibilityDate, f as ensureWranglerNodejsAlsFlag, g as normalizeWranglerCompatibilityFlags, h as nextDurableObjectMigrationTag, m as hasDurableObjectSqliteMigration, p as findBindingNameOwner, u as ensureWranglerCompat, w as readWranglerWebSocketTombstones } from "./wrangler-7K-bW_DL.mjs";
15
+ import { Ct as hasDocumentRoutes, H as selectNativeVersionMetadataBinding, J as collectPages, St as hasApiRoutes, Tt as resolveAssetRoutingPolicy, V as CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV, X as hasRevalidate, Y as collectRevalidate, at as resolveDistDir, lt as resolveWorkerDirName, o as prerenderPages, s as prerenderPagesNode, u as SEND_EMAIL_BINDING, xt as generateQueueResolverSource } from "./deploy-DfsAe-T0.mjs";
22
16
  import { i as voidWarn, n as voidError, r as voidLog, t as setShowTimestamp } from "./log-vqY3pqwI.mjs";
23
17
  import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-2rKda9Y6.mjs";
24
- import { n as ensureProjectTsconfig, r as writeFileIfChanged } from "./project-tsconfig-B8GeM0RM.mjs";
25
- import { n as writeLocalD1PersistState, t as resolveLocalD1Target } from "./local-d1-2CMnpuW_.mjs";
18
+ import { n as readDeploymentPlatform, r as readProjectConfig } from "./project-BkbfDs0V.mjs";
19
+ import { a as restoreCloudflarePreviewPointer, r as materializeCloudflareConfig, s as stashCloudflareDeployPointer } from "./cloudflare-config-Bktvwtpf.mjs";
20
+ import { D as syncWranglerDurableObjects, E as syncWranglerBindings, O as syncWranglerWebSockets, T as resolveLiveBindingConfig, _ as readExistingLiveBindingConfig, a as LIVE_MIGRATION_TAG, b as readWranglerCompat, d as ensureWranglerCompatibilityDate, f as ensureWranglerNodejsAlsFlag, g as normalizeWranglerCompatibilityFlags, h as nextDurableObjectMigrationTag, m as hasDurableObjectSqliteMigration, p as findBindingNameOwner, u as ensureWranglerCompat, w as readWranglerWebSocketTombstones } from "./wrangler-DhUerFkW.mjs";
21
+ import { n as writeLocalD1PersistState, t as resolveLocalD1Target } from "./local-d1-Df4XpwhW.mjs";
22
+ import { n as scopedAccessCredentials } from "./cf-access-TWNzXltp.mjs";
23
+ import { r as ensurePlatformProxyAccess } from "./cf-access-AHKBzmuu.mjs";
24
+ import { a as getApiUrl, c as getToken, o as getProxyUrl } from "./auth-7wuZ5ob8.mjs";
26
25
  import { n as parseDotenvFile } from "./dotenv-VQxupEUv.mjs";
26
+ import { n as ensureProjectTsconfig, r as writeFileIfChanged } from "./project-tsconfig-B8GeM0RM.mjs";
27
27
  import { i as generateDbDts, n as generateQueueTypes, r as DRIZZLE_OPERATORS, t as generateRouteTypes } from "./route-types-Clm4WD0B.mjs";
28
28
  import { t as discoverSchema } from "./discover-BcPPiF1o.mjs";
29
- import { _ as parseCapturedMessage, a as DEV_INBOX_ENV_VAR, c as DEV_INBOX_TOKEN_ENV_VAR, f as clearDevBuffer, g as listDevMessages, l as DEV_INBOX_TOKEN_HEADER, m as findDevMessage, n as DEV_INBOX_ACK_VALUE, o as DEV_INBOX_MAX_CAPTURE_BYTES, s as DEV_INBOX_PREFIX, t as DEV_INBOX_ACK_HEADER, u as DEV_INBOX_TOKEN_QUERY, v as pushToDevBuffer, y as registerEmailSink } from "./dev-inbox-P0u4tM8Y.mjs";
29
+ import { _ as parseCapturedMessage, a as DEV_INBOX_ENV_VAR, c as DEV_INBOX_TOKEN_ENV_VAR, f as clearDevBuffer, g as listDevMessages, l as DEV_INBOX_TOKEN_HEADER, m as findDevMessage, n as DEV_INBOX_ACK_VALUE, o as DEV_INBOX_MAX_CAPTURE_BYTES, s as DEV_INBOX_PREFIX, t as DEV_INBOX_ACK_HEADER, u as DEV_INBOX_TOKEN_QUERY, v as pushToDevBuffer, y as registerEmailSink } from "./dev-inbox-D64ZKUod.mjs";
30
30
  import { t as MagicString } from "./dist-AoCzRTJE.mjs";
31
31
  import { n as findEnvFileForPaths, r as generateEnvTypes, t as findEnvFile } from "./env-types-D5kds_OZ.mjs";
32
- import { y as loadUserEnvFile } from "./env-validation-C_8tPdhs.mjs";
32
+ import { y as loadUserEnvFile } from "./env-validation-vBSQ8gw5.mjs";
33
33
  import { a as loadLocalEnv, o as resolveRemoteMode, r as findLegacyEnvFiles, s as stripInternalEnvKeys, t as CLIENT_ENV_PREFIX$2 } from "./env-_v349JBd.mjs";
34
+ import { n as INTERNAL_BINDING_PREFIXES, t as INTERNAL_BINDING_EXACT_NAMES } from "./env-raw-Cx8ElDdj.mjs";
34
35
  import { n as completeApplicationAnalysis } from "./application-routing-B7QEjqSR.mjs";
35
36
  import { n as generateVoidAuthDrizzleSchema, t as generateVoidAuthDatabaseModule } from "./drizzle-Beb2Am5S.mjs";
36
- import { n as INTERNAL_BINDING_PREFIXES, t as INTERNAL_BINDING_EXACT_NAMES } from "./env-raw-Cx8ElDdj.mjs";
37
37
  import { n as defer } from "./defer-YsYDUoii.mjs";
38
38
  import { a as defineMiddleware, c as defineScheduled, i as defineHead, n as basicAuth, o as defineQueue, r as defineHandler, s as defineRender, t as VoidAssetRewriteError } from "./handler-DghKr6dU.mjs";
39
39
  import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
40
- import { fileURLToPath, pathToFileURL } from "node:url";
41
40
  import { cloudflare } from "@cloudflare/vite-plugin";
42
- import { appendSqliteClassMigration } from "@void/deploy-core";
41
+ import { fileURLToPath, pathToFileURL } from "node:url";
43
42
  import { normalizePath, parseSync } from "vite";
43
+ import { appendSqliteClassMigration } from "@void/deploy-core";
44
44
  import { timingSafeEqual } from "node:crypto";
45
45
  import { homedir } from "node:os";
46
46
  import { isDeepStrictEqual } from "node:util";
47
47
  import { init, parse } from "es-module-lexer";
48
48
  import { compilePattern } from "@void/edge";
49
- //#region src/cloudflare-durable-patch.ts
50
- var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
51
- function entries(value, label) {
52
- if (value === void 0) return [];
53
- if (!Array.isArray(value) || value.some((entry) => !entry || typeof entry !== "object" || Array.isArray(entry))) throw new Error(`Cloudflare ${label} configuration must be an array of objects.`);
54
- return value;
55
- }
56
- function byIdentity(rows, field, label, unique = false) {
57
- const map = /* @__PURE__ */ new Map();
58
- for (const row of rows) {
59
- const id = row[field];
60
- if (typeof id !== "string" || !id) throw new Error(`Cloudflare ${label} is missing its ${field}.`);
61
- const previous = map.get(id);
62
- if (previous && unique) throw new Error(`Cloudflare ${label} ${JSON.stringify(id)} is configured more than once.`);
63
- if (previous && !isDeepStrictEqual(previous, row)) throw new Error(`Cloudflare ${label} ${JSON.stringify(id)} has conflicting configuration.`);
64
- if (!previous) map.set(id, row);
65
- }
66
- return map;
67
- }
68
- /** Finish cumulative feature planning without repeating the Cloudflare plugin's array fallback. */
69
- function finalizeDurableObjectPatch(resolved, patch, generatedLocalBindings) {
70
- const baselineObjects = resolved.durable_objects;
71
- const pendingObjects = patch.durable_objects;
72
- const baselineBindings = byIdentity(entries(baselineObjects?.bindings, "Durable Object bindings"), "name", "Durable Object binding", true);
73
- const pendingBindings = byIdentity(entries(pendingObjects?.bindings, "Durable Object bindings"), "name", "Durable Object binding");
74
- const history = entries(resolved.migrations, "Durable Object migrations");
75
- const baselineMigrations = byIdentity(history, "tag", "Durable Object migration", true);
76
- for (const local of generatedLocalBindings) for (const map of [baselineBindings, pendingBindings]) {
77
- const existing = map.get(local.name);
78
- if (existing && (existing.class_name !== local.class_name || existing.script_name !== void 0 || existing.environment !== void 0)) throw new Error(`Cloudflare generated Durable Object binding ${JSON.stringify(local.name)} conflicts with its configured identity.`);
79
- }
80
- if (pendingObjects?.bindings !== void 0) {
81
- const additions = [];
82
- for (const [name, row] of pendingBindings) {
83
- const existing = baselineBindings.get(name);
84
- if (existing && !isDeepStrictEqual(existing, row)) throw new Error(`Cloudflare Durable Object binding ${JSON.stringify(name)} has conflicting configuration.`);
85
- if (!existing) additions.push(row);
86
- }
87
- patch.durable_objects = {
88
- ...pendingObjects,
89
- bindings: additions
90
- };
91
- }
92
- if (patch.migrations !== void 0) {
93
- const pending = byIdentity(entries(patch.migrations, "Durable Object migrations"), "tag", "Durable Object migration");
94
- const additions = [];
95
- for (const [tag, row] of pending) {
96
- const previous = baselineMigrations.get(tag);
97
- if (previous && !isDeepStrictEqual(previous, row)) throw new Error(`Cloudflare Durable Object migration ${JSON.stringify(tag)} has conflicting configuration.`);
98
- if (!previous) additions.push(row);
99
- }
100
- patch.migrations = [...history, ...additions];
101
- resolved.migrations = [];
102
- }
103
- }
104
- //#endregion
105
49
  //#region src/pages/dev-css-assets.ts
50
+ var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
106
51
  const DEV_CSS_ASSET_MODULE_ID = "virtual:void-pages-dev-assets";
107
52
  const DEV_CSS_ASSET_ENDPOINT = "/__void/dev/pages-css";
108
53
  const DEV_CSS_ASSET_GUARD_HEADER = "X-Void-Dev-Asset-Request";
@@ -501,6 +446,124 @@ export async function getVoidPagesDevCss(request, env, payload) {
501
446
  }];
502
447
  }
503
448
  //#endregion
449
+ //#region src/plugin-ai-setup.ts
450
+ /**
451
+ * Decide whether to inject a Workers AI binding into the built wrangler config.
452
+ *
453
+ * Builds use the production binding. During dev, a project explicitly saved
454
+ * with the direct Cloudflare destination uses Cloudflare's remote binding;
455
+ * managed and unsaved projects keep using the Void HTTPS proxy.
456
+ *
457
+ * `!resolvedAi` is load-bearing: mergeBindings' return is defu-merged over the
458
+ * resolved root-wrangler config and wins, so skipping when the user already
459
+ * declared an `ai` binding avoids clobbering theirs. `!resultAi` is harmless
460
+ * defense-in-depth (nothing else in mergeBindings sets `result.ai`).
461
+ */
462
+ function shouldBindWorkersAi(command, needsAI, resolvedAi, resultAi, directCloudflareDev = false) {
463
+ return needsAI && (command === "build" || command === "serve" && directCloudflareDev) && !resultAi && !resolvedAi;
464
+ }
465
+ /** Resolve the runtime Workers AI binding, preserving a root Wrangler declaration. */
466
+ function resolveWorkersAiBindingName(resolvedAi, configuredName) {
467
+ if (typeof resolvedAi === "object" && resolvedAi !== null) {
468
+ const binding = resolvedAi.binding;
469
+ if (typeof binding === "string") return binding;
470
+ }
471
+ return configuredName ?? "AI";
472
+ }
473
+ /**
474
+ * Read Void auth credentials for local dev AI proxy access.
475
+ * Returns a discriminated union so callers can distinguish
476
+ * "no token" from "no project" for targeted onboarding guidance.
477
+ */
478
+ function readVoidDevCredentials(root) {
479
+ const token = getToken(root);
480
+ if (!token) return { status: "no-token" };
481
+ const apiUrl = getApiUrl(root);
482
+ const project = readProjectConfig(root);
483
+ if (!project) return {
484
+ status: "no-project",
485
+ apiUrl,
486
+ token
487
+ };
488
+ const proxyUrl = getProxyUrl(root);
489
+ ensurePlatformProxyAccess(apiUrl, proxyUrl);
490
+ return {
491
+ status: "ok",
492
+ token,
493
+ projectId: project.projectId,
494
+ proxyUrl,
495
+ accessCredentials: scopedAccessCredentials(process.env, proxyUrl)
496
+ };
497
+ }
498
+ /**
499
+ * Interactive AI setup prompt shown during dev server startup when the user
500
+ * has a token but no linked project. Runs as fire-and-forget so it doesn't
501
+ * block the server. On completion, writes project.json and restarts the server.
502
+ */
503
+ async function runAiSetupPrompt(root, token, apiUrl, server) {
504
+ const { select, text, isCancel, log } = await import("./prompts-ZAuJlyCj.mjs");
505
+ const { PlatformClient } = await import("./client-B4Me-9HE.mjs");
506
+ const { attachSelectedPlatform, writeProjectConfig } = await import("./project-IOjBU8R6.mjs");
507
+ const { inferDefaultSlug } = await import("./resolve-project-DtbidV4y.mjs");
508
+ const client = new PlatformClient(token, apiUrl);
509
+ let projects;
510
+ try {
511
+ projects = await client.listProjects();
512
+ } catch {
513
+ voidWarn("Failed to fetch projects. Run `void project link` to link a project manually.");
514
+ return;
515
+ }
516
+ const options = [{
517
+ value: "__create__",
518
+ label: "Create new project"
519
+ }, ...projects.map((p) => ({
520
+ value: p.id,
521
+ label: p.slug
522
+ }))];
523
+ console.log();
524
+ log.info("[void] AI requires a linked project. Let's set one up:");
525
+ const result = await select({
526
+ message: "Select a project:",
527
+ options
528
+ });
529
+ if (isCancel(result)) return;
530
+ let projectId;
531
+ let slug;
532
+ if (result === "__create__") {
533
+ const { validateProjectSlug } = await import("./project-slug-DofjTFd-.mjs");
534
+ const { createProjectWithSlugRetry } = await import("./create-project-C734I-Mu.mjs");
535
+ let created;
536
+ try {
537
+ created = await createProjectWithSlugRetry(client, inferDefaultSlug(root), async (defaultSlug) => {
538
+ const slugResult = await text({
539
+ message: "Choose a slug for your new Void project:",
540
+ placeholder: "my-app",
541
+ initialValue: defaultSlug,
542
+ validate: validateProjectSlug
543
+ });
544
+ if (isCancel(slugResult) || typeof slugResult === "symbol") return;
545
+ return slugResult.trim();
546
+ });
547
+ } catch (err) {
548
+ voidError(`project: Failed to create project: '${err instanceof Error ? err.message : String(err)}'.`);
549
+ return;
550
+ }
551
+ if (!created) return;
552
+ projectId = created.id;
553
+ slug = created.slug;
554
+ } else {
555
+ const project = projects.find((p) => p.id === result);
556
+ projectId = project.id;
557
+ slug = project.slug;
558
+ }
559
+ writeProjectConfig(root, attachSelectedPlatform(root, {
560
+ projectId,
561
+ slug
562
+ }, client.apiUrl));
563
+ log.success(`Linked to ${import_picocolors.default.blue(slug)}`);
564
+ await server.restart();
565
+ }
566
+ //#endregion
504
567
  //#region src/plugin-binding-runtime.ts
505
568
  const envModuleResolved$1 = fileURLToPath(import.meta.resolve("#self/_env"));
506
569
  const kvModuleResolved = fileURLToPath(import.meta.resolve("void/kv"));
@@ -554,31 +617,206 @@ export const storage = new Proxy({}, {
554
617
  };
555
618
  }
556
619
  //#endregion
557
- //#region src/plugin-dev-triggers-hint.ts
558
- /**
559
- * Prints a one-shot hint on dev server start when the project has crons
560
- * or queues, explaining how to trigger them locally. Both default Void mode
561
- * and framework mode (SvelteKit, Nuxt, Analog, Astro, TanStack Start, React
562
- * Router, vinext) expose `/__void/scheduled` and `/__void/queue`.
563
- */
564
- function devTriggersHintPlugin(root, options) {
565
- const { devTriggerToken } = options;
620
+ //#region src/plugin-client-stubs.ts
621
+ function createClientStubsPlugin() {
622
+ const STUB_PREFIX = "\0void-client-stub:";
623
+ const SERVER_ONLY_STUBS = {
624
+ "void/db": "export const db = {}; export const createDb = () => ({});",
625
+ "void/kv": "export const kv = {}; export const createKV = () => ({});",
626
+ "void/ai": "export const ai = {};",
627
+ "void/queues": "export const queues = {};",
628
+ "void/storage": "export const storage = {}; export const createStorage = () => ({});",
629
+ "void/isr": "export const revalidate = async () => {};",
630
+ "void/sandbox": `
631
+ export class SandboxLimitError extends Error {
632
+ constructor(reason = 'concurrency', message) {
633
+ super(message ?? 'Sandbox limit reached: ' + reason);
634
+ this.name = 'SandboxLimitError';
635
+ this.reason = reason;
636
+ }
637
+ }
638
+ export class SandboxUnavailableError extends Error {
639
+ constructor(message = 'Managed Sandbox is unavailable on this Void platform.') {
640
+ super(message);
641
+ this.name = 'SandboxUnavailableError';
642
+ }
643
+ }
644
+ export async function getSandbox() {
645
+ throw new Error('void/sandbox is server-only and cannot be imported by browser code.');
646
+ }
647
+ export const sandbox = { get: getSandbox };
648
+ `,
649
+ "void/email": `
650
+ const serverOnly = (name) => () => {
651
+ throw new Error(name + ' is server-only and cannot be imported by browser code.');
652
+ };
653
+ export const sendEmail = serverOnly('sendEmail');
654
+ export const createEmail = serverOnly('createEmail');
655
+ export const defineEmail = serverOnly('defineEmail');
656
+ export const parseEmail = serverOnly('parseEmail');
657
+ export const replyEmail = serverOnly('replyEmail');
658
+ export const createMessageProxy = serverOnly('createMessageProxy');
659
+ `,
660
+ "void/email/testing": `
661
+ const serverOnly = (name) => () => {
662
+ throw new Error(name + ' is server-only and cannot be imported by browser code.');
663
+ };
664
+ export const createEmailTestHarness = serverOnly('createEmailTestHarness');
665
+ export const createInboundTestHarness = serverOnly('createInboundTestHarness');
666
+ `,
667
+ "void/live": "",
668
+ "void/durable": ""
669
+ };
670
+ const STUB_RESOLVE_FILTER = new RegExp(`^(${Object.keys(SERVER_ONLY_STUBS).join("|").replaceAll("/", "\\/")})$`);
671
+ const STUB_LOAD_FILTER = new RegExp(`^\\0void-client-stub:`);
566
672
  return {
567
- name: "void:dev-triggers-hint",
568
- apply: "serve",
569
- configureServer(server) {
570
- const paths = readProjectPaths(root);
571
- const jobs = scanJobsSync(root, paths.cronsDir);
572
- const queues = scanQueuesSync(root, paths.queuesDir);
573
- if (jobs.length === 0 && queues.length === 0) return;
574
- const afterReady = (fn) => {
575
- if (server.httpServer) server.httpServer.once("listening", () => setTimeout(fn, 100));
576
- else fn();
577
- };
578
- afterReady(() => {
579
- const address = server.httpServer?.address();
580
- const port = typeof address === "object" && address ? address.port : null;
581
- if (port == null) return;
673
+ name: "void:client-stubs",
674
+ enforce: "pre",
675
+ resolveId: {
676
+ filter: { id: STUB_RESOLVE_FILTER },
677
+ handler(id) {
678
+ if (this.environment?.name === "client") {
679
+ if (id === "void/live" || id === "void/durable") this.error(id === "void/live" ? "void/live is server-only and cannot be imported by browser code. Import void/live/client instead." : "void/durable is server-only and cannot be imported by browser code.");
680
+ return STUB_PREFIX + id;
681
+ }
682
+ }
683
+ },
684
+ load: {
685
+ filter: { id: STUB_LOAD_FILTER },
686
+ handler(id) {
687
+ return SERVER_ONLY_STUBS[id.slice(18)] ?? "export default {};";
688
+ }
689
+ }
690
+ };
691
+ }
692
+ //#endregion
693
+ //#region src/plugin-dev-config.ts
694
+ /**
695
+ * Persist the dev-trigger token at `<root>/.void/dev-trigger-token` so
696
+ * every voidPlugin() invocation against the same project reads the same
697
+ * value. This matters because `@cloudflare/vite-plugin` spawns a
698
+ * worker-environment child Vite that re-imports vite.config.ts and calls
699
+ * voidPlugin() again — without persistence the parent and child would
700
+ * generate different UUIDs, the dispatch file would embed one and the
701
+ * hint plugin would print the other, and curls would land on
702
+ * `unauthorized`.
703
+ *
704
+ * The file persists across dev sessions by design — it lives under `.void/`
705
+ * (gitignored), and `cleanupWrapper` in `void deploy` explicitly preserves
706
+ * it across its own wipe so saved curl scripts keep working between deploys.
707
+ * The token is local to the project and only honored by a worker bound to
708
+ * localhost; treat it like the rest of `.void/` — disposable scratch state,
709
+ * safe to remove manually if desired.
710
+ *
711
+ * The file is owner-only (0600). It is the one credential the dev-inbox
712
+ * routes accept, and loopback is reachable by every account on the host,
713
+ * so a default-umask 0644 file would hand any other local user the raw MIME,
714
+ * the DELETE, and the capture endpoint. A file persisted by an earlier
715
+ * release is tightened in place, never rotated. Windows has no group/other
716
+ * bits — `mode` and `chmod` only toggle write there — so it is left alone.
717
+ */
718
+ /**
719
+ * Best-effort detection of `vite build` (and `vite preview`) so we can skip
720
+ * dev-only side effects — writing the dev-trigger token and the dispatch
721
+ * file — when no dev server will run. Vite's CLI passes the subcommand as
722
+ * `process.argv[2]`; programmatic API users (and `vite` with no subcommand)
723
+ * default to `dev`, which we detect as the absence of a build-shaped first
724
+ * positional. This is intentionally permissive: when in doubt, we behave as
725
+ * if we're in dev so first-request correctness is preserved.
726
+ */
727
+ function isViteBuildOrPreview() {
728
+ const subcommand = process.argv[2];
729
+ return subcommand === "build" || subcommand === "preview";
730
+ }
731
+ const DEV_TRIGGER_TOKEN_FILE_MODE = 384;
732
+ function getOrCreateDevTriggerToken(root) {
733
+ const tokenPath = join(root, ".void", "dev-trigger-token");
734
+ const readExisting = () => {
735
+ try {
736
+ const existing = readFileSync(tokenPath, "utf-8").trim();
737
+ return existing.length > 0 ? existing : void 0;
738
+ } catch {
739
+ return;
740
+ }
741
+ };
742
+ const tightenExisting = () => {
743
+ if (process.platform === "win32") return;
744
+ try {
745
+ if ((statSync(tokenPath).mode & 63) !== 0) chmodSync(tokenPath, DEV_TRIGGER_TOKEN_FILE_MODE);
746
+ } catch {}
747
+ };
748
+ const existing = readExisting();
749
+ if (existing) {
750
+ tightenExisting();
751
+ return existing;
752
+ }
753
+ mkdirSync(join(root, ".void"), { recursive: true });
754
+ const fresh = crypto.randomUUID();
755
+ try {
756
+ const fd = openSync(tokenPath, "wx", DEV_TRIGGER_TOKEN_FILE_MODE);
757
+ try {
758
+ writeSync(fd, fresh);
759
+ } finally {
760
+ closeSync(fd);
761
+ }
762
+ return fresh;
763
+ } catch (err) {
764
+ if (err.code !== "EEXIST") throw err;
765
+ const winner = readExisting();
766
+ if (winner) return winner;
767
+ return fresh;
768
+ }
769
+ }
770
+ /**
771
+ * Warn when the project's `package.json` does not declare `"type": "module"`.
772
+ *
773
+ * Vite's default `configLoader: "bundle"` bundles `vite.config.ts`,
774
+ * externalizes deps, and then loads the bundle. When `package.json` is non-ESM
775
+ * the bundle is written as `.js` and loaded via CJS `require()`, which throws
776
+ * `ERR_REQUIRE_ASYNC_MODULE` if any externalized dep's ESM graph contains
777
+ * top-level await. `@cloudflare/vite-plugin` (which voidPlugin pulls in) has
778
+ * exactly such a TLA, so non-ESM consumers hit this on every Vite invocation.
779
+ *
780
+ * Runs once per Vite process (factory time), single sync read.
781
+ */
782
+ function warnIfPackageJsonIsNotEsm(root) {
783
+ const pkgPath = join(root, "package.json");
784
+ if (!existsSync(pkgPath)) return;
785
+ let pkg;
786
+ try {
787
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
788
+ } catch {
789
+ return;
790
+ }
791
+ if (pkg.type === "module") return;
792
+ 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`.");
793
+ }
794
+ //#endregion
795
+ //#region src/plugin-dev-triggers-hint.ts
796
+ /**
797
+ * Prints a one-shot hint on dev server start when the project has crons
798
+ * or queues, explaining how to trigger them locally. Both default Void mode
799
+ * and framework mode (SvelteKit, Nuxt, Analog, Astro, TanStack Start, React
800
+ * Router, vinext) expose `/__void/scheduled` and `/__void/queue`.
801
+ */
802
+ function devTriggersHintPlugin(root, options) {
803
+ const { devTriggerToken } = options;
804
+ return {
805
+ name: "void:dev-triggers-hint",
806
+ apply: "serve",
807
+ configureServer(server) {
808
+ const paths = readProjectPaths(root);
809
+ const jobs = scanJobsSync(root, paths.cronsDir);
810
+ const queues = scanQueuesSync(root, paths.queuesDir);
811
+ if (jobs.length === 0 && queues.length === 0) return;
812
+ const afterReady = (fn) => {
813
+ if (server.httpServer) server.httpServer.once("listening", () => setTimeout(fn, 100));
814
+ else fn();
815
+ };
816
+ afterReady(() => {
817
+ const address = server.httpServer?.address();
818
+ const port = typeof address === "object" && address ? address.port : null;
819
+ if (port == null) return;
582
820
  const baseUrl = `http://localhost:${port}`;
583
821
  const summary = [];
584
822
  if (jobs.length > 0) summary.push(`${jobs.length} cron${jobs.length === 1 ? "" : "s"}`);
@@ -1426,8 +1664,7 @@ function envSchemaPlugin(root, options = {}) {
1426
1664
  const requiredServerKeys = [];
1427
1665
  for (const key of serverKeys) {
1428
1666
  const result = schema[key]["~standard"].validate(void 0);
1429
- const resolved = result instanceof Promise ? await result : result;
1430
- if (resolved && typeof resolved === "object" && "issues" in resolved && resolved.issues) requiredServerKeys.push(key);
1667
+ if ((result instanceof Promise ? await result : result).issues) requiredServerKeys.push(key);
1431
1668
  }
1432
1669
  options.onSchema({
1433
1670
  keys,
@@ -1697,12 +1934,17 @@ function __wrapMessage(m, decisions) {
1697
1934
 
1698
1935
  async function __dispatchScheduled(request, env, ctx) {
1699
1936
  const { cron, scheduledTime } = await request.json();
1700
- const job = __jobs.find((j) => j.crons.includes(cron));
1701
- if (!job) {
1937
+ const jobs = __jobs.filter((j) => j.crons.includes(cron));
1938
+ if (jobs.length === 0) {
1702
1939
  return __json({ error: \`No cron handler matches "\${cron}"\` }, 404);
1703
1940
  }
1704
1941
  const controller = { cron, scheduledTime, noRetry() {} };
1705
- await job.handler(controller, env, ctx ?? { waitUntil() {}, passThroughOnException() {} });
1942
+ const executionContext = ctx ?? { waitUntil() {}, passThroughOnException() {} };
1943
+ const results = await Promise.allSettled(jobs.map(async (job) => {
1944
+ await job.handler(controller, env, executionContext);
1945
+ }));
1946
+ const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
1947
+ if (errors.length) throw new AggregateError(errors, "Scheduled jobs failed");
1706
1948
  return __json({ ok: true });
1707
1949
  }
1708
1950
 
@@ -1728,17 +1970,15 @@ async function __dispatchQueue(request, env) {
1728
1970
  for (const m of messages) m.retry(opts);
1729
1971
  },
1730
1972
  };
1973
+ let ok = true;
1731
1974
  try {
1732
1975
  await q.handler(batch, env);
1733
1976
  } catch (err) {
1977
+ ok = false;
1734
1978
  console.error("[void] queue handler threw:", err);
1735
- // CF queues retry the entire batch when a consumer throws, regardless
1736
- // of any ack/retry calls made before the throw — drop partial decisions
1737
- // so dev-mode response semantics match prod.
1738
- return __json({ ok: false });
1739
1979
  }
1740
1980
  return __json({
1741
- ok: true,
1981
+ ok,
1742
1982
  ...(decisions.size > 0 ? { decisions: Object.fromEntries(decisions) } : {}),
1743
1983
  });
1744
1984
  }
@@ -2035,550 +2275,183 @@ function astroDevTriggerInjectPlugin(root) {
2035
2275
  };
2036
2276
  }
2037
2277
  //#endregion
2038
- //#region src/plugin-migration.ts
2039
- function readDatabaseUrl(root) {
2040
- return loadLocalEnv(root).DATABASE_URL;
2041
- }
2042
- /**
2043
- * Vite plugin that runs local migrations on dev server startup.
2044
- * For SQLite/D1: applies migrations to the local miniflare database.
2045
- * For PostgreSQL/MySQL: connects via DATABASE_URL from .env.
2046
- */
2047
- function migrationPlugin(root, dialect, databaseId = "local", persistRoot = join(root, ".void"), auth) {
2048
- return {
2049
- name: "void:migrations",
2050
- apply: "serve",
2051
- async configureServer(server) {
2052
- const migrationsDir = readProjectPaths(root).migrationsDir;
2053
- const hasSqlMigrations = existsSync(migrationsDir);
2054
- if (!hasSqlMigrations && !auth?.enabled) return;
2055
- try {
2056
- if (hasSqlMigrations && dialect !== "sqlite") {
2057
- const url = readDatabaseUrl(root);
2058
- if (!url) {
2059
- const warn = () => {
2060
- console.log();
2061
- voidError(`DATABASE_URL not found in .env — skipping ${dialect === "mysql" ? "MySQL" : "PostgreSQL"} migrations.`);
2062
- };
2063
- if (server.httpServer) server.httpServer.once("listening", () => setTimeout(warn, 100));
2064
- else warn();
2065
- return;
2066
- }
2067
- const { collectMigrations } = await import("./collect-DLyFDiKW.mjs");
2068
- const { validateMigrations } = await import("./validate-Dq_L3s0S.mjs");
2069
- const allMigrations = collectMigrations(migrationsDir);
2070
- const appliedNames = dialect === "mysql" ? await import("./runner-mysql-CEG5JozK.mjs").then((runner) => runner.getMySqlAppliedMigrations(url)) : await import("./runner-pg-Bh4AgZdj.mjs").then((runner) => runner.getPgAppliedMigrations(url));
2071
- const appliedSet = new Set(appliedNames);
2072
- const pending = allMigrations.filter((migration) => !appliedSet.has(migration.name));
2073
- if (pending.length > 0) try {
2074
- validateMigrations(pending);
2075
- } catch (err) {
2076
- voidWarn(err instanceof Error ? err.message : String(err));
2077
- }
2078
- const applied = dialect === "mysql" ? await import("./runner-mysql-CEG5JozK.mjs").then((runner) => runner.runMySqlMigrations(migrationsDir, url)) : await import("./runner-pg-Bh4AgZdj.mjs").then((runner) => runner.runPgMigrations(migrationsDir, url));
2079
- if (applied.length > 0) voidLog(`Applied ${applied.length} migration(s): ${applied.join(", ")}`);
2080
- } else if (hasSqlMigrations) {
2081
- const { runMigrations, getMiniflareDatabasePath, getAppliedMigrations } = await import("./runner-CXA9Fh8h.mjs");
2082
- const { collectMigrations } = await import("./collect-DLyFDiKW.mjs");
2083
- const { validateMigrations } = await import("./validate-Dq_L3s0S.mjs");
2084
- const dbPath = getMiniflareDatabasePath(persistRoot, databaseId);
2085
- const allMigrations = collectMigrations(migrationsDir);
2086
- const appliedRows = await getAppliedMigrations(dbPath);
2087
- const appliedSet = new Set(appliedRows.map((r) => r.name));
2088
- const pending = allMigrations.filter((m) => !appliedSet.has(m.name));
2089
- if (pending.length > 0) try {
2090
- validateMigrations(pending);
2091
- } catch (err) {
2092
- voidWarn(err instanceof Error ? err.message : String(err));
2093
- }
2094
- const applied = await runMigrations(migrationsDir, dbPath);
2095
- if (applied.length > 0) voidLog(`Applied ${applied.length} migration(s): ${applied.join(", ")}`);
2096
- }
2097
- if (auth?.enabled) {
2098
- const { runLocalVoidAuthMigrations } = await import("./node-BkyRWRx8.mjs");
2099
- await runLocalVoidAuthMigrations(root, dialect, persistRoot, databaseId, auth.configPath);
2100
- voidLog("Applied Better Auth migrations");
2101
- }
2102
- } catch (err) {
2103
- voidError(`Migration error: ${err}`);
2104
- }
2105
- }
2106
- };
2278
+ //#region src/router/compile.ts
2279
+ const ROUTE_URL_PATH = fileURLToPath(import.meta.resolve("#self/runtime/route-url"));
2280
+ const ROUTING_RUNTIME_PATH = fileURLToPath(import.meta.resolve("#self/runtime/routing"));
2281
+ function pushDefaultErrorPageHelpers(lines, options) {
2282
+ lines.push(`const __errorRenderer = __createErrorRenderer(${JSON.stringify({
2283
+ dev: options.dev === true,
2284
+ projectRoot: options.projectRoot,
2285
+ homeDir: options.homeDir
2286
+ })});`);
2287
+ lines.push("const serveCustomErrorPage = __errorRenderer.serveCustomPage;");
2288
+ lines.push("app.onError(__errorRenderer.onError);");
2107
2289
  }
2108
- const CF_ONLY_REGEX = new RegExp(`^(${[
2109
- "void/db",
2110
- "void/kv",
2111
- "void/auth",
2112
- "void/storage",
2113
- "void/ai",
2114
- "void/sandbox",
2115
- "void/env"
2116
- ].map((s) => s.replace("/", "\\/")).join("|")})$`);
2117
2290
  /**
2118
- * Bridge Node.js HTTP to Web API and serve via a Hono app.
2119
- * Used by both dev server and preview server middleware.
2291
+ * Generate the code for the virtual route table module (virtual:void-routes).
2292
+ * This module creates a Hono app with:
2293
+ * - Middleware registered via app.use()
2294
+ * - Routes registered with lazy-loaded handlers and auto-conversion
2120
2295
  */
2121
- function honoMiddleware(getApp) {
2122
- return async (req, res, next) => {
2123
- try {
2124
- if (!req.url || req.url.startsWith("/@") || req.url.startsWith("/__") || req.url.startsWith("/node_modules/")) {
2125
- next();
2126
- return;
2127
- }
2128
- const app = await getApp();
2129
- if (!app?.fetch) {
2130
- next();
2131
- return;
2132
- }
2133
- const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
2134
- const headers = new Headers();
2135
- for (const [key, value] of Object.entries(req.headers)) if (value) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
2136
- const hasBody = req.method !== "GET" && req.method !== "HEAD";
2137
- const request = new Request(url.toString(), {
2138
- method: req.method,
2139
- headers,
2140
- body: hasBody ? req : void 0,
2141
- duplex: hasBody ? "half" : void 0
2142
- });
2143
- const response = await app.fetch(request);
2144
- if (response.status === 404) {
2145
- next();
2146
- return;
2147
- }
2148
- res.statusCode = response.status;
2149
- response.headers.forEach((value, key) => {
2150
- res.setHeader(key, value);
2151
- });
2152
- if (response.body) {
2153
- const reader = response.body.getReader();
2154
- while (true) {
2155
- const { done, value } = await reader.read();
2156
- if (done) break;
2157
- res.write(value);
2158
- }
2159
- res.end();
2160
- } else res.end();
2161
- } catch (err) {
2162
- next(err);
2296
+ function compileRouteTable(scan, routesDir, middlewareDir, jobsDir, queuesDir, ssr, options, pages = null, mergedRevalidate, isrPages, isrPathRevalidate) {
2297
+ const lines = [];
2298
+ const auth = options.auth;
2299
+ const webSockets = scan.websockets ?? [];
2300
+ const managedDispatch = options.nodeTarget !== true && options.dev !== true && options.managedDispatch === true;
2301
+ lines.push(`import { AsyncLocalStorage } from "node:async_hooks";`);
2302
+ lines.push(`import { Hono } from ${JSON.stringify(options.honoPath)};`);
2303
+ lines.push(`import { createRoutingRuntime as __createRoutingRuntime, createHttpRouteHandler as __createHttpRouteHandler, createErrorRenderer as __createErrorRenderer, createPageGetHandler as __createPageGetHandler, createPageMutationHandler as __createPageMutationHandler, createPagesFallback as __createPagesFallback, createModuleLoader as __createModuleLoader, createClientAssetTags as __createClientAssetTags, createManifestLoader as __createManifestLoader, createSsrFallback as __createSsrFallback, createAssetNotFound as __createAssetNotFound } from ${JSON.stringify(ROUTING_RUNTIME_PATH)};`);
2304
+ if (options.nodeTarget) lines.push(`const withRuntimeEnv = (_env, fn) => fn();`);
2305
+ else lines.push(`import { withRuntimeEnv } from ${JSON.stringify(options.runtimeEnvPath)};`);
2306
+ if (auth) {
2307
+ const authImports = [
2308
+ "runVoidAuthMiddleware",
2309
+ "handleVoidAuthRequest",
2310
+ "runVoidAuthMigrations"
2311
+ ];
2312
+ if (webSockets.length > 0) authImports.push("resolveVoidAuthState as __resolveVoidAuthStateRuntime");
2313
+ lines.push(`import { ${authImports.join(", ")} } from ${JSON.stringify(auth.authRuntimePath)};`);
2314
+ if (auth.authDatabasePath) lines.push(`import { __voidAuthDatabase } from ${JSON.stringify(auth.authDatabasePath)};`);
2315
+ if (auth.authSchemaPath) lines.push(`import { migrationSchema as __voidAuthMigrationSchema } from ${JSON.stringify(auth.authSchemaPath)};`);
2316
+ if (auth.authConfigPath) lines.push(`import __voidAuthConfig from ${JSON.stringify(auth.authConfigPath)};`);
2317
+ }
2318
+ if ((webSockets.length > 0 || options.webSocketTombstones?.length) && options.webSocketRuntimePath) lines.push(`import { createWebSocketDurableObject as __createWebSocketDurableObject, createWebSocketTombstoneDurableObject as __createWebSocketTombstoneDurableObject, forwardWebSocketRequest as __forwardWebSocketRequest } from ${JSON.stringify(options.webSocketRuntimePath)};`);
2319
+ if (options.hasLive && options.liveRuntimePath) lines.push(`import { createLiveDurableObject as __createLiveDurableObject } from ${JSON.stringify(options.liveRuntimePath)};`);
2320
+ lines.push("");
2321
+ for (let i = 0; i < scan.middleware.length; i++) {
2322
+ const mw = scan.middleware[i];
2323
+ lines.push(`import middleware${i} from ${JSON.stringify(`${middlewareDir}/${mw.filePath}`)};`);
2324
+ }
2325
+ for (let i = 0; i < webSockets.length; i++) {
2326
+ const route = webSockets[i];
2327
+ lines.push(`import websocket${i} from ${JSON.stringify(`${routesDir}/${route.filePath}`)};`);
2328
+ }
2329
+ lines.push("");
2330
+ if (auth) {
2331
+ lines.push(`const __voidAuthOptions = { dialect: ${JSON.stringify(auth.dialect)}, providers: ${JSON.stringify(auth.providers)}, userConfig: ${auth.authConfigPath ? "__voidAuthConfig" : "undefined"}, dev: ${JSON.stringify(options.dev === true)} };`);
2332
+ if (webSockets.length > 0) lines.push(`const __resolveVoidAuthState = (request, env, runtime) => __resolveVoidAuthStateRuntime(request, env, runtime${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
2333
+ lines.push("");
2334
+ }
2335
+ lines.push(`const app = new Hono();`);
2336
+ const ASSET_EXTENSIONS = [
2337
+ "png",
2338
+ "jpg",
2339
+ "jpeg",
2340
+ "gif",
2341
+ "webp",
2342
+ "avif",
2343
+ "svg",
2344
+ "ico",
2345
+ "css",
2346
+ "js",
2347
+ "mjs",
2348
+ "cjs",
2349
+ "woff",
2350
+ "woff2",
2351
+ "ttf",
2352
+ "otf",
2353
+ "eot",
2354
+ "mp4",
2355
+ "webm",
2356
+ "mp3",
2357
+ "wav",
2358
+ "pdf",
2359
+ "txt",
2360
+ "xml",
2361
+ "json",
2362
+ "wasm",
2363
+ "map"
2364
+ ];
2365
+ const routePatterns = [];
2366
+ for (const r of scan.routes) routePatterns.push(r.pattern);
2367
+ if (pages && scan.pages.pages.length > 0) for (const p of scan.pages.pages) routePatterns.push(p.pattern);
2368
+ const usedExts = /* @__PURE__ */ new Set();
2369
+ for (const p of routePatterns) {
2370
+ const m = /\.([A-Za-z0-9]+)$/.exec(p);
2371
+ if (m) usedExts.add(m[1].toLowerCase());
2372
+ }
2373
+ const blockedExts = ASSET_EXTENSIONS.filter((e) => !usedExts.has(e));
2374
+ lines.push(`const __routingRuntime = __createRoutingRuntime(app, ${JSON.stringify({
2375
+ dev: options.dev === true,
2376
+ nodeTarget: options.nodeTarget === true,
2377
+ managedDispatch,
2378
+ blockedAssetExtensions: blockedExts
2379
+ })});`);
2380
+ lines.push("export const requestContext = __routingRuntime.requestContext;");
2381
+ lines.push("app.use(__routingRuntime.contextMiddleware);");
2382
+ lines.push("app.use(__routingRuntime.requestMiddleware);");
2383
+ lines.push("");
2384
+ const emitEdgeRules = !managedDispatch;
2385
+ if (emitEdgeRules && options.redirectRules?.length) compileRoutingRulesMiddleware(lines, options.redirectRules, options.dev === true, options.nodeTarget === true);
2386
+ if (emitEdgeRules && options.headerRules?.length) compileHeaderMiddleware(lines, options.headerRules);
2387
+ if (auth) {
2388
+ lines.push("app.use(async (c, next) => {");
2389
+ lines.push(` return runVoidAuthMiddleware(c, next, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
2390
+ lines.push("});");
2391
+ lines.push("");
2392
+ }
2393
+ for (let i = 0; i < scan.middleware.length; i++) lines.push(`app.use(middleware${i});`);
2394
+ if (scan.middleware.length > 0) lines.push("");
2395
+ for (let i = 0; i < webSockets.length; i++) {
2396
+ const route = webSockets[i];
2397
+ lines.push(`app.all(${JSON.stringify(route.pattern)}, async (c, next) => {`);
2398
+ lines.push(` if (c.req.raw.headers.get("upgrade")?.toLowerCase() !== "websocket") return next();`);
2399
+ lines.push(` return __forwardWebSocketRequest(c.req.raw, c.env, __voidWebSocketRoutes[${i}]);`);
2400
+ lines.push("});");
2401
+ }
2402
+ if (webSockets.length > 0) lines.push("");
2403
+ if (auth) {
2404
+ lines.push("app.all(\"/api/auth\", async (c) => {");
2405
+ lines.push(` return handleVoidAuthRequest(c, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
2406
+ lines.push("});");
2407
+ lines.push("");
2408
+ lines.push("app.all(\"/api/auth/*\", async (c) => {");
2409
+ lines.push(` return handleVoidAuthRequest(c, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
2410
+ lines.push("});");
2411
+ lines.push("");
2412
+ }
2413
+ const seenFiles = /* @__PURE__ */ new Set();
2414
+ for (const route of scan.routes) {
2415
+ const filePath = `${routesDir}/${route.filePath}`;
2416
+ const loaderName = `_load_${route.filePath.replace(/[^a-zA-Z0-9]/g, "_")}`;
2417
+ if (!seenFiles.has(route.filePath)) {
2418
+ seenFiles.add(route.filePath);
2419
+ lines.push(`const ${loaderName} = __createModuleLoader(() => import(${JSON.stringify(filePath)}), ${JSON.stringify(options.dev !== true)});`);
2163
2420
  }
2164
- };
2165
- }
2166
- /**
2167
- * Vite plugin for non-Cloudflare deploy targets (Node.js, Bun, Deno).
2168
- *
2169
- * Replaces `@cloudflare/vite-plugin` in the plugin composition. Provides:
2170
- * 1. Binding guard — compile-time errors for CF-only void/* imports
2171
- * 2. Dev server — Vite SSR middleware to run Hono in Node.js
2172
- * 3. Preview server — serves from the built SSR entry
2173
- * 4. Build — dual-environment build (client + SSR) for pages apps,
2174
- * single SSR build for API-only apps
2175
- */
2176
- function nodeTargetPlugin(target) {
2177
- return [{
2178
- name: "void:node-target",
2179
- enforce: "pre",
2180
- resolveId: {
2181
- filter: { id: CF_ONLY_REGEX },
2182
- handler(id) {
2183
- throw new Error(`target: '${id}' is not available with target '${target}'. CF bindings require target: "cloudflare".`);
2184
- }
2185
- },
2186
- renderChunk(code, chunk) {
2187
- if (!code.includes("import.meta.voidClientDirectory")) return null;
2188
- const clientPath = relative(dirname(chunk.fileName), "../client");
2189
- return {
2190
- code: code.replaceAll("import.meta.voidClientDirectory", `new URL(${JSON.stringify(`${clientPath}/`)}, import.meta.url)`),
2191
- map: null
2192
- };
2193
- },
2194
- config(_, { command }) {
2195
- if (command !== "build") return;
2196
- const root = process.cwd();
2197
- const voidDir = join(root, ".void");
2198
- const ssrBuildOptions = {
2199
- outDir: "dist/ssr",
2200
- rollupOptions: {
2201
- input: {
2202
- app: join(voidDir, "app.ts"),
2203
- index: join(voidDir, "index.ts")
2204
- },
2205
- external: [
2206
- /^node:/,
2207
- "@hono/node-server",
2208
- "@hono/node-server/serve-static"
2209
- ]
2210
- }
2211
- };
2212
- if (existsSync(readProjectPaths(root).pagesDir)) return {
2213
- build: { outDir: "dist/client" },
2214
- environments: { ssr: { build: ssrBuildOptions } },
2215
- builder: {}
2216
- };
2217
- return { build: {
2218
- ssr: true,
2219
- ...ssrBuildOptions
2220
- } };
2221
- },
2222
- async buildApp(builder) {
2223
- await builder.build(builder.environments.ssr);
2224
- await builder.build(builder.environments.client);
2225
- },
2226
- configureServer(server) {
2227
- server.middlewares.use(honoMiddleware(async () => {
2228
- return (await server.ssrLoadModule("virtual:void-routes")).default;
2229
- }));
2230
- },
2231
- configurePreviewServer(server) {
2232
- const entryPath = join(process.cwd(), "dist", "ssr", "app.js");
2233
- let appPromise = null;
2234
- server.middlewares.use(honoMiddleware(() => {
2235
- if (!appPromise) appPromise = import(
2236
- /* @vite-ignore */
2237
- pathToFileURL(entryPath).href
2238
- ).then((m) => m.default, () => null);
2239
- return appPromise;
2240
- }));
2421
+ if (route.methods.length === 0) lines.push(`app.all(${JSON.stringify(route.pattern)}, __createHttpRouteHandler(${loaderName}, "default"));`);
2422
+ else for (const method of route.methods) {
2423
+ const honoMethod = method.toLowerCase();
2424
+ lines.push(`app.${honoMethod}(${JSON.stringify(route.pattern)}, __createHttpRouteHandler(${loaderName}, ${JSON.stringify(method)}));`);
2241
2425
  }
2242
- }];
2243
- }
2244
- //#endregion
2245
- //#region src/plugin-prerender.ts
2246
- /**
2247
- * Vite plugin that prerenders pages at build time when `output: "static"`.
2248
- *
2249
- * Runs in the `closeBundle` hook so prerender logs appear after Vite's
2250
- * asset summary. Gated on the "client" environment so it only runs once.
2251
- */
2252
- function prerenderPlugin(root, config) {
2253
- return {
2254
- name: "void:prerender",
2255
- apply: "build",
2256
- async closeBundle() {
2257
- if (config.output !== "static") return;
2258
- if (process.env.VOID_DEPLOY_BUILD === "1") return;
2259
- if (this.environment?.name !== "client") return;
2260
- setShowTimestamp(false);
2261
- const distDir = resolveDistDir(root);
2262
- const clientDir = join(distDir, "client");
2263
- let result;
2264
- if (isNodeTarget(config.target)) result = await prerenderPagesNode({
2265
- root,
2266
- ssrDir: join(distDir, "ssr"),
2267
- clientDir
2268
- });
2269
- else {
2270
- const persistDir = join(root, ".void");
2271
- const workerDirName = resolveWorkerDirName(distDir);
2272
- const workerDir = join(distDir, workerDirName);
2273
- const remoteMode = resolveRemoteMode(config.remote);
2274
- result = await prerenderPages({
2275
- root,
2276
- workerDir,
2277
- clientDir,
2278
- persistDir,
2279
- remoteMode,
2280
- projectId: process.env.VOID_DEPLOY_PROJECT_ID || void 0
2281
- });
2282
- }
2283
- if (result.paths.length === 0) {
2284
- voidLog("No pages to prerender");
2285
- return;
2286
- }
2287
- if (result.failed.length > 0) voidWarn(`${result.failed.length} page(s) failed to prerender`);
2288
- }
2289
- };
2290
- }
2291
- //#endregion
2292
- //#region src/plugin-restart.ts
2293
- const BINDING_HINT_RESOLVED = "\0virtual:void/binding-hint";
2294
- const RESTARTING_HTML = `<!DOCTYPE html>
2295
- <html lang="en">
2296
- <head>
2297
- <meta charset="UTF-8">
2298
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
2299
- <title>Restarting</title>
2300
- <style>
2301
- 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}
2302
- .tag{color:#c4a7e7;font-weight:700}
2303
- @keyframes d{0%,80%{opacity:0}40%{opacity:1}}
2304
- .dots span{opacity:0;animation:d 1.4s infinite}
2305
- .dots span:nth-child(2){animation-delay:.2s}
2306
- .dots span:nth-child(3){animation-delay:.4s}
2307
- </style>
2308
- </head>
2309
- <body>
2310
- <div><span class="tag">[void]</span> Restarting dev server<span class="dots"><span>.</span><span>.</span><span>.</span></span></div>
2311
- <script>
2312
- fetch("/__void/restart",{method:"POST"}).catch(function(){});
2313
- setTimeout(function p(){
2314
- fetch("/__void/ping").then(function(r){
2315
- if(r.ok)location.href=new URLSearchParams(location.search).get("return")||"/";
2316
- else setTimeout(p,150);
2317
- }).catch(function(){setTimeout(p,150)});
2318
- },300);
2319
- <\/script>
2320
- </body>
2321
- </html>`;
2322
- const CLIENT_MODULE = `if (import.meta.hot) {
2323
- import.meta.hot.on("void:new-binding", (d) => {
2324
- if (document.getElementById("void-binding-hint")) return;
2325
- const e = document.createElement("div");
2326
- e.id = "void-binding-hint";
2327
- 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";
2328
- e.innerHTML = "<strong style=\\"color:#c4a7e7\\">[void]</strong> " + d.message + " Click to restart.";
2329
- e.onclick = () => { location.href = "/__void/restarting?return=" + encodeURIComponent(location.pathname + location.search); };
2330
- document.body.appendChild(e);
2331
- requestAnimationFrame(() => requestAnimationFrame(() => {
2332
- e.style.transform = "translateX(0)";
2333
- }));
2334
- });
2335
- }`;
2336
- /**
2337
- * Vite plugin that provides controlled dev-server restart via the browser.
2338
- *
2339
- * Serves three endpoints:
2340
- * - `/__void/restarting` — standalone loading page (no Vite HMR client)
2341
- * - `/__void/restart` — POST triggers `server.restart()` after a short delay
2342
- * - `/__void/ping` — health check; returns 503 on the old server instance
2343
- * (after restart is triggered) and 200 on the new one
2344
- *
2345
- * Also injects a virtual module (`virtual:void/binding-hint`) that listens for
2346
- * `void:new-binding` HMR events and shows a toast with a click-to-restart action.
2347
- */
2348
- function restartPlugin() {
2349
- return {
2350
- name: "void:restart",
2351
- apply: "serve",
2352
- resolveId: {
2353
- filter: { id: /^virtual:void\/binding-hint$/ },
2354
- handler() {
2355
- return BINDING_HINT_RESOLVED;
2356
- }
2357
- },
2358
- load: {
2359
- filter: { id: /^\0virtual:void\/binding-hint$/ },
2360
- handler() {
2361
- return CLIENT_MODULE;
2362
- }
2363
- },
2364
- configureServer(server) {
2365
- let restartPending = false;
2366
- server.middlewares.use("/__void/ping", (_req, res) => {
2367
- res.writeHead(restartPending ? 503 : 200);
2368
- res.end();
2369
- });
2370
- server.middlewares.use("/__void/restart", (req, res) => {
2371
- if (req.method !== "POST") {
2372
- res.writeHead(405);
2373
- res.end();
2374
- return;
2375
- }
2376
- restartPending = true;
2377
- res.writeHead(200);
2378
- res.end();
2379
- setTimeout(() => server.restart(), 50);
2380
- });
2381
- server.middlewares.use("/__void/restarting", (_req, res) => {
2382
- res.writeHead(200, { "Content-Type": "text/html" });
2383
- res.end(RESTARTING_HTML);
2384
- });
2385
- }
2386
- };
2387
- }
2388
- //#endregion
2389
- //#region src/client-asset-manifest.ts
2390
- /** Attach cache provenance to the existing manifest without changing its entry inventory. */
2391
- function stampClientAssetCachePolicy(bundle, manifestPath, headerRules = []) {
2392
- const asset = bundle[manifestPath];
2393
- if (asset?.type !== "asset") return;
2394
- const fingerprinted = /* @__PURE__ */ new Set();
2395
- const authoredCachePatterns = headerRules.filter((rule) => rule.headers.some(([name]) => name.toLowerCase() === "cache-control")).map((rule) => compilePattern(rule.pattern).regex);
2396
- for (const item of Object.values(bundle)) if (item.type === "chunk" && typeof item.preliminaryFileName === "string" && item.preliminaryFileName !== item.fileName) {
2397
- if (!authoredCachePatterns.some((pattern) => pattern.test("/" + item.fileName))) fingerprinted.add(item.fileName);
2398
- }
2399
- const manifest = JSON.parse(String(asset.source));
2400
- for (const entry of Object.values(manifest)) if (entry.file && fingerprinted.has(entry.file)) entry.immutableFiles = [entry.file];
2401
- else delete entry.immutableFiles;
2402
- asset.source = JSON.stringify(manifest, null, 2);
2403
- }
2404
- //#endregion
2405
- //#region src/router/compile.ts
2406
- const ROUTE_URL_PATH = fileURLToPath(import.meta.resolve("#self/runtime/route-url"));
2407
- const ROUTING_RUNTIME_PATH = fileURLToPath(import.meta.resolve("#self/runtime/routing"));
2408
- function pushDefaultErrorPageHelpers(lines, options) {
2409
- lines.push(`const __errorRenderer = __createErrorRenderer(${JSON.stringify({
2410
- dev: options.dev === true,
2411
- projectRoot: options.projectRoot,
2412
- homeDir: options.homeDir
2413
- })});`);
2414
- lines.push("const serveCustomErrorPage = __errorRenderer.serveCustomPage;");
2415
- lines.push("app.onError(__errorRenderer.onError);");
2416
- }
2417
- /**
2418
- * Generate the code for the virtual route table module (virtual:void-routes).
2419
- * This module creates a Hono app with:
2420
- * - Middleware registered via app.use()
2421
- * - Routes registered with lazy-loaded handlers and auto-conversion
2422
- */
2423
- function compileRouteTable(scan, routesDir, middlewareDir, jobsDir, queuesDir, ssr, options, pages = null, mergedRevalidate, isrPages, isrPathRevalidate) {
2424
- const lines = [];
2425
- const auth = options.auth;
2426
- const webSockets = scan.websockets ?? [];
2427
- const managedDispatch = options.nodeTarget !== true && options.dev !== true && options.managedDispatch === true;
2428
- lines.push(`import { AsyncLocalStorage } from "node:async_hooks";`);
2429
- lines.push(`import { Hono } from ${JSON.stringify(options.honoPath)};`);
2430
- lines.push(`import { createRoutingRuntime as __createRoutingRuntime, createHttpRouteHandler as __createHttpRouteHandler, createErrorRenderer as __createErrorRenderer, createPageGetHandler as __createPageGetHandler, createPageMutationHandler as __createPageMutationHandler, createPagesFallback as __createPagesFallback, createModuleLoader as __createModuleLoader, createClientAssetTags as __createClientAssetTags, createManifestLoader as __createManifestLoader, createSsrFallback as __createSsrFallback, createAssetNotFound as __createAssetNotFound } from ${JSON.stringify(ROUTING_RUNTIME_PATH)};`);
2431
- if (options.nodeTarget) lines.push(`const withRuntimeEnv = (_env, fn) => fn();`);
2432
- else lines.push(`import { withRuntimeEnv } from ${JSON.stringify(options.runtimeEnvPath)};`);
2433
- if (auth) {
2434
- const authImports = [
2435
- "runVoidAuthMiddleware",
2436
- "handleVoidAuthRequest",
2437
- "runVoidAuthMigrations"
2438
- ];
2439
- if (webSockets.length > 0) authImports.push("resolveVoidAuthState as __resolveVoidAuthStateRuntime");
2440
- lines.push(`import { ${authImports.join(", ")} } from ${JSON.stringify(auth.authRuntimePath)};`);
2441
- if (auth.authDatabasePath) lines.push(`import { __voidAuthDatabase } from ${JSON.stringify(auth.authDatabasePath)};`);
2442
- if (auth.authSchemaPath) lines.push(`import { migrationSchema as __voidAuthMigrationSchema } from ${JSON.stringify(auth.authSchemaPath)};`);
2443
- if (auth.authConfigPath) lines.push(`import __voidAuthConfig from ${JSON.stringify(auth.authConfigPath)};`);
2444
- }
2445
- if ((webSockets.length > 0 || options.webSocketTombstones?.length) && options.webSocketRuntimePath) lines.push(`import { createWebSocketDurableObject as __createWebSocketDurableObject, createWebSocketTombstoneDurableObject as __createWebSocketTombstoneDurableObject, forwardWebSocketRequest as __forwardWebSocketRequest } from ${JSON.stringify(options.webSocketRuntimePath)};`);
2446
- if (options.hasLive && options.liveRuntimePath) lines.push(`import { createLiveDurableObject as __createLiveDurableObject } from ${JSON.stringify(options.liveRuntimePath)};`);
2447
- lines.push("");
2448
- for (let i = 0; i < scan.middleware.length; i++) {
2449
- const mw = scan.middleware[i];
2450
- lines.push(`import middleware${i} from ${JSON.stringify(`${middlewareDir}/${mw.filePath}`)};`);
2451
- }
2452
- for (let i = 0; i < webSockets.length; i++) {
2453
- const route = webSockets[i];
2454
- lines.push(`import websocket${i} from ${JSON.stringify(`${routesDir}/${route.filePath}`)};`);
2455
2426
  }
2456
2427
  lines.push("");
2457
- if (auth) {
2458
- lines.push(`const __voidAuthOptions = { dialect: ${JSON.stringify(auth.dialect)}, providers: ${JSON.stringify(auth.providers)}, userConfig: ${auth.authConfigPath ? "__voidAuthConfig" : "undefined"}, dev: ${JSON.stringify(options.dev === true)} };`);
2459
- if (webSockets.length > 0) lines.push(`const __resolveVoidAuthState = (request, env, runtime) => __resolveVoidAuthStateRuntime(request, env, runtime${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
2428
+ if (webSockets.length > 0) {
2429
+ for (let i = 0; i < webSockets.length; i++) {
2430
+ const route = webSockets[i];
2431
+ lines.push(`export const ${route.className} = __createWebSocketDurableObject(websocket${i}, ${JSON.stringify({
2432
+ pattern: route.pattern,
2433
+ params: route.params,
2434
+ className: route.className,
2435
+ bindingName: route.bindingName,
2436
+ ...route.name !== void 0 && { name: route.name }
2437
+ })}, ${auth ? "__voidAuthOptions" : "undefined"}, ${auth ? "__resolveVoidAuthState" : "undefined"});`);
2438
+ }
2439
+ lines.push("");
2440
+ lines.push(`const __voidWebSocketRoutes = ${JSON.stringify(webSockets.map((route) => ({
2441
+ pattern: route.pattern,
2442
+ params: route.params,
2443
+ className: route.className,
2444
+ bindingName: route.bindingName,
2445
+ ...route.name !== void 0 && { name: route.name }
2446
+ })))};`);
2460
2447
  lines.push("");
2461
2448
  }
2462
- lines.push(`const app = new Hono();`);
2463
- const ASSET_EXTENSIONS = [
2464
- "png",
2465
- "jpg",
2466
- "jpeg",
2467
- "gif",
2468
- "webp",
2469
- "avif",
2470
- "svg",
2471
- "ico",
2472
- "css",
2473
- "js",
2474
- "mjs",
2475
- "cjs",
2476
- "woff",
2477
- "woff2",
2478
- "ttf",
2479
- "otf",
2480
- "eot",
2481
- "mp4",
2482
- "webm",
2483
- "mp3",
2484
- "wav",
2485
- "pdf",
2486
- "txt",
2487
- "xml",
2488
- "json",
2489
- "wasm",
2490
- "map"
2491
- ];
2492
- const routePatterns = [];
2493
- for (const r of scan.routes) routePatterns.push(r.pattern);
2494
- if (pages && scan.pages.pages.length > 0) for (const p of scan.pages.pages) routePatterns.push(p.pattern);
2495
- const usedExts = /* @__PURE__ */ new Set();
2496
- for (const p of routePatterns) {
2497
- const m = /\.([A-Za-z0-9]+)$/.exec(p);
2498
- if (m) usedExts.add(m[1].toLowerCase());
2499
- }
2500
- const blockedExts = ASSET_EXTENSIONS.filter((e) => !usedExts.has(e));
2501
- lines.push(`const __routingRuntime = __createRoutingRuntime(app, ${JSON.stringify({
2502
- dev: options.dev === true,
2503
- nodeTarget: options.nodeTarget === true,
2504
- managedDispatch,
2505
- blockedAssetExtensions: blockedExts
2506
- })});`);
2507
- lines.push("export const requestContext = __routingRuntime.requestContext;");
2508
- lines.push("app.use(__routingRuntime.contextMiddleware);");
2509
- lines.push("app.use(__routingRuntime.requestMiddleware);");
2510
- lines.push("");
2511
- const emitEdgeRules = !managedDispatch;
2512
- if (emitEdgeRules && options.redirectRules?.length) compileRoutingRulesMiddleware(lines, options.redirectRules, options.dev === true, options.nodeTarget === true);
2513
- if (emitEdgeRules && options.headerRules?.length) compileHeaderMiddleware(lines, options.headerRules);
2514
- if (auth) {
2515
- lines.push("app.use(async (c, next) => {");
2516
- lines.push(` return runVoidAuthMiddleware(c, next, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
2517
- lines.push("});");
2518
- lines.push("");
2519
- }
2520
- for (let i = 0; i < scan.middleware.length; i++) lines.push(`app.use(middleware${i});`);
2521
- if (scan.middleware.length > 0) lines.push("");
2522
- for (let i = 0; i < webSockets.length; i++) {
2523
- const route = webSockets[i];
2524
- lines.push(`app.all(${JSON.stringify(route.pattern)}, async (c, next) => {`);
2525
- lines.push(` if (c.req.raw.headers.get("upgrade")?.toLowerCase() !== "websocket") return next();`);
2526
- lines.push(` return __forwardWebSocketRequest(c.req.raw, c.env, __voidWebSocketRoutes[${i}]);`);
2527
- lines.push("});");
2528
- }
2529
- if (webSockets.length > 0) lines.push("");
2530
- if (auth) {
2531
- lines.push("app.all(\"/api/auth\", async (c) => {");
2532
- lines.push(` return handleVoidAuthRequest(c, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
2533
- lines.push("});");
2534
- lines.push("");
2535
- lines.push("app.all(\"/api/auth/*\", async (c) => {");
2536
- lines.push(` return handleVoidAuthRequest(c, __voidAuthOptions${auth.authDatabasePath ? ", __voidAuthDatabase" : ""});`);
2537
- lines.push("});");
2538
- lines.push("");
2539
- }
2540
- const seenFiles = /* @__PURE__ */ new Set();
2541
- for (const route of scan.routes) {
2542
- const filePath = `${routesDir}/${route.filePath}`;
2543
- const loaderName = `_load_${route.filePath.replace(/[^a-zA-Z0-9]/g, "_")}`;
2544
- if (!seenFiles.has(route.filePath)) {
2545
- seenFiles.add(route.filePath);
2546
- lines.push(`const ${loaderName} = __createModuleLoader(() => import(${JSON.stringify(filePath)}), ${JSON.stringify(options.dev !== true)});`);
2547
- }
2548
- if (route.methods.length === 0) lines.push(`app.all(${JSON.stringify(route.pattern)}, __createHttpRouteHandler(${loaderName}, "default"));`);
2549
- else for (const method of route.methods) {
2550
- const honoMethod = method.toLowerCase();
2551
- lines.push(`app.${honoMethod}(${JSON.stringify(route.pattern)}, __createHttpRouteHandler(${loaderName}, ${JSON.stringify(method)}));`);
2552
- }
2553
- }
2554
- lines.push("");
2555
- if (webSockets.length > 0) {
2556
- for (let i = 0; i < webSockets.length; i++) {
2557
- const route = webSockets[i];
2558
- lines.push(`export const ${route.className} = __createWebSocketDurableObject(websocket${i}, ${JSON.stringify({
2559
- pattern: route.pattern,
2560
- params: route.params,
2561
- className: route.className,
2562
- bindingName: route.bindingName,
2563
- ...route.name !== void 0 && { name: route.name }
2564
- })}, ${auth ? "__voidAuthOptions" : "undefined"}, ${auth ? "__resolveVoidAuthState" : "undefined"});`);
2565
- }
2566
- lines.push("");
2567
- lines.push(`const __voidWebSocketRoutes = ${JSON.stringify(webSockets.map((route) => ({
2568
- pattern: route.pattern,
2569
- params: route.params,
2570
- className: route.className,
2571
- bindingName: route.bindingName,
2572
- ...route.name !== void 0 && { name: route.name }
2573
- })))};`);
2574
- lines.push("");
2575
- }
2576
- for (let i = 0; i < webSockets.length; i++) lines.push(`__voidWebSocketRoutes[${i}].key = websocket${i}.key;`);
2577
- for (const className of options.webSocketTombstones ?? []) lines.push(`export const ${className} = __createWebSocketTombstoneDurableObject(${JSON.stringify(className)});`);
2578
- if (options.webSocketTombstones?.length) lines.push("");
2579
- if (options.hasLive) {
2580
- lines.push("export const VoidLiveStreamDurableObject = __createLiveDurableObject();");
2581
- lines.push("");
2449
+ for (let i = 0; i < webSockets.length; i++) lines.push(`__voidWebSocketRoutes[${i}].key = websocket${i}.key;`);
2450
+ for (const className of options.webSocketTombstones ?? []) lines.push(`export const ${className} = __createWebSocketTombstoneDurableObject(${JSON.stringify(className)});`);
2451
+ if (options.webSocketTombstones?.length) lines.push("");
2452
+ if (options.hasLive) {
2453
+ lines.push("export const VoidLiveStreamDurableObject = __createLiveDurableObject();");
2454
+ lines.push("");
2582
2455
  }
2583
2456
  if (pages && scan.pages.pages.length > 0) compilePageRoutes(lines, scan, pages, options);
2584
2457
  for (let i = 0; i < webSockets.length; i++) lines.push(`app.all(${JSON.stringify(webSockets[i].pattern)}, (c) => __forwardWebSocketRequest(c.req.raw, c.env, __voidWebSocketRoutes[${i}]));`);
@@ -2791,644 +2664,1352 @@ function compileRouteTable(scan, routesDir, middlewareDir, jobsDir, queuesDir, s
2791
2664
  lines.push(" return __voidCreateMigrationRequestHandler(request, true, true)(request);");
2792
2665
  lines.push(" }");
2793
2666
  }
2794
- lines.push(" return null;");
2795
- lines.push("}");
2796
- lines.push("app.post(\"/__void/migrate\", async (c) => {");
2797
- lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2798
- lines.push(" __voidGetMigrationEnv(),");
2799
- lines.push(" __migrations,");
2800
- lines.push(" __migrationDialect,");
2801
- if (auth) lines.push(` () => runVoidAuthMigrations(new Request(c.req.url), __voidGetMigrationEnv(), __voidAuthOptions${auth.authSchemaPath ? ", __voidAuthMigrationSchema" : ""}),`);
2802
- else lines.push(" undefined,");
2803
- lines.push(" );");
2804
- lines.push(" return __voidMigrationHandler(c.req.raw);");
2805
- lines.push("});");
2806
- lines.push("app.get(\"/__void/migrate/capabilities\", async (c) => {");
2807
- lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2808
- lines.push(" __voidGetMigrationEnv(),");
2809
- lines.push(" __migrations,");
2810
- lines.push(" __migrationDialect,");
2811
- if (auth) lines.push(` () => runVoidAuthMigrations(new Request(c.req.url), __voidGetMigrationEnv(), __voidAuthOptions${auth.authSchemaPath ? ", __voidAuthMigrationSchema" : ""}),`);
2812
- else lines.push(" undefined,");
2813
- lines.push(" __migrationPlanBundle,");
2814
- lines.push(" );");
2815
- lines.push(" return __voidMigrationHandler(c.req.raw);");
2816
- lines.push("});");
2817
- lines.push("app.post(\"/__void/migrate/app\", async (c) => {");
2818
- lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2819
- lines.push(" __voidGetMigrationEnv(),");
2820
- lines.push(" __migrations,");
2821
- lines.push(" __migrationDialect,");
2822
- lines.push(" undefined,");
2823
- lines.push(" __migrationPlanBundle,");
2824
- lines.push(" );");
2825
- lines.push(" return __voidMigrationHandler(c.req.raw);");
2826
- lines.push("});");
2827
- lines.push("app.get(\"/__void/migrate/app/attempt/:attemptId\", async (c) => {");
2828
- lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2829
- lines.push(" __voidGetMigrationEnv(),");
2830
- lines.push(" __migrations,");
2831
- lines.push(" __migrationDialect,");
2832
- lines.push(" undefined,");
2833
- lines.push(" __migrationPlanBundle,");
2834
- lines.push(" );");
2835
- lines.push(" return __voidMigrationHandler(c.req.raw);");
2836
- lines.push("});");
2837
- if (auth) {
2838
- lines.push("app.post(\"/__void/migrate/auth\", async (c) => {");
2839
- lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2840
- lines.push(" __voidGetMigrationEnv(),");
2841
- lines.push(" __migrations,");
2842
- lines.push(" __migrationDialect,");
2843
- lines.push(` () => runVoidAuthMigrations(new Request(c.req.url), __voidGetMigrationEnv(), __voidAuthOptions${auth.authSchemaPath ? ", __voidAuthMigrationSchema" : ""}),`);
2844
- lines.push(" __migrationPlanBundle,");
2845
- lines.push(" );");
2846
- lines.push(" return __voidMigrationHandler(c.req.raw);");
2847
- lines.push("});");
2667
+ lines.push(" return null;");
2668
+ lines.push("}");
2669
+ lines.push("app.post(\"/__void/migrate\", async (c) => {");
2670
+ lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2671
+ lines.push(" __voidGetMigrationEnv(),");
2672
+ lines.push(" __migrations,");
2673
+ lines.push(" __migrationDialect,");
2674
+ if (auth) lines.push(` () => runVoidAuthMigrations(new Request(c.req.url), __voidGetMigrationEnv(), __voidAuthOptions${auth.authSchemaPath ? ", __voidAuthMigrationSchema" : ""}),`);
2675
+ else lines.push(" undefined,");
2676
+ lines.push(" );");
2677
+ lines.push(" return __voidMigrationHandler(c.req.raw);");
2678
+ lines.push("});");
2679
+ lines.push("app.get(\"/__void/migrate/capabilities\", async (c) => {");
2680
+ lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2681
+ lines.push(" __voidGetMigrationEnv(),");
2682
+ lines.push(" __migrations,");
2683
+ lines.push(" __migrationDialect,");
2684
+ if (auth) lines.push(` () => runVoidAuthMigrations(new Request(c.req.url), __voidGetMigrationEnv(), __voidAuthOptions${auth.authSchemaPath ? ", __voidAuthMigrationSchema" : ""}),`);
2685
+ else lines.push(" undefined,");
2686
+ lines.push(" __migrationPlanBundle,");
2687
+ lines.push(" );");
2688
+ lines.push(" return __voidMigrationHandler(c.req.raw);");
2689
+ lines.push("});");
2690
+ lines.push("app.post(\"/__void/migrate/app\", async (c) => {");
2691
+ lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2692
+ lines.push(" __voidGetMigrationEnv(),");
2693
+ lines.push(" __migrations,");
2694
+ lines.push(" __migrationDialect,");
2695
+ lines.push(" undefined,");
2696
+ lines.push(" __migrationPlanBundle,");
2697
+ lines.push(" );");
2698
+ lines.push(" return __voidMigrationHandler(c.req.raw);");
2699
+ lines.push("});");
2700
+ lines.push("app.get(\"/__void/migrate/app/attempt/:attemptId\", async (c) => {");
2701
+ lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2702
+ lines.push(" __voidGetMigrationEnv(),");
2703
+ lines.push(" __migrations,");
2704
+ lines.push(" __migrationDialect,");
2705
+ lines.push(" undefined,");
2706
+ lines.push(" __migrationPlanBundle,");
2707
+ lines.push(" );");
2708
+ lines.push(" return __voidMigrationHandler(c.req.raw);");
2709
+ lines.push("});");
2710
+ if (auth) {
2711
+ lines.push("app.post(\"/__void/migrate/auth\", async (c) => {");
2712
+ lines.push(" const __voidMigrationHandler = __createMigrationHandler(");
2713
+ lines.push(" __voidGetMigrationEnv(),");
2714
+ lines.push(" __migrations,");
2715
+ lines.push(" __migrationDialect,");
2716
+ lines.push(` () => runVoidAuthMigrations(new Request(c.req.url), __voidGetMigrationEnv(), __voidAuthOptions${auth.authSchemaPath ? ", __voidAuthMigrationSchema" : ""}),`);
2717
+ lines.push(" __migrationPlanBundle,");
2718
+ lines.push(" );");
2719
+ lines.push(" return __voidMigrationHandler(c.req.raw);");
2720
+ lines.push("});");
2721
+ }
2722
+ lines.push("");
2723
+ } else {
2724
+ lines.push("export async function __voidHandleInternalRoute() { return null; }");
2725
+ lines.push("");
2726
+ }
2727
+ if (options.bindingHandlerPath) {
2728
+ lines.push(`import { createBindingHandler as __createBindingHandler } from ${JSON.stringify(options.bindingHandlerPath)};`);
2729
+ lines.push("let __voidBindingApp;");
2730
+ lines.push("app.all(\"/__void/*\", async (c) => {");
2731
+ lines.push(" if (!__voidBindingApp) __voidBindingApp = __createBindingHandler(__getRawEnv());");
2732
+ lines.push(" const url = new URL(c.req.url);");
2733
+ lines.push(" url.pathname = url.pathname.slice(7);");
2734
+ lines.push(" return __voidBindingApp.fetch(new Request(url.toString(), c.req.raw));");
2735
+ lines.push("});");
2736
+ lines.push("");
2737
+ }
2738
+ lines.push("const __rawEnvStore = new AsyncLocalStorage();");
2739
+ lines.push("export function __withRawEnv(env, fn) { return __rawEnvStore.run(env, fn); }");
2740
+ lines.push("function __getRawEnv() { return __rawEnvStore.getStore(); }");
2741
+ lines.push(`export const __VOID_BUILD_KEY = ${JSON.stringify(crypto.randomUUID())};`);
2742
+ lines.push(`export const __VOID_DEV_TRIGGER_TOKEN = ${JSON.stringify(options.devTriggerToken ?? crypto.randomUUID())};`);
2743
+ lines.push(`export const __VOID_ISR_REVALIDATE = ${JSON.stringify(mergedRevalidate)};`);
2744
+ lines.push(`export const __VOID_ISR_PAGES = ${JSON.stringify(isrPages)};`);
2745
+ lines.push(`export const __VOID_ISR_PATH_REVALIDATE = ${JSON.stringify(isrPathRevalidate)};`);
2746
+ lines.push(`export const __VOID_ISR_DEPLOYMENT_ID = ${JSON.stringify(crypto.randomUUID())};`);
2747
+ lines.push("");
2748
+ lines.push(`export default app;`);
2749
+ return lines.join("\n");
2750
+ }
2751
+ function compilePrerenderCollector(lines, scan, ssr, options) {
2752
+ const staticPaths = scan.pages.pages.filter((page) => page.shouldPrerender && page.params.length === 0 && !page.catchAll).map((page) => page.pattern);
2753
+ const dynamicPages = scan.pages.pages.filter((page) => page.shouldPrerender && page.hasGetPrerenderPaths && (page.params.length > 0 || page.catchAll));
2754
+ const explicitPaths = options.explicitPrerenderPaths ?? [];
2755
+ lines.push(`function __voidBuildPrerenderPath(pattern, params) {`);
2756
+ lines.push(` let path = pattern;`);
2757
+ lines.push(` for (const [key, value] of Object.entries(params)) {`);
2758
+ lines.push(` path = path.replace(":" + key + "{.+}", value);`);
2759
+ lines.push(` path = path.replace(":" + key, value);`);
2760
+ lines.push(` }`);
2761
+ lines.push(` return path;`);
2762
+ lines.push(`}`);
2763
+ lines.push("");
2764
+ lines.push(`export async function __voidCollectPrerenderPaths(request, env, ctx) {`);
2765
+ lines.push(` return withRuntimeEnv(env, async () => {`);
2766
+ if (options.nodeTarget) lines.push(` return requestContext.run({ request }, async () => {`);
2767
+ else lines.push(` return requestContext.run({ request, executionCtx: ctx }, async () => {`);
2768
+ lines.push(` const paths = [];`);
2769
+ for (const path of staticPaths) lines.push(` paths.push(${JSON.stringify(path)});`);
2770
+ for (const page of dynamicPages) {
2771
+ const loaderName = `_pload_${page.componentId.replace(/[^a-zA-Z0-9]/g, "_")}`;
2772
+ lines.push(` {`);
2773
+ lines.push(` const mod = await ${loaderName}();`);
2774
+ lines.push(` if (typeof mod.getPrerenderPaths === "function") {`);
2775
+ lines.push(` const paramSets = await mod.getPrerenderPaths();`);
2776
+ lines.push(` if (Array.isArray(paramSets)) {`);
2777
+ lines.push(` for (const params of paramSets) {`);
2778
+ lines.push(` paths.push(__voidBuildPrerenderPath(${JSON.stringify(page.pattern)}, params));`);
2779
+ lines.push(` }`);
2780
+ lines.push(` }`);
2781
+ lines.push(` }`);
2782
+ lines.push(` }`);
2783
+ }
2784
+ if (ssr) {
2785
+ lines.push(` {`);
2786
+ lines.push(` const mod = await import(${JSON.stringify(ssr.serverEntryPath)});`);
2787
+ lines.push(` if (typeof mod.getPrerenderPaths === "function") {`);
2788
+ lines.push(` const ssrPaths = await mod.getPrerenderPaths();`);
2789
+ lines.push(` if (Array.isArray(ssrPaths)) {`);
2790
+ lines.push(` for (const path of ssrPaths) {`);
2791
+ lines.push(` if (typeof path === "string") paths.push(path);`);
2792
+ lines.push(` }`);
2793
+ lines.push(` }`);
2794
+ lines.push(` }`);
2795
+ lines.push(` }`);
2796
+ }
2797
+ for (const path of explicitPaths) lines.push(` paths.push(${JSON.stringify(path)});`);
2798
+ lines.push(` return [...new Set(paths)];`);
2799
+ lines.push(` });`);
2800
+ lines.push(` });`);
2801
+ lines.push(`}`);
2802
+ lines.push("");
2803
+ }
2804
+ function compilePageRoutes(lines, scan, pages, options) {
2805
+ lines.push(`import { renderPage } from "virtual:void-pages-renderer";`);
2806
+ if (options.dev) lines.push(`import { getVoidPagesDevCss } from "virtual:void-pages-dev-assets";`);
2807
+ const hasIslandPages = scan.pages.pages.some((p) => p.renderMode === "island");
2808
+ if (hasIslandPages) lines.push(`import { renderIslandPage } from "virtual:void-islands-renderer";`);
2809
+ lines.push("");
2810
+ lines.push(`const __voidHeadConfig = ${JSON.stringify(pages.headConfig ?? null)};`);
2811
+ lines.push("");
2812
+ pushDefaultErrorPageHelpers(lines, options);
2813
+ const relPagesDir = pages.pagesDirRelative;
2814
+ const pageCssKeys = {};
2815
+ const pageDevRootPhases = {};
2816
+ const projectRoot = options.projectRoot;
2817
+ const toDevRootUrl = (absolutePath) => "/" + absolutePath.replaceAll("\\", "/").slice(projectRoot.replaceAll("\\", "/").length + 1);
2818
+ const virtualDevUrl = (id) => "/@id/__x00__" + id;
2819
+ for (const page of scan.pages.pages) {
2820
+ const keys = [];
2821
+ const routeRootPhases = [[virtualDevUrl(page.island ? pages.islandClientEntryId ?? pages.clientEntryId : pages.clientEntryId)]];
2822
+ if (page.island && pages.mdClientEntryId) routeRootPhases[0].push(virtualDevUrl(pages.mdClientEntryId));
2823
+ const layoutIds = resolveLayoutIds(page, scan.pages.layouts, scan.pages.namedLayouts);
2824
+ const layoutRoots = [];
2825
+ for (const layoutId of layoutIds) {
2826
+ const layout = scan.pages.layouts.find((l) => (l.directory ? `${l.directory}/layout` : "layout") === layoutId);
2827
+ if (layout) {
2828
+ keys.push(`${relPagesDir}/${layout.filePath}`);
2829
+ layoutRoots.push(toDevRootUrl(`${pages.pagesDir}/${layout.filePath}`));
2830
+ continue;
2831
+ }
2832
+ const named = scan.pages.namedLayouts.find((nl) => {
2833
+ return (nl.directory ? `${nl.directory}/_layouts/${nl.name}` : `_layouts/${nl.name}`) === layoutId;
2834
+ });
2835
+ if (named) {
2836
+ keys.push(`${relPagesDir}/${named.filePath}`);
2837
+ layoutRoots.push(toDevRootUrl(`${pages.pagesDir}/${named.filePath}`));
2838
+ }
2839
+ }
2840
+ keys.push(`${relPagesDir}/${page.componentPath}`);
2841
+ routeRootPhases.push(layoutRoots);
2842
+ routeRootPhases.push([toDevRootUrl(`${pages.pagesDir}/${page.componentPath}`)]);
2843
+ pageCssKeys[page.componentId] = keys;
2844
+ pageDevRootPhases[page.componentId] = routeRootPhases.filter((phase) => phase.length > 0);
2845
+ }
2846
+ lines.push("const __loadPagesManifest = __createManifestLoader();");
2847
+ const assetMetadata = {
2848
+ pageCssKeys,
2849
+ devRootPhases: options.dev ? pageDevRootPhases : void 0,
2850
+ dev: options.dev === true
2851
+ };
2852
+ const devCssOption = options.dev ? ", devCss: getVoidPagesDevCss" : "";
2853
+ lines.push(`const getPagesAssetTags = __createClientAssetTags({ ...${JSON.stringify({
2854
+ ...assetMetadata,
2855
+ entryId: pages.clientEntryId,
2856
+ entryUrl: "/@id/__x00__" + pages.clientEntryId,
2857
+ kind: "pages"
2858
+ })}${devCssOption} }, __loadPagesManifest);`);
2859
+ if (hasIslandPages && pages.islandClientEntryId) lines.push(`const getIslandsAssetTags = __createClientAssetTags({ ...${JSON.stringify({
2860
+ ...assetMetadata,
2861
+ entryId: pages.islandClientEntryId,
2862
+ entryUrl: "/@id/__x00__" + pages.islandClientEntryId,
2863
+ extraEntryId: pages.mdClientEntryId,
2864
+ extraEntryUrl: pages.mdClientEntryId ? "/@id/__x00__" + pages.mdClientEntryId : void 0,
2865
+ kind: "islands"
2866
+ })}${devCssOption} }, __loadPagesManifest);`);
2867
+ const seenServerFiles = /* @__PURE__ */ new Set();
2868
+ for (const page of scan.pages.pages) {
2869
+ const safeId = page.componentId.replace(/[^a-zA-Z0-9]/g, "_");
2870
+ let serverImportPath = null;
2871
+ if (page.serverPath) serverImportPath = `${pages.pagesDir}/${page.serverPath}`;
2872
+ if (serverImportPath && !seenServerFiles.has(serverImportPath)) {
2873
+ seenServerFiles.add(serverImportPath);
2874
+ const loaderName = `_pload_${safeId}`;
2875
+ lines.push(`const ${loaderName} = __createModuleLoader(() => import(${JSON.stringify(serverImportPath)}));`);
2876
+ }
2877
+ let mdHeadRef = null;
2878
+ if (!serverImportPath && page.metadata && (page.metadata.title || page.metadata.description)) {
2879
+ const headObj = {};
2880
+ if (page.metadata.title) headObj.title = page.metadata.title;
2881
+ if (page.metadata.description) headObj.meta = [{
2882
+ name: "description",
2883
+ content: page.metadata.description
2884
+ }];
2885
+ const headVarName = `__mdHead_${safeId}`;
2886
+ lines.push(`const ${headVarName} = () => (${JSON.stringify(headObj)});`);
2887
+ mdHeadRef = headVarName;
2888
+ }
2889
+ const renderer = page.renderMode === "island" ? "renderIslandPage" : "renderPage";
2890
+ const assetTags = page.renderMode === "island" && pages.islandClientEntryId ? "getIslandsAssetTags" : "getPagesAssetTags";
2891
+ const routeOptions = `{ ${serverImportPath ? "load: _pload_" + safeId + ", " : ""}${mdHeadRef ? "head: " + mdHeadRef + ", " : ""}componentId: ${JSON.stringify(page.componentId)}, render: ${renderer}, assets: ${assetTags}, headConfig: __voidHeadConfig, renderOptions: ${JSON.stringify({ renderMode: page.renderMode })} }`;
2892
+ lines.push(`app.get(${JSON.stringify(page.pattern)}, __createPageGetHandler(${routeOptions}));`);
2893
+ if (page.methods.includes("action") || page.methods.includes("actions")) lines.push(`app.on(["POST", "PUT", "PATCH", "DELETE"], ${JSON.stringify(page.pattern)}, __createPageMutationHandler({ ...${routeOptions}, namedActions: ${JSON.stringify(page.methods.includes("actions"))} }));`);
2894
+ }
2895
+ lines.push("");
2896
+ }
2897
+ /**
2898
+ * Generate the asset fallthrough helper shared by native Void workers and
2899
+ * Class A framework wrappers. If the app returns 404, check ASSETS binding
2900
+ * for static files and set cache headers. Edge caching of hashed assets
2901
+ * happens at the dispatch layer (see design doc 0042).
2902
+ *
2903
+ * `notFound` is the RESOLVED `assetConfig.not_found_handling` for this build
2904
+ * (`resolveAssetRoutingPolicy`), and it decides what happens once the asset
2905
+ * binding has also answered 404. It is a separate argument from
2906
+ * `serveSpaFallback` because the two are not derived from each other: the
2907
+ * common worker-first app runs with `not_found_handling: 'none'` and STILL
2908
+ * wants the worker-side shell fallback (that is the whole reason the flag
2909
+ * exists). They are mutually exclusive in practice — `resolveAssetRoutingPolicy`
2910
+ * clears `workerSpaFallback` whenever an explicit `routing.notFound` asks for
2911
+ * anything other than the SPA shell.
2912
+ */
2913
+ function compileAssetFallbackHelper(assetsDir = "assets", serveSpaFallback = false, notFound = "none") {
2914
+ return `import { createAssetFallback as __createAssetFallback } from ${JSON.stringify(ROUTING_RUNTIME_PATH)};
2915
+ const serveWithAssets = __createAssetFallback(${JSON.stringify({
2916
+ assetsDir,
2917
+ serveSpaFallback,
2918
+ notFound
2919
+ })});
2920
+ `;
2921
+ }
2922
+ /**
2923
+ * Generate the Worker entry module code.
2924
+ * Re-exports the Hono app from the virtual module with an asset-serving
2925
+ * fallthrough: when the app returns 404, the entry tries env.ASSETS.fetch()
2926
+ * and adds proper Cache-Control headers (immutable for Vite-hashed /assets/*).
2927
+ *
2928
+ * When `hasQueues` is true, also exports a `queue()` handler that
2929
+ * serializes the native MessageBatch and dispatches it to the
2930
+ * `/__void/queue` Hono route. In local dev Miniflare delivers batches
2931
+ * natively to this export; in production the relay worker does it.
2932
+ *
2933
+ * When `remotePath` is provided, wraps the fetch handler to detect
2934
+ * remote mode (`__VOID_REMOTE`) and replace local bindings with
2935
+ * proxy-backed versions.
2936
+ */
2937
+ function deriveEmailRuntimePath(runtimeEnvPath) {
2938
+ return runtimeEnvPath.replace(/_?env(\.[^/]+)?$/, (_m, ext) => "email" + (ext ?? ""));
2939
+ }
2940
+ function compileWorkerEntry(options = {}) {
2941
+ const { hasQueues = false, versionMetadataBinding, remote, assetsDir = "assets", webSocketClasses = [], runtimeEnvPath = "void/_env", runtimeEmailPath, envSchemaModule, sandbox, serveSpaFallback = false, hasEmail = false, live, isr, notFound = "none", durableObjects = [] } = options;
2942
+ const remotePath = remote?.remotePath;
2943
+ const remoteBindingNames = JSON.stringify(remote?.bindingNames);
2944
+ const createRemoteEnvCall = `createRemoteEnv(env${remote?.bindingNames ? `, ${remoteBindingNames}` : ""})`;
2945
+ const hasSandbox = sandbox === true || typeof sandbox === "object";
2946
+ const sandboxClassName = typeof sandbox === "object" ? sandbox.className ?? "SandboxV1" : DEFAULT_SANDBOX_CLASS_NAME;
2947
+ const sandboxBindingName = typeof sandbox === "object" ? sandbox.bindingName ?? "SANDBOX" : "SANDBOX";
2948
+ const sandboxRuntimePath = typeof sandbox === "object" ? sandbox.runtimePath ?? "void/sandbox" : "void/sandbox";
2949
+ const sandboxImport = hasSandbox ? `import { VoidPlatformSandbox as __VoidSandbox, __setSandboxBindingName } from ${JSON.stringify(sandboxRuntimePath)};\n` : "";
2950
+ const sandboxInstanceType = typeof sandbox === "object" ? sandbox.instanceType : void 0;
2951
+ const sandboxExport = hasSandbox ? sandboxClassName === "SandboxV1" && !sandboxInstanceType ? `export { __VoidSandbox as SandboxV1 };\n__setSandboxBindingName(${JSON.stringify(sandboxBindingName)});\n` : `export class ${sandboxClassName} extends __VoidSandbox {${sandboxInstanceType ? ` protected override instanceType = ${JSON.stringify(sandboxInstanceType)} as const; ` : ""}}\n__setSandboxBindingName(${JSON.stringify(sandboxBindingName)});\n` : "";
2952
+ const hasLive = live === true || typeof live === "object";
2953
+ const liveBindingName = typeof live === "object" ? live.bindingName ?? "VOID_LIVE" : "VOID_LIVE";
2954
+ const liveClassName = typeof live === "object" ? live.className ?? "VoidLiveStreamDurableObject" : "VoidLiveStreamDurableObject";
2955
+ const liveRouteImport = hasLive ? ", VoidLiveStreamDurableObject" : "";
2956
+ const liveRuntimeImport = hasLive ? `import { __setLiveBindingName } from "void/live";\n` : "";
2957
+ const liveExport = hasLive ? liveClassName === "VoidLiveStreamDurableObject" ? `export { VoidLiveStreamDurableObject };\n__setLiveBindingName(${JSON.stringify(liveBindingName)});\n` : `export { VoidLiveStreamDurableObject as ${liveClassName} };\n__setLiveBindingName(${JSON.stringify(liveBindingName)});\n` : "";
2958
+ const durableObjectImports = durableObjects.map(({ filePath }, index) => `import __voidDurableObject${index} from ${JSON.stringify(filePath)};`).join("\n");
2959
+ const durableObjectExports = durableObjects.map(({ bindingName, className, name }, index) => `__voidDurableObject${index}.__bind(${JSON.stringify(bindingName)}${name !== void 0 ? `, ${JSON.stringify(name)}` : ""});\nexport const ${className} = __voidDurableObject${index}.class;`).join("\n");
2960
+ const runtimeEnvImport = `import { withRuntimeEnv } from ${JSON.stringify(runtimeEnvPath)};\n`;
2961
+ const remoteImport = remote ? `import { createRemoteEnv } from ${JSON.stringify(remote.remotePath)};\n` : "";
2962
+ const envSchemaImport = envSchemaModule ? `import ${JSON.stringify(envSchemaModule.importPath)};\nimport { _validateEnvOnce } from ${JSON.stringify(envSchemaModule.helperPath)};\n` : "";
2963
+ const envValidateCall = envSchemaModule ? " await _validateEnvOnce(env);\n" : "";
2964
+ const exactNames = isr ? INTERNAL_BINDING_EXACT_NAMES : [];
2965
+ const filterBindingsHelper = `import { filterInternalBindings as __filterBindings, createWorkerFetch as __createWorkerFetch } from ${JSON.stringify(ROUTING_RUNTIME_PATH)};
2966
+ const __filterInternalBindings = (env) => __filterBindings(env, ${JSON.stringify(INTERNAL_BINDING_PREFIXES)}, ${JSON.stringify(exactNames)});
2967
+ `;
2968
+ const assetHelper = compileAssetFallbackHelper(assetsDir, serveSpaFallback, notFound);
2969
+ const isrImport = isr ? `import { handleIsr } from ${JSON.stringify(isr.runtimePath)};\nimport { __VOID_ISR_REVALIDATE, __VOID_ISR_PAGES, __VOID_ISR_PATH_REVALIDATE, __VOID_ISR_DEPLOYMENT_ID } from 'virtual:void-routes';\n` : "";
2970
+ const isrDeploymentStamp = isr ? ` env.__VOID_ISR_DEPLOYMENT_ID ??= __VOID_ISR_DEPLOYMENT_ID;\n` : "";
2971
+ const fetchHandler = `__createWorkerFetch({
2972
+ app, assets: serveWithAssets, withRawEnv: __withRawEnv, withRuntimeEnv,
2973
+ filterBindings: __filterInternalBindings,
2974
+ internalRoute: __voidHandleInternalRoute,
2975
+ collectPrerenderPaths: __voidCollectPrerenderPaths${versionMetadataBinding ? ", versionMetadataBinding: " + JSON.stringify(versionMetadataBinding) : ""}${envSchemaModule ? ", validateEnv: _validateEnvOnce" : ""}${remotePath ? ", remoteEnv: (env) => " + createRemoteEnvCall : ""}${hasEmail ? ", emailReceipt: __handleEmailReceipt" : ""}${isr ? ", isr: { deploymentId: __VOID_ISR_DEPLOYMENT_ID, handle: (request, env, ctx, render) => handleIsr(request, env, ctx, { revalidate: __VOID_ISR_REVALIDATE, pages: __VOID_ISR_PAGES, pathRevalidate: __VOID_ISR_PATH_REVALIDATE, deploymentId: __VOID_ISR_DEPLOYMENT_ID, queryAllowlist: " + JSON.stringify(isr.queryAllowlist) + " }, render) }" : ""}
2976
+ })`;
2977
+ const scheduledHandler = remotePath ? `async function handleScheduled(controller, env, ctx) {
2978
+ ${envValidateCall} if (env.__VOID_REMOTE === "1") env = ${createRemoteEnvCall};
2979
+ ${isrDeploymentStamp} const userEnv = __filterInternalBindings(env);
2980
+ return withRuntimeEnv(env, () => scheduled(controller, userEnv, ctx));
2981
+ }` : `async function handleScheduled(controller, env, ctx) {
2982
+ ${envValidateCall}${isrDeploymentStamp} const userEnv = __filterInternalBindings(env);
2983
+ return withRuntimeEnv(env, () => scheduled(controller, userEnv, ctx));
2984
+ }`;
2985
+ const emailImport = hasEmail ? ", __emailHandler" : "";
2986
+ const emailRuntimePath = runtimeEmailPath ?? deriveEmailRuntimePath(runtimeEnvPath);
2987
+ const emailProxyImport = hasEmail ? `import { createMessageProxy as __createMessageProxy, enactEmailActions as __enactEmailActions, isForwardableEmailMessage as __isForwardableEmailMessage } from ${JSON.stringify(emailRuntimePath)};\nimport { EMAIL_PROTOCOL_VERSION as __EMAIL_PROTOCOL_VERSION, encodeEmailActions as __encodeEmailActions, readEmailEnvelope as __readEmailEnvelope, verifyEmailReceiptThroughProxy as __verifyEmailReceiptThroughProxy } from "void/_email-protocol";\n` : "";
2988
+ const emailHandler = hasEmail ? remotePath ? `async function handleEmail(envelope, env, ctx) {
2989
+ ${envValidateCall} if (env.__VOID_REMOTE === "1") env = ${createRemoteEnvCall};
2990
+ const userEnv = __filterInternalBindings(env);
2991
+ const __relayed = typeof envelope.viaDomain === "string" && envelope.viaDomain.length > 0;
2992
+ const __slug = __relayed ? undefined : (typeof env.__VOID_PROJECT_SLUG === "string" && env.__VOID_PROJECT_SLUG.length > 0 ? env.__VOID_PROJECT_SLUG : undefined);
2993
+ const { message, getActions } = __createMessageProxy(envelope, { slug: __slug });
2994
+ await __withRawEnv(env, () => withRuntimeEnv(env, () => __emailHandler(message, userEnv, ctx, { slug: __slug })));
2995
+ const __actions = getActions();
2996
+ if (__isForwardableEmailMessage(envelope)) await __enactEmailActions(envelope, __actions);
2997
+ return __actions;
2998
+ }` : `async function handleEmail(envelope, env, ctx) {
2999
+ ${envValidateCall} const userEnv = __filterInternalBindings(env);
3000
+ const __relayed = typeof envelope.viaDomain === "string" && envelope.viaDomain.length > 0;
3001
+ const __slug = __relayed ? undefined : (typeof env.__VOID_PROJECT_SLUG === "string" && env.__VOID_PROJECT_SLUG.length > 0 ? env.__VOID_PROJECT_SLUG : undefined);
3002
+ const { message, getActions } = __createMessageProxy(envelope, { slug: __slug });
3003
+ await __withRawEnv(env, () => withRuntimeEnv(env, () => __emailHandler(message, userEnv, ctx, { slug: __slug })));
3004
+ const __actions = getActions();
3005
+ if (__isForwardableEmailMessage(envelope)) await __enactEmailActions(envelope, __actions);
3006
+ return __actions;
3007
+ }` : "";
3008
+ const emailReceiptHandler = hasEmail ? `async function __handleEmailReceipt(request, env, ctx) {
3009
+ if (request.method !== "POST" || request.headers.get("content-encoding")) return new Response(null, { status: 404 });
3010
+ const __token = request.headers.get("x-void-email-receipt");
3011
+ const __recipient = request.headers.get("x-void-email-recipient");
3012
+ if (!__token || __token.length > 1536 || !__recipient || __recipient.length > 320 ||
3013
+ !env.__VOID_PROXY_TOKEN || !env.__PROJECT_ID || !env.__VOID_EMAIL_DEPLOYMENT_ID || !env.__VOID_PROXY) {
3014
+ return new Response(null, { status: 404 });
3015
+ }
3016
+ const __verified = await __verifyEmailReceiptThroughProxy(env.__VOID_PROXY, env.__VOID_PROXY_TOKEN, {
3017
+ receipt: __token, projectId: env.__PROJECT_ID,
3018
+ deploymentId: env.__VOID_EMAIL_DEPLOYMENT_ID, recipient: __recipient,
3019
+ });
3020
+ if (!__verified) return new Response(null, { status: 404 });
3021
+ let __envelope;
3022
+ try { __envelope = await __readEmailEnvelope(request.body); }
3023
+ catch { return new Response(null, { status: 400 }); }
3024
+ if (__envelope.to !== __recipient) return new Response(null, { status: 400 });
3025
+ const __actions = await handleEmail(__envelope, env, ctx);
3026
+ return new Response(__encodeEmailActions(__actions), {
3027
+ headers: { "content-type": "application/vnd.void.email-actions; version=" + __EMAIL_PROTOCOL_VERSION,
3028
+ "cache-control": "no-store" },
3029
+ });
3030
+ }` : "";
3031
+ const entryImport = `import { WorkerEntrypoint } from "cloudflare:workers";\n`;
3032
+ const fetchDeclaration = `const __voidFetch = ${fetchHandler};`;
3033
+ const entryClass = `export default class extends WorkerEntrypoint {
3034
+ async fetch(request) { return __voidFetch(request, this.env, this.ctx); }
3035
+ async scheduled(controller) { return handleScheduled(controller, this.env, this.ctx); }
3036
+ ${hasQueues ? ` async queue(batch) { return __voidQueue(batch, this.env, this.ctx); }\n` : ""}${hasEmail ? ` async email(message) {
3037
+ if (this.env.__VOID_EMAIL_DEPLOYMENT_ID || !__isForwardableEmailMessage(message)) {
3038
+ throw new TypeError("Native email event required");
3039
+ }
3040
+ await handleEmail(message, this.env, this.ctx);
3041
+ }\n` : ""}}`;
3042
+ if (hasQueues) return `${sandboxImport}${entryImport}${liveRuntimeImport}import app, { scheduled, __voidCollectPrerenderPaths, __voidHandleInternalRoute, __withRawEnv, __processQueue${emailImport}${webSocketClasses.length > 0 ? `, ${webSocketClasses.join(", ")}` : ""}${liveRouteImport} } from 'virtual:void-routes';
3043
+ ${durableObjectImports}\n${runtimeEnvImport}${emailProxyImport}${envSchemaImport}${remoteImport}${isrImport}${filterBindingsHelper}${assetHelper}
3044
+ ${fetchDeclaration}
3045
+ ${scheduledHandler}
3046
+ ${emailHandler}
3047
+ ${emailReceiptHandler}
3048
+ async function __voidQueue(batch, env, ctx) {
3049
+ ${envValidateCall}${remotePath ? ` if (env.__VOID_REMOTE === "1") env = ${createRemoteEnvCall};\n` : ""}${isrDeploymentStamp} const userEnv = __filterInternalBindings(env);
3050
+ const messages = [...batch.messages].map(m => ({
3051
+ id: m.id, timestamp: m.timestamp.toISOString(),
3052
+ body: m.body, attempts: m.attempts,
3053
+ }));
3054
+ const payload = { queue: batch.queue, messages };
3055
+ const __qr = await __withRawEnv(env, () => withRuntimeEnv(env, () => __processQueue(payload, userEnv)));
3056
+ if (__qr.decisions) {
3057
+ const __msgMap = new Map();
3058
+ for (const m of batch.messages) __msgMap.set(m.id, m);
3059
+ for (const [id, d] of Object.entries(__qr.decisions)) {
3060
+ const m = __msgMap.get(id);
3061
+ if (!m) continue;
3062
+ if (d.action === "ack") m.ack();
3063
+ else if (d.action === "retry") m.retry(d.delaySeconds != null ? { delaySeconds: d.delaySeconds } : undefined);
3064
+ }
3065
+ }
3066
+ if (!__qr.ok) throw new Error("queue: Handler failed.");
3067
+ }
3068
+
3069
+ ${webSocketClasses.map((name) => `export { ${name} };`).join("\n")}
3070
+ ${liveExport}
3071
+ ${sandboxExport}
3072
+ ${durableObjectExports}
3073
+ ${entryClass}`;
3074
+ return `${sandboxImport}${entryImport}${liveRuntimeImport}import app, { scheduled, __voidCollectPrerenderPaths, __voidHandleInternalRoute, __withRawEnv${emailImport}${webSocketClasses.length > 0 ? `, ${webSocketClasses.join(", ")}` : ""}${liveRouteImport} } from 'virtual:void-routes';
3075
+ ${durableObjectImports}\n${runtimeEnvImport}${emailProxyImport}${envSchemaImport}${remoteImport}${isrImport}${filterBindingsHelper}${assetHelper}
3076
+ ${fetchDeclaration}
3077
+ ${scheduledHandler}
3078
+ ${emailHandler}
3079
+ ${emailReceiptHandler}
3080
+ ${webSocketClasses.map((name) => `export { ${name} };`).join("\n")}
3081
+ ${liveExport}
3082
+ ${sandboxExport}
3083
+ ${durableObjectExports}
3084
+ ${entryClass}`;
3085
+ }
3086
+ /**
3087
+ * Generate the app module — exports the Hono app with static asset serving.
3088
+ * This is the entry imported by `vite preview` and can be used programmatically.
3089
+ *
3090
+ * When `envSchemaModule` is provided, splices boot-time env validation so it
3091
+ * runs on the first request (including the prerender child process spawned
3092
+ * by `prerenderPagesNode`). Mirrors `compileWorkerEntry`: a side-effect import
3093
+ * registers the schema, and `_validateEnvOnce` throws `EnvValidationError` on
3094
+ * bad values. Env source on Node/Bun is `process.env`; on Deno it's
3095
+ * `Deno.env.toObject()`.
3096
+ */
3097
+ function compileNodeApp(target, honoPath, envSchemaModule) {
3098
+ const staticImport = target === "bun" ? `import { serveStatic } from "hono/bun";` : target === "deno" ? `import { serveStatic } from "hono/deno";` : `import { serveStatic } from "@hono/node-server/serve-static";`;
3099
+ const envSchemaImport = envSchemaModule ? `import ${JSON.stringify(envSchemaModule.importPath)};\nimport { _validateEnvOnce } from ${JSON.stringify(envSchemaModule.helperPath)};\n` : "";
3100
+ const envMiddleware = envSchemaModule ? `app.use("*", async (c, next) => { if (c.req.method !== "POST" || c.req.path !== "/__void/prerender-paths") await _validateEnvOnce(${target === "deno" ? "Deno.env.toObject()" : "process.env"}); return next(); });\n` : "";
3101
+ return `${staticImport}
3102
+ import { fileURLToPath } from "node:url";
3103
+ import { Hono } from ${JSON.stringify(honoPath)};
3104
+ import innerApp, { __voidCollectPrerenderPaths } from "virtual:void-routes";
3105
+ ${envSchemaImport}
3106
+ // Resolved by nodeTargetPlugin for this emitted chunk, including shared chunks.
3107
+ const clientRoot = fileURLToPath(import.meta.voidClientDirectory);
3108
+ const assetApp = new Hono();
3109
+ assetApp.get("*", serveStatic({ root: clientRoot }));
3110
+ const assets = { fetch: (request) => assetApp.fetch(request) };
3111
+ const app = new Hono();
3112
+ ${envMiddleware}app.post("/__void/prerender-paths", async (c) => {
3113
+ const __expected = c.env?.__VOID_PROXY_TOKEN;
3114
+ if (__expected) {
3115
+ const __token = c.req.header("x-void-internal");
3116
+ if (!__token || __token !== __expected) return c.json({ error: "unauthorized" }, 401);
3117
+ }
3118
+ const paths = await __voidCollectPrerenderPaths(c.req.raw);
3119
+ return c.json({ paths });
3120
+ });
3121
+ app.get("/assets/*", (c) => assets.fetch(c.req.raw));
3122
+ app.all("*", (c) => innerApp.fetch(c.req.raw, { ...c.env, ASSETS: assets }));
3123
+ export default app;`;
3124
+ }
3125
+ /**
3126
+ * Generate the server entry — imports the app and starts an HTTP server.
3127
+ * This is what users run with `node dist/ssr/index.js`.
3128
+ */
3129
+ function compileNodeServer(target) {
3130
+ if (target === "bun") return `import app from "./app.js";
3131
+
3132
+ const port = Number(process.env.PORT || 3000);
3133
+ Bun.serve({ fetch: app.fetch, port });
3134
+ console.log("Listening on http://localhost:" + port);`;
3135
+ if (target === "deno") return `import app from "./app.js";
3136
+
3137
+ const port = Number(Deno.env.get("PORT") || 3000);
3138
+ Deno.serve({ port }, app.fetch);`;
3139
+ return `import { serve } from "@hono/node-server";
3140
+ import app from "./app.js";
3141
+
3142
+ const port = Number(process.env.PORT || 3000);
3143
+ serve({ fetch: app.fetch, port }, (info) => {
3144
+ console.log("Listening on http://localhost:" + info.port);
3145
+ });`;
3146
+ }
3147
+ /**
3148
+ * Generate the server-side fetch client module for non-CF targets.
3149
+ * Same as compileWorkerClient but without cloudflare:workers dependency.
3150
+ * Calls app.fetch() directly (no HTTP round-trip) with ALS context forwarding.
3151
+ */
3152
+ function compileBrowserClient(authClientPath, fetchPath, fetchStreamPath) {
3153
+ return `export { fetch, FetchError } from ${JSON.stringify(fetchPath)};
3154
+ export { fetchStream, StreamError } from ${JSON.stringify(fetchStreamPath)};
3155
+ export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};`;
3156
+ }
3157
+ function compileNodeClient(authClientPath) {
3158
+ return `import app, { requestContext } from "virtual:void-routes";
3159
+ import { interpolateRouteParams } from ${JSON.stringify(ROUTE_URL_PATH)};
3160
+ export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};
3161
+
3162
+ export class FetchError extends Error {
3163
+ constructor(status, statusText, data) {
3164
+ super(\`fetch: Request failed with status \${status}.\`);
3165
+ this.name = "FetchError";
3166
+ this.status = status;
3167
+ this.statusText = statusText;
3168
+ this.data = data;
3169
+ }
3170
+ }
3171
+
3172
+ export async function fetch(path, options) {
3173
+ let url = interpolateRouteParams(path, options?.params);
3174
+
3175
+ if (options?.query) {
3176
+ const qs = new URLSearchParams(options.query).toString();
3177
+ if (qs) url += "?" + qs;
3178
+ }
3179
+
3180
+ const method = options?.method ?? "GET";
3181
+ const headers = new Headers(options?.headers);
3182
+
3183
+ const store = requestContext.getStore();
3184
+ if (store?.request) {
3185
+ const outerReq = store.request;
3186
+ if (!headers.has("cookie")) {
3187
+ const cookie = outerReq.headers.get("cookie");
3188
+ if (cookie) headers.set("cookie", cookie);
3189
+ }
3190
+ if (!headers.has("authorization")) {
3191
+ const auth = outerReq.headers.get("authorization");
3192
+ if (auth) headers.set("authorization", auth);
3193
+ }
3194
+ }
3195
+
3196
+ if (options?.body != null && !headers.has("content-type")) {
3197
+ headers.set("content-type", "application/json");
3198
+ }
3199
+
3200
+ const request = new Request("http://localhost" + url, {
3201
+ method,
3202
+ headers,
3203
+ body: options?.body != null ? JSON.stringify(options.body) : undefined,
3204
+ });
3205
+
3206
+ const response = await app.fetch(request);
3207
+
3208
+ if (response.status === 204) return undefined;
3209
+
3210
+ const contentType = response.headers.get("content-type") ?? "";
3211
+ const data = contentType.includes("application/json") ? await response.json() : await response.text();
3212
+
3213
+ if (!response.ok) {
3214
+ throw new FetchError(response.status, response.statusText, data);
3215
+ }
3216
+
3217
+ return data;
3218
+ }`;
3219
+ }
3220
+ function stripOriginForProd(rules) {
3221
+ return rules.map(({ origin: _origin, ...rest }) => rest);
3222
+ }
3223
+ function compileRoutingRulesMiddleware(lines, rules, dev, _nodeTarget) {
3224
+ lines.push("// Routing rules: redirects + rewrites (applied before route handlers)");
3225
+ lines.push(`const __routingRules = ${JSON.stringify(dev ? rules : stripOriginForProd(rules))};`);
3226
+ lines.push("app.use(\"*\", __routingRuntime.routingMiddleware(__routingRules));");
3227
+ }
3228
+ function compileHeaderMiddleware(lines, rules) {
3229
+ lines.push("// Header rules (applied to responses)");
3230
+ lines.push(`const __headerRules = ${JSON.stringify(rules)};`);
3231
+ lines.push("app.use(\"*\", __routingRuntime.headerMiddleware(__headerRules));");
3232
+ }
3233
+ function compileWorkerClient(authClientPath) {
3234
+ return `import app, { requestContext } from "virtual:void-routes";
3235
+ import { interpolateRouteParams } from ${JSON.stringify(ROUTE_URL_PATH)};
3236
+ import { env } from "cloudflare:workers";
3237
+ export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};
3238
+
3239
+ export class FetchError extends Error {
3240
+ constructor(status, statusText, data) {
3241
+ super(\`fetch: Request failed with status \${status}.\`);
3242
+ this.name = "FetchError";
3243
+ this.status = status;
3244
+ this.statusText = statusText;
3245
+ this.data = data;
3246
+ }
3247
+ }
3248
+
3249
+ export async function fetch(path, options) {
3250
+ let url = interpolateRouteParams(path, options?.params);
3251
+
3252
+ if (options?.query) {
3253
+ const qs = new URLSearchParams(options.query).toString();
3254
+ if (qs) url += "?" + qs;
3255
+ }
3256
+
3257
+ const method = options?.method ?? "GET";
3258
+ const headers = new Headers(options?.headers);
3259
+
3260
+ // Auto-forward cookie and authorization from the outer request via ALS
3261
+ const store = requestContext.getStore();
3262
+ if (store?.request) {
3263
+ const outerReq = store.request;
3264
+ if (!headers.has("cookie")) {
3265
+ const cookie = outerReq.headers.get("cookie");
3266
+ if (cookie) headers.set("cookie", cookie);
3267
+ }
3268
+ if (!headers.has("authorization")) {
3269
+ const auth = outerReq.headers.get("authorization");
3270
+ if (auth) headers.set("authorization", auth);
3271
+ }
3272
+ }
3273
+
3274
+ if (options?.body != null && !headers.has("content-type")) {
3275
+ headers.set("content-type", "application/json");
3276
+ }
3277
+
3278
+ const request = new Request("http://worker.local" + url, {
3279
+ method,
3280
+ headers,
3281
+ body: options?.body != null ? JSON.stringify(options.body) : undefined,
3282
+ });
3283
+
3284
+ // Use real executionCtx from ALS if available, otherwise provide a no-op fallback
3285
+ const executionCtx = store?.executionCtx ?? { waitUntil() {}, passThroughOnException() {} };
3286
+ const response = await app.fetch(request, env, executionCtx);
3287
+
3288
+ if (response.status === 204) return undefined;
3289
+
3290
+ const contentType = response.headers.get("content-type") ?? "";
3291
+ const data = contentType.includes("application/json") ? await response.json() : await response.text();
3292
+
3293
+ if (!response.ok) {
3294
+ throw new FetchError(response.status, response.statusText, data);
3295
+ }
3296
+
3297
+ return data;
3298
+ }
3299
+ `;
3300
+ }
3301
+ //#endregion
3302
+ //#region src/plugin-framework-entry.ts
3303
+ /**
3304
+ * Resolve the server entry module for a Class A framework. We always wrap
3305
+ * the framework's own entry with a tiny shim that intercepts
3306
+ * `/__void/{scheduled,queue}` in dev so cron and queue handlers run with
3307
+ * the worker's full CF bindings. The interception block is gated on
3308
+ * `import.meta.env.DEV` and the dispatch module is loaded via dynamic
3309
+ * `import()` so the entire dev path (including user cron imports) is
3310
+ * dead-code-eliminated in `vite build`. Production deploys go through
3311
+ * `cli/wrapper.ts` instead.
3312
+ */
3313
+ function getFrameworkEntry(framework, root, devTriggerToken, notFound) {
3314
+ const isBuild = isViteBuildOrPreview();
3315
+ if (!isBuild) writeFrameworkTriggersDispatch(root, devTriggerToken);
3316
+ return generateClassATriggerWrapper(root, framework, getOriginalFrameworkEntry(framework, root), isBuild, void 0, notFound);
3317
+ }
3318
+ /** The framework's own server entry, before our trigger wrapper. */
3319
+ function getOriginalFrameworkEntry(framework, root) {
3320
+ switch (framework) {
3321
+ case "tanstack-start": return "@tanstack/react-start/server-entry";
3322
+ case "react-router": return generateReactRouterEntry(root);
3323
+ case "vinext-app": return "vinext/server/app-router-entry";
3324
+ case "vinext-pages": return generateVinextPagesEntry(root);
3325
+ }
3326
+ }
3327
+ /**
3328
+ * Generate `<root>/.void/<framework>-trigger-wrapper.ts`. This file becomes
3329
+ * `workerConfig.main` so the dev miniflare/workerd evaluates *our* fetch
3330
+ * first, dispatches `/__void/{scheduled,queue}` if it matches, and otherwise
3331
+ * delegates to the framework's original entry.
3332
+ *
3333
+ * We emit two shapes of the wrapper depending on the Vite command:
3334
+ *
3335
+ * - Serve: dispatch is dynamically imported and gets a chance to handle
3336
+ * `/__void/{scheduled,queue}` before falling through to the framework.
3337
+ * - Build: dispatch is omitted entirely. The wrapper just delegates fetch.
3338
+ *
3339
+ * We can't simply gate the dynamic import on `import.meta.env.DEV` and
3340
+ * trust DCE: rolldown emits chunks for dynamic imports during chunk
3341
+ * splitting, *before* constant folding removes the dead branch, so the
3342
+ * dispatch file (and its transitive user-cron/queue imports + dev token)
3343
+ * would still ship in the production bundle.
3344
+ */
3345
+ function generateClassATriggerWrapper(root, framework, original, isBuild, assetsDir = "assets", notFound) {
3346
+ const outDir = join(root, ".void");
3347
+ mkdirSync(outDir, { recursive: true });
3348
+ const wrapperPath = join(outDir, `${framework}-trigger-wrapper.ts`);
3349
+ const body = isBuild ? `// @ts-nocheck — Auto-generated by Void for vite build. Do NOT edit.
3350
+ import original from ${JSON.stringify(original)};
3351
+
3352
+ export default {
3353
+ ...original,
3354
+ async fetch(request, env, ctx) {
3355
+ return serveWithAssets(request, env, await original.fetch(request, env, ctx));
3356
+ },
3357
+ };
3358
+ ${compileAssetFallbackHelper(assetsDir, false, notFound)}
3359
+ ` : `// @ts-nocheck — Auto-generated by Void in dev. Do NOT edit.
3360
+ import original from ${JSON.stringify(original)};
3361
+
3362
+ export default {
3363
+ ...original,
3364
+ async fetch(request, env, ctx) {
3365
+ if (request.method === "POST") {
3366
+ const pathname = new URL(request.url).pathname;
3367
+ if (pathname === "/__void/scheduled" || pathname === "/__void/queue") {
3368
+ const { handleDevTrigger } = await import(${JSON.stringify(frameworkTriggersDispatchPath(root))});
3369
+ const response = await handleDevTrigger(request, env, ctx);
3370
+ if (response) return response;
3371
+ }
3372
+ }
3373
+ return original.fetch(request, env, ctx);
3374
+ },
3375
+ };
3376
+ `;
3377
+ writeFileIfChanged(wrapperPath, body);
3378
+ return wrapperPath;
3379
+ }
3380
+ /** Generate a worker entry for vinext Pages Router projects. */
3381
+ function generateVinextPagesEntry(root) {
3382
+ const outDir = join(root, ".void");
3383
+ mkdirSync(outDir, { recursive: true });
3384
+ const entryPath = join(outDir, "vinext-pages-entry.ts");
3385
+ writeFileIfChanged(entryPath, `// Auto-generated by Void — vinext Pages Router worker entry
3386
+ // @ts-expect-error virtual module resolved by vinext at build time
3387
+ import { renderPage, handleApiRoute } from "virtual:vinext-server-entry";
3388
+
3389
+ export default {
3390
+ async fetch(request: Request): Promise<Response> {
3391
+ const url = new URL(request.url);
3392
+ const pathname = url.pathname;
3393
+ if (pathname.replaceAll("\\\\", "/").startsWith("//")) {
3394
+ return new Response("404 Not Found", { status: 404 });
3395
+ }
3396
+ const urlWithQuery = pathname + url.search;
3397
+ if (pathname.startsWith("/api/") || pathname === "/api") {
3398
+ return await handleApiRoute(request, urlWithQuery);
3399
+ }
3400
+ return await renderPage(request, urlWithQuery, null);
3401
+ },
3402
+ };
3403
+ `);
3404
+ return entryPath;
3405
+ }
3406
+ /** Generate a worker entry for React Router v7 projects. */
3407
+ function generateReactRouterEntry(root) {
3408
+ const outDir = join(root, ".void");
3409
+ mkdirSync(outDir, { recursive: true });
3410
+ const entryPath = join(outDir, "react-router-entry.ts");
3411
+ writeFileIfChanged(entryPath, `// Auto-generated by Void — React Router v7 worker entry
3412
+ import { createRequestHandler } from "react-router";
3413
+
3414
+ const requestHandler = createRequestHandler(
3415
+ // @ts-expect-error virtual module resolved by @react-router/dev at build time
3416
+ () => import("virtual:react-router/server-build"),
3417
+ import.meta.env.MODE,
3418
+ );
3419
+
3420
+ export default {
3421
+ async fetch(request: Request): Promise<Response> {
3422
+ return requestHandler(request);
3423
+ },
3424
+ };
3425
+ `);
3426
+ return entryPath;
3427
+ }
3428
+ //#endregion
3429
+ //#region src/plugin-migration.ts
3430
+ function readDatabaseUrl(root) {
3431
+ return loadLocalEnv(root).DATABASE_URL;
3432
+ }
3433
+ /**
3434
+ * Vite plugin that runs local migrations on dev server startup.
3435
+ * For SQLite/D1: applies migrations to the local miniflare database.
3436
+ * For PostgreSQL/MySQL: connects via DATABASE_URL from .env.
3437
+ */
3438
+ function migrationPlugin(root, dialect, databaseId = "local", persistRoot = join(root, ".void"), auth) {
3439
+ return {
3440
+ name: "void:migrations",
3441
+ apply: "serve",
3442
+ async configureServer(server) {
3443
+ const migrationsDir = readProjectPaths(root).migrationsDir;
3444
+ const hasSqlMigrations = existsSync(migrationsDir);
3445
+ if (!hasSqlMigrations && !auth?.enabled) return;
3446
+ try {
3447
+ if (hasSqlMigrations && dialect !== "sqlite") {
3448
+ const url = readDatabaseUrl(root);
3449
+ if (!url) {
3450
+ const warn = () => {
3451
+ console.log();
3452
+ voidError(`DATABASE_URL not found in .env — skipping ${dialect === "mysql" ? "MySQL" : "PostgreSQL"} migrations.`);
3453
+ };
3454
+ if (server.httpServer) server.httpServer.once("listening", () => setTimeout(warn, 100));
3455
+ else warn();
3456
+ return;
3457
+ }
3458
+ const { collectMigrations } = await import("./collect-DLyFDiKW.mjs");
3459
+ const { validateMigrations } = await import("./validate-Dq_L3s0S.mjs");
3460
+ const allMigrations = collectMigrations(migrationsDir);
3461
+ const appliedNames = dialect === "mysql" ? await import("./runner-mysql-CEG5JozK.mjs").then((runner) => runner.getMySqlAppliedMigrations(url)) : await import("./runner-pg-Bh4AgZdj.mjs").then((runner) => runner.getPgAppliedMigrations(url));
3462
+ const appliedSet = new Set(appliedNames);
3463
+ const pending = allMigrations.filter((migration) => !appliedSet.has(migration.name));
3464
+ if (pending.length > 0) try {
3465
+ validateMigrations(pending);
3466
+ } catch (err) {
3467
+ voidWarn(err instanceof Error ? err.message : String(err));
3468
+ }
3469
+ const applied = dialect === "mysql" ? await import("./runner-mysql-CEG5JozK.mjs").then((runner) => runner.runMySqlMigrations(migrationsDir, url)) : await import("./runner-pg-Bh4AgZdj.mjs").then((runner) => runner.runPgMigrations(migrationsDir, url));
3470
+ if (applied.length > 0) voidLog(`Applied ${applied.length} migration(s): ${applied.join(", ")}`);
3471
+ } else if (hasSqlMigrations) {
3472
+ const { runMigrations, getMiniflareDatabasePath, getAppliedMigrations } = await import("./runner-CXA9Fh8h.mjs");
3473
+ const { collectMigrations } = await import("./collect-DLyFDiKW.mjs");
3474
+ const { validateMigrations } = await import("./validate-Dq_L3s0S.mjs");
3475
+ const dbPath = getMiniflareDatabasePath(persistRoot, databaseId);
3476
+ const allMigrations = collectMigrations(migrationsDir);
3477
+ const appliedRows = await getAppliedMigrations(dbPath);
3478
+ const appliedSet = new Set(appliedRows.map((r) => r.name));
3479
+ const pending = allMigrations.filter((m) => !appliedSet.has(m.name));
3480
+ if (pending.length > 0) try {
3481
+ validateMigrations(pending);
3482
+ } catch (err) {
3483
+ voidWarn(err instanceof Error ? err.message : String(err));
3484
+ }
3485
+ const applied = await runMigrations(migrationsDir, dbPath);
3486
+ if (applied.length > 0) voidLog(`Applied ${applied.length} migration(s): ${applied.join(", ")}`);
3487
+ }
3488
+ if (auth?.enabled) {
3489
+ const { runLocalVoidAuthMigrations } = await import("./node-BkyRWRx8.mjs");
3490
+ await runLocalVoidAuthMigrations(root, dialect, persistRoot, databaseId, auth.configPath);
3491
+ voidLog("Applied Better Auth migrations");
3492
+ }
3493
+ } catch (err) {
3494
+ voidError(`Migration error: ${err}`);
3495
+ }
3496
+ }
3497
+ };
3498
+ }
3499
+ //#endregion
3500
+ //#region src/plugin-worker-environment.ts
3501
+ const CLOUDFLARE_BUILTIN_IMPORT = /^cloudflare:/;
3502
+ const SANDBOX_SDK_IMPORT = "@cloudflare/sandbox";
3503
+ const SANDBOX_SDK_IMPORT_FILTER = /^@cloudflare\/sandbox$/;
3504
+ const DEFAULT_WORKER_ENVIRONMENT_NAME = "void_worker";
3505
+ const DEFAULT_WORKER_OUT_DIR = "dist/ssr";
3506
+ const DEV_ASSET_ROUTING_EXCLUSIONS = [
3507
+ "!/@vite/*",
3508
+ "!/@id/*",
3509
+ "!/@fs/*",
3510
+ "!/@react-refresh",
3511
+ "!/@svelte/*",
3512
+ "!/node_modules/*",
3513
+ "!/__uno.css"
3514
+ ];
3515
+ function resolveSandboxSdkEntry() {
3516
+ return fileURLToPath(import.meta.resolve(SANDBOX_SDK_IMPORT));
3517
+ }
3518
+ function createCloudflareBuiltinsExternalPlugin() {
3519
+ return {
3520
+ name: "void:cloudflare-builtins-external",
3521
+ config(_, env) {
3522
+ if (env.command !== "build") return;
3523
+ return { build: { rollupOptions: { external: [CLOUDFLARE_BUILTIN_IMPORT] } } };
2848
3524
  }
2849
- lines.push("");
2850
- } else {
2851
- lines.push("export async function __voidHandleInternalRoute() { return null; }");
2852
- lines.push("");
2853
- }
2854
- if (options.bindingHandlerPath) {
2855
- lines.push(`import { createBindingHandler as __createBindingHandler } from ${JSON.stringify(options.bindingHandlerPath)};`);
2856
- lines.push("let __voidBindingApp;");
2857
- lines.push("app.all(\"/__void/*\", async (c) => {");
2858
- lines.push(" if (!__voidBindingApp) __voidBindingApp = __createBindingHandler(__getRawEnv());");
2859
- lines.push(" const url = new URL(c.req.url);");
2860
- lines.push(" url.pathname = url.pathname.slice(7);");
2861
- lines.push(" return __voidBindingApp.fetch(new Request(url.toString(), c.req.raw));");
2862
- lines.push("});");
2863
- lines.push("");
2864
- }
2865
- lines.push("const __rawEnvStore = new AsyncLocalStorage();");
2866
- lines.push("export function __withRawEnv(env, fn) { return __rawEnvStore.run(env, fn); }");
2867
- lines.push("function __getRawEnv() { return __rawEnvStore.getStore(); }");
2868
- lines.push(`export const __VOID_BUILD_KEY = ${JSON.stringify(crypto.randomUUID())};`);
2869
- lines.push(`export const __VOID_DEV_TRIGGER_TOKEN = ${JSON.stringify(options.devTriggerToken ?? crypto.randomUUID())};`);
2870
- lines.push(`export const __VOID_ISR_REVALIDATE = ${JSON.stringify(mergedRevalidate)};`);
2871
- lines.push(`export const __VOID_ISR_PAGES = ${JSON.stringify(isrPages)};`);
2872
- lines.push(`export const __VOID_ISR_PATH_REVALIDATE = ${JSON.stringify(isrPathRevalidate)};`);
2873
- lines.push(`export const __VOID_ISR_DEPLOYMENT_ID = ${JSON.stringify(crypto.randomUUID())};`);
2874
- lines.push("");
2875
- lines.push(`export default app;`);
2876
- return lines.join("\n");
3525
+ };
2877
3526
  }
2878
- function compilePrerenderCollector(lines, scan, ssr, options) {
2879
- const staticPaths = scan.pages.pages.filter((page) => page.shouldPrerender && page.params.length === 0 && !page.catchAll).map((page) => page.pattern);
2880
- const dynamicPages = scan.pages.pages.filter((page) => page.shouldPrerender && page.hasGetPrerenderPaths && (page.params.length > 0 || page.catchAll));
2881
- const explicitPaths = options.explicitPrerenderPaths ?? [];
2882
- lines.push(`function __voidBuildPrerenderPath(pattern, params) {`);
2883
- lines.push(` let path = pattern;`);
2884
- lines.push(` for (const [key, value] of Object.entries(params)) {`);
2885
- lines.push(` path = path.replace(":" + key + "{.+}", value);`);
2886
- lines.push(` path = path.replace(":" + key, value);`);
2887
- lines.push(` }`);
2888
- lines.push(` return path;`);
2889
- lines.push(`}`);
2890
- lines.push("");
2891
- lines.push(`export async function __voidCollectPrerenderPaths(request, env, ctx) {`);
2892
- lines.push(` return withRuntimeEnv(env, async () => {`);
2893
- if (options.nodeTarget) lines.push(` return requestContext.run({ request }, async () => {`);
2894
- else lines.push(` return requestContext.run({ request, executionCtx: ctx }, async () => {`);
2895
- lines.push(` const paths = [];`);
2896
- for (const path of staticPaths) lines.push(` paths.push(${JSON.stringify(path)});`);
2897
- for (const page of dynamicPages) {
2898
- const loaderName = `_pload_${page.componentId.replace(/[^a-zA-Z0-9]/g, "_")}`;
2899
- lines.push(` {`);
2900
- lines.push(` const mod = await ${loaderName}();`);
2901
- lines.push(` if (typeof mod.getPrerenderPaths === "function") {`);
2902
- lines.push(` const paramSets = await mod.getPrerenderPaths();`);
2903
- lines.push(` if (Array.isArray(paramSets)) {`);
2904
- lines.push(` for (const params of paramSets) {`);
2905
- lines.push(` paths.push(__voidBuildPrerenderPath(${JSON.stringify(page.pattern)}, params));`);
2906
- lines.push(` }`);
2907
- lines.push(` }`);
2908
- lines.push(` }`);
2909
- lines.push(` }`);
2910
- }
2911
- if (ssr) {
2912
- lines.push(` {`);
2913
- lines.push(` const mod = await import(${JSON.stringify(ssr.serverEntryPath)});`);
2914
- lines.push(` if (typeof mod.getPrerenderPaths === "function") {`);
2915
- lines.push(` const ssrPaths = await mod.getPrerenderPaths();`);
2916
- lines.push(` if (Array.isArray(ssrPaths)) {`);
2917
- lines.push(` for (const path of ssrPaths) {`);
2918
- lines.push(` if (typeof path === "string") paths.push(path);`);
2919
- lines.push(` }`);
2920
- lines.push(` }`);
2921
- lines.push(` }`);
2922
- lines.push(` }`);
2923
- }
2924
- for (const path of explicitPaths) lines.push(` paths.push(${JSON.stringify(path)});`);
2925
- lines.push(` return [...new Set(paths)];`);
2926
- lines.push(` });`);
2927
- lines.push(` });`);
2928
- lines.push(`}`);
2929
- lines.push("");
3527
+ function createDefaultWorkerEnvironmentPlugin() {
3528
+ return {
3529
+ name: "void:default-worker-environment",
3530
+ config(config) {
3531
+ const currentEnvironment = config.environments?.[DEFAULT_WORKER_ENVIRONMENT_NAME];
3532
+ return { environments: { [DEFAULT_WORKER_ENVIRONMENT_NAME]: {
3533
+ ...currentEnvironment,
3534
+ build: {
3535
+ ...currentEnvironment?.build,
3536
+ outDir: currentEnvironment?.build?.outDir ?? DEFAULT_WORKER_OUT_DIR
3537
+ }
3538
+ } } };
3539
+ }
3540
+ };
2930
3541
  }
2931
- function compilePageRoutes(lines, scan, pages, options) {
2932
- lines.push(`import * as __voidPagesRenderer from "virtual:void-pages-renderer";`);
2933
- if (options.dev) lines.push(`import { getVoidPagesDevCss } from "virtual:void-pages-dev-assets";`);
2934
- const hasIslandPages = scan.pages.pages.some((p) => p.renderMode === "island");
2935
- if (hasIslandPages) lines.push(`import { renderIslandPage } from "virtual:void-islands-renderer";`);
2936
- lines.push("");
2937
- lines.push(`const renderPage = __voidPagesRenderer.renderPage;`);
2938
- lines.push(`if (typeof __voidPagesRenderer.renderPreambleScript === "string") {`);
2939
- lines.push(` renderPage.preambleScript = __voidPagesRenderer.renderPreambleScript;`);
2940
- lines.push(`}`);
2941
- lines.push("");
2942
- lines.push(`const __voidHeadConfig = ${JSON.stringify(pages.headConfig ?? null)};`);
2943
- lines.push("");
2944
- pushDefaultErrorPageHelpers(lines, options);
2945
- const relPagesDir = pages.pagesDirRelative;
2946
- const pageCssKeys = {};
2947
- const pageDevRootPhases = {};
2948
- const projectRoot = options.projectRoot;
2949
- const toDevRootUrl = (absolutePath) => "/" + absolutePath.replaceAll("\\", "/").slice(projectRoot.replaceAll("\\", "/").length + 1);
2950
- const virtualDevUrl = (id) => "/@id/__x00__" + id;
2951
- for (const page of scan.pages.pages) {
2952
- const keys = [];
2953
- const routeRootPhases = [[virtualDevUrl(page.island ? pages.islandClientEntryId ?? pages.clientEntryId : pages.clientEntryId)]];
2954
- if (page.island && pages.mdClientEntryId) routeRootPhases[0].push(virtualDevUrl(pages.mdClientEntryId));
2955
- const layoutIds = resolveLayoutIds(page, scan.pages.layouts, scan.pages.namedLayouts);
2956
- const layoutRoots = [];
2957
- for (const layoutId of layoutIds) {
2958
- const layout = scan.pages.layouts.find((l) => (l.directory ? `${l.directory}/layout` : "layout") === layoutId);
2959
- if (layout) {
2960
- keys.push(`${relPagesDir}/${layout.filePath}`);
2961
- layoutRoots.push(toDevRootUrl(`${pages.pagesDir}/${layout.filePath}`));
2962
- continue;
3542
+ function createWorkerEnvironmentSanitizerPlugin(environmentNames) {
3543
+ const names = new Set(environmentNames);
3544
+ return {
3545
+ name: "void:worker-environment-sanitizer",
3546
+ enforce: "pre",
3547
+ configResolved(config) {
3548
+ for (const name of names) {
3549
+ const resolve = config.environments[name]?.resolve;
3550
+ if (resolve && "external" in resolve) resolve.external = [];
2963
3551
  }
2964
- const named = scan.pages.namedLayouts.find((nl) => {
2965
- return (nl.directory ? `${nl.directory}/_layouts/${nl.name}` : `_layouts/${nl.name}`) === layoutId;
2966
- });
2967
- if (named) {
2968
- keys.push(`${relPagesDir}/${named.filePath}`);
2969
- layoutRoots.push(toDevRootUrl(`${pages.pagesDir}/${named.filePath}`));
3552
+ }
3553
+ };
3554
+ }
3555
+ function createSandboxSdkResolvePlugin() {
3556
+ let resolved;
3557
+ return {
3558
+ name: "void:sandbox-sdk-resolve",
3559
+ enforce: "pre",
3560
+ resolveId: {
3561
+ filter: { id: SANDBOX_SDK_IMPORT_FILTER },
3562
+ handler(id) {
3563
+ if (id !== SANDBOX_SDK_IMPORT) return;
3564
+ resolved ??= resolveSandboxSdkEntry();
3565
+ return resolved;
2970
3566
  }
2971
3567
  }
2972
- keys.push(`${relPagesDir}/${page.componentPath}`);
2973
- routeRootPhases.push(layoutRoots);
2974
- routeRootPhases.push([toDevRootUrl(`${pages.pagesDir}/${page.componentPath}`)]);
2975
- pageCssKeys[page.componentId] = keys;
2976
- pageDevRootPhases[page.componentId] = routeRootPhases.filter((phase) => phase.length > 0);
2977
- }
2978
- lines.push("const __loadPagesManifest = __createManifestLoader();");
2979
- const assetMetadata = {
2980
- pageCssKeys,
2981
- devRootPhases: options.dev ? pageDevRootPhases : void 0,
2982
- dev: options.dev === true
2983
3568
  };
2984
- const devCssOption = options.dev ? ", devCss: getVoidPagesDevCss" : "";
2985
- lines.push(`const getPagesAssetTags = __createClientAssetTags({ ...${JSON.stringify({
2986
- ...assetMetadata,
2987
- entryId: pages.clientEntryId,
2988
- entryUrl: "/@id/__x00__" + pages.clientEntryId,
2989
- kind: "pages"
2990
- })}${devCssOption} }, __loadPagesManifest);`);
2991
- if (hasIslandPages && pages.islandClientEntryId) lines.push(`const getIslandsAssetTags = __createClientAssetTags({ ...${JSON.stringify({
2992
- ...assetMetadata,
2993
- entryId: pages.islandClientEntryId,
2994
- entryUrl: "/@id/__x00__" + pages.islandClientEntryId,
2995
- extraEntryId: pages.mdClientEntryId,
2996
- extraEntryUrl: pages.mdClientEntryId ? "/@id/__x00__" + pages.mdClientEntryId : void 0,
2997
- kind: "islands"
2998
- })}${devCssOption} }, __loadPagesManifest);`);
2999
- const seenServerFiles = /* @__PURE__ */ new Set();
3000
- for (const page of scan.pages.pages) {
3001
- const safeId = page.componentId.replace(/[^a-zA-Z0-9]/g, "_");
3002
- let serverImportPath = null;
3003
- if (page.serverPath) serverImportPath = `${pages.pagesDir}/${page.serverPath}`;
3004
- if (serverImportPath && !seenServerFiles.has(serverImportPath)) {
3005
- seenServerFiles.add(serverImportPath);
3006
- const loaderName = `_pload_${safeId}`;
3007
- lines.push(`const ${loaderName} = __createModuleLoader(() => import(${JSON.stringify(serverImportPath)}));`);
3008
- }
3009
- let mdHeadRef = null;
3010
- if (!serverImportPath && page.metadata && (page.metadata.title || page.metadata.description)) {
3011
- const headObj = {};
3012
- if (page.metadata.title) headObj.title = page.metadata.title;
3013
- if (page.metadata.description) headObj.meta = [{
3014
- name: "description",
3015
- content: page.metadata.description
3016
- }];
3017
- const headVarName = `__mdHead_${safeId}`;
3018
- lines.push(`const ${headVarName} = () => (${JSON.stringify(headObj)});`);
3019
- mdHeadRef = headVarName;
3020
- }
3021
- const renderer = page.renderMode === "island" ? "renderIslandPage" : "renderPage";
3022
- const assetTags = page.renderMode === "island" && pages.islandClientEntryId ? "getIslandsAssetTags" : "getPagesAssetTags";
3023
- const routeOptions = `{ ${serverImportPath ? "load: _pload_" + safeId + ", " : ""}${mdHeadRef ? "head: " + mdHeadRef + ", " : ""}componentId: ${JSON.stringify(page.componentId)}, render: ${renderer}, assets: ${assetTags}, headConfig: __voidHeadConfig, renderOptions: ${JSON.stringify({ renderMode: page.renderMode })} }`;
3024
- lines.push(`app.get(${JSON.stringify(page.pattern)}, __createPageGetHandler(${routeOptions}));`);
3025
- if (page.methods.includes("action") || page.methods.includes("actions")) lines.push(`app.on(["POST", "PUT", "PATCH", "DELETE"], ${JSON.stringify(page.pattern)}, __createPageMutationHandler({ ...${routeOptions}, namedActions: ${JSON.stringify(page.methods.includes("actions"))} }));`);
3026
- }
3027
- lines.push("");
3028
3569
  }
3029
- /**
3030
- * Generate the asset fallthrough helper shared by native Void workers and
3031
- * Class A framework wrappers. If the app returns 404, check ASSETS binding
3032
- * for static files and set cache headers. Edge caching of hashed assets
3033
- * happens at the dispatch layer (see design doc 0042).
3034
- *
3035
- * `notFound` is the RESOLVED `assetConfig.not_found_handling` for this build
3036
- * (`resolveAssetRoutingPolicy`), and it decides what happens once the asset
3037
- * binding has also answered 404. It is a separate argument from
3038
- * `serveSpaFallback` because the two are not derived from each other: the
3039
- * common worker-first app runs with `not_found_handling: 'none'` and STILL
3040
- * wants the worker-side shell fallback (that is the whole reason the flag
3041
- * exists). They are mutually exclusive in practice — `resolveAssetRoutingPolicy`
3042
- * clears `workerSpaFallback` whenever an explicit `routing.notFound` asks for
3043
- * anything other than the SPA shell.
3044
- */
3045
- function compileAssetFallbackHelper(assetsDir = "assets", serveSpaFallback = false, notFound = "none") {
3046
- return `import { createAssetFallback as __createAssetFallback } from ${JSON.stringify(ROUTING_RUNTIME_PATH)};
3047
- const serveWithAssets = __createAssetFallback(${JSON.stringify({
3048
- assetsDir,
3049
- serveSpaFallback,
3050
- notFound
3051
- })});
3052
- `;
3570
+ function createWranglerCompatPersistPlugin(root, config) {
3571
+ let persisted = false;
3572
+ const persist = () => {
3573
+ if (persisted || isNodeTarget(config.target)) return;
3574
+ persisted = true;
3575
+ ensureWranglerCompat(root, config);
3576
+ };
3577
+ return {
3578
+ name: "void:wrangler-compat-persist",
3579
+ buildStart() {
3580
+ persist();
3581
+ },
3582
+ configureServer() {
3583
+ persist();
3584
+ }
3585
+ };
3053
3586
  }
3054
- /**
3055
- * Generate the Worker entry module code.
3056
- * Re-exports the Hono app from the virtual module with an asset-serving
3057
- * fallthrough: when the app returns 404, the entry tries env.ASSETS.fetch()
3058
- * and adds proper Cache-Control headers (immutable for Vite-hashed /assets/*).
3059
- *
3060
- * When `hasQueues` is true, also exports a `queue()` handler that
3061
- * serializes the native MessageBatch and dispatches it to the
3062
- * `/__void/queue` Hono route. In local dev Miniflare delivers batches
3063
- * natively to this export; in production the relay worker does it.
3064
- *
3065
- * When `remotePath` is provided, wraps the fetch handler to detect
3066
- * remote mode (`__VOID_REMOTE`) and replace local bindings with
3067
- * proxy-backed versions.
3068
- */
3069
- function deriveEmailRuntimePath(runtimeEnvPath) {
3070
- return runtimeEnvPath.replace(/_?env(\.[^/]+)?$/, (_m, ext) => "email" + (ext ?? ""));
3587
+ function createServerRuntimeOptimizerPlugin() {
3588
+ return {
3589
+ name: "void:server-runtime-optimizer",
3590
+ configEnvironment(name) {
3591
+ if (name !== "client") return { optimizeDeps: { exclude: ["void"] } };
3592
+ }
3593
+ };
3071
3594
  }
3072
- function compileWorkerEntry(options = {}) {
3073
- const { hasQueues = false, versionMetadataBinding, remote, assetsDir = "assets", webSocketClasses = [], runtimeEnvPath = "void/_env", runtimeEmailPath, envSchemaModule, sandbox, serveSpaFallback = false, hasEmail = false, live, isr, notFound = "none", durableObjects = [] } = options;
3074
- const remotePath = remote?.remotePath;
3075
- const remoteBindingNames = JSON.stringify(remote?.bindingNames);
3076
- const createRemoteEnvCall = `createRemoteEnv(env${remote?.bindingNames ? `, ${remoteBindingNames}` : ""})`;
3077
- const hasSandbox = sandbox === true || typeof sandbox === "object";
3078
- const sandboxClassName = typeof sandbox === "object" ? sandbox.className ?? "SandboxV1" : DEFAULT_SANDBOX_CLASS_NAME;
3079
- const sandboxBindingName = typeof sandbox === "object" ? sandbox.bindingName ?? "SANDBOX" : "SANDBOX";
3080
- const sandboxRuntimePath = typeof sandbox === "object" ? sandbox.runtimePath ?? "void/sandbox" : "void/sandbox";
3081
- const sandboxImport = hasSandbox ? `import { VoidPlatformSandbox as __VoidSandbox, __setSandboxBindingName } from ${JSON.stringify(sandboxRuntimePath)};\n` : "";
3082
- const sandboxInstanceType = typeof sandbox === "object" ? sandbox.instanceType : void 0;
3083
- const sandboxExport = hasSandbox ? sandboxClassName === "SandboxV1" && !sandboxInstanceType ? `export { __VoidSandbox as SandboxV1 };\n__setSandboxBindingName(${JSON.stringify(sandboxBindingName)});\n` : `export class ${sandboxClassName} extends __VoidSandbox {${sandboxInstanceType ? ` protected override instanceType = ${JSON.stringify(sandboxInstanceType)} as const; ` : ""}}\n__setSandboxBindingName(${JSON.stringify(sandboxBindingName)});\n` : "";
3084
- const hasLive = live === true || typeof live === "object";
3085
- const liveBindingName = typeof live === "object" ? live.bindingName ?? "VOID_LIVE" : "VOID_LIVE";
3086
- const liveClassName = typeof live === "object" ? live.className ?? "VoidLiveStreamDurableObject" : "VoidLiveStreamDurableObject";
3087
- const liveRouteImport = hasLive ? ", VoidLiveStreamDurableObject" : "";
3088
- const liveRuntimeImport = hasLive ? `import { __setLiveBindingName } from "void/live";\n` : "";
3089
- const liveExport = hasLive ? liveClassName === "VoidLiveStreamDurableObject" ? `export { VoidLiveStreamDurableObject };\n__setLiveBindingName(${JSON.stringify(liveBindingName)});\n` : `export { VoidLiveStreamDurableObject as ${liveClassName} };\n__setLiveBindingName(${JSON.stringify(liveBindingName)});\n` : "";
3090
- const durableObjectImports = durableObjects.map(({ filePath }, index) => `import __voidDurableObject${index} from ${JSON.stringify(filePath)};`).join("\n");
3091
- const durableObjectExports = durableObjects.map(({ bindingName, className, name }, index) => `__voidDurableObject${index}.__bind(${JSON.stringify(bindingName)}${name !== void 0 ? `, ${JSON.stringify(name)}` : ""});\nexport const ${className} = __voidDurableObject${index}.class;`).join("\n");
3092
- const runtimeEnvImport = `import { withRuntimeEnv } from ${JSON.stringify(runtimeEnvPath)};\n`;
3093
- const remoteImport = remote ? `import { createRemoteEnv } from ${JSON.stringify(remote.remotePath)};\n` : "";
3094
- const envSchemaImport = envSchemaModule ? `import ${JSON.stringify(envSchemaModule.importPath)};\nimport { _validateEnvOnce } from ${JSON.stringify(envSchemaModule.helperPath)};\n` : "";
3095
- const envValidateCall = envSchemaModule ? " await _validateEnvOnce(env);\n" : "";
3096
- const exactNames = isr ? INTERNAL_BINDING_EXACT_NAMES : [];
3097
- const filterBindingsHelper = `import { filterInternalBindings as __filterBindings, createWorkerFetch as __createWorkerFetch } from ${JSON.stringify(ROUTING_RUNTIME_PATH)};
3098
- const __filterInternalBindings = (env) => __filterBindings(env, ${JSON.stringify(INTERNAL_BINDING_PREFIXES)}, ${JSON.stringify(exactNames)});
3099
- `;
3100
- const assetHelper = compileAssetFallbackHelper(assetsDir, serveSpaFallback, notFound);
3101
- const isrImport = isr ? `import { handleIsr } from ${JSON.stringify(isr.runtimePath)};\nimport { __VOID_ISR_REVALIDATE, __VOID_ISR_PAGES, __VOID_ISR_PATH_REVALIDATE, __VOID_ISR_DEPLOYMENT_ID } from 'virtual:void-routes';\n` : "";
3102
- const isrDeploymentStamp = isr ? ` env.__VOID_ISR_DEPLOYMENT_ID ??= __VOID_ISR_DEPLOYMENT_ID;\n` : "";
3103
- const fetchHandler = `__createWorkerFetch({
3104
- app, assets: serveWithAssets, withRawEnv: __withRawEnv, withRuntimeEnv,
3105
- filterBindings: __filterInternalBindings,
3106
- internalRoute: __voidHandleInternalRoute,
3107
- collectPrerenderPaths: __voidCollectPrerenderPaths${versionMetadataBinding ? ", versionMetadataBinding: " + JSON.stringify(versionMetadataBinding) : ""}${envSchemaModule ? ", validateEnv: _validateEnvOnce" : ""}${remotePath ? ", remoteEnv: (env) => " + createRemoteEnvCall : ""}${hasEmail ? ", emailReceipt: __handleEmailReceipt" : ""}${isr ? ", isr: { deploymentId: __VOID_ISR_DEPLOYMENT_ID, handle: (request, env, ctx, render) => handleIsr(request, env, ctx, { revalidate: __VOID_ISR_REVALIDATE, pages: __VOID_ISR_PAGES, pathRevalidate: __VOID_ISR_PATH_REVALIDATE, deploymentId: __VOID_ISR_DEPLOYMENT_ID, queryAllowlist: " + JSON.stringify(isr.queryAllowlist) + " }, render) }" : ""}
3108
- })`;
3109
- const scheduledHandler = remotePath ? `async function handleScheduled(controller, env, ctx) {
3110
- ${envValidateCall} if (env.__VOID_REMOTE === "1") env = ${createRemoteEnvCall};
3111
- ${isrDeploymentStamp} const userEnv = __filterInternalBindings(env);
3112
- return withRuntimeEnv(env, () => scheduled(controller, userEnv, ctx));
3113
- }` : `async function handleScheduled(controller, env, ctx) {
3114
- ${envValidateCall}${isrDeploymentStamp} const userEnv = __filterInternalBindings(env);
3115
- return withRuntimeEnv(env, () => scheduled(controller, userEnv, ctx));
3116
- }`;
3117
- const emailImport = hasEmail ? ", __emailHandler" : "";
3118
- const emailRuntimePath = runtimeEmailPath ?? deriveEmailRuntimePath(runtimeEnvPath);
3119
- const emailProxyImport = hasEmail ? `import { createMessageProxy as __createMessageProxy, enactEmailActions as __enactEmailActions, isForwardableEmailMessage as __isForwardableEmailMessage } from ${JSON.stringify(emailRuntimePath)};\nimport { EMAIL_PROTOCOL_VERSION as __EMAIL_PROTOCOL_VERSION, encodeEmailActions as __encodeEmailActions, readEmailEnvelope as __readEmailEnvelope, verifyEmailReceiptThroughProxy as __verifyEmailReceiptThroughProxy } from "void/_email-protocol";\n` : "";
3120
- const emailHandler = hasEmail ? remotePath ? `async function handleEmail(envelope, env, ctx) {
3121
- ${envValidateCall} if (env.__VOID_REMOTE === "1") env = ${createRemoteEnvCall};
3122
- const userEnv = __filterInternalBindings(env);
3123
- const __relayed = typeof envelope.viaDomain === "string" && envelope.viaDomain.length > 0;
3124
- const __slug = __relayed ? undefined : (typeof env.__VOID_PROJECT_SLUG === "string" && env.__VOID_PROJECT_SLUG.length > 0 ? env.__VOID_PROJECT_SLUG : undefined);
3125
- const { message, getActions } = __createMessageProxy(envelope, { slug: __slug });
3126
- await __withRawEnv(env, () => withRuntimeEnv(env, () => __emailHandler(message, userEnv, ctx, { slug: __slug })));
3127
- const __actions = getActions();
3128
- if (__isForwardableEmailMessage(envelope)) await __enactEmailActions(envelope, __actions);
3129
- return __actions;
3130
- }` : `async function handleEmail(envelope, env, ctx) {
3131
- ${envValidateCall} const userEnv = __filterInternalBindings(env);
3132
- const __relayed = typeof envelope.viaDomain === "string" && envelope.viaDomain.length > 0;
3133
- const __slug = __relayed ? undefined : (typeof env.__VOID_PROJECT_SLUG === "string" && env.__VOID_PROJECT_SLUG.length > 0 ? env.__VOID_PROJECT_SLUG : undefined);
3134
- const { message, getActions } = __createMessageProxy(envelope, { slug: __slug });
3135
- await __withRawEnv(env, () => withRuntimeEnv(env, () => __emailHandler(message, userEnv, ctx, { slug: __slug })));
3136
- const __actions = getActions();
3137
- if (__isForwardableEmailMessage(envelope)) await __enactEmailActions(envelope, __actions);
3138
- return __actions;
3139
- }` : "";
3140
- const emailReceiptHandler = hasEmail ? `async function __handleEmailReceipt(request, env, ctx) {
3141
- if (request.method !== "POST" || request.headers.get("content-encoding")) return new Response(null, { status: 404 });
3142
- const __token = request.headers.get("x-void-email-receipt");
3143
- const __recipient = request.headers.get("x-void-email-recipient");
3144
- if (!__token || __token.length > 1536 || !__recipient || __recipient.length > 320 ||
3145
- !env.__VOID_PROXY_TOKEN || !env.__PROJECT_ID || !env.__VOID_EMAIL_DEPLOYMENT_ID || !env.__VOID_PROXY) {
3146
- return new Response(null, { status: 404 });
3147
- }
3148
- const __verified = await __verifyEmailReceiptThroughProxy(env.__VOID_PROXY, env.__VOID_PROXY_TOKEN, {
3149
- receipt: __token, projectId: env.__PROJECT_ID,
3150
- deploymentId: env.__VOID_EMAIL_DEPLOYMENT_ID, recipient: __recipient,
3151
- });
3152
- if (!__verified) return new Response(null, { status: 404 });
3153
- let __envelope;
3154
- try { __envelope = await __readEmailEnvelope(request.body); }
3155
- catch { return new Response(null, { status: 400 }); }
3156
- if (__envelope.to !== __recipient) return new Response(null, { status: 400 });
3157
- const __actions = await handleEmail(__envelope, env, ctx);
3158
- return new Response(__encodeEmailActions(__actions), {
3159
- headers: { "content-type": "application/vnd.void.email-actions; version=" + __EMAIL_PROTOCOL_VERSION,
3160
- "cache-control": "no-store" },
3161
- });
3162
- }` : "";
3163
- const entryImport = `import { WorkerEntrypoint } from "cloudflare:workers";\n`;
3164
- const fetchDeclaration = `const __voidFetch = ${fetchHandler};`;
3165
- const entryClass = `export default class extends WorkerEntrypoint {
3166
- async fetch(request) { return __voidFetch(request, this.env, this.ctx); }
3167
- async scheduled(controller) { return handleScheduled(controller, this.env, this.ctx); }
3168
- ${hasQueues ? ` async queue(batch) { return __voidQueue(batch, this.env, this.ctx); }\n` : ""}${hasEmail ? ` async email(message) {
3169
- if (this.env.__VOID_EMAIL_DEPLOYMENT_ID || !__isForwardableEmailMessage(message)) {
3170
- throw new TypeError("Native email event required");
3171
- }
3172
- await handleEmail(message, this.env, this.ctx);
3173
- }\n` : ""}}`;
3174
- if (hasQueues) return `${sandboxImport}${entryImport}${liveRuntimeImport}import app, { scheduled, __voidCollectPrerenderPaths, __voidHandleInternalRoute, __withRawEnv, __processQueue${emailImport}${webSocketClasses.length > 0 ? `, ${webSocketClasses.join(", ")}` : ""}${liveRouteImport} } from 'virtual:void-routes';
3175
- ${durableObjectImports}\n${runtimeEnvImport}${emailProxyImport}${envSchemaImport}${remoteImport}${isrImport}${filterBindingsHelper}${assetHelper}
3176
- ${fetchDeclaration}
3177
- ${scheduledHandler}
3178
- ${emailHandler}
3179
- ${emailReceiptHandler}
3180
- async function __voidQueue(batch, env, ctx) {
3181
- ${envValidateCall}${remotePath ? ` if (env.__VOID_REMOTE === "1") env = ${createRemoteEnvCall};\n` : ""}${isrDeploymentStamp} const userEnv = __filterInternalBindings(env);
3182
- const messages = [...batch.messages].map(m => ({
3183
- id: m.id, timestamp: m.timestamp.toISOString(),
3184
- body: m.body, attempts: m.attempts,
3185
- }));
3186
- const payload = { queue: batch.queue, messages };
3187
- const __qr = await __withRawEnv(env, () => withRuntimeEnv(env, () => __processQueue(payload, userEnv)));
3188
- if (__qr.decisions) {
3189
- const __msgMap = new Map();
3190
- for (const m of batch.messages) __msgMap.set(m.id, m);
3191
- for (const [id, d] of Object.entries(__qr.decisions)) {
3192
- const m = __msgMap.get(id);
3193
- if (!m) continue;
3194
- if (d.action === "ack") m.ack();
3195
- else if (d.action === "retry") m.retry(d.delaySeconds != null ? { delaySeconds: d.delaySeconds } : undefined);
3196
- }
3197
- }
3198
- if (!__qr.ok) throw new Error("queue: Handler failed.");
3595
+ //#endregion
3596
+ //#region src/sveltekit-hooks-transform.ts
3597
+ /** Add Void's dev bindings to SvelteKit's generated get_hooks function. */
3598
+ function injectSvelteKitHooks(code, id, dispatchPath) {
3599
+ if (!code.includes("get_hooks")) return;
3600
+ const hooks = (parseSync(id, code).program.body ?? []).filter((node) => node.type === "ExportNamedDeclaration").map((node) => node.declaration).filter((node) => {
3601
+ const name = node?.id;
3602
+ return node?.type === "FunctionDeclaration" && isIdentifier(name) && name.name === "get_hooks";
3603
+ });
3604
+ if (hooks.length !== 1) throw new Error("sveltekit: Expected one generated get_hooks function.");
3605
+ const body = hooks[0].body;
3606
+ const returns = (body?.body)?.filter((node) => node.type === "ReturnStatement" && node.argument?.type === "ObjectExpression") ?? [];
3607
+ if (body?.type !== "BlockStatement" || body.start === void 0 || returns.length !== 1 || returns[0]?.start === void 0) throw new Error("sveltekit: Expected one object return in generated get_hooks.");
3608
+ const ms = new MagicString(code);
3609
+ ms.appendLeft(body.start + 1, `\n const { withRuntimeEnv } = await import("void/_env");\n const { handleDevTrigger } = await import(${JSON.stringify(dispatchPath)});`);
3610
+ ms.appendLeft(returns[0].start, "const __origHandle = handle ?? (({ event, resolve }) => resolve(event));\n handle = async ({ event, resolve }) => {\n const __voidEnv = event.platform?.env ?? (await import('cloudflare:workers')).env;\n return withRuntimeEnv(__voidEnv, async () => {\n const __triggerResp = await handleDevTrigger(event.request, __voidEnv, undefined);\n if (__triggerResp) return __triggerResp;\n return __origHandle({ event, resolve });\n });\n };\n ");
3611
+ return {
3612
+ code: ms.toString(),
3613
+ map: ms.generateMap({ hires: true })
3614
+ };
3199
3615
  }
3200
-
3201
- ${webSocketClasses.map((name) => `export { ${name} };`).join("\n")}
3202
- ${liveExport}
3203
- ${sandboxExport}
3204
- ${durableObjectExports}
3205
- ${entryClass}`;
3206
- return `${sandboxImport}${entryImport}${liveRuntimeImport}import app, { scheduled, __voidCollectPrerenderPaths, __voidHandleInternalRoute, __withRawEnv${emailImport}${webSocketClasses.length > 0 ? `, ${webSocketClasses.join(", ")}` : ""}${liveRouteImport} } from 'virtual:void-routes';
3207
- ${durableObjectImports}\n${runtimeEnvImport}${emailProxyImport}${envSchemaImport}${remoteImport}${isrImport}${filterBindingsHelper}${assetHelper}
3208
- ${fetchDeclaration}
3209
- ${scheduledHandler}
3210
- ${emailHandler}
3211
- ${emailReceiptHandler}
3212
- ${webSocketClasses.map((name) => `export { ${name} };`).join("\n")}
3213
- ${liveExport}
3214
- ${sandboxExport}
3215
- ${durableObjectExports}
3216
- ${entryClass}`;
3616
+ //#endregion
3617
+ //#region src/plugin-framework.ts
3618
+ function createFrameworkPlugins(input) {
3619
+ const { localD1, root, directCloudflareDestination, bindings, config, dialect, application, bindingNames, websocketRoutes, sandboxConfig, liveConfig, detected, clientStubsPlugin, serverRuntimeOptimizerPlugin, authEnabled, devTriggerToken, implicitDeployGuard } = input;
3620
+ const frameworkPersistRoot = localD1.persistRoot;
3621
+ const syncFrameworkCompat = (compat) => {
3622
+ if (ensureWranglerCompatibilityDate(root, compat.compatibilityDate)) voidLog("Synced compatibility_date to Cloudflare config");
3623
+ };
3624
+ const wranglerCompatPlugin = {
3625
+ name: "void:wrangler-compat",
3626
+ enforce: "pre",
3627
+ config(_, env) {
3628
+ if (env.command === "serve" && directCloudflareDestination && bindings.needsAI) process.env.CLOUDFLARE_AUTH_USE_KEYRING = "true";
3629
+ syncFrameworkCompat(ensureWranglerCompat(root, config));
3630
+ if (env.command !== "serve") return;
3631
+ if (ensureWranglerNodejsAlsFlag(root)) voidLog("Synced nodejs_als to Cloudflare config");
3632
+ },
3633
+ buildStart() {
3634
+ syncFrameworkCompat(ensureWranglerCompat(root, config));
3635
+ },
3636
+ configureServer() {
3637
+ syncFrameworkCompat(ensureWranglerCompat(root, config));
3638
+ }
3639
+ };
3640
+ const syncFrameworkBindings = (command) => {
3641
+ if (command !== "serve") return;
3642
+ if (syncWranglerBindings(root, {
3643
+ ...bindings,
3644
+ needsD1: dialect === "sqlite" && application.needsDatabase
3645
+ }, {
3646
+ bindingNames,
3647
+ ...directCloudflareDestination && bindings.needsAI ? { directCloudflareAi: { gateway: config.ai?.gateway } } : {},
3648
+ websockets: websocketRoutes,
3649
+ sandbox: sandboxConfig,
3650
+ live: liveConfig
3651
+ })) voidLog("Synced bindings to Cloudflare config");
3652
+ };
3653
+ const wranglerSyncPlugin = {
3654
+ name: "void:wrangler-sync",
3655
+ enforce: "pre",
3656
+ config(_, env) {
3657
+ syncFrameworkBindings(env.command);
3658
+ },
3659
+ configResolved(resolvedConfig) {
3660
+ syncFrameworkCompat(readWranglerCompat(root, config));
3661
+ syncFrameworkBindings(resolvedConfig.command);
3662
+ }
3663
+ };
3664
+ const nuxtVite8WebSocketCompatPlugin = detected.name === "nuxt" ? {
3665
+ name: "void:nuxt-vite8-websocket-compat",
3666
+ enforce: "post",
3667
+ configResolved(resolvedConfig) {
3668
+ if (!resolvedConfig.build.ssr) return;
3669
+ if (typeof resolvedConfig.server.hmr === "object" && resolvedConfig.server.hmr.port !== void 0) resolvedConfig.server.hmr = {
3670
+ ...resolvedConfig.server.hmr,
3671
+ server: void 0
3672
+ };
3673
+ if (typeof resolvedConfig.server.ws === "object" && resolvedConfig.server.ws.port !== void 0) resolvedConfig.server.ws = {
3674
+ ...resolvedConfig.server.ws,
3675
+ server: void 0
3676
+ };
3677
+ }
3678
+ } : void 0;
3679
+ const svelteKitHooksInjectPlugin = {
3680
+ name: "void:sveltekit-hooks-inject",
3681
+ apply: "serve",
3682
+ transform(code, id) {
3683
+ if (detected.class !== "b") return;
3684
+ if (!id.includes(".svelte-kit/generated/server/internal") && !id.includes(".svelte-kit/generated/dev/server.js")) return;
3685
+ return injectSvelteKitHooks(code, id, frameworkTriggersDispatchPath(root));
3686
+ }
3687
+ };
3688
+ return [
3689
+ createCloudflareBuiltinsExternalPlugin(),
3690
+ wranglerCompatPlugin,
3691
+ wranglerSyncPlugin,
3692
+ ...nuxtVite8WebSocketCompatPlugin ? [nuxtVite8WebSocketCompatPlugin] : [],
3693
+ svelteKitHooksInjectPlugin,
3694
+ clientStubsPlugin,
3695
+ serverRuntimeOptimizerPlugin,
3696
+ bindingRuntimePlugin(bindingNames),
3697
+ envSchemaPlugin(root),
3698
+ envClientGuardPlugin(root),
3699
+ envClientFoldPlugin(),
3700
+ drizzlePlugin(root, false, dialect, {
3701
+ d1BindingName: bindingNames?.d1,
3702
+ forceVirtualDb: authEnabled || bindingNames?.d1 !== void 0,
3703
+ localD1DatabaseId: localD1.databaseId,
3704
+ localD1PersistRoot: frameworkPersistRoot
3705
+ }),
3706
+ migrationPlugin(root, dialect, localD1.databaseId, frameworkPersistRoot, { enabled: false }),
3707
+ devTriggersHintPlugin(root, {
3708
+ framework: true,
3709
+ devTriggerToken
3710
+ }),
3711
+ frameworkTriggersPlugin(root, { devTriggerToken }),
3712
+ ...detected.name === "nuxt" ? [nitroDevTriggerInjectPlugin(root, { serverDir: "server" })] : [],
3713
+ ...detected.name === "analog" ? [analogDevTriggerInjectPlugin(root)] : [],
3714
+ ...detected.name === "astro" ? [astroDevTriggerInjectPlugin(root)] : [],
3715
+ implicitDeployGuard
3716
+ ];
3217
3717
  }
3718
+ const CF_ONLY_REGEX = new RegExp(`^(${[
3719
+ "void/db",
3720
+ "void/kv",
3721
+ "void/auth",
3722
+ "void/storage",
3723
+ "void/ai",
3724
+ "void/sandbox",
3725
+ "void/env"
3726
+ ].map((s) => s.replace("/", "\\/")).join("|")})$`);
3218
3727
  /**
3219
- * Generate the app module — exports the Hono app with static asset serving.
3220
- * This is the entry imported by `vite preview` and can be used programmatically.
3221
- *
3222
- * When `envSchemaModule` is provided, splices boot-time env validation so it
3223
- * runs on the first request (including the prerender child process spawned
3224
- * by `prerenderPagesNode`). Mirrors `compileWorkerEntry`: a side-effect import
3225
- * registers the schema, and `_validateEnvOnce` throws `EnvValidationError` on
3226
- * bad values. Env source on Node/Bun is `process.env`; on Deno it's
3227
- * `Deno.env.toObject()`.
3728
+ * Bridge Node.js HTTP to Web API and serve via a Hono app.
3729
+ * Used by both dev server and preview server middleware.
3228
3730
  */
3229
- function compileNodeApp(target, honoPath, envSchemaModule) {
3230
- const staticImport = target === "bun" ? `import { serveStatic } from "hono/bun";` : target === "deno" ? `import { serveStatic } from "hono/deno";` : `import { serveStatic } from "@hono/node-server/serve-static";`;
3231
- const envSchemaImport = envSchemaModule ? `import ${JSON.stringify(envSchemaModule.importPath)};\nimport { _validateEnvOnce } from ${JSON.stringify(envSchemaModule.helperPath)};\n` : "";
3232
- const envMiddleware = envSchemaModule ? `app.use("*", async (c, next) => { if (c.req.method !== "POST" || c.req.path !== "/__void/prerender-paths") await _validateEnvOnce(${target === "deno" ? "Deno.env.toObject()" : "process.env"}); return next(); });\n` : "";
3233
- return `${staticImport}
3234
- import { fileURLToPath } from "node:url";
3235
- import { Hono } from ${JSON.stringify(honoPath)};
3236
- import innerApp, { __voidCollectPrerenderPaths } from "virtual:void-routes";
3237
- ${envSchemaImport}
3238
- // Resolved by nodeTargetPlugin for this emitted chunk, including shared chunks.
3239
- const clientRoot = fileURLToPath(import.meta.voidClientDirectory);
3240
- const assetApp = new Hono();
3241
- assetApp.get("*", serveStatic({ root: clientRoot }));
3242
- const assets = { fetch: (request) => assetApp.fetch(request) };
3243
- const app = new Hono();
3244
- ${envMiddleware}app.post("/__void/prerender-paths", async (c) => {
3245
- const __expected = c.env?.__VOID_PROXY_TOKEN;
3246
- if (__expected) {
3247
- const __token = c.req.header("x-void-internal");
3248
- if (!__token || __token !== __expected) return c.json({ error: "unauthorized" }, 401);
3249
- }
3250
- const paths = await __voidCollectPrerenderPaths(c.req.raw);
3251
- return c.json({ paths });
3252
- });
3253
- app.get("/assets/*", (c) => assets.fetch(c.req.raw));
3254
- app.all("*", (c) => innerApp.fetch(c.req.raw, { ...c.env, ASSETS: assets }));
3255
- export default app;`;
3731
+ function honoMiddleware(getApp) {
3732
+ return async (req, res, next) => {
3733
+ try {
3734
+ if (!req.url || req.url.startsWith("/@") || req.url.startsWith("/__") || req.url.startsWith("/node_modules/")) {
3735
+ next();
3736
+ return;
3737
+ }
3738
+ const app = await getApp();
3739
+ if (!app?.fetch) {
3740
+ next();
3741
+ return;
3742
+ }
3743
+ const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
3744
+ const headers = new Headers();
3745
+ for (const [key, value] of Object.entries(req.headers)) if (value) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
3746
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
3747
+ const request = new Request(url.toString(), {
3748
+ method: req.method,
3749
+ headers,
3750
+ body: hasBody ? req : void 0,
3751
+ duplex: hasBody ? "half" : void 0
3752
+ });
3753
+ const response = await app.fetch(request);
3754
+ if (response.status === 404) {
3755
+ next();
3756
+ return;
3757
+ }
3758
+ res.statusCode = response.status;
3759
+ response.headers.forEach((value, key) => {
3760
+ res.setHeader(key, value);
3761
+ });
3762
+ if (response.body) {
3763
+ const reader = response.body.getReader();
3764
+ while (true) {
3765
+ const { done, value } = await reader.read();
3766
+ if (done) break;
3767
+ res.write(value);
3768
+ }
3769
+ res.end();
3770
+ } else res.end();
3771
+ } catch (err) {
3772
+ next(err);
3773
+ }
3774
+ };
3256
3775
  }
3257
3776
  /**
3258
- * Generate the server entry — imports the app and starts an HTTP server.
3259
- * This is what users run with `node dist/ssr/index.js`.
3777
+ * Vite plugin for non-Cloudflare deploy targets (Node.js, Bun, Deno).
3778
+ *
3779
+ * Replaces `@cloudflare/vite-plugin` in the plugin composition. Provides:
3780
+ * 1. Binding guard — compile-time errors for CF-only void/* imports
3781
+ * 2. Dev server — Vite SSR middleware to run Hono in Node.js
3782
+ * 3. Preview server — serves from the built SSR entry
3783
+ * 4. Build — dual-environment build (client + SSR) for pages apps,
3784
+ * single SSR build for API-only apps
3260
3785
  */
3261
- function compileNodeServer(target) {
3262
- if (target === "bun") return `import app from "./app.js";
3263
-
3264
- const port = Number(process.env.PORT || 3000);
3265
- Bun.serve({ fetch: app.fetch, port });
3266
- console.log("Listening on http://localhost:" + port);`;
3267
- if (target === "deno") return `import app from "./app.js";
3268
-
3269
- const port = Number(Deno.env.get("PORT") || 3000);
3270
- Deno.serve({ port }, app.fetch);`;
3271
- return `import { serve } from "@hono/node-server";
3272
- import app from "./app.js";
3273
-
3274
- const port = Number(process.env.PORT || 3000);
3275
- serve({ fetch: app.fetch, port }, (info) => {
3276
- console.log("Listening on http://localhost:" + info.port);
3277
- });`;
3786
+ function nodeTargetPlugin(target) {
3787
+ return [{
3788
+ name: "void:node-target",
3789
+ enforce: "pre",
3790
+ resolveId: {
3791
+ filter: { id: CF_ONLY_REGEX },
3792
+ handler(id) {
3793
+ throw new Error(`target: '${id}' is not available with target '${target}'. CF bindings require target: "cloudflare".`);
3794
+ }
3795
+ },
3796
+ renderChunk(code, chunk) {
3797
+ if (!code.includes("import.meta.voidClientDirectory")) return null;
3798
+ const clientPath = relative(dirname(chunk.fileName), "../client");
3799
+ return {
3800
+ code: code.replaceAll("import.meta.voidClientDirectory", `new URL(${JSON.stringify(`${clientPath}/`)}, import.meta.url)`),
3801
+ map: null
3802
+ };
3803
+ },
3804
+ config(_, { command }) {
3805
+ if (command !== "build") return;
3806
+ const root = process.cwd();
3807
+ const voidDir = join(root, ".void");
3808
+ const ssrBuildOptions = {
3809
+ outDir: "dist/ssr",
3810
+ rollupOptions: {
3811
+ input: {
3812
+ app: join(voidDir, "app.ts"),
3813
+ index: join(voidDir, "index.ts")
3814
+ },
3815
+ external: [
3816
+ /^node:/,
3817
+ "@hono/node-server",
3818
+ "@hono/node-server/serve-static"
3819
+ ]
3820
+ }
3821
+ };
3822
+ if (existsSync(readProjectPaths(root).pagesDir)) return {
3823
+ build: { outDir: "dist/client" },
3824
+ environments: { ssr: { build: ssrBuildOptions } },
3825
+ builder: {}
3826
+ };
3827
+ return { build: {
3828
+ ssr: true,
3829
+ ...ssrBuildOptions
3830
+ } };
3831
+ },
3832
+ async buildApp(builder) {
3833
+ await builder.build(builder.environments.ssr);
3834
+ await builder.build(builder.environments.client);
3835
+ },
3836
+ configureServer(server) {
3837
+ server.middlewares.use(honoMiddleware(async () => {
3838
+ return (await server.ssrLoadModule("virtual:void-routes")).default;
3839
+ }));
3840
+ },
3841
+ configurePreviewServer(server) {
3842
+ const entryPath = join(process.cwd(), "dist", "ssr", "app.js");
3843
+ let appPromise = null;
3844
+ server.middlewares.use(honoMiddleware(() => {
3845
+ if (!appPromise) appPromise = import(
3846
+ /* @vite-ignore */
3847
+ pathToFileURL(entryPath).href
3848
+ ).then((m) => m.default, () => null);
3849
+ return appPromise;
3850
+ }));
3851
+ }
3852
+ }];
3278
3853
  }
3854
+ //#endregion
3855
+ //#region src/plugin-prerender.ts
3279
3856
  /**
3280
- * Generate the server-side fetch client module for non-CF targets.
3281
- * Same as compileWorkerClient but without cloudflare:workers dependency.
3282
- * Calls app.fetch() directly (no HTTP round-trip) with ALS context forwarding.
3857
+ * Vite plugin that prerenders pages at build time when `output: "static"`.
3858
+ *
3859
+ * Runs in the `closeBundle` hook so prerender logs appear after Vite's
3860
+ * asset summary. Gated on the "client" environment so it only runs once.
3283
3861
  */
3284
- function compileBrowserClient(authClientPath, fetchPath, fetchStreamPath) {
3285
- return `export { fetch, FetchError } from ${JSON.stringify(fetchPath)};
3286
- export { fetchStream, StreamError } from ${JSON.stringify(fetchStreamPath)};
3287
- export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};`;
3288
- }
3289
- function compileNodeClient(authClientPath) {
3290
- return `import app, { requestContext } from "virtual:void-routes";
3291
- import { interpolateRouteParams } from ${JSON.stringify(ROUTE_URL_PATH)};
3292
- export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};
3293
-
3294
- export class FetchError extends Error {
3295
- constructor(status, statusText, data) {
3296
- super(\`fetch: Request failed with status \${status}.\`);
3297
- this.name = "FetchError";
3298
- this.status = status;
3299
- this.statusText = statusText;
3300
- this.data = data;
3301
- }
3862
+ function prerenderPlugin(root, config) {
3863
+ return {
3864
+ name: "void:prerender",
3865
+ apply: "build",
3866
+ async closeBundle() {
3867
+ if (config.output !== "static") return;
3868
+ if (process.env.VOID_DEPLOY_BUILD === "1") return;
3869
+ if (this.environment?.name !== "client") return;
3870
+ setShowTimestamp(false);
3871
+ const distDir = resolveDistDir(root);
3872
+ const clientDir = join(distDir, "client");
3873
+ let result;
3874
+ if (isNodeTarget(config.target)) result = await prerenderPagesNode({
3875
+ root,
3876
+ ssrDir: join(distDir, "ssr"),
3877
+ clientDir
3878
+ });
3879
+ else {
3880
+ const persistDir = join(root, ".void");
3881
+ const workerDirName = resolveWorkerDirName(distDir);
3882
+ const workerDir = join(distDir, workerDirName);
3883
+ const remoteMode = resolveRemoteMode(config.remote);
3884
+ result = await prerenderPages({
3885
+ root,
3886
+ workerDir,
3887
+ clientDir,
3888
+ persistDir,
3889
+ remoteMode,
3890
+ projectId: process.env.VOID_DEPLOY_PROJECT_ID || void 0
3891
+ });
3892
+ }
3893
+ if (result.paths.length === 0) {
3894
+ voidLog("No pages to prerender");
3895
+ return;
3896
+ }
3897
+ if (result.failed.length > 0) voidWarn(`${result.failed.length} page(s) failed to prerender`);
3898
+ }
3899
+ };
3302
3900
  }
3303
-
3304
- export async function fetch(path, options) {
3305
- let url = interpolateRouteParams(path, options?.params);
3306
-
3307
- if (options?.query) {
3308
- const qs = new URLSearchParams(options.query).toString();
3309
- if (qs) url += "?" + qs;
3310
- }
3311
-
3312
- const method = options?.method ?? "GET";
3313
- const headers = new Headers(options?.headers);
3314
-
3315
- const store = requestContext.getStore();
3316
- if (store?.request) {
3317
- const outerReq = store.request;
3318
- if (!headers.has("cookie")) {
3319
- const cookie = outerReq.headers.get("cookie");
3320
- if (cookie) headers.set("cookie", cookie);
3321
- }
3322
- if (!headers.has("authorization")) {
3323
- const auth = outerReq.headers.get("authorization");
3324
- if (auth) headers.set("authorization", auth);
3325
- }
3326
- }
3327
-
3328
- if (options?.body != null && !headers.has("content-type")) {
3329
- headers.set("content-type", "application/json");
3330
- }
3331
-
3332
- const request = new Request("http://localhost" + url, {
3333
- method,
3334
- headers,
3335
- body: options?.body != null ? JSON.stringify(options.body) : undefined,
3901
+ //#endregion
3902
+ //#region src/plugin-restart.ts
3903
+ const BINDING_HINT_RESOLVED = "\0virtual:void/binding-hint";
3904
+ const RESTARTING_HTML = `<!DOCTYPE html>
3905
+ <html lang="en">
3906
+ <head>
3907
+ <meta charset="UTF-8">
3908
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3909
+ <title>Restarting</title>
3910
+ <style>
3911
+ 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}
3912
+ .tag{color:#c4a7e7;font-weight:700}
3913
+ @keyframes d{0%,80%{opacity:0}40%{opacity:1}}
3914
+ .dots span{opacity:0;animation:d 1.4s infinite}
3915
+ .dots span:nth-child(2){animation-delay:.2s}
3916
+ .dots span:nth-child(3){animation-delay:.4s}
3917
+ </style>
3918
+ </head>
3919
+ <body>
3920
+ <div><span class="tag">[void]</span> Restarting dev server<span class="dots"><span>.</span><span>.</span><span>.</span></span></div>
3921
+ <script>
3922
+ fetch("/__void/restart",{method:"POST"}).catch(function(){});
3923
+ setTimeout(function p(){
3924
+ fetch("/__void/ping").then(function(r){
3925
+ if(r.ok)location.href=new URLSearchParams(location.search).get("return")||"/";
3926
+ else setTimeout(p,150);
3927
+ }).catch(function(){setTimeout(p,150)});
3928
+ },300);
3929
+ <\/script>
3930
+ </body>
3931
+ </html>`;
3932
+ const CLIENT_MODULE = `if (import.meta.hot) {
3933
+ import.meta.hot.on("void:new-binding", (d) => {
3934
+ if (document.getElementById("void-binding-hint")) return;
3935
+ const e = document.createElement("div");
3936
+ e.id = "void-binding-hint";
3937
+ 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";
3938
+ e.innerHTML = "<strong style=\\"color:#c4a7e7\\">[void]</strong> " + d.message + " Click to restart.";
3939
+ e.onclick = () => { location.href = "/__void/restarting?return=" + encodeURIComponent(location.pathname + location.search); };
3940
+ document.body.appendChild(e);
3941
+ requestAnimationFrame(() => requestAnimationFrame(() => {
3942
+ e.style.transform = "translateX(0)";
3943
+ }));
3336
3944
  });
3337
-
3338
- const response = await app.fetch(request);
3339
-
3340
- if (response.status === 204) return undefined;
3341
-
3342
- const contentType = response.headers.get("content-type") ?? "";
3343
- const data = contentType.includes("application/json") ? await response.json() : await response.text();
3344
-
3345
- if (!response.ok) {
3346
- throw new FetchError(response.status, response.statusText, data);
3347
- }
3348
-
3349
- return data;
3350
3945
  }`;
3946
+ /**
3947
+ * Vite plugin that provides controlled dev-server restart via the browser.
3948
+ *
3949
+ * Serves three endpoints:
3950
+ * - `/__void/restarting` — standalone loading page (no Vite HMR client)
3951
+ * - `/__void/restart` — POST triggers `server.restart()` after a short delay
3952
+ * - `/__void/ping` — health check; returns 503 on the old server instance
3953
+ * (after restart is triggered) and 200 on the new one
3954
+ *
3955
+ * Also injects a virtual module (`virtual:void/binding-hint`) that listens for
3956
+ * `void:new-binding` HMR events and shows a toast with a click-to-restart action.
3957
+ */
3958
+ function restartPlugin() {
3959
+ return {
3960
+ name: "void:restart",
3961
+ apply: "serve",
3962
+ resolveId: {
3963
+ filter: { id: /^virtual:void\/binding-hint$/ },
3964
+ handler() {
3965
+ return BINDING_HINT_RESOLVED;
3966
+ }
3967
+ },
3968
+ load: {
3969
+ filter: { id: /^\0virtual:void\/binding-hint$/ },
3970
+ handler() {
3971
+ return CLIENT_MODULE;
3972
+ }
3973
+ },
3974
+ configureServer(server) {
3975
+ let restartPending = false;
3976
+ server.middlewares.use("/__void/ping", (_req, res) => {
3977
+ res.writeHead(restartPending ? 503 : 200);
3978
+ res.end();
3979
+ });
3980
+ server.middlewares.use("/__void/restart", (req, res) => {
3981
+ if (req.method !== "POST") {
3982
+ res.writeHead(405);
3983
+ res.end();
3984
+ return;
3985
+ }
3986
+ restartPending = true;
3987
+ res.writeHead(200);
3988
+ res.end();
3989
+ setTimeout(() => server.restart(), 50);
3990
+ });
3991
+ server.middlewares.use("/__void/restarting", (_req, res) => {
3992
+ res.writeHead(200, { "Content-Type": "text/html" });
3993
+ res.end(RESTARTING_HTML);
3994
+ });
3995
+ }
3996
+ };
3351
3997
  }
3352
- function stripOriginForProd(rules) {
3353
- return rules.map(({ origin: _origin, ...rest }) => rest);
3354
- }
3355
- function compileRoutingRulesMiddleware(lines, rules, dev, _nodeTarget) {
3356
- lines.push("// Routing rules: redirects + rewrites (applied before route handlers)");
3357
- lines.push(`const __routingRules = ${JSON.stringify(dev ? rules : stripOriginForProd(rules))};`);
3358
- lines.push("app.use(\"*\", __routingRuntime.routingMiddleware(__routingRules));");
3359
- }
3360
- function compileHeaderMiddleware(lines, rules) {
3361
- lines.push("// Header rules (applied to responses)");
3362
- lines.push(`const __headerRules = ${JSON.stringify(rules)};`);
3363
- lines.push("app.use(\"*\", __routingRuntime.headerMiddleware(__headerRules));");
3364
- }
3365
- function compileWorkerClient(authClientPath) {
3366
- return `import app, { requestContext } from "virtual:void-routes";
3367
- import { interpolateRouteParams } from ${JSON.stringify(ROUTE_URL_PATH)};
3368
- import { env } from "cloudflare:workers";
3369
- export { auth, createAuthClient } from ${JSON.stringify(authClientPath)};
3370
-
3371
- export class FetchError extends Error {
3372
- constructor(status, statusText, data) {
3373
- super(\`fetch: Request failed with status \${status}.\`);
3374
- this.name = "FetchError";
3375
- this.status = status;
3376
- this.statusText = statusText;
3377
- this.data = data;
3378
- }
3379
- }
3380
-
3381
- export async function fetch(path, options) {
3382
- let url = interpolateRouteParams(path, options?.params);
3383
-
3384
- if (options?.query) {
3385
- const qs = new URLSearchParams(options.query).toString();
3386
- if (qs) url += "?" + qs;
3387
- }
3388
-
3389
- const method = options?.method ?? "GET";
3390
- const headers = new Headers(options?.headers);
3391
-
3392
- // Auto-forward cookie and authorization from the outer request via ALS
3393
- const store = requestContext.getStore();
3394
- if (store?.request) {
3395
- const outerReq = store.request;
3396
- if (!headers.has("cookie")) {
3397
- const cookie = outerReq.headers.get("cookie");
3398
- if (cookie) headers.set("cookie", cookie);
3399
- }
3400
- if (!headers.has("authorization")) {
3401
- const auth = outerReq.headers.get("authorization");
3402
- if (auth) headers.set("authorization", auth);
3403
- }
3404
- }
3405
-
3406
- if (options?.body != null && !headers.has("content-type")) {
3407
- headers.set("content-type", "application/json");
3408
- }
3409
-
3410
- const request = new Request("http://worker.local" + url, {
3411
- method,
3412
- headers,
3413
- body: options?.body != null ? JSON.stringify(options.body) : undefined,
3414
- });
3415
-
3416
- // Use real executionCtx from ALS if available, otherwise provide a no-op fallback
3417
- const executionCtx = store?.executionCtx ?? { waitUntil() {}, passThroughOnException() {} };
3418
- const response = await app.fetch(request, env, executionCtx);
3419
-
3420
- if (response.status === 204) return undefined;
3421
-
3422
- const contentType = response.headers.get("content-type") ?? "";
3423
- const data = contentType.includes("application/json") ? await response.json() : await response.text();
3424
-
3425
- if (!response.ok) {
3426
- throw new FetchError(response.status, response.statusText, data);
3427
- }
3428
-
3429
- return data;
3430
- }
3431
- `;
3998
+ //#endregion
3999
+ //#region src/client-asset-manifest.ts
4000
+ /** Attach cache provenance to the existing manifest without changing its entry inventory. */
4001
+ function stampClientAssetCachePolicy(bundle, manifestPath, headerRules = []) {
4002
+ const asset = bundle[manifestPath];
4003
+ if (asset?.type !== "asset") return;
4004
+ const fingerprinted = /* @__PURE__ */ new Set();
4005
+ const authoredCachePatterns = headerRules.filter((rule) => rule.headers.some(([name]) => name.toLowerCase() === "cache-control")).map((rule) => compilePattern(rule.pattern).regex);
4006
+ for (const item of Object.values(bundle)) if (item.type === "chunk" && typeof item.preliminaryFileName === "string" && item.preliminaryFileName !== item.fileName) {
4007
+ if (!authoredCachePatterns.some((pattern) => pattern.test("/" + item.fileName))) fingerprinted.add(item.fileName);
4008
+ }
4009
+ const manifest = JSON.parse(String(asset.source));
4010
+ for (const entry of Object.values(manifest)) if (entry.file && fingerprinted.has(entry.file)) entry.immutableFiles = [entry.file];
4011
+ else delete entry.immutableFiles;
4012
+ asset.source = JSON.stringify(manifest, null, 2);
3432
4013
  }
3433
4014
  //#endregion
3434
4015
  //#region src/plugin-routing.ts
@@ -3907,284 +4488,78 @@ function routingPlugin(root, options) {
3907
4488
  if (mod) {
3908
4489
  env.moduleGraph.invalidateModule(mod);
3909
4490
  env.hot.send({ type: "full-reload" });
3910
- }
3911
- }
3912
- };
3913
- watcher.on("add", handleRulesFileChange);
3914
- watcher.on("change", handleRulesFileChange);
3915
- watcher.on("unlink", handleRulesFileChange);
3916
- }
3917
- }
3918
- };
3919
- }
3920
- //#endregion
3921
- //#region src/sveltekit-hooks-transform.ts
3922
- /** Add Void's dev bindings to SvelteKit's generated get_hooks function. */
3923
- function injectSvelteKitHooks(code, id, dispatchPath) {
3924
- if (!code.includes("get_hooks")) return;
3925
- const hooks = (parseSync(id, code).program.body ?? []).filter((node) => node.type === "ExportNamedDeclaration").map((node) => node.declaration).filter((node) => {
3926
- const name = node?.id;
3927
- return node?.type === "FunctionDeclaration" && isIdentifier(name) && name.name === "get_hooks";
3928
- });
3929
- if (hooks.length !== 1) throw new Error("sveltekit: Expected one generated get_hooks function.");
3930
- const body = hooks[0].body;
3931
- const returns = (body?.body)?.filter((node) => node.type === "ReturnStatement" && node.argument?.type === "ObjectExpression") ?? [];
3932
- if (body?.type !== "BlockStatement" || body.start === void 0 || returns.length !== 1 || returns[0]?.start === void 0) throw new Error("sveltekit: Expected one object return in generated get_hooks.");
3933
- const ms = new MagicString(code);
3934
- ms.appendLeft(body.start + 1, `\n const { withRuntimeEnv } = await import("void/_env");\n const { handleDevTrigger } = await import(${JSON.stringify(dispatchPath)});`);
3935
- ms.appendLeft(returns[0].start, "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 ");
3936
- return {
3937
- code: ms.toString(),
3938
- map: ms.generateMap({ hires: true })
3939
- };
3940
- }
3941
- //#endregion
3942
- //#region src/index.ts
3943
- /**
3944
- * Persist the dev-trigger token at `<root>/.void/dev-trigger-token` so
3945
- * every voidPlugin() invocation against the same project reads the same
3946
- * value. This matters because `@cloudflare/vite-plugin` spawns a
3947
- * worker-environment child Vite that re-imports vite.config.ts and calls
3948
- * voidPlugin() again — without persistence the parent and child would
3949
- * generate different UUIDs, the dispatch file would embed one and the
3950
- * hint plugin would print the other, and curls would land on
3951
- * `unauthorized`.
3952
- *
3953
- * The file persists across dev sessions by design — it lives under `.void/`
3954
- * (gitignored), and `cleanupWrapper` in `void deploy` explicitly preserves
3955
- * it across its own wipe so saved curl scripts keep working between deploys.
3956
- * The token is local to the project and only honored by a worker bound to
3957
- * localhost; treat it like the rest of `.void/` — disposable scratch state,
3958
- * safe to remove manually if desired.
3959
- *
3960
- * The file is owner-only (0600). It is the one credential the dev-inbox
3961
- * routes accept, and loopback is reachable by every account on the host,
3962
- * so a default-umask 0644 file would hand any other local user the raw MIME,
3963
- * the DELETE, and the capture endpoint. A file persisted by an earlier
3964
- * release is tightened in place, never rotated. Windows has no group/other
3965
- * bits — `mode` and `chmod` only toggle write there — so it is left alone.
3966
- */
3967
- /**
3968
- * Best-effort detection of `vite build` (and `vite preview`) so we can skip
3969
- * dev-only side effects — writing the dev-trigger token and the dispatch
3970
- * file — when no dev server will run. Vite's CLI passes the subcommand as
3971
- * `process.argv[2]`; programmatic API users (and `vite` with no subcommand)
3972
- * default to `dev`, which we detect as the absence of a build-shaped first
3973
- * positional. This is intentionally permissive: when in doubt, we behave as
3974
- * if we're in dev so first-request correctness is preserved.
3975
- */
3976
- function isViteBuildOrPreview() {
3977
- const subcommand = process.argv[2];
3978
- return subcommand === "build" || subcommand === "preview";
3979
- }
3980
- const DEV_TRIGGER_TOKEN_FILE_MODE = 384;
3981
- function getOrCreateDevTriggerToken(root) {
3982
- const tokenPath = join(root, ".void", "dev-trigger-token");
3983
- const readExisting = () => {
3984
- try {
3985
- const existing = readFileSync(tokenPath, "utf-8").trim();
3986
- return existing.length > 0 ? existing : void 0;
3987
- } catch {
3988
- return;
3989
- }
3990
- };
3991
- const tightenExisting = () => {
3992
- if (process.platform === "win32") return;
3993
- try {
3994
- if ((statSync(tokenPath).mode & 63) !== 0) chmodSync(tokenPath, DEV_TRIGGER_TOKEN_FILE_MODE);
3995
- } catch {}
3996
- };
3997
- const existing = readExisting();
3998
- if (existing) {
3999
- tightenExisting();
4000
- return existing;
4001
- }
4002
- mkdirSync(join(root, ".void"), { recursive: true });
4003
- const fresh = crypto.randomUUID();
4004
- try {
4005
- const fd = openSync(tokenPath, "wx", DEV_TRIGGER_TOKEN_FILE_MODE);
4006
- try {
4007
- writeSync(fd, fresh);
4008
- } finally {
4009
- closeSync(fd);
4010
- }
4011
- return fresh;
4012
- } catch (err) {
4013
- if (err.code !== "EEXIST") throw err;
4014
- const winner = readExisting();
4015
- if (winner) return winner;
4016
- return fresh;
4017
- }
4018
- }
4019
- /**
4020
- * Warn when the project's `package.json` does not declare `"type": "module"`.
4021
- *
4022
- * Vite's default `configLoader: "bundle"` bundles `vite.config.ts`,
4023
- * externalizes deps, and then loads the bundle. When `package.json` is non-ESM
4024
- * the bundle is written as `.js` and loaded via CJS `require()`, which throws
4025
- * `ERR_REQUIRE_ASYNC_MODULE` if any externalized dep's ESM graph contains
4026
- * top-level await. `@cloudflare/vite-plugin` (which voidPlugin pulls in) has
4027
- * exactly such a TLA, so non-ESM consumers hit this on every Vite invocation.
4028
- *
4029
- * Runs once per Vite process (factory time), single sync read.
4030
- */
4031
- function warnIfPackageJsonIsNotEsm(root) {
4032
- const pkgPath = join(root, "package.json");
4033
- if (!existsSync(pkgPath)) return;
4034
- let pkg;
4035
- try {
4036
- pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
4037
- } catch {
4038
- return;
4039
- }
4040
- if (pkg.type === "module") return;
4041
- 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`.");
4042
- }
4043
- /**
4044
- * Resolve the server entry module for a Class A framework. We always wrap
4045
- * the framework's own entry with a tiny shim that intercepts
4046
- * `/__void/{scheduled,queue}` in dev so cron and queue handlers run with
4047
- * the worker's full CF bindings. The interception block is gated on
4048
- * `import.meta.env.DEV` and the dispatch module is loaded via dynamic
4049
- * `import()` so the entire dev path (including user cron imports) is
4050
- * dead-code-eliminated in `vite build`. Production deploys go through
4051
- * `cli/wrapper.ts` instead.
4052
- */
4053
- function getFrameworkEntry(framework, root, devTriggerToken, notFound) {
4054
- const isBuild = isViteBuildOrPreview();
4055
- if (!isBuild) writeFrameworkTriggersDispatch(root, devTriggerToken);
4056
- return generateClassATriggerWrapper(root, framework, getOriginalFrameworkEntry(framework, root), isBuild, void 0, notFound);
4057
- }
4058
- /** The framework's own server entry, before our trigger wrapper. */
4059
- function getOriginalFrameworkEntry(framework, root) {
4060
- switch (framework) {
4061
- case "tanstack-start": return "@tanstack/react-start/server-entry";
4062
- case "react-router": return generateReactRouterEntry(root);
4063
- case "vinext-app": return "vinext/server/app-router-entry";
4064
- case "vinext-pages": return generateVinextPagesEntry(root);
4065
- }
4066
- }
4067
- /**
4068
- * Generate `<root>/.void/<framework>-trigger-wrapper.ts`. This file becomes
4069
- * `workerConfig.main` so the dev miniflare/workerd evaluates *our* fetch
4070
- * first, dispatches `/__void/{scheduled,queue}` if it matches, and otherwise
4071
- * delegates to the framework's original entry.
4072
- *
4073
- * We emit two shapes of the wrapper depending on the Vite command:
4074
- *
4075
- * - Serve: dispatch is dynamically imported and gets a chance to handle
4076
- * `/__void/{scheduled,queue}` before falling through to the framework.
4077
- * - Build: dispatch is omitted entirely. The wrapper just delegates fetch.
4078
- *
4079
- * We can't simply gate the dynamic import on `import.meta.env.DEV` and
4080
- * trust DCE: rolldown emits chunks for dynamic imports during chunk
4081
- * splitting, *before* constant folding removes the dead branch, so the
4082
- * dispatch file (and its transitive user-cron/queue imports + dev token)
4083
- * would still ship in the production bundle.
4084
- */
4085
- function generateClassATriggerWrapper(root, framework, original, isBuild, assetsDir = "assets", notFound) {
4086
- const outDir = join(root, ".void");
4087
- mkdirSync(outDir, { recursive: true });
4088
- const wrapperPath = join(outDir, `${framework}-trigger-wrapper.ts`);
4089
- const body = isBuild ? `// @ts-nocheck — Auto-generated by Void for vite build. Do NOT edit.
4090
- import original from ${JSON.stringify(original)};
4091
-
4092
- export default {
4093
- ...original,
4094
- async fetch(request, env, ctx) {
4095
- return serveWithAssets(request, env, await original.fetch(request, env, ctx));
4096
- },
4097
- };
4098
- ${compileAssetFallbackHelper(assetsDir, false, notFound)}
4099
- ` : `// @ts-nocheck — Auto-generated by Void in dev. Do NOT edit.
4100
- import original from ${JSON.stringify(original)};
4101
-
4102
- export default {
4103
- ...original,
4104
- async fetch(request, env, ctx) {
4105
- if (request.method === "POST") {
4106
- const pathname = new URL(request.url).pathname;
4107
- if (pathname === "/__void/scheduled" || pathname === "/__void/queue") {
4108
- const { handleDevTrigger } = await import(${JSON.stringify(frameworkTriggersDispatchPath(root))});
4109
- const response = await handleDevTrigger(request, env, ctx);
4110
- if (response) return response;
4111
- }
4112
- }
4113
- return original.fetch(request, env, ctx);
4114
- },
4115
- };
4116
- `;
4117
- writeFileIfChanged(wrapperPath, body);
4118
- return wrapperPath;
4491
+ }
4492
+ }
4493
+ };
4494
+ watcher.on("add", handleRulesFileChange);
4495
+ watcher.on("change", handleRulesFileChange);
4496
+ watcher.on("unlink", handleRulesFileChange);
4497
+ }
4498
+ }
4499
+ };
4119
4500
  }
4120
- /** Generate a worker entry for vinext Pages Router projects. */
4121
- function generateVinextPagesEntry(root) {
4122
- const outDir = join(root, ".void");
4123
- mkdirSync(outDir, { recursive: true });
4124
- const entryPath = join(outDir, "vinext-pages-entry.ts");
4125
- writeFileIfChanged(entryPath, `// Auto-generated by Void — vinext Pages Router worker entry
4126
- // @ts-expect-error virtual module resolved by vinext at build time
4127
- import { renderPage, handleApiRoute } from "virtual:vinext-server-entry";
4128
-
4129
- export default {
4130
- async fetch(request: Request): Promise<Response> {
4131
- const url = new URL(request.url);
4132
- const pathname = url.pathname;
4133
- if (pathname.replaceAll("\\\\", "/").startsWith("//")) {
4134
- return new Response("404 Not Found", { status: 404 });
4135
- }
4136
- const urlWithQuery = pathname + url.search;
4137
- if (pathname.startsWith("/api/") || pathname === "/api") {
4138
- return await handleApiRoute(request, urlWithQuery);
4139
- }
4140
- return await renderPage(request, urlWithQuery, null);
4141
- },
4142
- };
4143
- `);
4144
- return entryPath;
4501
+ //#endregion
4502
+ //#region src/cloudflare-durable-patch.ts
4503
+ function entries(value, label) {
4504
+ if (value === void 0) return [];
4505
+ if (!Array.isArray(value) || value.some((entry) => !entry || typeof entry !== "object" || Array.isArray(entry))) throw new Error(`Cloudflare ${label} configuration must be an array of objects.`);
4506
+ return value;
4145
4507
  }
4146
- /** Generate a worker entry for React Router v7 projects. */
4147
- function generateReactRouterEntry(root) {
4148
- const outDir = join(root, ".void");
4149
- mkdirSync(outDir, { recursive: true });
4150
- const entryPath = join(outDir, "react-router-entry.ts");
4151
- writeFileIfChanged(entryPath, `// Auto-generated by Void — React Router v7 worker entry
4152
- import { createRequestHandler } from "react-router";
4153
-
4154
- const requestHandler = createRequestHandler(
4155
- // @ts-expect-error virtual module resolved by @react-router/dev at build time
4156
- () => import("virtual:react-router/server-build"),
4157
- import.meta.env.MODE,
4158
- );
4159
-
4160
- export default {
4161
- async fetch(request: Request): Promise<Response> {
4162
- return requestHandler(request);
4163
- },
4164
- };
4165
- `);
4166
- return entryPath;
4508
+ function byIdentity(rows, field, label, unique = false) {
4509
+ const map = /* @__PURE__ */ new Map();
4510
+ for (const row of rows) {
4511
+ const id = row[field];
4512
+ if (typeof id !== "string" || !id) throw new Error(`Cloudflare ${label} is missing its ${field}.`);
4513
+ const previous = map.get(id);
4514
+ if (previous && unique) throw new Error(`Cloudflare ${label} ${JSON.stringify(id)} is configured more than once.`);
4515
+ if (previous && !isDeepStrictEqual(previous, row)) throw new Error(`Cloudflare ${label} ${JSON.stringify(id)} has conflicting configuration.`);
4516
+ if (!previous) map.set(id, row);
4517
+ }
4518
+ return map;
4519
+ }
4520
+ /** Finish cumulative feature planning without repeating the Cloudflare plugin's array fallback. */
4521
+ function finalizeDurableObjectPatch(resolved, patch, generatedLocalBindings) {
4522
+ const baselineObjects = resolved.durable_objects;
4523
+ const pendingObjects = patch.durable_objects;
4524
+ const baselineBindings = byIdentity(entries(baselineObjects?.bindings, "Durable Object bindings"), "name", "Durable Object binding", true);
4525
+ const pendingBindings = byIdentity(entries(pendingObjects?.bindings, "Durable Object bindings"), "name", "Durable Object binding");
4526
+ const history = entries(resolved.migrations, "Durable Object migrations");
4527
+ const baselineMigrations = byIdentity(history, "tag", "Durable Object migration", true);
4528
+ for (const local of generatedLocalBindings) for (const map of [baselineBindings, pendingBindings]) {
4529
+ const existing = map.get(local.name);
4530
+ if (existing && (existing.class_name !== local.class_name || existing.script_name !== void 0 || existing.environment !== void 0)) throw new Error(`Cloudflare generated Durable Object binding ${JSON.stringify(local.name)} conflicts with its configured identity.`);
4531
+ }
4532
+ if (pendingObjects?.bindings !== void 0) {
4533
+ const additions = [];
4534
+ for (const [name, row] of pendingBindings) {
4535
+ const existing = baselineBindings.get(name);
4536
+ if (existing && !isDeepStrictEqual(existing, row)) throw new Error(`Cloudflare Durable Object binding ${JSON.stringify(name)} has conflicting configuration.`);
4537
+ if (!existing) additions.push(row);
4538
+ }
4539
+ patch.durable_objects = {
4540
+ ...pendingObjects,
4541
+ bindings: additions
4542
+ };
4543
+ }
4544
+ if (patch.migrations !== void 0) {
4545
+ const pending = byIdentity(entries(patch.migrations, "Durable Object migrations"), "tag", "Durable Object migration");
4546
+ const additions = [];
4547
+ for (const [tag, row] of pending) {
4548
+ const previous = baselineMigrations.get(tag);
4549
+ if (previous && !isDeepStrictEqual(previous, row)) throw new Error(`Cloudflare Durable Object migration ${JSON.stringify(tag)} has conflicting configuration.`);
4550
+ if (!previous) additions.push(row);
4551
+ }
4552
+ patch.migrations = [...history, ...additions];
4553
+ resolved.migrations = [];
4554
+ }
4167
4555
  }
4556
+ //#endregion
4557
+ //#region src/plugin-binding-utils.ts
4168
4558
  /** Check whether a binding name already exists in a resolved bindings array. */
4169
4559
  function hasBinding(list, name) {
4170
4560
  return Array.isArray(list) && list.some((b) => b.binding === name);
4171
4561
  }
4172
4562
  /**
4173
- * Decide whether to inject a Workers AI binding into the built wrangler config.
4174
- *
4175
- * Builds use the production binding. During dev, a project explicitly saved
4176
- * with the direct Cloudflare destination uses Cloudflare's remote binding;
4177
- * managed and unsaved projects keep using the Void HTTPS proxy.
4178
- *
4179
- * `!resolvedAi` is load-bearing: mergeBindings' return is defu-merged over the
4180
- * resolved root-wrangler config and wins, so skipping when the user already
4181
- * declared an `ai` binding avoids clobbering theirs. `!resultAi` is harmless
4182
- * defense-in-depth (nothing else in mergeBindings sets `result.ai`).
4183
- */
4184
- function shouldBindWorkersAi(command, needsAI, resolvedAi, resultAi, directCloudflareDev = false) {
4185
- return needsAI && (command === "build" || command === "serve" && directCloudflareDev) && !resultAi && !resolvedAi;
4186
- }
4187
- /**
4188
4563
  * Decide whether to inject the `void dev` email-inbox loopback var.
4189
4564
  *
4190
4565
  * The dev inbox buffer lives in the Vite dev server's Node process;
@@ -4199,202 +4574,233 @@ function shouldBindWorkersAi(command, needsAI, resolvedAi, resultAi, directCloud
4199
4574
  *
4200
4575
  * Gated on `routeEnvironment`, not on `command`: `vite preview` also reports
4201
4576
  * `command: 'serve'`, but it runs the BUILT worker whose assets binding is a
4202
- * real asset directory rather than a dev-server loopback. `routeEnvironment`
4203
- * is the existing `command === 'serve' && !isPreview` distinction.
4204
- *
4205
- * Without an assets binding there is no loopback, so injecting the var would
4206
- * only arm a path that must then fail; gate on it instead.
4207
- */
4208
- function shouldInjectEmailDevInboxVar(routeEnvironment, needsEmail, assetsBinding) {
4209
- return routeEnvironment === "dev" && !!needsEmail && typeof assetsBinding === "string" && !!assetsBinding;
4210
- }
4211
- /** Resolve the runtime Workers AI binding, preserving a root Wrangler declaration. */
4212
- function resolveWorkersAiBindingName(resolvedAi, configuredName) {
4213
- if (typeof resolvedAi === "object" && resolvedAi !== null) {
4214
- const binding = resolvedAi.binding;
4215
- if (typeof binding === "string") return binding;
4216
- }
4217
- return configuredName ?? "AI";
4218
- }
4219
- const CLOUDFLARE_BUILTIN_IMPORT = /^cloudflare:/;
4220
- const SANDBOX_SDK_IMPORT = "@cloudflare/sandbox";
4221
- const SANDBOX_SDK_IMPORT_FILTER = /^@cloudflare\/sandbox$/;
4222
- const DEFAULT_WORKER_ENVIRONMENT_NAME = "void_worker";
4223
- const DEFAULT_WORKER_OUT_DIR = "dist/ssr";
4224
- const DEV_ASSET_ROUTING_EXCLUSIONS = [
4225
- "!/@vite/*",
4226
- "!/@id/*",
4227
- "!/@fs/*",
4228
- "!/@react-refresh",
4229
- "!/@svelte/*",
4230
- "!/node_modules/*",
4231
- "!/__uno.css"
4232
- ];
4233
- function resolveSandboxSdkEntry() {
4234
- return fileURLToPath(import.meta.resolve(SANDBOX_SDK_IMPORT));
4235
- }
4236
- function createCloudflareBuiltinsExternalPlugin() {
4237
- return {
4238
- name: "void:cloudflare-builtins-external",
4239
- config(_, env) {
4240
- if (env.command !== "build") return;
4241
- return { build: { rollupOptions: { external: [CLOUDFLARE_BUILTIN_IMPORT] } } };
4242
- }
4243
- };
4244
- }
4245
- function createDefaultWorkerEnvironmentPlugin() {
4246
- return {
4247
- name: "void:default-worker-environment",
4248
- config(config) {
4249
- const currentEnvironment = config.environments?.[DEFAULT_WORKER_ENVIRONMENT_NAME];
4250
- return { environments: { [DEFAULT_WORKER_ENVIRONMENT_NAME]: {
4251
- ...currentEnvironment,
4252
- build: {
4253
- ...currentEnvironment?.build,
4254
- outDir: currentEnvironment?.build?.outDir ?? DEFAULT_WORKER_OUT_DIR
4255
- }
4256
- } } };
4257
- }
4258
- };
4259
- }
4260
- function createWorkerEnvironmentSanitizerPlugin(environmentNames) {
4261
- const names = new Set(environmentNames);
4262
- return {
4263
- name: "void:worker-environment-sanitizer",
4264
- enforce: "pre",
4265
- configResolved(config) {
4266
- for (const name of names) {
4267
- const resolve = config.environments[name]?.resolve;
4268
- if (resolve && "external" in resolve) resolve.external = [];
4269
- }
4270
- }
4271
- };
4272
- }
4273
- function createSandboxSdkResolvePlugin() {
4274
- let resolved;
4275
- return {
4276
- name: "void:sandbox-sdk-resolve",
4277
- enforce: "pre",
4278
- resolveId: {
4279
- filter: { id: SANDBOX_SDK_IMPORT_FILTER },
4280
- handler(id) {
4281
- if (id !== SANDBOX_SDK_IMPORT) return;
4282
- resolved ??= resolveSandboxSdkEntry();
4283
- return resolved;
4284
- }
4285
- }
4286
- };
4287
- }
4288
- function createWranglerCompatPersistPlugin(root, config) {
4289
- let persisted = false;
4290
- const persist = () => {
4291
- if (persisted || isNodeTarget(config.target)) return;
4292
- persisted = true;
4293
- ensureWranglerCompat(root, config);
4294
- };
4295
- return {
4296
- name: "void:wrangler-compat-persist",
4297
- buildStart() {
4298
- persist();
4299
- },
4300
- configureServer() {
4301
- persist();
4302
- }
4303
- };
4304
- }
4305
- /**
4306
- * Read Void auth credentials for local dev AI proxy access.
4307
- * Returns a discriminated union so callers can distinguish
4308
- * "no token" from "no project" for targeted onboarding guidance.
4309
- */
4310
- function readVoidDevCredentials(root) {
4311
- const token = getToken(root);
4312
- if (!token) return { status: "no-token" };
4313
- const apiUrl = getApiUrl(root);
4314
- const project = readProjectConfig(root);
4315
- if (!project) return {
4316
- status: "no-project",
4317
- apiUrl,
4318
- token
4319
- };
4320
- const proxyUrl = getProxyUrl(root);
4321
- ensurePlatformProxyAccess(apiUrl, proxyUrl);
4322
- return {
4323
- status: "ok",
4324
- token,
4325
- projectId: project.projectId,
4326
- proxyUrl,
4327
- accessCredentials: scopedAccessCredentials(process.env, proxyUrl)
4328
- };
4329
- }
4330
- /**
4331
- * Interactive AI setup prompt shown during dev server startup when the user
4332
- * has a token but no linked project. Runs as fire-and-forget so it doesn't
4333
- * block the server. On completion, writes project.json and restarts the server.
4577
+ * real asset directory rather than a dev-server loopback. `routeEnvironment`
4578
+ * is the existing `command === 'serve' && !isPreview` distinction.
4579
+ *
4580
+ * Without an assets binding there is no loopback, so injecting the var would
4581
+ * only arm a path that must then fail; gate on it instead.
4334
4582
  */
4335
- async function runAiSetupPrompt(root, token, apiUrl, server) {
4336
- const { select, text, isCancel, log } = await import("./prompts-2TGE6oXx.mjs");
4337
- const { PlatformClient } = await import("./client-B9UBI03V.mjs");
4338
- const { attachSelectedPlatform, writeProjectConfig } = await import("./project-DsblYifa.mjs");
4339
- const { inferDefaultSlug } = await import("./resolve-project-DtbidV4y.mjs");
4340
- const client = new PlatformClient(token, apiUrl);
4341
- let projects;
4342
- try {
4343
- projects = await client.listProjects();
4344
- } catch {
4345
- voidWarn("Failed to fetch projects. Run `void project link` to link a project manually.");
4346
- return;
4583
+ function shouldInjectEmailDevInboxVar(routeEnvironment, needsEmail, assetsBinding) {
4584
+ return routeEnvironment === "dev" && !!needsEmail && typeof assetsBinding === "string" && !!assetsBinding;
4585
+ }
4586
+ //#endregion
4587
+ //#region src/plugin-worker-bindings.ts
4588
+ function mergeWorkerBindings(resolved, input) {
4589
+ const { websocketRoutes, routeEnvironment, workerConfig, envSchemaFacts, authEnabled, viteCommand, bindingNames, dialect, application, bindings, directCloudflareDestination, config, devTriggerToken, queues, durableObjects, sandboxConfig, shouldEmitLive, liveConfig, root, remoteMode } = input;
4590
+ const environmentWebsocketRoutes = websocketRoutes.filter((route) => isRouteEnabledForEnvironment(route, routeEnvironment));
4591
+ const result = { ...workerConfig };
4592
+ result.compatibility_flags = [.../* @__PURE__ */ new Set([...result.compatibility_flags ?? [], ...resolved.compatibility_flags ?? []])];
4593
+ resolved.compatibility_flags = [];
4594
+ if (envSchemaFacts || authEnabled && process.env["VOID_CLOUDFLARE_DEPLOY_BUILD"] === "1") {
4595
+ const plaintextVars = {
4596
+ ...resolved.vars ?? {},
4597
+ ...result.vars ?? {}
4598
+ };
4599
+ const plaintextServerKeys = (envSchemaFacts?.serverKeys ?? []).filter((key) => key in plaintextVars);
4600
+ if (viteCommand === "build" && plaintextServerKeys.length > 0) throw new Error(`Server env keys cannot be Worker plaintext vars: ${plaintextServerKeys.join(", ")}. Remove them from worker.vars/wrangler vars and set them with the remote secret store.`);
4601
+ const configuredRequired = resolved.secrets?.required ?? [];
4602
+ const required = [.../* @__PURE__ */ new Set([
4603
+ ...configuredRequired,
4604
+ ...envSchemaFacts?.requiredServerKeys ?? [],
4605
+ ...authEnabled && process.env["VOID_CLOUDFLARE_DEPLOY_BUILD"] === "1" ? ["BETTER_AUTH_SECRET"] : []
4606
+ ])];
4607
+ if (required.length > 0) result.secrets = { required };
4347
4608
  }
4348
- const options = [{
4349
- value: "__create__",
4350
- label: "Create new project"
4351
- }, ...projects.map((p) => ({
4352
- value: p.id,
4353
- label: p.slug
4354
- }))];
4355
- console.log();
4356
- log.info("[void] AI requires a linked project. Let's set one up:");
4357
- const result = await select({
4358
- message: "Select a project:",
4359
- options
4609
+ if (workerConfig.assets && resolved.assets) {
4610
+ delete resolved.assets.not_found_handling;
4611
+ delete resolved.assets.run_worker_first;
4612
+ }
4613
+ const d1Name = bindingNames?.d1 ?? "DB";
4614
+ const d1 = [];
4615
+ if (dialect === "sqlite" && application.needsDatabase && !hasBinding(resolved.d1_databases, d1Name)) d1.push({
4616
+ binding: d1Name,
4617
+ database_name: "default",
4618
+ database_id: "local"
4360
4619
  });
4361
- if (isCancel(result)) return;
4362
- let projectId;
4363
- let slug;
4364
- if (result === "__create__") {
4365
- const { validateProjectSlug } = await import("./project-slug-DofjTFd-.mjs");
4366
- const { createProjectWithSlugRetry } = await import("./create-project-FWbf5Olo.mjs");
4367
- let created;
4368
- try {
4369
- created = await createProjectWithSlugRetry(client, inferDefaultSlug(root), async (defaultSlug) => {
4370
- const slugResult = await text({
4371
- message: "Choose a slug for your new Void project:",
4372
- placeholder: "my-app",
4373
- initialValue: defaultSlug,
4374
- validate: validateProjectSlug
4375
- });
4376
- if (isCancel(slugResult) || typeof slugResult === "symbol") return;
4377
- return slugResult.trim();
4378
- });
4379
- } catch (err) {
4380
- voidError(`project: Failed to create project: '${err instanceof Error ? err.message : String(err)}'.`);
4381
- return;
4620
+ if (d1.length > 0) result.d1_databases = d1;
4621
+ const kvName = bindingNames?.kv ?? "KV";
4622
+ const kv = [];
4623
+ if (bindings.needsKV && !hasBinding(resolved.kv_namespaces, kvName)) kv.push({
4624
+ binding: kvName,
4625
+ id: "local"
4626
+ });
4627
+ if (kv.length > 0) result.kv_namespaces = kv;
4628
+ const r2Name = bindingNames?.r2 ?? "STORAGE";
4629
+ if (bindings.needsR2 && !hasBinding(resolved.r2_buckets, r2Name)) result.r2_buckets = [{
4630
+ binding: r2Name,
4631
+ bucket_name: "default"
4632
+ }];
4633
+ const resolvedAi = resolved.ai;
4634
+ const resultAi = result.ai;
4635
+ const aiBindingName = resolveWorkersAiBindingName(resolvedAi, bindingNames?.ai);
4636
+ if (viteCommand === "serve" && bindings.needsAI && directCloudflareDestination && !resultAi && typeof resolvedAi === "object" && resolvedAi !== null && "binding" in resolvedAi && typeof resolvedAi.binding === "string") result.ai = {
4637
+ ...resolvedAi,
4638
+ remote: true
4639
+ };
4640
+ else if (shouldBindWorkersAi(viteCommand, bindings.needsAI, resolvedAi, resultAi, directCloudflareDestination)) result.ai = {
4641
+ binding: aiBindingName,
4642
+ ...viteCommand === "serve" && { remote: true }
4643
+ };
4644
+ if (aiBindingName !== "AI" || config.ai?.gateway) {
4645
+ const vars = result.vars ?? {};
4646
+ if (aiBindingName !== "AI") vars.__VOID_AI_BINDING = aiBindingName;
4647
+ if (config.ai?.gateway) vars.__VOID_AI_GATEWAY = config.ai.gateway;
4648
+ result.vars = vars;
4649
+ }
4650
+ const emailDevInboxBinding = workerConfig.assets?.binding ?? resolved.assets?.binding;
4651
+ if (shouldInjectEmailDevInboxVar(routeEnvironment, bindings.needsEmail, emailDevInboxBinding)) {
4652
+ const vars = result.vars ?? {};
4653
+ vars[DEV_INBOX_ENV_VAR] = emailDevInboxBinding;
4654
+ vars[DEV_INBOX_TOKEN_ENV_VAR] = devTriggerToken;
4655
+ result.vars = vars;
4656
+ }
4657
+ if (viteCommand === "serve" && bindings.needsEmail) {
4658
+ const sendEmail = resolved.send_email;
4659
+ if (Array.isArray(sendEmail)) {
4660
+ const kept = sendEmail.filter((entry) => entry?.name !== SEND_EMAIL_BINDING);
4661
+ if (kept.length > 0) resolved.send_email = kept;
4662
+ else delete resolved.send_email;
4382
4663
  }
4383
- if (!created) return;
4384
- projectId = created.id;
4385
- slug = created.slug;
4386
- } else {
4387
- const project = projects.find((p) => p.id === result);
4388
- projectId = project.id;
4389
- slug = project.slug;
4390
4664
  }
4391
- writeProjectConfig(root, attachSelectedPlatform(root, {
4392
- projectId,
4393
- slug
4394
- }, client.apiUrl));
4395
- log.success(`Linked to ${import_picocolors.default.blue(slug)}`);
4396
- await server.restart();
4665
+ if (viteCommand === "serve" && config.email?.from) {
4666
+ const vars = result.vars ?? {};
4667
+ vars.__VOID_EMAIL_FROM = config.email.from;
4668
+ result.vars = vars;
4669
+ }
4670
+ if (queues.length > 0) result.queues = {
4671
+ producers: queues.map((q) => ({
4672
+ queue: q.name,
4673
+ binding: q.bindingName
4674
+ })),
4675
+ consumers: queues.map((q) => ({
4676
+ queue: q.name,
4677
+ ...q.maxBatchSize != null && { max_batch_size: q.maxBatchSize },
4678
+ ...q.maxBatchTimeout != null && { max_batch_timeout: q.maxBatchTimeout },
4679
+ ...q.maxRetries != null && { max_retries: q.maxRetries },
4680
+ ...q.retryDelay != null && { retry_delay: q.retryDelay }
4681
+ }))
4682
+ };
4683
+ if (environmentWebsocketRoutes.length > 0) {
4684
+ const existingDurableObjectBindings = resolved.durable_objects?.bindings ?? [];
4685
+ for (const route of environmentWebsocketRoutes) {
4686
+ const conflict = existingDurableObjectBindings.find((binding) => binding.name === route.bindingName && binding.class_name !== route.className);
4687
+ if (conflict) throw new Error(`websocket: binding '${route.bindingName}' is already configured for class '${conflict.class_name}', but '${route.filePath}' requires '${route.className}'.`);
4688
+ }
4689
+ const missingDurableObjectBindings = environmentWebsocketRoutes.filter((route) => !existingDurableObjectBindings.some((binding) => binding.name === route.bindingName)).map((route) => ({
4690
+ name: route.bindingName,
4691
+ class_name: route.className
4692
+ }));
4693
+ if (missingDurableObjectBindings.length > 0) result.durable_objects = { bindings: [...existingDurableObjectBindings, ...missingDurableObjectBindings] };
4694
+ const currentMigrations = resolved.migrations;
4695
+ const planned = appendSqliteClassMigration(Array.isArray(currentMigrations) ? currentMigrations : [], environmentWebsocketRoutes.map((route) => route.className), "void-ws-v1", existingDurableObjectBindings.map((binding) => binding.class_name));
4696
+ if (planned.added) result.migrations = planned.migrations;
4697
+ }
4698
+ if (durableObjects.length > 0) {
4699
+ const existingBindings = (result.durable_objects ?? resolved.durable_objects)?.bindings ?? [];
4700
+ for (const module of durableObjects) {
4701
+ const conflict = existingBindings.find((binding) => binding.name === module.bindingName && binding.class_name !== module.className);
4702
+ if (conflict) throw new Error(`durable: binding '${module.bindingName}' is already configured for class '${conflict.class_name}', but durable-objects/ requires '${module.className}'.`);
4703
+ }
4704
+ const additions = durableObjects.filter((module) => !existingBindings.some((binding) => binding.name === module.bindingName)).map((module) => ({
4705
+ name: module.bindingName,
4706
+ class_name: module.className
4707
+ }));
4708
+ if (additions.length > 0) result.durable_objects = { bindings: [...existingBindings, ...additions] };
4709
+ const currentMigrations = result.migrations ?? resolved.migrations;
4710
+ const existingMigrations = Array.isArray(currentMigrations) ? currentMigrations : [];
4711
+ for (const module of durableObjects) if (existingMigrations.find((migration) => migration.tag === module.migrationTag && !migration.new_sqlite_classes?.includes(module.className))) throw new Error(`durable: migration tag '${module.migrationTag}' is already used by another Durable Object migration.`);
4712
+ const migrationAdditions = durableObjects.filter((module) => !existingMigrations.some((migration) => migration.tag === module.migrationTag || migration.new_sqlite_classes?.includes(module.className))).map((module) => ({
4713
+ tag: module.migrationTag,
4714
+ new_sqlite_classes: [module.className]
4715
+ }));
4716
+ if (migrationAdditions.length > 0) result.migrations = [...existingMigrations, ...migrationAdditions];
4717
+ }
4718
+ if (bindings.needsSandbox && sandboxConfig) {
4719
+ assertSandboxNamespaceConfig({
4720
+ ...resolved,
4721
+ ...result
4722
+ }, sandboxConfig);
4723
+ const existingDurableObjectBindings = (result.durable_objects ?? resolved.durable_objects)?.bindings ?? [];
4724
+ if (!existingDurableObjectBindings.some((binding) => binding.name === sandboxConfig.binding)) result.durable_objects = { bindings: [...existingDurableObjectBindings, {
4725
+ name: sandboxConfig.binding,
4726
+ class_name: sandboxConfig.className
4727
+ }] };
4728
+ result.containers = [...(result.containers ?? resolved.containers ?? []).filter((container) => container.class_name !== sandboxConfig.className), sandboxContainerConfig(sandboxConfig)];
4729
+ resolved.containers = [];
4730
+ const currentMigrations = result.migrations ?? resolved.migrations;
4731
+ const existingMigrations = Array.isArray(currentMigrations) ? currentMigrations : [];
4732
+ if (!hasSandboxSqliteMigration(existingMigrations, sandboxConfig.className)) result.migrations = [...existingMigrations, {
4733
+ tag: SANDBOX_MIGRATION_TAG,
4734
+ new_sqlite_classes: [sandboxConfig.className]
4735
+ }];
4736
+ }
4737
+ if (shouldEmitLive() && liveConfig) {
4738
+ const existingDurableObjectBindings = (result.durable_objects ?? resolved.durable_objects)?.bindings ?? [];
4739
+ if (!existingDurableObjectBindings.some((binding) => binding.name === liveConfig.bindingName)) result.durable_objects = { bindings: [...existingDurableObjectBindings, {
4740
+ name: liveConfig.bindingName,
4741
+ class_name: liveConfig.className
4742
+ }] };
4743
+ const currentMigrations = result.migrations ?? resolved.migrations;
4744
+ const existingMigrations = Array.isArray(currentMigrations) ? currentMigrations : [];
4745
+ if (!hasDurableObjectSqliteMigration(existingMigrations, liveConfig.className)) result.migrations = [...existingMigrations, {
4746
+ tag: nextDurableObjectMigrationTag(existingMigrations, LIVE_MIGRATION_TAG),
4747
+ new_sqlite_classes: [liveConfig.className]
4748
+ }];
4749
+ }
4750
+ if (viteCommand === "serve" && bindings.needsAI && !directCloudflareDestination) {
4751
+ const credentials = readVoidDevCredentials(root);
4752
+ if (credentials.status === "ok") {
4753
+ const vars = result.vars ?? {};
4754
+ vars.__VOID_TOKEN = credentials.token;
4755
+ vars.__VOID_PROJECT_ID = credentials.projectId;
4756
+ vars.__VOID_PROXY_URL = credentials.proxyUrl;
4757
+ if (credentials.accessCredentials) vars.__VOID_ACCESS_CREDENTIALS = credentials.accessCredentials;
4758
+ result.vars = vars;
4759
+ }
4760
+ }
4761
+ if (viteCommand === "serve" && remoteMode && (bindings.needsD1 || bindings.needsKV || bindings.needsR2)) {
4762
+ const credentials = readVoidDevCredentials(root);
4763
+ if (credentials.status === "ok") {
4764
+ const vars = result.vars ?? {};
4765
+ vars.__VOID_PROXY_URL = credentials.proxyUrl;
4766
+ if (credentials.accessCredentials) vars.__VOID_ACCESS_CREDENTIALS = credentials.accessCredentials;
4767
+ vars.__VOID_TOKEN = credentials.token;
4768
+ vars.__VOID_PROJECT_ID = credentials.projectId;
4769
+ vars.__VOID_REMOTE = "1";
4770
+ result.vars = vars;
4771
+ }
4772
+ }
4773
+ input.versionMetadataBinding = selectNativeVersionMetadataBinding(resolved, {
4774
+ nativeBuild: viteCommand === "build" && process.env["VOID_CLOUDFLARE_DEPLOY_BUILD"] === "1",
4775
+ preserveBindings: process.env[CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV] === "1"
4776
+ });
4777
+ if (input.versionMetadataBinding) {
4778
+ const owner = findBindingNameOwner(result, "version_metadata", input.versionMetadataBinding);
4779
+ if (owner) throw new Error(`Cloudflare version metadata binding ${input.versionMetadataBinding} conflicts with ${owner}.`);
4780
+ if (!resolved.version_metadata) result.version_metadata = { binding: input.versionMetadataBinding };
4781
+ }
4782
+ finalizeDurableObjectPatch(resolved, result, [
4783
+ ...environmentWebsocketRoutes.map((route) => ({
4784
+ name: route.bindingName,
4785
+ class_name: route.className
4786
+ })),
4787
+ ...durableObjects.map((module) => ({
4788
+ name: module.bindingName,
4789
+ class_name: module.className
4790
+ })),
4791
+ ...bindings.needsSandbox && sandboxConfig ? [{
4792
+ name: sandboxConfig.binding,
4793
+ class_name: sandboxConfig.className
4794
+ }] : [],
4795
+ ...shouldEmitLive() && liveConfig ? [{
4796
+ name: liveConfig.bindingName,
4797
+ class_name: liveConfig.className
4798
+ }] : []
4799
+ ]);
4800
+ return result;
4397
4801
  }
4802
+ //#endregion
4803
+ //#region src/index.ts
4398
4804
  /**
4399
4805
  * Void plugin — the main entry point.
4400
4806
  *
@@ -4484,81 +4890,8 @@ function voidPlugin(options) {
4484
4890
  if (detected && bindings.needsLive) throw new Error("live: void/live is currently supported in Void apps only.");
4485
4891
  const hasInboundEmail = hasEmailHandlers(application.email);
4486
4892
  if (detected && hasInboundEmail) throw new Error("email: inbound email handlers are only supported in Void apps. Remove the email/ directory — `sendEmail` from void/email keeps working.");
4487
- const STUB_PREFIX = "\0void-client-stub:";
4488
- const SERVER_ONLY_STUBS = {
4489
- "void/db": "export const db = {}; export const createDb = () => ({});",
4490
- "void/kv": "export const kv = {}; export const createKV = () => ({});",
4491
- "void/ai": "export const ai = {};",
4492
- "void/queues": "export const queues = {};",
4493
- "void/storage": "export const storage = {}; export const createStorage = () => ({});",
4494
- "void/isr": "export const revalidate = async () => {};",
4495
- "void/sandbox": `
4496
- export class SandboxLimitError extends Error {
4497
- constructor(reason = 'concurrency', message) {
4498
- super(message ?? 'Sandbox limit reached: ' + reason);
4499
- this.name = 'SandboxLimitError';
4500
- this.reason = reason;
4501
- }
4502
- }
4503
- export class SandboxUnavailableError extends Error {
4504
- constructor(message = 'Managed Sandbox is unavailable on this Void platform.') {
4505
- super(message);
4506
- this.name = 'SandboxUnavailableError';
4507
- }
4508
- }
4509
- export async function getSandbox() {
4510
- throw new Error('void/sandbox is server-only and cannot be imported by browser code.');
4511
- }
4512
- export const sandbox = { get: getSandbox };
4513
- `,
4514
- "void/email": `
4515
- const serverOnly = (name) => () => {
4516
- throw new Error(name + ' is server-only and cannot be imported by browser code.');
4517
- };
4518
- export const sendEmail = serverOnly('sendEmail');
4519
- export const createEmail = serverOnly('createEmail');
4520
- export const defineEmail = serverOnly('defineEmail');
4521
- export const parseEmail = serverOnly('parseEmail');
4522
- export const replyEmail = serverOnly('replyEmail');
4523
- export const createMessageProxy = serverOnly('createMessageProxy');
4524
- `,
4525
- "void/email/testing": `
4526
- const serverOnly = (name) => () => {
4527
- throw new Error(name + ' is server-only and cannot be imported by browser code.');
4528
- };
4529
- export const createEmailTestHarness = serverOnly('createEmailTestHarness');
4530
- export const createInboundTestHarness = serverOnly('createInboundTestHarness');
4531
- `,
4532
- "void/live": "",
4533
- "void/durable": ""
4534
- };
4535
- const STUB_RESOLVE_FILTER = new RegExp(`^(${Object.keys(SERVER_ONLY_STUBS).join("|").replaceAll("/", "\\/")})$`);
4536
- const STUB_LOAD_FILTER = new RegExp(`^\\0void-client-stub:`);
4537
- const clientStubsPlugin = {
4538
- name: "void:client-stubs",
4539
- enforce: "pre",
4540
- resolveId: {
4541
- filter: { id: STUB_RESOLVE_FILTER },
4542
- handler(id) {
4543
- if (this.environment?.name === "client") {
4544
- if (id === "void/live" || id === "void/durable") this.error(id === "void/live" ? "void/live is server-only and cannot be imported by browser code. Import void/live/client instead." : "void/durable is server-only and cannot be imported by browser code.");
4545
- return STUB_PREFIX + id;
4546
- }
4547
- }
4548
- },
4549
- load: {
4550
- filter: { id: STUB_LOAD_FILTER },
4551
- handler(id) {
4552
- return SERVER_ONLY_STUBS[id.slice(18)] ?? "export default {};";
4553
- }
4554
- }
4555
- };
4556
- const serverRuntimeOptimizerPlugin = {
4557
- name: "void:server-runtime-optimizer",
4558
- configEnvironment(name) {
4559
- if (name !== "client") return { optimizeDeps: { exclude: ["void"] } };
4560
- }
4561
- };
4893
+ const clientStubsPlugin = createClientStubsPlugin();
4894
+ const serverRuntimeOptimizerPlugin = createServerRuntimeOptimizerPlugin();
4562
4895
  const queues = framework ? [] : application.queues;
4563
4896
  const websocketRoutes = framework ? [] : application.allWebsockets;
4564
4897
  const durableObjects = application.durableObjects;
@@ -4585,105 +4918,25 @@ function voidPlugin(options) {
4585
4918
  ]
4586
4919
  }) : existingLiveConfig : void 0;
4587
4920
  const shouldEmitLive = () => bindings.needsLive || existingLiveConfig !== void 0;
4588
- if (detected && (detected.class === "b" || detected.class === "c")) {
4589
- const frameworkPersistRoot = localD1.persistRoot;
4590
- const syncFrameworkCompat = (compat) => {
4591
- if (ensureWranglerCompatibilityDate(root, compat.compatibilityDate)) voidLog("Synced compatibility_date to Cloudflare config");
4592
- };
4593
- const wranglerCompatPlugin = {
4594
- name: "void:wrangler-compat",
4595
- enforce: "pre",
4596
- config(_, env) {
4597
- if (env.command === "serve" && directCloudflareDestination && bindings.needsAI) process.env.CLOUDFLARE_AUTH_USE_KEYRING = "true";
4598
- syncFrameworkCompat(ensureWranglerCompat(root, config));
4599
- if (env.command !== "serve") return;
4600
- if (ensureWranglerNodejsAlsFlag(root)) voidLog("Synced nodejs_als to Cloudflare config");
4601
- },
4602
- buildStart() {
4603
- syncFrameworkCompat(ensureWranglerCompat(root, config));
4604
- },
4605
- configureServer() {
4606
- syncFrameworkCompat(ensureWranglerCompat(root, config));
4607
- }
4608
- };
4609
- const syncFrameworkBindings = (command) => {
4610
- if (command !== "serve") return;
4611
- if (syncWranglerBindings(root, {
4612
- ...bindings,
4613
- needsD1: dialect === "sqlite" && application.needsDatabase
4614
- }, {
4615
- bindingNames,
4616
- ...directCloudflareDestination && bindings.needsAI ? { directCloudflareAi: { gateway: config.ai?.gateway } } : {},
4617
- websockets: websocketRoutes,
4618
- sandbox: sandboxConfig,
4619
- live: liveConfig
4620
- })) voidLog("Synced bindings to Cloudflare config");
4621
- };
4622
- const wranglerSyncPlugin = {
4623
- name: "void:wrangler-sync",
4624
- enforce: "pre",
4625
- config(_, env) {
4626
- syncFrameworkBindings(env.command);
4627
- },
4628
- configResolved(resolvedConfig) {
4629
- syncFrameworkCompat(readWranglerCompat(root, config));
4630
- syncFrameworkBindings(resolvedConfig.command);
4631
- }
4632
- };
4633
- const nuxtVite8WebSocketCompatPlugin = detected.name === "nuxt" ? {
4634
- name: "void:nuxt-vite8-websocket-compat",
4635
- enforce: "post",
4636
- configResolved(resolvedConfig) {
4637
- if (!resolvedConfig.build.ssr) return;
4638
- if (typeof resolvedConfig.server.hmr === "object" && resolvedConfig.server.hmr.port !== void 0) resolvedConfig.server.hmr = {
4639
- ...resolvedConfig.server.hmr,
4640
- server: void 0
4641
- };
4642
- if (typeof resolvedConfig.server.ws === "object" && resolvedConfig.server.ws.port !== void 0) resolvedConfig.server.ws = {
4643
- ...resolvedConfig.server.ws,
4644
- server: void 0
4645
- };
4646
- }
4647
- } : void 0;
4648
- const svelteKitHooksInjectPlugin = {
4649
- name: "void:sveltekit-hooks-inject",
4650
- apply: "serve",
4651
- transform(code, id) {
4652
- if (detected.class !== "b") return;
4653
- if (!id.includes(".svelte-kit/generated/server/internal")) return;
4654
- return injectSvelteKitHooks(code, id, frameworkTriggersDispatchPath(root));
4655
- }
4656
- };
4657
- return [
4658
- createCloudflareBuiltinsExternalPlugin(),
4659
- wranglerCompatPlugin,
4660
- wranglerSyncPlugin,
4661
- ...nuxtVite8WebSocketCompatPlugin ? [nuxtVite8WebSocketCompatPlugin] : [],
4662
- svelteKitHooksInjectPlugin,
4663
- clientStubsPlugin,
4664
- serverRuntimeOptimizerPlugin,
4665
- bindingRuntimePlugin(bindingNames),
4666
- envSchemaPlugin(root),
4667
- envClientGuardPlugin(root),
4668
- envClientFoldPlugin(),
4669
- drizzlePlugin(root, false, dialect, {
4670
- d1BindingName: bindingNames?.d1,
4671
- forceVirtualDb: authEnabled || bindingNames?.d1 !== void 0,
4672
- localD1DatabaseId: localD1.databaseId,
4673
- localD1PersistRoot: frameworkPersistRoot
4674
- }),
4675
- migrationPlugin(root, dialect, localD1.databaseId, frameworkPersistRoot, { enabled: false }),
4676
- devTriggersHintPlugin(root, {
4677
- framework: true,
4678
- devTriggerToken
4679
- }),
4680
- frameworkTriggersPlugin(root, { devTriggerToken }),
4681
- ...detected.name === "nuxt" ? [nitroDevTriggerInjectPlugin(root, { serverDir: "server" })] : [],
4682
- ...detected.name === "analog" ? [analogDevTriggerInjectPlugin(root)] : [],
4683
- ...detected.name === "astro" ? [astroDevTriggerInjectPlugin(root)] : [],
4684
- implicitDeployGuard
4685
- ];
4686
- }
4921
+ if (detected && (detected.class === "b" || detected.class === "c")) return createFrameworkPlugins({
4922
+ localD1,
4923
+ root,
4924
+ directCloudflareDestination,
4925
+ bindings,
4926
+ config,
4927
+ dialect,
4928
+ application,
4929
+ bindingNames,
4930
+ websocketRoutes,
4931
+ sandboxConfig,
4932
+ liveConfig,
4933
+ detected,
4934
+ clientStubsPlugin,
4935
+ serverRuntimeOptimizerPlugin,
4936
+ authEnabled,
4937
+ devTriggerToken,
4938
+ implicitDeployGuard
4939
+ });
4687
4940
  const remoteMode = resolveRemoteMode(config.remote);
4688
4941
  const workerConfig = {};
4689
4942
  const jobs = application.jobs;
@@ -4705,217 +4958,34 @@ function voidPlugin(options) {
4705
4958
  const outDir = join(root, ".void");
4706
4959
  mkdirSync(outDir, { recursive: true });
4707
4960
  function mergeBindings(resolved) {
4708
- const environmentWebsocketRoutes = websocketRoutes.filter((route) => isRouteEnabledForEnvironment(route, routeEnvironment));
4709
- const result = { ...workerConfig };
4710
- result.compatibility_flags = [.../* @__PURE__ */ new Set([...result.compatibility_flags ?? [], ...resolved.compatibility_flags ?? []])];
4711
- resolved.compatibility_flags = [];
4712
- if (envSchemaFacts || authEnabled && process.env["VOID_CLOUDFLARE_DEPLOY_BUILD"] === "1") {
4713
- const plaintextVars = {
4714
- ...resolved.vars ?? {},
4715
- ...result.vars ?? {}
4716
- };
4717
- const plaintextServerKeys = (envSchemaFacts?.serverKeys ?? []).filter((key) => key in plaintextVars);
4718
- if (viteCommand === "build" && plaintextServerKeys.length > 0) throw new Error(`Server env keys cannot be Worker plaintext vars: ${plaintextServerKeys.join(", ")}. Remove them from worker.vars/wrangler vars and set them with the remote secret store.`);
4719
- const configuredRequired = resolved.secrets?.required ?? [];
4720
- const required = [.../* @__PURE__ */ new Set([
4721
- ...configuredRequired,
4722
- ...envSchemaFacts?.requiredServerKeys ?? [],
4723
- ...authEnabled && process.env["VOID_CLOUDFLARE_DEPLOY_BUILD"] === "1" ? ["BETTER_AUTH_SECRET"] : []
4724
- ])];
4725
- if (required.length > 0) result.secrets = { required };
4726
- }
4727
- if (workerConfig.assets && resolved.assets) {
4728
- delete resolved.assets.not_found_handling;
4729
- delete resolved.assets.run_worker_first;
4730
- }
4731
- const d1Name = bindingNames?.d1 ?? "DB";
4732
- const d1 = [];
4733
- if (dialect === "sqlite" && application.needsDatabase && !hasBinding(resolved.d1_databases, d1Name)) d1.push({
4734
- binding: d1Name,
4735
- database_name: "default",
4736
- database_id: "local"
4737
- });
4738
- if (d1.length > 0) result.d1_databases = d1;
4739
- const kvName = bindingNames?.kv ?? "KV";
4740
- const kv = [];
4741
- if (bindings.needsKV && !hasBinding(resolved.kv_namespaces, kvName)) kv.push({
4742
- binding: kvName,
4743
- id: "local"
4744
- });
4745
- if (kv.length > 0) result.kv_namespaces = kv;
4746
- const r2Name = bindingNames?.r2 ?? "STORAGE";
4747
- if (bindings.needsR2 && !hasBinding(resolved.r2_buckets, r2Name)) result.r2_buckets = [{
4748
- binding: r2Name,
4749
- bucket_name: "default"
4750
- }];
4751
- const resolvedAi = resolved.ai;
4752
- const resultAi = result.ai;
4753
- const aiBindingName = resolveWorkersAiBindingName(resolvedAi, bindingNames?.ai);
4754
- if (viteCommand === "serve" && bindings.needsAI && directCloudflareDestination && !resultAi && typeof resolvedAi === "object" && resolvedAi !== null && "binding" in resolvedAi && typeof resolvedAi.binding === "string") result.ai = {
4755
- ...resolvedAi,
4756
- remote: true
4757
- };
4758
- else if (shouldBindWorkersAi(viteCommand, bindings.needsAI, resolvedAi, resultAi, directCloudflareDestination)) result.ai = {
4759
- binding: aiBindingName,
4760
- ...viteCommand === "serve" && { remote: true }
4761
- };
4762
- if (aiBindingName !== "AI" || config.ai?.gateway) {
4763
- const vars = result.vars ?? {};
4764
- if (aiBindingName !== "AI") vars.__VOID_AI_BINDING = aiBindingName;
4765
- if (config.ai?.gateway) vars.__VOID_AI_GATEWAY = config.ai.gateway;
4766
- result.vars = vars;
4767
- }
4768
- const emailDevInboxBinding = workerConfig.assets?.binding ?? resolved.assets?.binding;
4769
- if (shouldInjectEmailDevInboxVar(routeEnvironment, bindings.needsEmail, emailDevInboxBinding)) {
4770
- const vars = result.vars ?? {};
4771
- vars[DEV_INBOX_ENV_VAR] = emailDevInboxBinding;
4772
- vars[DEV_INBOX_TOKEN_ENV_VAR] = devTriggerToken;
4773
- result.vars = vars;
4774
- }
4775
- if (viteCommand === "serve" && bindings.needsEmail) {
4776
- const sendEmail = resolved.send_email;
4777
- if (Array.isArray(sendEmail)) {
4778
- const kept = sendEmail.filter((entry) => entry?.name !== SEND_EMAIL_BINDING);
4779
- if (kept.length > 0) resolved.send_email = kept;
4780
- else delete resolved.send_email;
4781
- }
4782
- }
4783
- if (viteCommand === "serve" && config.email?.from) {
4784
- const vars = result.vars ?? {};
4785
- vars.__VOID_EMAIL_FROM = config.email.from;
4786
- result.vars = vars;
4787
- }
4788
- if (queues.length > 0) result.queues = {
4789
- producers: queues.map((q) => ({
4790
- queue: q.name,
4791
- binding: q.bindingName
4792
- })),
4793
- consumers: queues.map((q) => ({
4794
- queue: q.name,
4795
- ...q.maxBatchSize != null && { max_batch_size: q.maxBatchSize },
4796
- ...q.maxBatchTimeout != null && { max_batch_timeout: q.maxBatchTimeout },
4797
- ...q.maxRetries != null && { max_retries: q.maxRetries },
4798
- ...q.retryDelay != null && { retry_delay: q.retryDelay }
4799
- }))
4800
- };
4801
- if (environmentWebsocketRoutes.length > 0) {
4802
- const existingDurableObjectBindings = resolved.durable_objects?.bindings ?? [];
4803
- for (const route of environmentWebsocketRoutes) {
4804
- const conflict = existingDurableObjectBindings.find((binding) => binding.name === route.bindingName && binding.class_name !== route.className);
4805
- if (conflict) throw new Error(`websocket: binding '${route.bindingName}' is already configured for class '${conflict.class_name}', but '${route.filePath}' requires '${route.className}'.`);
4806
- }
4807
- const missingDurableObjectBindings = environmentWebsocketRoutes.filter((route) => !existingDurableObjectBindings.some((binding) => binding.name === route.bindingName)).map((route) => ({
4808
- name: route.bindingName,
4809
- class_name: route.className
4810
- }));
4811
- if (missingDurableObjectBindings.length > 0) result.durable_objects = { bindings: [...existingDurableObjectBindings, ...missingDurableObjectBindings] };
4812
- const currentMigrations = resolved.migrations;
4813
- const planned = appendSqliteClassMigration(Array.isArray(currentMigrations) ? currentMigrations : [], environmentWebsocketRoutes.map((route) => route.className), "void-ws-v1", existingDurableObjectBindings.map((binding) => binding.class_name));
4814
- if (planned.added) result.migrations = planned.migrations;
4815
- }
4816
- if (durableObjects.length > 0) {
4817
- const existingBindings = (result.durable_objects ?? resolved.durable_objects)?.bindings ?? [];
4818
- for (const module of durableObjects) {
4819
- const conflict = existingBindings.find((binding) => binding.name === module.bindingName && binding.class_name !== module.className);
4820
- if (conflict) throw new Error(`durable: binding '${module.bindingName}' is already configured for class '${conflict.class_name}', but durable-objects/ requires '${module.className}'.`);
4821
- }
4822
- const additions = durableObjects.filter((module) => !existingBindings.some((binding) => binding.name === module.bindingName)).map((module) => ({
4823
- name: module.bindingName,
4824
- class_name: module.className
4825
- }));
4826
- if (additions.length > 0) result.durable_objects = { bindings: [...existingBindings, ...additions] };
4827
- const currentMigrations = result.migrations ?? resolved.migrations;
4828
- const existingMigrations = Array.isArray(currentMigrations) ? currentMigrations : [];
4829
- for (const module of durableObjects) if (existingMigrations.find((migration) => migration.tag === module.migrationTag && !migration.new_sqlite_classes?.includes(module.className))) throw new Error(`durable: migration tag '${module.migrationTag}' is already used by another Durable Object migration.`);
4830
- const migrationAdditions = durableObjects.filter((module) => !existingMigrations.some((migration) => migration.tag === module.migrationTag || migration.new_sqlite_classes?.includes(module.className))).map((module) => ({
4831
- tag: module.migrationTag,
4832
- new_sqlite_classes: [module.className]
4833
- }));
4834
- if (migrationAdditions.length > 0) result.migrations = [...existingMigrations, ...migrationAdditions];
4835
- }
4836
- if (bindings.needsSandbox && sandboxConfig) {
4837
- assertSandboxNamespaceConfig({
4838
- ...resolved,
4839
- ...result
4840
- }, sandboxConfig);
4841
- const existingDurableObjectBindings = (result.durable_objects ?? resolved.durable_objects)?.bindings ?? [];
4842
- if (!existingDurableObjectBindings.some((binding) => binding.name === sandboxConfig.binding)) result.durable_objects = { bindings: [...existingDurableObjectBindings, {
4843
- name: sandboxConfig.binding,
4844
- class_name: sandboxConfig.className
4845
- }] };
4846
- result.containers = [...(result.containers ?? resolved.containers ?? []).filter((container) => container.class_name !== sandboxConfig.className), sandboxContainerConfig(sandboxConfig)];
4847
- resolved.containers = [];
4848
- const currentMigrations = result.migrations ?? resolved.migrations;
4849
- const existingMigrations = Array.isArray(currentMigrations) ? currentMigrations : [];
4850
- if (!hasSandboxSqliteMigration(existingMigrations, sandboxConfig.className)) result.migrations = [...existingMigrations, {
4851
- tag: SANDBOX_MIGRATION_TAG,
4852
- new_sqlite_classes: [sandboxConfig.className]
4853
- }];
4854
- }
4855
- if (shouldEmitLive() && liveConfig) {
4856
- const existingDurableObjectBindings = (result.durable_objects ?? resolved.durable_objects)?.bindings ?? [];
4857
- if (!existingDurableObjectBindings.some((binding) => binding.name === liveConfig.bindingName)) result.durable_objects = { bindings: [...existingDurableObjectBindings, {
4858
- name: liveConfig.bindingName,
4859
- class_name: liveConfig.className
4860
- }] };
4861
- const currentMigrations = result.migrations ?? resolved.migrations;
4862
- const existingMigrations = Array.isArray(currentMigrations) ? currentMigrations : [];
4863
- if (!hasDurableObjectSqliteMigration(existingMigrations, liveConfig.className)) result.migrations = [...existingMigrations, {
4864
- tag: nextDurableObjectMigrationTag(existingMigrations, LIVE_MIGRATION_TAG),
4865
- new_sqlite_classes: [liveConfig.className]
4866
- }];
4867
- }
4868
- if (viteCommand === "serve" && bindings.needsAI && !directCloudflareDestination) {
4869
- const credentials = readVoidDevCredentials(root);
4870
- if (credentials.status === "ok") {
4871
- const vars = result.vars ?? {};
4872
- vars.__VOID_TOKEN = credentials.token;
4873
- vars.__VOID_PROJECT_ID = credentials.projectId;
4874
- vars.__VOID_PROXY_URL = credentials.proxyUrl;
4875
- if (credentials.accessCredentials) vars.__VOID_ACCESS_CREDENTIALS = credentials.accessCredentials;
4876
- result.vars = vars;
4877
- }
4878
- }
4879
- if (viteCommand === "serve" && remoteMode && (bindings.needsD1 || bindings.needsKV || bindings.needsR2)) {
4880
- const credentials = readVoidDevCredentials(root);
4881
- if (credentials.status === "ok") {
4882
- const vars = result.vars ?? {};
4883
- vars.__VOID_PROXY_URL = credentials.proxyUrl;
4884
- if (credentials.accessCredentials) vars.__VOID_ACCESS_CREDENTIALS = credentials.accessCredentials;
4885
- vars.__VOID_TOKEN = credentials.token;
4886
- vars.__VOID_PROJECT_ID = credentials.projectId;
4887
- vars.__VOID_REMOTE = "1";
4888
- result.vars = vars;
4961
+ return mergeWorkerBindings(resolved, {
4962
+ websocketRoutes,
4963
+ routeEnvironment,
4964
+ workerConfig,
4965
+ envSchemaFacts,
4966
+ authEnabled,
4967
+ viteCommand,
4968
+ bindingNames,
4969
+ dialect,
4970
+ application,
4971
+ bindings,
4972
+ directCloudflareDestination,
4973
+ config,
4974
+ devTriggerToken,
4975
+ queues,
4976
+ durableObjects,
4977
+ sandboxConfig,
4978
+ shouldEmitLive,
4979
+ liveConfig,
4980
+ root,
4981
+ remoteMode,
4982
+ get versionMetadataBinding() {
4983
+ return versionMetadataBinding;
4984
+ },
4985
+ set versionMetadataBinding(value) {
4986
+ versionMetadataBinding = value;
4889
4987
  }
4890
- }
4891
- versionMetadataBinding = selectNativeVersionMetadataBinding(resolved, {
4892
- nativeBuild: viteCommand === "build" && process.env["VOID_CLOUDFLARE_DEPLOY_BUILD"] === "1",
4893
- preserveBindings: process.env[CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV] === "1"
4894
4988
  });
4895
- if (versionMetadataBinding) {
4896
- const owner = findBindingNameOwner(result, "version_metadata", versionMetadataBinding);
4897
- if (owner) throw new Error(`Cloudflare version metadata binding ${versionMetadataBinding} conflicts with ${owner}.`);
4898
- if (!resolved.version_metadata) result.version_metadata = { binding: versionMetadataBinding };
4899
- }
4900
- finalizeDurableObjectPatch(resolved, result, [
4901
- ...environmentWebsocketRoutes.map((route) => ({
4902
- name: route.bindingName,
4903
- class_name: route.className
4904
- })),
4905
- ...durableObjects.map((module) => ({
4906
- name: module.bindingName,
4907
- class_name: module.className
4908
- })),
4909
- ...bindings.needsSandbox && sandboxConfig ? [{
4910
- name: sandboxConfig.binding,
4911
- class_name: sandboxConfig.className
4912
- }] : [],
4913
- ...shouldEmitLive() && liveConfig ? [{
4914
- name: liveConfig.bindingName,
4915
- class_name: liveConfig.className
4916
- }] : []
4917
- ]);
4918
- return result;
4919
4989
  }
4920
4990
  function applyEnvSchema(facts) {
4921
4991
  envSchemaFacts = facts;