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