Skip to content

Commit

Permalink
Create Pages project command (#782)
Browse files Browse the repository at this point in the history
* Create Pages project command

* Improve success message

* Add pages beta epilogue everywhere
  • Loading branch information
GregBrimble authored Apr 14, 2022
1 parent 5852bba commit 34552d9
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 73 deletions.
7 changes: 7 additions & 0 deletions .changeset/afraid-crabs-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

feature: Add 'pages create project [name]' command.

This command will create a Pages project with a given name, and optionally set its `--production-branch=[production]`.
72 changes: 71 additions & 1 deletion packages/wrangler/src/__tests__/pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,26 @@ describe("subcommand implicit help ran on incomplete command execution", () => {
const projects: Project[] = [
{
name: "dogs",
subdomain: "docs.pages.dev",
domains: ["dogs.pages.dev"],
source: {
type: "github",
},
latest_deployment: {
modified_on: "2021-11-17T14:52:26.133835Z",
},
created_on: "2021-11-17T14:52:26.133835Z",
production_branch: "main",
},
{
name: "cats",
domains: ["cats.pages.dev", "kitten.pages.dev"],
subdomain: "cats.pages.dev",
domains: ["cats.pages.dev", "kitten.com"],
latest_deployment: {
modified_on: "2021-11-17T14:52:26.133835Z",
},
created_on: "2021-11-17T14:52:26.133835Z",
production_branch: "main",
},
];

Expand All @@ -115,13 +121,16 @@ describe("subcommand implicit help ran on incomplete command execution", () => {
for (let i = 0; i < 15; i++) {
projects.push({
name: "dogs" + i,
subdomain: i + "dogs.pages.dev",
domains: [i + "dogs.pages.dev"],
source: {
type: "github",
},
latest_deployment: {
modified_on: "2021-11-17T14:52:26.133835Z",
},
created_on: "2021-11-17T14:52:26.133835Z",
production_branch: "main",
});
}
const requests = mockListRequest(projects);
Expand All @@ -130,6 +139,67 @@ describe("subcommand implicit help ran on incomplete command execution", () => {
});
});

describe("project create", () => {
mockAccountId();
mockApiToken();

afterEach(() => {
unsetAllMocks();
});

it("should create a project with a the default production branch", async () => {
setMockResponse(
"/accounts/:accountId/pages/projects",
([_url, accountId], init) => {
expect(accountId).toEqual("some-account-id");
expect(init.method).toEqual("POST");
const body = JSON.parse(init.body as string);
expect(body).toEqual({
name: "a-new-project",
production_branch: "production",
});
return {
name: "a-new-project",
subdomain: "a-new-project.pages.dev",
production_branch: "production",
};
}
);
await runWrangler("pages project create a-new-project");
expect(std.out).toMatchInlineSnapshot(`
"✨ Successfully created the 'a-new-project' project. It will be available at https://a-new-project.pages.dev/ once you create your first deployment.
To deploy a folder of assets, run 'wrangler pages publish [directory]'."
`);
});

it("should create a project with a the default production branch", async () => {
setMockResponse(
"/accounts/:accountId/pages/projects",
([_url, accountId], init) => {
expect(accountId).toEqual("some-account-id");
expect(init.method).toEqual("POST");
const body = JSON.parse(init.body as string);
expect(body).toEqual({
name: "a-new-project",
production_branch: "main",
});
return {
name: "a-new-project",
subdomain: "a-new-project.pages.dev",
production_branch: "main",
};
}
);
await runWrangler(
"pages project create a-new-project --production-branch=main"
);
expect(std.out).toMatchInlineSnapshot(`
"✨ Successfully created the 'a-new-project' project. It will be available at https://a-new-project.pages.dev/ once you create your first deployment.
To deploy a folder of assets, run 'wrangler pages publish [directory]'."
`);
});
});

describe("deployment list", () => {
mockAccountId();
mockApiToken();
Expand Down
201 changes: 129 additions & 72 deletions packages/wrangler/src/pages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ type ConfigPath = string | undefined;

export type Project = {
name: string;
subdomain: string;
domains: Array<string>;
source?: {
type: string;
};
latest_deployment: {
latest_deployment?: {
modified_on: string;
};
created_on: string;
production_branch: string;
};

export type Deployment = {
Expand Down Expand Up @@ -1098,80 +1101,134 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
)
)
.command("project", false, (yargs) =>
yargs.command(
"list",
"List your Cloudflare Pages projects",
() => {},
async (args) => {
const config = readConfig(args.config as ConfigPath, args);
const accountId = await requireAuth(config);

const projects: Array<Project> = await listProjects({ accountId });

const data = projects.map((project) => {
return {
"Project Name": project.name,
"Project Domains": `${project.domains.join(", ")}`,
"Git Provider": project.source ? "Yes" : "No",
"Last Modified": timeagoFormat(
project.latest_deployment.modified_on
),
};
});
render(<Table data={data}></Table>);
}
)
)
.command("deployment", false, (yargs) =>
yargs.command(
"list",
"List deployments in your Cloudflare Pages project",
(yargs) =>
yargs.options({
project: {
type: "string",
demandOption: true,
description:
"The name of the project you would like to list deployments for",
},
}),
async (args) => {
const config = readConfig(args.config as ConfigPath, args);
const accountId = await requireAuth(config);

const deployments: Array<Deployment> = await fetchResult(
`/accounts/${accountId}/pages/projects/${args.project}/deployments`
);

const titleCase = (word: string) =>
word.charAt(0).toUpperCase() + word.slice(1);

const shortSha = (sha: string) => sha.slice(0, 7);

const getStatus = (deployment: Deployment) => {
// Return a pretty time since timestamp if successful otherwise the status
if (deployment.latest_stage.status === `success`) {
return timeagoFormat(deployment.latest_stage.ended_on);
yargs
.command(
"list",
"List your Cloudflare Pages projects",
(yargs) => yargs.epilogue(pagesBetaWarning),
async (args) => {
const config = readConfig(args.config as ConfigPath, args);
const accountId = await requireAuth(config);

const projects: Array<Project> = await listProjects({ accountId });

const data = projects.map((project) => {
return {
"Project Name": project.name,
"Project Domains": `${project.domains.join(", ")}`,
"Git Provider": project.source ? "Yes" : "No",
"Last Modified": project.latest_deployment
? timeagoFormat(project.latest_deployment.modified_on)
: timeagoFormat(project.created_on),
};
});
render(<Table data={data}></Table>);
}
)
.command(
"create [name]",
"Create a new Cloudflare Pages project",
(yargs) =>
yargs
.positional("name", {
type: "string",
demandOption: true,
description: "The name of your Pages project",
})
.options({
"production-branch": {
type: "string",
// TODO: Should we default to the current git branch?
default: "production",
description:
"The name of the production branch of your project",
},
})
.epilogue(pagesBetaWarning),
async (args) => {
const { "production-branch": productionBranch, name } = args;

if (!name) {
throw new FatalError("Must specify a project name.", 1);
}
return titleCase(deployment.latest_stage.status);
};

const data = deployments.map((deployment) => {
return {
Environment: titleCase(deployment.environment),
Branch: deployment.deployment_trigger.metadata.branch,
Source: shortSha(
deployment.deployment_trigger.metadata.commit_hash
),
Deployment: deployment.url,
Status: getStatus(deployment),
// TODO: Use a url shortener
Build: `https://dash.cloudflare.com/${accountId}/pages/view/${deployment.project_name}/${deployment.id}`,
const config = readConfig(args.config as ConfigPath, args);
const accountId = await requireAuth(config);

const { subdomain } = await fetchResult<Project>(
`/accounts/${accountId}/pages/projects`,
{
method: "POST",
body: JSON.stringify({
name,
production_branch: productionBranch,
}),
}
);
console.log(
`✨ Successfully created the '${name}' project. It will be available at https://${subdomain}/ once you create your first deployment.`
);
console.log(
`To deploy a folder of assets, run 'wrangler pages publish [directory]'.`
);
}
)
.epilogue(pagesBetaWarning)
)
.command("deployment", false, (yargs) =>
yargs
.command(
"list",
"List deployments in your Cloudflare Pages project",
(yargs) =>
yargs
.options({
project: {
type: "string",
demandOption: true,
description:
"The name of the project you would like to list deployments for",
},
})
.epilogue(pagesBetaWarning),
async (args) => {
const config = readConfig(args.config as ConfigPath, args);
const accountId = await requireAuth(config);

const deployments: Array<Deployment> = await fetchResult(
`/accounts/${accountId}/pages/projects/${args.project}/deployments`
);

const titleCase = (word: string) =>
word.charAt(0).toUpperCase() + word.slice(1);

const shortSha = (sha: string) => sha.slice(0, 7);

const getStatus = (deployment: Deployment) => {
// Return a pretty time since timestamp if successful otherwise the status
if (deployment.latest_stage.status === `success`) {
return timeagoFormat(deployment.latest_stage.ended_on);
}
return titleCase(deployment.latest_stage.status);
};
});
render(<Table data={data}></Table>);
}
)

const data = deployments.map((deployment) => {
return {
Environment: titleCase(deployment.environment),
Branch: deployment.deployment_trigger.metadata.branch,
Source: shortSha(
deployment.deployment_trigger.metadata.commit_hash
),
Deployment: deployment.url,
Status: getStatus(deployment),
// TODO: Use a url shortener
Build: `https://dash.cloudflare.com/${accountId}/pages/view/${deployment.project_name}/${deployment.id}`,
};
});
render(<Table data={data}></Table>);
}
)
.epilogue(pagesBetaWarning)
);
};

Expand Down

0 comments on commit 34552d9

Please sign in to comment.