diff --git a/.changeset/slimy-apples-reply.md b/.changeset/slimy-apples-reply.md new file mode 100644 index 000000000000..e6c48c686e68 --- /dev/null +++ b/.changeset/slimy-apples-reply.md @@ -0,0 +1,7 @@ +--- +"create-cloudflare": minor +--- + +feature: Add `getBindingsProxy` support to `nuxt` template via `nitro-cloudflare-dev` module. + +The `nuxt` template now uses the default dev command from `create-nuxt` instead of using `wrangler paves dev` on build output in order to improve the developer workflow. `nitro-cloudflare-dev` is a nitro module that leverages `getBindingsProxy` and allows bindings to work in nitro commands. diff --git a/.changeset/spotty-suns-learn.md b/.changeset/spotty-suns-learn.md new file mode 100644 index 000000000000..b21469801d23 --- /dev/null +++ b/.changeset/spotty-suns-learn.md @@ -0,0 +1,5 @@ +--- +"create-cloudflare": patch +--- + +feature: Add an empty `wrangler.toml` file to qwik and nuxt templates. diff --git a/packages/create-cloudflare/e2e-tests/fixtures/nuxt/server/routes/test.ts b/packages/create-cloudflare/e2e-tests/fixtures/nuxt/server/routes/test.ts new file mode 100644 index 000000000000..2ee3ef2bd346 --- /dev/null +++ b/packages/create-cloudflare/e2e-tests/fixtures/nuxt/server/routes/test.ts @@ -0,0 +1,8 @@ +export default eventHandler(async (event) => { + if (!event.context.cloudflare) { + return { success: false }; + } + const { TEST } = event.context.cloudflare.env; + + return { value: TEST, success: true }; +}); diff --git a/packages/create-cloudflare/e2e-tests/fixtures/nuxt/wrangler.toml b/packages/create-cloudflare/e2e-tests/fixtures/nuxt/wrangler.toml new file mode 100644 index 000000000000..4679b8cbbddd --- /dev/null +++ b/packages/create-cloudflare/e2e-tests/fixtures/nuxt/wrangler.toml @@ -0,0 +1,2 @@ +[vars] +TEST = "C3_TEST" diff --git a/packages/create-cloudflare/e2e-tests/fixtures/qwik/src/routes/test/index.ts b/packages/create-cloudflare/e2e-tests/fixtures/qwik/src/routes/test/index.ts index c31b601352fb..5e6d9d030a81 100644 --- a/packages/create-cloudflare/e2e-tests/fixtures/qwik/src/routes/test/index.ts +++ b/packages/create-cloudflare/e2e-tests/fixtures/qwik/src/routes/test/index.ts @@ -6,5 +6,5 @@ export const onGet: RequestHandler = async ({ platform, json }) => { return; } - json(200, { value: platform.env["TEST"], version: 1 }); + json(200, { value: platform.env["TEST"], success: true }); }; diff --git a/packages/create-cloudflare/e2e-tests/frameworks.test.ts b/packages/create-cloudflare/e2e-tests/frameworks.test.ts index 5433e81276d1..c6ea30c8f4bb 100644 --- a/packages/create-cloudflare/e2e-tests/frameworks.test.ts +++ b/packages/create-cloudflare/e2e-tests/frameworks.test.ts @@ -152,10 +152,21 @@ const frameworkTests: Record = { nuxt: { testCommitMessage: true, timeout: LONG_TIMEOUT, + unsupportedOSs: ["win32"], verifyDeploy: { route: "/", expectedText: "Welcome to Nuxt!", }, + verifyDev: { + route: "/test", + expectedText: "C3_TEST", + }, + verifyBuild: { + outputDir: "./dist", + script: "build", + route: "/test", + expectedText: "C3_TEST", + }, }, react: { testCommitMessage: true, @@ -421,8 +432,8 @@ const verifyDevScript = async ( logStream ); - // Wait a few seconds for dev server to spin up - await sleep(4000); + // Wait an eternity for the dev server to spin up + await sleep(12000); // Make a request to the specified test route const res = await fetch(`http://localhost:${TEST_PORT}${verifyDev.route}`); @@ -474,7 +485,7 @@ const verifyBuildScript = async ( ); // Wait a few seconds for dev server to spin up - await sleep(4000); + await sleep(7000); // Make a request to the specified test route const res = await fetch(`http://localhost:${TEST_PORT}${route}`); diff --git a/packages/create-cloudflare/src/workers.ts b/packages/create-cloudflare/src/workers.ts index 16550f655374..5bd01edab09d 100644 --- a/packages/create-cloudflare/src/workers.ts +++ b/packages/create-cloudflare/src/workers.ts @@ -11,6 +11,11 @@ import type { C3Context } from "types"; const { npm } = detectPackageManager(); +export const wranglerTomlExists = (ctx: C3Context) => { + const wranglerTomlPath = resolve(ctx.project.path, "wrangler.toml"); + return existsSync(wranglerTomlPath); +}; + export const readWranglerToml = (ctx: C3Context) => { const wranglerTomlPath = resolve(ctx.project.path, "wrangler.toml"); return readFile(wranglerTomlPath); @@ -26,7 +31,7 @@ export const writeWranglerToml = (ctx: C3Context, contents: string) => { * to the selected project name and adding the latest compatibility date. */ export const updateWranglerToml = async (ctx: C3Context) => { - if (ctx.template.platform !== "workers") { + if (!wranglerTomlExists(ctx)) { return; } diff --git a/packages/create-cloudflare/templates/nuxt/c3.ts b/packages/create-cloudflare/templates/nuxt/c3.ts index a0998b009017..ab38c5fced5b 100644 --- a/packages/create-cloudflare/templates/nuxt/c3.ts +++ b/packages/create-cloudflare/templates/nuxt/c3.ts @@ -1,11 +1,11 @@ -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; import { logRaw } from "@cloudflare/cli"; import { brandColor, dim } from "@cloudflare/cli/colors"; import { spinner } from "@cloudflare/cli/interactive"; -import { runFrameworkGenerator } from "helpers/command"; -import { compatDateFlag, writeFile } from "helpers/files"; +import { transformFile } from "helpers/codemod"; +import { installPackages, runFrameworkGenerator } from "helpers/command"; +import { writeFile } from "helpers/files"; import { detectPackageManager } from "helpers/packages"; +import * as recast from "recast"; import type { TemplateConfig } from "../../src/templates"; import type { C3Context } from "types"; @@ -28,31 +28,71 @@ const generate = async (ctx: C3Context) => { }; const configure = async () => { - const configFileName = "nuxt.config.ts"; - const configFilePath = resolve(configFileName); + await installPackages(["nitro-cloudflare-dev"], { + dev: true, + startText: "Installing nitro module `nitro-cloudflare-dev`", + doneText: `${brandColor("installed")} ${dim(`via \`${npm} install\``)}`, + }); + updateNuxtConfig(); +}; + +const updateNuxtConfig = () => { const s = spinner(); - s.start(`Updating \`${configFileName}\``); - // Add the cloudflare preset into the configuration file. - const originalConfigFile = readFileSync(configFilePath, "utf8"); - const updatedConfigFile = originalConfigFile.replace( - "defineNuxtConfig({", - "defineNuxtConfig({\n nitro: {\n preset: 'cloudflare-pages'\n }," + + const configFile = "nuxt.config.ts"; + s.start(`Updating \`${configFile}\``); + + const b = recast.types.builders; + + const presetDef = b.objectProperty( + b.identifier("nitro"), + b.objectExpression([ + b.objectProperty( + b.identifier("preset"), + b.stringLiteral("cloudflare-pages") + ), + ]) + ); + + const moduleDef = b.objectProperty( + b.identifier("modules"), + b.arrayExpression([b.stringLiteral("nitro-cloudflare-dev")]) ); - writeFile(configFilePath, updatedConfigFile); - s.stop(`${brandColor(`updated`)} ${dim(`\`${configFileName}\``)}`); + + transformFile(configFile, { + visitCallExpression: function (n) { + const callee = n.node.callee as recast.types.namedTypes.Identifier; + if (callee.name === "defineNuxtConfig") { + const obj = n.node + .arguments[0] as recast.types.namedTypes.ObjectExpression; + + obj.properties.push(presetDef); + obj.properties.push(moduleDef); + } + + return this.traverse(n); + }, + }); + + s.stop(`${brandColor(`updated`)} ${dim(`\`${configFile}\``)}`); }; const config: TemplateConfig = { configVersion: 1, id: "nuxt", platform: "pages", + copyFiles: { + path: "./templates", + }, displayName: "Nuxt", + devScript: "dev", + deployScript: "deploy", generate, configure, transformPackageJson: async () => ({ scripts: { - "pages:dev": `wrangler pages dev ${await compatDateFlag()} --proxy 3000 -- ${npm} run dev`, - "pages:deploy": `${npm} run build && wrangler pages deploy ./dist`, + deploy: `${npm} run build && wrangler pages deploy ./dist`, + preview: `${npm} run build && wrangler pages dev ./dist`, }, }), }; diff --git a/packages/create-cloudflare/templates/nuxt/templates/wrangler.toml b/packages/create-cloudflare/templates/nuxt/templates/wrangler.toml new file mode 100644 index 000000000000..08652e52729b --- /dev/null +++ b/packages/create-cloudflare/templates/nuxt/templates/wrangler.toml @@ -0,0 +1,50 @@ +name = "" +compatibility_date = "" + +# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables) +# Note: Use secrets to store sensitive data. +# Docs: https://developers.cloudflare.com/workers/platform/environment-variables +# [vars] +# MY_VARIABLE = "production_value" + +# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs. +# Docs: https://developers.cloudflare.com/workers/runtime-apis/kv +# [[kv_namespaces]] +# binding = "MY_KV_NAMESPACE" +# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files. +# Docs: https://developers.cloudflare.com/r2/api/workers/workers-api-usage/ +# [[r2_buckets]] +# binding = "MY_BUCKET" +# bucket_name = "my-bucket" + +# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer. +# Docs: https://developers.cloudflare.com/queues/get-started +# [[queues.producers]] +# binding = "MY_QUEUE" +# queue = "my-queue" + +# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them. +# Docs: https://developers.cloudflare.com/queues/get-started +# [[queues.consumers]] +# queue = "my-queue" + +# Bind another Worker service. Use this binding to call another Worker without network overhead. +# Docs: https://developers.cloudflare.com/workers/platform/services +# [[services]] +# binding = "MY_SERVICE" +# service = "my-service" + +# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model. +# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps. +# Docs: https://developers.cloudflare.com/workers/runtime-apis/durable-objects +# [[durable_objects.bindings]] +# name = "MY_DURABLE_OBJECT" +# class_name = "MyDurableObject" + +# Durable Object migrations. +# Docs: https://developers.cloudflare.com/workers/learning/using-durable-objects#configure-durable-object-classes-with-migrations +# [[migrations]] +# tag = "v1" +# new_classes = ["MyDurableObject"] diff --git a/packages/create-cloudflare/templates/qwik/c3.ts b/packages/create-cloudflare/templates/qwik/c3.ts index 0256e90dc917..45c47dd1dfe0 100644 --- a/packages/create-cloudflare/templates/qwik/c3.ts +++ b/packages/create-cloudflare/templates/qwik/c3.ts @@ -79,6 +79,9 @@ const config: TemplateConfig = { id: "qwik", displayName: "Qwik", platform: "pages", + copyFiles: { + path: "./templates", + }, devScript: "dev", deployScript: "deploy", generate, diff --git a/packages/create-cloudflare/templates/qwik/templates/wrangler.toml b/packages/create-cloudflare/templates/qwik/templates/wrangler.toml new file mode 100644 index 000000000000..08652e52729b --- /dev/null +++ b/packages/create-cloudflare/templates/qwik/templates/wrangler.toml @@ -0,0 +1,50 @@ +name = "" +compatibility_date = "" + +# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables) +# Note: Use secrets to store sensitive data. +# Docs: https://developers.cloudflare.com/workers/platform/environment-variables +# [vars] +# MY_VARIABLE = "production_value" + +# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs. +# Docs: https://developers.cloudflare.com/workers/runtime-apis/kv +# [[kv_namespaces]] +# binding = "MY_KV_NAMESPACE" +# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files. +# Docs: https://developers.cloudflare.com/r2/api/workers/workers-api-usage/ +# [[r2_buckets]] +# binding = "MY_BUCKET" +# bucket_name = "my-bucket" + +# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer. +# Docs: https://developers.cloudflare.com/queues/get-started +# [[queues.producers]] +# binding = "MY_QUEUE" +# queue = "my-queue" + +# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them. +# Docs: https://developers.cloudflare.com/queues/get-started +# [[queues.consumers]] +# queue = "my-queue" + +# Bind another Worker service. Use this binding to call another Worker without network overhead. +# Docs: https://developers.cloudflare.com/workers/platform/services +# [[services]] +# binding = "MY_SERVICE" +# service = "my-service" + +# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model. +# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps. +# Docs: https://developers.cloudflare.com/workers/runtime-apis/durable-objects +# [[durable_objects.bindings]] +# name = "MY_DURABLE_OBJECT" +# class_name = "MyDurableObject" + +# Durable Object migrations. +# Docs: https://developers.cloudflare.com/workers/learning/using-durable-objects#configure-durable-object-classes-with-migrations +# [[migrations]] +# tag = "v1" +# new_classes = ["MyDurableObject"]