diff --git a/README.md b/README.md index d52b580..5536aa0 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,9 @@ jobs: deploy: runs-on: ubuntu-latest name: Deploy + permissions: + contents: read + deployments: write steps: - uses: actions/checkout@v4 - name: Deploy @@ -180,6 +183,8 @@ jobs: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 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 diff --git a/action.yml b/action.yml index 4493360..eedd189 100644 --- a/action.yml +++ b/action.yml @@ -44,6 +44,9 @@ inputs: 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`." required: false + githubToken: + description: "GitHub Token" + required: false outputs: command-output: description: "The output of the Wrangler command (comes from stdout)" diff --git a/src/exec.ts b/src/exec.ts index e20a2e1..2c5189b 100644 --- a/src/exec.ts +++ b/src/exec.ts @@ -43,7 +43,7 @@ export async function execShell( await promise; return child.exitCode; - } catch (err: any) { + } catch (err) { if (isExecAsyncException(err)) { process.stderr.write(err.stderr); throw new Error(`Process failed with exit code ${err.code}`); diff --git a/src/github.ts b/src/github.ts new file mode 100644 index 0000000..a6332e9 --- /dev/null +++ b/src/github.ts @@ -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; + +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(); +} diff --git a/src/index.ts b/src/index.ts index 24f7cfd..96eed5a 100755 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,7 @@ const config: WranglerActionConfig = { tmpdir(), `wranglerArtifacts-${crypto.randomUUID()}`, )}`, + GITHUB_TOKEN: getInput("gitHubToken", { required: false }), } as const; const packageManager = getPackageManager(config.PACKAGE_MANAGER, { diff --git a/src/wranglerAction.ts b/src/wranglerAction.ts index a41c6c8..c028d9b 100644 --- a/src/wranglerAction.ts +++ b/src/wranglerAction.ts @@ -15,6 +15,8 @@ import { exec, execShell } from "./exec"; import { PackageManager } from "./packageManagers"; import { semverCompare } from "./utils"; import { getDetailedPagesDeployOutput } from "./wranglerArtifactManager"; +import { createGitHubDeployment, createJobSummary } from "./github"; +import { getOctokit } from "@actions/github"; export type WranglerActionConfig = z.infer; export const wranglerActionConfig = z.object({ @@ -30,6 +32,7 @@ export const wranglerActionConfig = z.object({ QUIET_MODE: z.boolean(), PACKAGE_MANAGER: z.string(), WRANGLER_OUTPUT_DIR: z.string(), + GITHUB_TOKEN: z.string(), }); function info( @@ -424,6 +427,36 @@ async function wranglerCommands( setOutput("pages-deployment-alias-url", pagesArtifactFields.alias); setOutput("pages-deployment-id", pagesArtifactFields.deployment_id); 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 { info( config, diff --git a/src/wranglerArtifactManager.ts b/src/wranglerArtifactManager.ts index f12dc77..46882d8 100644 --- a/src/wranglerArtifactManager.ts +++ b/src/wranglerArtifactManager.ts @@ -14,6 +14,43 @@ const OutputEntryPagesDeployment = OutputEntryBase.merge( url: z.string().optional(), alias: z.string().optional(), 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(), }), );