void 0.7.1 → 0.7.3

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 (167) hide show
  1. package/dist/agents-DqkFfc2c.mjs +151 -0
  2. package/dist/{auth-cmd-DVKi6dzh.mjs → auth-cmd-Dk0acCT5.mjs} +2 -2
  3. package/dist/{better-auth-shared-C9_GHSkR.d.mts → better-auth-shared-CZsIpjey.d.mts} +1 -1
  4. package/dist/{cache-B0BgSTZi.mjs → cache-DGSZ5Bh6.mjs} +2 -2
  5. package/dist/{cancel-deploy-D9OFt5gA.mjs → cancel-deploy-CrY3kt93.mjs} +1 -1
  6. package/dist/cli/cli.mjs +27 -174
  7. package/dist/{client-BUdfE3QJ.mjs → client-DCqnMpDt.mjs} +97 -11
  8. package/dist/{create-project-CN1pF-OQ.mjs → create-project-Bg88Kq_I.mjs} +3 -3
  9. package/dist/{db-BIP2kuEt.mjs → db-ClNu7vYQ.mjs} +13 -13
  10. package/dist/{delete-DJTvwbr-.mjs → delete-DXcX1yQZ.mjs} +2 -2
  11. package/dist/{deploy-BqXz1ycW.mjs → deploy-BkjqNk9U.mjs} +565 -161
  12. package/dist/{domain-B-fIU3VE.mjs → domain-CDQhvYNZ.mjs} +1 -1
  13. package/dist/{env-BwbZJd2x.mjs → env-CnrQY2b6.mjs} +1 -1
  14. package/dist/{env-helpers-Dr9Y7RnE.d.mts → env-helpers-CbeM_7-k.d.mts} +1 -1
  15. package/dist/{gen-U0Ktr4Zd.mjs → gen-C0EY2k27.mjs} +1 -1
  16. package/dist/{handler-B0ds0OHJ.d.mts → handler-dKQWyF-G.d.mts} +3 -3
  17. package/dist/index.d.mts +3 -3
  18. package/dist/index.mjs +13 -12
  19. package/dist/{init-Bb_Qsdq6.mjs → init-CPny6w9D.mjs} +63 -28
  20. package/dist/{link-D4d26PCm.mjs → link-eZ0aiHFK.mjs} +2 -2
  21. package/dist/{list-bQc1eQCZ.mjs → list-ztyEz4TW.mjs} +2 -2
  22. package/dist/{login-RWUDCfdx.mjs → login-B5HHT32i.mjs} +1 -1
  23. package/dist/{logs-DrkTklop.mjs → logs-J4BN0LXd.mjs} +1 -1
  24. package/dist/{mcp-kZ4zg13a.mjs → mcp-Bdu9bnjR.mjs} +1 -1
  25. package/dist/{node-DDfXj10V.mjs → node-DFqMcZR1.mjs} +3 -3
  26. package/dist/pages/client.d.mts +1 -1
  27. package/dist/pages/client.mjs +3 -0
  28. package/dist/pages/head-client.d.mts +1 -1
  29. package/dist/pages/head.d.mts +1 -1
  30. package/dist/pages/index.d.mts +2 -2
  31. package/dist/pages/index.mjs +1 -1
  32. package/dist/pages/islands-plugin.d.mts +1 -1
  33. package/dist/pages/protocol.d.mts +2 -2
  34. package/dist/pages/protocol.mjs +23 -18
  35. package/dist/{prepare-BAtWufvm.mjs → prepare-DKkx-2Kt.mjs} +1 -1
  36. package/dist/{project-cmd-ATFi3kRm.mjs → project-cmd-DKiQYdSd.mjs} +8 -8
  37. package/dist/{protocol-BWzXs2A2.d.mts → protocol-CK4OFwfR.d.mts} +2 -2
  38. package/dist/{rollback-BSyita3C.mjs → rollback-ZNvT8T54.mjs} +1 -1
  39. package/dist/{runner-6Ep3fNQu.mjs → runner-BUPRnMFN.mjs} +1 -1
  40. package/dist/runtime/ai.mjs +1 -1
  41. package/dist/runtime/auth.d.mts +1 -1
  42. package/dist/runtime/better-auth-pg.d.mts +1 -1
  43. package/dist/runtime/better-auth-pg.mjs +2 -2
  44. package/dist/runtime/better-auth.d.mts +1 -1
  45. package/dist/runtime/better-auth.mjs +2 -2
  46. package/dist/runtime/client.d.mts +2 -2
  47. package/dist/runtime/client.mjs +1 -1
  48. package/dist/runtime/env-helpers.d.mts +1 -1
  49. package/dist/runtime/env-public-client.d.mts +1 -1
  50. package/dist/runtime/env-public.d.mts +2 -2
  51. package/dist/runtime/env-public.mjs +1 -1
  52. package/dist/runtime/env.mjs +1 -1
  53. package/dist/runtime/fetch-stream.d.mts +1 -1
  54. package/dist/runtime/fetch-stream.mjs +1 -1
  55. package/dist/runtime/fetch.d.mts +1 -1
  56. package/dist/runtime/fetch.mjs +1 -1
  57. package/dist/runtime/handler.d.mts +1 -1
  58. package/dist/runtime/handler.mjs +1 -1
  59. package/dist/runtime/isr.mjs +1 -1
  60. package/dist/runtime/migration-handler.mjs +2 -2
  61. package/dist/runtime/validator.d.mts +1 -1
  62. package/dist/runtime/ws-server.d.mts +2 -2
  63. package/dist/runtime/ws.d.mts +3 -3
  64. package/dist/{secret-DmjBDxB1.mjs → secret-BXHx515u.mjs} +2 -2
  65. package/dist/{skills-ipldjlKE.mjs → skills-CbuYOthf.mjs} +1 -1
  66. package/package.json +13 -13
  67. package/skills/void/docs/guide/deployment.md +4 -6
  68. package/skills/void/docs/index.md +3 -3
  69. package/skills/void/docs/node_modules/void/AGENTS.md +1 -1
  70. package/skills/void/docs/node_modules/void/node_modules/@types/node/README.md +1 -1
  71. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@types/node/README.md +1 -1
  72. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/AGENTS.md +15 -0
  73. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/README.md +208 -0
  74. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/build.md +21 -0
  75. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/fmt.md +18 -0
  76. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/index.md +31 -0
  77. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/lint.md +24 -0
  78. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/pack.md +17 -0
  79. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/run.md +249 -0
  80. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/staged.md +15 -0
  81. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/test.md +18 -0
  82. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/build.md +40 -0
  83. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/cache.md +119 -0
  84. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/check.md +44 -0
  85. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ci.md +64 -0
  86. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/commit-hooks.md +51 -0
  87. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/create.md +88 -0
  88. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/dev.md +24 -0
  89. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/env.md +102 -0
  90. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/fmt.md +41 -0
  91. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ide-integration.md +101 -0
  92. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/implode.md +23 -0
  93. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/index.md +128 -0
  94. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/install.md +147 -0
  95. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/lint.md +50 -0
  96. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate.md +173 -0
  97. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/pack.md +61 -0
  98. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/run.md +324 -0
  99. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/test.md +35 -0
  100. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/troubleshooting.md +132 -0
  101. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/upgrade.md +49 -0
  102. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/vpx.md +66 -0
  103. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/why.md +39 -0
  104. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/index.md +12 -0
  105. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/team.md +35 -0
  106. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/generator/README.md +35 -0
  107. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/monorepo/README.md +29 -0
  108. package/skills/void/docs/node_modules/void/node_modules/vite-plus/AGENTS.md +15 -0
  109. package/skills/void/docs/node_modules/void/node_modules/vite-plus/README.md +208 -0
  110. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/build.md +21 -0
  111. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/fmt.md +18 -0
  112. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/index.md +31 -0
  113. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/lint.md +24 -0
  114. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/pack.md +17 -0
  115. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/run.md +249 -0
  116. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/staged.md +15 -0
  117. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/test.md +18 -0
  118. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/build.md +40 -0
  119. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/cache.md +119 -0
  120. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/check.md +44 -0
  121. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ci.md +64 -0
  122. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/commit-hooks.md +51 -0
  123. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/create.md +88 -0
  124. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/dev.md +24 -0
  125. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/env.md +102 -0
  126. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/fmt.md +41 -0
  127. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ide-integration.md +101 -0
  128. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/implode.md +23 -0
  129. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/index.md +128 -0
  130. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/install.md +147 -0
  131. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/lint.md +50 -0
  132. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate.md +173 -0
  133. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/pack.md +61 -0
  134. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/run.md +324 -0
  135. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/test.md +35 -0
  136. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/troubleshooting.md +132 -0
  137. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/upgrade.md +49 -0
  138. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/vpx.md +66 -0
  139. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/why.md +39 -0
  140. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/index.md +12 -0
  141. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/team.md +35 -0
  142. package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/generator/README.md +35 -0
  143. package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/monorepo/README.md +29 -0
  144. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/tsdown/README.md +0 -55
  145. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite/LICENSE.md +0 -2230
  146. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite/README.md +0 -20
  147. package/skills/void/docs/node_modules/void/node_modules/tsdown/README.md +0 -55
  148. package/skills/void/docs/node_modules/void/node_modules/vite/LICENSE.md +0 -2230
  149. package/skills/void/docs/node_modules/void/node_modules/vite/README.md +0 -20
  150. /package/dist/{auth-BdsJ0Aff.d.mts → auth-DrfOTMmr.d.mts} +0 -0
  151. /package/dist/{auth-migrations-BAtAck2g.mjs → auth-migrations-BwLPwRgH.mjs} +0 -0
  152. /package/dist/{better-auth-shared-CdYmQGry.mjs → better-auth-shared-APuDaPqW.mjs} +0 -0
  153. /package/dist/{defer-DcxEsVH1.mjs → defer-2ARBu8Et.mjs} +0 -0
  154. /package/dist/{drizzle-NnudE_UN.mjs → drizzle-C-NRqGhx.mjs} +0 -0
  155. /package/dist/{env-raw-BDL4TvdN.mjs → env-raw-DtfQ9E31.mjs} +0 -0
  156. /package/dist/{fetch-error-BQ8sZ5Nd.mjs → fetch-error-CEr0ACTl.mjs} +0 -0
  157. /package/dist/{fetch-error-CVZ5CGA-.d.mts → fetch-error-DflegrF3.d.mts} +0 -0
  158. /package/dist/{head-P-egrtFE.d.mts → head-CZGAunBV.d.mts} +0 -0
  159. /package/dist/{headers-DCXc7mDs.mjs → headers-YVkHjOyq.mjs} +0 -0
  160. /package/dist/{preset-D4I73kT4.mjs → preset-DFvePt0l.mjs} +0 -0
  161. /package/dist/{project-slug-CKam8lF9.mjs → project-slug-KRvHQEQI.mjs} +0 -0
  162. /package/dist/{resolve-project-Br5BR03U.mjs → resolve-project-DdjLQ2tB.mjs} +0 -0
  163. /package/dist/{runner-pg-D0wWHYnr.mjs → runner-pg-BI6f6Ncm.mjs} +0 -0
  164. /package/dist/{standard-schema-9CRjx-uR.d.mts → standard-schema-BfGDWXff.d.mts} +0 -0
  165. /package/dist/{subcommand-prompt-BKjuNAPb.mjs → subcommand-prompt-BMS1TNG5.mjs} +0 -0
  166. /package/dist/{types-mHOEwpW4.d.mts → types-AdKzPp2C.d.mts} +0 -0
  167. /package/dist/{yarn-pnp-BFqMV_bl.mjs → yarn-pnp-6LD6_3Ej.mjs} +0 -0
@@ -4,7 +4,7 @@ import { n as cliTitle, r as createSpinner, s as import_picocolors } from "./out
4
4
  import { t as findVoidAuthConfig } from "./config-CvHtTM0q.mjs";
5
5
  import { c as R, g as ge, u as Se, v as ue, x as q, y as ye } from "./dist-Dayj3gCK.mjs";
6
6
  import { a as writeProjectConfig, r as readProjectConfig } from "./project-TqORyHn8.mjs";
7
- import { a as parsePlatformErrorBody, c as getTokenSource, i as isExpiredTokenError, l as isStagingMode, n as PlatformClient, s as getToken, t as PlatformApiError } from "./client-BUdfE3QJ.mjs";
7
+ import { a as isExpiredTokenError, c as getToken, i as isCliOutdatedError, l as getTokenSource, n as PlatformClient, o as parsePlatformErrorBody, t as PlatformApiError, u as isStagingMode } from "./client-DCqnMpDt.mjs";
8
8
  import { c as getDatabaseDialect, f as readConfig, l as isNodeTarget, p as resolveBindingNames } from "./config-BIa9HwVX.mjs";
9
9
  import { i as scanJobsSync, n as scanWebSocketRoutesSync, r as scanQueuesSync, t as scanRoutes } from "./scan-C6HMEIdW.mjs";
10
10
  import { c as validateSsrEntry, n as detectFramework, r as inferProjectBindings, t as FRAMEWORK_SCAN_DIRS } from "./plugin-inference-oZ6Ybu2_.mjs";
@@ -16,11 +16,11 @@ import { n as writeDrizzleConfig } from "./config-BzM9Dy7T.mjs";
16
16
  import { t as collectMigrations } from "./collect-CjeZgz5D.mjs";
17
17
  import { r as validateMigrations, t as assertJournalCoherence } from "./validate-CaMavMxu.mjs";
18
18
  import { t as scanPages } from "./scan-Ba4hFwlH.mjs";
19
- import { n as promptProjectSelection, r as promptProjectSetupAction, t as promptAndCreateProject } from "./create-project-CN1pF-OQ.mjs";
20
- import { r as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-Br5BR03U.mjs";
21
- import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-DCXc7mDs.mjs";
22
- import { t as promptForLoginToken } from "./login-RWUDCfdx.mjs";
23
- import { i as resolveProjectCommand, n as detectPreset, r as formatProjectCommand, t as FRAMEWORK_PRESETS } from "./preset-D4I73kT4.mjs";
19
+ import { n as promptProjectSelection, r as promptProjectSetupAction, t as promptAndCreateProject } from "./create-project-Bg88Kq_I.mjs";
20
+ import { r as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-DdjLQ2tB.mjs";
21
+ import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-YVkHjOyq.mjs";
22
+ import { t as promptForLoginToken } from "./login-B5HHT32i.mjs";
23
+ import { i as resolveProjectCommand, n as detectPreset, r as formatProjectCommand, t as FRAMEWORK_PRESETS } from "./preset-DFvePt0l.mjs";
24
24
  import { createRequire } from "node:module";
25
25
  import { copyFileSync, cpSync, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
26
26
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -28,6 +28,7 @@ import { build, loadEnv } from "vite";
28
28
  import { homedir, tmpdir } from "node:os";
29
29
  import { parse } from "jsonc-parser";
30
30
  import { execFile, execFileSync, execSync } from "node:child_process";
31
+ import { createHash } from "node:crypto";
31
32
  import { hash } from "blake3-jit";
32
33
  import ignore from "ignore";
33
34
  const SANDBOX_MIGRATION_TAG = "void-sandbox-v1";
@@ -848,6 +849,7 @@ function formatStreamDeployError(err, deploymentId, logPath) {
848
849
  const parsed = parsePlatformErrorBody(err.body);
849
850
  const parsedError = parsed?.error ?? parsed?.message ?? null;
850
851
  const effectiveDeploymentId = parsed?.deployment ?? null ?? deploymentId;
852
+ if (isCliOutdatedError(err) && parsed?.message) return new Error(appendTrailingLines(parsed.message, effectiveDeploymentId, logPath));
851
853
  if (err.status === 409 && parsedError) return new Error(appendTrailingLines(`deploy: Deploy failed: ${parsedError}`, effectiveDeploymentId, logPath));
852
854
  const detail = parsedError ?? trimSnippet(err.body);
853
855
  const base = detail ? `deploy: Deploy failed (HTTP ${err.status}): ${detail}` : `deploy: Deploy failed (HTTP ${err.status}).`;
@@ -856,6 +858,241 @@ function formatStreamDeployError(err, deploymentId, logPath) {
856
858
  const original = err instanceof Error ? err.message : String(err);
857
859
  return new Error(appendTrailingLines(`deploy: Connection lost during deploy: '${original}'.`, deploymentId, logPath));
858
860
  }
861
+ /**
862
+ * Maximum retry attempts for a single R2 PUT before giving up. Matched to
863
+ * the design doc 0068 spec: 5xx triggers retry, 4xx surfaces immediately.
864
+ */
865
+ const MAX_R2_PUT_RETRIES = 3;
866
+ /**
867
+ * Retry backoff delays in milliseconds. After attempt `i` fails (5xx) we
868
+ * sleep `RETRY_BACKOFF_MS[i]` before retrying. Indexed from 0; once the
869
+ * array is exhausted we surface the error.
870
+ */
871
+ const RETRY_BACKOFF_MS = [
872
+ 250,
873
+ 500,
874
+ 1e3
875
+ ];
876
+ var DirectR2PutError = class extends Error {
877
+ status;
878
+ hash;
879
+ path;
880
+ /**
881
+ * `body` is the response body text (may be empty / non-text). Useful in
882
+ * the surface error so users can correlate against R2-side errors like
883
+ * `BadDigest` (Content-MD5 mismatch) or signature errors.
884
+ */
885
+ body;
886
+ constructor(status, hash, path, body) {
887
+ super(`R2 PUT failed for ${path} (${hash}): ${status}${body ? ` ${body}` : ""}`);
888
+ this.name = "DirectR2PutError";
889
+ this.status = status;
890
+ this.hash = hash;
891
+ this.path = path;
892
+ this.body = body;
893
+ }
894
+ };
895
+ /**
896
+ * Issue direct-to-R2 PUTs for every item in `items`, bounded to
897
+ * `R2_PUT_CONCURRENCY`. Surfaces the first failure (4xx immediately, 5xx
898
+ * after retry exhaustion) as a `DirectR2PutError` carrying the asset path
899
+ * and hash so the user can correlate against the JSONL log.
900
+ *
901
+ * Logs `r2_put_start` / `r2_put_end` per asset, plus `r2_put_retry` on
902
+ * each retry attempt. Per-asset durations help identify slow uploads.
903
+ *
904
+ * Memory: each successful PUT clears `item.body` and invokes `onAfterPut`
905
+ * so the caller can drop its own ref (e.g. delete from the in-memory
906
+ * `assetFiles` cache). For huge deploys this keeps peak memory bounded by
907
+ * the upload concurrency rather than the total bundle size.
908
+ */
909
+ async function uploadAssetsToR2(items, cliLog, fetchImpl = fetch, onAfterPut) {
910
+ if (items.length === 0) return;
911
+ let firstError = null;
912
+ let nextIndex = 0;
913
+ const workerCount = Math.min(10, items.length);
914
+ const workers = [];
915
+ for (let w = 0; w < workerCount; w++) workers.push((async () => {
916
+ while (firstError === null) {
917
+ const i = nextIndex++;
918
+ if (i >= items.length) return;
919
+ try {
920
+ await uploadOne(items[i], cliLog, fetchImpl);
921
+ } catch (err) {
922
+ if (firstError === null) firstError = err;
923
+ return;
924
+ }
925
+ items[i].body = void 0;
926
+ onAfterPut?.(items[i]);
927
+ }
928
+ })());
929
+ await Promise.all(workers);
930
+ if (firstError !== null) throw firstError;
931
+ }
932
+ async function uploadOne(item, cliLog, fetchImpl) {
933
+ if (!item.body) throw new Error(`internal: uploadOne invoked for ${item.path} with no body`);
934
+ const body = item.body;
935
+ const startedAt = Date.now();
936
+ cliLog?.info("r2_put_start", {
937
+ hash: item.hash,
938
+ path: item.path,
939
+ sizeBytes: body.length
940
+ });
941
+ let attempt = 0;
942
+ while (true) {
943
+ let res;
944
+ try {
945
+ res = await fetchImpl(item.url, {
946
+ method: "PUT",
947
+ body: new Uint8Array(body),
948
+ headers: item.headers
949
+ });
950
+ } catch (err) {
951
+ if (attempt < MAX_R2_PUT_RETRIES) {
952
+ cliLog?.warn("r2_put_retry", {
953
+ hash: item.hash,
954
+ path: item.path,
955
+ attempt: attempt + 1,
956
+ status: 0,
957
+ error: err instanceof Error ? err.message : String(err)
958
+ });
959
+ await sleep(RETRY_BACKOFF_MS[attempt]);
960
+ attempt++;
961
+ continue;
962
+ }
963
+ cliLog?.error("r2_put_end", err, {
964
+ hash: item.hash,
965
+ path: item.path,
966
+ durationMs: Date.now() - startedAt,
967
+ status: 0
968
+ });
969
+ throw new DirectR2PutError(0, item.hash, item.path, err instanceof Error ? err.message : String(err));
970
+ }
971
+ if (res.ok) {
972
+ cliLog?.info("r2_put_end", {
973
+ hash: item.hash,
974
+ path: item.path,
975
+ durationMs: Date.now() - startedAt,
976
+ status: res.status
977
+ });
978
+ return;
979
+ }
980
+ const errBody = await safeReadText(res);
981
+ if (res.status >= 500 && attempt < MAX_R2_PUT_RETRIES) {
982
+ cliLog?.warn("r2_put_retry", {
983
+ hash: item.hash,
984
+ path: item.path,
985
+ attempt: attempt + 1,
986
+ status: res.status,
987
+ body: truncate(errBody)
988
+ });
989
+ await sleep(RETRY_BACKOFF_MS[attempt]);
990
+ attempt++;
991
+ continue;
992
+ }
993
+ cliLog?.error("r2_put_end", void 0, {
994
+ hash: item.hash,
995
+ path: item.path,
996
+ durationMs: Date.now() - startedAt,
997
+ status: res.status,
998
+ body: truncate(errBody)
999
+ });
1000
+ throw new DirectR2PutError(res.status, item.hash, item.path, truncate(errBody));
1001
+ }
1002
+ }
1003
+ async function safeReadText(res) {
1004
+ try {
1005
+ return await res.text();
1006
+ } catch {
1007
+ return "";
1008
+ }
1009
+ }
1010
+ function truncate(s) {
1011
+ return s.length > 500 ? `${s.slice(0, 500)}...` : s;
1012
+ }
1013
+ function sleep(ms) {
1014
+ return new Promise((resolve) => setTimeout(resolve, ms));
1015
+ }
1016
+ //#endregion
1017
+ //#region src/cli/git-metadata.ts
1018
+ function git(root, args) {
1019
+ try {
1020
+ return execFileSync("git", args, {
1021
+ cwd: root,
1022
+ encoding: "utf-8",
1023
+ stdio: [
1024
+ "ignore",
1025
+ "pipe",
1026
+ "ignore"
1027
+ ]
1028
+ }).trim();
1029
+ } catch {
1030
+ return null;
1031
+ }
1032
+ }
1033
+ function stripGitSuffix(pathname) {
1034
+ return pathname.endsWith(".git") ? pathname.slice(0, -4) : pathname;
1035
+ }
1036
+ function normalizeGitRemoteUrl(remote) {
1037
+ if (!remote) return null;
1038
+ const trimmed = remote.trim();
1039
+ if (!trimmed) return null;
1040
+ const scpLike = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
1041
+ if (scpLike && !trimmed.includes("://")) {
1042
+ const host = scpLike[1]?.toLowerCase();
1043
+ const path = stripGitSuffix(scpLike[2] ?? "").replace(/^\/+/, "");
1044
+ return host && path ? `https://${host}/${path}` : null;
1045
+ }
1046
+ try {
1047
+ const url = new URL(trimmed);
1048
+ if (url.protocol !== "https:" && url.protocol !== "http:" && url.protocol !== "ssh:") return null;
1049
+ const host = url.hostname.toLowerCase();
1050
+ const path = stripGitSuffix(url.pathname).replace(/^\/+/, "");
1051
+ return host && path ? `https://${host}/${path}` : null;
1052
+ } catch {
1053
+ return null;
1054
+ }
1055
+ }
1056
+ function buildCommitUrl(repositoryUrl, commit) {
1057
+ if (!repositoryUrl || !commit) return null;
1058
+ try {
1059
+ const url = new URL(repositoryUrl);
1060
+ if (url.hostname === "github.com") return `${repositoryUrl}/commit/${commit}`;
1061
+ if (url.hostname === "gitlab.com") return `${repositoryUrl}/-/commit/${commit}`;
1062
+ if (url.hostname === "bitbucket.org") return `${repositoryUrl}/commits/${commit}`;
1063
+ } catch {
1064
+ return null;
1065
+ }
1066
+ return null;
1067
+ }
1068
+ function getGitRefFromEnv(env) {
1069
+ if (env.GITHUB_HEAD_REF) return env.GITHUB_HEAD_REF;
1070
+ if (env.GITHUB_REF_NAME) return env.GITHUB_REF_NAME;
1071
+ if (env.GITHUB_REF) return env.GITHUB_REF.replace(/^refs\/(?:heads|tags|pull)\//, "");
1072
+ return null;
1073
+ }
1074
+ function getGitHubRepositoryUrl(env) {
1075
+ if (!env.GITHUB_REPOSITORY) return null;
1076
+ return normalizeGitRemoteUrl(`${env.GITHUB_SERVER_URL ?? "https://github.com"}/${env.GITHUB_REPOSITORY}`);
1077
+ }
1078
+ function collectGitDeployMetadata(root, commit, env = process.env) {
1079
+ const repositoryUrl = getGitHubRepositoryUrl(env) ?? normalizeGitRemoteUrl(git(root, [
1080
+ "remote",
1081
+ "get-url",
1082
+ "origin"
1083
+ ]));
1084
+ const gitRef = getGitRefFromEnv(env) ?? git(root, [
1085
+ "rev-parse",
1086
+ "--abbrev-ref",
1087
+ "HEAD"
1088
+ ]);
1089
+ return {
1090
+ commit,
1091
+ commitUrl: buildCommitUrl(repositoryUrl, commit),
1092
+ gitRef: gitRef && gitRef !== "HEAD" ? gitRef : null,
1093
+ repositoryUrl
1094
+ };
1095
+ }
859
1096
  //#endregion
860
1097
  //#region src/cli/log-file.ts
861
1098
  /**
@@ -1110,8 +1347,88 @@ function computeAssetHash(content, filePath) {
1110
1347
  return Buffer.from(hash(input)).toString("hex").slice(0, 32);
1111
1348
  }
1112
1349
  /**
1350
+ * Map of common asset file extensions → content type. Mirrors
1351
+ * `packages/dispatch/src/static.ts`'s MIME_TYPES table so the value the CLI
1352
+ * declares for the R2 PUT signature matches what dispatch will serve later
1353
+ * (dispatch reads `obj.httpMetadata?.contentType` first, falling back to
1354
+ * path-based inference). Keeping the tables aligned avoids a needless
1355
+ * mismatch where R2-stored content-type and serve-time content-type
1356
+ * disagree on the same asset.
1357
+ */
1358
+ const ASSET_MIME_TYPES = {
1359
+ html: "text/html; charset=utf-8",
1360
+ htm: "text/html; charset=utf-8",
1361
+ css: "text/css; charset=utf-8",
1362
+ js: "text/javascript; charset=utf-8",
1363
+ mjs: "text/javascript; charset=utf-8",
1364
+ json: "application/json; charset=utf-8",
1365
+ xml: "application/xml; charset=utf-8",
1366
+ svg: "image/svg+xml; charset=utf-8",
1367
+ txt: "text/plain; charset=utf-8",
1368
+ csv: "text/csv; charset=utf-8",
1369
+ png: "image/png",
1370
+ jpg: "image/jpeg",
1371
+ jpeg: "image/jpeg",
1372
+ gif: "image/gif",
1373
+ ico: "image/x-icon",
1374
+ webp: "image/webp",
1375
+ avif: "image/avif",
1376
+ woff: "font/woff",
1377
+ woff2: "font/woff2",
1378
+ ttf: "font/ttf",
1379
+ otf: "font/otf",
1380
+ eot: "application/vnd.ms-fontobject",
1381
+ pdf: "application/pdf",
1382
+ wasm: "application/wasm",
1383
+ webmanifest: "application/manifest+json; charset=utf-8",
1384
+ map: "application/json; charset=utf-8",
1385
+ mp4: "video/mp4",
1386
+ webm: "video/webm",
1387
+ mp3: "audio/mpeg",
1388
+ ogg: "audio/ogg"
1389
+ };
1390
+ /**
1391
+ * Resolve the content-type to declare on a direct-to-R2 PUT for `path`.
1392
+ * The value is part of the signed Content-Type header on the presigned
1393
+ * URL — if the bytes are uploaded with a different content-type, R2
1394
+ * rejects the PUT (signature mismatch). Defaults to
1395
+ * `application/octet-stream` for unknown extensions.
1396
+ */
1397
+ function getAssetContentType(path) {
1398
+ return ASSET_MIME_TYPES[path.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream";
1399
+ }
1400
+ /**
1401
+ * Compute MD5 of raw content as a 32-char lowercase hex string.
1402
+ *
1403
+ * Used as the wire-level upload-integrity primitive on direct-to-R2 PUTs:
1404
+ * R2's S3 compatibility layer enforces `Content-MD5` (RFC 1864) on PutObject,
1405
+ * so the upload itself fails if uploaded bytes don't match the declared MD5.
1406
+ * MD5 collision resistance is acceptable here because (a) this hash is purely
1407
+ * an upload-integrity primitive (BLAKE3 remains the canonical content key),
1408
+ * and (b) project-scoped storage (`assets/${projectId}/${blake3}`) means any
1409
+ * collision attack is exploitable only against the attacker's own project.
1410
+ */
1411
+ function computeMd5(content) {
1412
+ return createHash("md5").update(content).digest("hex");
1413
+ }
1414
+ /**
1415
+ * Compute both BLAKE3 (CF-compatible asset hash) and MD5 (wire-level R2
1416
+ * upload-integrity checksum) from a single in-memory buffer. Returned in
1417
+ * the v2 manifest entry shape for direct-to-R2 deploys.
1418
+ *
1419
+ * Single-pass: callers read the file once into `content`; this function
1420
+ * then derives both digests from that buffer. The file is never read twice.
1421
+ */
1422
+ function computeAssetHashes(content, filePath) {
1423
+ return {
1424
+ blake3: computeAssetHash(content, filePath),
1425
+ md5: computeMd5(content)
1426
+ };
1427
+ }
1428
+ /**
1113
1429
  * Create a filter function that excludes .assetsignore, _headers, _redirects,
1114
- * deploy-internal patterns, and any patterns listed in a .assetsignore file.
1430
+ * OS metadata files, deploy-internal patterns, and any patterns listed in a
1431
+ * .assetsignore file.
1115
1432
  * Matches Wrangler's createAssetsIgnoreFunction from workers-shared/utils/helpers.ts.
1116
1433
  */
1117
1434
  function createAssetsIgnoreFunction(dir, extraPatterns = []) {
@@ -1119,6 +1436,7 @@ function createAssetsIgnoreFunction(dir, extraPatterns = []) {
1119
1436
  "/.assetsignore",
1120
1437
  "/_redirects",
1121
1438
  "/_headers",
1439
+ ".DS_Store",
1122
1440
  ...extraPatterns
1123
1441
  ];
1124
1442
  const assetsIgnorePath = join(dir, ".assetsignore");
@@ -1152,6 +1470,9 @@ function checkWorkerJsAsset(relativePath, assetsIgnoreFilePresent) {
1152
1470
  /**
1153
1471
  * Collect all files in a directory, compute hashes, and return both the
1154
1472
  * manifest (for preflight) and the raw file map (for selective packaging).
1473
+ *
1474
+ * Each file is read from disk exactly once. Both BLAKE3 (storage key) and
1475
+ * MD5 (R2 upload-integrity) digests are derived from that single buffer.
1155
1476
  */
1156
1477
  async function collectAndHashAssets(dir, onProgress, opts = {}) {
1157
1478
  const assetManifest = {};
@@ -1167,9 +1488,12 @@ async function collectAndHashAssets(dir, onProgress, opts = {}) {
1167
1488
  if (isIgnored(relativePath)) continue;
1168
1489
  checkWorkerJsAsset(relativePath, assetsIgnoreFilePresent);
1169
1490
  const content = readFileSync(filePath);
1491
+ const { blake3, md5 } = computeAssetHashes(content, filePath);
1170
1492
  assetManifest[relativePath] = {
1171
- hash: computeAssetHash(content, filePath),
1172
- size: content.length
1493
+ blake3,
1494
+ md5,
1495
+ size: content.length,
1496
+ hash: blake3
1173
1497
  };
1174
1498
  assetFiles.set(relativePath, content);
1175
1499
  }
@@ -1204,10 +1528,32 @@ function toSandboxManifest(sandbox) {
1204
1528
  ...sandbox.maxInstances != null && { maxInstances: sandbox.maxInstances }
1205
1529
  };
1206
1530
  }
1207
- async function packageBuild(distDir, workerDirName, bindings, migrations, schedules, ssr, framework, revalidate, vars, queues, prerender, assetConfig, options, neededAssets, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, dialect, webSockets, onProgress) {
1208
- const formData = new FormData();
1531
+ /**
1532
+ * If the user configured `routing.fallbacks` on a non-SPA app, the platform
1533
+ * still applies the rules but no `/* -> /index.html` SPA shell is synthesised
1534
+ * — unmatched paths fall through to the platform 404. Returns the warning
1535
+ * message string when that mismatch is detected, or `null` otherwise. Pure
1536
+ * function; callers decide whether/how to surface the message (CLI progress,
1537
+ * `console.warn`, etc.).
1538
+ */
1539
+ function detectNonSpaFallbackWarning(assetConfig, fallbackRules) {
1540
+ if (assetConfig?.not_found_handling === "single-page-application") return null;
1541
+ if (!fallbackRules || fallbackRules.length === 0) return null;
1542
+ return "routing.fallbacks is set but appType is not \"spa\"; fallbacks will be applied, but no \"/* -> /index.html\" SPA shell fallback is synthesised — unmatched paths fall through to the platform 404.";
1543
+ }
1544
+ /**
1545
+ * Build a Void-app deploy manifest from the inferred bindings + per-feature
1546
+ * inputs. Pure shape transformation: reads no files, has no I/O.
1547
+ *
1548
+ * Note: callers are responsible for surfacing the non-SPA-fallbacks warning
1549
+ * (see `detectNonSpaFallbackWarning`) before invoking this builder. The
1550
+ * builder itself stays I/O-free.
1551
+ */
1552
+ function buildVoidManifest(opts) {
1553
+ const { bindings, migrations, schedules, ssr, framework, revalidate, vars, queues, prerender, assetConfig, options, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, dialect, webSockets } = opts;
1209
1554
  const manifest = {
1210
1555
  version: 3,
1556
+ assetManifestVersion: 2,
1211
1557
  bindings: {}
1212
1558
  };
1213
1559
  if (bindings.needsAuth) manifest.auth = true;
@@ -1224,14 +1570,10 @@ async function packageBuild(distDir, workerDirName, bindings, migrations, schedu
1224
1570
  binding: options.bindingNames?.sandbox ?? options.sandbox.binding
1225
1571
  });
1226
1572
  }
1227
- if (migrations && migrations.length > 0) {
1228
- manifest.migrations = {
1229
- dialect: dialect ?? "sqlite",
1230
- pending: migrations.length
1231
- };
1232
- onProgress?.(`Packaging ${migrations.length} migration${migrations.length === 1 ? "" : "s"}...`);
1233
- for (const m of migrations) formData.append(`migration:${m.name}`, new Blob([m.sql]), m.name);
1234
- }
1573
+ if (migrations && migrations.length > 0) manifest.migrations = {
1574
+ dialect: dialect ?? "sqlite",
1575
+ pending: migrations.length
1576
+ };
1235
1577
  if (schedules && schedules.length > 0) manifest.schedules = schedules;
1236
1578
  if (queues && queues.length > 0) manifest.queues = queues;
1237
1579
  if (ssr) manifest.ssr = true;
@@ -1249,10 +1591,6 @@ async function packageBuild(distDir, workerDirName, bindings, migrations, schedu
1249
1591
  ...chain.assetConfig
1250
1592
  };
1251
1593
  resolvedFallbackRules = chain.fallbackRules;
1252
- } else if (fallbackRules && fallbackRules.length > 0) {
1253
- const msg = "routing.fallbacks is set but appType is not \"spa\"; fallbacks will be applied, but no \"/* -> /index.html\" SPA shell fallback is synthesised — unmatched paths fall through to the platform 404.";
1254
- onProgress?.(msg);
1255
- console.warn(`[void] ${msg}`);
1256
1594
  }
1257
1595
  if (resolvedAssetConfig) manifest.assetConfig = resolvedAssetConfig;
1258
1596
  if (vars && Object.keys(vars).length > 0) manifest.vars = vars;
@@ -1265,45 +1603,40 @@ async function packageBuild(distDir, workerDirName, bindings, migrations, schedu
1265
1603
  if (resolvedFallbackRules && resolvedFallbackRules.length > 0) manifest.fallbackRules = stripDevOnlyRuleFields(resolvedFallbackRules);
1266
1604
  const websocketManifest = toWebSocketManifest(webSockets);
1267
1605
  if (websocketManifest) manifest.websocket = websocketManifest;
1268
- formData.append("manifest", JSON.stringify(manifest));
1269
- onProgress?.("Packaging worker files...");
1270
- const workerDir = join(distDir, workerDirName);
1271
- const workerFiles = collectFiles(workerDir);
1272
- for (const filePath of workerFiles) {
1273
- const relativePath = toSlash(relative(workerDir, filePath));
1274
- if (!isWorkerModule(relativePath)) continue;
1275
- const content = readFileSync(filePath);
1276
- formData.append(`worker:${relativePath}`, new Blob([content]), relativePath);
1277
- }
1278
- const clientDir = join(distDir, "client");
1606
+ return manifest;
1607
+ }
1608
+ /**
1609
+ * Read every worker module under `workerDir` once and return them as a map
1610
+ * keyed by relative path. Skipped files (build metadata, wrangler config,
1611
+ * non-module extensions) are filtered out by `isWorkerModule`. Reads each
1612
+ * file from disk exactly once — buffer threading downstream is the caller's
1613
+ * responsibility.
1614
+ */
1615
+ function collectWorkerFiles(workerDir) {
1616
+ const out = /* @__PURE__ */ new Map();
1617
+ let workerPaths;
1279
1618
  try {
1280
- const { isIgnored, assetsIgnoreFilePresent } = createAssetsIgnoreFunction(clientDir);
1281
- const clientFiles = collectFiles(clientDir);
1282
- let processed = 0;
1283
- for (const filePath of clientFiles) {
1284
- processed++;
1285
- await tickProgress(processed, clientFiles.length, onProgress, "Packaging assets");
1286
- const relativePath = toSlash(relative(clientDir, filePath));
1287
- if (isIgnored("/" + relativePath)) continue;
1288
- checkWorkerJsAsset(relativePath, assetsIgnoreFilePresent);
1289
- if (neededAssets && !neededAssets.has("/" + relativePath)) continue;
1290
- const content = readFileSync(filePath);
1291
- formData.append(`asset:${relativePath}`, new Blob([content]), relativePath);
1292
- }
1619
+ workerPaths = collectFiles(workerDir);
1293
1620
  } catch (err) {
1294
- if (err instanceof Error && "code" in err && err.code === "ENOENT") {} else throw err;
1621
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") return out;
1622
+ throw err;
1623
+ }
1624
+ for (const filePath of workerPaths) {
1625
+ const relativePath = toSlash(relative(workerDir, filePath));
1626
+ if (!isWorkerModule(relativePath)) continue;
1627
+ out.set(relativePath, readFileSync(filePath));
1295
1628
  }
1296
- if (neededAssets) formData.append("preflight", "true");
1297
- return formData;
1629
+ return out;
1298
1630
  }
1299
1631
  /**
1300
- * Package a static build (no worker) into a FormData ready for upload.
1632
+ * Build a static-deploy manifest. Pure shape transformation no I/O.
1301
1633
  */
1302
- async function packageStaticBuild(outputDir, appType, neededAssets, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, onProgress) {
1303
- const formData = new FormData();
1634
+ function buildStaticManifest(opts) {
1635
+ const { appType, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules } = opts;
1304
1636
  const { assetConfig, fallbackRules: resolvedFallbackRules } = resolveSpaFallbackChain(appType, fallbackRules);
1305
1637
  const manifest = {
1306
1638
  version: 3,
1639
+ assetManifestVersion: 2,
1307
1640
  bindings: {},
1308
1641
  type: appType,
1309
1642
  assetConfig
@@ -1312,36 +1645,16 @@ async function packageStaticBuild(outputDir, appType, neededAssets, hashedAssets
1312
1645
  if (headerRules && headerRules.length > 0) manifest.headerRules = headerRules;
1313
1646
  if (redirectRules && redirectRules.length > 0) manifest.redirectRules = stripDevOnlyRuleFields(redirectRules);
1314
1647
  if (resolvedFallbackRules && resolvedFallbackRules.length > 0) manifest.fallbackRules = stripDevOnlyRuleFields(resolvedFallbackRules);
1315
- formData.append("manifest", JSON.stringify(manifest));
1316
- const { isIgnored, assetsIgnoreFilePresent } = createAssetsIgnoreFunction(outputDir);
1317
- const files = collectFiles(outputDir);
1318
- let processed = 0;
1319
- for (const filePath of files) {
1320
- processed++;
1321
- await tickProgress(processed, files.length, onProgress, "Packaging assets");
1322
- const relativePath = toSlash(relative(outputDir, filePath));
1323
- if (isIgnored("/" + relativePath)) continue;
1324
- checkWorkerJsAsset(relativePath, assetsIgnoreFilePresent);
1325
- if (neededAssets && !neededAssets.has("/" + relativePath)) continue;
1326
- const content = readFileSync(filePath);
1327
- formData.append(`asset:${relativePath}`, new Blob([content]), relativePath);
1328
- }
1329
- if (neededAssets) formData.append("preflight", "true");
1330
- return formData;
1648
+ return manifest;
1331
1649
  }
1332
1650
  /**
1333
- * Package a framework build (Class B/C) into a FormData ready for upload.
1334
- *
1335
- * Unlike `packageBuild`, the worker and assets directories are absolute paths
1336
- * (resolved from the project root + preset), and the worker directory may be
1337
- * inside the assets directory (SvelteKit, Astro). Worker files are excluded
1338
- * from asset collection when directories overlap.
1651
+ * Build a framework-deploy manifest. Pure shape transformation no I/O.
1339
1652
  */
1340
- async function packageFrameworkBuild(opts) {
1341
- const formData = new FormData();
1653
+ function buildFrameworkManifest(opts) {
1342
1654
  const po = opts.packageOptions;
1343
1655
  const manifest = {
1344
1656
  version: 3,
1657
+ assetManifestVersion: 2,
1345
1658
  bindings: {}
1346
1659
  };
1347
1660
  const hasMigrations = opts.migrations && opts.migrations.length > 0;
@@ -1350,14 +1663,10 @@ async function packageFrameworkBuild(opts) {
1350
1663
  if (opts.bindings.needsKV) manifest.bindings.kv = [po?.bindingNames?.kv ?? "KV"];
1351
1664
  if (opts.bindings.needsR2) manifest.bindings.r2 = [po?.bindingNames?.r2 ?? "STORAGE"];
1352
1665
  if (opts.bindings.needsAI) manifest.bindings.ai = true;
1353
- if (opts.migrations && opts.migrations.length > 0) {
1354
- manifest.migrations = {
1355
- dialect: opts.dialect ?? "sqlite",
1356
- pending: opts.migrations.length
1357
- };
1358
- opts.onProgress?.(`Packaging ${opts.migrations.length} migration${opts.migrations.length === 1 ? "" : "s"}...`);
1359
- for (const m of opts.migrations) formData.append(`migration:${m.name}`, new Blob([m.sql]), m.name);
1360
- }
1666
+ if (opts.migrations && opts.migrations.length > 0) manifest.migrations = {
1667
+ dialect: opts.dialect ?? "sqlite",
1668
+ pending: opts.migrations.length
1669
+ };
1361
1670
  if (opts.schedules && opts.schedules.length > 0) manifest.schedules = opts.schedules;
1362
1671
  if (opts.queues && opts.queues.length > 0) manifest.queues = opts.queues;
1363
1672
  if (opts.revalidate != null && opts.revalidate !== 0) manifest.revalidate = opts.revalidate;
@@ -1377,38 +1686,7 @@ async function packageFrameworkBuild(opts) {
1377
1686
  if (opts.fallbackRules && opts.fallbackRules.length > 0) manifest.fallbackRules = stripDevOnlyRuleFields(opts.fallbackRules);
1378
1687
  const websocketManifest = toWebSocketManifest(opts.webSockets);
1379
1688
  if (websocketManifest) manifest.websocket = websocketManifest;
1380
- formData.append("manifest", JSON.stringify(manifest));
1381
- const workerDirNorm = opts.workerDir.replace(/\\/g, "/");
1382
- const assetsDirNorm = opts.assetsDir.replace(/\\/g, "/");
1383
- const isOverlap = workerDirNorm.startsWith(assetsDirNorm + "/") || workerDirNorm === assetsDirNorm;
1384
- opts.onProgress?.("Packaging worker files...");
1385
- const workerFiles = collectFiles(opts.workerDir);
1386
- for (const filePath of workerFiles) {
1387
- const relativePath = toSlash(relative(opts.workerDir, filePath));
1388
- if (!isWorkerModule(relativePath)) continue;
1389
- const content = readFileSync(filePath);
1390
- formData.append(`worker:${relativePath}`, new Blob([content]), relativePath);
1391
- }
1392
- try {
1393
- const { isIgnored, assetsIgnoreFilePresent } = createAssetsIgnoreFunction(opts.assetsDir, opts.assetIgnorePatterns);
1394
- const assetFiles = collectFiles(opts.assetsDir);
1395
- let processed = 0;
1396
- for (const filePath of assetFiles) {
1397
- processed++;
1398
- await tickProgress(processed, assetFiles.length, opts.onProgress, "Packaging assets");
1399
- if (isOverlap && filePath.replace(/\\/g, "/").startsWith(workerDirNorm)) continue;
1400
- const relativePath = toSlash(relative(opts.assetsDir, filePath));
1401
- if (isIgnored("/" + relativePath)) continue;
1402
- checkWorkerJsAsset(relativePath, assetsIgnoreFilePresent);
1403
- if (opts.neededAssets && !opts.neededAssets.has("/" + relativePath)) continue;
1404
- const content = readFileSync(filePath);
1405
- formData.append(`asset:${relativePath}`, new Blob([content]), relativePath);
1406
- }
1407
- } catch (err) {
1408
- if (err instanceof Error && "code" in err && err.code === "ENOENT") {} else throw err;
1409
- }
1410
- if (opts.neededAssets) formData.append("preflight", "true");
1411
- return formData;
1689
+ return manifest;
1412
1690
  }
1413
1691
  /**
1414
1692
  * Check if a file (by relative path) should be included as a worker module.
@@ -1688,6 +1966,7 @@ var deploy_exports = /* @__PURE__ */ __exportAll({
1688
1966
  resolveStaticBuildCommand: () => resolveStaticBuildCommand,
1689
1967
  resolveWorkerDirName: () => resolveWorkerDirName,
1690
1968
  runDeploy: () => runDeploy,
1969
+ runStaticDeploy: () => runStaticDeploy,
1691
1970
  warnRedundantForceOn3xx: () => warnRedundantForceOn3xx
1692
1971
  });
1693
1972
  const drizzleKitBin = join(fileURLToPath(import.meta.resolve("drizzle-kit")), "..", "bin.cjs");
@@ -1801,15 +2080,15 @@ async function runDeployInner(root, options, cliLog) {
1801
2080
  name: detected.name,
1802
2081
  class: detected.class
1803
2082
  });
1804
- const gitCommit = options?.skipBuild ? null : getGitCommit(root);
2083
+ const gitMetadata = collectGitDeployMetadata(root, options?.skipBuild ? null : getGitCommit(root));
1805
2084
  const fwPreset = FRAMEWORK_PRESETS[detected.name];
1806
2085
  if (detected.class === "b" || detected.class === "c") {
1807
2086
  if (!fwPreset) {
1808
2087
  R.error(`No deploy preset found for framework "${detected.name}".`);
1809
2088
  process.exit(1);
1810
2089
  }
1811
- await runFrameworkDeploy(root, config, client, detected, fwPreset, voidConfig, options?.skipBuild, gitCommit, cliLog);
1812
- } else await runFullDeploy(root, config, client, fwPreset, options?.skipBuild, gitCommit, detected, cliLog);
2090
+ await runFrameworkDeploy(root, config, client, detected, fwPreset, voidConfig, options?.skipBuild, gitMetadata, cliLog);
2091
+ } else await runFullDeploy(root, config, client, fwPreset, options?.skipBuild, gitMetadata, detected, cliLog);
1813
2092
  return;
1814
2093
  }
1815
2094
  let preset = null;
@@ -1833,14 +2112,14 @@ async function runDeployInner(root, options, cliLog) {
1833
2112
  dir: options?.dir,
1834
2113
  spa: options?.spa
1835
2114
  });
1836
- const gitCommit = options?.skipBuild ? null : getGitCommit(root);
2115
+ const gitMetadata = collectGitDeployMetadata(root, options?.skipBuild ? null : getGitCommit(root));
1837
2116
  if (preset) {
1838
2117
  preset = {
1839
2118
  ...preset,
1840
2119
  buildCommand: resolveStaticBuildCommand(preset, voidConfig.inference?.build, Boolean(options?.dir))
1841
2120
  };
1842
- await runStaticDeploy(root, config, client, preset, options?.skipBuild, gitCommit, void 0, void 0, void 0, void 0, voidConfig.routing, cliLog);
1843
- } else await runFullDeploy(root, config, client, void 0, options?.skipBuild, gitCommit, void 0, cliLog);
2121
+ await runStaticDeploy(root, config, client, preset, options?.skipBuild, gitMetadata, void 0, void 0, void 0, void 0, voidConfig.routing, cliLog);
2122
+ } else await runFullDeploy(root, config, client, void 0, options?.skipBuild, gitMetadata, void 0, cliLog);
1844
2123
  return;
1845
2124
  } catch (error) {
1846
2125
  if (isExpiredTokenError(error)) {
@@ -1931,7 +2210,12 @@ function applyDeployEvent(event, s) {
1931
2210
  }
1932
2211
  return { kind: "continue" };
1933
2212
  }
1934
- async function streamDeploy(client, projectId, formData, s, cliLog) {
2213
+ /**
2214
+ * Apply a streamed `DeployEvent` iterator to the spinner / cliLog and
2215
+ * resolve with the terminal `done` event (or throw on terminal error /
2216
+ * connection loss).
2217
+ */
2218
+ async function streamDeployEvents(projectId, events, s, cliLog) {
1935
2219
  const startedAt = Date.now();
1936
2220
  let deploymentId = null;
1937
2221
  let interrupted = false;
@@ -1947,7 +2231,7 @@ async function streamDeploy(client, projectId, formData, s, cliLog) {
1947
2231
  };
1948
2232
  process.once("SIGINT", onSigint);
1949
2233
  try {
1950
- for await (const event of client.deploy(projectId, formData)) {
2234
+ for await (const event of events) {
1951
2235
  const result = applyDeployEvent(event, s);
1952
2236
  if (result.kind === "start") {
1953
2237
  deploymentId = result.deploymentId;
@@ -1982,11 +2266,110 @@ async function streamDeploy(client, projectId, formData, s, cliLog) {
1982
2266
  s.stop("Deploy failed");
1983
2267
  throw new Error(formatConnectionLostMessage(deploymentId, cliLog?.path ?? null));
1984
2268
  }
2269
+ /**
2270
+ * Direct-to-R2 deploy orchestration. Issues presigned PUT URLs for each
2271
+ * needed asset, uploads bytes directly to R2, then calls the manifest-only
2272
+ * deploy endpoint to finalize.
2273
+ *
2274
+ * Key invariants:
2275
+ * 1. No needed assets → skip presign + R2 PUT phase entirely (saves a
2276
+ * round-trip when every asset was already present from a prior deploy).
2277
+ * 2. Asset bytes come exclusively from the in-memory `assetFiles` cache
2278
+ * computed by `collectAndHashAssets` — files are NEVER re-read from
2279
+ * disk during this flow (Codex Finding #2 in design doc 0068 review).
2280
+ * 3. R2 PUT failures (4xx, 5xx, network) are surfaced with the asset
2281
+ * path + hash so the user can correlate against the JSONL trace.
2282
+ */
2283
+ async function streamDirectR2Deploy(client, projectId, manifest, assetManifest, assetFiles, needed, gitMetadata, preflightUsed, workerFiles, s, cliLog) {
2284
+ if (needed.length > 0) {
2285
+ const neededPaths = new Set(needed);
2286
+ const preBufferCount = assetFiles.size;
2287
+ const allPaths = Array.from(assetFiles.keys());
2288
+ for (const path of allPaths) if (!neededPaths.has(path)) assetFiles.delete(path);
2289
+ if (assetFiles.size !== preBufferCount) cliLog?.info("asset_cache_pruned", {
2290
+ before: preBufferCount,
2291
+ after: assetFiles.size
2292
+ });
2293
+ s.message(`Requesting upload URLs for ${needed.length} asset(s)...`);
2294
+ const seenBlake3 = /* @__PURE__ */ new Set();
2295
+ const uniqueNeeded = [];
2296
+ for (const path of needed) {
2297
+ const entry = assetManifest[path];
2298
+ if (!entry) throw new Error(`internal: preflight returned ${path} but assetManifest has no entry`);
2299
+ if (seenBlake3.has(entry.blake3)) continue;
2300
+ seenBlake3.add(entry.blake3);
2301
+ uniqueNeeded.push(path);
2302
+ }
2303
+ const assetsToUpload = uniqueNeeded.map((path) => {
2304
+ const entry = assetManifest[path];
2305
+ return {
2306
+ blake3: entry.blake3,
2307
+ md5: entry.md5,
2308
+ size: entry.size,
2309
+ contentType: getAssetContentType(path)
2310
+ };
2311
+ });
2312
+ cliLog?.info("presign_request_start", {
2313
+ assetCount: assetsToUpload.length,
2314
+ dedupedFrom: needed.length
2315
+ });
2316
+ const presignStartedAt = Date.now();
2317
+ const urls = await client.requestUploadUrls(projectId, assetsToUpload);
2318
+ cliLog?.info("presign_request_end", {
2319
+ urlsReceived: Object.keys(urls).length,
2320
+ durationMs: Date.now() - presignStartedAt
2321
+ });
2322
+ const items = uniqueNeeded.map((path) => {
2323
+ const entry = assetManifest[path];
2324
+ const presigned = urls[entry.blake3];
2325
+ if (!presigned) throw new Error(`Platform did not return an upload URL for ${path} (blake3=${entry.blake3}). This indicates a server-side bug or a stale client.`);
2326
+ const body = assetFiles.get(path);
2327
+ if (!body) throw new Error(`internal: asset ${path} declared in manifest but missing from buffer cache`);
2328
+ return {
2329
+ path,
2330
+ hash: entry.blake3,
2331
+ body,
2332
+ url: presigned.url,
2333
+ headers: presigned.headers
2334
+ };
2335
+ });
2336
+ s.message(`Uploading ${items.length} asset(s) to R2...`);
2337
+ await uploadAssetsToR2(items, cliLog, void 0, (item) => {
2338
+ assetFiles.delete(item.path);
2339
+ });
2340
+ } else cliLog?.info("presign_skipped", { reason: "no_needed_assets" });
2341
+ const payload = {
2342
+ manifest,
2343
+ assetManifest,
2344
+ source: getTokenSource(),
2345
+ commit: gitMetadata?.commit ?? null,
2346
+ repositoryUrl: gitMetadata?.repositoryUrl ?? null,
2347
+ commitUrl: gitMetadata?.commitUrl ?? null,
2348
+ gitRef: gitMetadata?.gitRef ?? null,
2349
+ preflight: preflightUsed ? "true" : null
2350
+ };
2351
+ if (workerFiles && workerFiles.size > 0) {
2352
+ const encoded = {};
2353
+ for (const [relPath, buf] of workerFiles) encoded[relPath] = buf.toString("base64");
2354
+ payload.workerFiles = encoded;
2355
+ }
2356
+ s.message("Finalizing deploy...");
2357
+ cliLog?.info("finalize_start", {
2358
+ workerFileCount: workerFiles?.size ?? 0,
2359
+ assetCount: Object.keys(assetManifest).length
2360
+ });
2361
+ const finalizeStartedAt = Date.now();
2362
+ try {
2363
+ return await streamDeployEvents(projectId, client.finalizeDeploy(projectId, payload), s, cliLog);
2364
+ } finally {
2365
+ cliLog?.info("finalize_end", { durationMs: Date.now() - finalizeStartedAt });
2366
+ }
2367
+ }
1985
2368
  function formatKnownAssetSummary(result, skipped) {
1986
2369
  const uploaded = result.assets - skipped;
1987
2370
  return skipped > 0 ? `${result.assets} static asset(s) (${skipped} unchanged, ${uploaded} uploaded)` : `${result.assets} static asset(s)`;
1988
2371
  }
1989
- async function runStaticDeploy(root, config, client, preset, skipBuild, commit, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, routing, cliLog) {
2372
+ async function runStaticDeploy(root, config, client, preset, skipBuild, gitMetadata, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, routing, cliLog) {
1990
2373
  const typeLabel = preset.appType === "spa" ? "Static SPA" : "Static Site";
1991
2374
  R.info(`${typeLabel} deploy`);
1992
2375
  cliLog?.info("deploy_mode", {
@@ -2025,7 +2408,7 @@ async function runStaticDeploy(root, config, client, preset, skipBuild, commit,
2025
2408
  const onProgress = (msg) => s.message(msg);
2026
2409
  s.start("Checking for changes...");
2027
2410
  cliLog?.info("preflight_start", { dir: preset.outputDir });
2028
- const { assetManifest } = await collectAndHashAssets(preset.outputDir, onProgress);
2411
+ const { assetManifest, assetFiles } = await collectAndHashAssets(preset.outputDir, onProgress);
2029
2412
  const { needed, skipped } = await client.preflight(config.projectId, assetManifest);
2030
2413
  s.stop("Checked for changes");
2031
2414
  cliLog?.info("preflight_end", {
@@ -2038,15 +2421,18 @@ async function runStaticDeploy(root, config, client, preset, skipBuild, commit,
2038
2421
  cliLog?.info("package_start", { mode: "static" });
2039
2422
  const packageStartedAt = Date.now();
2040
2423
  const neededSet = skipped > 0 ? new Set(needed) : null;
2041
- const formData = await packageStaticBuild(preset.outputDir, preset.appType, neededSet, hashedAssetsPrefix ?? "assets", headerRules, redirectRules, fallbackRules, onProgress);
2042
- formData.append("assetManifest", JSON.stringify(assetManifest));
2043
- formData.append("source", getTokenSource());
2044
- if (commit) formData.append("commit", commit);
2424
+ const manifest = buildStaticManifest({
2425
+ appType: preset.appType,
2426
+ hashedAssetsPrefix: hashedAssetsPrefix ?? "assets",
2427
+ headerRules,
2428
+ redirectRules,
2429
+ fallbackRules
2430
+ });
2045
2431
  cliLog?.info("package_end", {
2046
2432
  mode: "static",
2047
2433
  durationMs: Date.now() - packageStartedAt
2048
2434
  });
2049
- const result = await streamDeploy(client, config.projectId, formData, s, cliLog);
2435
+ const result = await streamDirectR2Deploy(client, config.projectId, manifest, assetManifest, assetFiles, needed, gitMetadata, neededSet !== null, void 0, s, cliLog);
2050
2436
  Se([`${typeLabel} — ${formatKnownAssetSummary(result, skipped)}`].join("\n"), result.url);
2051
2437
  ye("Done!");
2052
2438
  }
@@ -2064,7 +2450,7 @@ function warnRedundantForceOn3xx(count) {
2064
2450
  /**
2065
2451
  * Class B/C framework deploy: build with framework CLI, package with preset output paths.
2066
2452
  */
2067
- async function runFrameworkDeploy(root, config, client, detected, fwPreset, voidConfig, skipBuild, commit, cliLog) {
2453
+ async function runFrameworkDeploy(root, config, client, detected, fwPreset, voidConfig, skipBuild, gitMetadata, cliLog) {
2068
2454
  R.info(`${detected.name} framework deploy`);
2069
2455
  cliLog?.info("deploy_mode", {
2070
2456
  mode: "framework",
@@ -2182,7 +2568,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
2182
2568
  const onProgress = (msg) => s.message(msg);
2183
2569
  s.start("Checking for changes...");
2184
2570
  cliLog?.info("preflight_start", { dir: assetsDir });
2185
- const { assetManifest } = await collectAndHashAssets(assetsDir, onProgress, { ignorePatterns: frameworkAssetIgnorePatterns });
2571
+ const { assetManifest, assetFiles } = await collectAndHashAssets(assetsDir, onProgress, { ignorePatterns: frameworkAssetIgnorePatterns });
2186
2572
  const { needed, skipped } = await client.preflight(config.projectId, assetManifest, true);
2187
2573
  s.stop("Checked for changes");
2188
2574
  cliLog?.info("preflight_end", {
@@ -2198,43 +2584,37 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
2198
2584
  });
2199
2585
  const packageStartedAt = Date.now();
2200
2586
  const neededSet = skipped > 0 ? new Set(needed) : null;
2201
- const formData = await packageFrameworkBuild({
2587
+ const packageOptions = {
2588
+ bindingNames: resolveBindingNames(voidConfig.inference?.bindings),
2589
+ workerMain: actualWorkerMain,
2590
+ compatibilityDate: wranglerCompat.compatibilityDate,
2591
+ compatibilityFlags: wranglerCompat.compatibilityFlags
2592
+ };
2593
+ const manifest = buildFrameworkManifest({
2202
2594
  frameworkName: detected.name,
2203
- workerDir: actualWorkerDir,
2204
2595
  workerMain: actualWorkerMain,
2205
- assetsDir,
2206
2596
  bindings,
2207
2597
  migrations: validatedMigrations,
2208
2598
  schedules,
2209
2599
  queues,
2210
2600
  revalidate,
2601
+ revalidateQueryAllowlist: voidConfig.routing?.revalidateQueryAllowlist,
2211
2602
  prerender,
2212
2603
  assetConfig: { not_found_handling: "none" },
2213
2604
  vars: envVars,
2214
- packageOptions: {
2215
- bindingNames: resolveBindingNames(voidConfig.inference?.bindings),
2216
- workerMain: actualWorkerMain,
2217
- compatibilityDate: wranglerCompat.compatibilityDate,
2218
- compatibilityFlags: wranglerCompat.compatibilityFlags
2219
- },
2220
- revalidateQueryAllowlist: voidConfig.routing?.revalidateQueryAllowlist,
2221
- neededAssets: neededSet,
2605
+ packageOptions,
2222
2606
  hashedAssetsPrefix: fwPreset.hashedAssetsPrefix ?? "assets",
2223
2607
  headerRules,
2224
2608
  redirectRules,
2225
2609
  fallbackRules,
2226
- dialect,
2227
- assetIgnorePatterns: frameworkAssetIgnorePatterns,
2228
- onProgress
2610
+ dialect
2229
2611
  });
2230
- formData.append("assetManifest", JSON.stringify(assetManifest));
2231
- formData.append("source", getTokenSource());
2232
- if (commit) formData.append("commit", commit);
2612
+ const workerFiles = collectWorkerFiles(actualWorkerDir);
2233
2613
  cliLog?.info("package_end", {
2234
2614
  mode: "framework",
2235
2615
  durationMs: Date.now() - packageStartedAt
2236
2616
  });
2237
- const result = await streamDeploy(client, config.projectId, formData, s, cliLog);
2617
+ const result = await streamDirectR2Deploy(client, config.projectId, manifest, assetManifest, assetFiles, needed, gitMetadata, neededSet !== null, workerFiles, s, cliLog);
2238
2618
  const summary = [`${result.workers} worker module(s), ${formatKnownAssetSummary(result, skipped)}`];
2239
2619
  if (result.migrations) summary.push(`${result.migrations} migration(s) applied`);
2240
2620
  if (schedules.length > 0) summary.push(`${schedules.length} cron job(s) scheduled`);
@@ -2247,7 +2627,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
2247
2627
  cleanupWrapper(root);
2248
2628
  ye("Done!");
2249
2629
  }
2250
- async function runFullDeploy(root, config, client, fwPreset, skipBuild, commit, detected, cliLog) {
2630
+ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetadata, detected, cliLog) {
2251
2631
  cliLog?.info("deploy_mode", {
2252
2632
  mode: "full",
2253
2633
  framework: detected?.name
@@ -2390,7 +2770,7 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, commit,
2390
2770
  buildCommand: null,
2391
2771
  outputDir: clientDir,
2392
2772
  appType: "static"
2393
- }, true, commit, assetsPrefix, headerRules, redirectRules, fallbackRules, void 0, cliLog);
2773
+ }, true, gitMetadata, assetsPrefix, headerRules, redirectRules, fallbackRules, void 0, cliLog);
2394
2774
  }
2395
2775
  }
2396
2776
  const uniquePrerenderPaths = deployConfig.output === "static" || isFrameworkMode ? [] : isNodeTarget(deployConfig.target) ? await collectPrerenderPathsNode({
@@ -2421,7 +2801,7 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, commit,
2421
2801
  });
2422
2802
  s.start("Checking for changes...");
2423
2803
  cliLog?.info("preflight_start", { dir: clientDir });
2424
- const { assetManifest } = await collectAndHashAssets(clientDir, onProgress);
2804
+ const { assetManifest, assetFiles } = await collectAndHashAssets(clientDir, onProgress);
2425
2805
  const { needed, skipped } = await client.preflight(config.projectId, assetManifest, true);
2426
2806
  s.stop("Checked for changes");
2427
2807
  cliLog?.info("preflight_end", {
@@ -2442,20 +2822,42 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, commit,
2442
2822
  });
2443
2823
  const packageStartedAt = Date.now();
2444
2824
  const neededSet = skipped > 0 ? new Set(needed) : null;
2445
- const formData = await packageBuild(distDir, workerDirName, effectiveBindings, validatedMigrations, schedules, isSsr || hasRoutes || hasMiddleware || hasWebSockets || authEnabled, isFrameworkMode ? detected.name : void 0, revalidate, envVars, queues, uniquePrerenderPaths, assetConfig, {
2825
+ const voidPackageOptions = {
2446
2826
  compatibilityDate: wranglerCompat?.compatibilityDate,
2447
2827
  compatibilityFlags: wranglerCompat?.compatibilityFlags,
2448
2828
  ...sandboxConfig && { sandbox: sandboxConfig },
2449
2829
  revalidateQueryAllowlist: deployConfig.routing?.revalidateQueryAllowlist
2450
- }, neededSet, assetsPrefix, headerRules, redirectRules, fallbackRules, dialect, webSockets, onProgress);
2451
- formData.append("assetManifest", JSON.stringify(assetManifest));
2452
- formData.append("source", getTokenSource());
2453
- if (commit) formData.append("commit", commit);
2830
+ };
2831
+ const fallbackWarning = detectNonSpaFallbackWarning(assetConfig, fallbackRules);
2832
+ if (fallbackWarning) {
2833
+ onProgress?.(fallbackWarning);
2834
+ console.warn(`[void] ${fallbackWarning}`);
2835
+ }
2836
+ const manifest = buildVoidManifest({
2837
+ bindings: effectiveBindings,
2838
+ migrations: validatedMigrations,
2839
+ schedules,
2840
+ ssr: isSsr || hasRoutes || hasMiddleware || hasWebSockets || authEnabled,
2841
+ framework: isFrameworkMode ? detected.name : void 0,
2842
+ revalidate,
2843
+ vars: envVars,
2844
+ queues,
2845
+ prerender: uniquePrerenderPaths,
2846
+ assetConfig,
2847
+ options: voidPackageOptions,
2848
+ hashedAssetsPrefix: assetsPrefix,
2849
+ headerRules,
2850
+ redirectRules,
2851
+ fallbackRules,
2852
+ dialect,
2853
+ webSockets
2854
+ });
2855
+ const workerFiles = collectWorkerFiles(join(distDir, workerDirName));
2454
2856
  cliLog?.info("package_end", {
2455
2857
  mode: "full",
2456
2858
  durationMs: Date.now() - packageStartedAt
2457
2859
  });
2458
- const result = await streamDeploy(client, config.projectId, formData, s, cliLog);
2860
+ const result = await streamDirectR2Deploy(client, config.projectId, manifest, assetManifest, assetFiles, needed, gitMetadata, neededSet !== null, workerFiles, s, cliLog);
2459
2861
  const summary = [`${result.workers} worker module(s), ${formatKnownAssetSummary(result, skipped)}`];
2460
2862
  if (result.migrations) summary.push(`${result.migrations} migration(s) applied`);
2461
2863
  if (schedules.length > 0) summary.push(`${schedules.length} cron job(s) scheduled`);
@@ -2486,6 +2888,8 @@ async function createProjectFromSlug(root, client, slug, cliLog) {
2486
2888
  }
2487
2889
  function getGitCommit(root) {
2488
2890
  try {
2891
+ const githubSha = process.env.GITHUB_SHA?.trim();
2892
+ if (githubSha && /^[0-9a-f]{7,64}$/i.test(githubSha)) return githubSha;
2489
2893
  execSync("git rev-parse --is-inside-work-tree", {
2490
2894
  cwd: root,
2491
2895
  stdio: "ignore"