Skip to content

Commit

Permalink
reafactor code and add unit tests for parseArgs
Browse files Browse the repository at this point in the history
  • Loading branch information
dario-piotrowicz committed Aug 23, 2023
1 parent 4da5e7f commit 40aca43
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 97 deletions.
1 change: 1 addition & 0 deletions packages/create-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"test:e2e:npm": "npm run build && TEST_PM=npm vitest run --config ./vitest-e2e.config.ts",
"test:e2e:pnpm": "npm run build && TEST_PM=pnpm vitest run --config ./vitest-e2e.config.ts",
"test:unit": "vitest run --config ./vitest.config.ts",
"test:unit:watch": "vitest --config ./vitest.config.ts",
"watch": "node -r esbuild-register scripts/build.ts --watch"
},
"devDependencies": {
Expand Down
71 changes: 9 additions & 62 deletions packages/create-cloudflare/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
#!/usr/bin/env node
// import { TextPrompt, SelectPrompt, ConfirmPrompt } from "@clack/core";
import Haikunator from "haikunator";
import { crash, logRaw, startSection } from "helpers/cli";
import {
C3_DEFAULTS,
WRANGLER_DEFAULTS,
crash,
logRaw,
startSection,
} from "helpers/cli";
import { dim } from "helpers/colors";
import { processArgument } from "helpers/interactive";
import { processArgument } from "helpers/args";

Check warning on line 11 in packages/create-cloudflare/src/cli.ts

View workflow job for this annotation

GitHub Actions / Checks

`helpers/args` import should occur before import of `helpers/cli`

Check warning on line 11 in packages/create-cloudflare/src/cli.ts

View workflow job for this annotation

GitHub Actions / Checks (ubuntu-latest)

`helpers/args` import should occur before import of `helpers/cli`
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { version } from "../package.json";
import { validateProjectDirectory } from "./common";
import { runPagesGenerator } from "./pages";
import { runWorkersGenerator } from "./workers";
import { templateMap } from "./templateMap";
import type { C3Args } from "types";

export const C3_DEFAULTS = {
projectName: new Haikunator().haikunate({ tokenHex: true }),
type: "hello-world",
framework: "angular",
deploy: true,
git: true,
open: true,
ts: true,
};

const WRANGLER_DEFAULTS = {
...C3_DEFAULTS,
deploy: false,
};

export const main = async (argv: string[]) => {
const args = await parseArgs(argv);

Expand Down Expand Up @@ -128,46 +117,4 @@ const parseArgs = async (argv: string[]): Promise<Partial<C3Args>> => {
};
};

type TemplateConfig = {
label: string;
handler: (args: C3Args) => Promise<void>;
hidden?: boolean;
};

const templateMap: Record<string, TemplateConfig> = {
webFramework: {
label: "Website or web app",
handler: runPagesGenerator,
},
"hello-world": {
label: `"Hello World" Worker`,
handler: runWorkersGenerator,
},
common: {
label: "Example router & proxy Worker",
handler: runWorkersGenerator,
},
scheduled: {
label: "Scheduled Worker (Cron Trigger)",
handler: runWorkersGenerator,
},
queues: {
label: "Queue consumer & producer Worker",
handler: runWorkersGenerator,
},
chatgptPlugin: {
label: `ChatGPT plugin`,
handler: (args) =>
runWorkersGenerator({
...args,
ts: true,
}),
},
"pre-existing": {
label: "Pre-existing Worker (from Dashboard)",
handler: runWorkersGenerator,
hidden: true,
},
};

main(process.argv).catch((e) => crash(e));
5 changes: 3 additions & 2 deletions packages/create-cloudflare/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { existsSync, mkdirSync, readdirSync } from "fs";
import { basename, dirname, resolve } from "path";
import { chdir } from "process";
import { getFrameworkCli } from "frameworks/index";
import { processArgument } from "helpers/args";
import {
C3_DEFAULTS,
crash,
endSection,
log,
Expand All @@ -21,12 +23,11 @@ import {
runCommands,
wranglerLogin,
} from "helpers/command";
import { inputPrompt, processArgument, spinner } from "helpers/interactive";
import { inputPrompt, spinner } from "helpers/interactive";
import { detectPackageManager } from "helpers/packages";
import { poll } from "helpers/poll";
import { version as wranglerVersion } from "wrangler/package.json";
import { version } from "../package.json";
import { C3_DEFAULTS } from "./cli";
import type { C3Args, PagesGeneratorContext } from "types";

const { name, npm } = detectPackageManager();
Expand Down
2 changes: 1 addition & 1 deletion packages/create-cloudflare/src/frameworks/next/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mkdirSync } from "fs";
import { processArgument } from "helpers/args";
import { updateStatus, warn } from "helpers/cli";
import { brandColor, dim } from "helpers/colors";
import { installPackages, runFrameworkGenerator } from "helpers/command";
Expand All @@ -10,7 +11,6 @@ import {
writeFile,
writeJSON,
} from "helpers/files";
import { processArgument } from "helpers/interactive";
import { detectPackageManager } from "helpers/packages";
import { getFrameworkCli } from "../index";
import {
Expand Down
83 changes: 83 additions & 0 deletions packages/create-cloudflare/src/helpers/__tests__/args.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, expect, test, vi } from "vitest";
import { parseArgs } from "../args";

vi.mock("yargs/helpers", () => ({ hideBin: (x: unknown) => x }));

describe("Cli", () => {
describe("parseArgs", () => {
test("parsing the first argument as the projectName", async () => {
const result = await parseArgs(["my-project"]);
expect(result.projectName).toBe("my-project");
});

test("not parsing first argument as the projectName if it is after --", async () => {
const result = await parseArgs(["--", "my-project"]);
expect(result.projectName).toBeFalsy();
});

test("parsing optional C3 arguments correctly", async () => {
const result = await parseArgs(["--framework", "angular", "--ts=true"]);
expect(result.projectName).toBeFalsy();
expect(result.framework).toEqual("angular");
expect(result.ts).toEqual(true);
expect(result.additionalArgs).toEqual([]);
});

test("parsing positional + optional C3 arguments correctly", async () => {
const result = await parseArgs([
"my-project",
"--framework",
"angular",
"--deploy",
"true",
"--git=false",
]);
expect(result.projectName).toEqual("my-project");
expect(result.framework).toEqual("angular");
expect(result.deploy).toEqual(true);
expect(result.git).toEqual(false);
expect(result.additionalArgs).toEqual([]);
});

test("parsing optional C3 arguments + additional arguments correctly", async () => {
const result = await parseArgs([
"--framework",
"react",
"--ts=true",
"--",
"positional-arg",
"--react-option",
"5",
]);
expect(result.projectName).toBeFalsy();
expect(result.framework).toEqual("react");
expect(result.ts).toEqual(true);
expect(result.additionalArgs).toEqual([
"positional-arg",
"--react-option",
"5",
]);
});

test("parsing positional + optional C3 arguments + additional arguments correctly", async () => {
const result = await parseArgs([
"my-react-project",
"--framework",
"react",
"--ts=true",
"--",
"positional-arg",
"--react-option",
"5",
]);
expect(result.projectName).toBe("my-react-project");
expect(result.framework).toEqual("react");
expect(result.ts).toEqual(true);
expect(result.additionalArgs).toEqual([
"positional-arg",
"--react-option",
"5",
]);
});
});
});
75 changes: 75 additions & 0 deletions packages/create-cloudflare/src/helpers/args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { version } from "../../package.json";
import { C3_DEFAULTS, WRANGLER_DEFAULTS, logRaw } from "./cli";
import { getRenderers, inputPrompt } from "./interactive";
import type { PromptConfig } from "./interactive";
import type { C3Args } from "types";

export const parseArgs = async (argv: string[]): Promise<Partial<C3Args>> => {
const doubleDashesIdx = argv.indexOf("--");
const c3Args = argv.slice(
0,
doubleDashesIdx < 0 ? undefined : doubleDashesIdx
);
const additionalArgs =
doubleDashesIdx < 0 ? [] : argv.slice(doubleDashesIdx + 1);

const args = await yargs(hideBin(c3Args))
.scriptName("create-cloudflare")
.usage("$0 [args]")
.positional("name", { type: "string" })
.option("type", { type: "string" })
.option("framework", { type: "string" })
.option("deploy", { type: "boolean" })
.option("ts", { type: "boolean" })
.option("git", { type: "boolean" })
.option("open", {
type: "boolean",
default: true,
description:
"opens your browser after your deployment, set --no-open to disable",
})
.option("existing-script", {
type: "string",
hidden: true, // templateMap["pre-existing"].hidden,
})
.option("accept-defaults", {
alias: "y",
type: "boolean",
})
.option("wrangler-defaults", { type: "boolean", hidden: true })
.version(version)
.help().argv;

return {
...(args.wranglerDefaults && WRANGLER_DEFAULTS),
...(args.acceptDefaults && C3_DEFAULTS),
projectName: args._[0] as string | undefined,
additionalArgs,
...args,
};
};

export const processArgument = async <T>(
args: Partial<C3Args>,
name: keyof C3Args,
promptConfig: PromptConfig
) => {
let value = args[name];
const renderSubmitted = getRenderers(promptConfig).submit;

// If the value has already been set via args, use that
if (value !== undefined) {
promptConfig.validate?.(value);

const lines = renderSubmitted({ value });
logRaw(lines.join("\n"));

return value as T;
}

value = await inputPrompt(promptConfig);

return value as T;
};
16 changes: 16 additions & 0 deletions packages/create-cloudflare/src/helpers/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { exit } from "process";
import Haikunator from "haikunator";
import open from "open";
import { brandColor, dim, gray, white, red, hidden, bgRed } from "./colors";

Expand Down Expand Up @@ -125,3 +126,18 @@ export async function openInBrowser(url: string): Promise<void> {
warn("Failed to open browser");
});
}

export const C3_DEFAULTS = {
projectName: new Haikunator().haikunate({ tokenHex: true }),
type: "hello-world",
framework: "angular",
deploy: true,
git: true,
open: true,
ts: true,
};

export const WRANGLER_DEFAULTS = {
...C3_DEFAULTS,
deploy: false,
};
29 changes: 3 additions & 26 deletions packages/create-cloudflare/src/helpers/interactive.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { TextPrompt, SelectPrompt, ConfirmPrompt } from "@clack/core";
import { isCancel } from "@clack/prompts";
import logUpdate from "log-update";
import { shapes, cancel, space, status, newline, logRaw } from "./cli";
import { shapes, cancel, space, status, newline } from "./cli";
import { blue, dim, gray, brandColor, bold } from "./colors";
import type { C3Arg, C3Args } from "types";
import type { C3Arg } from "types";

const grayBar = gray(shapes.bar);
const blCorner = gray(shapes.corners.bl);
Expand Down Expand Up @@ -49,29 +49,6 @@ export type PromptConfig =
| ConfirmPromptConfig
| SelectPromptConfig;

export const processArgument = async <T>(
args: Partial<C3Args>,
name: keyof C3Args,
promptConfig: PromptConfig
) => {
let value = args[name];
const renderSubmitted = getRenderers(promptConfig).submit;

// If the value has already been set via args, use that
if (value !== undefined) {
promptConfig.validate?.(value);

const lines = renderSubmitted({ value });
logRaw(lines.join("\n"));

return value as T;
}

value = await inputPrompt(promptConfig);

return value as T;
};

type RenderProps =
| Omit<SelectPrompt<Option>, "prompt">
| Omit<TextPrompt, "prompt">
Expand Down Expand Up @@ -150,7 +127,7 @@ const handleCancel = () => {
process.exit(0);
};

const getRenderers = (config: PromptConfig) => {
export const getRenderers = (config: PromptConfig) => {
switch (config.type) {
case "select":
return getSelectRenderers(config);
Expand Down
6 changes: 3 additions & 3 deletions packages/create-cloudflare/src/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import { resolve } from "path";
import { chdir } from "process";
import { FrameworkMap, supportedFramework } from "frameworks/index";
import { crash, endSection, startSection } from "helpers/cli";
import { processArgument } from "helpers/args";
import { C3_DEFAULTS, crash, endSection, startSection } from "helpers/cli";
import { dim, brandColor } from "helpers/colors";
import { installWrangler, retry, runCommand } from "helpers/command";
import { readJSON, writeFile } from "helpers/files";
import { processArgument, spinner } from "helpers/interactive";
import { spinner } from "helpers/interactive";
import { detectPackageManager } from "helpers/packages";
import { C3_DEFAULTS } from "./cli";
import {
getProductionBranch,
gitCommit,
Expand Down
Loading

0 comments on commit 40aca43

Please sign in to comment.