Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(create-cloudflare): prompt user to correct the answer if the arg value is invalid #7055

Merged
merged 6 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shiny-files-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-cloudflare": minor
---

feat: prompt user to correct the answer if the arg value is invalid
11 changes: 10 additions & 1 deletion packages/cli/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export type BasePromptConfig = {
helpText?: string;
// The value to use by default
defaultValue?: Arg;
// The error message to display if the initial value is invalid
initialErrorMessage?: string | null;
// Accept the initialValue/defaultValue as if the user pressed ENTER when prompted
acceptDefault?: boolean;
// The status label to be shown after submitting
Expand Down Expand Up @@ -141,7 +143,14 @@ export const inputPrompt = async <T = string>(

// Looks up the needed renderer by the current state ('initial', 'submitted', etc.)
const dispatchRender = (props: RenderProps, p: Prompt): string | void => {
const renderedLines = renderers[props.state](props, p);
let state = props.state;

if (state === "initial" && promptConfig.initialErrorMessage) {
state = "error";
props.error = promptConfig.initialErrorMessage;
}

const renderedLines = renderers[state](props, p);
return renderedLines.join("\n");
};

Expand Down
80 changes: 57 additions & 23 deletions packages/create-cloudflare/e2e-tests/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from "node:fs";
import { basename } from "node:path";
import { beforeAll, describe, expect } from "vitest";
import { version } from "../package.json";
import { getFrameworkToTest } from "./frameworkToTest";
Expand Down Expand Up @@ -133,30 +135,61 @@ describe.skipIf(experimental || frameworkToTest || isQuarantineMode())(
test({ experimental }).skipIf(process.platform === "win32")(
"Mixed args and interactive",
async ({ logStream, project }) => {
const { output } = await runC3(
[project.path, "--ts", "--no-deploy"],
[
{
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 use git for version control/,
input: ["n"],
},
],
logStream,
const projectName = basename(project.path);
const existingProjectName = Array.from(projectName).reverse().join("");
const existingProjectPath = project.path.replace(
projectName,
existingProjectName,
);
const existingFilePath = `${existingProjectPath}/example.json`;

expect(project.path).toExist();
expect(output).toContain(`type Hello World Worker`);
expect(output).toContain(`lang TypeScript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
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 use git for version control/,
input: ["n"],
},
],
logStream,
);

expect(project.path).toExist();
expect(output).toContain(`type Hello World Worker`);
expect(output).toContain(`lang TypeScript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
} finally {
fs.rmSync(existingFilePath, {
recursive: true,
force: true,
maxRetries: 10,
retryDelay: 100,
});
}
},
);

Expand Down Expand Up @@ -243,8 +276,9 @@ describe.skipIf(experimental || frameworkToTest || isQuarantineMode())(
test({ experimental }).skipIf(process.platform === "win32")(
"Going back and forth between the category, type, framework and lang prompts",
async ({ logStream, project }) => {
const testProjectPath = "/test-project-path";
const { output } = await runC3(
["/invalid-project-name", "--git=false", "--no-deploy"],
[testProjectPath, "--git=false", "--no-deploy"],
[
{
matcher: /What would you like to start with\?/,
Expand Down
21 changes: 21 additions & 0 deletions packages/create-cloudflare/e2e-tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export type PromptHandler = {
matcher: RegExp;
input:
| string[]
| {
type: "text";
chunks: string[];
assertErrorMessage?: string;
}
| {
type: "select";
target: RegExp | string;
Expand Down Expand Up @@ -117,6 +122,22 @@ export const runC3 = async (
currentDialog.input.forEach((keystroke) => {
proc.stdin.write(keystroke);
});
} else if (currentDialog.input.type === "text") {
// text prompt handler
const { assertErrorMessage, chunks } = currentDialog.input;

if (
assertErrorMessage !== undefined &&
!text.includes(assertErrorMessage)
) {
throw new Error(
`The error message does not match; Expected "${assertErrorMessage}" but found "${text}".`,
);
}

chunks.forEach((keystroke) => {
proc.stdin.write(keystroke);
});
} else if (currentDialog.input.type === "select") {
// select prompt handler

Expand Down
5 changes: 4 additions & 1 deletion packages/create-cloudflare/src/helpers/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,11 +415,14 @@ export const processArgument = async <Key extends keyof C3Args>(
disableTelemetry: args[key] !== undefined,
async promise() {
const value = args[key];
const error = promptConfig.validate?.(value) ?? null;
const result = await inputPrompt<Required<C3Args>[Key]>({
...promptConfig,
// Accept the default value if the arg is already set
acceptDefault: promptConfig.acceptDefault ?? value !== undefined,
acceptDefault:
promptConfig.acceptDefault ?? (value !== undefined && !error),
defaultValue: value ?? promptConfig.defaultValue,
initialErrorMessage: error,
throwOnError: true,
});

Expand Down
Loading