Add github deployments to wrangler action for pages parity

This commit is contained in:
Maximo Guk 2024-11-13 16:20:24 -06:00
parent 64b6339110
commit d58f116d72
No known key found for this signature in database
GPG key ID: 6ACC2847315F8810
7 changed files with 158 additions and 1 deletions

View file

@ -172,6 +172,9 @@ jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Deploy name: Deploy
permissions:
contents: read
deployments: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Deploy - name: Deploy
@ -180,6 +183,8 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy YOUR_DIST_FOLDER --project-name=example command: pages deploy YOUR_DIST_FOLDER --project-name=example
# Optional: Enable this if you want to have GitHub Deployments triggered
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
``` ```
### Deploying on a schedule ### Deploying on a schedule

View file

@ -44,6 +44,9 @@ inputs:
packageManager: packageManager:
description: "The package manager you'd like to use to install and run wrangler. If not specified, the preferred package manager will be inferred based on the presence of a lockfile or fallback to using npm if no lockfile is found. Valid values are `npm` | `pnpm` | `yarn` | `bun`." description: "The package manager you'd like to use to install and run wrangler. If not specified, the preferred package manager will be inferred based on the presence of a lockfile or fallback to using npm if no lockfile is found. Valid values are `npm` | `pnpm` | `yarn` | `bun`."
required: false required: false
githubToken:
description: "GitHub Token"
required: false
outputs: outputs:
command-output: command-output:
description: "The output of the Wrangler command (comes from stdout)" description: "The output of the Wrangler command (comes from stdout)"

View file

@ -43,7 +43,7 @@ export async function execShell(
await promise; await promise;
return child.exitCode; return child.exitCode;
} catch (err: any) { } catch (err) {
if (isExecAsyncException(err)) { if (isExecAsyncException(err)) {
process.stderr.write(err.stderr); process.stderr.write(err.stderr);
throw new Error(`Process failed with exit code ${err.code}`); throw new Error(`Process failed with exit code ${err.code}`);

78
src/github.ts Normal file
View file

@ -0,0 +1,78 @@
import { summary } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import { env } from "process";
import { WranglerActionConfig } from "./wranglerAction";
type Octokit = ReturnType<typeof getOctokit>;
export async function createGitHubDeployment({
config,
octokit,
productionBranch,
environment,
deploymentId,
projectName,
deploymentUrl,
}: {
config: WranglerActionConfig;
octokit: Octokit;
productionBranch: string;
environment: string;
deploymentId: string | null;
projectName: string;
deploymentUrl?: string;
}) {
const githubBranch = env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME;
const productionEnvironment = githubBranch === productionBranch;
const deployment = await octokit.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: githubBranch || context.ref,
auto_merge: false,
description: "Cloudflare Pages",
required_contexts: [],
environment,
production_environment: productionEnvironment,
});
if (deployment.status === 201) {
await octokit.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.data.id,
environment,
environment_url: deploymentUrl,
production_environment: productionEnvironment,
// don't have project_name or deployment_id I think
log_url: `https://dash.cloudflare.com/${config.CLOUDFLARE_ACCOUNT_ID}/pages/view/${projectName}/${deploymentId}`,
description: "Cloudflare Pages",
state: "success",
auto_inactive: false,
});
}
}
export async function createJobSummary({
commitHash,
deploymentUrl,
aliasUrl,
}: {
commitHash: string;
deploymentUrl?: string;
aliasUrl?: string;
}) {
await summary
.addRaw(
`
# Deploying with Cloudflare Pages
| Name | Result |
| ----------------------- | - |
| **Last commit:** | ${commitHash} |
| **Preview URL**: | ${deploymentUrl} |
| **Branch Preview URL**: | ${aliasUrl} |
`,
)
.write();
}

View file

@ -26,6 +26,7 @@ const config: WranglerActionConfig = {
tmpdir(), tmpdir(),
`wranglerArtifacts-${crypto.randomUUID()}`, `wranglerArtifacts-${crypto.randomUUID()}`,
)}`, )}`,
GITHUB_TOKEN: getInput("gitHubToken", { required: false }),
} as const; } as const;
const packageManager = getPackageManager(config.PACKAGE_MANAGER, { const packageManager = getPackageManager(config.PACKAGE_MANAGER, {

View file

@ -15,6 +15,8 @@ import { exec, execShell } from "./exec";
import { PackageManager } from "./packageManagers"; import { PackageManager } from "./packageManagers";
import { semverCompare } from "./utils"; import { semverCompare } from "./utils";
import { getDetailedPagesDeployOutput } from "./wranglerArtifactManager"; import { getDetailedPagesDeployOutput } from "./wranglerArtifactManager";
import { createGitHubDeployment, createJobSummary } from "./github";
import { getOctokit } from "@actions/github";
export type WranglerActionConfig = z.infer<typeof wranglerActionConfig>; export type WranglerActionConfig = z.infer<typeof wranglerActionConfig>;
export const wranglerActionConfig = z.object({ export const wranglerActionConfig = z.object({
@ -30,6 +32,7 @@ export const wranglerActionConfig = z.object({
QUIET_MODE: z.boolean(), QUIET_MODE: z.boolean(),
PACKAGE_MANAGER: z.string(), PACKAGE_MANAGER: z.string(),
WRANGLER_OUTPUT_DIR: z.string(), WRANGLER_OUTPUT_DIR: z.string(),
GITHUB_TOKEN: z.string(),
}); });
function info( function info(
@ -424,6 +427,36 @@ async function wranglerCommands(
setOutput("pages-deployment-alias-url", pagesArtifactFields.alias); setOutput("pages-deployment-alias-url", pagesArtifactFields.alias);
setOutput("pages-deployment-id", pagesArtifactFields.deployment_id); setOutput("pages-deployment-id", pagesArtifactFields.deployment_id);
setOutput("pages-environment", pagesArtifactFields.environment); setOutput("pages-environment", pagesArtifactFields.environment);
// create github deployment, if GITHUB_TOKEN is provided
if (
config.GITHUB_TOKEN &&
pagesArtifactFields.production_branch &&
pagesArtifactFields.project_name &&
pagesArtifactFields.deployment_trigger &&
pagesArtifactFields.stages
) {
const octokit = getOctokit(config.GITHUB_TOKEN);
await Promise.all([
createGitHubDeployment({
config,
octokit,
deploymentUrl: pagesArtifactFields.url,
productionBranch: pagesArtifactFields.production_branch,
environment: pagesArtifactFields.environment,
deploymentId: pagesArtifactFields.deployment_id,
projectName: pagesArtifactFields.project_name,
}),
createJobSummary({
commitHash:
pagesArtifactFields.deployment_trigger.metadata.commit_hash.substring(
0,
8,
),
deploymentUrl: pagesArtifactFields.url,
aliasUrl: pagesArtifactFields.alias,
}),
]);
}
} else { } else {
info( info(
config, config,

View file

@ -14,6 +14,43 @@ const OutputEntryPagesDeployment = OutputEntryBase.merge(
url: z.string().optional(), url: z.string().optional(),
alias: z.string().optional(), alias: z.string().optional(),
environment: z.enum(["production", "preview"]), environment: z.enum(["production", "preview"]),
// optional, added in wrangler@TBD
project_name: z.string().optional(),
// optional, added in wrangler@TBD
production_branch: z.string().optional(),
// optional, added in wrangler@TBD
stages: z
.array(
z.object({
name: z.enum([
"queued",
"initialize",
"clone_repo",
"build",
"deploy",
]),
status: z.enum([
"idle",
"active",
"canceled",
"success",
"failure",
"skipped",
]),
started_on: z.string().nullable(),
ended_on: z.string().nullable(),
}),
)
.optional(),
// optional, added in wrangler@TBD
deployment_trigger: z
.object({
metadata: z.object({
/** Commit hash of the deployment trigger metadata for the pages project */
commit_hash: z.string(),
}),
})
.optional(),
}), }),
); );