vinext 0.1.8 → 0.2.1

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 (285) hide show
  1. package/README.md +51 -30
  2. package/dist/build/css-url-assets.d.ts +3 -1
  3. package/dist/build/css-url-assets.js +18 -1
  4. package/dist/build/inject-pregenerated-paths.d.ts +7 -0
  5. package/dist/build/inject-pregenerated-paths.js +21 -0
  6. package/dist/build/layout-classification.js +1 -1
  7. package/dist/build/nitro-route-rules.js +1 -1
  8. package/dist/build/pages-client-assets-module.d.ts +11 -0
  9. package/dist/build/pages-client-assets-module.js +27 -0
  10. package/dist/build/prerender-server-entry.d.ts +1 -0
  11. package/dist/build/prerender-server-entry.js +49 -0
  12. package/dist/build/prerender-server-pool.d.ts +44 -0
  13. package/dist/build/prerender-server-pool.js +194 -0
  14. package/dist/build/prerender.d.ts +2 -1
  15. package/dist/build/prerender.js +98 -27
  16. package/dist/build/report.d.ts +13 -2
  17. package/dist/build/report.js +99 -2
  18. package/dist/build/run-prerender.d.ts +7 -0
  19. package/dist/build/run-prerender.js +12 -2
  20. package/dist/build/standalone.js +2 -0
  21. package/dist/cache/cache-adapters-virtual.js +3 -3
  22. package/dist/check.d.ts +4 -6
  23. package/dist/check.js +48 -11
  24. package/dist/cli-args.d.ts +1 -2
  25. package/dist/cli-args.js +1 -1
  26. package/dist/cli-dev-config.d.ts +12 -0
  27. package/dist/cli-dev-config.js +23 -0
  28. package/dist/cli.js +112 -96
  29. package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
  30. package/dist/{server → client}/dev-error-overlay-store.js +1 -1
  31. package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
  32. package/dist/{server → client}/dev-error-overlay.js +2 -2
  33. package/dist/client/navigation-runtime.d.ts +2 -0
  34. package/dist/client/navigation-runtime.js +3 -1
  35. package/dist/client/vinext-next-data.d.ts +4 -1
  36. package/dist/client/window-next.d.ts +2 -1
  37. package/dist/cloudflare/index.d.ts +2 -2
  38. package/dist/cloudflare/index.js +2 -2
  39. package/dist/config/config-matchers.js +45 -19
  40. package/dist/config/next-config.d.ts +41 -9
  41. package/dist/config/next-config.js +60 -3
  42. package/dist/config/prerender.d.ts +34 -0
  43. package/dist/config/prerender.js +55 -0
  44. package/dist/entries/app-browser-entry.js +5 -1
  45. package/dist/entries/app-rsc-entry.d.ts +5 -3
  46. package/dist/entries/app-rsc-entry.js +44 -10
  47. package/dist/entries/app-rsc-manifest.js +2 -0
  48. package/dist/entries/pages-client-entry.d.ts +3 -0
  49. package/dist/entries/pages-client-entry.js +28 -3
  50. package/dist/entries/pages-server-entry.js +23 -6
  51. package/dist/image/image-adapters-virtual.d.ts +59 -0
  52. package/dist/image/image-adapters-virtual.js +59 -0
  53. package/dist/index.d.ts +33 -0
  54. package/dist/index.js +482 -120
  55. package/dist/init-cloudflare.d.ts +43 -0
  56. package/dist/init-cloudflare.js +839 -0
  57. package/dist/init-platform.d.ts +40 -0
  58. package/dist/init-platform.js +195 -0
  59. package/dist/init.d.ts +16 -38
  60. package/dist/init.js +209 -99
  61. package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
  62. package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
  63. package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
  64. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
  65. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +9 -2
  66. package/dist/packages/cloudflare/src/deploy-config.js +150 -0
  67. package/dist/packages/cloudflare/src/deploy-help.js +55 -0
  68. package/dist/packages/cloudflare/src/deploy.js +272 -0
  69. package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
  70. package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +44 -20
  71. package/dist/plugins/ast-scope.d.ts +16 -0
  72. package/dist/plugins/ast-scope.js +62 -0
  73. package/dist/plugins/ast-utils.d.ts +27 -1
  74. package/dist/plugins/ast-utils.js +32 -1
  75. package/dist/plugins/css-module-imports.d.ts +14 -0
  76. package/dist/plugins/css-module-imports.js +59 -0
  77. package/dist/plugins/extensionless-dynamic-import.js +2 -2
  78. package/dist/plugins/fonts.js +1 -1
  79. package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
  80. package/dist/plugins/ignore-dynamic-requests.js +530 -0
  81. package/dist/plugins/og-assets.js +2 -1
  82. package/dist/plugins/optimize-imports.js +1 -1
  83. package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
  84. package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
  85. package/dist/plugins/sass.d.ts +17 -1
  86. package/dist/plugins/sass.js +74 -1
  87. package/dist/plugins/styled-jsx.d.ts +16 -0
  88. package/dist/plugins/styled-jsx.js +149 -0
  89. package/dist/plugins/typeof-window.d.ts +1 -1
  90. package/dist/plugins/typeof-window.js +28 -56
  91. package/dist/routing/app-route-graph.d.ts +23 -2
  92. package/dist/routing/app-route-graph.js +138 -38
  93. package/dist/routing/file-matcher.d.ts +11 -1
  94. package/dist/routing/file-matcher.js +18 -3
  95. package/dist/routing/pages-router.js +2 -2
  96. package/dist/server/api-handler.js +9 -14
  97. package/dist/server/app-browser-action-result.d.ts +4 -1
  98. package/dist/server/app-browser-action-result.js +8 -1
  99. package/dist/server/app-browser-entry.js +198 -57
  100. package/dist/server/app-browser-interception-context.d.ts +1 -0
  101. package/dist/server/app-browser-interception-context.js +4 -2
  102. package/dist/server/app-browser-navigation-controller.d.ts +2 -0
  103. package/dist/server/app-browser-navigation-controller.js +60 -31
  104. package/dist/server/app-browser-server-action-client.d.ts +1 -1
  105. package/dist/server/app-browser-server-action-client.js +8 -7
  106. package/dist/server/app-browser-state.d.ts +12 -1
  107. package/dist/server/app-browser-state.js +25 -8
  108. package/dist/server/app-browser-visible-commit.d.ts +6 -1
  109. package/dist/server/app-browser-visible-commit.js +42 -24
  110. package/dist/server/app-elements-wire.d.ts +9 -2
  111. package/dist/server/app-elements-wire.js +8 -1
  112. package/dist/server/app-elements.d.ts +2 -2
  113. package/dist/server/app-elements.js +4 -3
  114. package/dist/server/app-fallback-renderer.d.ts +2 -1
  115. package/dist/server/app-fallback-renderer.js +10 -6
  116. package/dist/server/app-hydration-cache-publication.d.ts +11 -0
  117. package/dist/server/app-hydration-cache-publication.js +43 -0
  118. package/dist/server/app-middleware.d.ts +4 -3
  119. package/dist/server/app-middleware.js +8 -3
  120. package/dist/server/app-optimistic-routing.js +11 -4
  121. package/dist/server/app-page-boundary.js +2 -1
  122. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  123. package/dist/server/app-page-cache-finalizer.js +5 -4
  124. package/dist/server/app-page-cache-render.d.ts +1 -0
  125. package/dist/server/app-page-cache-render.js +8 -4
  126. package/dist/server/app-page-cache.d.ts +1 -0
  127. package/dist/server/app-page-cache.js +27 -7
  128. package/dist/server/app-page-dispatch.d.ts +1 -0
  129. package/dist/server/app-page-dispatch.js +26 -8
  130. package/dist/server/app-page-element-builder.d.ts +2 -1
  131. package/dist/server/app-page-element-builder.js +59 -4
  132. package/dist/server/app-page-execution.js +2 -1
  133. package/dist/server/app-page-probe.js +5 -0
  134. package/dist/server/app-page-render-identity.d.ts +2 -0
  135. package/dist/server/app-page-render-identity.js +3 -2
  136. package/dist/server/app-page-render.d.ts +2 -0
  137. package/dist/server/app-page-render.js +85 -16
  138. package/dist/server/app-page-request.d.ts +14 -0
  139. package/dist/server/app-page-request.js +69 -4
  140. package/dist/server/app-page-response.d.ts +7 -0
  141. package/dist/server/app-page-response.js +16 -4
  142. package/dist/server/app-page-route-wiring.d.ts +8 -1
  143. package/dist/server/app-page-route-wiring.js +32 -18
  144. package/dist/server/app-page-segment-state.d.ts +2 -1
  145. package/dist/server/app-page-segment-state.js +3 -1
  146. package/dist/server/app-page-stream.d.ts +7 -1
  147. package/dist/server/app-page-stream.js +3 -1
  148. package/dist/server/app-route-handler-execution.js +5 -1
  149. package/dist/server/app-route-handler-response.js +1 -0
  150. package/dist/server/app-route-handler-runtime.js +1 -1
  151. package/dist/server/app-route-tree-prefetch.d.ts +43 -0
  152. package/dist/server/app-route-tree-prefetch.js +187 -0
  153. package/dist/server/app-router-entry.js +9 -2
  154. package/dist/server/app-rsc-cache-busting.d.ts +2 -1
  155. package/dist/server/app-rsc-cache-busting.js +9 -4
  156. package/dist/server/app-rsc-handler.d.ts +7 -0
  157. package/dist/server/app-rsc-handler.js +148 -39
  158. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  159. package/dist/server/app-rsc-render-mode.js +4 -1
  160. package/dist/server/app-rsc-request-normalization.d.ts +6 -6
  161. package/dist/server/app-rsc-request-normalization.js +10 -8
  162. package/dist/server/app-segment-config.d.ts +11 -0
  163. package/dist/server/app-segment-config.js +62 -6
  164. package/dist/server/app-server-action-execution.d.ts +1 -0
  165. package/dist/server/app-server-action-execution.js +61 -30
  166. package/dist/server/app-ssr-entry.d.ts +3 -0
  167. package/dist/server/app-ssr-entry.js +13 -5
  168. package/dist/server/app-ssr-stream.d.ts +7 -3
  169. package/dist/server/app-ssr-stream.js +10 -6
  170. package/dist/server/app-visited-response-cache.d.ts +6 -0
  171. package/dist/server/app-visited-response-cache.js +3 -1
  172. package/dist/server/cache-control.d.ts +3 -1
  173. package/dist/server/cache-control.js +13 -1
  174. package/dist/server/client-trace-metadata.js +26 -0
  175. package/dist/server/default-global-not-found-module.d.ts +14 -0
  176. package/dist/server/default-global-not-found-module.js +14 -0
  177. package/dist/server/dev-route-files.js +4 -0
  178. package/dist/server/dev-server.d.ts +11 -1
  179. package/dist/server/dev-server.js +94 -35
  180. package/dist/server/dev-stack-sourcemap.d.ts +1 -1
  181. package/dist/server/dev-stack-sourcemap.js +1 -1
  182. package/dist/server/fetch-handler.d.ts +2 -0
  183. package/dist/server/fetch-handler.js +18 -0
  184. package/dist/server/headers.d.ts +7 -15
  185. package/dist/server/headers.js +11 -18
  186. package/dist/server/image-optimization.d.ts +51 -1
  187. package/dist/server/image-optimization.js +52 -2
  188. package/dist/server/implicit-tags.js +1 -1
  189. package/dist/server/isr-cache.d.ts +3 -2
  190. package/dist/server/isr-cache.js +9 -5
  191. package/dist/server/metadata-routes.d.ts +3 -1
  192. package/dist/server/metadata-routes.js +12 -2
  193. package/dist/server/middleware-runtime.js +6 -1
  194. package/dist/server/navigation-planner.d.ts +1 -0
  195. package/dist/server/navigation-planner.js +14 -3
  196. package/dist/server/pages-api-route.js +2 -2
  197. package/dist/server/pages-asset-tags.d.ts +4 -6
  198. package/dist/server/pages-asset-tags.js +12 -12
  199. package/dist/server/pages-client-assets.d.ts +12 -0
  200. package/dist/server/pages-client-assets.js +10 -0
  201. package/dist/server/pages-data-route.d.ts +4 -2
  202. package/dist/server/pages-data-route.js +18 -4
  203. package/dist/server/pages-dev-module-url.d.ts +2 -1
  204. package/dist/server/pages-dev-module-url.js +6 -3
  205. package/dist/server/pages-node-compat.d.ts +19 -1
  206. package/dist/server/pages-node-compat.js +81 -4
  207. package/dist/server/pages-page-data.d.ts +33 -1
  208. package/dist/server/pages-page-data.js +62 -33
  209. package/dist/server/pages-page-handler.d.ts +2 -1
  210. package/dist/server/pages-page-handler.js +38 -19
  211. package/dist/server/pages-page-response.d.ts +8 -0
  212. package/dist/server/pages-page-response.js +7 -3
  213. package/dist/server/pages-request-pipeline.d.ts +9 -5
  214. package/dist/server/pages-request-pipeline.js +55 -7
  215. package/dist/server/pages-router-entry.d.ts +30 -0
  216. package/dist/server/pages-router-entry.js +108 -0
  217. package/dist/server/prerender-manifest.d.ts +3 -1
  218. package/dist/server/prerender-route-params.js +1 -1
  219. package/dist/server/prod-server.d.ts +5 -3
  220. package/dist/server/prod-server.js +68 -39
  221. package/dist/server/request-pipeline.js +2 -1
  222. package/dist/server/seed-cache.js +4 -4
  223. package/dist/shims/app-router-scroll-state.d.ts +1 -0
  224. package/dist/shims/app-router-scroll-state.js +1 -0
  225. package/dist/shims/app-router-scroll.js +2 -1
  226. package/dist/shims/cache-handler.js +8 -1
  227. package/dist/shims/cache.js +22 -15
  228. package/dist/shims/cdn-cache.js +1 -1
  229. package/dist/shims/dynamic-preload-chunks.js +2 -1
  230. package/dist/shims/fetch-cache.d.ts +3 -1
  231. package/dist/shims/fetch-cache.js +77 -52
  232. package/dist/shims/form.d.ts +4 -2
  233. package/dist/shims/form.js +37 -43
  234. package/dist/shims/headers.d.ts +7 -1
  235. package/dist/shims/headers.js +17 -3
  236. package/dist/shims/image.js +75 -14
  237. package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
  238. package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
  239. package/dist/shims/internal/app-route-detection.d.ts +2 -17
  240. package/dist/shims/internal/app-route-detection.js +4 -17
  241. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
  242. package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
  243. package/dist/shims/internal/hybrid-client-route-owner.d.ts +3 -15
  244. package/dist/shims/internal/hybrid-client-route-owner.js +40 -58
  245. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +10 -5
  246. package/dist/shims/internal/pages-data-fetch-dedup.js +84 -14
  247. package/dist/shims/internal/pages-data-target.d.ts +18 -12
  248. package/dist/shims/internal/pages-data-target.js +97 -13
  249. package/dist/shims/internal/pages-router-components.d.ts +7 -0
  250. package/dist/shims/internal/pages-router-components.js +13 -0
  251. package/dist/shims/layout-segment-context.d.ts +4 -1
  252. package/dist/shims/layout-segment-context.js +18 -4
  253. package/dist/shims/link.d.ts +1 -1
  254. package/dist/shims/link.js +224 -58
  255. package/dist/shims/navigation.d.ts +19 -3
  256. package/dist/shims/navigation.js +245 -47
  257. package/dist/shims/request-context.js +18 -0
  258. package/dist/shims/root-params.d.ts +15 -1
  259. package/dist/shims/root-params.js +21 -1
  260. package/dist/shims/router.d.ts +5 -7
  261. package/dist/shims/router.js +387 -102
  262. package/dist/shims/server.js +3 -2
  263. package/dist/shims/slot.js +3 -6
  264. package/dist/shims/unified-request-context.js +1 -0
  265. package/dist/typegen.js +1 -1
  266. package/dist/utils/client-runtime-metadata.d.ts +2 -18
  267. package/dist/utils/client-runtime-metadata.js +31 -22
  268. package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
  269. package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
  270. package/dist/utils/domain-locale.d.ts +6 -3
  271. package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
  272. package/dist/{server → utils}/middleware-request-headers.js +3 -3
  273. package/dist/utils/project.d.ts +48 -2
  274. package/dist/utils/project.js +225 -4
  275. package/dist/utils/protocol-headers.d.ts +23 -0
  276. package/dist/utils/protocol-headers.js +23 -0
  277. package/dist/utils/react-version.d.ts +4 -0
  278. package/dist/utils/react-version.js +44 -0
  279. package/dist/utils/vite-version.d.ts +2 -0
  280. package/dist/utils/vite-version.js +11 -1
  281. package/package.json +47 -2
  282. package/dist/deploy.d.ts +0 -197
  283. package/dist/deploy.js +0 -1040
  284. package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
  285. /package/dist/{cloudflare → packages/cloudflare}/src/utils/cache-control-metadata.js +0 -0
package/dist/deploy.js DELETED
@@ -1,1040 +0,0 @@
1
- import { detectPackageManager as detectPackageManager$1, ensureESModule as ensureESModule$1, findDir, findInNodeModules, hasAppDir, renameCJSConfigs as renameCJSConfigs$1 } from "./utils/project.js";
2
- import { parsePositiveIntegerArg } from "./cli-args.js";
3
- import { escapeRegExp } from "./utils/regex.js";
4
- import { normalizePathSeparators } from "./utils/path.js";
5
- import { loadNextConfig, resolveNextConfig } from "./config/next-config.js";
6
- import { getReactUpgradeDeps } from "./init.js";
7
- import { runTPR } from "./cloudflare/tpr.js";
8
- import { buildPregeneratedConcretePathTable, readPrerenderManifest } from "./server/prerender-manifest.js";
9
- import { runPrerender } from "./build/run-prerender.js";
10
- import { loadDotenv } from "./config/dotenv.js";
11
- import { createRequire } from "node:module";
12
- import fs from "node:fs";
13
- import path from "node:path";
14
- import { pathToFileURL } from "node:url";
15
- import { parseArgs } from "node:util";
16
- import { execFileSync } from "node:child_process";
17
- //#region src/deploy.ts
18
- /**
19
- * vinext deploy — one-command Cloudflare Workers deployment.
20
- *
21
- * Takes any Next.js app and deploys it to Cloudflare Workers:
22
- *
23
- * 1. Detects App Router vs Pages Router
24
- * 2. Auto-generates missing config files (wrangler.jsonc, worker/index.ts, vite.config.ts)
25
- * 3. Ensures dependencies are installed (@cloudflare/vite-plugin, wrangler, @vitejs/plugin-react, App Router deps)
26
- * 4. Runs the Vite build
27
- * 5. Deploys to Cloudflare Workers via wrangler
28
- *
29
- * Design: Everything is auto-generated into a `.vinext/` directory (not the
30
- * project root) to avoid cluttering the user's project. If the user already
31
- * has these files, we use theirs.
32
- */
33
- /** Deploy command flag definitions for util.parseArgs. */
34
- const deployArgOptions = {
35
- help: {
36
- type: "boolean",
37
- short: "h",
38
- default: false
39
- },
40
- preview: {
41
- type: "boolean",
42
- default: false
43
- },
44
- env: { type: "string" },
45
- name: { type: "string" },
46
- "skip-build": {
47
- type: "boolean",
48
- default: false
49
- },
50
- "dry-run": {
51
- type: "boolean",
52
- default: false
53
- },
54
- "prerender-all": {
55
- type: "boolean",
56
- default: false
57
- },
58
- "prerender-concurrency": { type: "string" },
59
- "experimental-tpr": {
60
- type: "boolean",
61
- default: false
62
- },
63
- "tpr-coverage": { type: "string" },
64
- "tpr-limit": { type: "string" },
65
- "tpr-window": { type: "string" }
66
- };
67
- function parseDeployArgs(args) {
68
- const { values } = parseArgs({
69
- args,
70
- options: deployArgOptions,
71
- strict: true
72
- });
73
- function parseIntArg(name, raw) {
74
- if (!raw) return void 0;
75
- const n = parseInt(raw, 10);
76
- if (isNaN(n)) {
77
- console.error(` --${name} must be a number (got: ${raw})`);
78
- process.exit(1);
79
- }
80
- return n;
81
- }
82
- return {
83
- help: values.help,
84
- preview: values.preview,
85
- env: values.env?.trim() || void 0,
86
- name: values.name?.trim() || void 0,
87
- skipBuild: values["skip-build"],
88
- dryRun: values["dry-run"],
89
- prerenderAll: values["prerender-all"],
90
- prerenderConcurrency: values["prerender-concurrency"] === void 0 ? void 0 : parsePositiveIntegerArg(values["prerender-concurrency"], "--prerender-concurrency"),
91
- experimentalTPR: values["experimental-tpr"],
92
- tprCoverage: parseIntArg("tpr-coverage", values["tpr-coverage"]),
93
- tprLimit: parseIntArg("tpr-limit", values["tpr-limit"]),
94
- tprWindow: parseIntArg("tpr-window", values["tpr-window"])
95
- };
96
- }
97
- /** Check whether a wrangler config file exists in the given directory. */
98
- function hasWranglerConfig(root) {
99
- return fs.existsSync(path.join(root, "wrangler.jsonc")) || fs.existsSync(path.join(root, "wrangler.json")) || fs.existsSync(path.join(root, "wrangler.toml"));
100
- }
101
- /**
102
- * Build the error message thrown when cloudflare() is missing from the Vite config.
103
- * Shared between the build-time guard (index.ts configResolved) and the
104
- * deploy-time guard (deploy.ts deploy()).
105
- */
106
- function formatMissingCloudflarePluginError(options) {
107
- const cfArg = options.isAppRouter ? "{\n viteEnvironment: { name: \"rsc\", childEnvironments: [\"ssr\"] },\n }" : "";
108
- const configRef = options.configFile ? options.configFile : "your Vite config";
109
- return `[vinext] Missing @cloudflare/vite-plugin in ${configRef}.\n\n Cloudflare Workers builds require the cloudflare() plugin.\n Add it to ${configRef}:\n\n import { cloudflare } from "@cloudflare/vite-plugin";\n\n export default defineConfig({\n plugins: [\n vinext(),\n cloudflare(${cfArg}),\n ],\n });\n\n Or delete ${configRef} and re-run \`vinext deploy\` to auto-generate it.`;
110
- }
111
- /**
112
- * Detect the project structure (router, config, worker entry, package name).
113
- *
114
- * `root` must be forward-slash — it is joined with `path.posix.*`, passed to
115
- * `findDir` / `hasAppDir`, and used as the base of `path.posix.basename`.
116
- * Callers normalize it at the deploy entry.
117
- */
118
- function detectProject(root) {
119
- const hasApp = hasAppDir(root);
120
- const hasPages = findDir(root, "pages", "src/pages") !== null;
121
- const isAppRouter = hasApp;
122
- const isPagesRouter = !hasApp && hasPages;
123
- const hasViteConfig = fs.existsSync(path.posix.join(root, "vite.config.ts")) || fs.existsSync(path.posix.join(root, "vite.config.js")) || fs.existsSync(path.posix.join(root, "vite.config.mjs"));
124
- const wranglerConfigExists = hasWranglerConfig(root);
125
- const hasWorkerEntry = fs.existsSync(path.posix.join(root, "worker", "index.ts")) || fs.existsSync(path.posix.join(root, "worker", "index.js"));
126
- const hasCloudflarePlugin = findInNodeModules(root, "@cloudflare/vite-plugin") !== null;
127
- const hasRscPlugin = findInNodeModules(root, "@vitejs/plugin-rsc") !== null;
128
- const hasWrangler = findInNodeModules(root, ".bin/wrangler") !== null;
129
- const pkgPath = path.posix.join(root, "package.json");
130
- let pkg = null;
131
- if (fs.existsSync(pkgPath)) try {
132
- pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
133
- } catch {}
134
- let projectName = path.posix.basename(root);
135
- if (pkg?.name && typeof pkg.name === "string") projectName = pkg.name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
136
- const hasTypeModule = pkg?.type === "module";
137
- let hasISR = false;
138
- let hasMDX = detectMDXFromConfig(root);
139
- if (isAppRouter) {
140
- const appDir = resolveProjectDir(root, "app");
141
- if (appDir) {
142
- const found = scanTreeForDetection(appDir, {
143
- isr: true,
144
- mdx: !hasMDX
145
- });
146
- hasISR = found.isr;
147
- hasMDX = hasMDX || found.mdx;
148
- }
149
- }
150
- if (hasPages && !hasMDX) {
151
- const pagesDir = resolveProjectDir(root, "pages");
152
- if (pagesDir) hasMDX = scanTreeForDetection(pagesDir, {
153
- isr: false,
154
- mdx: true
155
- }).mdx;
156
- }
157
- const allDeps = {
158
- ...pkg?.dependencies,
159
- ...pkg?.devDependencies
160
- };
161
- const hasCodeHike = "codehike" in allDeps;
162
- const nativeModulesToStub = detectNativeModules(allDeps);
163
- return {
164
- root,
165
- isAppRouter,
166
- isPagesRouter,
167
- hasViteConfig,
168
- hasWranglerConfig: wranglerConfigExists,
169
- hasWorkerEntry,
170
- hasCloudflarePlugin,
171
- hasRscPlugin,
172
- hasWrangler,
173
- projectName,
174
- hasISR,
175
- hasTypeModule,
176
- hasMDX,
177
- hasCodeHike,
178
- nativeModulesToStub
179
- };
180
- }
181
- /** Matches `export const revalidate = …` (ISR opt-in) in App Router source. */
182
- const ISR_REVALIDATE_PATTERN = /export\s+const\s+revalidate\s*=/;
183
- /** Source extensions whose contents are scanned for the ISR pattern. */
184
- const ISR_SCANNABLE_EXTENSION = /\.(ts|tsx|js|jsx)$/;
185
- /**
186
- * Resolve a project subdirectory (`app`/`pages`), preferring the root-level
187
- * location and falling back to the `src/` variant. Returns null when neither
188
- * exists.
189
- */
190
- function resolveProjectDir(root, name) {
191
- const rootDir = path.join(root, name);
192
- if (fs.existsSync(rootDir)) return rootDir;
193
- const srcDir = path.join(root, "src", name);
194
- if (fs.existsSync(srcDir)) return srcDir;
195
- return null;
196
- }
197
- /**
198
- * Recursively walk `dir` once, evaluating the requested detection predicates
199
- * per entry. Each flag short-circuits independently: an `.mdx` file sets `mdx`;
200
- * a scannable source file containing `export const revalidate` sets `isr`. The
201
- * walk stops as soon as every requested flag is satisfied, so callers that only
202
- * want one signal don't pay for the other.
203
- *
204
- * Replaces the previous pair of single-purpose recursive walkers
205
- * (`scanDirForPattern` + `scanDirForExtension`) that traversed the same tree
206
- * twice. Detection semantics are unchanged: same dirs skipped (dotfiles,
207
- * node_modules), same extension and content tests.
208
- */
209
- function scanTreeForDetection(dir, want) {
210
- const found = {
211
- isr: false,
212
- mdx: false
213
- };
214
- const walk = (current) => {
215
- let entries;
216
- try {
217
- entries = fs.readdirSync(current, { withFileTypes: true });
218
- } catch {
219
- return;
220
- }
221
- for (const entry of entries) {
222
- if ((!want.isr || found.isr) && (!want.mdx || found.mdx)) return;
223
- const fullPath = path.join(current, entry.name);
224
- if (entry.isDirectory()) {
225
- if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
226
- walk(fullPath);
227
- } else if (entry.isFile()) {
228
- if (want.mdx && !found.mdx && entry.name.endsWith(".mdx")) found.mdx = true;
229
- if (want.isr && !found.isr && ISR_SCANNABLE_EXTENSION.test(entry.name)) try {
230
- if (ISR_REVALIDATE_PATTERN.test(fs.readFileSync(fullPath, "utf-8"))) found.isr = true;
231
- } catch {}
232
- }
233
- }
234
- };
235
- walk(dir);
236
- return found;
237
- }
238
- /**
239
- * Detect MDX usage declared in next.config (`pageExtensions` including "mdx" or
240
- * an `@next/mdx` import). Filesystem `.mdx` detection is handled separately by
241
- * the shared app/pages tree walk in `detectProject`.
242
- */
243
- function detectMDXFromConfig(root) {
244
- for (const f of [
245
- "next.config.ts",
246
- "next.config.mts",
247
- "next.config.mjs",
248
- "next.config.js",
249
- "next.config.cjs"
250
- ]) {
251
- const p = path.join(root, f);
252
- if (fs.existsSync(p)) try {
253
- const content = fs.readFileSync(p, "utf-8");
254
- if (/pageExtensions.*mdx/i.test(content) || /@next\/mdx/.test(content)) return true;
255
- } catch {}
256
- }
257
- return false;
258
- }
259
- /** Known native Node modules that can't run in Workers */
260
- const NATIVE_MODULES_TO_STUB = [
261
- "@resvg/resvg-js",
262
- "satori",
263
- "lightningcss",
264
- "@napi-rs/canvas",
265
- "sharp"
266
- ];
267
- /**
268
- * Detect native Node modules in the project's merged dependency map that need
269
- * stubbing for Workers. Accepts the already-built `allDeps` (dependencies +
270
- * devDependencies) so package.json is not re-read or re-parsed.
271
- */
272
- function detectNativeModules(allDeps) {
273
- return NATIVE_MODULES_TO_STUB.filter((mod) => mod in allDeps);
274
- }
275
- /** @see {@link _ensureESModule} */
276
- const ensureESModule = ensureESModule$1;
277
- /** @see {@link _renameCJSConfigs} */
278
- const renameCJSConfigs = renameCJSConfigs$1;
279
- /** Generate wrangler.jsonc content */
280
- function generateWranglerConfig(info) {
281
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
282
- const config = {
283
- $schema: "node_modules/wrangler/config-schema.json",
284
- name: info.projectName,
285
- compatibility_date: today,
286
- compatibility_flags: ["nodejs_compat"],
287
- main: "./worker/index.ts",
288
- assets: {
289
- directory: "dist/client",
290
- not_found_handling: "none",
291
- binding: "ASSETS"
292
- },
293
- images: { binding: "IMAGES" }
294
- };
295
- if (info.hasISR) config.kv_namespaces = [{
296
- binding: "VINEXT_KV_CACHE",
297
- id: "<your-kv-namespace-id>"
298
- }];
299
- return JSON.stringify(config, null, 2) + "\n";
300
- }
301
- /** Generate worker/index.ts for App Router */
302
- function generateAppRouterWorkerEntry() {
303
- return `/**
304
- * Cloudflare Worker entry point — auto-generated by vinext deploy.
305
- * Edit freely or delete to regenerate on next deploy.
306
- *
307
- * Cache backends (data + page ISR) are configured declaratively in
308
- * vite.config via the vinext({ cache }) option.
309
- *
310
- * For apps without image optimization, you can use vinext/server/app-router-entry
311
- * directly in wrangler.jsonc: "main": "vinext/server/app-router-entry"
312
- */
313
- import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath } from "vinext/server/image-optimization";
314
- import type { ImageConfig } from "vinext/server/image-optimization";
315
- import handler from "vinext/server/app-router-entry";
316
-
317
- const imageConfig: ImageConfig = {
318
- deviceSizes: JSON.parse(
319
- process.env.__VINEXT_IMAGE_DEVICE_SIZES ?? JSON.stringify(DEFAULT_DEVICE_SIZES),
320
- ),
321
- imageSizes: JSON.parse(
322
- process.env.__VINEXT_IMAGE_SIZES ?? JSON.stringify(DEFAULT_IMAGE_SIZES),
323
- ),
324
- qualities: JSON.parse(process.env.__VINEXT_IMAGE_QUALITIES ?? "null") ?? undefined,
325
- dangerouslyAllowSVG: process.env.__VINEXT_IMAGE_DANGEROUSLY_ALLOW_SVG === "true",
326
- };
327
-
328
- interface Env {
329
- ASSETS: Fetcher;
330
- IMAGES: {
331
- input(stream: ReadableStream): {
332
- transform(options: Record<string, unknown>): {
333
- output(options: { format: string; quality: number }): Promise<{ response(): Response }>;
334
- };
335
- };
336
- };
337
- }
338
-
339
- interface ExecutionContext {
340
- waitUntil(promise: Promise<unknown>): void;
341
- passThroughOnException(): void;
342
- }
343
-
344
- // Image security config. SVG sources with .svg extension auto-skip the
345
- // optimization endpoint on the client side (served directly, no proxy).
346
- // To route SVGs through the optimizer (with security headers), set
347
- // dangerouslyAllowSVG: true in next.config.js and uncomment below:
348
- // const imageConfig: ImageConfig = { dangerouslyAllowSVG: true };
349
-
350
- export default {
351
- async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
352
- const url = new URL(request.url);
353
-
354
- // Image optimization via Cloudflare Images binding.
355
- // The parseImageParams validation inside handleImageOptimization
356
- // normalizes backslashes and validates the origin hasn't changed.
357
- if (isImageOptimizationPath(url.pathname)) {
358
- const allowedWidths = [
359
- ...(imageConfig.deviceSizes ?? DEFAULT_DEVICE_SIZES),
360
- ...(imageConfig.imageSizes ?? DEFAULT_IMAGE_SIZES),
361
- ];
362
- return handleImageOptimization(request, {
363
- fetchAsset: (path) => env.ASSETS.fetch(new Request(new URL(path, request.url))),
364
- transformImage: async (body, { width, format, quality }) => {
365
- const result = await env.IMAGES.input(body).transform(width > 0 ? { width } : {}).output({ format, quality });
366
- return result.response();
367
- },
368
- }, allowedWidths, imageConfig);
369
- }
370
-
371
- // Delegate everything else to vinext, forwarding ctx so that
372
- // ctx.waitUntil() is available to background cache writes and
373
- // other deferred work via getRequestExecutionContext().
374
- return handler.fetch(request, env, ctx);
375
- },
376
- };
377
- `;
378
- }
379
- /** Generate worker/index.ts for Pages Router */
380
- function generatePagesRouterWorkerEntry() {
381
- return `/**
382
- * Cloudflare Worker entry point -- auto-generated by vinext deploy.
383
- * Edit freely or delete to regenerate on next deploy.
384
- */
385
- import { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath } from "vinext/server/pages-request-pipeline";
386
- import type { PagesPipelineDeps } from "vinext/server/pages-request-pipeline";
387
- import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath } from "vinext/server/image-optimization";
388
- import type { ImageConfig } from "vinext/server/image-optimization";
389
- import { cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, isOpenRedirectShaped } from "vinext/server/request-pipeline";
390
- import { notFoundStaticAssetResponse } from "vinext/server/http-error-responses";
391
- import { finalizeMissingStaticAssetResponse } from "vinext/server/worker-utils";
392
- import { assetPrefixPathname, isNextStaticPath } from "vinext/utils/asset-prefix";
393
- import { hasBasePath, stripBasePath } from "vinext/utils/base-path";
394
-
395
- // @ts-expect-error -- virtual module resolved by vinext at build time
396
- import { renderPage, handleApiRoute, runMiddleware, normalizeDataRequest, vinextConfig, matchPageRoute } from "virtual:vinext-server-entry";
397
- // @ts-expect-error -- virtual module resolved by vinext at build time
398
- import { registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
399
-
400
- interface Env {
401
- ASSETS: Fetcher;
402
- IMAGES: {
403
- input(stream: ReadableStream): {
404
- transform(options: Record<string, unknown>): {
405
- output(options: { format: string; quality: number }): Promise<{ response(): Response }>;
406
- };
407
- };
408
- };
409
- }
410
-
411
- interface ExecutionContext {
412
- waitUntil(promise: Promise<unknown>): void;
413
- passThroughOnException(): void;
414
- }
415
-
416
- // Extract config values (embedded at build time in the server entry)
417
- const basePath: string = vinextConfig?.basePath ?? "";
418
- const assetPathPrefix: string = assetPrefixPathname(vinextConfig?.assetPrefix ?? "");
419
- const trailingSlash: boolean = vinextConfig?.trailingSlash ?? false;
420
- const i18nConfig = vinextConfig?.i18n ?? null;
421
- const configRedirects = vinextConfig?.redirects ?? [];
422
- const configRewrites = vinextConfig?.rewrites ?? { beforeFiles: [], afterFiles: [], fallback: [] };
423
- const configHeaders = vinextConfig?.headers ?? [];
424
- const imageConfig: ImageConfig | undefined = vinextConfig?.images ? {
425
- qualities: vinextConfig.images.qualities,
426
- dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
427
- dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
428
- contentDispositionType: vinextConfig.images.contentDispositionType,
429
- contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy,
430
- } : undefined;
431
-
432
- export default {
433
- async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
434
- // Pass the Worker \`env\` so binding-backed adapters (e.g. KV) resolve.
435
- registerConfiguredCacheAdapters(env);
436
- try {
437
- const url = new URL(request.url);
438
- let pathname = url.pathname;
439
-
440
- // Block protocol-relative URL open redirects in all shapes:
441
- // literal //evil.com, /\\\\evil.com
442
- // encoded /%5Cevil.com, /%2F/evil.com
443
- // Browsers normalize backslash to forward slash, and they percent-decode
444
- // Location headers, so an encoded backslash in a downstream 308 redirect
445
- // would also navigate to the attacker's origin.
446
- if (isOpenRedirectShaped(pathname)) {
447
- return new Response("This page could not be found", { status: 404 });
448
- }
449
-
450
- // Valid assets are served by Cloudflare's ASSETS binding before the
451
- // worker runs. Missing asset-shaped requests still need to reach
452
- // middleware so it can rewrite or respond; a final 404 is converted
453
- // back to Next.js's canonical plain-text static-file response below.
454
- const missingBuildAsset = isNextStaticPath(pathname, basePath, assetPathPrefix);
455
-
456
- // Strip internal headers from inbound requests so they cannot be
457
- // forged to influence routing or impersonate internal state.
458
- // Request.headers is immutable in Workers, so build a clean copy.
459
- {
460
- const filteredHeaders = filterInternalHeaders(request.headers);
461
- request = cloneRequestWithHeaders(request, filteredHeaders);
462
- }
463
-
464
- // ── 1. Strip basePath ─────────────────────────────────────────
465
- // Track basePath presence on the original request so the matcher
466
- // gating below can distinguish requests inside basePath (default
467
- // rules apply) from requests outside it (only opt-out rules apply).
468
- const hadBasePath = !basePath || hasBasePath(pathname, basePath);
469
- {
470
- const stripped = stripBasePath(pathname, basePath);
471
- if (stripped !== pathname) {
472
- const strippedUrl = new URL(request.url);
473
- strippedUrl.pathname = stripped;
474
- request = cloneRequestWithUrl(request, strippedUrl.toString());
475
- pathname = stripped;
476
- }
477
- }
478
-
479
- const dataNorm = normalizeDataRequest(request);
480
- if (dataNorm.notFoundResponse) return dataNorm.notFoundResponse;
481
- const isDataReq = dataNorm.isDataReq;
482
- if (isDataReq) {
483
- request = dataNorm.request;
484
- pathname = dataNorm.normalizedPathname;
485
- }
486
-
487
- // ── Image optimization via Cloudflare Images binding ──────────
488
- // Checked after basePath stripping so /<basePath>/_next/image works.
489
- if (isImageOptimizationPath(pathname)) {
490
- const allowedWidths = [
491
- ...(vinextConfig?.images?.deviceSizes ?? DEFAULT_DEVICE_SIZES),
492
- ...(vinextConfig?.images?.imageSizes ?? DEFAULT_IMAGE_SIZES),
493
- ];
494
- return handleImageOptimization(request, {
495
- fetchAsset: (path) => env.ASSETS.fetch(new Request(new URL(path, request.url))),
496
- transformImage: async (body, { width, format, quality }) => {
497
- const result = await env.IMAGES.input(body).transform(width > 0 ? { width } : {}).output({ format, quality });
498
- return result.response();
499
- },
500
- }, allowedWidths, imageConfig);
501
- }
502
-
503
- // Delegate the canonical 9-step Next.js pipeline to the shared owner.
504
- // The worker adapter is responsible for: open-redirect guard, _next/static
505
- // 404 short-circuit, header filtering, basePath stripping, and image
506
- // optimization. runPagesRequest receives a clean, basePath-stripped request.
507
- const deps: PagesPipelineDeps = {
508
- basePath,
509
- trailingSlash,
510
- i18nConfig,
511
- configRedirects,
512
- configRewrites,
513
- configHeaders,
514
- hadBasePath,
515
- isDataReq,
516
- isDataRequest: isDataReq,
517
- ctx,
518
- matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
519
- // Pass the original (pre-basePath-stripping) URL to middleware so that
520
- // request.nextUrl.basePath reflects whether the URL actually had the
521
- // basePath prefix. Matches Next.js behavior and the prod-server.ts
522
- // equivalent (shared via wrapMiddlewareWithBasePath).
523
- runMiddleware:
524
- typeof runMiddleware === "function"
525
- ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath)
526
- : null,
527
- renderPage: typeof renderPage === "function"
528
- ? (req, resolvedUrl, options, stagedHeaders) =>
529
- renderPage(req, resolvedUrl, null, ctx, stagedHeaders, options)
530
- : null,
531
- handleApi: typeof handleApiRoute === "function"
532
- ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx)
533
- : null,
534
- serveFilesystemRoute: async (requestPathname, _stagedHeaders, phase) => {
535
- return fetchWorkerFilesystemRoute(
536
- request,
537
- requestPathname,
538
- phase,
539
- (assetRequest) => env.ASSETS.fetch(assetRequest),
540
- );
541
- },
542
- };
543
-
544
- const result = await runPagesRequest(request, deps);
545
- if (result.type === "response") {
546
- return finalizeMissingStaticAssetResponse(result.response, missingBuildAsset);
547
- }
548
- // Should not reach here for prod/worker (all callbacks supplied).
549
- return missingBuildAsset
550
- ? notFoundStaticAssetResponse()
551
- : new Response("This page could not be found", { status: 404 });
552
-
553
- } catch (error) {
554
- console.error("[vinext] Worker error:", error);
555
- return new Response("Internal Server Error", { status: 500 });
556
- }
557
- },
558
- };
559
-
560
- `;
561
- }
562
- /** Generate vite.config.ts for App Router */
563
- function generateAppRouterViteConfig(info) {
564
- const imports = [
565
- `import { defineConfig } from "vite";`,
566
- `import vinext from "vinext";`,
567
- `import { cloudflare } from "@cloudflare/vite-plugin";`
568
- ];
569
- if (info?.nativeModulesToStub && info.nativeModulesToStub.length > 0) imports.push(`import path from "node:path";`);
570
- const plugins = [];
571
- if (info?.hasMDX) plugins.push(` // vinext auto-injects @mdx-js/rollup with plugins from next.config`);
572
- plugins.push(` vinext(),`);
573
- plugins.push(` cloudflare({
574
- viteEnvironment: {
575
- name: "rsc",
576
- childEnvironments: ["ssr"],
577
- },
578
- }),`);
579
- let resolveBlock = "";
580
- const aliases = [];
581
- if (info?.nativeModulesToStub && info.nativeModulesToStub.length > 0) for (const mod of info.nativeModulesToStub) aliases.push(` "${mod}": path.resolve(__dirname, "empty-stub.js"),`);
582
- if (aliases.length > 0) resolveBlock = `\n resolve: {\n alias: {\n${aliases.join("\n")}\n },\n },`;
583
- return `${imports.join("\n")}
584
-
585
- export default defineConfig({
586
- plugins: [
587
- ${plugins.join("\n")}
588
- ],${resolveBlock}
589
- });
590
- `;
591
- }
592
- /** Generate vite.config.ts for Pages Router */
593
- function generatePagesRouterViteConfig(info) {
594
- const imports = [
595
- `import { defineConfig } from "vite";`,
596
- `import vinext from "vinext";`,
597
- `import { cloudflare } from "@cloudflare/vite-plugin";`
598
- ];
599
- if (info?.nativeModulesToStub && info.nativeModulesToStub.length > 0) imports.push(`import path from "node:path";`);
600
- let resolveBlock = "";
601
- const aliases = [];
602
- if (info?.nativeModulesToStub && info.nativeModulesToStub.length > 0) for (const mod of info.nativeModulesToStub) aliases.push(` "${mod}": path.resolve(__dirname, "empty-stub.js"),`);
603
- if (aliases.length > 0) resolveBlock = `\n resolve: {\n alias: {\n${aliases.join("\n")}\n },\n },`;
604
- return `${imports.join("\n")}
605
-
606
- export default defineConfig({
607
- plugins: [
608
- vinext(),
609
- cloudflare(),
610
- ],${resolveBlock}
611
- });
612
- `;
613
- }
614
- /**
615
- * Check if a package is resolvable from a given root directory using
616
- * Node's module resolution (createRequire). Handles hoisting, pnpm
617
- * symlinks, monorepos, and Yarn PnP correctly.
618
- */
619
- function isPackageResolvable(root, packageName) {
620
- try {
621
- createRequire(path.join(root, "package.json")).resolve(packageName);
622
- return true;
623
- } catch {
624
- return false;
625
- }
626
- }
627
- function getMissingDeps(info, _isResolvable = isPackageResolvable) {
628
- const missing = [];
629
- if (!info.hasCloudflarePlugin) missing.push({
630
- name: "@cloudflare/vite-plugin",
631
- version: "latest"
632
- });
633
- if (!info.hasWrangler) missing.push({
634
- name: "wrangler",
635
- version: "latest"
636
- });
637
- if (!_isResolvable(info.root, "@vitejs/plugin-react")) missing.push({
638
- name: "@vitejs/plugin-react",
639
- version: "latest"
640
- });
641
- if (info.isAppRouter && !info.hasRscPlugin) missing.push({
642
- name: "@vitejs/plugin-rsc",
643
- version: "latest"
644
- });
645
- if (info.isAppRouter) {
646
- if (!_isResolvable(info.root, "react-server-dom-webpack")) missing.push({
647
- name: "react-server-dom-webpack",
648
- version: "latest"
649
- });
650
- }
651
- if (info.hasMDX) {
652
- if (!_isResolvable(info.root, "@mdx-js/rollup")) missing.push({
653
- name: "@mdx-js/rollup",
654
- version: "latest"
655
- });
656
- }
657
- return missing;
658
- }
659
- function installDeps(root, deps) {
660
- if (deps.length === 0) return;
661
- const depSpecs = deps.map((d) => `${d.name}@${d.version}`);
662
- const [pm, ...pmArgs] = detectPackageManager(root).split(" ");
663
- console.log(` Installing: ${deps.map((d) => d.name).join(", ")}`);
664
- execFileSync(pm, [...pmArgs, ...depSpecs], {
665
- cwd: root,
666
- stdio: "inherit",
667
- shell: process.platform === "win32"
668
- });
669
- }
670
- const detectPackageManager = detectPackageManager$1;
671
- /**
672
- * Check whether an existing vite.config file already imports and uses the
673
- * Cloudflare Vite plugin. This is a heuristic text scan — it doesn't execute
674
- * the config — so it may produce false negatives for unusual configurations.
675
- *
676
- * Returns true if `@cloudflare/vite-plugin` appears to be configured, false
677
- * if it is missing (meaning the build will fail with "could not resolve
678
- * virtual:vinext-rsc-entry").
679
- */
680
- function viteConfigHasCloudflarePlugin(root) {
681
- const candidates = [
682
- path.join(root, "vite.config.ts"),
683
- path.join(root, "vite.config.js"),
684
- path.join(root, "vite.config.mjs")
685
- ];
686
- for (const candidate of candidates) if (fs.existsSync(candidate)) try {
687
- return fs.readFileSync(candidate, "utf-8").includes("@cloudflare/vite-plugin");
688
- } catch {
689
- return true;
690
- }
691
- return false;
692
- }
693
- /**
694
- * Extract the object-literal text of the `cache:` key (the `{ ... }` passed as
695
- * `vinext({ cache })`) from a Vite config source, via brace matching. Returns
696
- * null if there is no `cache:` object literal.
697
- */
698
- function extractCacheBlock(content) {
699
- const m = /\bcache\s*:\s*\{/.exec(content);
700
- if (!m) return null;
701
- const open = m.index + m[0].length - 1;
702
- let depth = 0;
703
- for (let i = open; i < content.length; i++) {
704
- const ch = content[i];
705
- if (ch === "{") depth++;
706
- else if (ch === "}" && --depth === 0) return content.slice(open, i + 1);
707
- }
708
- return null;
709
- }
710
- /**
711
- * Whether a `cdn` / `data` field inside the cache object is assigned a real
712
- * value (not absent, `undefined`, or `null`). Reads the value up to the next
713
- * comma / closing brace / newline, which is enough to tell an assignment like
714
- * `data: kvDataAdapter()` from `data: undefined`.
715
- */
716
- function cacheFieldAssigned(cacheBlock, field) {
717
- const m = new RegExp(`\\b${field}\\s*:\\s*([^,}\\n]+)`).exec(cacheBlock);
718
- if (!m) return false;
719
- const value = m[1].trim();
720
- return value.length > 0 && value !== "undefined" && value !== "null";
721
- }
722
- /**
723
- * Detect whether the Vite config assigns a CDN or data cache adapter — i.e. the
724
- * `cdn` or `data` field of the `vinext({ cache })` option is given a value.
725
- * This is a source-level check on those exact object fields, not a fuzzy scan
726
- * for adapter names. Mirrors {@link viteConfigHasCloudflarePlugin}'s leniency:
727
- * an unreadable or absent config is treated as configured so a deploy is never
728
- * blocked on a false negative.
729
- */
730
- function viteConfigHasCacheAdapter(root) {
731
- const candidates = [
732
- path.join(root, "vite.config.ts"),
733
- path.join(root, "vite.config.js"),
734
- path.join(root, "vite.config.mjs")
735
- ];
736
- for (const candidate of candidates) {
737
- if (!fs.existsSync(candidate)) continue;
738
- let content;
739
- try {
740
- content = fs.readFileSync(candidate, "utf-8");
741
- } catch {
742
- return true;
743
- }
744
- const block = extractCacheBlock(content);
745
- if (!block) return false;
746
- return cacheFieldAssigned(block, "cdn") || cacheFieldAssigned(block, "data");
747
- }
748
- return true;
749
- }
750
- /**
751
- * Detect whether an existing user-authored Worker entry wires up a cache
752
- * backend imperatively via one of the `setCacheHandler` / `setDataCacheHandler`
753
- * / `setCdnCacheAdapter` setters. These setters are deprecated in favour of the
754
- * declarative `vinext({ cache })` option, but older apps that scaffolded a KV
755
- * cache handler into their Worker entry must keep working — so a deploy should
756
- * not be blocked when the Worker entry already configures a backend.
757
- *
758
- * This is a heuristic text scan (it doesn't execute the entry), mirroring
759
- * {@link viteConfigHasCacheAdapter}'s leniency: an unreadable Worker entry is
760
- * treated as configured so a deploy is never blocked on a false negative. A
761
- * missing Worker entry returns false (nothing to inspect — defer to other
762
- * checks).
763
- */
764
- function workerEntryHasCacheHandler(root) {
765
- const candidates = [path.join(root, "worker", "index.ts"), path.join(root, "worker", "index.js")];
766
- for (const candidate of candidates) {
767
- if (!fs.existsSync(candidate)) continue;
768
- let content;
769
- try {
770
- content = fs.readFileSync(candidate, "utf-8");
771
- } catch {
772
- return true;
773
- }
774
- return /\b(?:setCacheHandler|setDataCacheHandler|setCdnCacheAdapter)\s*\(/.test(content);
775
- }
776
- return false;
777
- }
778
- /**
779
- * Build the error thrown when an ISR/cached app is deployed without a cache
780
- * adapter configured in the Vite config. Production deployments need a
781
- * persistent cache backend; vinext no longer scaffolds one into the Worker
782
- * entry, so it must be declared via `vinext({ cache })`.
783
- */
784
- function formatMissingCacheAdapterError(options) {
785
- const configRef = options.configFile ? options.configFile : "your Vite config";
786
- return `[vinext] This app uses ISR / caching but no cache adapter is configured in ${configRef}.\n\n Production deployments need a persistent cache backend. Declare one on the\n vinext() plugin in ${configRef}:\n\n import { kvDataAdapter } from "@vinext/cloudflare/cache/kv-data-adapter";\n\n export default defineConfig({\n plugins: [\n vinext({\n cache: {\n data: kvDataAdapter(), // KV-backed data cache (binding: VINEXT_KV_CACHE)\n },\n }),\n cloudflare(),\n ],\n });\n\n The VINEXT_KV_CACHE namespace binding is added to wrangler.jsonc for you.\n Create the namespace with:\n\n npx wrangler kv namespace create VINEXT_KV_CACHE`;
787
- }
788
- function getFilesToGenerate(info) {
789
- const files = [];
790
- if (!info.hasWranglerConfig) files.push({
791
- path: path.join(info.root, "wrangler.jsonc"),
792
- content: generateWranglerConfig(info),
793
- description: "wrangler.jsonc"
794
- });
795
- if (!info.hasWorkerEntry) {
796
- const workerContent = info.isAppRouter ? generateAppRouterWorkerEntry() : generatePagesRouterWorkerEntry();
797
- files.push({
798
- path: path.join(info.root, "worker", "index.ts"),
799
- content: workerContent,
800
- description: "worker/index.ts"
801
- });
802
- }
803
- if (!info.hasViteConfig) {
804
- const viteContent = info.isAppRouter ? generateAppRouterViteConfig(info) : generatePagesRouterViteConfig(info);
805
- files.push({
806
- path: path.join(info.root, "vite.config.ts"),
807
- content: viteContent,
808
- description: "vite.config.ts"
809
- });
810
- }
811
- return files;
812
- }
813
- function writeGeneratedFiles(files) {
814
- for (const file of files) {
815
- const dir = path.dirname(file.path);
816
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
817
- fs.writeFileSync(file.path, file.content, "utf-8");
818
- console.log(` Created ${file.description}`);
819
- }
820
- }
821
- /**
822
- * Run a function with `process.env.CLOUDFLARE_ENV` set to the given value,
823
- * restoring the previous state (whether set or absent) after the function
824
- * resolves or throws.
825
- *
826
- * The `@cloudflare/vite-plugin` reads `CLOUDFLARE_ENV` from `process.env` to
827
- * drive the multi-environment merge applied to the emitted `wrangler.json`.
828
- * Without this propagation the `--env <name>` CLI flag is silently ignored at
829
- * build time and the top-level config is emitted regardless. See issue #1210.
830
- *
831
- * Passing `undefined` is a no-op; the callback runs with `process.env` untouched.
832
- */
833
- async function withCloudflareEnv(env, fn) {
834
- if (env === void 0 || env === "") return fn();
835
- const hadPrev = "CLOUDFLARE_ENV" in process.env;
836
- const prev = process.env.CLOUDFLARE_ENV;
837
- process.env.CLOUDFLARE_ENV = env;
838
- try {
839
- return await fn();
840
- } finally {
841
- if (hadPrev) process.env.CLOUDFLARE_ENV = prev;
842
- else delete process.env.CLOUDFLARE_ENV;
843
- }
844
- }
845
- async function runBuild(info, env) {
846
- console.log("\n Building for Cloudflare Workers...\n");
847
- let vitePath;
848
- try {
849
- vitePath = createRequire(path.join(info.root, "package.json")).resolve("vite");
850
- } catch {
851
- vitePath = "vite";
852
- }
853
- const { createBuilder } = await (vitePath === "vite" ? import(vitePath) : import(pathToFileURL(vitePath).href));
854
- await withCloudflareEnv(env, async () => {
855
- await (await createBuilder({ root: info.root })).buildApp();
856
- });
857
- }
858
- function validateWranglerEnvName(env) {
859
- if (env.includes("\0")) throw new Error("Wrangler environment names cannot contain null bytes.");
860
- return env;
861
- }
862
- function buildWranglerDeployArgs(options) {
863
- const args = ["deploy"];
864
- const env = options.env || (options.preview ? "preview" : void 0);
865
- if (env) args.push("--env", validateWranglerEnvName(env));
866
- return {
867
- args,
868
- env
869
- };
870
- }
871
- /**
872
- * Resolve Wrangler's JavaScript CLI entrypoint in node_modules.
873
- *
874
- * Invoking the JavaScript file through `process.execPath` avoids the `.cmd`
875
- * shim and command shell that package managers create on Windows.
876
- */
877
- function resolveWranglerBin(root, resolvePackageJson = (projectRoot) => {
878
- try {
879
- return createRequire(path.join(projectRoot, "package.json")).resolve("wrangler/package.json");
880
- } catch {
881
- return findInNodeModules(projectRoot, "wrangler/package.json");
882
- }
883
- }) {
884
- const packageJsonPath = resolvePackageJson(root);
885
- if (packageJsonPath) {
886
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
887
- const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.wrangler;
888
- if (bin) return path.resolve(path.dirname(packageJsonPath), bin);
889
- }
890
- return path.join(root, "node_modules", "wrangler", "bin", "wrangler.js");
891
- }
892
- function buildNodeCliInvocation(scriptPath, args, nodeExecutable = process.execPath) {
893
- return {
894
- file: nodeExecutable,
895
- args: [scriptPath, ...args]
896
- };
897
- }
898
- function buildWranglerInvocation(root, options, nodeExecutable = process.execPath) {
899
- const wranglerBin = resolveWranglerBin(root);
900
- const { args, env } = buildWranglerDeployArgs(options);
901
- return {
902
- ...buildNodeCliInvocation(wranglerBin, args, nodeExecutable),
903
- env
904
- };
905
- }
906
- function runWranglerDeploy(root, options, execute = execFileSync) {
907
- const execOpts = {
908
- cwd: root,
909
- stdio: "pipe",
910
- encoding: "utf-8",
911
- shell: false
912
- };
913
- const { file, args, env } = buildWranglerInvocation(root, options);
914
- if (env) console.log(`\n Deploying to env: ${env}...`);
915
- else console.log("\n Deploying to production...");
916
- const output = execute(file, args, execOpts);
917
- const urlMatch = output.match(/https:\/\/[^\s]+\.workers\.dev[^\s]*/);
918
- const deployedUrl = urlMatch ? urlMatch[0] : null;
919
- if (output.trim()) for (const line of output.trim().split("\n")) console.log(` ${line}`);
920
- return deployedUrl ?? "(URL not detected in wrangler output)";
921
- }
922
- const VINEXT_PREGEN_START = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_START__ */";
923
- const VINEXT_PREGEN_END = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_END__ */";
924
- const VINEXT_PREGEN_RE = new RegExp(`${escapeRegExp(VINEXT_PREGEN_START)}[\\s\\S]*?${escapeRegExp(VINEXT_PREGEN_END)}\\n?`, "g");
925
- /**
926
- * Read the prerender manifest and inject pregenerated concrete paths into the
927
- * App Router Worker bundle so the PPR fallback-shell guard is populated at
928
- * module init time without calling `seedMemoryCacheFromPrerender`.
929
- *
930
- * The paths are injected as `globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS`
931
- * wrapped in replaceable marker comments, and consumed by
932
- * `initPregeneratedPathsFromGlobals` in the generated RSC entry.
933
- *
934
- * Idempotent: repeated calls strip the previous injection before writing the
935
- * new one. If the manifest is missing, corrupt, or empty, any prior injection
936
- * is stripped and nothing new is written — failing closed to empty.
937
- */
938
- function injectPregeneratedConcretePaths(root) {
939
- const workerEntry = path.resolve(root, "dist", "server", "index.js");
940
- if (!fs.existsSync(workerEntry)) return;
941
- let code = fs.readFileSync(workerEntry, "utf-8");
942
- code = code.replace(VINEXT_PREGEN_RE, "");
943
- const table = buildPregeneratedConcretePathTable(readPrerenderManifest(path.join(root, "dist", "server", "vinext-prerender.json")) ?? {});
944
- if (table.length > 0) code = `${VINEXT_PREGEN_START}\nglobalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = ${JSON.stringify(table)};\n${VINEXT_PREGEN_END}\n` + code;
945
- fs.writeFileSync(workerEntry, code);
946
- }
947
- async function deploy(options) {
948
- const deployEnv = validateWranglerEnvName(options.env || (options.preview ? "preview" : "production"));
949
- const root = path.resolve(options.root);
950
- loadDotenv({
951
- root,
952
- mode: "production"
953
- });
954
- console.log("\n vinext deploy\n");
955
- const info = detectProject(normalizePathSeparators(root));
956
- if (!info.isAppRouter && !info.isPagesRouter) {
957
- console.error(" Error: No app/ or pages/ directory found.");
958
- console.error(" vinext deploy requires a Next.js project with an app/ or pages/ directory");
959
- console.error(" (also checks src/app/ and src/pages/).\n");
960
- process.exit(1);
961
- }
962
- if (options.name) info.projectName = options.name;
963
- console.log(` Project: ${info.projectName}`);
964
- console.log(` Router: ${info.isAppRouter ? "App Router" : "Pages Router"}`);
965
- console.log(` ISR: ${info.hasISR ? "detected" : "none"}`);
966
- if (info.isAppRouter) {
967
- const reactUpgrade = getReactUpgradeDeps(root);
968
- if (reactUpgrade.length > 0) {
969
- const [pm, ...pmArgs] = detectPackageManager(root).replace(/ -D$/, "").split(" ");
970
- console.log(` Upgrading ${reactUpgrade.map((d) => d.replace(/@latest$/, "")).join(", ")}...`);
971
- execFileSync(pm, [...pmArgs, ...reactUpgrade], {
972
- cwd: root,
973
- stdio: "inherit",
974
- shell: process.platform === "win32"
975
- });
976
- }
977
- }
978
- const missingDeps = getMissingDeps(info);
979
- if (missingDeps.length > 0) {
980
- console.log();
981
- installDeps(root, missingDeps);
982
- const nameOverride = options.name ? info.projectName : void 0;
983
- Object.assign(info, detectProject(normalizePathSeparators(root)));
984
- if (nameOverride) info.projectName = nameOverride;
985
- }
986
- if (!info.hasTypeModule) {
987
- const renamedConfigs = renameCJSConfigs(root);
988
- for (const [oldName, newName] of renamedConfigs) console.log(` Renamed ${oldName} → ${newName} (CJS → .cjs)`);
989
- if (ensureESModule(root)) {
990
- console.log(` Added "type": "module" to package.json`);
991
- info.hasTypeModule = true;
992
- }
993
- }
994
- const filesToGenerate = getFilesToGenerate(info);
995
- if (filesToGenerate.length > 0) {
996
- console.log();
997
- writeGeneratedFiles(filesToGenerate);
998
- }
999
- if (info.hasViteConfig && !viteConfigHasCloudflarePlugin(root)) throw new Error(formatMissingCloudflarePluginError({ isAppRouter: info.isAppRouter }));
1000
- if (info.hasISR && !viteConfigHasCacheAdapter(root) && !workerEntryHasCacheHandler(root)) throw new Error(formatMissingCacheAdapterError({}));
1001
- if (options.dryRun) {
1002
- console.log("\n Dry run complete. Files generated but no build or deploy performed.\n");
1003
- return;
1004
- }
1005
- if (!options.skipBuild) await runBuild(info, deployEnv === "production" && !options.env ? void 0 : deployEnv);
1006
- else console.log("\n Skipping build (--skip-build)");
1007
- {
1008
- const nextConfig = await resolveNextConfig(await loadNextConfig(info.root), info.root);
1009
- const isStaticExport = nextConfig.output === "export";
1010
- if (options.prerenderAll || isStaticExport) {
1011
- const label = isStaticExport && !options.prerenderAll ? "Pre-rendering all routes (output: 'export')..." : "Pre-rendering all routes...";
1012
- console.log(`\n ${label}`);
1013
- if (nextConfig.enablePrerenderSourceMaps) {
1014
- process.setSourceMapsEnabled(true);
1015
- Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 50);
1016
- }
1017
- await runPrerender({
1018
- root: info.root,
1019
- concurrency: options.prerenderConcurrency
1020
- });
1021
- }
1022
- injectPregeneratedConcretePaths(root);
1023
- }
1024
- if (options.experimentalTPR) {
1025
- console.log();
1026
- const tprResult = await runTPR({
1027
- root,
1028
- coverage: Math.max(1, Math.min(100, options.tprCoverage ?? 90)),
1029
- limit: Math.max(1, options.tprLimit ?? 1e3),
1030
- window: Math.max(1, options.tprWindow ?? 24)
1031
- });
1032
- if (tprResult.skipped) console.log(` TPR: Skipped (${tprResult.skipped})`);
1033
- }
1034
- const url = runWranglerDeploy(root, { env: deployEnv === "production" && !options.env ? void 0 : deployEnv });
1035
- console.log("\n ─────────────────────────────────────────");
1036
- console.log(` Deployed to: ${url}`);
1037
- console.log(" ─────────────────────────────────────────\n");
1038
- }
1039
- //#endregion
1040
- export { buildNodeCliInvocation, buildWranglerDeployArgs, buildWranglerInvocation, deploy, detectProject, ensureESModule, formatMissingCacheAdapterError, formatMissingCloudflarePluginError, generateAppRouterViteConfig, generateAppRouterWorkerEntry, generatePagesRouterViteConfig, generatePagesRouterWorkerEntry, generateWranglerConfig, getFilesToGenerate, getMissingDeps, hasWranglerConfig, injectPregeneratedConcretePaths, isPackageResolvable, parseDeployArgs, renameCJSConfigs, resolveWranglerBin, runWranglerDeploy, validateWranglerEnvName, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, withCloudflareEnv, workerEntryHasCacheHandler };