Skip to content

Commit

Permalink
C3: Automatically use the latest version of c3 if not running latest (#…
Browse files Browse the repository at this point in the history
…3803)

* C3: Automatically use the latest version of c3 if not running latest

* Responding to PR feedback

* Fixing lint issue

* Skip auto-updates for non interactive environments

* fix lint issue

* Don't auto-update to new major versions

* Update changeset to indicate minor bump
  • Loading branch information
jculvey authored Aug 24, 2023
1 parent 6b6788d commit 9156994
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/nervous-insects-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-cloudflare": minor
---

C3: Checks for a newer version of create-cloudflare and uses it if available. This behavior can be suppressed with the --no-auto-update flag.
7 changes: 5 additions & 2 deletions packages/create-cloudflare/e2e-tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { tmpdir } from "os";
import { join } from "path";
import { spawn } from "cross-spawn";
import { spinnerFrames } from "helpers/interactive";
import type { SpinnerStyle } from "helpers/interactive";

export const C3_E2E_PREFIX = "c3-e2e-";

Expand Down Expand Up @@ -122,8 +123,10 @@ export const condenseOutput = (lines: string[]) => {

const filterLine = (line: string) => {
// Remove all lines with spinners
for (const frame of spinnerFrames) {
if (line.includes(frame)) return false;
for (const spinnerType of Object.keys(spinnerFrames)) {
for (const frame of spinnerFrames[spinnerType as SpinnerStyle]) {
if (line.includes(frame)) return false;
}
}

// Remove empty lines
Expand Down
101 changes: 90 additions & 11 deletions packages/create-cloudflare/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
#!/usr/bin/env node
// import { TextPrompt, SelectPrompt, ConfirmPrompt } from "@clack/core";
import Haikunator from "haikunator";
import { crash, logRaw, startSection } from "helpers/cli";
import { dim } from "helpers/colors";
import { processArgument } from "helpers/interactive";
import { blue, dim } from "helpers/colors";
import { runCommand } from "helpers/command";
import {
isInteractive,
processArgument,
spinner,
spinnerFrames,
} from "helpers/interactive";
import { detectPackageManager } from "helpers/packages";
import semver from "semver";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { version } from "../package.json";
Expand All @@ -16,6 +23,7 @@ export const C3_DEFAULTS = {
projectName: new Haikunator().haikunate({ tokenHex: true }),
type: "hello-world",
framework: "angular",
autoUpdate: true,
deploy: true,
git: true,
open: true,
Expand All @@ -27,9 +35,51 @@ const WRANGLER_DEFAULTS = {
deploy: false,
};

const { npm } = detectPackageManager();

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

// Print a newline
logRaw("");

if (args.autoUpdate && (await isUpdateAvailable())) {
await runLatest();
} else {
await runCli(args);
}
};

// Detects if a newer version of c3 is available by comparing the version
// specified in package.json with the `latest` tag from npm
const isUpdateAvailable = async () => {
if (process.env.VITEST || process.env.CI || !isInteractive()) {
return false;
}

// Use a spinner when running this check since it may take some time
const s = spinner(spinnerFrames.vertical, blue);
s.start("Checking if a newer version is available");
const latestVersion = await runCommand(
`npm info create-cloudflare@latest dist-tags.latest`,
{ silent: true, useSpinner: false }
);
s.stop();

// Don't auto-update to major versions
if (semver.diff(latestVersion, version) === "major") return false;

return semver.gt(latestVersion, version);
};

// Spawn a separate process running the most recent version of c3
export const runLatest = async () => {
const args = process.argv.slice(2);
await runCommand(`${npm} create cloudflare@latest ${args.join(" ")}`);
};

// Entrypoint to c3
export const runCli = async (args: Partial<C3Args>) => {
printBanner();

const projectName = await processArgument<string>(args, "projectName", {
Expand Down Expand Up @@ -79,32 +129,61 @@ export const main = async (argv: string[]) => {
};

const printBanner = () => {
logRaw(dim(`\nusing create-cloudflare version ${version}\n`));
logRaw(dim(`using create-cloudflare version ${version}\n`));
startSection(`Create an application with Cloudflare`, "Step 1 of 3");
};

export const parseArgs = async (argv: string[]): Promise<Partial<C3Args>> => {
const args = await yargs(hideBin(argv))
.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" })
.positional("name", {
type: "string",
description:
"The name of your application. Will be used as the directory name",
})
.option("type", {
type: "string",
description: `The base template to use when scaffolding your application`,
})
.option("framework", {
type: "string",
description:
"When using the `webApp` template, specifies the desired framework",
})
.option("deploy", {
type: "boolean",
description: "Deploy your application to Cloudflare after scaffolding",
})
.option("auto-update", {
type: "boolean",
default: C3_DEFAULTS.autoUpdate,
description:
"Automatically uses the latest version of `create-cloudflare`. Set --no-auto-update to disable",
})
.option("ts", {
type: "boolean",
description: "Adds typescript support to your application",
})
.option("git", {
type: "boolean",
description: "Initializes a git repository after scaffolding",
})
.option("open", {
type: "boolean",
default: true,
description:
"opens your browser after your deployment, set --no-open to disable",
"Opens your browser after your deployment, set --no-open to disable",
})
.option("existing-script", {
type: "string",
description:
"An existing workers script to initialize an application from",
hidden: templateMap["pre-existing"].hidden,
})
.option("accept-defaults", {
alias: "y",
description: "Accept all defaults and bypass interactive prompts",
type: "boolean",
})
.option("wrangler-defaults", { type: "boolean", hidden: true })
Expand Down
21 changes: 17 additions & 4 deletions packages/create-cloudflare/src/helpers/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isCancel } from "@clack/prompts";
import logUpdate from "log-update";
import { shapes, cancel, space, status, newline, logRaw } from "./cli";
import { blue, dim, gray, brandColor, bold } from "./colors";
import type { ChalkInstance } from "chalk";
import type { C3Arg, C3Args } from "types";

const grayBar = gray(shapes.bar);
Expand Down Expand Up @@ -259,10 +260,19 @@ const getConfirmRenderers = (config: ConfirmPromptConfig) => {
};
};

export const spinnerFrames = ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"];
export type SpinnerStyle = keyof typeof spinnerFrames;

export const spinnerFrames = {
clockwise: ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"],
vertical: ["▁", "▃", "▄", "▅", "▆", "▇", "▆", "▅", "▄", "▃"],
};

const ellipsisFrames = ["", ".", "..", "...", " ..", " .", ""];

export const spinner = () => {
export const spinner = (
frames: string[] = spinnerFrames.clockwise,
color: ChalkInstance = brandColor
) => {
// Alternative animations we considered. Keeping around in case we
// introduce different animations for different use cases.
// const frames = ["▁", "▃", "▄", "▅", "▆", "▇", "▆", "▅", "▄", "▃"];
Expand All @@ -272,7 +282,6 @@ export const spinner = () => {
// const frames = ["◐", "◓", "◑", "◒"];
// const frames = ["㊂", "㊀", "㊁"];

const color = brandColor;
const frameRate = 120;
let loop: NodeJS.Timer | null = null;
let startMsg: string;
Expand All @@ -296,7 +305,7 @@ export const spinner = () => {
clearLoop();
loop = setInterval(() => {
index++;
const spinnerFrame = spinnerFrames[index % spinnerFrames.length];
const spinnerFrame = frames[index % frames.length];
const ellipsisFrame = ellipsisFrames[index % ellipsisFrames.length];

if (msg) {
Expand All @@ -319,3 +328,7 @@ export const spinner = () => {
},
};
};

export const isInteractive = () => {
return process.stdin.isTTY;
};
1 change: 1 addition & 0 deletions packages/create-cloudflare/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type C3Args = {
deploy?: boolean;
open?: boolean;
git?: boolean;
autoUpdate?: boolean;
// pages specific
framework?: string;
// workers specific
Expand Down
2 changes: 1 addition & 1 deletion packages/create-cloudflare/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

"pipeline": {
"build": {
"env": ["VITEST", "TEST_PM"]
"env": ["VITEST", "TEST_PM", "CI"]
},
"test:e2e:*": {
"env": ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"]
Expand Down

0 comments on commit 9156994

Please sign in to comment.