Skip to content

Commit

Permalink
feat(create-cloudflare): update submenu userflow (#6320)
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung authored Jul 29, 2024
1 parent 200332c commit 5aa4753
Show file tree
Hide file tree
Showing 16 changed files with 272 additions and 115 deletions.
9 changes: 9 additions & 0 deletions .changeset/rich-nails-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"create-cloudflare": minor
---

feat: update submenu userflow

Now, we will first prompt for the kind of templates that should be created and show a different set of templates depending on the category. If the template selected supports different languages, we will also ask for the language user preferred.

Two new arguments are also added to support the new structure: `--category` and `--lang`. For more details, please refer to our [docs](https://developers.cloudflare.com/pages/get-started/c3#cli-arguments).
59 changes: 47 additions & 12 deletions packages/create-cloudflare/e2e-tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ describe.skipIf(frameworkToTest || isQuarantineMode())(
[projectPath],
[
{
matcher: /What type of application do you want to create/,
matcher: /What do you want to start with\?/,
input: [keys.enter],
},
{
matcher: /Do you want to use TypeScript/,
matcher: /Which template would you like to use\?/,
input: [keys.enter],
},
{
matcher: /Which language do you want to use\?/,
input: [keys.enter],
},
{
Expand All @@ -95,8 +99,9 @@ describe.skipIf(frameworkToTest || isQuarantineMode())(
);

expect(projectPath).toExist();
expect(output).toContain(`type "Hello World" Worker`);
expect(output).toContain(`yes typescript`);
expect(output).toContain(`category Hello World example`);
expect(output).toContain(`type Hello World Worker`);
expect(output).toContain(`lang TypeScript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
},
Expand All @@ -114,12 +119,16 @@ describe.skipIf(frameworkToTest || isQuarantineMode())(
input: [projectPath, keys.enter],
},
{
matcher: /What type of application do you want to create/,
input: [keys.down, keys.down, keys.down, keys.enter],
matcher: /What do you want to start with\?/,
input: [keys.down, keys.down, keys.enter],
},
{
matcher: /Do you want to use TypeScript/,
input: ["n"],
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 use git for version control/,
Expand All @@ -135,7 +144,7 @@ describe.skipIf(frameworkToTest || isQuarantineMode())(

expect(projectPath).toExist();
expect(output).toContain(`type Example router & proxy Worker`);
expect(output).toContain(`no typescript`);
expect(output).toContain(`lang JavaScript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
},
Expand All @@ -148,7 +157,11 @@ describe.skipIf(frameworkToTest || isQuarantineMode())(
[projectPath, "--ts", "--no-deploy"],
[
{
matcher: /What type of application do you want to create/,
matcher: /What do you want to start with\?/,
input: [keys.enter],
},
{
matcher: /Which template would you like to use\?/,
input: [keys.enter],
},
{
Expand All @@ -160,8 +173,8 @@ describe.skipIf(frameworkToTest || isQuarantineMode())(
);

expect(projectPath).toExist();
expect(output).toContain(`type "Hello World" Worker`);
expect(output).toContain(`yes typescript`);
expect(output).toContain(`type Hello World Worker`);
expect(output).toContain(`lang TypeScript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
},
Expand Down Expand Up @@ -190,5 +203,27 @@ describe.skipIf(frameworkToTest || isQuarantineMode())(
expect(output).toContain(`template cloned and validated`);
},
);

test.skipIf(process.platform === "win32")(
"Inferring the category, type and language if the type is `hello-world-python`",
async () => {
// The `hello-world-python` template is now the python variant of the `hello-world` template
const { output } = await runC3(
[
projectPath,
"--type=hello-world-python",
"--no-deploy",
"--git=false",
],
[],
logStream,
);

expect(projectPath).toExist();
expect(output).toContain(`category Hello World example`);
expect(output).toContain(`type Hello World Worker`);
expect(output).toContain(`lang Python`);
},
);
},
);
46 changes: 17 additions & 29 deletions packages/create-cloudflare/e2e-tests/workers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { frameworkToTest } from "./frameworkToTest";
import {
createTestLogStream,
isQuarantineMode,
keys,
recreateLogFolder,
runC3,
testProjectDir,
Expand All @@ -22,42 +23,39 @@ const TEST_TIMEOUT = 1000 * 60 * 5;
type WorkerTestConfig = RunnerConfig & {
name?: string;
template: string;
variants: string[];
};

const workerTemplates: WorkerTestConfig[] = [
{
template: "hello-world",
verifyDeploy: {
route: "/",
expectedText: "Hello World!",
},
},
{
template: "hello-world-python",
promptHandlers: [],
variants: ["ts", "js", "python"],
verifyDeploy: {
route: "/",
expectedText: "Hello World!",
},
},
{
template: "common",
variants: ["ts", "js"],
verifyDeploy: {
route: "/",
expectedText: "Try making requests to:",
},
},
{
template: "queues",
variants: ["ts", "js"],
// Skipped for now, since C3 does not yet support resource creation
},
{
template: "scheduled",
variants: ["ts", "js"],
// Skipped for now, since it's not possible to test scheduled events on deployed Workers
},
{
template: "openapi",
promptHandlers: [],
variants: [],
verifyDeploy: {
route: "/",
expectedText: "SwaggerUI",
Expand All @@ -80,31 +78,21 @@ describe

workerTemplates
.flatMap<WorkerTestConfig>((template) =>
template.promptHandlers
? [template]
: [
{
...template,
name: `${template.name ?? template.template}-ts`,
promptHandlers: [
{
matcher: /Do you want to use TypeScript\?/,
input: ["y"],
},
],
},

{
template.variants.length > 0
? template.variants.map((variant, index) => {
return {
...template,
name: `${template.name ?? template.template}-js`,
name: `${template.name ?? template.template}-${variant}`,
promptHandlers: [
{
matcher: /Do you want to use TypeScript\?/,
input: ["n"],
matcher: /Which language do you want to use\?/,
// Assuming the variants are defined in the same order it is displayed in the prompt
input: Array(index).fill(keys.down).concat(keys.enter),
},
],
},
],
};
})
: [template],
)
.forEach((template) => {
const name = template.name ?? template.template;
Expand Down
59 changes: 52 additions & 7 deletions packages/create-cloudflare/src/__tests__/templates.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { existsSync, statSync } from "fs";
import { crash } from "@cloudflare/cli";
import { spinner } from "@cloudflare/cli/interactive";
import degit from "degit";
import { mockSpinner } from "helpers/__tests__/mocks";
Expand All @@ -8,14 +9,19 @@ import {
readFile,
writeFile,
} from "helpers/files";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { addWranglerToGitIgnore, downloadRemoteTemplate } from "../templates";
import { beforeEach, describe, expect, test, vi } from "vitest";
import {
addWranglerToGitIgnore,
downloadRemoteTemplate,
inferLanguageArg,
} from "../templates";
import type { PathLike } from "fs";
import type { C3Context } from "types";
import type { C3Args, C3Context } from "types";

vi.mock("degit");
vi.mock("fs");
vi.mock("helpers/files");
vi.mock("@cloudflare/cli");
vi.mock("@cloudflare/cli/interactive");

beforeEach(() => {
Expand Down Expand Up @@ -258,10 +264,6 @@ describe("downloadRemoteTemplate", () => {
});
}

afterEach(() => {
vi.resetAllMocks();
});

test("should download template using degit", async () => {
const mock = mockDegit();

Expand All @@ -280,3 +282,46 @@ describe("downloadRemoteTemplate", () => {
expect(spinner).not.toBeCalled();
});
});

describe("inferLanguageArg", () => {
test("should infer as TypeScript if `--ts` is specified", async () => {
const args: Partial<C3Args> = {
ts: true,
};

inferLanguageArg(args);

expect(args.lang).toBe("ts");
});

test("should infer as JavaScript if `--ts=false` is specified", async () => {
const args: Partial<C3Args> = {
ts: false,
};

inferLanguageArg(args);

expect(args.lang).toBe("js");
});

test("should crash only if both the lang and ts arguments are specified", async () => {
let args: Partial<C3Args> = {
lang: "ts",
};

inferLanguageArg(args);

expect(args.lang).toBe("ts");
expect(crash).not.toBeCalled();

args = {
ts: true,
lang: "ts",
};
inferLanguageArg(args);

expect(crash).toBeCalledWith(
"The `--ts` argument cannot be specified in conjunction with the `--lang` argument",
);
});
});
2 changes: 1 addition & 1 deletion packages/create-cloudflare/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const runCli = async (args: Partial<C3Args>) => {
const originalCWD = process.cwd();
const { name, path } = setupProjectDirectory(validatedArgs);

const template = await selectTemplate(args);
const template = await selectTemplate(validatedArgs);
const ctx: C3Context = {
project: { name, path },
args: validatedArgs,
Expand Down
26 changes: 20 additions & 6 deletions packages/create-cloudflare/src/helpers/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ const cliDefinition: ArgumentsDefinition = {
},
],
options: [
{
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: "Demo application" },
{ name: "remote-template", description: "Template from a Github repo" },
],
},
{
name: "type",
alias: "t",
Expand All @@ -54,13 +65,9 @@ const cliDefinition: ArgumentsDefinition = {
description: `
When using a built-in template, specifies the type of application that should be created.
Note that "--type" and "--template" are mutually exclusive options. If both are provided, "--type" will be used.
Note that "--category" and "--template" are mutually exclusive options. If both are provided, "--category" will be used.
`,
values: [
{
name: "web-framework",
description: "A website or web application.",
},
{
name: "hello-world",
description: "A basic “Hello World” Cloudflare Worker.",
Expand Down Expand Up @@ -101,7 +108,7 @@ const cliDefinition: ArgumentsDefinition = {
alias: "f",
type: "string",
requiresArg: true,
description: `The type of framework to use to create a web application (when using this option "--type" is coerced to "web-framework")
description: `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 (ex. "create-remix" is used for Remix).
Expand All @@ -127,6 +134,12 @@ const cliDefinition: ArgumentsDefinition = {
{ name: "vue" },
],
},
{
name: "lang",
type: "string",
description: `The programming language of the template`,
values: [{ name: "ts" }, { name: "js" }, { name: "python" }],
},
{
name: "deploy",
type: "boolean",
Expand All @@ -136,6 +149,7 @@ const cliDefinition: ArgumentsDefinition = {
name: "ts",
type: "boolean",
description: "Use TypeScript in your application",
hidden: true,
},
{
name: "git",
Expand Down
Loading

0 comments on commit 5aa4753

Please sign in to comment.