void 1.5.0 → 1.6.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 (178) hide show
  1. package/dist/{account-cmd-ChVKPZ-w.mjs → account-cmd-DRhmCWcm.mjs} +3 -3
  2. package/dist/{application-analysis-2vGZrKzg.mjs → application-analysis-HRFsZOv3.mjs} +3 -3
  3. package/dist/{application-routing-DnUQoOJD.mjs → application-routing-P4TggvWp.mjs} +1 -1
  4. package/dist/{auth-link-D4uMdOsw.mjs → auth-link-CVXP1G6G.mjs} +3 -3
  5. package/dist/{auth-router-62dna450.mjs → auth-router-D1pGdbvB.mjs} +2 -2
  6. package/dist/{better-auth-shared-CiclgPde.mjs → better-auth-shared-C958aM7W.mjs} +28 -3
  7. package/dist/{build-cmd-D93K_4Sa.mjs → build-cmd-DmjbuT6d.mjs} +1 -1
  8. package/dist/{cache-CiIzEED4.mjs → cache-2khBO1MR.mjs} +1 -1
  9. package/dist/{cancel-deploy-BNoGfn2e.mjs → cancel-deploy-_itbbvY3.mjs} +1 -1
  10. package/dist/cli/cli.mjs +27 -27
  11. package/dist/client-CEtr5UOR.mjs +1 -0
  12. package/dist/{client-Qayy9xEh.mjs → client-CoxPEhJq.mjs} +6 -2
  13. package/dist/{cloudflare-cmd-DiFGaLow.mjs → cloudflare-cmd-fZ6AMSWk.mjs} +1 -1
  14. package/dist/{cloudflare-config-Chet188_.mjs → cloudflare-config-eQunqX6A.mjs} +1 -1
  15. package/dist/{cloudflare-connect-BdgAQBc7.mjs → cloudflare-connect-9OZe4XVQ.mjs} +1 -1
  16. package/dist/{cloudflare-operations-CWA24vCh.mjs → cloudflare-operations-CrskD4_O.mjs} +4 -4
  17. package/dist/{cloudflare-operations-D8MoezuN.mjs → cloudflare-operations-oLvAd9Nd.mjs} +1 -1
  18. package/dist/{config-Bt4rw93x.mjs → config-BBlVXZCU.mjs} +1 -1
  19. package/dist/{config-DkA9jYF2.mjs → config-BITKQ3O6.mjs} +2 -1
  20. package/dist/{config-BszFVhpk.mjs → config-BgysUhnm.mjs} +1 -1
  21. package/dist/{config-CzsNzGVH.d.mts → config-CPxJmt4M.d.mts} +2 -0
  22. package/dist/config-entry.d.mts +1 -1
  23. package/dist/{connect-b9mJbb6X.mjs → connect-CKvmkl2R.mjs} +4 -4
  24. package/dist/{create-project-rnjum4_V.mjs → create-project-BiMmR5wA.mjs} +1 -1
  25. package/dist/{create-project-eomwm0wa.mjs → create-project-Bq0M8MtO.mjs} +1 -1
  26. package/dist/{db-UPWmiSk1.mjs → db-cq5RW4Vy.mjs} +17 -17
  27. package/dist/{delete-BeeYi1WO.mjs → delete-CY4L8fi2.mjs} +1 -1
  28. package/dist/deploy-B029oHlE.mjs +2 -0
  29. package/dist/{deploy-CwmCcTlq.mjs → deploy-D_gEojD2.mjs} +79 -42
  30. package/dist/{deploy-managed-static-gPDxyp3v.mjs → deploy-managed-static-BHVbB_-A.mjs} +11 -11
  31. package/dist/{deploy-transport-L6zzPE9T.mjs → deploy-transport-DEyOUfac.mjs} +2 -2
  32. package/dist/deploy.mjs +5 -4
  33. package/dist/{dev-inbox-CwH5VuZj.mjs → dev-inbox-CUUS6iUg.mjs} +1 -1
  34. package/dist/{dispatch-CiJ1Xrba.mjs → dispatch-BNLB5_Gm.mjs} +1 -1
  35. package/dist/{domain-3xdH9p2L.mjs → domain-BlH90Ods.mjs} +2 -2
  36. package/dist/{email-Du3YRnM5.mjs → email-Bkq8ZL1p.mjs} +6 -6
  37. package/dist/{email-cloudflare-CtD8qE13.mjs → email-cloudflare-MvJIxrwx.mjs} +5 -107
  38. package/dist/{env-Cnvp0pGy.mjs → env-AvuCSeCH.mjs} +2 -2
  39. package/dist/{gen-BvVmpZ5f.mjs → gen-BD-dfjxY.mjs} +3 -3
  40. package/dist/gen-CIHXh9I8.mjs +1 -0
  41. package/dist/{github-cmd-X2GKiLi-.mjs → github-cmd-CA-lBdkn.mjs} +1 -1
  42. package/dist/{handler-Drm_lyrV.d.mts → handler-BR6Tvjkp.d.mts} +8 -3
  43. package/dist/{handler-xmzz0U38.mjs → handler-CKeTtYID.mjs} +2 -12
  44. package/dist/headers-CuTEdP9C.mjs +445 -0
  45. package/dist/{help-EWDNxST-.mjs → help-1OCFDsRr.mjs} +9 -1
  46. package/dist/help-Ba8Dq5-r.mjs +1 -0
  47. package/dist/{inbound-DSKSHDUj.d.mts → inbound-BHt4dIlL.d.mts} +16 -5
  48. package/dist/{inbound-DQfekMtz.mjs → inbound-CdO-qY0w.mjs} +2 -76
  49. package/dist/index.d.mts +1 -1
  50. package/dist/index.mjs +65 -37
  51. package/dist/{info-jW8_d3VI.mjs → info-CJxua_db.mjs} +4 -4
  52. package/dist/{init-jDQ5G9QC.mjs → init-B-4CluYx.mjs} +12 -11
  53. package/dist/{isr-config-DhBkzCdY.mjs → isr-config-Czzb4mIq.mjs} +1 -47
  54. package/dist/{link-DOyxX5Xs.mjs → link-DAXOT-G_.mjs} +2 -2
  55. package/dist/{list-C9pTYDtl.mjs → list-BYSOo9fm.mjs} +2 -2
  56. package/dist/{live-CDBHyYiR.d.mts → live-0xrcVpyq.d.mts} +1 -1
  57. package/dist/{local-d1-CKfek6Z4.mjs → local-d1-BLgsNUj9.mjs} +1 -1
  58. package/dist/login-BpbYeu-n.mjs +1 -0
  59. package/dist/{login-koQbjzPd.mjs → login-CJopTWX4.mjs} +2 -2
  60. package/dist/{logs-cTU5TsRW.mjs → logs-DE0weBsR.mjs} +13 -11
  61. package/dist/{migrate-C62pGTfr.mjs → migrate-ChHHZBTi.mjs} +3 -3
  62. package/dist/migrate-HdA6Kh2p.mjs +1 -0
  63. package/dist/{mime-DKkO7ZtA.mjs → mime-CtP6-qAF.mjs} +143 -15
  64. package/dist/nitro.mjs +3 -3
  65. package/dist/{node-BfAt2rz8.mjs → node-CZ6lGNLn.mjs} +1 -1
  66. package/dist/{operator-cmd-CnAvSqJ5.mjs → operator-cmd-BUqsdk2K.mjs} +28 -10
  67. package/dist/operator-cmd-wBYyp8iQ.mjs +1 -0
  68. package/dist/pages/client.d.mts +143 -11
  69. package/dist/pages/client.mjs +173 -12
  70. package/dist/pages/index.d.mts +22 -3
  71. package/dist/pages/index.mjs +18 -4
  72. package/dist/pages/islands-plugin.d.mts +46 -2
  73. package/dist/pages/islands-plugin.mjs +91 -5
  74. package/dist/pages/prefetch.d.mts +2 -2
  75. package/dist/pages/prefetch.mjs +1 -1
  76. package/dist/pages/protocol.d.mts +2 -2
  77. package/dist/pages/protocol.mjs +2 -2
  78. package/dist/platform-args-C7XUw9mw.mjs +1 -0
  79. package/dist/{platform-args-Bv9hoLEL.mjs → platform-args-Cx1M-7bq.mjs} +18 -4
  80. package/dist/{platform-auth-config-CLj0EvnB.mjs → platform-auth-config-K4m_LE8b.mjs} +4 -4
  81. package/dist/{platform-auth-protection-DQDSxVhr.mjs → platform-auth-protection-KF5Vo-1I.mjs} +2 -2
  82. package/dist/{platform-auth-recovery-DD3oLerq.mjs → platform-auth-recovery-BOdFrl7R.mjs} +2 -2
  83. package/dist/{platform-cmd-WnOmdLzR.mjs → platform-cmd-DpCHawGF.mjs} +1 -1
  84. package/dist/{platform-cmd-PLNLg-2N.mjs → platform-cmd-Ds3EAV5t.mjs} +5 -4
  85. package/dist/{platform-domain-Bnq0vg0q.mjs → platform-domain-rWJshybd.mjs} +2 -2
  86. package/dist/{platform-lifecycle-BQiN9HN5.mjs → platform-lifecycle-BH34I0rh.mjs} +1 -1
  87. package/dist/{platform-lifecycle-BCsFg1Tl.mjs → platform-lifecycle-D9RdGub1.mjs} +66 -31
  88. package/dist/{platform-management-nzCDwtiN.mjs → platform-management-BRefdHGD.mjs} +21 -3
  89. package/dist/{platform-management-CMJR8Zi-.mjs → platform-management-BfLW0Zlo.mjs} +1 -1
  90. package/dist/{platform-recovery-DBNoME3I.mjs → platform-recovery-DZuZGJtt.mjs} +2 -2
  91. package/dist/platform-runtime-logs-CdJGnk3f.mjs +68 -0
  92. package/dist/{platform-setup-VEcHu0Vq.mjs → platform-setup-Vw1AaUN8.mjs} +15 -2
  93. package/dist/{plugin-inference-DmwjC3y2.mjs → plugin-inference-CdLbxZL_.mjs} +1 -1
  94. package/dist/{prefetch-L93_1Jk7.mjs → prefetch-Dx2jIbtg.mjs} +19 -1
  95. package/dist/{prefetch-CG0K6JPd.d.mts → prefetch-NdNeubbH.d.mts} +7 -1
  96. package/dist/{prepare-pfl2LIMy.mjs → prepare-BJW-f1f1.mjs} +4 -4
  97. package/dist/{prepare-BPsfLWaW.mjs → prepare-BMGeKL16.mjs} +1 -1
  98. package/dist/prepare-BdxJJCMv.mjs +1 -0
  99. package/dist/{project-cmd-5Izu4Pg3.mjs → project-cmd-B_bUyZ3J.mjs} +15 -15
  100. package/dist/{project-team-DSUyjZFh.mjs → project-team-MjT5K4rQ.mjs} +1 -1
  101. package/dist/{project-token-wj09YQSB.mjs → project-token-BKHGwY3k.mjs} +1 -1
  102. package/dist/{project-zero-trust-BMHf2YF0.mjs → project-zero-trust-Bzdd05-h.mjs} +1 -1
  103. package/dist/{protocol-0-lp9ZYx.mjs → protocol-COyySMki.mjs} +130 -107
  104. package/dist/{protocol-xdrl-In0.d.mts → protocol-CkwjASep.d.mts} +2 -2
  105. package/dist/{provision-CRiNnlfE.mjs → provision-BbCzpbXs.mjs} +1 -1
  106. package/dist/{provision-CxHIGBIZ.mjs → provision-DQF6Kw-6.mjs} +1 -1
  107. package/dist/{requests-DicgB5jU.mjs → requests-daJmVpjk.mjs} +1 -1
  108. package/dist/{rollback-DrDZOKNE.mjs → rollback-BgL0wY1J.mjs} +2 -2
  109. package/dist/{route-types-irZc1txT.mjs → route-types-CZc1YXjF.mjs} +40 -4
  110. package/dist/route-url-C_YIH_zO.d.mts +37 -0
  111. package/dist/routes-stub.d.mts +6 -2
  112. package/dist/runtime/better-auth-mysql.mjs +1 -1
  113. package/dist/runtime/better-auth-pg.mjs +1 -1
  114. package/dist/runtime/better-auth.mjs +1 -1
  115. package/dist/runtime/email/testing.d.mts +1 -1
  116. package/dist/runtime/email/testing.mjs +3 -3
  117. package/dist/runtime/email.d.mts +1 -1
  118. package/dist/runtime/email.mjs +3 -3
  119. package/dist/runtime/handler.d.mts +1 -1
  120. package/dist/runtime/handler.mjs +2 -2
  121. package/dist/runtime/live-client.d.mts +1 -1
  122. package/dist/runtime/live.d.mts +1 -1
  123. package/dist/runtime/route-url.d.mts +2 -2
  124. package/dist/runtime/routing.d.mts +3 -3
  125. package/dist/runtime/routing.mjs +37 -16
  126. package/dist/runtime/validator.d.mts +1 -1
  127. package/dist/runtime/ws-server.d.mts +1 -1
  128. package/dist/runtime/ws.d.mts +1 -1
  129. package/dist/{scan-DSQtH3oS.mjs → scan-X2rDDRNa.mjs} +18 -10
  130. package/dist/{secret-C_QtTzJG.mjs → secret-CPq-B_vp.mjs} +2 -2
  131. package/dist/{static-asset-marker-NR1XS2z5.mjs → static-asset-marker-DsQKz6lt.mjs} +8 -1
  132. package/dist/validation-error-uAAjlsiW.mjs +82 -0
  133. package/dist/{version-metadata-Blf5H9BQ.mjs → version-metadata-C8hLzQkv.mjs} +1 -1
  134. package/dist/{wrangler-p-kLCJqF.mjs → wrangler-DESSbN8L.mjs} +2 -2
  135. package/dist/wrangler-DUB3MGHV.mjs +1 -0
  136. package/dist/{wrapper-iejSJDwL.mjs → wrapper-C9SUxoj7.mjs} +2 -2
  137. package/dist/{ws-DzBD-NUj.d.mts → ws-Dmjg5NH2.d.mts} +1 -1
  138. package/docs/guide/edge/headers.md +2 -0
  139. package/docs/guide/edge/rewrites.md +1 -1
  140. package/docs/guide/email/receiving.md +1 -1
  141. package/docs/guide/email/sending.md +42 -12
  142. package/docs/guide/pages-routing/actions-and-forms.md +21 -1
  143. package/docs/guide/pages-routing/islands.md +3 -3
  144. package/docs/guide/pages-routing/layouts.md +16 -2
  145. package/docs/guide/pages-routing/loaders.md +2 -2
  146. package/docs/guide/pages-routing/markdown.md +1 -1
  147. package/docs/guide/pages-routing/overview.md +103 -0
  148. package/docs/guide/platform/administration/connected-apps.md +4 -2
  149. package/docs/guide/platform/installation/credentials.md +6 -4
  150. package/docs/guide/platform/installation/maintenance.md +13 -0
  151. package/docs/guide/platform/installation/setup.md +22 -1
  152. package/docs/guide/ssg.md +3 -3
  153. package/docs/guide/type-safety.md +42 -1
  154. package/docs/integrations/frameworks/astro.md +17 -12
  155. package/docs/integrations/frameworks/overview.md +2 -2
  156. package/docs/reference/api/pages.md +101 -50
  157. package/docs/reference/api/rewrites.md +4 -1
  158. package/docs/reference/api/types.md +14 -10
  159. package/docs/reference/api.md +1 -1
  160. package/docs/reference/cli/platform-installation.md +4 -1
  161. package/docs/reference/cli/setup.md +1 -1
  162. package/docs/reference/config.md +10 -0
  163. package/package.json +8 -7
  164. package/schema.json +4 -0
  165. package/skills/void/SKILL.md +5 -0
  166. package/dist/client-DVtAKeUm.mjs +0 -1
  167. package/dist/deploy-CfU7CR-b.mjs +0 -2
  168. package/dist/gen-i61rdQgg.mjs +0 -1
  169. package/dist/headers-DEUBAM6W.mjs +0 -225
  170. package/dist/help-BcZDUcTw.mjs +0 -1
  171. package/dist/login-C0MiQrYs.mjs +0 -1
  172. package/dist/migrate-BG09-KOv.mjs +0 -1
  173. package/dist/operator-cmd-BhHXIL2G.mjs +0 -1
  174. package/dist/platform-args-C1kqYUen.mjs +0 -1
  175. package/dist/prepare-BJMmfq3W.mjs +0 -1
  176. package/dist/route-url-BGOiyKNz.d.mts +0 -24
  177. package/dist/validation-error-rDVbUJet.mjs +0 -47
  178. package/dist/wrangler-CPRX2d9A.mjs +0 -1
@@ -0,0 +1,445 @@
1
+ import { a as join } from "./dist-DMYe8CZJ.mjs";
2
+ import { a as convertRewritesConfig, d as isProtocolRelative, f as parseHostPrefixedSource, i as convertRedirectsConfig, n as convertFallbacksConfig, o as countSplats, r as convertHeadersConfig, t as VALID_REDIRECT_STATUSES } from "./config-BITKQ3O6.mjs";
3
+ import { i as voidWarn } from "./log-D9hwSySP.mjs";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ // src/asset-routing.ts
6
+ function isApiRoutePattern(pattern) {
7
+ return pattern === "/api" || pattern.startsWith("/api/");
8
+ }
9
+ function hasDocumentRoutes(routes) {
10
+ return routes.some((route) => !isApiRoutePattern(route.pattern));
11
+ }
12
+ function hasApiRoutes(routes) {
13
+ return routes.some((route) => isApiRoutePattern(route.pattern));
14
+ }
15
+ /**
16
+ * Returns true when browser document navigations must reach the worker before
17
+ * a static asset fallback can serve HTML.
18
+ */
19
+ function workerOwnsHtmlRouting(inputs) {
20
+ return inputs.isSsr || inputs.hasPages || inputs.hasDocumentRoutes || inputs.hasMiddleware || inputs.hasWebSockets === true || inputs.liveEnabled === true;
21
+ }
22
+ /**
23
+ * Returns true when native edge rules must run in the worker before Cloudflare
24
+ * Static Assets can answer: native builds compile HTTPS redirects and header,
25
+ * redirect, rewrite and fallback rules into the worker.
26
+ */
27
+ function edgeRulesNeedWorker(rules) {
28
+ return rules.alwaysUseHttps === true || (rules.headerRules?.length ?? 0) > 0 || (rules.redirectRules?.length ?? 0) > 0 || (rules.fallbackRules?.length ?? 0) > 0;
29
+ }
30
+ function resolveAssetConfig(inputs) {
31
+ return resolveAssetRoutingPolicy(inputs).assetConfig;
32
+ }
33
+ const VOID_INTERNAL_WORKER_FIRST = ["/__void", "/__void/*"];
34
+ function resolveAssetRoutingPolicy(inputs) {
35
+ const workerFirst = workerOwnsHtmlRouting(inputs);
36
+ const apiWorkerFirst = !workerFirst && (inputs.hasApiRoutes === true || inputs.authEnabled === true);
37
+ const assetConfig = workerFirst ? {
38
+ not_found_handling: "none",
39
+ run_worker_first: ["/**"]
40
+ } : apiWorkerFirst ? {
41
+ not_found_handling: "single-page-application",
42
+ run_worker_first: [
43
+ "/api",
44
+ "/api/*",
45
+ ...VOID_INTERNAL_WORKER_FIRST
46
+ ]
47
+ } : {
48
+ not_found_handling: "single-page-application",
49
+ run_worker_first: [...VOID_INTERNAL_WORKER_FIRST]
50
+ };
51
+ if (inputs.notFound !== void 0) assetConfig.not_found_handling = inputs.notFound;
52
+ return {
53
+ assetConfig,
54
+ apiWorkerFirst,
55
+ workerFirst,
56
+ workerSpaFallback: workerFirst && !inputs.isSsr && !inputs.hasPages && (inputs.notFound === void 0 || inputs.notFound === "single-page-application")
57
+ };
58
+ }
59
+ // src/asset-worker-bypass.ts
60
+ /**
61
+ * Build-time decision for letting Cloudflare serve fingerprinted build files
62
+ * without starting the Worker. Native Cloudflare builds of apps with no global
63
+ * middleware and no edge rules gain nothing from running the Worker for those
64
+ * files: the Worker would only copy the asset response. Skipping it makes
65
+ * those requests free static requests and keeps auth and its session read off
66
+ * them. Each file gets its own negative `run_worker_first` rule, because a
67
+ * request matching a negative rule never reaches the Worker, even when the
68
+ * file is missing. Misses and other paths in the folder keep the Worker's
69
+ * not-found, SPA and page handling.
70
+ * `routing.alwaysUseHttps` alone does not block this: hashed files are only
71
+ * linked from pages the Worker has already moved to HTTPS.
72
+ *
73
+ * The asset layer does not know which files are fingerprinted, so a generated
74
+ * `_headers` file gives every bypassed chunk the immutable policy the Worker
75
+ * would have set.
76
+ *
77
+ * An `output: "static"` Pages build goes further when nothing site-wide must
78
+ * run first (auth, live, `routing.alwaysUseHttps`): every file answers first,
79
+ * and only paths with server code reach the Worker first. A page without a
80
+ * server module prerenders the HTML the Worker would render, so its file can
81
+ * answer. Hashed files keep their
82
+ * immutable policy from the generated `_headers`, or stay on the Worker when
83
+ * it cannot list them.
84
+ */
85
+ const GENERATED_ASSET_HEADERS_MARKER = "# Generated by Void: immutable caching for fingerprinted build files. Do not edit.";
86
+ /** Cloudflare accepts at most 100 `run_worker_first` rules. */
87
+ const MAX_RUN_WORKER_FIRST_RULES = 100;
88
+ /** Cloudflare limits each `run_worker_first` rule to 100 characters. */
89
+ const MAX_RUN_WORKER_FIRST_RULE_LENGTH = 100;
90
+ /** Cloudflare accepts at most 100 `_headers` rules. */
91
+ const MAX_HEADERS_RULES = 100;
92
+ const AUTH_PATTERNS = ["/api/auth/*"];
93
+ /** Trim surrounding slashes; null when nothing is left. */
94
+ function normalizeAssetsDir(dir) {
95
+ let start = 0;
96
+ let end = dir.length;
97
+ while (start < end && dir[start] === "/") start++;
98
+ while (end > start && dir[end - 1] === "/") end--;
99
+ const trimmed = dir.slice(start, end);
100
+ return trimmed === "" || trimmed === "." ? null : trimmed;
101
+ }
102
+ /**
103
+ * Whether a route pattern can match a path below `/<dir>/`. Parameters,
104
+ * wildcards and patterns with more segments than the directory count as a
105
+ * match, so the answer errs towards keeping the Worker in front.
106
+ */
107
+ function patternCanReachUnder(pattern, dir) {
108
+ const patternSegments = (pattern.startsWith("/") ? pattern.slice(1) : pattern).split("/");
109
+ const dirSegments = (dir.startsWith("/") ? dir.slice(1) : dir).split("/");
110
+ for (let i = 0; i < dirSegments.length; i++) {
111
+ const segment = patternSegments[i];
112
+ if (segment === void 0) return false;
113
+ if (segment.includes("*") || segment.includes("{")) return true;
114
+ if (segment.startsWith(":")) continue;
115
+ if (segment.toLowerCase() !== dirSegments[i].toLowerCase()) return false;
116
+ }
117
+ return patternSegments.length > dirSegments.length;
118
+ }
119
+ /**
120
+ * Characters that are plain text in both a `_headers` path and a
121
+ * `run_worker_first` rule. `*`, `:` and `!` have meaning there, and anything
122
+ * that needs URL encoding would not match the request path as written.
123
+ */
124
+ function isPlainPath(path) {
125
+ for (const char of path) if (!(char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "-" || char === "_" || char === "." || char === "~" || char === "/")) return false;
126
+ return path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
127
+ }
128
+ /**
129
+ * Whether `run_worker_first` is the rule an earlier bypass wrote. A watch
130
+ * rebuild of only the client still finds it, because the Worker build that
131
+ * would write `['/**']` again did not run.
132
+ */
133
+ function isAssetBypassRule(runWorkerFirst, assetsDir) {
134
+ const dir = normalizeAssetsDir(assetsDir);
135
+ return dir !== null && Array.isArray(runWorkerFirst) && runWorkerFirst.length > 1 && runWorkerFirst[0] === "/**" && runWorkerFirst.slice(1).every((rule) => typeof rule === "string" && rule.startsWith("!/" + dir + "/"));
136
+ }
137
+ /** The bypass this build can use, or null when any rule keeps the Worker in front. */
138
+ function resolveAssetWorkerBypass(facts) {
139
+ const { runWorkerFirst } = facts;
140
+ if (!(Array.isArray(runWorkerFirst) && runWorkerFirst.length === 1 && runWorkerFirst[0] === "/**") || facts.notFoundHandling !== "none" || facts.base !== "/" || facts.hasMiddleware || facts.headerRuleCount > 0 || facts.redirectRuleCount > 0 || facts.fallbackRuleCount > 0 || facts.publicEdgeFiles) return null;
141
+ const assetsDir = normalizeAssetsDir(facts.assetsDir);
142
+ const files = assetsDir === null ? [] : [...new Set(facts.immutableFiles)].filter((file) => file.startsWith(assetsDir + "/")).sort();
143
+ return resolveStaticPageBypass(facts, assetsDir, files) ?? resolveHashedFileBypass(facts, assetsDir, files);
144
+ }
145
+ /** One negative rule per hashed file, so only those files skip the Worker. */
146
+ function resolveHashedFileBypass(facts, assetsDir, files) {
147
+ if (!assetsDir || !isPlainPath(assetsDir)) return null;
148
+ if ([
149
+ ...facts.patterns,
150
+ ...VOID_INTERNAL_WORKER_FIRST,
151
+ ...facts.authEnabled ? AUTH_PATTERNS : []
152
+ ].some((pattern) => patternCanReachUnder(pattern, assetsDir))) return null;
153
+ const rules = ["/**", ...files.map((file) => "!/" + file)];
154
+ if (files.length === 0 || files.some((file) => !isPlainPath(file)) || !fitsRunWorkerFirstLimits(rules)) return null;
155
+ return {
156
+ runWorkerFirst: rules,
157
+ headers: renderGeneratedAssetHeaders(files)
158
+ };
159
+ }
160
+ /**
161
+ * Void's own paths, then every server pattern widened to the static prefix
162
+ * before its first parameter: matching too much only keeps the Worker in
163
+ * front. A request that matches no file still reaches the Worker, since these
164
+ * builds use `not_found_handling: 'none'`. Hashed files get the generated
165
+ * `_headers`, or stay on the Worker when it cannot list them all. Null when
166
+ * site-wide behavior must run first or a pattern covers every path.
167
+ */
168
+ function resolveStaticPageBypass(facts, assetsDir, files) {
169
+ if (!facts.serverPatterns || facts.authEnabled || facts.liveEnabled || facts.alwaysUseHttps) return null;
170
+ const rules = /* @__PURE__ */ new Set();
171
+ for (const pattern of facts.serverPatterns) {
172
+ const patternRules = workerPathRules(pattern);
173
+ if (patternRules === null) return null;
174
+ for (const rule of patternRules) rules.add(rule);
175
+ }
176
+ let headers = null;
177
+ if (files.length > MAX_HEADERS_RULES || !files.every(isPlainPath)) {
178
+ if (!assetsDir || !isPlainPath(assetsDir)) return null;
179
+ rules.add("/" + assetsDir + "/*");
180
+ } else if (files.length > 0) headers = renderGeneratedAssetHeaders(files);
181
+ const runWorkerFirst = [...VOID_INTERNAL_WORKER_FIRST, ...[...rules].sort()];
182
+ return fitsRunWorkerFirstLimits(runWorkerFirst) ? {
183
+ runWorkerFirst,
184
+ headers
185
+ } : null;
186
+ }
187
+ /**
188
+ * The rules covering every path `pattern` matches; null when that is every
189
+ * path. A trailing `*` also matches the path before it: `/api/*` matches `/api`.
190
+ */
191
+ function workerPathRules(pattern) {
192
+ if (pattern === "/") return ["/"];
193
+ const segments = (pattern.startsWith("/") ? pattern.slice(1) : pattern).split("/");
194
+ const dynamic = segments.findIndex((segment) => segment.startsWith(":") || segment.includes("*") || segment.includes("{"));
195
+ if (dynamic === 0) return null;
196
+ const prefix = (dynamic === -1 ? segments : segments.slice(0, dynamic)).join("/");
197
+ if (!isPlainPath(prefix)) return null;
198
+ if (dynamic === -1) return ["/" + prefix];
199
+ const rule = "/" + prefix + "/*";
200
+ return dynamic === segments.length - 1 && segments[dynamic] === "*" ? ["/" + prefix, rule] : [rule];
201
+ }
202
+ function fitsRunWorkerFirstLimits(rules) {
203
+ return rules.length <= MAX_RUN_WORKER_FIRST_RULES && rules.every((rule) => rule.length <= MAX_RUN_WORKER_FIRST_RULE_LENGTH);
204
+ }
205
+ /** One immutable rule per file after the marker line. */
206
+ function renderGeneratedAssetHeaders(files) {
207
+ const lines = [GENERATED_ASSET_HEADERS_MARKER];
208
+ for (const file of files) lines.push("/" + file, " Cache-Control: public, max-age=31536000, immutable");
209
+ return lines.join("\n") + "\n";
210
+ }
211
+ /** Whether a `_headers` file is the one Void generated. */
212
+ function isGeneratedAssetHeaders(text) {
213
+ const newline = text.indexOf("\n");
214
+ let firstLine = newline === -1 ? text : text.slice(0, newline);
215
+ if (firstLine.endsWith("\r")) firstLine = firstLine.slice(0, -1);
216
+ return firstLine === GENERATED_ASSET_HEADERS_MARKER;
217
+ }
218
+ // src/cli/headers.ts
219
+ /**
220
+ * Parse a Cloudflare/Netlify `_headers` file into HeaderRule[].
221
+ *
222
+ * Format:
223
+ * - Non-indented lines = path pattern (starts a new block)
224
+ * - Indented lines = `Header-Name: value` (split on first `:`)
225
+ * - Lines starting with `#` are comments
226
+ * - `! Header-Name` unset directives are skipped (last-match-wins handles this)
227
+ *
228
+ * Output is sorted by pattern length ascending (shortest first) so that
229
+ * more-specific patterns come last — matching dispatch's last-match-wins semantics.
230
+ *
231
+ * Returns `undefined` if the file doesn't exist or yields no valid rules.
232
+ */
233
+ function parseHeadersFile(dir) {
234
+ const filePath = join(dir, "_headers");
235
+ if (!existsSync(filePath)) return;
236
+ const content = readFileSync(filePath, "utf-8");
237
+ const rules = [];
238
+ let current = null;
239
+ for (const line of content.split("\n")) {
240
+ const trimmed = line.trimEnd();
241
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
242
+ if (line[0] === " " || line[0] === " ") {
243
+ if (!current) continue;
244
+ const stripped = trimmed.trimStart();
245
+ if (stripped.startsWith("!")) continue;
246
+ const colonIdx = stripped.indexOf(":");
247
+ if (colonIdx === -1) continue;
248
+ const name = stripped.slice(0, colonIdx).trim();
249
+ const value = stripped.slice(colonIdx + 1).trim();
250
+ if (name) current.headers.push([name, value]);
251
+ } else {
252
+ if (current && current.headers.length > 0) rules.push(current);
253
+ current = {
254
+ pattern: trimmed,
255
+ headers: []
256
+ };
257
+ }
258
+ }
259
+ if (current && current.headers.length > 0) rules.push(current);
260
+ if (rules.length === 0) return;
261
+ rules.sort((a, b) => a.pattern.length - b.pattern.length);
262
+ return rules;
263
+ }
264
+ /** The `_headers` text in `dir` when Void generated it, otherwise undefined. */
265
+ function readGeneratedAssetHeadersFile(dir) {
266
+ const filePath = join(dir, "_headers");
267
+ if (!existsSync(filePath)) return;
268
+ const content = readFileSync(filePath, "utf-8");
269
+ return isGeneratedAssetHeaders(content) ? content : void 0;
270
+ }
271
+ /**
272
+ * Parse a `_redirects` file: `source destination [status]` per line (status
273
+ * defaults to 302, `#` starts a comment). Keeps 3xx redirects and 200 rewrites.
274
+ *
275
+ * A `!` suffix on 200 sets `force`, so the rewrite applies even when a static
276
+ * asset matches. On a 3xx it is a no-op, stripped and counted in
277
+ * `redundantForceOn3xx`.
278
+ *
279
+ * Returns `undefined` when the file doesn't exist or yields no valid rules.
280
+ */
281
+ function parseRedirectsFile(dir) {
282
+ const filePath = join(dir, "_redirects");
283
+ if (!existsSync(filePath)) return;
284
+ const content = readFileSync(filePath, "utf-8");
285
+ const rules = [];
286
+ let redundantForceOn3xx = 0;
287
+ const rawLines = content.split("\n");
288
+ for (let i = 0; i < rawLines.length; i++) {
289
+ const lineNumber = i + 1;
290
+ const trimmed = rawLines[i].trim();
291
+ if (!trimmed || trimmed.startsWith("#")) continue;
292
+ const parts = trimmed.split(/\s+/);
293
+ if (parts.length < 2) continue;
294
+ const origin = `_redirects:${lineNumber}`;
295
+ const [rawSource, destination, statusStr] = parts;
296
+ const parsedSource = parseHostPrefixedSource(rawSource);
297
+ if ("error" in parsedSource) {
298
+ voidWarn(`Skipping _redirects entry "${rawSource} ${destination}${statusStr ? ` ${statusStr}` : ""}" (line ${lineNumber}): source ${parsedSource.error}.`);
299
+ continue;
300
+ }
301
+ const { host, path: source } = parsedSource;
302
+ if (countSplats(source) > 1) {
303
+ voidWarn(`Skipping _redirects entry "${rawSource} ${destination}${statusStr ? ` ${statusStr}` : ""}" (line ${lineNumber}): source must contain at most one "*" wildcard (captured as :splat).`);
304
+ continue;
305
+ }
306
+ let status;
307
+ let force = false;
308
+ if (statusStr) {
309
+ if (statusStr.endsWith("!")) {
310
+ const numericPart = statusStr.slice(0, -1);
311
+ status = parseInt(numericPart, 10);
312
+ if (status === 200) force = true;
313
+ else if (status >= 300 && status < 400) redundantForceOn3xx++;
314
+ } else status = parseInt(statusStr, 10);
315
+ } else status = 302;
316
+ if (status === 200) {
317
+ if (!destination.startsWith("/")) {
318
+ voidWarn(`Skipping _redirects rewrite "${rawSource} ${destination} ${force ? "200!" : "200"}": destination must start with "/" (only internal paths are supported).`);
319
+ continue;
320
+ }
321
+ if (isProtocolRelative(destination)) {
322
+ voidWarn(`Skipping _redirects rewrite "${rawSource} ${destination} ${force ? "200!" : "200"}": destination must not start with "//" (protocol-relative URLs parse as cross-origin; only internal paths are supported).`);
323
+ continue;
324
+ }
325
+ if (destination.includes("?")) {
326
+ voidWarn(`Skipping _redirects rewrite "${rawSource} ${destination} ${force ? "200!" : "200"}": destination must not contain "?" (query strings are not supported in rewrite destinations; the incoming request's query is preserved automatically).`);
327
+ continue;
328
+ }
329
+ rules.push({
330
+ source,
331
+ destination,
332
+ status,
333
+ force,
334
+ origin,
335
+ ...host && { host }
336
+ });
337
+ } else if (status >= 300 && status < 400) {
338
+ if (!VALID_REDIRECT_STATUSES.has(status)) {
339
+ voidWarn(`Skipping _redirects entry "${rawSource} ${destination} ${statusStr}" (line ${lineNumber}): redirect status must be 301, 302, 303, 307, or 308.`);
340
+ continue;
341
+ }
342
+ rules.push({
343
+ source,
344
+ destination,
345
+ status,
346
+ origin,
347
+ ...host && { host }
348
+ });
349
+ }
350
+ }
351
+ if (rules.length === 0 && redundantForceOn3xx === 0) return;
352
+ return {
353
+ rules,
354
+ redundantForceOn3xx
355
+ };
356
+ }
357
+ /**
358
+ * Split `_redirects` rules into rules that always apply and fallbacks: unforced
359
+ * 200 rewrites, which apply only when no static asset matches.
360
+ */
361
+ function splitRedirectRulesByForce(rules) {
362
+ const always = [];
363
+ const fallbacks = [];
364
+ if (!rules) return {
365
+ always,
366
+ fallbacks
367
+ };
368
+ for (const rule of rules) if (rule.status === 200 && rule.force !== true) fallbacks.push(rule);
369
+ else always.push(rule);
370
+ return {
371
+ always,
372
+ fallbacks
373
+ };
374
+ }
375
+ /**
376
+ * Merge `_redirects` and `_headers` in `dir` with `routing` from void.config.ts.
377
+ * Deploy and dev share it so both build the same rule tables; callers lint them.
378
+ */
379
+ function mergeRoutingRules(opts) {
380
+ const { dir, routing } = opts;
381
+ const fileDir = dir !== null && existsSync(dir) ? dir : null;
382
+ const fileHeaders = fileDir !== null ? parseHeadersFile(fileDir) : void 0;
383
+ const fileParsed = fileDir !== null ? parseRedirectsFile(fileDir) : void 0;
384
+ const { always: fileAlways, fallbacks: fileFallbacks } = splitRedirectRulesByForce(fileParsed?.rules);
385
+ const cfgHeaders = convertHeadersConfig(routing?.headers);
386
+ const cfgRedirects = convertRedirectsConfig(routing?.redirects);
387
+ const cfgRewrites = convertRewritesConfig(routing?.rewrites);
388
+ const cfgFallbacks = convertFallbacksConfig(routing?.fallbacks);
389
+ const headerRules = [...fileHeaders ?? [], ...cfgHeaders ?? []];
390
+ const redirectRules = [
391
+ ...cfgRedirects ?? [],
392
+ ...cfgRewrites ?? [],
393
+ ...fileAlways
394
+ ];
395
+ const mergedFallbacks = [...cfgFallbacks ?? [], ...fileFallbacks];
396
+ return {
397
+ headerRules,
398
+ redirectRules,
399
+ fallbackRules: mergedFallbacks.length > 0 ? mergedFallbacks : void 0,
400
+ redundantForceOn3xx: fileParsed?.redundantForceOn3xx ?? 0
401
+ };
402
+ }
403
+ /**
404
+ * Lint each phase on its own: only duplicates within a phase shadow each other,
405
+ * so a redirect and a fallback with the same source are fine.
406
+ */
407
+ function lintRoutingPhases(rules) {
408
+ lintDuplicateSources(rules.redirectRules, "redirects/rewrites (pre-asset)");
409
+ lintDuplicateSources(rules.fallbackRules, "fallbacks (post-asset)");
410
+ lintDestinationSplats(rules.redirectRules, "redirects/rewrites (pre-asset)");
411
+ lintDestinationSplats(rules.fallbackRules, "fallbacks (post-asset)");
412
+ }
413
+ /**
414
+ * Warn about a literal `*` in a destination. Destinations substitute only
415
+ * `:name` params such as `:splat`, so a `*` passes through unchanged.
416
+ */
417
+ function lintDestinationSplats(rules, phaseName) {
418
+ if (!rules || rules.length === 0) return;
419
+ const affected = rules.filter((r) => r.destination.includes("*"));
420
+ if (affected.length === 0) return;
421
+ const lines = affected.map((r) => ` ${r.source} → ${r.destination} (${r.origin ?? "<unknown>"})`);
422
+ voidWarn(`destinations in ${phaseName} contain a literal "*" — splat tokens are not substituted in destinations; use ":splat" (or other named params) or rewrite the destination:\n${lines.join("\n")}`);
423
+ }
424
+ /**
425
+ * Warn when one phase repeats an exact `source`: first match wins, so the later
426
+ * rule never applies. Phases run separately, so callers lint each one.
427
+ */
428
+ function lintDuplicateSources(rules, phaseName) {
429
+ if (!rules || rules.length < 2) return;
430
+ const bySource = /* @__PURE__ */ new Map();
431
+ for (const rule of rules) {
432
+ const existing = bySource.get(rule.source);
433
+ if (existing) existing.push(rule);
434
+ else bySource.set(rule.source, [rule]);
435
+ }
436
+ const lines = [];
437
+ for (const [source, occurrences] of bySource) {
438
+ if (occurrences.length < 2) continue;
439
+ const origins = occurrences.map((r) => r.origin ?? "<unknown>").join(", ");
440
+ lines.push(` ${source} — ${origins}`);
441
+ }
442
+ if (lines.length === 0) return;
443
+ voidWarn(`duplicate rule sources detected in ${phaseName} (first match wins — the earlier entry takes effect):\n${lines.join("\n")}`);
444
+ }
445
+ export { isAssetBypassRule as a, VOID_INTERNAL_WORKER_FIRST as c, hasDocumentRoutes as d, resolveAssetConfig as f, readGeneratedAssetHeadersFile as i, edgeRulesNeedWorker as l, mergeRoutingRules as n, isGeneratedAssetHeaders as o, resolveAssetRoutingPolicy as p, parseRedirectsFile as r, resolveAssetWorkerBypass as s, lintRoutingPhases as t, hasApiRoutes as u };
@@ -688,6 +688,10 @@ const HELP_TREE = {
688
688
  command: "--runtime <path>",
689
689
  description: "Deploy an integrity-checked runtime directory instead of the package"
690
690
  },
691
+ {
692
+ command: "--runtime-logs <full|minimal>",
693
+ description: "minimal keeps request URLs, console and exception messages out of runtime logs"
694
+ },
691
695
  {
692
696
  command: "--yes",
693
697
  description: "Acknowledge changes without an interactive prompt"
@@ -1780,7 +1784,7 @@ function withProjectOption(summary, usage) {
1780
1784
  function lifecycleHelp(command, summary) {
1781
1785
  return {
1782
1786
  summary,
1783
- usage: [`void platform ${command} [id] [--plan]${command === "disable" ? "" : " [--runtime <path>]"}${command === "upgrade" || command === "repair" ? " [--dashboard-url <origin>] [--custom-domains]" : ""}${command === "repair" ? " [--auth-config <path>]" : ""} [--yes]`],
1787
+ usage: [`void platform ${command} [id] [--plan]${command === "disable" ? "" : " [--runtime <path>]"}${command === "upgrade" || command === "repair" ? " [--dashboard-url <origin>] [--custom-domains]" : ""}${command === "upgrade" ? " [--runtime-logs <full|minimal>]" : ""}${command === "repair" ? " [--auth-config <path>]" : ""} [--yes]`],
1784
1788
  options: [
1785
1789
  {
1786
1790
  command: "--plan",
@@ -1797,6 +1801,10 @@ function lifecycleHelp(command, summary) {
1797
1801
  command: "--custom-domains",
1798
1802
  description: "Turn on project custom domains; omission keeps the saved setting"
1799
1803
  }] : [],
1804
+ ...command === "upgrade" ? [{
1805
+ command: "--runtime-logs <full|minimal>",
1806
+ description: "minimal keeps request URLs, console and exception messages out of runtime logs; omission keeps the saved setting"
1807
+ }] : [],
1800
1808
  ...command === "repair" ? [{
1801
1809
  command: "--auth-config <path>",
1802
1810
  description: "While first administrator setup is pending, replace its login settings (install file format)"
@@ -0,0 +1 @@
1
+ export { r as printHelpPage } from "./help-1OCFDsRr.mjs";
@@ -33,8 +33,21 @@ interface SendEmailOptions {
33
33
  replyTo?: Address;
34
34
  cc?: Address | Array<Address>;
35
35
  bcc?: Address | Array<Address>;
36
+ /** Custom headers. Reserved names such as `From`, `Date` and the threading headers are ignored. */
36
37
  headers?: Record<string, string>;
37
38
  attachments?: Array<Attachment>;
39
+ /**
40
+ * Threads this email under an earlier message, such as a reply sent from a
41
+ * queue job after the inbound handler returned. `inReplyTo` is the parent's
42
+ * `Message-ID`. `references` lists the thread's ids, oldest first, ending
43
+ * with the parent; it defaults to `inReplyTo`. An id that cannot be sent,
44
+ * such as one with a control character, is left out. If `references`
45
+ * cannot be sent, it falls back to `inReplyTo`. The email still sends.
46
+ */
47
+ threading?: {
48
+ inReplyTo?: string;
49
+ references?: string;
50
+ };
38
51
  /**
39
52
  * Deduplicates a platform send for 30 days. Reuse the same key and message
40
53
  * when checking an uncertain attempt; a changed message is a conflict.
@@ -145,11 +158,9 @@ interface NormalizedEmail {
145
158
  headers: Record<string, string>;
146
159
  attachments: Array<NormalizedAttachment>;
147
160
  /**
148
- * Internal-only threading headers, set by `replyEmail`/`buildReplyMessage`.
149
- * The MIME builder emits these as proper `In-Reply-To` / `References`
150
- * top-level headers. User-supplied `headers` with these names are
151
- * always dropped (they're in `RESERVED_HEADERS`); this field is the
152
- * supported channel for the trusted reply path.
161
+ * `In-Reply-To` / `References` after the ids were checked, from the
162
+ * `threading` option or `replyEmail`. User-supplied `headers` with these
163
+ * names are always dropped (they're in `RESERVED_HEADERS`).
153
164
  */
154
165
  threading?: {
155
166
  inReplyTo?: string;
@@ -1,5 +1,5 @@
1
1
  import { t as base64ToBytes } from "./base64-ikl9yBy3.mjs";
2
- import { c as validateAddressParts, i as indexOfControl, n as MimeError, o as normalizeEmail, r as buildMime } from "./mime-DKkO7ZtA.mjs";
2
+ import { a as normalizeEmail, c as fitsHeaderLine, l as replyReferences, n as MimeError, r as buildMime, s as validateAddressParts, u as threadingId } from "./mime-CtP6-qAF.mjs";
3
3
  import { a as getRawRuntimeEnv } from "./env-raw-e8qgqAzQ.mjs";
4
4
  import { o as withTimeout } from "./email-protocol-DTs_TjCS.mjs";
5
5
  import { EMAIL_MAX_ACTIONS, EMAIL_MAX_INBOUND_BYTES, EMAIL_MAX_INBOUND_HEADERS, EMAIL_MAX_METADATA_BYTES, EMAIL_MAX_REPLY_BYTES, EMAIL_MAX_RESPONSE_BYTES } from "@void/platform/email-protocol";
@@ -349,52 +349,6 @@ function createMessageProxy(envelope, opts = {}) {
349
349
  };
350
350
  }
351
351
  /**
352
- * Whether `Name: value` fits RFC 5322's 998-octet line. Folding happens only
353
- * at SP or HTAB, so the widest unbroken run decides.
354
- */
355
- function fitsHeaderLine(name, value) {
356
- if (!value) return false;
357
- const budget = 998 - (name.length + 2);
358
- let run = 0;
359
- for (let i = 0; i < value.length; i++) {
360
- const c = value.charCodeAt(i);
361
- if (c === 32 || c === 9) {
362
- run = 0;
363
- continue;
364
- }
365
- if (c < 128) run += 1;
366
- else if (c < 2048) run += 2;
367
- else if (c >= 55296 && c <= 56319 && i + 1 < value.length && (value.charCodeAt(i + 1) & 64512) === 56320) {
368
- run += 4;
369
- i++;
370
- } else run += 3;
371
- if (run > budget) return false;
372
- }
373
- return true;
374
- }
375
- /**
376
- * `chain` without the ids that hold a control character, joined by single
377
- * spaces. Called only on a chain already cut to `MAX_REPLY_REFERENCES_CHARS`.
378
- */
379
- function dropControlIds(chain) {
380
- if (indexOfControl(chain) === -1) return chain;
381
- const kept = [];
382
- let start = -1;
383
- let tainted = false;
384
- for (let i = 0; i <= chain.length; i++) {
385
- const c = i < chain.length ? chain.charCodeAt(i) : 32;
386
- if (c === 32 || c === 9) {
387
- if (start >= 0 && !tainted) kept.push(chain.slice(start, i));
388
- start = -1;
389
- tainted = false;
390
- } else {
391
- if (start < 0) start = i;
392
- if (c < 32 || c === 127) tainted = true;
393
- }
394
- }
395
- return kept.join(" ");
396
- }
397
- /**
398
352
  * Longest decoded inbound subject the default reply subject is built from;
399
353
  * longer falls back to a bare `Re:`. Real subjects are far shorter, and the
400
354
  * bound keeps the control-character replacement cheap.
@@ -410,34 +364,6 @@ function prefixReplySubject(original) {
410
364
  return prefixed;
411
365
  }
412
366
  /**
413
- * Longest `References` value a reply carries, in UTF-16 code units. 8 KiB
414
- * keeps any real thread whole; past it the newest ids are kept, since mail
415
- * clients thread on any id they recognise and the parent is `In-Reply-To`.
416
- */
417
- const MAX_REPLY_REFERENCES_CHARS = 8192;
418
- /**
419
- * The reply's `References`, or `''`: the inbound chain plus the parent's id,
420
- * trimmed to its newest whole ids, or the parent's id alone when an id
421
- * cannot fit a line.
422
- */
423
- function replyReferences(originalReferences, messageId) {
424
- const parent = messageId ? ` ${messageId}` : "";
425
- let chain = originalReferences;
426
- const budget = MAX_REPLY_REFERENCES_CHARS - parent.length;
427
- if (chain.length > budget) {
428
- let cut = chain.length - budget;
429
- while (cut < chain.length) {
430
- const c = chain.charCodeAt(cut);
431
- if (c === 32 || c === 9) break;
432
- cut++;
433
- }
434
- chain = chain.slice(cut);
435
- }
436
- chain = dropControlIds(chain);
437
- chain = `${chain}${parent}`.trim();
438
- return fitsHeaderLine("References", chain) ? chain : messageId;
439
- }
440
- /**
441
367
  * Minimal RFC 2047 decoder for the reply subject: B and Q encoded-words,
442
368
  * unknown charsets read as UTF-8, malformed input returned unchanged.
443
369
  */
@@ -504,7 +430,7 @@ async function buildReplyMessage(message, opts) {
504
430
  const toAddr = opts.to ?? message.from;
505
431
  const subject = opts.subject ?? prefixReplySubject(originalSubject);
506
432
  const threading = {};
507
- const messageId = fitsHeaderLine("In-Reply-To", originalMessageId) && indexOfControl(originalMessageId) === -1 ? originalMessageId : "";
433
+ const messageId = threadingId("In-Reply-To", originalMessageId);
508
434
  if (messageId) threading.inReplyTo = messageId;
509
435
  const references = replyReferences(originalReferences, messageId);
510
436
  if (references) threading.references = references;
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as HeadDescriptor } from "./head-MeF_5M_3.mjs";
2
2
  import { r as AuthUser } from "./auth-CH4PLAX1.mjs";
3
3
  import { s as RouteEnvironment } from "./types-agfx0alu.mjs";
4
- import { C as defer, E as ValidatorSlots, S as InferProps, T as HandlerInput, _ as defineQueue, a as CloudEnv, b as Deferred, c as QueueRetryOptions, d as TypedQueueHandler, f as VoidAssetRewriteError, g as defineMiddleware, h as defineHead, i as CloudContextVariables, l as RenderAssetTags, m as defineHandler, o as QueueBatch, p as basicAuth, r as CloudContext, s as QueueMessage, t as BasicAuthOptions, u as TypedHandler, v as defineRender, x as DeferredState, y as defineScheduled } from "./handler-Drm_lyrV.mjs";
4
+ import { C as defer, E as ValidatorSlots, S as InferProps, T as HandlerInput, _ as defineQueue, a as CloudEnv, b as Deferred, c as QueueRetryOptions, d as TypedQueueHandler, f as VoidAssetRewriteError, g as defineMiddleware, h as defineHead, i as CloudContextVariables, l as RenderAssetTags, m as defineHandler, o as QueueBatch, p as basicAuth, r as CloudContext, s as QueueMessage, t as BasicAuthOptions, u as TypedHandler, v as defineRender, x as DeferredState, y as defineScheduled } from "./handler-BR6Tvjkp.mjs";
5
5
  import { t as QueueMap } from "./queues-DdM42xXW.mjs";
6
6
  import { Plugin } from "vite";
7
7
  import { convertToWranglerConfig } from "@cloudflare/config";