chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,955 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
import { removeDirSync } from "@cloudflare/workers-utils/fs-helpers";
|
||||
import { detectPackageManager } from "helpers/packageManagers";
|
||||
import { beforeAll, describe } from "vitest";
|
||||
import { version } from "../../../package.json";
|
||||
import {
|
||||
CLOUDFLARE_API_TOKEN,
|
||||
isExperimental,
|
||||
isWindows,
|
||||
keys,
|
||||
} from "../../helpers/constants";
|
||||
import { test } from "../../helpers/index";
|
||||
import { recreateLogFolder } from "../../helpers/log-stream";
|
||||
import { runC3 } from "../../helpers/run-c3";
|
||||
import type { RunnerTestSuite } from "vitest";
|
||||
|
||||
const { name: pm } = detectPackageManager();
|
||||
const workersDomain =
|
||||
process.env.E2E_ACCOUNT_WORKERS_DEV_DOMAIN ??
|
||||
"devprod-testing7928.workers.dev";
|
||||
|
||||
describe("Create Cloudflare CLI", () => {
|
||||
// eslint-disable-next-line no-empty-pattern -- Vitest requires the 1st argument to use object destructuring
|
||||
beforeAll(({}, ctx) => {
|
||||
recreateLogFolder(ctx as RunnerTestSuite);
|
||||
});
|
||||
|
||||
describe.skipIf(isExperimental)("E2E: Basic C3 functionality ", () => {
|
||||
test("--version", async ({ expect, logStream }) => {
|
||||
const { output } = await runC3(["--version"], [], logStream);
|
||||
expect(output).toEqual(version);
|
||||
});
|
||||
|
||||
test("--version with positionals", async ({ expect, logStream }) => {
|
||||
const argv = ["foo", "bar", "baz", "--version"];
|
||||
const { output } = await runC3(argv, [], logStream);
|
||||
expect(output).toEqual(version);
|
||||
});
|
||||
|
||||
test("--version with flags", async ({ expect, logStream }) => {
|
||||
const argv = [
|
||||
"foo",
|
||||
"--type",
|
||||
"web-framework",
|
||||
"--no-deploy",
|
||||
"--version",
|
||||
];
|
||||
const { output } = await runC3(argv, [], logStream);
|
||||
expect(output).toEqual(version);
|
||||
});
|
||||
|
||||
test.skipIf(isWindows || isExperimental)(
|
||||
"Using arrow keys + enter",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const { output } = await runC3(
|
||||
[project.path],
|
||||
[
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: [keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Which template would you like to use\?/,
|
||||
input: [keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Which language do you want to use\?/,
|
||||
input: [keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to add an AGENTS\.md file/,
|
||||
input: ["n"],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to use git for version control/,
|
||||
input: [keys.right, keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to deploy your application/,
|
||||
input: [keys.enter],
|
||||
},
|
||||
],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(project.path).toExist();
|
||||
expect(output).toContain(`category Hello World example`);
|
||||
expect(output).toContain(`type SSR / full-stack app`);
|
||||
expect(output).toContain(`lang TypeScript`);
|
||||
expect(output).toContain(`no deploy`);
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows)(
|
||||
"Typing custom responses",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const { output } = await runC3(
|
||||
[],
|
||||
[
|
||||
{
|
||||
matcher:
|
||||
/In which directory do you want to create your application/,
|
||||
input: [project.path, keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: [keys.down, keys.down, keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Which template would you like to use\?/,
|
||||
input: [keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Which language do you want to use\?/,
|
||||
input: [keys.down, keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to add an AGENTS\.md file/,
|
||||
input: ["n"],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to use git for version control/,
|
||||
input: ["n"],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to deploy your application/,
|
||||
input: ["n"],
|
||||
},
|
||||
],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(project.path).toExist();
|
||||
expect(output).toContain(`type Scheduled Worker (Cron Trigger)`);
|
||||
expect(output).toContain(`lang JavaScript`);
|
||||
expect(output).toContain(`no deploy`);
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows)(
|
||||
"Mixed args and interactive",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const projectName = basename(project.path);
|
||||
const existingProjectName = Array.from(projectName).reverse().join("");
|
||||
const existingProjectPath = project.path.replace(
|
||||
projectName,
|
||||
existingProjectName
|
||||
);
|
||||
const existingFilePath = `${existingProjectPath}/example.json`;
|
||||
|
||||
try {
|
||||
// Prepare an existing project with a file
|
||||
fs.mkdirSync(existingProjectPath, { recursive: true });
|
||||
fs.writeFileSync(existingFilePath, `"Hello World"`);
|
||||
|
||||
const { output } = await runC3(
|
||||
[existingProjectPath, "--ts", "--no-deploy"],
|
||||
[
|
||||
// c3 will ask for a new project name as the provided one already exists
|
||||
{
|
||||
matcher:
|
||||
/In which directory do you want to create your application/,
|
||||
input: {
|
||||
type: "text",
|
||||
chunks: [project.path, keys.enter],
|
||||
assertErrorMessage: `ERROR Directory \`${existingProjectPath}\` already exists and contains files that might conflict. Please choose a new name.`,
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: [keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Which template would you like to use\?/,
|
||||
input: [keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to add an AGENTS\.md file/,
|
||||
input: ["n"],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to use git for version control/,
|
||||
input: ["n"],
|
||||
},
|
||||
],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(project.path).toExist();
|
||||
expect(output).toContain(`type SSR / full-stack app`);
|
||||
expect(output).toContain(`lang TypeScript`);
|
||||
expect(output).toContain(`no deploy`);
|
||||
} finally {
|
||||
removeDirSync(existingFilePath);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows)(
|
||||
"Cloning remote template with full GitHub URL",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const { output } = await runC3(
|
||||
[
|
||||
project.path,
|
||||
"--template=https://github.com/cloudflare/workers-graphql-server",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
"--no-agents",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(output).toContain(
|
||||
`repository https://github.com/cloudflare/workers-graphql-server`
|
||||
);
|
||||
expect(output).toContain(
|
||||
`Cloning template from: https://github.com/cloudflare/workers-graphql-server`
|
||||
);
|
||||
expect(output).toContain(`template cloned and validated`);
|
||||
}
|
||||
);
|
||||
|
||||
// Skipping this on npm because the template that we are downloading has a package-lock file that
|
||||
// contains checksum that won't match the locally published packages we put in the Verdaccio mock npm registry.
|
||||
// Otherwise `npm install`, which C3 does, will error with messages like:
|
||||
//
|
||||
// ```
|
||||
// ERROR Error: npm warn tarball tarball data for wrangler@http://localhost:61599/wrangler/-/wrangler-4.7.0.tgz
|
||||
// (sha512-5LoyNxpPG8K0kcU43Ossyj7+Hq78v8BNtu7ZNNSxDOUcairMEDwcbrbUOqzu/iM4yHiri5wCjl4Ja57fKED/Sg==) seems to be corrupted.
|
||||
// ```
|
||||
test.skipIf(isWindows || pm === "npm")(
|
||||
"Cloning remote template that uses wrangler.json",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const { output } = await runC3(
|
||||
[
|
||||
project.path,
|
||||
"--template=cloudflare/templates/multiplayer-globe-template",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
"--no-agents",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(output).toContain(
|
||||
`repository cloudflare/templates/multiplayer-globe-template`
|
||||
);
|
||||
expect(output).toContain(
|
||||
`Cloning template from: cloudflare/templates/multiplayer-globe-template`
|
||||
);
|
||||
expect(output).toContain(`template cloned and validated`);
|
||||
// the template fails between these two assertions. however, the
|
||||
// underlying issue appears to be with the packages pinned in
|
||||
// the template - not whether or not the settings.json file is
|
||||
// created
|
||||
expect(fs.readFileSync(`${project.path}/.vscode/settings.json`, "utf8"))
|
||||
.toMatchInlineSnapshot(`
|
||||
"{
|
||||
"files.associations": {
|
||||
"wrangler.json": "jsonc"
|
||||
}
|
||||
}"
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows)(
|
||||
"Inferring the category, type and language if the type is `hello-world-python`",
|
||||
async ({ expect, logStream, project }) => {
|
||||
// The `hello-world-python` template is now the python variant of the `hello-world` template
|
||||
const { output } = await runC3(
|
||||
[
|
||||
project.path,
|
||||
"--type=hello-world-python",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
"--no-agents",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(project.path).toExist();
|
||||
expect(output).toContain(`category Hello World example`);
|
||||
expect(output).toContain(`type Worker only`);
|
||||
expect(output).toContain(`lang Python`);
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows)(
|
||||
"Filtering templates when --lang=python is specified",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const { output } = await runC3(
|
||||
[
|
||||
project.path,
|
||||
"--lang=python",
|
||||
"--type=hello-world",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
"--no-agents",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(project.path).toExist();
|
||||
expect(output).toContain(`category Hello World example`);
|
||||
expect(output).toContain(`type Worker only`);
|
||||
expect(output).toContain(`lang Python`);
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows)(
|
||||
"Error when --lang=python is used with a category that has no Python templates",
|
||||
async ({ expect, logStream, project }) => {
|
||||
await expect(
|
||||
runC3(
|
||||
[
|
||||
project.path,
|
||||
"--lang=python",
|
||||
"--category=demo",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
)
|
||||
).rejects.toMatchObject({
|
||||
errors: expect.stringContaining(
|
||||
`No templates available for language "python" in the "demo" category`
|
||||
),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Skipping in yarn due to node version resolution conflict
|
||||
* The Openapi C3 template depends on `chanfana`, which has a dependency
|
||||
* on `yargs-parser`. The latest chanfana version(`2.8.1` at the time
|
||||
* of this writing) has a dep on `yargs-parser@22` which requires a
|
||||
* node version of `20.19.0` or higher. Our CI is currently using node
|
||||
* version `20.11.1`. We currently can't bump the CI node version as other
|
||||
* tests will fail, therefore skipping for now until we can properly fix
|
||||
*/
|
||||
test.skipIf(isWindows || pm === "yarn")(
|
||||
"Selecting template by description",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const { output } = await runC3(
|
||||
[project.path, "--no-deploy", "--git=false", "--no-agents"],
|
||||
[
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Application Starter",
|
||||
assertDescriptionText:
|
||||
"Select from a range of starter applications using various Cloudflare products",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which template would you like to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "API starter (OpenAPI compliant)",
|
||||
assertDescriptionText:
|
||||
"Get started building a basic API on Workers",
|
||||
},
|
||||
},
|
||||
],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(project.path).toExist();
|
||||
expect(output).toContain(`category Application Starter`);
|
||||
expect(output).toContain(`type API starter (OpenAPI compliant)`);
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows)(
|
||||
"Going back and forth between the category, type, framework and lang prompts",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const testProjectPath = "/test-project-path";
|
||||
const { output } = await runC3(
|
||||
[testProjectPath, "--git=false", "--no-deploy", "--no-agents"],
|
||||
[
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Go back",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher:
|
||||
/In which directory do you want to create your application/,
|
||||
input: [project.path, keys.enter],
|
||||
},
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Application Starter",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which template would you like to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Queue consumer & producer Worker",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which language do you want to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Go back",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which template would you like to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Go back",
|
||||
assertDefaultSelection: "Queue consumer & producer Worker",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Framework Starter",
|
||||
assertDefaultSelection: "Application Starter",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which development framework do you want to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Go back",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Hello World example",
|
||||
assertDefaultSelection: "Framework Starter",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which template would you like to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Worker + Durable Objects",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which language do you want to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Go back",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which template would you like to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Worker only",
|
||||
assertDefaultSelection: "Worker + Durable Objects",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which language do you want to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "JavaScript",
|
||||
},
|
||||
},
|
||||
],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(project.path).toExist();
|
||||
expect(output).toContain(`type Worker only`);
|
||||
expect(output).toContain(`lang JavaScript`);
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows || pm === "yarn" || !CLOUDFLARE_API_TOKEN)(
|
||||
"--existing-script",
|
||||
{ timeout: 120_000 },
|
||||
async ({ expect, logStream, project }) => {
|
||||
// Ensure the worker to download exists
|
||||
try {
|
||||
if (
|
||||
(
|
||||
await fetch(
|
||||
`https://existing-script-test-do-not-delete.${workersDomain}/`
|
||||
)
|
||||
).status === 404
|
||||
) {
|
||||
throw new Error("Remote worker not found");
|
||||
}
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console -- e2e setup logging for worker redeployment
|
||||
console.log(
|
||||
"Redeploying the existing-script-test-do-not-delete worker"
|
||||
);
|
||||
const workerPath = resolve(
|
||||
__dirname,
|
||||
"fixtures/existing-script-test-do-not-delete"
|
||||
);
|
||||
execSync("pnpx wrangler@latest deploy", { cwd: workerPath });
|
||||
}
|
||||
|
||||
const { output } = await runC3(
|
||||
[
|
||||
project.path,
|
||||
"--existing-script=existing-script-test-do-not-delete",
|
||||
"--git=false",
|
||||
"--no-deploy",
|
||||
"--no-agents",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
);
|
||||
expect(output).toContain("Pre-existing Worker (from Dashboard)");
|
||||
expect(output).toContain("Application created successfully!");
|
||||
expect(fs.existsSync(join(project.path, "wrangler.jsonc"))).toBe(true);
|
||||
expect(fs.existsSync(join(project.path, "wrangler.json"))).toBe(false);
|
||||
expect(fs.existsSync(join(project.path, "wrangler.toml"))).toBe(false);
|
||||
expect(
|
||||
JSON.parse(
|
||||
fs.readFileSync(join(project.path, "wrangler.jsonc"), "utf8")
|
||||
)
|
||||
).toMatchObject({ vars: { FOO: "bar" } });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe("help text", () => {
|
||||
test("--help", async ({ expect, logStream }) => {
|
||||
if (isExperimental) {
|
||||
const { output } = await runC3(
|
||||
["--help", "--experimental"],
|
||||
[],
|
||||
logStream
|
||||
);
|
||||
expect(normalizeOutput(output)).toMatchInlineSnapshot(`
|
||||
"create-cloudflare <version>
|
||||
The create-cloudflare CLI (also known as C3) is a command-line tool designed to help you set up and deploy new applications to Cloudflare. In addition to speed, it leverages officially developed templates for Workers and framework-specific setup guides to ensure each new application that you set up follows Cloudflare and any third-party best practices for deployment on the Cloudflare network.
|
||||
USAGE
|
||||
<USAGE>
|
||||
OPTIONS
|
||||
You have selected experimental mode - the options below are filtered to those that support experimental mode.
|
||||
directory
|
||||
The directory where the application should be created. Also used as the name of the application.
|
||||
If a path is provided that includes intermediary directories, only the base name will be used as the name of the application.
|
||||
--category=<value>
|
||||
Specifies the kind of templates that should be created
|
||||
Allowed Values:
|
||||
web-framework
|
||||
Framework Starter
|
||||
--type=<value>, -t
|
||||
When using a built-in template, specifies the type of application that should be created.
|
||||
Note that "--category" and "--template" are mutually exclusive options. If both are provided, "--category" will be used.
|
||||
--framework=<value>, -f
|
||||
The type of framework to use to create a web application (when using this option "--category" is coerced to "web-framework")
|
||||
When using the --framework option, C3 will dispatch to the official creation tool used by the framework (e.g. "create-astro" is used for Astro).
|
||||
You may specify additional arguments to be passed directly to these underlying tools by adding them after a "--" argument, like so:
|
||||
npm create cloudflare -- --framework svelte -- --types=ts
|
||||
pnpm create cloudflare --framework svelte -- --types=ts
|
||||
Allowed Values:
|
||||
analog, angular, astro, docusaurus, gatsby, next, nuxt, qwik, react, react-router, redwood, solid, svelte, tanstack-start, vike, vue, waku
|
||||
--platform=<value>
|
||||
Whether the application should be deployed to Pages or Workers. This is only applicable for Frameworks templates that support both Pages and Workers.
|
||||
Allowed Values:
|
||||
workers
|
||||
Create a web application that can be deployed to Workers.
|
||||
pages
|
||||
Create a web application that can be deployed to Pages.
|
||||
--variant=<value>
|
||||
The variant of the framework to use. This is only applicable for certain frameworks that support multiple variants (e.g. React with TypeScript, TypeScript + SWC, JavaScript, JavaScript + SWC).
|
||||
--lang=<value>
|
||||
The programming language of the template
|
||||
Allowed Values:
|
||||
ts, js, python
|
||||
--deploy, --no-deploy
|
||||
Deploy your application after it has been created
|
||||
--git, --no-git
|
||||
Initialize a local git repository for your application
|
||||
--agents, --no-agents
|
||||
Add an AGENTS.md file to provide AI coding agents with guidance for the Cloudflare platform
|
||||
--open, --no-open
|
||||
Opens the deployed application in your browser (this option is ignored if the application is not deployed)
|
||||
--existing-script=<value>
|
||||
The name of an existing Cloudflare Workers script to clone locally (when using this option "--type" is coerced to "pre-existing").
|
||||
When "--existing-script" is specified, "deploy" will be ignored.
|
||||
--template=<value>
|
||||
An external template to be used when creating your project.
|
||||
Any "degit" compatible string may be specified. For example:
|
||||
npm create cloudflare my-project -- --template github:user/repo
|
||||
npm create cloudflare my-project -- --template git@github.com:user/repo
|
||||
npm create cloudflare my-project -- --template https://github.com/user/repo
|
||||
npm create cloudflare my-project -- --template git@github.com:user/repo#dev (branch)
|
||||
npm create cloudflare my-project -- --template git@github.com:user/repo#v1.2.3 (tag)
|
||||
npm create cloudflare my-project -- --template git@github.com:user/repo#1234abcd (commit)
|
||||
Note that subdirectories may also be used. For example:
|
||||
npm create cloudflare -- --template https://github.com/cloudflare/workers-sdk/templates/worker-r2
|
||||
--template-mode=<value>
|
||||
The mechanism to use when fetching the template.
|
||||
Can be either "git" or "tar". "tar" does not support fetching from private
|
||||
repositories. By default, degit will use "tar" if the template is hosted on GitHub, BitBucket, GitLab, or git.sr.ht.
|
||||
Otherwise, it will use "git".
|
||||
Allowed Values:
|
||||
git
|
||||
Use git to fetch the template. Supports private repositories.
|
||||
tar
|
||||
Use tar to fetch the template. Only supported on public repositories hosted on GitHub, BitBucket, GitLab, or git.sr.ht.
|
||||
--accept-defaults, --no-accept-defaults, -y
|
||||
Use all the default C3 options (each can also be overridden by specifying it)
|
||||
--auto-update, --no-auto-update
|
||||
Automatically uses the latest version of C3"
|
||||
`);
|
||||
} else {
|
||||
const { output } = await runC3(["--help"], [], logStream);
|
||||
expect(normalizeOutput(output)).toMatchInlineSnapshot(`
|
||||
"create-cloudflare <version>
|
||||
The create-cloudflare CLI (also known as C3) is a command-line tool designed to help you set up and deploy new applications to Cloudflare. In addition to speed, it leverages officially developed templates for Workers and framework-specific setup guides to ensure each new application that you set up follows Cloudflare and any third-party best practices for deployment on the Cloudflare network.
|
||||
USAGE
|
||||
<USAGE>
|
||||
OPTIONS
|
||||
directory
|
||||
The directory where the application should be created. Also used as the name of the application.
|
||||
If a path is provided that includes intermediary directories, only the base name will be used as the name of the application.
|
||||
--category=<value>
|
||||
Specifies the kind of templates that should be created
|
||||
Allowed Values:
|
||||
hello-world
|
||||
Hello World Starter
|
||||
web-framework
|
||||
Framework Starter
|
||||
demo
|
||||
Application Starter
|
||||
remote-template
|
||||
Template from a GitHub repo
|
||||
--type=<value>, -t
|
||||
When using a built-in template, specifies the type of application that should be created.
|
||||
Note that "--category" and "--template" are mutually exclusive options. If both are provided, "--category" will be used.
|
||||
Allowed Values:
|
||||
hello-world
|
||||
For processing requests, transforming responses, or API endpoints
|
||||
hello-world-assets-only
|
||||
For static sites or when using your own backend. Uses Workers Static Assets.
|
||||
hello-world-with-assets
|
||||
For sites with a backend API, or server-side rendering (SSR). Uses Static Assets with a Worker.
|
||||
hello-world-durable-object
|
||||
For multiplayer apps using WebSockets, or when you need synchronization
|
||||
hello-world-durable-object-with-assets
|
||||
For full-stack applications requiring static assets, an API, and real-time coordination
|
||||
hello-world-workflows
|
||||
For multi-step applications that automatically retry, persist state, and run for minutes, hours, days or weeks
|
||||
common
|
||||
Create a Worker to route and forward requests to other services
|
||||
scheduled
|
||||
Create a Worker to be executed on a schedule for periodic (cron) jobs
|
||||
queues
|
||||
Get started with a Worker that processes background tasks and message batches with Cloudflare Queues
|
||||
openapi
|
||||
Get started building a basic API on Workers
|
||||
pre-existing
|
||||
Fetch a Worker initialized from the Cloudflare dashboard.
|
||||
--framework=<value>, -f
|
||||
The type of framework to use to create a web application (when using this option "--category" is coerced to "web-framework")
|
||||
When using the --framework option, C3 will dispatch to the official creation tool used by the framework (e.g. "create-astro" is used for Astro).
|
||||
You may specify additional arguments to be passed directly to these underlying tools by adding them after a "--" argument, like so:
|
||||
npm create cloudflare -- --framework svelte -- --types=ts
|
||||
pnpm create cloudflare --framework svelte -- --types=ts
|
||||
Allowed Values:
|
||||
analog, angular, astro, docusaurus, gatsby, hono, next, nuxt, qwik, react, react-router, redwood, solid, svelte, tanstack-start, vike, vue, waku
|
||||
--platform=<value>
|
||||
Whether the application should be deployed to Pages or Workers. This is only applicable for Frameworks templates that support both Pages and Workers.
|
||||
Allowed Values:
|
||||
workers
|
||||
Create a web application that can be deployed to Workers.
|
||||
pages
|
||||
Create a web application that can be deployed to Pages.
|
||||
--variant=<value>
|
||||
The variant of the framework to use. This is only applicable for certain frameworks that support multiple variants (e.g. React with TypeScript, TypeScript + SWC, JavaScript, JavaScript + SWC).
|
||||
--lang=<value>
|
||||
The programming language of the template
|
||||
Allowed Values:
|
||||
ts, js, python
|
||||
--deploy, --no-deploy
|
||||
Deploy your application after it has been created
|
||||
--git, --no-git
|
||||
Initialize a local git repository for your application
|
||||
--agents, --no-agents
|
||||
Add an AGENTS.md file to provide AI coding agents with guidance for the Cloudflare platform
|
||||
--open, --no-open
|
||||
Opens the deployed application in your browser (this option is ignored if the application is not deployed)
|
||||
--existing-script=<value>
|
||||
The name of an existing Cloudflare Workers script to clone locally (when using this option "--type" is coerced to "pre-existing").
|
||||
When "--existing-script" is specified, "deploy" will be ignored.
|
||||
--template=<value>
|
||||
An external template to be used when creating your project.
|
||||
Any "degit" compatible string may be specified. For example:
|
||||
npm create cloudflare my-project -- --template github:user/repo
|
||||
npm create cloudflare my-project -- --template git@github.com:user/repo
|
||||
npm create cloudflare my-project -- --template https://github.com/user/repo
|
||||
npm create cloudflare my-project -- --template git@github.com:user/repo#dev (branch)
|
||||
npm create cloudflare my-project -- --template git@github.com:user/repo#v1.2.3 (tag)
|
||||
npm create cloudflare my-project -- --template git@github.com:user/repo#1234abcd (commit)
|
||||
Note that subdirectories may also be used. For example:
|
||||
npm create cloudflare -- --template https://github.com/cloudflare/workers-sdk/templates/worker-r2
|
||||
--template-mode=<value>
|
||||
The mechanism to use when fetching the template.
|
||||
Can be either "git" or "tar". "tar" does not support fetching from private
|
||||
repositories. By default, degit will use "tar" if the template is hosted on GitHub, BitBucket, GitLab, or git.sr.ht.
|
||||
Otherwise, it will use "git".
|
||||
Allowed Values:
|
||||
git
|
||||
Use git to fetch the template. Supports private repositories.
|
||||
tar
|
||||
Use tar to fetch the template. Only supported on public repositories hosted on GitHub, BitBucket, GitLab, or git.sr.ht.
|
||||
--accept-defaults, --no-accept-defaults, -y
|
||||
Use all the default C3 options (each can also be overridden by specifying it)
|
||||
--auto-update, --no-auto-update
|
||||
Automatically uses the latest version of C3"
|
||||
`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("frameworks related", () => {
|
||||
["solid", "next", "react-router", "analog"].forEach((framework) =>
|
||||
test(`error when trying to create a ${framework} app on Pages`, async ({
|
||||
expect,
|
||||
logStream,
|
||||
}) => {
|
||||
await expect(
|
||||
runC3(
|
||||
["--platform=pages", `--framework=${framework}`, "my-app"],
|
||||
[],
|
||||
logStream
|
||||
)
|
||||
).rejects.toMatchObject({
|
||||
errors: expect.stringMatching(
|
||||
/Error: The .*? framework doesn't support the "pages" platform/
|
||||
),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test("error when using invalid --variant for React framework", async ({
|
||||
expect,
|
||||
logStream,
|
||||
}) => {
|
||||
await expect(
|
||||
runC3(
|
||||
[
|
||||
"my-app",
|
||||
"--framework=react",
|
||||
"--platform=workers",
|
||||
"--variant=invalid-variant",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
)
|
||||
).rejects.toMatchObject({
|
||||
errors: expect.stringContaining(
|
||||
'Unknown variant "invalid-variant". Valid variants are: react-ts, react'
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
test("error when using deprecated SWC --variant for React Workers framework", async ({
|
||||
expect,
|
||||
logStream,
|
||||
}) => {
|
||||
await expect(
|
||||
runC3(
|
||||
[
|
||||
"my-app",
|
||||
"--framework=react",
|
||||
"--platform=workers",
|
||||
"--variant=react-swc-ts",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
)
|
||||
).rejects.toMatchObject({
|
||||
errors: expect.stringContaining(
|
||||
'The React variant "react-swc-ts" is no longer available. Use "react-ts" instead.'
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
test("error when using invalid --variant for React Pages framework", async ({
|
||||
expect,
|
||||
logStream,
|
||||
}) => {
|
||||
await expect(
|
||||
runC3(
|
||||
[
|
||||
"my-app",
|
||||
"--framework=react",
|
||||
"--platform=pages",
|
||||
"--variant=invalid-variant",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
)
|
||||
).rejects.toMatchObject({
|
||||
errors: expect.stringContaining(
|
||||
'Unknown variant "invalid-variant". Valid variants are: react-ts, react-swc-ts, react, react-swc'
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
test("accepts --variant for React Pages framework without prompting", async ({
|
||||
expect,
|
||||
logStream,
|
||||
}) => {
|
||||
const { output } = await runC3(
|
||||
[
|
||||
"my-app",
|
||||
"--framework=react",
|
||||
"--platform=pages",
|
||||
"--variant=react-ts",
|
||||
"--no-deploy",
|
||||
"--git=false",
|
||||
],
|
||||
[],
|
||||
logStream
|
||||
);
|
||||
expect(output).toContain("--template react-ts");
|
||||
expect(output).not.toContain("Select a variant");
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(isExperimental)("platform filtering", () => {
|
||||
test.skipIf(isWindows)(
|
||||
"--platform=pages hides non-framework categories",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const { output } = await runC3(
|
||||
[project.path, "--platform=pages"],
|
||||
[
|
||||
{
|
||||
matcher: /What would you like to start with\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Framework Starter",
|
||||
// "Framework Starter" should be the default (first visible)
|
||||
// option since "Hello World example" is hidden
|
||||
assertDefaultSelection: "Framework Starter",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Which development framework do you want to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Angular",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to use git for version control/,
|
||||
input: ["n"],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to deploy your application/,
|
||||
input: ["n"],
|
||||
},
|
||||
],
|
||||
logStream
|
||||
);
|
||||
|
||||
expect(output).toContain("category Framework Starter");
|
||||
expect(output).not.toContain("Hello World");
|
||||
expect(output).not.toContain("Application Starter");
|
||||
}
|
||||
);
|
||||
|
||||
test.skipIf(isWindows)(
|
||||
"--platform=pages filters out workers-only frameworks",
|
||||
async ({ expect, logStream, project }) => {
|
||||
const { output } = await runC3(
|
||||
[project.path, "--platform=pages", "--category=web-framework"],
|
||||
[
|
||||
{
|
||||
matcher: /Which development framework do you want to use\?/,
|
||||
input: {
|
||||
type: "select",
|
||||
target: "Angular",
|
||||
},
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to use git for version control/,
|
||||
input: ["n"],
|
||||
},
|
||||
{
|
||||
matcher: /Do you want to deploy your application/,
|
||||
input: ["n"],
|
||||
},
|
||||
],
|
||||
logStream
|
||||
);
|
||||
|
||||
// Workers-only frameworks should not appear in the output
|
||||
expect(output).not.toContain("Next");
|
||||
expect(output).not.toContain("SolidStart");
|
||||
expect(output).not.toContain("React Router");
|
||||
expect(output).not.toContain("Analog");
|
||||
}
|
||||
);
|
||||
|
||||
["hello-world", "demo"].forEach((category) =>
|
||||
test(`--platform=pages --category=${category} errors for workers-only category`, async ({
|
||||
expect,
|
||||
logStream,
|
||||
project,
|
||||
}) => {
|
||||
await expect(
|
||||
runC3(
|
||||
[project.path, "--platform=pages", `--category=${category}`],
|
||||
[],
|
||||
logStream
|
||||
)
|
||||
).rejects.toMatchObject({
|
||||
errors: expect.stringContaining(
|
||||
`The "${category}" category is not available for the "pages" platform`
|
||||
),
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function normalizeOutput(output: string): string {
|
||||
return output
|
||||
.replaceAll("\r\n", "\n")
|
||||
.replaceAll(/\n\n+/g, "\n")
|
||||
.replace(/create-cloudflare v\d+\.\d+\.\d+/, "create-cloudflare <version>")
|
||||
.replace(/USAGE\n[^\n]+/, `USAGE\n <USAGE>`);
|
||||
}
|
||||
Reference in New Issue
Block a user