From 335288367d3e82eac12f3bacf23bfc5fe7898981 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 20 Dec 2024 12:26:41 +0100 Subject: [PATCH 01/13] feat(cloudflare): experimental config redirection support --- playground/nitro.config.ts | 5 + src/presets/cloudflare/types.wrangler.ts | 4 +- src/presets/cloudflare/utils.ts | 133 ++++++++++++++++++----- 3 files changed, 111 insertions(+), 31 deletions(-) diff --git a/playground/nitro.config.ts b/playground/nitro.config.ts index 2d5eb0abf7..ab99046e9b 100644 --- a/playground/nitro.config.ts +++ b/playground/nitro.config.ts @@ -2,4 +2,9 @@ import { defineNitroConfig } from "nitropack/config"; export default defineNitroConfig({ compatibilityDate: "2024-09-19", + cloudflare: { + wrangler: { + compatibility_flags: ["nodejs_als"], + }, + }, }); diff --git a/src/presets/cloudflare/types.wrangler.ts b/src/presets/cloudflare/types.wrangler.ts index a3142da739..09ec175861 100644 --- a/src/presets/cloudflare/types.wrangler.ts +++ b/src/presets/cloudflare/types.wrangler.ts @@ -24,7 +24,9 @@ * - `@breaking`: the deprecation/optionality is a breaking change from Wrangler v1. * - `@todo`: there's more work to be done (with details attached). */ -export type Config = ConfigFields & PagesConfigFields & Environment; +export type Config = Partial< + ConfigFields & PagesConfigFields & Environment +>; export type RawConfig = Partial> & PagesConfigFields & diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index b12cc93d7f..b6245e6e05 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -1,10 +1,12 @@ -import { existsSync, promises as fsp } from "node:fs"; +import { existsSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import { relative, dirname } from "node:path"; +import { writeFile } from "nitropack/kit"; import { parseTOML, stringifyTOML } from "confbox"; import defu from "defu"; import { globby } from "globby"; import type { Nitro } from "nitropack/types"; import { join, resolve } from "pathe"; -import { isCI } from "std-env"; import { joinURL, hasProtocol, @@ -13,12 +15,13 @@ import { withoutLeadingSlash, } from "ufo"; import type { CloudflarePagesRoutes } from "./types"; +import type { Config as WranglerConfig } from "./types.wrangler"; export async function writeCFPagesFiles(nitro: Nitro) { await writeCFRoutes(nitro); await writeCFPagesHeaders(nitro); await writeCFPagesRedirects(nitro); - await writeCFWrangler(nitro); + await writeCFWranglerConfig(nitro); } export async function writeCFPagesStaticFiles(nitro: Nitro) { @@ -35,9 +38,10 @@ async function writeCFRoutes(nitro: Nitro) { }; const writeRoutes = () => - fsp.writeFile( + writeFile( resolve(nitro.options.output.dir, "_routes.json"), - JSON.stringify(routes, undefined, 2) + JSON.stringify(routes, undefined, 2), + true ); if (_cfPagesConfig.defaultRoutes === false) { @@ -129,7 +133,7 @@ async function writeCFPagesHeaders(nitro: Nitro) { } if (existsSync(headersPath)) { - const currentHeaders = await fsp.readFile(headersPath, "utf8"); + const currentHeaders = await readFile(headersPath, "utf8"); if (/^\/\* /m.test(currentHeaders)) { nitro.logger.info( "Not adding Nitro fallback to `_headers` (as an existing fallback was found)." @@ -142,7 +146,7 @@ async function writeCFPagesHeaders(nitro: Nitro) { contents.unshift(currentHeaders); } - await fsp.writeFile(headersPath, contents.join("\n")); + await writeFile(headersPath, contents.join("\n"), true); } async function writeCFPagesRedirects(nitro: Nitro) { @@ -169,7 +173,7 @@ async function writeCFPagesRedirects(nitro: Nitro) { } if (existsSync(redirectsPath)) { - const currentRedirects = await fsp.readFile(redirectsPath, "utf8"); + const currentRedirects = await readFile(redirectsPath, "utf8"); if (/^\/\* /m.test(currentRedirects)) { nitro.logger.info( "Not adding Nitro fallback to `_redirects` (as an existing fallback was found)." @@ -182,37 +186,106 @@ async function writeCFPagesRedirects(nitro: Nitro) { contents.unshift(currentRedirects); } - await fsp.writeFile(redirectsPath, contents.join("\n")); + await writeFile(redirectsPath, contents.join("\n"), true); } -async function writeCFWrangler(nitro: Nitro) { - type WranglerConfig = typeof nitro.options.cloudflare.wrangler; +async function writeCFWranglerConfig(nitro: Nitro) { + const extraConfig: WranglerConfig = nitro.options.cloudflare?.wrangler || {}; - const inlineConfig: WranglerConfig = - nitro.options.cloudflare?.wrangler || ({} as WranglerConfig); - - // Write wrangler.toml only if config is not empty - if (!inlineConfig || Object.keys(inlineConfig).length === 0) { + // Skip if there are no extra config + if (Object.keys(extraConfig || {}).length === 0) { return; } - let configFromFile: WranglerConfig = {} as WranglerConfig; - const configPath = resolve( - nitro.options.rootDir, - inlineConfig.configPath || "wrangler.toml" - ); - if (existsSync(configPath)) { - configFromFile = parseTOML( - await fsp.readFile(configPath, "utf8") + // Read user config + const userConfig = await resolveWranglerConfig(nitro.options.rootDir); + + // Merge configs + const mergedConfig = userConfig.config + ? mergeWranglerConfig(userConfig.config, extraConfig) + : extraConfig; + + // Explicitly fail if pages_build_output_dir is set + if (mergedConfig.pages_build_output_dir) { + throw new Error( + "Custom wrangler `pages_build_output_dir` is not supported." ); } - const wranglerConfig: WranglerConfig = defu(configFromFile, inlineConfig); + // Write config + // https://github.com/cloudflare/workers-sdk/pull/7442 + const configRedirect = !!process.env.EXPERIMENTAL_WRANGLER_CONFIG; + if (configRedirect) { + const configPath = join( + nitro.options.rootDir, + ".wrangler/deploy/config.json" + ); + const wranglerConfigPath = join( + nitro.options.output.serverDir, + "wrangler.json" + ); + await writeFile( + configPath, + JSON.stringify({ + configPath: relative(dirname(configPath), wranglerConfigPath), + }), + true + ); + await writeFile( + wranglerConfigPath, + JSON.stringify(mergedConfig, null, 2), + true + ); + } else { + // Overwrite user config (TODO: remove when cloudflare/workers-sdk#7442 is GA) + const jsonConfig = join(nitro.options.rootDir, "wrangler.json"); + if (existsSync(jsonConfig)) { + await writeFile(jsonConfig, JSON.stringify(mergedConfig, null, 2), true); + } else { + const tomlConfig = join(nitro.options.rootDir, "wrangler.toml"); + await writeFile(tomlConfig, stringifyTOML(mergedConfig), true); + } + } +} - const wranglerPath = join( - isCI ? nitro.options.rootDir : nitro.options.buildDir, - "wrangler.toml" - ); +async function resolveWranglerConfig( + dir: string +): Promise<{ path: string; config?: WranglerConfig }> { + const jsonConfig = join(dir, "wrangler.json"); + if (existsSync(jsonConfig)) { + const config = JSON.parse( + await readFile(join(dir, "wrangler.json"), "utf8") + ) as WranglerConfig; + return { + config, + path: jsonConfig, + }; + } + const tomlConfig = join(dir, "wrangler.toml"); + if (existsSync(tomlConfig)) { + const config = parseTOML( + await readFile(join(dir, "wrangler.toml"), "utf8") + ); + return { + config, + path: tomlConfig, + }; + } + return { + path: tomlConfig, + }; +} - await fsp.writeFile(wranglerPath, stringifyTOML(wranglerConfig)); +/** + * Merge user config with extra config + * + * - Objects/Arrays are merged + * - User config takes precedence over extra config + */ +function mergeWranglerConfig( + userConfig: WranglerConfig = {}, + extraConfig: WranglerConfig = {} +): WranglerConfig { + // TODO: Improve logic with explicit merging + return defu(userConfig, extraConfig); } From 71fd0c579ef3477b4ccbc16a1db86955d1dcd7dc Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 20 Dec 2024 14:08:26 +0100 Subject: [PATCH 02/13] dedup compatibility_flags --- src/presets/cloudflare/utils.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index b6245e6e05..ccf85e11f8 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -287,5 +287,12 @@ function mergeWranglerConfig( extraConfig: WranglerConfig = {} ): WranglerConfig { // TODO: Improve logic with explicit merging - return defu(userConfig, extraConfig); + const mergedConfig: WranglerConfig = defu(userConfig, extraConfig); + if (mergedConfig.compatibility_flags) { + // TODO: exclude `no_` configs from userConfig + mergedConfig.compatibility_flags = [ + ...new Set(mergedConfig.compatibility_flags), + ]; + } + return mergedConfig; } From 9aca49f653edc5fe9714a3490b9f310a89774755 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 20 Dec 2024 14:11:28 +0100 Subject: [PATCH 03/13] respect `no_nodejs_compat_v2` --- src/presets/cloudflare/utils.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index ccf85e11f8..244c0c652b 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -289,10 +289,15 @@ function mergeWranglerConfig( // TODO: Improve logic with explicit merging const mergedConfig: WranglerConfig = defu(userConfig, extraConfig); if (mergedConfig.compatibility_flags) { - // TODO: exclude `no_` configs from userConfig mergedConfig.compatibility_flags = [ ...new Set(mergedConfig.compatibility_flags), ]; + if (mergedConfig.compatibility_flags.includes?.("no_nodejs_compat_v2")) { + mergedConfig.compatibility_flags = + mergedConfig.compatibility_flags.filter( + (flag) => flag !== "no_nodejs_compat_v2" + ); + } } return mergedConfig; } From fc9dcec13f6d8eef5a4b4cd718d291423aeba286 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 20 Dec 2024 14:12:20 +0100 Subject: [PATCH 04/13] up --- src/presets/cloudflare/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index 244c0c652b..afa8c96c82 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -290,12 +290,12 @@ function mergeWranglerConfig( const mergedConfig: WranglerConfig = defu(userConfig, extraConfig); if (mergedConfig.compatibility_flags) { mergedConfig.compatibility_flags = [ - ...new Set(mergedConfig.compatibility_flags), + ...new Set(mergedConfig.compatibility_flags || []), ]; - if (mergedConfig.compatibility_flags.includes?.("no_nodejs_compat_v2")) { + if (mergedConfig.compatibility_flags.includes("no_nodejs_compat_v2")) { mergedConfig.compatibility_flags = mergedConfig.compatibility_flags.filter( - (flag) => flag !== "no_nodejs_compat_v2" + (flag) => flag !== "nodejs_compat_v2" ); } } From 948be84df82a4ac151c497b64364be916e60d155 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 20 Dec 2024 14:15:04 +0100 Subject: [PATCH 05/13] update err --- src/presets/cloudflare/utils.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index afa8c96c82..48dd340717 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -205,17 +205,15 @@ async function writeCFWranglerConfig(nitro: Nitro) { ? mergeWranglerConfig(userConfig.config, extraConfig) : extraConfig; - // Explicitly fail if pages_build_output_dir is set - if (mergedConfig.pages_build_output_dir) { - throw new Error( - "Custom wrangler `pages_build_output_dir` is not supported." - ); - } - // Write config // https://github.com/cloudflare/workers-sdk/pull/7442 const configRedirect = !!process.env.EXPERIMENTAL_WRANGLER_CONFIG; if (configRedirect) { + if (mergedConfig.pages_build_output_dir) { + throw new Error( + "`pages_build_output_dir` wrangler config should not be set." + ); + } const configPath = join( nitro.options.rootDir, ".wrangler/deploy/config.json" From af09529146c8973eef4cebfcb56bedd4626cb13f Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 30 Jan 2025 17:20:56 +0100 Subject: [PATCH 06/13] update --- playground/nitro.config.ts | 5 - playground/wrangler.toml | 14 +-- src/presets/cloudflare/preset.ts | 22 +++- src/presets/cloudflare/types.ts | 5 + src/presets/cloudflare/utils.ts | 187 +++++++++++++++---------------- 5 files changed, 117 insertions(+), 116 deletions(-) diff --git a/playground/nitro.config.ts b/playground/nitro.config.ts index 42c47b999c..a0f2ae67e8 100644 --- a/playground/nitro.config.ts +++ b/playground/nitro.config.ts @@ -2,9 +2,4 @@ import { defineNitroConfig } from "nitropack/config"; export default defineNitroConfig({ compatibilityDate: "2025-01-30", - cloudflare: { - wrangler: { - compatibility_flags: ["nodejs_als"], - }, - }, }); diff --git a/playground/wrangler.toml b/playground/wrangler.toml index 330db4201d..19686b6861 100644 --- a/playground/wrangler.toml +++ b/playground/wrangler.toml @@ -2,12 +2,10 @@ name = "nitro-test" compatibility_date = "2024-09-19" -assets = { directory = "./.output/public/", binding = "ASSETS" } +# [[durable_objects.bindings]] +# name = "$DurableObject" +# class_name = "$DurableObject" -[[durable_objects.bindings]] -name = "$DurableObject" -class_name = "$DurableObject" - -[[migrations]] -tag = "v1" -new_sqlite_classes = ["$DurableObject"] +# [[migrations]] +# tag = "v1" +# new_sqlite_classes = ["$DurableObject"] diff --git a/src/presets/cloudflare/preset.ts b/src/presets/cloudflare/preset.ts index 6332ab7b27..fa28cfd7c7 100644 --- a/src/presets/cloudflare/preset.ts +++ b/src/presets/cloudflare/preset.ts @@ -2,7 +2,12 @@ import { defineNitroPreset } from "nitropack/kit"; import { writeFile } from "nitropack/kit"; import type { Nitro } from "nitropack/types"; import { resolve } from "pathe"; -import { writeCFPagesFiles, writeCFPagesStaticFiles } from "./utils"; +import { + writeWranglerConfig, + writeCFRoutes, + writeCFPagesHeaders, + writeCFPagesRedirects, +} from "./utils"; export type { CloudflareOptions as PresetOptions } from "./types"; @@ -52,7 +57,10 @@ const cloudflarePages = defineNitroPreset( }, hooks: { async compiled(nitro: Nitro) { - await writeCFPagesFiles(nitro); + await writeWranglerConfig(nitro); + await writeCFRoutes(nitro); + await writeCFPagesHeaders(nitro); + await writeCFPagesRedirects(nitro); }, }, }, @@ -76,7 +84,9 @@ const cloudflarePagesStatic = defineNitroPreset( }, hooks: { async compiled(nitro: Nitro) { - await writeCFPagesStaticFiles(nitro); + await writeWranglerConfig(nitro); + await writeCFPagesHeaders(nitro); + await writeCFPagesRedirects(nitro); }, }, }, @@ -105,6 +115,7 @@ const cloudflare = defineNitroPreset( }, hooks: { async compiled(nitro: Nitro) { + await writeWranglerConfig(nitro); await writeFile( resolve(nitro.options.output.dir, "package.json"), JSON.stringify({ private: true, main: "./server/index.mjs" }, null, 2) @@ -173,8 +184,8 @@ const cloudflareModule = defineNitroPreset( entry: "./runtime/cloudflare-module", exportConditions: ["workerd"], commands: { - preview: "npx wrangler dev ./server/index.mjs --assets ./public/", - deploy: "npx wrangler deploy", + preview: "npx wrangler dev -c ./server/wrangler.json", + deploy: "npx wrangler deploy -c ./server/wrangler.json", }, unenv: { external: [...cloudflareExternals], @@ -192,6 +203,7 @@ const cloudflareModule = defineNitroPreset( }, hooks: { async compiled(nitro: Nitro) { + await writeWranglerConfig(nitro); await writeFile( resolve(nitro.options.output.dir, "package.json"), JSON.stringify({ private: true, main: "./server/index.mjs" }, null, 2) diff --git a/src/presets/cloudflare/types.ts b/src/presets/cloudflare/types.ts index 4888e2f14c..85612caa44 100644 --- a/src/presets/cloudflare/types.ts +++ b/src/presets/cloudflare/types.ts @@ -28,6 +28,11 @@ export interface CloudflareOptions { */ wrangler?: WranglerConfig; + /** + * Disable the automatic generation of the Wrangler configuration file. + */ + noWranglerConfig?: boolean; + pages: { /** * Nitro will automatically generate a `_routes.json` that controls which files get served statically and diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index 48dd340717..7bf8090bbb 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -2,8 +2,8 @@ import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { relative, dirname } from "node:path"; import { writeFile } from "nitropack/kit"; -import { parseTOML, stringifyTOML } from "confbox"; -import defu from "defu"; +import { parseTOML } from "confbox"; +import { defu } from "defu"; import { globby } from "globby"; import type { Nitro } from "nitropack/types"; import { join, resolve } from "pathe"; @@ -17,19 +17,7 @@ import { import type { CloudflarePagesRoutes } from "./types"; import type { Config as WranglerConfig } from "./types.wrangler"; -export async function writeCFPagesFiles(nitro: Nitro) { - await writeCFRoutes(nitro); - await writeCFPagesHeaders(nitro); - await writeCFPagesRedirects(nitro); - await writeCFWranglerConfig(nitro); -} - -export async function writeCFPagesStaticFiles(nitro: Nitro) { - await writeCFPagesHeaders(nitro); - await writeCFPagesRedirects(nitro); -} - -async function writeCFRoutes(nitro: Nitro) { +export async function writeCFRoutes(nitro: Nitro) { const _cfPagesConfig = nitro.options.cloudflare?.pages || {}; const routes: CloudflarePagesRoutes = { version: _cfPagesConfig.routes?.version || 1, @@ -111,7 +99,7 @@ function comparePaths(a: string, b: string) { return a.split("/").length - b.split("/").length || a.localeCompare(b); } -async function writeCFPagesHeaders(nitro: Nitro) { +export async function writeCFPagesHeaders(nitro: Nitro) { const headersPath = join(nitro.options.output.dir, "_headers"); const contents = []; @@ -149,7 +137,7 @@ async function writeCFPagesHeaders(nitro: Nitro) { await writeFile(headersPath, contents.join("\n"), true); } -async function writeCFPagesRedirects(nitro: Nitro) { +export async function writeCFPagesRedirects(nitro: Nitro) { const redirectsPath = join(nitro.options.output.dir, "_redirects"); const staticFallback = existsSync( join(nitro.options.output.publicDir, "404.html") @@ -189,113 +177,116 @@ async function writeCFPagesRedirects(nitro: Nitro) { await writeFile(redirectsPath, contents.join("\n"), true); } -async function writeCFWranglerConfig(nitro: Nitro) { - const extraConfig: WranglerConfig = nitro.options.cloudflare?.wrangler || {}; - - // Skip if there are no extra config - if (Object.keys(extraConfig || {}).length === 0) { +// https://developers.cloudflare.com/workers/wrangler/configuration/#generated-wrangler-configuration +export async function writeWranglerConfig(nitro: Nitro, isPages: boolean) { + // Allow skipping generated wrangler.json + if (nitro.options.cloudflare?.noWranglerConfig) { return; } - // Read user config - const userConfig = await resolveWranglerConfig(nitro.options.rootDir); + // Compute path to generated wrangler.json + const wranglerConfigDir = nitro.options.output.serverDir; + const wranglerConfigPath = join(wranglerConfigDir, "wrangler.json"); - // Merge configs - const mergedConfig = userConfig.config - ? mergeWranglerConfig(userConfig.config, extraConfig) - : extraConfig; - - // Write config - // https://github.com/cloudflare/workers-sdk/pull/7442 - const configRedirect = !!process.env.EXPERIMENTAL_WRANGLER_CONFIG; - if (configRedirect) { - if (mergedConfig.pages_build_output_dir) { - throw new Error( - "`pages_build_output_dir` wrangler config should not be set." - ); - } - const configPath = join( - nitro.options.rootDir, - ".wrangler/deploy/config.json" - ); - const wranglerConfigPath = join( - nitro.options.output.serverDir, - "wrangler.json" - ); - await writeFile( - configPath, - JSON.stringify({ - configPath: relative(dirname(configPath), wranglerConfigPath), - }), - true - ); - await writeFile( - wranglerConfigPath, - JSON.stringify(mergedConfig, null, 2), - true + // Default configs + const defaults: WranglerConfig = {}; + + // Compatibility date + defaults.compatibility_date = + nitro.options.compatibilityDate.cloudflare || + nitro.options.compatibilityDate.default; + + // Enable nodejs compatibility by default but disable wrangler transforms + defaults.compatibility_flags = ["nodejs_compat", "no_nodejs_compat_v2"]; + + if (isPages) { + // Pages + defaults.pages_build_output_dir = relative( + dirname(wranglerConfigPath), + nitro.options.output.publicDir ); } else { - // Overwrite user config (TODO: remove when cloudflare/workers-sdk#7442 is GA) - const jsonConfig = join(nitro.options.rootDir, "wrangler.json"); - if (existsSync(jsonConfig)) { - await writeFile(jsonConfig, JSON.stringify(mergedConfig, null, 2), true); - } else { - const tomlConfig = join(nitro.options.rootDir, "wrangler.toml"); - await writeFile(tomlConfig, stringifyTOML(mergedConfig), true); - } + // Modules + defaults.main = relative( + wranglerConfigDir, + join(nitro.options.output.serverDir, "index.mjs") + ); + defaults.assets = { + // @ts-expect-error + binding: "ASSETS", + directory: relative( + dirname(wranglerConfigPath), + nitro.options.output.publicDir + ), + }; } + + // Read user config + const userConfig = await resolveWranglerConfig(nitro.options.rootDir); + + // (first argument takes precedence) + const wranglerConfig = mergeWranglerConfigs( + userConfig, + nitro.options.cloudflare?.wrangler, + defaults + ); + + // Write wrangler.json + await writeFile( + wranglerConfigPath, + JSON.stringify(wranglerConfig, null, 2), + true + ); + + // Write .wrangler/deploy/config.json (redirect file) + const configPath = join( + nitro.options.rootDir, + ".wrangler/deploy/config.json" + ); + await writeFile( + configPath, + JSON.stringify({ + configPath: relative(dirname(configPath), wranglerConfigPath), + }), + true + ); } -async function resolveWranglerConfig( - dir: string -): Promise<{ path: string; config?: WranglerConfig }> { +async function resolveWranglerConfig(dir: string): Promise { const jsonConfig = join(dir, "wrangler.json"); if (existsSync(jsonConfig)) { const config = JSON.parse( await readFile(join(dir, "wrangler.json"), "utf8") ) as WranglerConfig; - return { - config, - path: jsonConfig, - }; + return config; } const tomlConfig = join(dir, "wrangler.toml"); if (existsSync(tomlConfig)) { const config = parseTOML( await readFile(join(dir, "wrangler.toml"), "utf8") ); - return { - config, - path: tomlConfig, - }; + return config; } - return { - path: tomlConfig, - }; + return {}; } /** - * Merge user config with extra config - * - * - Objects/Arrays are merged - * - User config takes precedence over extra config + * Merge wrangler configs (first argument takes precedence) */ -function mergeWranglerConfig( - userConfig: WranglerConfig = {}, - extraConfig: WranglerConfig = {} +function mergeWranglerConfigs( + ...configs: (WranglerConfig | undefined)[] ): WranglerConfig { - // TODO: Improve logic with explicit merging - const mergedConfig: WranglerConfig = defu(userConfig, extraConfig); - if (mergedConfig.compatibility_flags) { - mergedConfig.compatibility_flags = [ - ...new Set(mergedConfig.compatibility_flags || []), - ]; - if (mergedConfig.compatibility_flags.includes("no_nodejs_compat_v2")) { - mergedConfig.compatibility_flags = - mergedConfig.compatibility_flags.filter( - (flag) => flag !== "nodejs_compat_v2" - ); + // Merge configs + const merged = defu({}, ...configs) as WranglerConfig; + + // Normalize compatibility flags + if (merged.compatibility_flags) { + let flags = [...new Set(merged.compatibility_flags || [])]; + if (flags.includes("no_nodejs_compat_v2")) { + flags = flags.filter((flag) => flag !== "nodejs_compat_v2"); } + merged.compatibility_flags = flags; } - return mergedConfig; + + return merged; } From c9c69ee573a1fe62dab86dbaa96dcb6bc28c0472 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 30 Jan 2025 17:38:17 +0100 Subject: [PATCH 07/13] update --- src/presets/cloudflare/preset.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/presets/cloudflare/preset.ts b/src/presets/cloudflare/preset.ts index fa28cfd7c7..33802bed6f 100644 --- a/src/presets/cloudflare/preset.ts +++ b/src/presets/cloudflare/preset.ts @@ -57,7 +57,7 @@ const cloudflarePages = defineNitroPreset( }, hooks: { async compiled(nitro: Nitro) { - await writeWranglerConfig(nitro); + await writeWranglerConfig(nitro, true /* pages */); await writeCFRoutes(nitro); await writeCFPagesHeaders(nitro); await writeCFPagesRedirects(nitro); @@ -84,7 +84,7 @@ const cloudflarePagesStatic = defineNitroPreset( }, hooks: { async compiled(nitro: Nitro) { - await writeWranglerConfig(nitro); + await writeWranglerConfig(nitro, true /* pages */); await writeCFPagesHeaders(nitro); await writeCFPagesRedirects(nitro); }, @@ -115,7 +115,6 @@ const cloudflare = defineNitroPreset( }, hooks: { async compiled(nitro: Nitro) { - await writeWranglerConfig(nitro); await writeFile( resolve(nitro.options.output.dir, "package.json"), JSON.stringify({ private: true, main: "./server/index.mjs" }, null, 2) @@ -203,7 +202,7 @@ const cloudflareModule = defineNitroPreset( }, hooks: { async compiled(nitro: Nitro) { - await writeWranglerConfig(nitro); + await writeWranglerConfig(nitro, false /* module */); await writeFile( resolve(nitro.options.output.dir, "package.json"), JSON.stringify({ private: true, main: "./server/index.mjs" }, null, 2) From 8cb65d7a154b8462fd9a6486b92ed6f5b6f72611 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 30 Jan 2025 17:46:25 +0100 Subject: [PATCH 08/13] update --- src/presets/cloudflare/types.ts | 4 ++-- src/presets/cloudflare/utils.ts | 29 +++++++++++++---------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/presets/cloudflare/types.ts b/src/presets/cloudflare/types.ts index 85612caa44..6aea4d0ac4 100644 --- a/src/presets/cloudflare/types.ts +++ b/src/presets/cloudflare/types.ts @@ -29,9 +29,9 @@ export interface CloudflareOptions { wrangler?: WranglerConfig; /** - * Disable the automatic generation of the Wrangler configuration file. + * Disable the automatic generation of .wrangler/deploy/config.json */ - noWranglerConfig?: boolean; + noWranglerDeployConfig?: boolean; pages: { /** diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index 7bf8090bbb..ad9c799656 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -179,11 +179,6 @@ export async function writeCFPagesRedirects(nitro: Nitro) { // https://developers.cloudflare.com/workers/wrangler/configuration/#generated-wrangler-configuration export async function writeWranglerConfig(nitro: Nitro, isPages: boolean) { - // Allow skipping generated wrangler.json - if (nitro.options.cloudflare?.noWranglerConfig) { - return; - } - // Compute path to generated wrangler.json const wranglerConfigDir = nitro.options.output.serverDir; const wranglerConfigPath = join(wranglerConfigDir, "wrangler.json"); @@ -239,17 +234,19 @@ export async function writeWranglerConfig(nitro: Nitro, isPages: boolean) { ); // Write .wrangler/deploy/config.json (redirect file) - const configPath = join( - nitro.options.rootDir, - ".wrangler/deploy/config.json" - ); - await writeFile( - configPath, - JSON.stringify({ - configPath: relative(dirname(configPath), wranglerConfigPath), - }), - true - ); + if (!nitro.options.cloudflare?.noWranglerDeployConfig) { + const configPath = join( + nitro.options.rootDir, + ".wrangler/deploy/config.json" + ); + await writeFile( + configPath, + JSON.stringify({ + configPath: relative(dirname(configPath), wranglerConfigPath), + }), + true + ); + } } async function resolveWranglerConfig(dir: string): Promise { From 2238a2f0d4931e5078c38ffbb1e34139ba629d7b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 31 Jan 2025 19:34:06 +0100 Subject: [PATCH 09/13] update --- src/presets/cloudflare/types.ts | 8 +++++++- src/presets/cloudflare/utils.ts | 9 ++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/presets/cloudflare/types.ts b/src/presets/cloudflare/types.ts index 27c796dc68..d2bac062e2 100644 --- a/src/presets/cloudflare/types.ts +++ b/src/presets/cloudflare/types.ts @@ -6,7 +6,13 @@ import type { TraceItem, } from "@cloudflare/workers-types"; import type { DurableObject } from "cloudflare:workers"; -import type { Config as WranglerConfig } from "./wrangler/config"; + +import type { + Config as _Config, + ComputedFields as _ComputedFields, +} from "./wrangler/config"; + +export type WranglerConfig = Partial>; /** * https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index ad9c799656..a7d63ccfba 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -1,3 +1,5 @@ +import type { Nitro } from "nitropack/types"; +import type { WranglerConfig, CloudflarePagesRoutes } from "./types"; import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { relative, dirname } from "node:path"; @@ -5,7 +7,6 @@ import { writeFile } from "nitropack/kit"; import { parseTOML } from "confbox"; import { defu } from "defu"; import { globby } from "globby"; -import type { Nitro } from "nitropack/types"; import { join, resolve } from "pathe"; import { joinURL, @@ -14,8 +15,6 @@ import { withTrailingSlash, withoutLeadingSlash, } from "ufo"; -import type { CloudflarePagesRoutes } from "./types"; -import type { Config as WranglerConfig } from "./types.wrangler"; export async function writeCFRoutes(nitro: Nitro) { const _cfPagesConfig = nitro.options.cloudflare?.pages || {}; @@ -191,7 +190,8 @@ export async function writeWranglerConfig(nitro: Nitro, isPages: boolean) { nitro.options.compatibilityDate.cloudflare || nitro.options.compatibilityDate.default; - // Enable nodejs compatibility by default but disable wrangler transforms + // Enable native workerd nodejs compatibility by default + // But disable wrangler transforms which are not compatible with Nitro currently defaults.compatibility_flags = ["nodejs_compat", "no_nodejs_compat_v2"]; if (isPages) { @@ -207,7 +207,6 @@ export async function writeWranglerConfig(nitro: Nitro, isPages: boolean) { join(nitro.options.output.serverDir, "index.mjs") ); defaults.assets = { - // @ts-expect-error binding: "ASSETS", directory: relative( dirname(wranglerConfigPath), From e8bb13811aa89a87c67acb88a3de6d018e83ddee Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 31 Jan 2025 19:40:46 +0100 Subject: [PATCH 10/13] revert nodejs_compat for next PR --- playground/wrangler.toml | 11 ----------- src/presets/cloudflare/utils.ts | 4 ---- 2 files changed, 15 deletions(-) delete mode 100644 playground/wrangler.toml diff --git a/playground/wrangler.toml b/playground/wrangler.toml deleted file mode 100644 index 19686b6861..0000000000 --- a/playground/wrangler.toml +++ /dev/null @@ -1,11 +0,0 @@ -name = "nitro-test" - -compatibility_date = "2024-09-19" - -# [[durable_objects.bindings]] -# name = "$DurableObject" -# class_name = "$DurableObject" - -# [[migrations]] -# tag = "v1" -# new_sqlite_classes = ["$DurableObject"] diff --git a/src/presets/cloudflare/utils.ts b/src/presets/cloudflare/utils.ts index a7d63ccfba..4622f65f31 100644 --- a/src/presets/cloudflare/utils.ts +++ b/src/presets/cloudflare/utils.ts @@ -190,10 +190,6 @@ export async function writeWranglerConfig(nitro: Nitro, isPages: boolean) { nitro.options.compatibilityDate.cloudflare || nitro.options.compatibilityDate.default; - // Enable native workerd nodejs compatibility by default - // But disable wrangler transforms which are not compatible with Nitro currently - defaults.compatibility_flags = ["nodejs_compat", "no_nodejs_compat_v2"]; - if (isPages) { // Pages defaults.pages_build_output_dir = relative( From 1947c79f96725e5c0a64448e4e62c6d21003dab7 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 3 Feb 2025 13:58:31 +0100 Subject: [PATCH 11/13] add workaround for cd commands --- src/presets/cloudflare/preset.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/presets/cloudflare/preset.ts b/src/presets/cloudflare/preset.ts index 33802bed6f..4644bb23a9 100644 --- a/src/presets/cloudflare/preset.ts +++ b/src/presets/cloudflare/preset.ts @@ -22,14 +22,20 @@ const cloudflareExternals = [ "cloudflare:workflows", ] as const; +// TODO: Remove when wrangler -C support landed +// https://github.com/cloudflare/workers-sdk/pull/7994 +const isWindows = process.platform === "win32"; +const commandWithDir = (command: string) => + isWindows ? `cmd /c ${command}` : `(cd ./ && ${command})`; + const cloudflarePages = defineNitroPreset( { extends: "cloudflare", entry: "./runtime/cloudflare-pages", exportConditions: ["workerd"], commands: { - preview: "npx wrangler pages dev ./", - deploy: "npx wrangler pages deploy ./", + preview: commandWithDir("npx wrangler pages dev"), + deploy: commandWithDir("npx wrangler pages deploy"), }, output: { dir: "{{ rootDir }}/dist", @@ -79,8 +85,8 @@ const cloudflarePagesStatic = defineNitroPreset( publicDir: "{{ output.dir }}/{{ baseURL }}", }, commands: { - preview: "npx wrangler pages dev dist", - deploy: "npx wrangler pages deploy dist", + preview: commandWithDir("npx wrangler pages dev"), + deploy: commandWithDir("npx wrangler pages deploy"), }, hooks: { async compiled(nitro: Nitro) { @@ -183,8 +189,8 @@ const cloudflareModule = defineNitroPreset( entry: "./runtime/cloudflare-module", exportConditions: ["workerd"], commands: { - preview: "npx wrangler dev -c ./server/wrangler.json", - deploy: "npx wrangler deploy -c ./server/wrangler.json", + preview: commandWithDir("npx wrangler dev"), + deploy: commandWithDir("npx wrangler deploy"), }, unenv: { external: [...cloudflareExternals], From de1662e08ca7c98895c8b12bc4b90f51c4eaef79 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 3 Feb 2025 14:01:40 +0100 Subject: [PATCH 12/13] update windows cmd --- src/presets/cloudflare/preset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/presets/cloudflare/preset.ts b/src/presets/cloudflare/preset.ts index 4644bb23a9..cab73b5fc0 100644 --- a/src/presets/cloudflare/preset.ts +++ b/src/presets/cloudflare/preset.ts @@ -26,7 +26,7 @@ const cloudflareExternals = [ // https://github.com/cloudflare/workers-sdk/pull/7994 const isWindows = process.platform === "win32"; const commandWithDir = (command: string) => - isWindows ? `cmd /c ${command}` : `(cd ./ && ${command})`; + isWindows ? `cmd /c "cd ./ && ${command}"` : `(cd ./ && ${command})`; const cloudflarePages = defineNitroPreset( { From ecf492fd250bf48ccb806dcf5d14f42716580a58 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 3 Feb 2025 14:04:16 +0100 Subject: [PATCH 13/13] add link to docs --- src/presets/cloudflare/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/presets/cloudflare/types.ts b/src/presets/cloudflare/types.ts index d2bac062e2..952a7e41b7 100644 --- a/src/presets/cloudflare/types.ts +++ b/src/presets/cloudflare/types.ts @@ -36,6 +36,8 @@ export interface CloudflareOptions { /** * Disable the automatic generation of .wrangler/deploy/config.json + * + * More info: https://developers.cloudflare.com/workers/wrangler/configuration#generated-wrangler-configuration */ noWranglerDeployConfig?: boolean;