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

[C3] add final commit to pages projects #3776

Merged
merged 32 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b3665fe
c3 add final commit to pages projects
dario-piotrowicz Aug 15, 2023
85a1071
move framework commit message generation inside gitCommit
dario-piotrowicz Aug 15, 2023
8788ea4
add framework cli name and version to framework commit
dario-piotrowicz Aug 16, 2023
5f36888
add package manager info to framework commit
dario-piotrowicz Aug 16, 2023
7b26f2c
run prettify
dario-piotrowicz Aug 16, 2023
b30bacb
make commit header imperative
dario-piotrowicz Aug 16, 2023
6cef3d3
update Web to web
dario-piotrowicz Aug 16, 2023
cdd9cb2
lowercase Create-Cloudflare
dario-piotrowicz Aug 16, 2023
975da3e
refactor generateFrameworkCommitMessage to generateCommit message
dario-piotrowicz Aug 16, 2023
e3e0e42
convert cliMap.json to package.json so that we can leverage dependabot
dario-piotrowicz Aug 16, 2023
3129445
add wrangler version to commit message
dario-piotrowicz Aug 16, 2023
54ac490
add git version to commit
dario-piotrowicz Aug 16, 2023
f81bea2
run prettify
dario-piotrowicz Aug 16, 2023
0eb3c54
move comments to description field
dario-piotrowicz Aug 16, 2023
829bf69
improve git version gathering
dario-piotrowicz Aug 16, 2023
78a9bb4
add commit message to deployment without git
dario-piotrowicz Aug 16, 2023
e066945
fix the commit message addition to wrangler pages deploy
dario-piotrowicz Aug 16, 2023
eb202b0
fix user facing label
dario-piotrowicz Aug 16, 2023
9adc949
fix commit message not being properly passed
dario-piotrowicz Aug 16, 2023
88da1c7
run prettify
dario-piotrowicz Aug 16, 2023
56a3d0a
add e2e for deployment commit message
dario-piotrowicz Aug 17, 2023
c0a8e9a
run prettify
dario-piotrowicz Aug 17, 2023
647f775
fix incorrectly gathering projectName
dario-piotrowicz Aug 17, 2023
64a7226
remove checks failing because of commit message current limit
dario-piotrowicz Aug 21, 2023
62d0588
update qwik's pages:deploy script
dario-piotrowicz Aug 21, 2023
1d78574
don't test commit message for hono
dario-piotrowicz Aug 21, 2023
23241a9
make sure hono doesn't try to use --commit-message
dario-piotrowicz Aug 21, 2023
78d6b62
run prettify
dario-piotrowicz Aug 21, 2023
d596029
fix commitMessage generation on non-pages projects
dario-piotrowicz Aug 21, 2023
91c08b7
remove unused import
dario-piotrowicz Aug 22, 2023
d1373e8
include -- only for npm
dario-piotrowicz Aug 22, 2023
3f02a17
fix linting and formatting
dario-piotrowicz Aug 22, 2023
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
11 changes: 11 additions & 0 deletions .changeset/nasty-dolphins-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"create-cloudflare": minor
---

add final commit when generating Pages projects

before after the user would have completed the creation of a Pages project
they would find the Cloudflare added/modified files uncommitted, instead of
leaving these uncommitted this change adds an extra commit (on top of the
framework specific) which also contains some useful information about the
project
67 changes: 65 additions & 2 deletions packages/create-cloudflare/e2e-tests/pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

type FrameworkTestConfig = RunnerConfig & {
expectResponseToContain: string;
testCommitMessage: boolean;
};

describe(`E2E: Web frameworks`, () => {
Expand Down Expand Up @@ -89,7 +90,10 @@
return { output };
};

const runCliWithDeploy = async (framework: string) => {
const runCliWithDeploy = async (
framework: string,
testCommitMessage: boolean
) => {
const { argv, overrides, promptHandlers, expectResponseToContain } =
frameworkTests[framework];

Expand Down Expand Up @@ -119,15 +123,21 @@
body,
`(${framework}) Deployed page (${projectUrl}) didn't contain expected string: "${expectResponseToContain}"`
).toContain(expectResponseToContain);

if (testCommitMessage) {
await testDeploymentCommitMessage(getName(framework), framework);
}
};

// These are ordered based on speed and reliability for ease of debugging
const frameworkTests: Record<string, FrameworkTestConfig> = {
astro: {
expectResponseToContain: "Hello, Astronaut!",
testCommitMessage: true,
},
hono: {
expectResponseToContain: "Hello Hono!",
testCommitMessage: false,
},
qwik: {
expectResponseToContain: "Welcome to Qwik",
Expand All @@ -137,9 +147,11 @@
input: [keys.enter],
},
],
testCommitMessage: true,
},
remix: {
expectResponseToContain: "Welcome to Remix",
testCommitMessage: true,
},
next: {
expectResponseToContain: "Create Next App",
Expand All @@ -149,6 +161,7 @@
input: ["y"],
},
],
testCommitMessage: true,
},
nuxt: {
expectResponseToContain: "Welcome to Nuxt!",
Expand All @@ -157,9 +170,11 @@
build: "NITRO_PRESET=cloudflare-pages nuxt build",
},
},
testCommitMessage: true,
},
react: {
expectResponseToContain: "React App",
testCommitMessage: true,
},
solid: {
expectResponseToContain: "Hello world",
Expand All @@ -177,6 +192,7 @@
input: [keys.enter],
},
],
testCommitMessage: true,
},
svelte: {
expectResponseToContain: "SvelteKit app",
Expand All @@ -194,16 +210,18 @@
input: [keys.enter],
},
],
testCommitMessage: true,
},
vue: {
expectResponseToContain: "Vite App",
testCommitMessage: true,
},
};

test.concurrent.each(Object.keys(frameworkTests))(
"%s",
async (name) => {
await runCliWithDeploy(name);
await runCliWithDeploy(name, frameworkTests[name].testCommitMessage);
},
{ retry: 3 }
);
Expand All @@ -212,3 +230,48 @@
await runCli("hono", { argv: ["--wrangler-defaults"] });
});
});

const testDeploymentCommitMessage = async (
projectName: string,
framework: string

Check warning on line 236 in packages/create-cloudflare/e2e-tests/pages.test.ts

View workflow job for this annotation

GitHub Actions / Checks

'framework' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 236 in packages/create-cloudflare/e2e-tests/pages.test.ts

View workflow job for this annotation

GitHub Actions / Checks (ubuntu-latest)

'framework' is defined but never used. Allowed unused args must match /^_/u
) => {
// Note: we cannot simply run git and check the result since the commit can be part of the
// deployment even without git, so instead we fetch the deployment info from the pages api
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}/pages/projects`,
{
headers: {
Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
},
}
);

const result = (
(await response.json()) as {
result: {
name: string;
latest_deployment?: {
deployment_trigger: {
metadata?: {
commit_message: string;
};
};
};
}[];
}
).result;

const projectLatestCommitMessage = result.find(
(project) => project.name === projectName
)?.latest_deployment?.deployment_trigger?.metadata?.commit_message;
expect(projectLatestCommitMessage).toMatch(
/^Initialize web application via create-cloudflare CLI/
);
// TODO: add back checks the following when the commit message doesn't
// get truncated at 128 characters
// expect(projectLatestCommitMessage).toContain(
// `C3 = create-cloudflare@${version}`
// );
// expect(projectLatestCommitMessage).toContain(`project name = ${projectName}`);
// expect(projectLatestCommitMessage).toContain(`framework = ${framework}`);
};
132 changes: 107 additions & 25 deletions packages/create-cloudflare/src/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { existsSync, mkdirSync, readdirSync } from "fs";
import { basename, dirname, resolve } from "path";
import { chdir } from "process";
import { getFrameworkCli } from "frameworks/index";
import {
crash,
endSection,
Expand All @@ -23,10 +24,12 @@ import {
import { inputPrompt, processArgument, 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 { npm } = detectPackageManager();
const { name, npm } = detectPackageManager();

export const validateProjectDirectory = (relativePath: string) => {
const path = resolve(relativePath);
Expand All @@ -47,15 +50,15 @@ export const setupProjectDirectory = (args: C3Args) => {
}

const directory = dirname(path);
const name = basename(path);
const pathBasename = basename(path);

// If the target is a nested directory, create the parent
mkdirSync(directory, { recursive: true });

// Change to the parent directory
chdir(directory);

return { name, path };
return { name: pathBasename, path };
};

export const offerToDeploy = async (ctx: PagesGeneratorContext) => {
Expand Down Expand Up @@ -87,15 +90,36 @@ export const runDeploy = async (ctx: PagesGeneratorContext) => {
return;
}

const deployCmd = `${npm} run ${
ctx.framework?.config.deployCommand ?? "deploy"
}`;
const baseDeployCmd = [
npm,
"run",
ctx.framework?.config.deployCommand ?? "deploy",
];

const insideGitRepo = await isInsideGitRepo(ctx.project.path);

const deployCmd = [
...baseDeployCmd,
// Important: the following assumes that all framework deploy commands terminate with `wrangler pages deploy`
...(ctx.framework?.commitMessage && !insideGitRepo
? [
...(name === "npm" ? ["--"] : []),
`--commit-message="${ctx.framework.commitMessage.replaceAll(
'"',
'\\"'
)}"`,
]
: []),
];

const result = await runCommand(deployCmd, {
silent: true,
cwd: ctx.project.path,
env: { CLOUDFLARE_ACCOUNT_ID: ctx.account.id, NODE_ENV: "production" },
startText: `Deploying your application`,
doneText: `${brandColor("deployed")} ${dim(`via \`${deployCmd}\``)}`,
startText: "Deploying your application",
doneText: `${brandColor("deployed")} ${dim(
`via \`${baseDeployCmd.join(" ")}\``
)}`,
});

const deployedUrlRegex = /https:\/\/.+\.(pages|workers)\.dev/;
Expand Down Expand Up @@ -130,10 +154,12 @@ export const chooseAccount = async (ctx: PagesGeneratorContext) => {
s.stop(
`${brandColor("account")} ${dim("more than one account available")}`
);
const accountOptions = Object.entries(accounts).map(([name, id]) => ({
label: name,
value: id,
}));
const accountOptions = Object.entries(accounts).map(
([accountName, id]) => ({
label: accountName,
value: id,
})
);

accountId = await inputPrompt({
type: "select",
Expand All @@ -144,7 +170,7 @@ export const chooseAccount = async (ctx: PagesGeneratorContext) => {
});
}
const accountName = Object.keys(accounts).find(
(name) => accounts[name] == accountId
(account) => accounts[account] == accountId
) as string;

ctx.account = { id: accountId, name: accountName };
Expand Down Expand Up @@ -250,33 +276,89 @@ export const offerGit = async (ctx: PagesGeneratorContext) => {
};

export const gitCommit = async (ctx: PagesGeneratorContext) => {
if (!ctx.args.git) return;
// Note: createCommitMessage stores the message in ctx so that it can
// be used later even if we're not in a git repository, that's why
// we unconditionally run this command here
const commitMessage = await createCommitMessage(ctx);

if (!(await isGitInstalled()) || !(await isInsideGitRepo(ctx.project.path)))
return;

await runCommands({
silent: true,
cwd: ctx.project.path,
commands: [
"git add .",
["git", "commit", "-m", "Initial commit (by Create-Cloudflare CLI)"],
],
commands: ["git add .", ["git", "commit", "-m", commitMessage]],
startText: "Committing new files",
doneText: `${brandColor("git")} ${dim(`initial commit`)}`,
doneText: `${brandColor("git")} ${dim(`commit`)}`,
});
};

const createCommitMessage = async (ctx: PagesGeneratorContext) => {
if (!ctx.framework) return "Initial commit (by create-cloudflare CLI)";

const header = "Initialize web application via create-cloudflare CLI";

const packageManager = detectPackageManager();

const gitVersion = await getGitVersion();
const insideRepo = await isInsideGitRepo(ctx.project.path);

const details = [
{ key: "C3", value: `create-cloudflare@${version}` },
{ key: "project name", value: ctx.project.name },
{ key: "framework", value: ctx.framework.name },
{ key: "framework cli", value: getFrameworkCli(ctx) },
{
key: "package manager",
value: `${packageManager.name}@${packageManager.version}`,
},
{
key: "wrangler",
value: `wrangler@${wranglerVersion}`,
},
{
key: "git",
value: insideRepo ? gitVersion : "N/A",
},
];

const body = `Details:\n${details
.map(({ key, value }) => ` ${key} = ${value}`)
.join("\n")}\n`;

const commitMessage = `${header}\n\n${body}\n`;

if (ctx.type !== "workers") {
ctx.framework.commitMessage = commitMessage;
}

return commitMessage;
};

/**
* Check whether git is available on the user's machine.
* Return the version of git on the user's machine, or null if git is not available.
*/
export async function isGitInstalled() {
async function getGitVersion() {
try {
await runCommand("git -v", { useSpinner: false, silent: true });

return true;
const rawGitVersion = await runCommand("git --version", {
useSpinner: false,
silent: true,
});
// let's remove the "git version " prefix as it isn't really helpful
const gitVersion = rawGitVersion.replace(/^git\s+version\s+/, "");
return gitVersion;
} catch {
return false;
return null;
}
}

/**
* Check whether git is available on the user's machine.
*/
async function isGitInstalled() {
return (await getGitVersion()) !== null;
}

/**
* Check whether the given current working directory is within a git repository
* by looking for a `.git` directory in this or an ancestor directory.
Expand Down
Loading
Loading