From 15af87d7ef764ee36a6903eedf826e736fe52eda Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 15:45:48 +0100 Subject: [PATCH 01/17] feat: add --experimental flag to allow access to experimental templates --- .prettierignore | 13 +-- packages/create-cloudflare/package.json | 3 +- packages/create-cloudflare/scripts/build.ts | 2 +- .../create-cloudflare/src/helpers/args.ts | 7 ++ packages/create-cloudflare/src/helpers/cli.ts | 1 + packages/create-cloudflare/src/templates.ts | 89 ++++++++++++------- packages/create-cloudflare/src/types.ts | 3 +- 7 files changed, 77 insertions(+), 41 deletions(-) diff --git a/.prettierignore b/.prettierignore index f9587c3f48de..30e22762b39b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -16,14 +16,15 @@ CHANGELOG.md # In the C3 templates, in particular framework templates, we want to be able to # use any format that the framework authors prefer/use in their own templates, # so let's ignore all the c3 template files, exlcuding the c3.ts ones, and tests -packages/create-cloudflare/templates/**/*.* -!packages/create-cloudflare/templates/**/c3.ts -!packages/create-cloudflare/templates/**/test/**/*.ts -!packages/create-cloudflare/templates/**/test/**/*.js +packages/create-cloudflare/templates*/**/*.* +!packages/create-cloudflare/templates*/**/c3.ts +!packages/create-cloudflare/templates*/**/test/**/*.ts +!packages/create-cloudflare/templates*/**/test/**/*.js # Format the hello-world template (negate previous exclusion above) for best practices, since we control these # but still exclude the worker-configuration.d.ts file, since it's generated -!packages/create-cloudflare/templates/hello-world/**/*.* -packages/create-cloudflare/templates/hello-world/**/worker-configuration.d.ts +!packages/create-cloudflare/templates*/hello-world/**/*.* +packages/create-cloudflare/templates*/hello-world/**/worker-configuration.d.ts + # dist-functions are generated in the fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self folder dist-functions diff --git a/packages/create-cloudflare/package.json b/packages/create-cloudflare/package.json index a61d2bdfec23..cad965131002 100644 --- a/packages/create-cloudflare/package.json +++ b/packages/create-cloudflare/package.json @@ -23,7 +23,8 @@ "bin": "./dist/cli.js", "files": [ "dist", - "templates" + "templates", + "templates-experimental" ], "scripts": { "build": "node -r esbuild-register scripts/build.ts", diff --git a/packages/create-cloudflare/scripts/build.ts b/packages/create-cloudflare/scripts/build.ts index 063f72524561..9cc9c45fb0f6 100644 --- a/packages/create-cloudflare/scripts/build.ts +++ b/packages/create-cloudflare/scripts/build.ts @@ -23,7 +23,7 @@ const run = async () => { // The latter has been added to the project's .gitignore file // This renaming will be reversed when each template is used // We can continue to author ".gitignore" files in each template - for (const filepath of glob.sync("templates/**/.gitignore")) { + for (const filepath of glob.sync("templates*/**/.gitignore")) { await cp(filepath, filepath.replace(".gitignore", "__dot__gitignore")); } }; diff --git a/packages/create-cloudflare/src/helpers/args.ts b/packages/create-cloudflare/src/helpers/args.ts index 6638dfb42516..050223a825dc 100644 --- a/packages/create-cloudflare/src/helpers/args.ts +++ b/packages/create-cloudflare/src/helpers/args.ts @@ -134,6 +134,13 @@ const cliDefinition: ArgumentsDefinition = { { name: "vue" }, ], }, + { + name: "experimental", + hidden: true, + type: "boolean", + description: "Select from experimental frameworks.", + default: false, + }, { name: "lang", type: "string", diff --git a/packages/create-cloudflare/src/helpers/cli.ts b/packages/create-cloudflare/src/helpers/cli.ts index 0ab81392e921..296c6a6a5049 100644 --- a/packages/create-cloudflare/src/helpers/cli.ts +++ b/packages/create-cloudflare/src/helpers/cli.ts @@ -50,6 +50,7 @@ export const C3_DEFAULTS: C3Args = { category: "hello-world", type: "hello-world", framework: "analog", + experimental: false, autoUpdate: true, deploy: true, git: true, diff --git a/packages/create-cloudflare/src/templates.ts b/packages/create-cloudflare/src/templates.ts index 17266540a7c1..13fcd536418b 100644 --- a/packages/create-cloudflare/src/templates.ts +++ b/packages/create-cloudflare/src/templates.ts @@ -124,35 +124,56 @@ const defaultSelectVariant = async (ctx: C3Context) => { export type FrameworkMap = Awaited>; export type FrameworkName = keyof FrameworkMap; -export const getFrameworkMap = async () => ({ - analog: (await import("../templates/analog/c3")).default, - angular: (await import("../templates/angular/c3")).default, - astro: (await import("../templates/astro/c3")).default, - docusaurus: (await import("../templates/docusaurus/c3")).default, - gatsby: (await import("../templates/gatsby/c3")).default, - hono: (await import("../templates/hono/c3")).default, - next: (await import("../templates/next/c3")).default, - nuxt: (await import("../templates/nuxt/c3")).default, - qwik: (await import("../templates/qwik/c3")).default, - react: (await import("../templates/react/c3")).default, - remix: (await import("../templates/remix/c3")).default, - solid: (await import("../templates/solid/c3")).default, - svelte: (await import("../templates/svelte/c3")).default, - vue: (await import("../templates/vue/c3")).default, -}); - -export const getTemplateMap = async () => { - return { - "hello-world": (await import("../templates/hello-world/c3")).default, - common: (await import("../templates/common/c3")).default, - scheduled: (await import("../templates/scheduled/c3")).default, - queues: (await import("../templates/queues/c3")).default, - "hello-world-durable-object": ( - await import("../templates/hello-world-durable-object/c3") - ).default, - openapi: (await import("../templates/openapi/c3")).default, - "pre-existing": (await import("../templates/pre-existing/c3")).default, - } as Record; +export const getFrameworkMap = async ({ experimental = false }) => { + if (experimental) { + return { + astro: (await import("../templates-experimental/astro/c3")).default, + nuxt: (await import("../templates-experimental/nuxt/c3")).default, + qwik: (await import("../templates-experimental/qwik/c3")).default, + remix: (await import("../templates-experimental/remix/c3")).default, + solid: (await import("../templates-experimental/solid/c3")).default, + svelte: (await import("../templates-experimental/svelte/c3")).default, + }; + } else { + return { + analog: (await import("../templates/analog/c3")).default, + angular: (await import("../templates/angular/c3")).default, + astro: (await import("../templates/astro/c3")).default, + docusaurus: (await import("../templates/docusaurus/c3")).default, + gatsby: (await import("../templates/gatsby/c3")).default, + hono: (await import("../templates/hono/c3")).default, + next: (await import("../templates/next/c3")).default, + nuxt: (await import("../templates/nuxt/c3")).default, + qwik: (await import("../templates/qwik/c3")).default, + react: (await import("../templates/react/c3")).default, + remix: (await import("../templates/remix/c3")).default, + solid: (await import("../templates/solid/c3")).default, + svelte: (await import("../templates/svelte/c3")).default, + vue: (await import("../templates/vue/c3")).default, + }; + } +}; + +export const getTemplateMap = async ({ experimental = false }) => { + if (experimental) { + return { + "hello-world-with-assets": ( + await import("../templates-experimental/hello-world-with-assets/c3") + ).default, + } as Record; + } else { + return { + "hello-world": (await import("../templates/hello-world/c3")).default, + common: (await import("../templates/common/c3")).default, + scheduled: (await import("../templates/scheduled/c3")).default, + queues: (await import("../templates/queues/c3")).default, + "hello-world-durable-object": ( + await import("../templates/hello-world-durable-object/c3") + ).default, + openapi: (await import("../templates/openapi/c3")).default, + "pre-existing": (await import("../templates/pre-existing/c3")).default, + } as Record; + } }; export const deriveCorrelatedArgs = (args: Partial) => { @@ -314,7 +335,9 @@ export const createContext = async ( let template: TemplateConfig; if (category === "web-framework") { - const frameworkMap = await getFrameworkMap(); + const frameworkMap = await getFrameworkMap({ + experimental: args.experimental, + }); const frameworkOptions = Object.entries(frameworkMap).map( ([key, config]) => ({ label: config.displayName, @@ -352,7 +375,9 @@ export const createContext = async ( } else if (category === "remote-template") { template = await processRemoteTemplate(args); } else { - const templateMap = await getTemplateMap(); + const templateMap = await getTemplateMap({ + experimental: args.experimental, + }); const templateOptions: Option[] = Object.entries(templateMap).map( ([value, { displayName, description, hidden }]) => { const isHelloWorldExample = value.startsWith("hello-world"); @@ -655,7 +680,7 @@ export const updatePackageScripts = async (ctx: C3Context) => { export const getTemplatePath = (ctx: C3Context) => { if (ctx.template.path) { - return ctx.template.path; + return resolve(__dirname, "..", ctx.template.path); } return resolve(__dirname, "..", "templates", ctx.template.id); diff --git a/packages/create-cloudflare/src/types.ts b/packages/create-cloudflare/src/types.ts index 0660b5c5a0d8..03b65a38f4d4 100644 --- a/packages/create-cloudflare/src/types.ts +++ b/packages/create-cloudflare/src/types.ts @@ -8,8 +8,9 @@ export type C3Args = { git?: boolean; autoUpdate?: boolean; category?: string; - // pages specific + // frameworks specific framework?: string; + experimental?: boolean; // workers specific ts?: boolean; lang?: string; From 115adbb5b36c2d09537afe264092c8252104aa87 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 20:36:44 +0100 Subject: [PATCH 02/17] fix: do not crash if the chosen framework does not exist --- packages/cli/interactive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/interactive.ts b/packages/cli/interactive.ts index 461664e2f780..a2a48889e151 100644 --- a/packages/cli/interactive.ts +++ b/packages/cli/interactive.ts @@ -246,7 +246,7 @@ type Renderer = ( const renderSubmit = (config: PromptConfig, value: string) => { const { question, label } = config; - if (config.type !== "confirm" && value.length === 0) { + if (config.type !== "confirm" && !value) { return [`${leftT} ${question} ${dim("(skipped)")}`, `${grayBar}`]; } From 27dc0a0700c7325e02ef43523c5f7e47eb4f22f4 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 23:32:48 +0100 Subject: [PATCH 03/17] fix: offer to deploy workers with assets and no bindings --- .../src/__tests__/deploy.test.ts | 35 ++++++++++++++++++- packages/create-cloudflare/src/deploy.ts | 30 +++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/packages/create-cloudflare/src/__tests__/deploy.test.ts b/packages/create-cloudflare/src/__tests__/deploy.test.ts index f84efa676ad5..c69ddc8b98fa 100644 --- a/packages/create-cloudflare/src/__tests__/deploy.test.ts +++ b/packages/create-cloudflare/src/__tests__/deploy.test.ts @@ -49,7 +49,7 @@ describe("deploy helpers", async () => { await expect(offerToDeploy(ctx)).resolves.toBe(true); }); - test("project is undeployable", async () => { + test("project is undeployable (simple binding)", async () => { const ctx = createTestContext(); // Can't deploy things with bindings (yet!) vi.mocked(readFile).mockReturnValue(`binding = "MY_QUEUE"`); @@ -60,6 +60,39 @@ describe("deploy helpers", async () => { expect(wranglerLogin).not.toHaveBeenCalled(); }); + test("project is undeployable (complex binding)", async () => { + const ctx = createTestContext(); + // Can't deploy things with bindings (yet!) + vi.mocked(readFile).mockReturnValue(` + experimental_assets = { directory = "./dist", binding = "ASSETS" } + + [[durable_objects.bindings]] + name = "MY_DURABLE_OBJECT" + class_name = "MyDurableObject" + `); + + await expect(offerToDeploy(ctx)).resolves.toBe(false); + expect(processArgument).toHaveBeenCalledOnce(); + expect(ctx.args.deploy).toBe(false); + expect(wranglerLogin).not.toHaveBeenCalled(); + }); + + test("assets project is deployable (no other bindings)", async () => { + const ctx = createTestContext(); + vi.mocked(readFile).mockReturnValue(` + experimental_assets = { directory = "./dist", binding = "ASSETS" } + `); + // mock the user selecting yes when asked to deploy + vi.mocked(processArgument).mockResolvedValueOnce(true); + // mock a successful wrangler login + vi.mocked(wranglerLogin).mockResolvedValueOnce(true); + + await expect(offerToDeploy(ctx)).resolves.toBe(true); + expect(processArgument).toHaveBeenCalledOnce(); + expect(ctx.args.deploy).toBe(true); + expect(wranglerLogin).toHaveBeenCalled(); + }); + test("--no-deploy from command line", async () => { const ctx = createTestContext(); ctx.args.deploy = false; diff --git a/packages/create-cloudflare/src/deploy.ts b/packages/create-cloudflare/src/deploy.ts index bf2b94693ec3..af38aed5d568 100644 --- a/packages/create-cloudflare/src/deploy.ts +++ b/packages/create-cloudflare/src/deploy.ts @@ -1,6 +1,7 @@ import { crash, startSection, updateStatus } from "@cloudflare/cli"; import { processArgument } from "@cloudflare/cli/args"; import { blue, brandColor, dim } from "@cloudflare/cli/colors"; +import TOML from "@iarna/toml"; import { C3_DEFAULTS, openInBrowser } from "helpers/cli"; import { quoteShellArgs, runCommand } from "helpers/command"; import { detectPackageManager } from "helpers/packageManagers"; @@ -67,12 +68,11 @@ const isDeployable = async (ctx: C3Context) => { return true; } - const wranglerToml = readWranglerToml(ctx); - if (wranglerToml.match(/(? { @@ -139,3 +139,23 @@ export const maybeOpenBrowser = async (ctx: C3Context) => { } } }; + +/** + * Recursively search the properties of node for a binding. + */ +export const hasBinding = (node: unknown): boolean => { + if (typeof node !== "object" || node === null) { + return false; + } + for (const key of Object.keys(node)) { + if (key === "experimental_assets" || key === "assets") { + // Properties called "binding" within "assets" do not count as bindings. + continue; + } + if (key === "binding" || key === "bindings") { + return true; + } + return hasBinding(node[key as keyof typeof node]); + } + return false; +}; From cb2626a9d71bbffa331c507ae976fc678cae9e11 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 15:46:23 +0100 Subject: [PATCH 04/17] feat: add experimental qwik workers + assets template --- .../create-cloudflare/src/helpers/files.ts | 8 + .../templates-experimental/qwik/c3.ts | 152 ++++++++++++++++++ .../qwik/snippets/getPlatformProxy.ts | 6 + .../qwik/templates/public/.assetsignore | 4 + .../qwik/templates/worker-configuration.d.ts | 4 + .../qwik/templates/wrangler.toml | 6 + 6 files changed, 180 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/qwik/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/qwik/snippets/getPlatformProxy.ts create mode 100644 packages/create-cloudflare/templates-experimental/qwik/templates/public/.assetsignore create mode 100644 packages/create-cloudflare/templates-experimental/qwik/templates/worker-configuration.d.ts create mode 100644 packages/create-cloudflare/templates-experimental/qwik/templates/wrangler.toml diff --git a/packages/create-cloudflare/src/helpers/files.ts b/packages/create-cloudflare/src/helpers/files.ts index 408e17ca60ae..bf138d079a32 100644 --- a/packages/create-cloudflare/src/helpers/files.ts +++ b/packages/create-cloudflare/src/helpers/files.ts @@ -36,6 +36,14 @@ export const readFile = (path: string) => { } }; +export const removeFile = (path: string) => { + try { + fs.rmSync(path, { force: true }); + } catch (error) { + crash(error as string); + } +}; + export const directoryExists = (path: string): boolean => { try { const stat = statSync(path); diff --git a/packages/create-cloudflare/templates-experimental/qwik/c3.ts b/packages/create-cloudflare/templates-experimental/qwik/c3.ts new file mode 100644 index 000000000000..186c3b34a65a --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/qwik/c3.ts @@ -0,0 +1,152 @@ +import { crash, endSection } from "@cloudflare/cli"; +import { brandColor } from "@cloudflare/cli/colors"; +import { spinner } from "@cloudflare/cli/interactive"; +import { runFrameworkGenerator } from "frameworks/index"; +import { loadTemplateSnippets, transformFile } from "helpers/codemod"; +import { quoteShellArgs, runCommand } from "helpers/command"; +import { removeFile, usesTypescript } from "helpers/files"; +import { detectPackageManager } from "helpers/packageManagers"; +import * as recast from "recast"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context } from "types"; + +const { npm, npx } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + await runFrameworkGenerator(ctx, ["basic", ctx.project.name]); +}; + +const configure = async (ctx: C3Context) => { + // Add the pages integration + const cmd = [npx, "qwik", "add", "cloudflare-pages"]; + endSection(`Running ${quoteShellArgs(cmd)}`); + await runCommand(cmd); + + // Remove the extraneous Pages files + removeFile("./public/_headers"); + removeFile("./public/_redirects"); + removeFile("./public/_routes.json"); + + addBindingsProxy(ctx); + populateCloudflareEnv(); +}; + +const addBindingsProxy = (ctx: C3Context) => { + // Qwik only has a typescript template atm. + // This check is an extra precaution + if (!usesTypescript(ctx)) { + return; + } + + const s = spinner(); + s.start("Updating `vite.config.ts`"); + + const snippets = loadTemplateSnippets(ctx); + const b = recast.types.builders; + + transformFile("vite.config.ts", { + // Insert the env declaration after the last import (but before the rest of the body) + visitProgram: function (n) { + const lastImportIndex = n.node.body.findLastIndex( + (t) => t.type === "ImportDeclaration", + ); + const lastImport = n.get("body", lastImportIndex); + lastImport.insertAfter(...snippets.getPlatformProxyTs); + + return this.traverse(n); + }, + // Pass the `platform` object from the declaration to the `qwikCity` plugin + visitCallExpression: function (n) { + const callee = n.node.callee as recast.types.namedTypes.Identifier; + if (callee.name !== "qwikCity") { + return this.traverse(n); + } + + // The config object passed to `qwikCity` + const configArgument = n.node.arguments[0] as + | recast.types.namedTypes.ObjectExpression + | undefined; + + const platformPropery = b.objectProperty.from({ + key: b.identifier("platform"), + value: b.identifier("platform"), + shorthand: true, + }); + + if (!configArgument) { + n.node.arguments = [b.objectExpression([platformPropery])]; + + return false; + } + + if (configArgument.type !== "ObjectExpression") { + crash("Failed to update `vite.config.ts`"); + } + + // Add the `platform` object to the object + configArgument.properties.push(platformPropery); + + return false; + }, + }); + + s.stop(`${brandColor("updated")} \`vite.config.ts\``); +}; + +const populateCloudflareEnv = () => { + const entrypointPath = "src/entry.cloudflare-pages.tsx"; + + const s = spinner(); + s.start(`Updating \`${entrypointPath}\``); + + transformFile(entrypointPath, { + visitTSInterfaceDeclaration: function (n) { + const b = recast.types.builders; + const id = n.node.id as recast.types.namedTypes.Identifier; + if (id.name !== "QwikCityPlatform") { + this.traverse(n); + } + + const newBody = [ + ["env", "Env"], + // Qwik doesn't supply `cf` to the platform object. Should they do so, uncomment this + // ["cf", "CfProperties"], + ].map(([varName, type]) => + b.tsPropertySignature( + b.identifier(varName), + b.tsTypeAnnotation(b.tsTypeReference(b.identifier(type))), + ), + ); + + n.node.body.body = newBody; + + return false; + }, + }); + + s.stop(`${brandColor("updated")} \`${entrypointPath}\``); +}; + +const config: TemplateConfig = { + configVersion: 1, + id: "qwik", + displayName: "Qwik", + platform: "pages", + copyFiles: { + path: "./templates", + }, + path: "templates-experimental/qwik", + generate, + configure, + transformPackageJson: async () => ({ + scripts: { + deploy: `${npm} run build && wrangler deploy`, + preview: `${npm} run build && wrangler dev`, + "cf-typegen": `wrangler types`, + }, + }), + devScript: "dev", + deployScript: "deploy", + previewScript: "preview", +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/qwik/snippets/getPlatformProxy.ts b/packages/create-cloudflare/templates-experimental/qwik/snippets/getPlatformProxy.ts new file mode 100644 index 000000000000..80789f159a76 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/qwik/snippets/getPlatformProxy.ts @@ -0,0 +1,6 @@ +let platform = {}; + +if(process.env.NODE_ENV === 'development') { + const { getPlatformProxy } = await import('wrangler'); + platform = await getPlatformProxy(); +} diff --git a/packages/create-cloudflare/templates-experimental/qwik/templates/public/.assetsignore b/packages/create-cloudflare/templates-experimental/qwik/templates/public/.assetsignore new file mode 100644 index 000000000000..c2634cc3650a --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/qwik/templates/public/.assetsignore @@ -0,0 +1,4 @@ +_worker.js +_routes.json +_headers +_redirects diff --git a/packages/create-cloudflare/templates-experimental/qwik/templates/worker-configuration.d.ts b/packages/create-cloudflare/templates-experimental/qwik/templates/worker-configuration.d.ts new file mode 100644 index 000000000000..5b2319b3f29f --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/qwik/templates/worker-configuration.d.ts @@ -0,0 +1,4 @@ +// Generated by Wrangler +// After adding bindings to `wrangler.toml`, regenerate this interface via `npm run cf-typegen` +interface Env { +} diff --git a/packages/create-cloudflare/templates-experimental/qwik/templates/wrangler.toml b/packages/create-cloudflare/templates-experimental/qwik/templates/wrangler.toml new file mode 100644 index 000000000000..2128ee08849d --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/qwik/templates/wrangler.toml @@ -0,0 +1,6 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +compatibility_flags = ["nodejs_compat"] +main = "./dist/_worker.js" +experimental_assets = { directory = "./dist", binding = "ASSET" } From 7dab93d53153aeb62a0a05d4393965113193e062 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 15:46:34 +0100 Subject: [PATCH 05/17] feat: add experimental svelte workers + assets template --- .../templates-experimental/svelte/c3.ts | 131 ++++++++++++++++++ .../svelte/js/static/.assetsignore | 4 + .../svelte/js/wrangler.toml | 5 + .../svelte/ts/static/.assetsignore | 4 + .../svelte/ts/wrangler.toml | 5 + 5 files changed, 149 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/svelte/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/svelte/js/static/.assetsignore create mode 100644 packages/create-cloudflare/templates-experimental/svelte/js/wrangler.toml create mode 100644 packages/create-cloudflare/templates-experimental/svelte/ts/static/.assetsignore create mode 100644 packages/create-cloudflare/templates-experimental/svelte/ts/wrangler.toml diff --git a/packages/create-cloudflare/templates-experimental/svelte/c3.ts b/packages/create-cloudflare/templates-experimental/svelte/c3.ts new file mode 100644 index 000000000000..f728a95aeac6 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/svelte/c3.ts @@ -0,0 +1,131 @@ +import { platform } from "node:os"; +import { logRaw, updateStatus } from "@cloudflare/cli"; +import { blue, brandColor, dim } from "@cloudflare/cli/colors"; +import { runFrameworkGenerator } from "frameworks/index"; +import { transformFile } from "helpers/codemod"; +import { usesTypescript } from "helpers/files"; +import { detectPackageManager } from "helpers/packageManagers"; +import { installPackages } from "helpers/packages"; +import * as recast from "recast"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context, PackageJson } from "types"; + +const { npm } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + await runFrameworkGenerator(ctx, [ctx.project.name]); + + logRaw(""); +}; + +const configure = async (ctx: C3Context) => { + // Install the adapter + const pkg = `@sveltejs/adapter-cloudflare`; + await installPackages([pkg], { + dev: true, + startText: "Adding the Cloudflare Pages adapter", + doneText: `${brandColor(`installed`)} ${dim(pkg)}`, + }); + + updateSvelteConfig(); + updateTypeDefinitions(ctx); +}; + +const updateSvelteConfig = () => { + // All we need to do is change the import statement in svelte.config.js + updateStatus(`Changing adapter in ${blue("svelte.config.js")}`); + + transformFile("svelte.config.js", { + visitImportDeclaration: function (n) { + // importSource is the `x` in `import y from "x"` + const importSource = n.value.source; + if (importSource.value === "@sveltejs/adapter-auto") { + importSource.value = "@sveltejs/adapter-cloudflare"; + } + + // stop traversing this node + return false; + }, + }); +}; + +const updateTypeDefinitions = (ctx: C3Context) => { + if (!usesTypescript(ctx)) { + return; + } + + updateStatus(`Updating global type definitions in ${blue("app.d.ts")}`); + + const b = recast.types.builders; + + transformFile("src/app.d.ts", { + visitTSModuleDeclaration(n) { + if (n.value.id.name === "App" && n.node.body) { + const moduleBlock = n.node + .body as recast.types.namedTypes.TSModuleBlock; + + const platformInterface = b.tsInterfaceDeclaration( + b.identifier("Platform"), + b.tsInterfaceBody([ + b.tsPropertySignature( + b.identifier("env"), + b.tsTypeAnnotation(b.tsTypeReference(b.identifier("Env"))), + ), + b.tsPropertySignature( + b.identifier("cf"), + b.tsTypeAnnotation( + b.tsTypeReference(b.identifier("CfProperties")), + ), + ), + b.tsPropertySignature( + b.identifier("ctx"), + b.tsTypeAnnotation( + b.tsTypeReference(b.identifier("ExecutionContext")), + ), + ), + ]), + ); + + moduleBlock.body.unshift(platformInterface); + } + + this.traverse(n); + }, + }); +}; + +const config: TemplateConfig = { + configVersion: 1, + id: "svelte", + displayName: "Svelte", + platform: "pages", + copyFiles: { + variants: { + js: { path: "./js" }, + ts: { path: "./ts" }, + }, + }, + path: "templates-experimental/svelte", + generate, + configure, + transformPackageJson: async (original: PackageJson, ctx: C3Context) => { + let scripts: Record = { + preview: `${npm} run build && wrangler dev`, + deploy: `${npm} run build && wrangler deploy`, + }; + + if (usesTypescript(ctx)) { + const mv = platform() === "win32" ? "move" : "mv"; + scripts = { + ...scripts, + "cf-typegen": `wrangler types && ${mv} worker-configuration.d.ts src/`, + }; + } + + return { scripts }; + }, + devScript: "dev", + deployScript: "deploy", + previewScript: "preview", +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/svelte/js/static/.assetsignore b/packages/create-cloudflare/templates-experimental/svelte/js/static/.assetsignore new file mode 100644 index 000000000000..c2634cc3650a --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/svelte/js/static/.assetsignore @@ -0,0 +1,4 @@ +_worker.js +_routes.json +_headers +_redirects diff --git a/packages/create-cloudflare/templates-experimental/svelte/js/wrangler.toml b/packages/create-cloudflare/templates-experimental/svelte/js/wrangler.toml new file mode 100644 index 000000000000..b6cbc9e30304 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/svelte/js/wrangler.toml @@ -0,0 +1,5 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +main = ".svelte-kit/cloudflare/_worker.js" +experimental_assets = { directory = ".svelte-kit/cloudflare", binding = "ASSETS" } diff --git a/packages/create-cloudflare/templates-experimental/svelte/ts/static/.assetsignore b/packages/create-cloudflare/templates-experimental/svelte/ts/static/.assetsignore new file mode 100644 index 000000000000..c2634cc3650a --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/svelte/ts/static/.assetsignore @@ -0,0 +1,4 @@ +_worker.js +_routes.json +_headers +_redirects diff --git a/packages/create-cloudflare/templates-experimental/svelte/ts/wrangler.toml b/packages/create-cloudflare/templates-experimental/svelte/ts/wrangler.toml new file mode 100644 index 000000000000..b6cbc9e30304 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/svelte/ts/wrangler.toml @@ -0,0 +1,5 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +main = ".svelte-kit/cloudflare/_worker.js" +experimental_assets = { directory = ".svelte-kit/cloudflare", binding = "ASSETS" } From da5aa7ba0783e25559c7ec824cbb7182d6424f3d Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 15:46:45 +0100 Subject: [PATCH 06/17] feat: add experimental remix workers + assets template --- .../templates-experimental/remix/c3.ts | 76 +++++++++++++++++++ .../remix/templates/public/.assetsignore | 4 + .../remix/templates/worker-configuration.d.ts | 4 + .../remix/templates/wrangler.toml | 5 ++ 4 files changed, 89 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/remix/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/remix/templates/public/.assetsignore create mode 100644 packages/create-cloudflare/templates-experimental/remix/templates/worker-configuration.d.ts create mode 100644 packages/create-cloudflare/templates-experimental/remix/templates/wrangler.toml diff --git a/packages/create-cloudflare/templates-experimental/remix/c3.ts b/packages/create-cloudflare/templates-experimental/remix/c3.ts new file mode 100644 index 000000000000..c4d88261f375 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/remix/c3.ts @@ -0,0 +1,76 @@ +import { logRaw } from "@cloudflare/cli"; +import { brandColor, dim } from "@cloudflare/cli/colors"; +import { spinner } from "@cloudflare/cli/interactive"; +import { runFrameworkGenerator } from "frameworks/index"; +import { transformFile } from "helpers/codemod"; +import { detectPackageManager } from "helpers/packageManagers"; +import { installPackages } from "helpers/packages"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context } from "types"; + +const { npm } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + await runFrameworkGenerator(ctx, [ + ctx.project.name, + "--template", + "https://github.com/remix-run/remix/tree/main/templates/cloudflare", + ]); + + logRaw(""); // newline +}; + +const configure = async () => { + await installPackages(["wrangler@latest"], { + dev: true, + startText: "Updating the Wrangler version", + doneText: `${brandColor(`updateed`)} ${dim("wrangler@latest")}`, + }); + + const typeDefsPath = "load-context.ts"; + + const s = spinner(); + s.start(`Updating \`${typeDefsPath}\``); + + // Remove the empty Env declaration from the template to allow the type from + // worker-configuration.d.ts to take over + transformFile(typeDefsPath, { + visitTSInterfaceDeclaration(n) { + if (n.node.id.type === "Identifier" && n.node.id.name !== "Env") { + return this.traverse(n); + } + + // Removes the node + n.replace(); + return false; + }, + }); + + s.stop(`${brandColor("updated")} \`${dim(typeDefsPath)}\``); +}; + +const config: TemplateConfig = { + configVersion: 1, + id: "remix", + platform: "pages", + displayName: "Remix", + copyFiles: { + path: "./templates", + }, + path: "templates-experimental/remix", + generate, + configure, + transformPackageJson: async () => ({ + scripts: { + build: + "remix vite:build && wrangler pages functions build --outdir build/worker", + deploy: `${npm} run build && wrangler deploy`, + preview: `${npm} run build && wrangler dev`, + "cf-typegen": `wrangler types`, + }, + }), + devScript: "dev", + deployScript: "deploy", + previewScript: "preview", +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/remix/templates/public/.assetsignore b/packages/create-cloudflare/templates-experimental/remix/templates/public/.assetsignore new file mode 100644 index 000000000000..c2634cc3650a --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/remix/templates/public/.assetsignore @@ -0,0 +1,4 @@ +_worker.js +_routes.json +_headers +_redirects diff --git a/packages/create-cloudflare/templates-experimental/remix/templates/worker-configuration.d.ts b/packages/create-cloudflare/templates-experimental/remix/templates/worker-configuration.d.ts new file mode 100644 index 000000000000..5b2319b3f29f --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/remix/templates/worker-configuration.d.ts @@ -0,0 +1,4 @@ +// Generated by Wrangler +// After adding bindings to `wrangler.toml`, regenerate this interface via `npm run cf-typegen` +interface Env { +} diff --git a/packages/create-cloudflare/templates-experimental/remix/templates/wrangler.toml b/packages/create-cloudflare/templates-experimental/remix/templates/wrangler.toml new file mode 100644 index 000000000000..66763e80a4c3 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/remix/templates/wrangler.toml @@ -0,0 +1,5 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +main = "./build/worker/index.js" +experimental_assets = { directory = "./build/client" } From 68cfb8008c684fcac676041e2a421e4da26c20b9 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 15:46:57 +0100 Subject: [PATCH 07/17] feat: add experimental astro workers + assets template --- .../templates-experimental/astro/c3.ts | 113 ++++++++++++++++++ .../astro/snippets/runtimeDeclaration.ts | 5 + .../astro/templates/public/.assetsignore | 4 + .../astro/templates/wrangler.toml | 6 + 4 files changed, 128 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/astro/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/astro/snippets/runtimeDeclaration.ts create mode 100644 packages/create-cloudflare/templates-experimental/astro/templates/public/.assetsignore create mode 100644 packages/create-cloudflare/templates-experimental/astro/templates/wrangler.toml diff --git a/packages/create-cloudflare/templates-experimental/astro/c3.ts b/packages/create-cloudflare/templates-experimental/astro/c3.ts new file mode 100644 index 000000000000..a31e4497c8a2 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/astro/c3.ts @@ -0,0 +1,113 @@ +import { logRaw, updateStatus } from "@cloudflare/cli"; +import { blue, brandColor, dim } from "@cloudflare/cli/colors"; +import { runFrameworkGenerator } from "frameworks/index"; +import { loadTemplateSnippets, transformFile } from "helpers/codemod"; +import { runCommand } from "helpers/command"; +import { usesTypescript } from "helpers/files"; +import { detectPackageManager } from "helpers/packageManagers"; +import * as recast from "recast"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context, PackageJson } from "types"; + +const { npx } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + await runFrameworkGenerator(ctx, [ctx.project.name, "--no-install"]); + + logRaw(""); // newline +}; + +const configure = async (ctx: C3Context) => { + await runCommand([npx, "astro", "add", "cloudflare", "-y"], { + silent: true, + startText: "Installing adapter", + doneText: `${brandColor("installed")} ${dim( + `via \`${npx} astro add cloudflare\``, + )}`, + }); + + updateAstroConfig(); + updateEnvDeclaration(ctx); +}; + +const updateAstroConfig = () => { + const filePath = "astro.config.mjs"; + + updateStatus(`Updating configuration in ${blue(filePath)}`); + + transformFile(filePath, { + visitCallExpression: function (n) { + const callee = n.node.callee as recast.types.namedTypes.Identifier; + if (callee.name !== "cloudflare") { + return this.traverse(n); + } + + const b = recast.types.builders; + n.node.arguments = [ + b.objectExpression([ + b.objectProperty( + b.identifier("platformProxy"), + b.objectExpression([ + b.objectProperty(b.identifier("enabled"), b.booleanLiteral(true)), + ]), + ), + ]), + ]; + + return false; + }, + }); +}; + +const updateEnvDeclaration = (ctx: C3Context) => { + if (!usesTypescript(ctx)) { + return; + } + + const filePath = "src/env.d.ts"; + + updateStatus(`Adding type declarations in ${blue(filePath)}`); + + transformFile(filePath, { + visitProgram: function (n) { + const snippets = loadTemplateSnippets(ctx); + const patch = snippets.runtimeDeclarationTs; + const b = recast.types.builders; + + // Preserve comments with the new body + const comments = n.get("comments").value; + n.node.comments = comments.map((c: recast.types.namedTypes.CommentLine) => + b.commentLine(c.value), + ); + + // Add the patch + n.get("body").push(...patch); + + return false; + }, + }); +}; + +const config: TemplateConfig = { + configVersion: 1, + id: "astro", + platform: "pages", + displayName: "Astro", + copyFiles: { + path: "./templates", + }, + devScript: "dev", + deployScript: "deploy", + previewScript: "preview", + path: "templates-experimental/astro", + generate, + configure, + transformPackageJson: async (pkgJson: PackageJson, ctx: C3Context) => ({ + scripts: { + deploy: `astro build && wrangler deploy`, + preview: `astro build && wrangler dev`, + ...(usesTypescript(ctx) && { "cf-typegen": `wrangler types` }), + }, + }), +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/astro/snippets/runtimeDeclaration.ts b/packages/create-cloudflare/templates-experimental/astro/snippets/runtimeDeclaration.ts new file mode 100644 index 000000000000..328cb075b052 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/astro/snippets/runtimeDeclaration.ts @@ -0,0 +1,5 @@ +type Runtime = import("@astrojs/cloudflare").Runtime; + +declare namespace App { + interface Locals extends Runtime {} +} diff --git a/packages/create-cloudflare/templates-experimental/astro/templates/public/.assetsignore b/packages/create-cloudflare/templates-experimental/astro/templates/public/.assetsignore new file mode 100644 index 000000000000..c2634cc3650a --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/astro/templates/public/.assetsignore @@ -0,0 +1,4 @@ +_worker.js +_routes.json +_headers +_redirects diff --git a/packages/create-cloudflare/templates-experimental/astro/templates/wrangler.toml b/packages/create-cloudflare/templates-experimental/astro/templates/wrangler.toml new file mode 100644 index 000000000000..f0a68d0bb012 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/astro/templates/wrangler.toml @@ -0,0 +1,6 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +compatibility_flags = ["nodejs_compat_v2"] +main = "./dist/_worker.js" +experimental_assets = { directory = "./dist", binding = "ASSETS" } From 907cdacf5d0cd66c26a3537887e7864e2cb359ab Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 15:47:14 +0100 Subject: [PATCH 08/17] feat: add hello world with assets template --- .../hello-world-with-assets/c3.ts | 22 +++ .../hello-world-with-assets/js/.editorconfig | 12 ++ .../hello-world-with-assets/js/.gitignore | 172 ++++++++++++++++++ .../hello-world-with-assets/js/.prettierrc | 6 + .../js/__dot__gitignore | 172 ++++++++++++++++++ .../hello-world-with-assets/js/package.json | 16 ++ .../js/public/index.html | 19 ++ .../hello-world-with-assets/js/src/index.js | 15 ++ .../js/test/index.spec.js | 20 ++ .../js/vitest.config.js | 11 ++ .../hello-world-with-assets/js/wrangler.toml | 6 + .../hello-world-with-assets/py/.gitignore | 68 +++++++ .../py/__dot__gitignore | 68 +++++++ .../hello-world-with-assets/py/package.json | 13 ++ .../py/public/index.html | 19 ++ .../hello-world-with-assets/py/src/entry.py | 4 + .../hello-world-with-assets/py/wrangler.toml | 6 + .../hello-world-with-assets/ts/.editorconfig | 12 ++ .../hello-world-with-assets/ts/.gitignore | 172 ++++++++++++++++++ .../hello-world-with-assets/ts/.prettierrc | 6 + .../ts/__dot__gitignore | 172 ++++++++++++++++++ .../hello-world-with-assets/ts/package.json | 18 ++ .../ts/public/index.html | 19 ++ .../hello-world-with-assets/ts/src/index.ts | 18 ++ .../ts/test/index.spec.ts | 25 +++ .../ts/test/tsconfig.json | 8 + .../hello-world-with-assets/ts/tsconfig.json | 103 +++++++++++ .../ts/vitest.config.mts | 11 ++ .../ts/worker-configuration.d.ts | 4 + .../hello-world-with-assets/ts/wrangler.toml | 6 + 30 files changed, 1223 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.editorconfig create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.gitignore create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.prettierrc create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/__dot__gitignore create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/package.json create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/public/index.html create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/src/index.js create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/test/index.spec.js create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/vitest.config.js create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/wrangler.toml create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/.gitignore create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/__dot__gitignore create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/package.json create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/public/index.html create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/src/entry.py create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/wrangler.toml create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.editorconfig create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.gitignore create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.prettierrc create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/__dot__gitignore create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/package.json create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/public/index.html create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/src/index.ts create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/test/index.spec.ts create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/test/tsconfig.json create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/tsconfig.json create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/vitest.config.mts create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/worker-configuration.d.ts create mode 100644 packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/wrangler.toml diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/c3.ts b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/c3.ts new file mode 100644 index 000000000000..7ea37f837fda --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/c3.ts @@ -0,0 +1,22 @@ +export default { + configVersion: 1, + id: "hello-world-with-assets", + path: "templates-experimental/hello-world-with-assets", + displayName: "Hello World Worker with Assets", + description: + "Get started with a basic Worker that serves static assets, in the language of your choice", + platform: "workers", + copyFiles: { + variants: { + js: { + path: "./js", + }, + ts: { + path: "./ts", + }, + python: { + path: "./py", + }, + }, + }, +}; diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.editorconfig b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.editorconfig new file mode 100644 index 000000000000..a727df347a12 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yml] +indent_style = space diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.gitignore b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.gitignore new file mode 100644 index 000000000000..3b0fe33c47f1 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.gitignore @@ -0,0 +1,172 @@ +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# wrangler project + +.dev.vars +.wrangler/ diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.prettierrc b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.prettierrc new file mode 100644 index 000000000000..5c7b5d3c7a75 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 140, + "singleQuote": true, + "semi": true, + "useTabs": true +} diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/__dot__gitignore b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/__dot__gitignore new file mode 100644 index 000000000000..3b0fe33c47f1 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/__dot__gitignore @@ -0,0 +1,172 @@ +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# wrangler project + +.dev.vars +.wrangler/ diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/package.json b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/package.json new file mode 100644 index 000000000000..83655091a079 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/package.json @@ -0,0 +1,16 @@ +{ + "name": "", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler deploy", + "dev": "wrangler dev", + "start": "wrangler dev", + "test": "vitest" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.4.5", + "wrangler": "^3.60.3", + "vitest": "1.5.0" + } +} diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/public/index.html b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/public/index.html new file mode 100644 index 000000000000..eb30ad619868 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/public/index.html @@ -0,0 +1,19 @@ + + + + + + Hello, World! + + +

+ + + diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/src/index.js b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/src/index.js new file mode 100644 index 000000000000..de66b625906e --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/src/index.js @@ -0,0 +1,15 @@ +/** + * Welcome to Cloudflare Workers! This is your first worker. + * + * - Run `npm run dev` in your terminal to start a development server + * - Open a browser tab at http://localhost:8787/ to see your worker in action + * - Run `npm run deploy` to publish your worker + * + * Learn more at https://developers.cloudflare.com/workers/ + */ + +export default { + async fetch(request, env, ctx) { + return new Response('Hello World!'); + }, +}; diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/test/index.spec.js b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/test/index.spec.js new file mode 100644 index 000000000000..7d7e97eb6cda --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/test/index.spec.js @@ -0,0 +1,20 @@ +import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test'; +import { describe, it, expect } from 'vitest'; +import worker from '../src'; + +describe('Hello World worker', () => { + it('responds with Hello World! (unit style)', async () => { + const request = new Request('http://example.com'); + // Create an empty context to pass to `worker.fetch()`. + const ctx = createExecutionContext(); + const response = await worker.fetch(request, env, ctx); + // Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions + await waitOnExecutionContext(ctx); + expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); + }); + + it('responds with Hello World! (integration style)', async () => { + const response = await SELF.fetch(request, env, ctx); + expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); + }); +}); diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/vitest.config.js b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/vitest.config.js new file mode 100644 index 000000000000..503a717466ba --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/vitest.config.js @@ -0,0 +1,11 @@ +import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'; + +export default defineWorkersConfig({ + test: { + poolOptions: { + workers: { + wrangler: { configPath: './wrangler.toml' }, + }, + }, + }, +}); diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/wrangler.toml b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/wrangler.toml new file mode 100644 index 000000000000..a3da42fceec8 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/js/wrangler.toml @@ -0,0 +1,6 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +main = "src/index.js" +compatibility_date = "" +compatibility_flags = ["nodejs_compat"] +experimental_assets = { directory = "./public", binding = "ASSETS" } diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/.gitignore b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/.gitignore new file mode 100644 index 000000000000..5a6ee479c982 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/.gitignore @@ -0,0 +1,68 @@ +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Dependency directories + +node_modules/ +jspm_packages/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# public + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# wrangler project + +.dev.vars +.wrangler/ diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/__dot__gitignore b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/__dot__gitignore new file mode 100644 index 000000000000..5a6ee479c982 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/__dot__gitignore @@ -0,0 +1,68 @@ +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Dependency directories + +node_modules/ +jspm_packages/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# public + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# wrangler project + +.dev.vars +.wrangler/ diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/package.json b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/package.json new file mode 100644 index 000000000000..dae933856c82 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/package.json @@ -0,0 +1,13 @@ +{ + "name": "", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler deploy", + "dev": "wrangler dev", + "start": "wrangler dev" + }, + "devDependencies": { + "wrangler": "^3.60.3" + } +} diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/public/index.html b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/public/index.html new file mode 100644 index 000000000000..88fb9cfceda2 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/public/index.html @@ -0,0 +1,19 @@ + + + + + + Hello, World! + + +

+ + + diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/src/entry.py b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/src/entry.py new file mode 100644 index 000000000000..217d57544bc0 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/src/entry.py @@ -0,0 +1,4 @@ +from js import Response + +async def on_fetch(request, env): + return Response.new("Hello World!") diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/wrangler.toml b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/wrangler.toml new file mode 100644 index 000000000000..95c1777d9075 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/py/wrangler.toml @@ -0,0 +1,6 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +main = "src/entry.py" +compatibility_flags = ["python_workers"] +compatibility_date = "" +experimental_assets = { directory = "./public", binding = "ASSETS" } diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.editorconfig b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.editorconfig new file mode 100644 index 000000000000..a727df347a12 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yml] +indent_style = space diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.gitignore b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.gitignore new file mode 100644 index 000000000000..3b0fe33c47f1 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.gitignore @@ -0,0 +1,172 @@ +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# wrangler project + +.dev.vars +.wrangler/ diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.prettierrc b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.prettierrc new file mode 100644 index 000000000000..5c7b5d3c7a75 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 140, + "singleQuote": true, + "semi": true, + "useTabs": true +} diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/__dot__gitignore b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/__dot__gitignore new file mode 100644 index 000000000000..3b0fe33c47f1 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/__dot__gitignore @@ -0,0 +1,172 @@ +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# wrangler project + +.dev.vars +.wrangler/ diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/package.json b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/package.json new file mode 100644 index 000000000000..f90e235be2a8 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/package.json @@ -0,0 +1,18 @@ +{ + "name": "", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler deploy", + "dev": "wrangler dev", + "start": "wrangler dev", + "test": "vitest", + "cf-typegen": "wrangler types" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.4.5", + "typescript": "^5.5.2", + "vitest": "1.5.0", + "wrangler": "^3.60.3" + } +} diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/public/index.html b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/public/index.html new file mode 100644 index 000000000000..eb30ad619868 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/public/index.html @@ -0,0 +1,19 @@ + + + + + + Hello, World! + + +

+ + + diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/src/index.ts b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/src/index.ts new file mode 100644 index 000000000000..a7eeabf866bb --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/src/index.ts @@ -0,0 +1,18 @@ +/** + * Welcome to Cloudflare Workers! This is your first worker. + * + * - Run `npm run dev` in your terminal to start a development server + * - Open a browser tab at http://localhost:8787/ to see your worker in action + * - Run `npm run deploy` to publish your worker + * + * Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the + * `Env` object can be regenerated with `npm run cf-typegen`. + * + * Learn more at https://developers.cloudflare.com/workers/ + */ + +export default { + async fetch(_request, _env, _ctx): Promise { + return new Response('Hello World!'); + }, +} satisfies ExportedHandler; diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/test/index.spec.ts b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/test/index.spec.ts new file mode 100644 index 000000000000..fbee335d71cd --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/test/index.spec.ts @@ -0,0 +1,25 @@ +// test/index.spec.ts +import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test'; +import { describe, it, expect } from 'vitest'; +import worker from '../src/index'; + +// For now, you'll need to do something like this to get a correctly-typed +// `Request` to pass to `worker.fetch()`. +const IncomingRequest = Request; + +describe('Hello World worker', () => { + it('responds with Hello World! (unit style)', async () => { + const request = new IncomingRequest('http://example.com'); + // Create an empty context to pass to `worker.fetch()`. + const ctx = createExecutionContext(); + const response = await worker.fetch(request, env, ctx); + // Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions + await waitOnExecutionContext(ctx); + expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); + }); + + it('responds with Hello World! (integration style)', async () => { + const response = await SELF.fetch('https://example.com'); + expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); + }); +}); diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/test/tsconfig.json b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/test/tsconfig.json new file mode 100644 index 000000000000..bc019a7e2bfb --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/test/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["@cloudflare/workers-types/experimental", "@cloudflare/vitest-pool-workers"] + }, + "include": ["./**/*.ts", "../src/env.d.ts"], + "exclude": [] +} diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/tsconfig.json b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/tsconfig.json new file mode 100644 index 000000000000..c1ac9a4ccbca --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/tsconfig.json @@ -0,0 +1,103 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": ["es2021"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + "jsx": "react-jsx" /* Specify what JSX code is generated. */, + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "es2022" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + "types": ["@cloudflare/workers-types"] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true /* Enable importing .json files */, + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, + "checkJs": false /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true /* Disable emitting files from a compilation. */, + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, + "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, + // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "exclude": ["test"], + "include": ["worker-configuration.d.ts", "src/**/*.ts"] +} diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/vitest.config.mts b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/vitest.config.mts new file mode 100644 index 000000000000..503a717466ba --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/vitest.config.mts @@ -0,0 +1,11 @@ +import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'; + +export default defineWorkersConfig({ + test: { + poolOptions: { + workers: { + wrangler: { configPath: './wrangler.toml' }, + }, + }, + }, +}); diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/worker-configuration.d.ts b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/worker-configuration.d.ts new file mode 100644 index 000000000000..5b2319b3f29f --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/worker-configuration.d.ts @@ -0,0 +1,4 @@ +// Generated by Wrangler +// After adding bindings to `wrangler.toml`, regenerate this interface via `npm run cf-typegen` +interface Env { +} diff --git a/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/wrangler.toml b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/wrangler.toml new file mode 100644 index 000000000000..c9415bf3ec94 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/hello-world-with-assets/ts/wrangler.toml @@ -0,0 +1,6 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +main = "src/index.ts" +compatibility_date = "" +compatibility_flags = ["nodejs_compat"] +experimental_assets = { directory = "./public", binding = "ASSETS" } From 210ada4758371d4a4335e11ee8448470c9b9736b Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 15:48:36 +0100 Subject: [PATCH 09/17] feat: add experimental solid workers + assets template --- .../templates-experimental/solid/c3.ts | 137 ++++++++++++++++++ .../solid/templates/wrangler.toml | 6 + 2 files changed, 143 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/solid/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/solid/templates/wrangler.toml diff --git a/packages/create-cloudflare/templates-experimental/solid/c3.ts b/packages/create-cloudflare/templates-experimental/solid/c3.ts new file mode 100644 index 000000000000..dc37014effd2 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/solid/c3.ts @@ -0,0 +1,137 @@ +import { logRaw, updateStatus } from "@cloudflare/cli"; +import { blue, brandColor, dim } from "@cloudflare/cli/colors"; +import { runFrameworkGenerator } from "frameworks/index"; +import { mergeObjectProperties, transformFile } from "helpers/codemod"; +import { usesTypescript } from "helpers/files"; +import { detectPackageManager } from "helpers/packageManagers"; +import { installPackages } from "helpers/packages"; +import * as recast from "recast"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context } from "types"; + +const { npm } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + // Run the create-solid command + // -s flag forces solid-start + await runFrameworkGenerator(ctx, ["-p", ctx.project.name, "-s"]); + + logRaw(""); +}; + +const configure = async (ctx: C3Context) => { + const packages = ["nitropack"]; + await installPackages(packages, { + dev: true, + startText: "Installing nitro module `nitropack`", + doneText: `${brandColor("installed")} ${dim(`via \`${npm} install\``)}`, + }); + + usesTypescript(ctx); + const filePath = `app.config.${usesTypescript(ctx) ? "ts" : "js"}`; + + updateStatus(`Updating configuration in ${blue(filePath)}`); + + transformFile(filePath, { + visitCallExpression: function (n) { + const callee = n.node.callee as recast.types.namedTypes.Identifier; + if (callee.name !== "defineConfig") { + return this.traverse(n); + } + + const b = recast.types.builders; + debugger; + mergeObjectProperties( + n.node.arguments[0] as recast.types.namedTypes.ObjectExpression, + [ + b.objectProperty( + b.identifier("server"), + b.objectExpression([ + // preset: "cloudflare-pages" + b.objectProperty( + b.identifier("preset"), + b.stringLiteral("./cloudflare-pages"), + ), + // output: { + // dir: "{{ rootDir }}/dist", + // publicDir: "{{ output.dir }}/public", + // serverDir: "{{ output.dir }}/worker", + // }, + b.objectProperty( + b.identifier("output"), + b.objectExpression([ + b.objectProperty( + b.identifier("dir"), + b.stringLiteral("{{ rootDir }}/dist"), + ), + b.objectProperty( + b.identifier("publicDir"), + b.stringLiteral("{{ output.dir }}/public"), + ), + b.objectProperty( + b.identifier("serverDir"), + b.stringLiteral("{{ output.dir }}/worker"), + ), + ]), + ), + // rollupConfig: { + // external: ["node:async_hooks"], + // }, + b.objectProperty( + b.identifier("rollupConfig"), + b.objectExpression([ + b.objectProperty( + b.identifier("external"), + b.arrayExpression([b.stringLiteral("node:async_hooks")]), + ), + ]), + ), + // hooks: { + // // Prevent the Pages preset from writing the _routes.json etc. + // compiled() {}, + // }, + b.objectProperty( + b.identifier("hooks"), + b.objectExpression([ + b.objectMethod( + "method", + b.identifier("compiled"), + [], + b.blockStatement([]), + false, + ), + ]), + ), + ]), + ), + ], + ); + + return false; + }, + }); +}; + +const config: TemplateConfig = { + configVersion: 1, + id: "solid", + displayName: "Solid", + platform: "pages", + copyFiles: { + path: "./templates", + }, + path: "templates-experimental/solid", + generate, + configure, + transformPackageJson: async () => ({ + scripts: { + preview: `${npm} run build && npx wrangler dev`, + deploy: `${npm} run build && wrangler deploy`, + }, + }), + compatibilityFlags: ["nodejs_compat"], + devScript: "dev", + deployScript: "deploy", + previewScript: "preview", +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/solid/templates/wrangler.toml b/packages/create-cloudflare/templates-experimental/solid/templates/wrangler.toml new file mode 100644 index 000000000000..52347fd94c21 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/solid/templates/wrangler.toml @@ -0,0 +1,6 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +compatibility_flags = ["nodejs_compat"] +main = "./dist/worker/index.js" +experimental_assets = { directory = "./dist/public", binding = "ASSETS" } From 59a59ca0f2a75a99ff319b683e9eb48e5f68f3e6 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 16:05:42 +0100 Subject: [PATCH 10/17] feat: add experimental nuxt workers + assets template --- .../templates-experimental/nuxt/c3.ts | 134 ++++++++++++++++++ .../cloudflare-preset/nitro.config.ts | 27 ++++ .../nuxt/templates/env.d.ts | 14 ++ .../nuxt/templates/worker-configuration.d.ts | 4 + .../nuxt/templates/wrangler.toml | 5 + 5 files changed, 184 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/nuxt/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/nuxt/templates/cloudflare-preset/nitro.config.ts create mode 100644 packages/create-cloudflare/templates-experimental/nuxt/templates/env.d.ts create mode 100644 packages/create-cloudflare/templates-experimental/nuxt/templates/worker-configuration.d.ts create mode 100644 packages/create-cloudflare/templates-experimental/nuxt/templates/wrangler.toml diff --git a/packages/create-cloudflare/templates-experimental/nuxt/c3.ts b/packages/create-cloudflare/templates-experimental/nuxt/c3.ts new file mode 100644 index 000000000000..6b489b17f4ca --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/nuxt/c3.ts @@ -0,0 +1,134 @@ +import { logRaw } from "@cloudflare/cli"; +import { brandColor, dim } from "@cloudflare/cli/colors"; +import { spinner } from "@cloudflare/cli/interactive"; +import { runFrameworkGenerator } from "frameworks/index"; +import { mergeObjectProperties, transformFile } from "helpers/codemod"; +import { getLatestTypesEntrypoint } from "helpers/compatDate"; +import { readFile, writeFile } from "helpers/files"; +import { detectPackageManager } from "helpers/packageManagers"; +import { installPackages } from "helpers/packages"; +import * as recast from "recast"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context } from "types"; + +const { npm, name: pm } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + const gitFlag = ctx.args.git ? `--gitInit` : `--no-gitInit`; + + await runFrameworkGenerator(ctx, [ + "init", + ctx.project.name, + "--packageManager", + npm, + gitFlag, + ]); + + writeFile("./.node-version", "17"); + + logRaw(""); // newline +}; + +const configure = async (ctx: C3Context) => { + const packages = ["nitro-cloudflare-dev", "nitropack"]; + + // When using pnpm, explicitly add h3 package so the H3Event type declaration can be updated. + // Package managers other than pnpm will hoist the dependency, as will pnpm with `--shamefully-hoist` + if (pm === "pnpm") { + packages.push("h3"); + } + + await installPackages(packages, { + dev: true, + startText: "Installing nitro module `nitro-cloudflare-dev`", + doneText: `${brandColor("installed")} ${dim(`via \`${npm} install\``)}`, + }); + updateNuxtConfig(); + + updateEnvTypes(ctx); +}; + +const updateEnvTypes = (ctx: C3Context) => { + const filepath = "env.d.ts"; + + const s = spinner(); + s.start(`Updating ${filepath}`); + + let file = readFile(filepath); + + let typesEntrypoint = `@cloudflare/workers-types`; + const latestEntrypoint = getLatestTypesEntrypoint(ctx); + if (latestEntrypoint) { + typesEntrypoint += `/${latestEntrypoint}`; + } + + // Replace placeholder with actual types entrypoint + file = file.replace("WORKERS_TYPES_ENTRYPOINT", typesEntrypoint); + writeFile("env.d.ts", file); + + s.stop(`${brandColor(`updated`)} ${dim(`\`${filepath}\``)}`); +}; + +const updateNuxtConfig = () => { + const s = spinner(); + + 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-preset"), + ), + ]), + ); + + const moduleDef = b.objectProperty( + b.identifier("modules"), + b.arrayExpression([b.stringLiteral("nitro-cloudflare-dev")]), + ); + + transformFile(configFile, { + visitCallExpression: function (n) { + const callee = n.node.callee as recast.types.namedTypes.Identifier; + if (callee.name === "defineNuxtConfig") { + mergeObjectProperties( + n.node.arguments[0] as recast.types.namedTypes.ObjectExpression, + [presetDef, moduleDef], + ); + } + + return this.traverse(n); + }, + }); + + s.stop(`${brandColor(`updated`)} ${dim(`\`${configFile}\``)}`); +}; + +const config: TemplateConfig = { + configVersion: 1, + id: "nuxt", + platform: "pages", + displayName: "Nuxt", + copyFiles: { + path: "./templates", + }, + path: "templates-experimental/nuxt", + generate, + configure, + transformPackageJson: async () => ({ + scripts: { + deploy: `${npm} run build && wrangler deploy`, + preview: `${npm} run build && wrangler dev`, + "cf-typegen": `wrangler types`, + }, + }), + devScript: "dev", + deployScript: "deploy", + previewScript: "preview", +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/nuxt/templates/cloudflare-preset/nitro.config.ts b/packages/create-cloudflare/templates-experimental/nuxt/templates/cloudflare-preset/nitro.config.ts new file mode 100644 index 000000000000..3f7cd3ae3504 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/nuxt/templates/cloudflare-preset/nitro.config.ts @@ -0,0 +1,27 @@ +import { type NitroPreset } from "nitropack"; + +export default { + extends: "cloudflare", + exportConditions: ["workerd"], + output: { + dir: "{{ rootDir }}/dist", + publicDir: "{{ output.dir }}/public", + serverDir: "{{ output.dir }}/worker", + }, + commands: { + preview: "npx wrangler dev", + deploy: "npx wrangler deploy", + }, + wasm: { + lazy: false, + esmImport: true, + }, + rollupConfig: { + output: { + entryFileNames: "index.js", + format: "esm", + exports: "named", + inlineDynamicImports: false, + }, + }, +}; diff --git a/packages/create-cloudflare/templates-experimental/nuxt/templates/env.d.ts b/packages/create-cloudflare/templates-experimental/nuxt/templates/env.d.ts new file mode 100644 index 000000000000..dda21a4ab105 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/nuxt/templates/env.d.ts @@ -0,0 +1,14 @@ +/// + +declare module "h3" { + interface H3EventContext { + cf: CfProperties; + cloudflare: { + request: Request; + env: Env; + context: ExecutionContext; + }; + } +} + +export {}; diff --git a/packages/create-cloudflare/templates-experimental/nuxt/templates/worker-configuration.d.ts b/packages/create-cloudflare/templates-experimental/nuxt/templates/worker-configuration.d.ts new file mode 100644 index 000000000000..5b2319b3f29f --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/nuxt/templates/worker-configuration.d.ts @@ -0,0 +1,4 @@ +// Generated by Wrangler +// After adding bindings to `wrangler.toml`, regenerate this interface via `npm run cf-typegen` +interface Env { +} diff --git a/packages/create-cloudflare/templates-experimental/nuxt/templates/wrangler.toml b/packages/create-cloudflare/templates-experimental/nuxt/templates/wrangler.toml new file mode 100644 index 000000000000..b1203c74c4c5 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/nuxt/templates/wrangler.toml @@ -0,0 +1,5 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +main = "./dist/worker/index.js" +experimental_assets = { directory = "./dist/public", binding = "ASSETS" } From 343416f415438a287a73cc76ce6426a2abd9fd7e Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 19:59:11 +0100 Subject: [PATCH 11/17] feat: filter the C3 help info when in experimental mode --- .../e2e-tests/frameworks.test.ts | 12 +- packages/create-cloudflare/src/help.ts | 26 ++- .../create-cloudflare/src/helpers/args.ts | 163 ++++++++++-------- packages/create-cloudflare/src/templates.ts | 33 ++-- 4 files changed, 134 insertions(+), 100 deletions(-) diff --git a/packages/create-cloudflare/e2e-tests/frameworks.test.ts b/packages/create-cloudflare/e2e-tests/frameworks.test.ts index 47b0c3b40834..3b484f5cffcc 100644 --- a/packages/create-cloudflare/e2e-tests/frameworks.test.ts +++ b/packages/create-cloudflare/e2e-tests/frameworks.test.ts @@ -31,7 +31,7 @@ import { testProjectDir, waitForExit, } from "./helpers"; -import type { FrameworkMap, FrameworkName } from "../src/templates"; +import type { TemplateMap } from "../src/templates"; import type { RunnerConfig } from "./helpers"; import type { WriteStream } from "fs"; import type { Suite } from "vitest"; @@ -368,11 +368,11 @@ const frameworkTests: Record = { }; describe.concurrent(`E2E: Web frameworks`, () => { - let frameworkMap: FrameworkMap; + let frameworkMap: TemplateMap; let logStream: WriteStream; beforeAll(async (ctx) => { - frameworkMap = await getFrameworkMap(); + frameworkMap = await getFrameworkMap({ experimental: false }); recreateLogFolder(ctx as Suite); recreateDiffsFolder(); }); @@ -409,7 +409,7 @@ describe.concurrent(`E2E: Web frameworks`, () => { const { getPath, getName, clean } = testProjectDir("pages"); const projectPath = getPath(framework); const projectName = getName(framework); - const frameworkConfig = frameworkMap[framework as FrameworkName]; + const frameworkConfig = frameworkMap[framework]; const { promptHandlers, verifyDeploy, flags } = frameworkTests[framework]; @@ -575,8 +575,8 @@ const verifyDevScript = async ( return; } - const frameworkMap = await getFrameworkMap(); - const template = frameworkMap[framework as FrameworkName]; + const frameworkMap = await getFrameworkMap({ experimental: false }); + const template = frameworkMap[framework]; // Run the devserver on a random port to avoid colliding with other tests const TEST_PORT = Math.ceil(Math.random() * 1000) + 20000; diff --git a/packages/create-cloudflare/src/help.ts b/packages/create-cloudflare/src/help.ts index fce24aa0434b..b8c55e8e4560 100644 --- a/packages/create-cloudflare/src/help.ts +++ b/packages/create-cloudflare/src/help.ts @@ -1,5 +1,5 @@ import { logRaw } from "@cloudflare/cli"; -import { bold, brandColor, dim } from "@cloudflare/cli/colors"; +import { blue, bold, brandColor, dim } from "@cloudflare/cli/colors"; import { detectPackageManager } from "helpers/packageManagers"; import indentString from "indent-string"; import wrap from "wrap-ansi"; @@ -10,15 +10,15 @@ import type { ArgumentsDefinition, OptionDefinition, } from "helpers/args"; +import type { C3Args } from "types"; const MAX_WIDTH = 100; const PADDING_RIGHT = 5; -export const showHelp = ({ - positionals, - options, - intro, -}: ArgumentsDefinition) => { +export const showHelp = ( + args: C3Args | null, + { positionals, options, intro }: ArgumentsDefinition, +) => { const { name: pm } = detectPackageManager(); logRaw(`${brandColor("create-cloudflare")} ${dim("v" + version)}\n`); @@ -31,8 +31,16 @@ export const showHelp = ({ logRaw(bold("OPTIONS\n")); + if (args?.experimental) { + logRaw( + blue( + "You have selected experimental mode - the options below are filtered to those that support experimental mode.\n", + ), + ); + } + renderPositionals(positionals); - renderOptions(options); + renderOptions(args, options); }; /** @@ -61,7 +69,7 @@ const renderPositionals = (positionals?: ArgDefinition[]) => { } }; -const renderOptions = (options?: OptionDefinition[]) => { +const renderOptions = (args: C3Args | null, options?: OptionDefinition[]) => { if (!options) { return; } @@ -82,7 +90,7 @@ const renderOptions = (options?: OptionDefinition[]) => { indent(heading, 1); indent(`${description.trim()}\n`, 2); - renderValues(values); + renderValues(typeof values === "function" ? values(args) : values); } }; diff --git a/packages/create-cloudflare/src/helpers/args.ts b/packages/create-cloudflare/src/helpers/args.ts index 050223a825dc..02f9f35d2c1e 100644 --- a/packages/create-cloudflare/src/helpers/args.ts +++ b/packages/create-cloudflare/src/helpers/args.ts @@ -2,6 +2,11 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { version } from "../../package.json"; import { showHelp } from "../help"; +import { + getFrameworkMap, + getNamesAndDescriptions, + getTemplateMap, +} from "../templates"; import { C3_DEFAULTS, WRANGLER_DEFAULTS } from "./cli"; import type { C3Args } from "types"; import type { Argv } from "yargs"; @@ -18,7 +23,9 @@ export type ArgDefinition = { export type OptionDefinition = { alias?: string; footer?: string; - values?: AllowedValueDefinition[]; + values?: + | AllowedValueDefinition[] + | ((args: C3Args | null) => Promise); } & ArgDefinition; export type AllowedValueDefinition = { @@ -46,16 +53,36 @@ const cliDefinition: ArgumentsDefinition = { }, ], options: [ + { + name: "experimental", + hidden: true, + type: "boolean", + description: "Select from experimental frameworks.", + default: false, + }, { name: "category", type: "string", description: `Specifies the kind of templates that should be created`, - values: [ - { name: "hello-world", description: "Hello World example" }, - { name: "web-framework", description: "Framework Starter" }, - { name: "demo", description: "Application Starter" }, - { name: "remote-template", description: "Template from a Github repo" }, - ], + async values(args) { + const experimental = Boolean(args?.["experimental"]); + if (experimental) { + return [ + { name: "hello-world", description: "Hello World example" }, + { name: "web-framework", description: "Framework Starter" }, + ]; + } else { + return [ + { name: "hello-world", description: "Hello World example" }, + { name: "web-framework", description: "Framework Starter" }, + { name: "demo", description: "Application Starter" }, + { + name: "remote-template", + description: "Template from a Github repo", + }, + ]; + } + }, }, { name: "type", @@ -67,41 +94,50 @@ const cliDefinition: ArgumentsDefinition = { Note that "--category" and "--template" are mutually exclusive options. If both are provided, "--category" will be used. `, - values: [ - { - name: "hello-world", - description: "A basic “Hello World” Cloudflare Worker.", - }, - { - name: "hello-world-durable-object", - description: - "A basic “Hello World” Cloudflare Worker with a Durable Worker.", - }, - { - name: "common", - description: - "A Cloudflare Worker which implements a common example of routing/proxying functionalities.", - }, - { - name: "scheduled", - description: - "A scheduled Cloudflare Worker (triggered via Cron Triggers).", - }, - { - name: "queues", - description: - "A Cloudflare Worker which is both a consumer and produced of Queues.", - }, - { - name: "openapi", - description: "A Worker implementing an OpenAPI REST endpoint.", - }, - { - name: "pre-existing", - description: - "Fetch a Worker initialized from the Cloudflare dashboard.", - }, - ], + async values(args) { + const experimental = Boolean(args?.["experimental"]); + if (experimental) { + return getNamesAndDescriptions( + await getTemplateMap({ experimental }), + ); + } else { + return [ + { + name: "hello-world", + description: "A basic “Hello World” Cloudflare Worker.", + }, + { + name: "hello-world-durable-object", + description: + "A basic “Hello World” Cloudflare Worker with a Durable Worker.", + }, + { + name: "common", + description: + "A Cloudflare Worker which implements a common example of routing/proxying functionalities.", + }, + { + name: "scheduled", + description: + "A scheduled Cloudflare Worker (triggered via Cron Triggers).", + }, + { + name: "queues", + description: + "A Cloudflare Worker which is both a consumer and produced of Queues.", + }, + { + name: "openapi", + description: "A Worker implementing an OpenAPI REST endpoint.", + }, + { + name: "pre-existing", + description: + "Fetch a Worker initialized from the Cloudflare dashboard.", + }, + ]; + } + }, }, { name: "framework", @@ -117,29 +153,12 @@ const cliDefinition: ArgumentsDefinition = { npm create cloudflare -- --framework next -- --ts pnpm create clouldfare --framework next -- --ts `, - values: [ - { name: "analog" }, - { name: "angular" }, - { name: "astro" }, - { name: "docusaurus" }, - { name: "gatsby" }, - { name: "hono" }, - { name: "next" }, - { name: "nuxt" }, - { name: "qwik" }, - { name: "react" }, - { name: "remix" }, - { name: "solid" }, - { name: "svelte" }, - { name: "vue" }, - ], - }, - { - name: "experimental", - hidden: true, - type: "boolean", - description: "Select from experimental frameworks.", - default: false, + values: async (args) => + getNamesAndDescriptions( + await getFrameworkMap({ + experimental: Boolean(args?.["experimental"]), + }), + ), }, { name: "lang", @@ -253,7 +272,11 @@ export const parseArgs = async (argv: string[]): Promise> => { if (options) { for (const { name, alias, ...props } of options) { - yargsObj.option(name, props); + const values = + typeof props.values === "function" + ? await props.values(await yargsObj.argv) + : props.values; + yargsObj.option(name, { values, ...props }); if (alias) { yargsObj.alias(alias, name); } @@ -267,7 +290,7 @@ export const parseArgs = async (argv: string[]): Promise> => { } catch {} if (args === null) { - showHelp(cliDefinition); + showHelp(args, cliDefinition); process.exit(1); } @@ -276,7 +299,7 @@ export const parseArgs = async (argv: string[]): Promise> => { } if (args.help) { - showHelp(cliDefinition); + showHelp(args, cliDefinition); process.exit(0); } @@ -284,7 +307,7 @@ export const parseArgs = async (argv: string[]): Promise> => { for (const opt in args) { if (!validOption(opt)) { - showHelp(cliDefinition); + showHelp(args, cliDefinition); console.error(`\nUnrecognized option: ${opt}`); process.exit(1); } @@ -292,7 +315,7 @@ export const parseArgs = async (argv: string[]): Promise> => { // since `yargs.strict()` can't check the `positional`s for us we need to do it manually ourselves if (positionalArgs.length > 1) { - showHelp(cliDefinition); + showHelp(args, cliDefinition); console.error("\nToo many positional arguments provided"); process.exit(1); } diff --git a/packages/create-cloudflare/src/templates.ts b/packages/create-cloudflare/src/templates.ts index 13fcd536418b..a7cb9127ab2d 100644 --- a/packages/create-cloudflare/src/templates.ts +++ b/packages/create-cloudflare/src/templates.ts @@ -121,10 +121,11 @@ const defaultSelectVariant = async (ctx: C3Context) => { return ctx.args.lang; }; -export type FrameworkMap = Awaited>; -export type FrameworkName = keyof FrameworkMap; +export type TemplateMap = Record; -export const getFrameworkMap = async ({ experimental = false }) => { +export async function getFrameworkMap({ + experimental = false, +}): Promise { if (experimental) { return { astro: (await import("../templates-experimental/astro/c3")).default, @@ -152,7 +153,7 @@ export const getFrameworkMap = async ({ experimental = false }) => { vue: (await import("../templates/vue/c3")).default, }; } -}; +} export const getTemplateMap = async ({ experimental = false }) => { if (experimental) { @@ -176,6 +177,12 @@ export const getTemplateMap = async ({ experimental = false }) => { } }; +export function getNamesAndDescriptions(templateMap: TemplateMap) { + return Array.from(Object.entries(templateMap)).map( + ([name, { description }]) => ({ name, description }), + ); +} + export const deriveCorrelatedArgs = (args: Partial) => { // Derive the type based on the additional arguments provided // Both `web-framework` and `remote-template` types are no longer used @@ -345,17 +352,13 @@ export const createContext = async ( }), ); - const framework = await processArgument( - args, - "framework", - { - type: "select", - label: "framework", - question: "Which development framework do you want to use?", - options: frameworkOptions.concat(backOption), - defaultValue: prevArgs?.framework ?? C3_DEFAULTS.framework, - }, - ); + const framework = await processArgument(args, "framework", { + type: "select", + label: "framework", + question: "Which development framework do you want to use?", + options: frameworkOptions.concat(backOption), + defaultValue: prevArgs?.framework ?? C3_DEFAULTS.framework, + }); if (framework === BACK_VALUE) { return goBack("framework"); From bf1267f963e5e2a3a97ac24de90db6e715b5141a Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 22:24:33 +0100 Subject: [PATCH 12/17] fix: mark new Workers + Assets experimental templates as "workers" platform This avoids C3 from trying to create a Pages project for them. Needed a bit of refactoring to ensure that these new templates still work even if not marked as "pages". --- .../create-cloudflare/e2e-tests/cli.test.ts | 4 +- .../e2e-tests/frameworkToTest.ts | 37 +++---- .../e2e-tests/frameworks.test.ts | 7 +- .../e2e-tests/workers.test.ts | 8 +- .../create-cloudflare/src/frameworks/index.ts | 11 +- .../src/frameworks/package.json | 23 +--- .../src/frameworks/tsconfig.json | 3 + packages/create-cloudflare/src/git.ts | 10 +- .../create-cloudflare/src/helpers/args.ts | 14 ++- packages/create-cloudflare/src/templates.ts | 100 +++++++++++------- .../templates-experimental/astro/c3.ts | 3 +- .../templates-experimental/nuxt/c3.ts | 3 +- .../templates-experimental/qwik/c3.ts | 3 +- .../templates-experimental/remix/c3.ts | 3 +- .../templates-experimental/solid/c3.ts | 4 +- .../templates-experimental/svelte/c3.ts | 3 +- .../create-cloudflare/templates/analog/c3.ts | 1 + .../create-cloudflare/templates/angular/c3.ts | 1 + .../create-cloudflare/templates/astro/c3.ts | 1 + .../templates/docusaurus/c3.ts | 1 + .../create-cloudflare/templates/gatsby/c3.ts | 1 + .../create-cloudflare/templates/hono/c3.ts | 1 + .../create-cloudflare/templates/next/c3.ts | 1 + .../create-cloudflare/templates/nuxt/c3.ts | 1 + .../create-cloudflare/templates/qwik/c3.ts | 1 + .../create-cloudflare/templates/react/c3.ts | 2 + .../create-cloudflare/templates/remix/c3.ts | 1 + .../create-cloudflare/templates/solid/c3.ts | 1 + .../create-cloudflare/templates/svelte/c3.ts | 1 + .../create-cloudflare/templates/vue/c3.ts | 1 + 30 files changed, 139 insertions(+), 112 deletions(-) create mode 100644 packages/create-cloudflare/src/frameworks/tsconfig.json diff --git a/packages/create-cloudflare/e2e-tests/cli.test.ts b/packages/create-cloudflare/e2e-tests/cli.test.ts index 17812cf6bbc5..158e7991365c 100644 --- a/packages/create-cloudflare/e2e-tests/cli.test.ts +++ b/packages/create-cloudflare/e2e-tests/cli.test.ts @@ -10,7 +10,7 @@ import { test, } from "vitest"; import { version } from "../package.json"; -import { frameworkToTest } from "./frameworkToTest"; +import { getFrameworkToTest } from "./frameworkToTest"; import { createTestLogStream, isQuarantineMode, @@ -21,6 +21,8 @@ import { import type { WriteStream } from "fs"; import type { Suite } from "vitest"; +const frameworkToTest = getFrameworkToTest({ experimental: false }); + // Note: skipIf(frameworkToTest) makes it so that all the basic C3 functionality // tests are skipped in case we are testing a specific framework describe.skipIf(frameworkToTest || isQuarantineMode())( diff --git a/packages/create-cloudflare/e2e-tests/frameworkToTest.ts b/packages/create-cloudflare/e2e-tests/frameworkToTest.ts index 8b800a924a0f..9d2ad2a688eb 100644 --- a/packages/create-cloudflare/e2e-tests/frameworkToTest.ts +++ b/packages/create-cloudflare/e2e-tests/frameworkToTest.ts @@ -1,25 +1,22 @@ -import { frameworkCliMap } from "../src/frameworks/package.json"; +import { getFrameworkMap } from "../src/templates"; -let targetFramework = undefined; - -const envCliToTest = process.env.FRAMEWORK_CLI_TO_TEST; +/** + * Get the name of the framework to test or undefined if not focussing on a single framework. + */ +export function getFrameworkToTest({ experimental = false }) { + const envCliToTest = process.env.FRAMEWORK_CLI_TO_TEST; + if (!envCliToTest) { + return undefined; + } -if (envCliToTest) { - for (const [framework, cli] of Object.entries(frameworkCliMap)) { - if (cli === envCliToTest) { - targetFramework = framework; + const frameworks = getFrameworkMap({ experimental }); + for (const [framework, { frameworkCli }] of Object.entries(frameworks)) { + if (frameworkCli === envCliToTest) { + return framework; } } - if (!targetFramework) { - throw new Error( - `Specified cli doesn't exist in framework map: ${envCliToTest}`, - ); - } -} -/** - * In case the e2e run is supposed to only test a single framework - * the framework's name is set as the value of this variable, for standard - * runs this variable's value is `undefined` - */ -export const frameworkToTest = targetFramework; + throw new Error( + `Specified cli doesn't exist in framework map: ${envCliToTest}`, + ); +} diff --git a/packages/create-cloudflare/e2e-tests/frameworks.test.ts b/packages/create-cloudflare/e2e-tests/frameworks.test.ts index 3b484f5cffcc..5e81b21fef42 100644 --- a/packages/create-cloudflare/e2e-tests/frameworks.test.ts +++ b/packages/create-cloudflare/e2e-tests/frameworks.test.ts @@ -17,7 +17,7 @@ import { } from "vitest"; import { deleteProject, deleteWorker } from "../scripts/common"; import { getFrameworkMap } from "../src/templates"; -import { frameworkToTest } from "./frameworkToTest"; +import { getFrameworkToTest } from "./frameworkToTest"; import { createTestLogStream, getDiffsPath, @@ -372,7 +372,7 @@ describe.concurrent(`E2E: Web frameworks`, () => { let logStream: WriteStream; beforeAll(async (ctx) => { - frameworkMap = await getFrameworkMap({ experimental: false }); + frameworkMap = getFrameworkMap({ experimental: false }); recreateLogFolder(ctx as Suite); recreateDiffsFolder(); }); @@ -394,6 +394,7 @@ describe.concurrent(`E2E: Web frameworks`, () => { // If the framework in question is being run in isolation, always run it. // Otherwise, only run the test if it's configured `quarantine` value matches // what is set in E2E_QUARANTINE + const frameworkToTest = getFrameworkToTest({ experimental: false }); let shouldRun = frameworkToTest ? frameworkToTest === framework : quarantineModeMatch; @@ -575,7 +576,7 @@ const verifyDevScript = async ( return; } - const frameworkMap = await getFrameworkMap({ experimental: false }); + const frameworkMap = getFrameworkMap({ experimental: false }); const template = frameworkMap[framework]; // Run the devserver on a random port to avoid colliding with other tests diff --git a/packages/create-cloudflare/e2e-tests/workers.test.ts b/packages/create-cloudflare/e2e-tests/workers.test.ts index 4b4ec7c6b708..62d180073966 100644 --- a/packages/create-cloudflare/e2e-tests/workers.test.ts +++ b/packages/create-cloudflare/e2e-tests/workers.test.ts @@ -5,7 +5,7 @@ import { sleep } from "helpers/sleep"; import { fetch } from "undici"; import { beforeAll, beforeEach, describe, expect, test } from "vitest"; import { deleteWorker } from "../scripts/common"; -import { frameworkToTest } from "./frameworkToTest"; +import { getFrameworkToTest } from "./frameworkToTest"; import { createTestLogStream, isQuarantineMode, @@ -63,7 +63,11 @@ const workerTemplates: WorkerTestConfig[] = [ ]; describe - .skipIf(frameworkToTest || isQuarantineMode() || process.platform === "win32") + .skipIf( + getFrameworkToTest({ experimental: false }) || + isQuarantineMode() || + process.platform === "win32", + ) .concurrent(`E2E: Workers templates`, () => { let logStream: WriteStream; diff --git a/packages/create-cloudflare/src/frameworks/index.ts b/packages/create-cloudflare/src/frameworks/index.ts index 453b9622a8d9..c9f9f5121b37 100644 --- a/packages/create-cloudflare/src/frameworks/index.ts +++ b/packages/create-cloudflare/src/frameworks/index.ts @@ -3,7 +3,7 @@ import { dim } from "@cloudflare/cli/colors"; import { quoteShellArgs, runCommand } from "helpers/command"; import { detectPackageManager } from "helpers/packageManagers"; import { isInsideGitRepo } from "../git"; -import clisPackageJson from "./package.json"; +import frameworksPackageJson from "./package.json"; import type { C3Context } from "types"; export const getFrameworkCli = (ctx: C3Context, withVersion = true) => { @@ -11,12 +11,9 @@ export const getFrameworkCli = (ctx: C3Context, withVersion = true) => { return crash("Framework not specified."); } - const framework = ctx.template - .id as keyof typeof clisPackageJson.frameworkCliMap; - const frameworkCli = clisPackageJson.frameworkCliMap[ - framework - ] as keyof typeof clisPackageJson.dependencies; - const version = clisPackageJson.dependencies[frameworkCli]; + const frameworkCli = ctx.template + .frameworkCli as keyof typeof frameworksPackageJson.dependencies; + const version = frameworksPackageJson.dependencies[frameworkCli]; return withVersion ? `${frameworkCli}@${version}` : frameworkCli; }; diff --git a/packages/create-cloudflare/src/frameworks/package.json b/packages/create-cloudflare/src/frameworks/package.json index c0c0e5ed10e0..98643f93be67 100644 --- a/packages/create-cloudflare/src/frameworks/package.json +++ b/packages/create-cloudflare/src/frameworks/package.json @@ -1,9 +1,8 @@ { "name": "frameworks_clis_info", - "description": [ - "this package.json is only used to keep track of the frameworks cli dependencies", - "so that we can use dependabot to update these dependencies automatically", - "additionally it also contains a map that maps frameworks to their respective clis" + "info": [ + "This package.json is only used to keep track of the frameworks cli dependencies", + "so that we can use dependabot to update these dependencies automatically." ], "dependencies": { "create-astro": "4.8.0", @@ -20,21 +19,5 @@ "create-vue": "3.10.4", "gatsby": "5.13.7", "nuxi": "3.12.0" - }, - "frameworkCliMap": { - "analog": "create-analog", - "angular": "@angular/create", - "astro": "create-astro", - "docusaurus": "create-docusaurus", - "gatsby": "gatsby", - "hono": "create-hono", - "next": "create-next-app", - "nuxt": "nuxi", - "qwik": "create-qwik", - "react": "create-vite", - "remix": "create-remix", - "solid": "create-solid", - "svelte": "create-svelte", - "vue": "create-vue" } } diff --git a/packages/create-cloudflare/src/frameworks/tsconfig.json b/packages/create-cloudflare/src/frameworks/tsconfig.json new file mode 100644 index 000000000000..1864d58c1554 --- /dev/null +++ b/packages/create-cloudflare/src/frameworks/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/create-cloudflare/src/git.ts b/packages/create-cloudflare/src/git.ts index d7d801be64ad..2e8b46cdd66b 100644 --- a/packages/create-cloudflare/src/git.ts +++ b/packages/create-cloudflare/src/git.ts @@ -98,9 +98,9 @@ export const gitCommit = async (ctx: C3Context) => { }; const createCommitMessage = async (ctx: C3Context) => { - const isPages = ctx.template.platform === "pages"; + const framework = ctx.template.frameworkCli; - const header = isPages + const header = framework ? "Initialize web application via create-cloudflare CLI" : "Initial commit (by create-cloudflare CLI)"; @@ -109,13 +109,11 @@ const createCommitMessage = async (ctx: C3Context) => { const gitVersion = await getGitVersion(); const insideRepo = await isInsideGitRepo(ctx.project.path); - const showFramework = isPages || ctx.template.id === "hono"; - const details = [ { key: "C3", value: `create-cloudflare@${version}` }, { key: "project name", value: ctx.project.name }, - ...(showFramework ? [{ key: "framework", value: ctx.template.id }] : []), - ...(showFramework + ...(framework ? [{ key: "framework", value: ctx.template.id }] : []), + ...(framework ? [{ key: "framework cli", value: getFrameworkCli(ctx) }] : []), { diff --git a/packages/create-cloudflare/src/helpers/args.ts b/packages/create-cloudflare/src/helpers/args.ts index 02f9f35d2c1e..5d433f9eeecf 100644 --- a/packages/create-cloudflare/src/helpers/args.ts +++ b/packages/create-cloudflare/src/helpers/args.ts @@ -25,7 +25,7 @@ export type OptionDefinition = { footer?: string; values?: | AllowedValueDefinition[] - | ((args: C3Args | null) => Promise); + | ((args: C3Args | null) => AllowedValueDefinition[]); } & ArgDefinition; export type AllowedValueDefinition = { @@ -64,7 +64,7 @@ const cliDefinition: ArgumentsDefinition = { name: "category", type: "string", description: `Specifies the kind of templates that should be created`, - async values(args) { + values(args) { const experimental = Boolean(args?.["experimental"]); if (experimental) { return [ @@ -94,12 +94,10 @@ const cliDefinition: ArgumentsDefinition = { Note that "--category" and "--template" are mutually exclusive options. If both are provided, "--category" will be used. `, - async values(args) { + values(args) { const experimental = Boolean(args?.["experimental"]); if (experimental) { - return getNamesAndDescriptions( - await getTemplateMap({ experimental }), - ); + return getNamesAndDescriptions(getTemplateMap({ experimental })); } else { return [ { @@ -153,9 +151,9 @@ const cliDefinition: ArgumentsDefinition = { npm create cloudflare -- --framework next -- --ts pnpm create clouldfare --framework next -- --ts `, - values: async (args) => + values: (args) => getNamesAndDescriptions( - await getFrameworkMap({ + getFrameworkMap({ experimental: Boolean(args?.["experimental"]), }), ), diff --git a/packages/create-cloudflare/src/templates.ts b/packages/create-cloudflare/src/templates.ts index a7cb9127ab2d..060e7f6229aa 100644 --- a/packages/create-cloudflare/src/templates.ts +++ b/packages/create-cloudflare/src/templates.ts @@ -18,6 +18,34 @@ import { writeFile, writeJSON, } from "helpers/files"; +import astroTemplateExperimental from "../templates-experimental/astro/c3"; +import helloWorldWithAssetsTemplateExperimental from "../templates-experimental/hello-world-with-assets/c3"; +import nuxtTemplateExperimental from "../templates-experimental/nuxt/c3"; +import qwikTemplateExperimental from "../templates-experimental/qwik/c3"; +import remixTemplateExperimental from "../templates-experimental/remix/c3"; +import solidTemplateExperimental from "../templates-experimental/solid/c3"; +import svelteTemplateExperimental from "../templates-experimental/svelte/c3"; +import analogTemplate from "../templates/analog/c3"; +import angularTemplate from "../templates/angular/c3"; +import astroTemplate from "../templates/astro/c3"; +import commonTemplate from "../templates/common/c3"; +import docusaurusTemplate from "../templates/docusaurus/c3"; +import gatsbyTemplate from "../templates/gatsby/c3"; +import helloWorldDurableObjectTemplate from "../templates/hello-world-durable-object/c3"; +import helloWorldTemplate from "../templates/hello-world/c3"; +import honoTemplate from "../templates/hono/c3"; +import nextTemplate from "../templates/next/c3"; +import nuxtTemplate from "../templates/nuxt/c3"; +import openapiTemplate from "../templates/openapi/c3"; +import preExistingTemplate from "../templates/pre-existing/c3"; +import queuesTemplate from "../templates/queues/c3"; +import qwikTemplate from "../templates/qwik/c3"; +import reactTemplate from "../templates/react/c3"; +import remixTemplate from "../templates/remix/c3"; +import scheduledTemplate from "../templates/scheduled/c3"; +import solidTemplate from "../templates/solid/c3"; +import svelteTemplate from "../templates/svelte/c3"; +import vueTemplate from "../templates/vue/c3"; import { isInsideGitRepo } from "./git"; import { validateProjectDirectory, validateTemplateUrl } from "./validators"; import type { Option } from "@cloudflare/cli/interactive"; @@ -37,6 +65,8 @@ export type TemplateConfig = { description?: string; /** The deployment platform for this template */ platform: "workers" | "pages"; + /** The name of the framework cli tool that is used to generate this project or undefined if none. */ + frameworkCli?: string; /** When set to true, hides this template from the selection menu */ hidden?: boolean; /** Specifies a set of files that will be copied to the project directory during creation. @@ -123,59 +153,53 @@ const defaultSelectVariant = async (ctx: C3Context) => { export type TemplateMap = Record; -export async function getFrameworkMap({ - experimental = false, -}): Promise { +export function getFrameworkMap({ experimental = false }): TemplateMap { if (experimental) { return { - astro: (await import("../templates-experimental/astro/c3")).default, - nuxt: (await import("../templates-experimental/nuxt/c3")).default, - qwik: (await import("../templates-experimental/qwik/c3")).default, - remix: (await import("../templates-experimental/remix/c3")).default, - solid: (await import("../templates-experimental/solid/c3")).default, - svelte: (await import("../templates-experimental/svelte/c3")).default, + astro: astroTemplateExperimental, + nuxt: nuxtTemplateExperimental, + qwik: qwikTemplateExperimental, + remix: remixTemplateExperimental, + solid: solidTemplateExperimental, + svelte: svelteTemplateExperimental, }; } else { return { - analog: (await import("../templates/analog/c3")).default, - angular: (await import("../templates/angular/c3")).default, - astro: (await import("../templates/astro/c3")).default, - docusaurus: (await import("../templates/docusaurus/c3")).default, - gatsby: (await import("../templates/gatsby/c3")).default, - hono: (await import("../templates/hono/c3")).default, - next: (await import("../templates/next/c3")).default, - nuxt: (await import("../templates/nuxt/c3")).default, - qwik: (await import("../templates/qwik/c3")).default, - react: (await import("../templates/react/c3")).default, - remix: (await import("../templates/remix/c3")).default, - solid: (await import("../templates/solid/c3")).default, - svelte: (await import("../templates/svelte/c3")).default, - vue: (await import("../templates/vue/c3")).default, + analog: analogTemplate, + angular: angularTemplate, + astro: astroTemplate, + docusaurus: docusaurusTemplate, + gatsby: gatsbyTemplate, + hono: honoTemplate, + next: nextTemplate, + nuxt: nuxtTemplate, + qwik: qwikTemplate, + react: reactTemplate, + remix: remixTemplate, + solid: solidTemplate, + svelte: svelteTemplate, + vue: vueTemplate, }; } } -export const getTemplateMap = async ({ experimental = false }) => { +export function getTemplateMap({ experimental = false }) { if (experimental) { return { - "hello-world-with-assets": ( - await import("../templates-experimental/hello-world-with-assets/c3") - ).default, + "hello-world-with-assets": helloWorldWithAssetsTemplateExperimental, } as Record; } else { return { - "hello-world": (await import("../templates/hello-world/c3")).default, - common: (await import("../templates/common/c3")).default, - scheduled: (await import("../templates/scheduled/c3")).default, - queues: (await import("../templates/queues/c3")).default, - "hello-world-durable-object": ( - await import("../templates/hello-world-durable-object/c3") - ).default, - openapi: (await import("../templates/openapi/c3")).default, - "pre-existing": (await import("../templates/pre-existing/c3")).default, + "hello-world": helloWorldTemplate, + common: commonTemplate, + scheduled: scheduledTemplate, + queues: queuesTemplate, + "hello-world-durable-object": helloWorldDurableObjectTemplate, + openapi: openapiTemplate, + "pre-existing": preExistingTemplate, } as Record; } -}; +} export function getNamesAndDescriptions(templateMap: TemplateMap) { return Array.from(Object.entries(templateMap)).map( @@ -342,7 +366,7 @@ export const createContext = async ( let template: TemplateConfig; if (category === "web-framework") { - const frameworkMap = await getFrameworkMap({ + const frameworkMap = getFrameworkMap({ experimental: args.experimental, }); const frameworkOptions = Object.entries(frameworkMap).map( diff --git a/packages/create-cloudflare/templates-experimental/astro/c3.ts b/packages/create-cloudflare/templates-experimental/astro/c3.ts index a31e4497c8a2..1674809fd026 100644 --- a/packages/create-cloudflare/templates-experimental/astro/c3.ts +++ b/packages/create-cloudflare/templates-experimental/astro/c3.ts @@ -91,7 +91,8 @@ const updateEnvDeclaration = (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "astro", - platform: "pages", + frameworkCli: "create-astro", + platform: "workers", displayName: "Astro", copyFiles: { path: "./templates", diff --git a/packages/create-cloudflare/templates-experimental/nuxt/c3.ts b/packages/create-cloudflare/templates-experimental/nuxt/c3.ts index 6b489b17f4ca..128158eadfd8 100644 --- a/packages/create-cloudflare/templates-experimental/nuxt/c3.ts +++ b/packages/create-cloudflare/templates-experimental/nuxt/c3.ts @@ -112,7 +112,8 @@ const updateNuxtConfig = () => { const config: TemplateConfig = { configVersion: 1, id: "nuxt", - platform: "pages", + frameworkCli: "nuxi", + platform: "workers", displayName: "Nuxt", copyFiles: { path: "./templates", diff --git a/packages/create-cloudflare/templates-experimental/qwik/c3.ts b/packages/create-cloudflare/templates-experimental/qwik/c3.ts index 186c3b34a65a..b5b4a5d955c9 100644 --- a/packages/create-cloudflare/templates-experimental/qwik/c3.ts +++ b/packages/create-cloudflare/templates-experimental/qwik/c3.ts @@ -130,8 +130,9 @@ const populateCloudflareEnv = () => { const config: TemplateConfig = { configVersion: 1, id: "qwik", + frameworkCli: "create-qwik", displayName: "Qwik", - platform: "pages", + platform: "workers", copyFiles: { path: "./templates", }, diff --git a/packages/create-cloudflare/templates-experimental/remix/c3.ts b/packages/create-cloudflare/templates-experimental/remix/c3.ts index c4d88261f375..e2095cb38e39 100644 --- a/packages/create-cloudflare/templates-experimental/remix/c3.ts +++ b/packages/create-cloudflare/templates-experimental/remix/c3.ts @@ -52,7 +52,8 @@ const configure = async () => { const config: TemplateConfig = { configVersion: 1, id: "remix", - platform: "pages", + frameworkCli: "create-remix", + platform: "workers", displayName: "Remix", copyFiles: { path: "./templates", diff --git a/packages/create-cloudflare/templates-experimental/solid/c3.ts b/packages/create-cloudflare/templates-experimental/solid/c3.ts index dc37014effd2..1c087958cb82 100644 --- a/packages/create-cloudflare/templates-experimental/solid/c3.ts +++ b/packages/create-cloudflare/templates-experimental/solid/c3.ts @@ -40,7 +40,6 @@ const configure = async (ctx: C3Context) => { } const b = recast.types.builders; - debugger; mergeObjectProperties( n.node.arguments[0] as recast.types.namedTypes.ObjectExpression, [ @@ -115,8 +114,9 @@ const configure = async (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "solid", + frameworkCli: "create-solid", displayName: "Solid", - platform: "pages", + platform: "workers", copyFiles: { path: "./templates", }, diff --git a/packages/create-cloudflare/templates-experimental/svelte/c3.ts b/packages/create-cloudflare/templates-experimental/svelte/c3.ts index f728a95aeac6..04c92109d07d 100644 --- a/packages/create-cloudflare/templates-experimental/svelte/c3.ts +++ b/packages/create-cloudflare/templates-experimental/svelte/c3.ts @@ -97,8 +97,9 @@ const updateTypeDefinitions = (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "svelte", + frameworkCli: "create-svelte", displayName: "Svelte", - platform: "pages", + platform: "workers", copyFiles: { variants: { js: { path: "./js" }, diff --git a/packages/create-cloudflare/templates/analog/c3.ts b/packages/create-cloudflare/templates/analog/c3.ts index 44019c22752b..1df940eb00ae 100644 --- a/packages/create-cloudflare/templates/analog/c3.ts +++ b/packages/create-cloudflare/templates/analog/c3.ts @@ -113,6 +113,7 @@ const updateViteConfig = (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "analog", + frameworkCli: "create-analog", platform: "pages", displayName: "Analog", copyFiles: { diff --git a/packages/create-cloudflare/templates/angular/c3.ts b/packages/create-cloudflare/templates/angular/c3.ts index a93606fe2d59..4522b4441374 100644 --- a/packages/create-cloudflare/templates/angular/c3.ts +++ b/packages/create-cloudflare/templates/angular/c3.ts @@ -79,6 +79,7 @@ function updateAngularJson(ctx: C3Context) { const config: TemplateConfig = { configVersion: 1, id: "angular", + frameworkCli: "@angular/create", displayName: "Angular", platform: "pages", copyFiles: { diff --git a/packages/create-cloudflare/templates/astro/c3.ts b/packages/create-cloudflare/templates/astro/c3.ts index 98dcdd5af540..fbcacae2c35e 100644 --- a/packages/create-cloudflare/templates/astro/c3.ts +++ b/packages/create-cloudflare/templates/astro/c3.ts @@ -91,6 +91,7 @@ const updateEnvDeclaration = (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "astro", + frameworkCli: "create-astro", platform: "pages", displayName: "Astro", copyFiles: { diff --git a/packages/create-cloudflare/templates/docusaurus/c3.ts b/packages/create-cloudflare/templates/docusaurus/c3.ts index b7a947cf1ebe..f5bcaf62cca9 100644 --- a/packages/create-cloudflare/templates/docusaurus/c3.ts +++ b/packages/create-cloudflare/templates/docusaurus/c3.ts @@ -12,6 +12,7 @@ const generate = async (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "docusaurus", + frameworkCli: "create-docusaurus", platform: "pages", displayName: "Docusaurus", generate, diff --git a/packages/create-cloudflare/templates/gatsby/c3.ts b/packages/create-cloudflare/templates/gatsby/c3.ts index 525af174dc3f..7389ec431024 100644 --- a/packages/create-cloudflare/templates/gatsby/c3.ts +++ b/packages/create-cloudflare/templates/gatsby/c3.ts @@ -32,6 +32,7 @@ const generate = async (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "gatsby", + frameworkCli: "gatsby", platform: "pages", displayName: "Gatsby", generate, diff --git a/packages/create-cloudflare/templates/hono/c3.ts b/packages/create-cloudflare/templates/hono/c3.ts index 3183e14c8886..cacb6b504fd9 100644 --- a/packages/create-cloudflare/templates/hono/c3.ts +++ b/packages/create-cloudflare/templates/hono/c3.ts @@ -48,6 +48,7 @@ const configure = async (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "hono", + frameworkCli: "create-hono", displayName: "Hono", copyFiles: { path: "./templates", diff --git a/packages/create-cloudflare/templates/next/c3.ts b/packages/create-cloudflare/templates/next/c3.ts index bffa4d169652..44d0a3ef15e3 100644 --- a/packages/create-cloudflare/templates/next/c3.ts +++ b/packages/create-cloudflare/templates/next/c3.ts @@ -175,6 +175,7 @@ const addDevDependencies = async (installEslintPlugin: boolean) => { export default { configVersion: 1, id: "next", + frameworkCli: "create-next-app", platform: "pages", displayName: "Next", generate, diff --git a/packages/create-cloudflare/templates/nuxt/c3.ts b/packages/create-cloudflare/templates/nuxt/c3.ts index 9b3ec1926e0e..ca9780993cb3 100644 --- a/packages/create-cloudflare/templates/nuxt/c3.ts +++ b/packages/create-cloudflare/templates/nuxt/c3.ts @@ -112,6 +112,7 @@ const updateNuxtConfig = () => { const config: TemplateConfig = { configVersion: 1, id: "nuxt", + frameworkCli: "nuxi", platform: "pages", displayName: "Nuxt", copyFiles: { diff --git a/packages/create-cloudflare/templates/qwik/c3.ts b/packages/create-cloudflare/templates/qwik/c3.ts index 6eb52c5c2b2b..a39e94f302a6 100644 --- a/packages/create-cloudflare/templates/qwik/c3.ts +++ b/packages/create-cloudflare/templates/qwik/c3.ts @@ -125,6 +125,7 @@ const populateCloudflareEnv = () => { const config: TemplateConfig = { configVersion: 1, id: "qwik", + frameworkCli: "create-qwik", displayName: "Qwik", platform: "pages", copyFiles: { diff --git a/packages/create-cloudflare/templates/react/c3.ts b/packages/create-cloudflare/templates/react/c3.ts index 932795f8e5f0..583e146f0733 100644 --- a/packages/create-cloudflare/templates/react/c3.ts +++ b/packages/create-cloudflare/templates/react/c3.ts @@ -43,6 +43,8 @@ const variantsOptions = [ const config: TemplateConfig = { configVersion: 1, id: "react", + // React's documentation now recommends using create-vite. + frameworkCli: "create-vite", displayName: "React", platform: "pages", generate, diff --git a/packages/create-cloudflare/templates/remix/c3.ts b/packages/create-cloudflare/templates/remix/c3.ts index 78bf421aca6e..c7895911369f 100644 --- a/packages/create-cloudflare/templates/remix/c3.ts +++ b/packages/create-cloudflare/templates/remix/c3.ts @@ -45,6 +45,7 @@ const configure = async () => { const config: TemplateConfig = { configVersion: 1, id: "remix", + frameworkCli: "create-remix", platform: "pages", displayName: "Remix", copyFiles: { diff --git a/packages/create-cloudflare/templates/solid/c3.ts b/packages/create-cloudflare/templates/solid/c3.ts index c3da4aad6a05..6898b7a39bad 100644 --- a/packages/create-cloudflare/templates/solid/c3.ts +++ b/packages/create-cloudflare/templates/solid/c3.ts @@ -65,6 +65,7 @@ const configure = async (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "solid", + frameworkCli: "create-solid", displayName: "Solid", platform: "pages", copyFiles: { diff --git a/packages/create-cloudflare/templates/svelte/c3.ts b/packages/create-cloudflare/templates/svelte/c3.ts index ec2bd885f59e..1d221f68026c 100644 --- a/packages/create-cloudflare/templates/svelte/c3.ts +++ b/packages/create-cloudflare/templates/svelte/c3.ts @@ -97,6 +97,7 @@ const updateTypeDefinitions = (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "svelte", + frameworkCli: "create-svelte", displayName: "Svelte", platform: "pages", copyFiles: { diff --git a/packages/create-cloudflare/templates/vue/c3.ts b/packages/create-cloudflare/templates/vue/c3.ts index 306c3d14745d..2c4de5fd3f1f 100644 --- a/packages/create-cloudflare/templates/vue/c3.ts +++ b/packages/create-cloudflare/templates/vue/c3.ts @@ -12,6 +12,7 @@ const generate = async (ctx: C3Context) => { const config: TemplateConfig = { configVersion: 1, id: "vue", + frameworkCli: "create-vue", displayName: "Vue", platform: "pages", generate, From e08ec1478871ab29fc62c4f69bb722018f72c266 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 11 Sep 2024 23:52:06 +0100 Subject: [PATCH 13/17] feat: add experimental angular workers + assets template --- packages/create-cloudflare/.eslintrc.js | 3 +- packages/create-cloudflare/src/templates.ts | 2 + .../templates-experimental/angular/c3.ts | 102 ++++++++++++++++++ .../angular/templates/server.ts | 34 ++++++ .../angular/templates/src/.assetsignore | 4 + .../templates/tools/alter-polyfills.mjs | 27 +++++ .../angular/templates/tools/copy-files.mjs | 9 ++ .../angular/templates/tools/paths.mjs | 9 ++ .../angular/templates/wrangler.toml | 5 + packages/create-cloudflare/tsconfig.json | 2 +- 10 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 packages/create-cloudflare/templates-experimental/angular/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/angular/templates/server.ts create mode 100644 packages/create-cloudflare/templates-experimental/angular/templates/src/.assetsignore create mode 100644 packages/create-cloudflare/templates-experimental/angular/templates/tools/alter-polyfills.mjs create mode 100644 packages/create-cloudflare/templates-experimental/angular/templates/tools/copy-files.mjs create mode 100644 packages/create-cloudflare/templates-experimental/angular/templates/tools/paths.mjs create mode 100644 packages/create-cloudflare/templates-experimental/angular/templates/wrangler.toml diff --git a/packages/create-cloudflare/.eslintrc.js b/packages/create-cloudflare/.eslintrc.js index 1057fd4b8aa3..0d91ffc1852d 100644 --- a/packages/create-cloudflare/.eslintrc.js +++ b/packages/create-cloudflare/.eslintrc.js @@ -5,8 +5,9 @@ module.exports = { "dist", "scripts", "e2e-tests/fixtures/*", + "**/templates*/**/*.*", // template files are ignored by the eslint-config-worker configuration // we do however want the c3 files to be linted - "!**/templates/**/c3.ts", + "!**/templates*/**/c3.ts", ], }; diff --git a/packages/create-cloudflare/src/templates.ts b/packages/create-cloudflare/src/templates.ts index 060e7f6229aa..f7e8aa04a427 100644 --- a/packages/create-cloudflare/src/templates.ts +++ b/packages/create-cloudflare/src/templates.ts @@ -18,6 +18,7 @@ import { writeFile, writeJSON, } from "helpers/files"; +import angularTemplateExperimental from "../templates-experimental/angular/c3"; import astroTemplateExperimental from "../templates-experimental/astro/c3"; import helloWorldWithAssetsTemplateExperimental from "../templates-experimental/hello-world-with-assets/c3"; import nuxtTemplateExperimental from "../templates-experimental/nuxt/c3"; @@ -156,6 +157,7 @@ export type TemplateMap = Record; export function getFrameworkMap({ experimental = false }): TemplateMap { if (experimental) { return { + angular: angularTemplateExperimental, astro: astroTemplateExperimental, nuxt: nuxtTemplateExperimental, qwik: qwikTemplateExperimental, diff --git a/packages/create-cloudflare/templates-experimental/angular/c3.ts b/packages/create-cloudflare/templates-experimental/angular/c3.ts new file mode 100644 index 000000000000..751564aabb17 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/angular/c3.ts @@ -0,0 +1,102 @@ +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 "frameworks/index"; +import { readFile, readJSON, writeFile } from "helpers/files"; +import { detectPackageManager } from "helpers/packageManagers"; +import { installPackages } from "helpers/packages"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context } from "types"; + +const { npm } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + await runFrameworkGenerator(ctx, [ctx.project.name, "--ssr"]); + logRaw(""); +}; + +const configure = async (ctx: C3Context) => { + updateAngularJson(ctx); + await updateAppCode(); + await installCFWorker(); +}; + +async function installCFWorker() { + await installPackages( + ["@cloudflare/workers-types", "@miniflare/tre@next", "wrangler@beta"], + { + dev: true, + startText: "Installing adapter dependencies", + doneText: `${brandColor("installed")} ${dim(`via \`${npm} install\``)}`, + }, + ); +} +async function updateAppCode() { + const s = spinner(); + s.start(`Updating application code`); + + // Update an app config file to: + // - add the `provideHttpClient(withFetch())` call to enable `fetch` usage in `HttpClient` + const appConfigPath = "src/app/app.config.ts"; + const appConfig = readFile(resolve(appConfigPath)); + const newAppConfig = + "import { provideHttpClient, withFetch } from '@angular/common/http';\n" + + appConfig.replace( + "providers: [", + "providers: [provideHttpClient(withFetch()), ", + ); + writeFile(resolve(appConfigPath), newAppConfig); + s.stop(`${brandColor(`updated`)} ${dim(appConfigPath)}`); + + // Remove unwanted dependencies + s.start(`Updating package.json`); + const packageJsonPath = resolve("package.json"); + const packageManifest = readJSON(packageJsonPath); + + delete packageManifest["dependencies"]["@angular/ssr"]; + delete packageManifest["dependencies"]["express"]; + delete packageManifest["devDependencies"]["@types/express"]; + + writeFile(packageJsonPath, JSON.stringify(packageManifest, null, 2)); + s.stop(`${brandColor(`updated`)} ${dim(`\`package.json\``)}`); +} + +function updateAngularJson(ctx: C3Context) { + const s = spinner(); + s.start(`Updating angular.json config`); + const angularJson = readJSON(resolve("angular.json")); + // Update builder + const architectSection = angularJson.projects[ctx.project.name].architect; + architectSection.build.options.outputPath = "dist"; + architectSection.build.options.assets.push("src/_routes.json"); + + writeFile(resolve("angular.json"), JSON.stringify(angularJson, null, 2)); + s.stop(`${brandColor(`updated`)} ${dim(`\`angular.json\``)}`); +} + +const config: TemplateConfig = { + configVersion: 1, + id: "angular", + frameworkCli: "@angular/create", + displayName: "Angular", + platform: "workers", + copyFiles: { + path: "./templates", + }, + path: "templates-experimental/angular", + devScript: "start", + deployScript: "deploy", + generate, + configure, + transformPackageJson: async () => ({ + scripts: { + start: `${npm} run build && wrangler dev`, + build: `ng build && ${npm} run process`, + process: + "node ./tools/copy-files.mjs && node ./tools/alter-polyfills.mjs", + deploy: `${npm} run build && wrangler deploy`, + }, + }), +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/angular/templates/server.ts b/packages/create-cloudflare/templates-experimental/angular/templates/server.ts new file mode 100644 index 000000000000..406ee222615f --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/angular/templates/server.ts @@ -0,0 +1,34 @@ +import { renderApplication } from "@angular/platform-server"; +import bootstrap from "./src/main.server"; + +interface Env { + ASSETS: { fetch: typeof fetch }; +} + +// We attach the Cloudflare `fetch()` handler to the global scope +// so that we can export it when we process the Angular output. +// See tools/bundle.mjs +async function workerFetchHandler(request: Request, env: Env) { + const url = new URL(request.url); + console.log("render SSR", url.href); + + // Get the root `index.html` content. + const indexUrl = new URL("/index.html", url); + const indexResponse = await env.ASSETS.fetch(new Request(indexUrl)); + const document = await indexResponse.text(); + + const content = await renderApplication(bootstrap, { + document, + url: url.pathname, + }); + + // console.log("rendered SSR", content); + return new Response(content, indexResponse); +} + +export default { + fetch: (request: Request, env: Env) => + (globalThis as any)["__zone_symbol__Promise"].resolve( + workerFetchHandler(request, env), + ), +}; diff --git a/packages/create-cloudflare/templates-experimental/angular/templates/src/.assetsignore b/packages/create-cloudflare/templates-experimental/angular/templates/src/.assetsignore new file mode 100644 index 000000000000..c2634cc3650a --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/angular/templates/src/.assetsignore @@ -0,0 +1,4 @@ +_worker.js +_routes.json +_headers +_redirects diff --git a/packages/create-cloudflare/templates-experimental/angular/templates/tools/alter-polyfills.mjs b/packages/create-cloudflare/templates-experimental/angular/templates/tools/alter-polyfills.mjs new file mode 100644 index 000000000000..eb479826900b --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/angular/templates/tools/alter-polyfills.mjs @@ -0,0 +1,27 @@ +import fs from "node:fs"; +import { EOL } from "node:os"; +import { join } from "node:path"; +import { worker } from "./paths.mjs"; + +/** + * Split by lines and comment the banner + * ``` + * import { createRequire } from 'node:module'; + * globalThis['require'] ??= createRequire(import.meta.url); + * ``` + */ +const serverPolyfillsFile = join(worker, "polyfills.server.mjs"); +const serverPolyfillsData = fs + .readFileSync(serverPolyfillsFile, "utf8") + .split(/\r?\n/); + +for (let index = 0; index < 2; index++) { + if (serverPolyfillsData[index].includes("createRequire")) { + serverPolyfillsData[index] = "// " + serverPolyfillsData[index]; + } +} + +// Add needed polyfills +serverPolyfillsData.unshift(`globalThis['process'] = {};`); + +fs.writeFileSync(serverPolyfillsFile, serverPolyfillsData.join(EOL)); diff --git a/packages/create-cloudflare/templates-experimental/angular/templates/tools/copy-files.mjs b/packages/create-cloudflare/templates-experimental/angular/templates/tools/copy-files.mjs new file mode 100644 index 000000000000..1fba4ce08dd3 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/angular/templates/tools/copy-files.mjs @@ -0,0 +1,9 @@ +// Copy the files over so that they can be uploaded by the pages publish command. +import fs from "node:fs"; +import { join } from "node:path"; +import { client, cloudflare, ssr, worker } from "./paths.mjs"; + +fs.cpSync(client, cloudflare, { recursive: true }); +fs.cpSync(ssr, worker, { recursive: true }); + +fs.renameSync(join(worker, "server.mjs"), join(worker, "index.js")); diff --git a/packages/create-cloudflare/templates-experimental/angular/templates/tools/paths.mjs b/packages/create-cloudflare/templates-experimental/angular/templates/tools/paths.mjs new file mode 100644 index 000000000000..723b1acea1c6 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/angular/templates/tools/paths.mjs @@ -0,0 +1,9 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +export const root = path.resolve(dirname, ".."); +export const client = path.resolve(root, "dist/browser"); +export const ssr = path.resolve(root, "dist/server"); +export const cloudflare = path.resolve(root, "dist/cloudflare"); +export const worker = path.resolve(cloudflare, "_worker.js"); diff --git a/packages/create-cloudflare/templates-experimental/angular/templates/wrangler.toml b/packages/create-cloudflare/templates-experimental/angular/templates/wrangler.toml new file mode 100644 index 000000000000..845730f49059 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/angular/templates/wrangler.toml @@ -0,0 +1,5 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +main = "./dist/cloudflare/_worker.js" +experimental_assets = { directory = "./dist/cloudflare", binding = "ASSETS" } diff --git a/packages/create-cloudflare/tsconfig.json b/packages/create-cloudflare/tsconfig.json index 9e1fb4ba54e6..c6af4fca9e11 100644 --- a/packages/create-cloudflare/tsconfig.json +++ b/packages/create-cloudflare/tsconfig.json @@ -10,7 +10,7 @@ // exclude all template files other than the top level ones so // that we can catch `c3.ts`. For example, any top level files in // templates/angular/ will be included, but any directories will not - "templates/*/*/**" + "templates*/*/*/**" ], "compilerOptions": { "target": "ESNext", From 47fe0822cb2b690a8e08b0ec419097662c2346af Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 12 Sep 2024 08:56:03 +0100 Subject: [PATCH 14/17] refactor: use mapped paths to import templates --- packages/create-cloudflare/src/templates.ts | 58 ++++++++++----------- packages/create-cloudflare/tsconfig.json | 10 ++-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/create-cloudflare/src/templates.ts b/packages/create-cloudflare/src/templates.ts index f7e8aa04a427..798f2dd559f0 100644 --- a/packages/create-cloudflare/src/templates.ts +++ b/packages/create-cloudflare/src/templates.ts @@ -18,35 +18,35 @@ import { writeFile, writeJSON, } from "helpers/files"; -import angularTemplateExperimental from "../templates-experimental/angular/c3"; -import astroTemplateExperimental from "../templates-experimental/astro/c3"; -import helloWorldWithAssetsTemplateExperimental from "../templates-experimental/hello-world-with-assets/c3"; -import nuxtTemplateExperimental from "../templates-experimental/nuxt/c3"; -import qwikTemplateExperimental from "../templates-experimental/qwik/c3"; -import remixTemplateExperimental from "../templates-experimental/remix/c3"; -import solidTemplateExperimental from "../templates-experimental/solid/c3"; -import svelteTemplateExperimental from "../templates-experimental/svelte/c3"; -import analogTemplate from "../templates/analog/c3"; -import angularTemplate from "../templates/angular/c3"; -import astroTemplate from "../templates/astro/c3"; -import commonTemplate from "../templates/common/c3"; -import docusaurusTemplate from "../templates/docusaurus/c3"; -import gatsbyTemplate from "../templates/gatsby/c3"; -import helloWorldDurableObjectTemplate from "../templates/hello-world-durable-object/c3"; -import helloWorldTemplate from "../templates/hello-world/c3"; -import honoTemplate from "../templates/hono/c3"; -import nextTemplate from "../templates/next/c3"; -import nuxtTemplate from "../templates/nuxt/c3"; -import openapiTemplate from "../templates/openapi/c3"; -import preExistingTemplate from "../templates/pre-existing/c3"; -import queuesTemplate from "../templates/queues/c3"; -import qwikTemplate from "../templates/qwik/c3"; -import reactTemplate from "../templates/react/c3"; -import remixTemplate from "../templates/remix/c3"; -import scheduledTemplate from "../templates/scheduled/c3"; -import solidTemplate from "../templates/solid/c3"; -import svelteTemplate from "../templates/svelte/c3"; -import vueTemplate from "../templates/vue/c3"; +import angularTemplateExperimental from "templates-experimental/angular/c3"; +import astroTemplateExperimental from "templates-experimental/astro/c3"; +import helloWorldWithAssetsTemplateExperimental from "templates-experimental/hello-world-with-assets/c3"; +import nuxtTemplateExperimental from "templates-experimental/nuxt/c3"; +import qwikTemplateExperimental from "templates-experimental/qwik/c3"; +import remixTemplateExperimental from "templates-experimental/remix/c3"; +import solidTemplateExperimental from "templates-experimental/solid/c3"; +import svelteTemplateExperimental from "templates-experimental/svelte/c3"; +import analogTemplate from "templates/analog/c3"; +import angularTemplate from "templates/angular/c3"; +import astroTemplate from "templates/astro/c3"; +import commonTemplate from "templates/common/c3"; +import docusaurusTemplate from "templates/docusaurus/c3"; +import gatsbyTemplate from "templates/gatsby/c3"; +import helloWorldDurableObjectTemplate from "templates/hello-world-durable-object/c3"; +import helloWorldTemplate from "templates/hello-world/c3"; +import honoTemplate from "templates/hono/c3"; +import nextTemplate from "templates/next/c3"; +import nuxtTemplate from "templates/nuxt/c3"; +import openapiTemplate from "templates/openapi/c3"; +import preExistingTemplate from "templates/pre-existing/c3"; +import queuesTemplate from "templates/queues/c3"; +import qwikTemplate from "templates/qwik/c3"; +import reactTemplate from "templates/react/c3"; +import remixTemplate from "templates/remix/c3"; +import scheduledTemplate from "templates/scheduled/c3"; +import solidTemplate from "templates/solid/c3"; +import svelteTemplate from "templates/svelte/c3"; +import vueTemplate from "templates/vue/c3"; import { isInsideGitRepo } from "./git"; import { validateProjectDirectory, validateTemplateUrl } from "./validators"; import type { Option } from "@cloudflare/cli/interactive"; diff --git a/packages/create-cloudflare/tsconfig.json b/packages/create-cloudflare/tsconfig.json index c6af4fca9e11..4c844f9b8495 100644 --- a/packages/create-cloudflare/tsconfig.json +++ b/packages/create-cloudflare/tsconfig.json @@ -1,16 +1,15 @@ { "root": true, "extends": "@cloudflare/workers-tsconfig/tsconfig.json", - "include": ["**/*.ts", "templates/*/c3.ts", "**/*.mts"], + "include": ["**/*.ts", "**/*.mts"], "exclude": [ "node_modules", "dist", "scripts/snippets/*", "e2e-tests/fixtures/*", - // exclude all template files other than the top level ones so - // that we can catch `c3.ts`. For example, any top level files in - // templates/angular/ will be included, but any directories will not - "templates*/*/*/**" + // exclude all template files other than those are directly imported into the program, + // e.g. the c3.ts files. + "templates*" ], "compilerOptions": { "target": "ESNext", @@ -25,6 +24,7 @@ "paths": { "helpers/*": ["./src/helpers/*"], "templates/*": ["./templates/*"], + "templates-experimental/*": ["./templates-experimental/*"], "frameworks/*": ["./src/frameworks/*"], "types": ["./src/types.ts"] } From ce46e043fa5791e83f348d0b159f21e54ca0bfc9 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 12 Sep 2024 08:56:49 +0100 Subject: [PATCH 15/17] feat: add experimental docusaurus workers + assets template --- packages/create-cloudflare/src/templates.ts | 2 ++ .../templates-experimental/docusaurus/c3.ts | 32 +++++++++++++++++++ .../docusaurus/templates/wrangler.toml | 4 +++ 3 files changed, 38 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/docusaurus/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/docusaurus/templates/wrangler.toml diff --git a/packages/create-cloudflare/src/templates.ts b/packages/create-cloudflare/src/templates.ts index 798f2dd559f0..64f46be08026 100644 --- a/packages/create-cloudflare/src/templates.ts +++ b/packages/create-cloudflare/src/templates.ts @@ -20,6 +20,7 @@ import { } from "helpers/files"; import angularTemplateExperimental from "templates-experimental/angular/c3"; import astroTemplateExperimental from "templates-experimental/astro/c3"; +import docusaurusTemplateExperimental from "templates-experimental/docusaurus/c3"; import helloWorldWithAssetsTemplateExperimental from "templates-experimental/hello-world-with-assets/c3"; import nuxtTemplateExperimental from "templates-experimental/nuxt/c3"; import qwikTemplateExperimental from "templates-experimental/qwik/c3"; @@ -159,6 +160,7 @@ export function getFrameworkMap({ experimental = false }): TemplateMap { return { angular: angularTemplateExperimental, astro: astroTemplateExperimental, + docusaurus: docusaurusTemplateExperimental, nuxt: nuxtTemplateExperimental, qwik: qwikTemplateExperimental, remix: remixTemplateExperimental, diff --git a/packages/create-cloudflare/templates-experimental/docusaurus/c3.ts b/packages/create-cloudflare/templates-experimental/docusaurus/c3.ts new file mode 100644 index 000000000000..bb22570855ae --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/docusaurus/c3.ts @@ -0,0 +1,32 @@ +import { runFrameworkGenerator } from "frameworks/index"; +import { detectPackageManager } from "helpers/packageManagers"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context } from "types"; + +const { npm } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + await runFrameworkGenerator(ctx, [ctx.project.name, "classic"]); +}; + +const config: TemplateConfig = { + configVersion: 1, + id: "docusaurus", + frameworkCli: "create-docusaurus", + platform: "workers", + displayName: "Docusaurus", + copyFiles: { + path: "./templates", + }, + path: "templates-experimental/docusaurus", + generate, + transformPackageJson: async () => ({ + scripts: { + deploy: `${npm} run build && wrangler deploy`, + preview: `${npm} run build && wrangler dev`, + }, + }), + devScript: "start", + deployScript: "deploy", +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/docusaurus/templates/wrangler.toml b/packages/create-cloudflare/templates-experimental/docusaurus/templates/wrangler.toml new file mode 100644 index 000000000000..f533e6a012af --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/docusaurus/templates/wrangler.toml @@ -0,0 +1,4 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +experimental_assets = { directory = "./build" } From 7ac13a68a7449de7c218c4920fd1ad6975fafa69 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 12 Sep 2024 09:31:47 +0100 Subject: [PATCH 16/17] feat: add experimental gatsby workers + assets template --- packages/create-cloudflare/src/templates.ts | 2 + .../templates-experimental/gatsby/c3.ts | 53 +++++++++++++++++++ .../gatsby/templates/wrangler.toml | 4 ++ 3 files changed, 59 insertions(+) create mode 100644 packages/create-cloudflare/templates-experimental/gatsby/c3.ts create mode 100644 packages/create-cloudflare/templates-experimental/gatsby/templates/wrangler.toml diff --git a/packages/create-cloudflare/src/templates.ts b/packages/create-cloudflare/src/templates.ts index 64f46be08026..415d81d719e6 100644 --- a/packages/create-cloudflare/src/templates.ts +++ b/packages/create-cloudflare/src/templates.ts @@ -21,6 +21,7 @@ import { import angularTemplateExperimental from "templates-experimental/angular/c3"; import astroTemplateExperimental from "templates-experimental/astro/c3"; import docusaurusTemplateExperimental from "templates-experimental/docusaurus/c3"; +import gatsbyTemplateExperimental from "templates-experimental/gatsby/c3"; import helloWorldWithAssetsTemplateExperimental from "templates-experimental/hello-world-with-assets/c3"; import nuxtTemplateExperimental from "templates-experimental/nuxt/c3"; import qwikTemplateExperimental from "templates-experimental/qwik/c3"; @@ -161,6 +162,7 @@ export function getFrameworkMap({ experimental = false }): TemplateMap { angular: angularTemplateExperimental, astro: astroTemplateExperimental, docusaurus: docusaurusTemplateExperimental, + gatsby: gatsbyTemplateExperimental, nuxt: nuxtTemplateExperimental, qwik: qwikTemplateExperimental, remix: remixTemplateExperimental, diff --git a/packages/create-cloudflare/templates-experimental/gatsby/c3.ts b/packages/create-cloudflare/templates-experimental/gatsby/c3.ts new file mode 100644 index 000000000000..5fbfe88e8739 --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/gatsby/c3.ts @@ -0,0 +1,53 @@ +import { inputPrompt } from "@cloudflare/cli/interactive"; +import { runFrameworkGenerator } from "frameworks/index"; +import { detectPackageManager } from "helpers/packageManagers"; +import type { TemplateConfig } from "../../src/templates"; +import type { C3Context } from "types"; + +const { npm } = detectPackageManager(); + +const generate = async (ctx: C3Context) => { + const defaultTemplate = "https://github.com/gatsbyjs/gatsby-starter-blog"; + + const useTemplate = await inputPrompt({ + type: "confirm", + question: "Would you like to use a template?", + label: "template", + defaultValue: true, + }); + + let templateUrl = ""; + if (useTemplate) { + templateUrl = await inputPrompt({ + type: "text", + question: `Please specify the url of the template you'd like to use`, + label: "template", + defaultValue: defaultTemplate, + }); + } + + await runFrameworkGenerator(ctx, ["new", ctx.project.name, templateUrl]); +}; + +const config: TemplateConfig = { + configVersion: 1, + id: "gatsby", + frameworkCli: "gatsby", + platform: "workers", + displayName: "Gatsby", + copyFiles: { + path: "./templates", + }, + path: "templates-experimental/gatsby", + generate, + transformPackageJson: async () => ({ + scripts: { + deploy: `${npm} run build && wrangler deploy`, + preview: `${npm} run build && wrangler dev`, + }, + }), + devScript: "develop", + deployScript: "deploy", + previewScript: "preview", +}; +export default config; diff --git a/packages/create-cloudflare/templates-experimental/gatsby/templates/wrangler.toml b/packages/create-cloudflare/templates-experimental/gatsby/templates/wrangler.toml new file mode 100644 index 000000000000..99fcccb2bd2a --- /dev/null +++ b/packages/create-cloudflare/templates-experimental/gatsby/templates/wrangler.toml @@ -0,0 +1,4 @@ +#:schema node_modules/wrangler/config-schema.json +name = "" +compatibility_date = "" +experimental_assets = { directory = "./public" } From 1ab2c796ac260abedef9302340f08072516fa598 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 12 Sep 2024 10:00:46 +0100 Subject: [PATCH 17/17] add changesets --- .changeset/silly-taxis-sort.md | 5 +++++ .changeset/stale-students-crash.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/silly-taxis-sort.md create mode 100644 .changeset/stale-students-crash.md diff --git a/.changeset/silly-taxis-sort.md b/.changeset/silly-taxis-sort.md new file mode 100644 index 000000000000..f4ea20c3863c --- /dev/null +++ b/.changeset/silly-taxis-sort.md @@ -0,0 +1,5 @@ +--- +"create-cloudflare": patch +--- + +fix: do not crash if the chosen framework does not exist diff --git a/.changeset/stale-students-crash.md b/.changeset/stale-students-crash.md new file mode 100644 index 000000000000..cf92df852a5b --- /dev/null +++ b/.changeset/stale-students-crash.md @@ -0,0 +1,5 @@ +--- +"create-cloudflare": minor +--- + +feat: add experimental mode and associated workers + assets templates