From 7db40c41d4e2d28a6c71c215fbd2f6f29a413a85 Mon Sep 17 00:00:00 2001 From: WhatACotton Date: Thu, 18 Apr 2024 23:27:28 +0900 Subject: [PATCH] feat: specify production branch --- .github/workflows/example.yml | 2 + action.yml | 3 ++ index.js | 87 +++++++++++++++++++++++++++++++++-- src/generateAlias.ts | 61 ++++++++++++++++++++++++ src/index.ts | 46 +++++++++++++++--- 5 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 src/generateAlias.ts diff --git a/.github/workflows/example.yml b/.github/workflows/example.yml index ade64d3..a97211f 100644 --- a/.github/workflows/example.yml +++ b/.github/workflows/example.yml @@ -18,6 +18,8 @@ jobs: projectName: github-actions-example directory: example gitHubToken: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.head_ref || github.ref_name }} + productionBranch: main id: publish - name: Outputs run: | diff --git a/action.yml b/action.yml index 61e3f56..da342c5 100644 --- a/action.yml +++ b/action.yml @@ -19,6 +19,9 @@ inputs: gitHubToken: description: "GitHub Token" required: false + productionBranch: + description: "The name of the branch you want to deploy to production" + required: true branch: description: "The name of the branch you want to deploy to" required: false diff --git a/index.js b/index.js index 7df67c8..9f0f986 100644 --- a/index.js +++ b/index.js @@ -22061,6 +22061,55 @@ var src_default = shellac; var import_undici = __toESM(require_undici()); var import_process = require("process"); var import_node_path = __toESM(require("path")); + +// src/generateAlias.ts +var invalidCharsRegex = /[^a-z0-9-]/g; +var maxAliasLength = 28; +var alphanum = "abcdefghijklmnopqrstuvwxyz0123456789"; +var generateURL = (branch, URL2) => { + const generatedBranch = generateBranchAlias(branch); + if (!generatedBranch) { + return ""; + } + const url = URL2.split("."); + url[0] = "https://" + branch; + return url.join("."); +}; +function generateBranchAlias(branch) { + let normalised = branch.toLowerCase().replace(invalidCharsRegex, "-"); + if (normalised.length > maxAliasLength) { + normalised = normalised.substring(0, maxAliasLength); + } + normalised = trim(normalised, "-"); + if (normalised === "") { + return `branch-${randAlphaNum(10)}`; + } + return normalised; +} +function trim(str, char) { + while (str.startsWith(char)) { + if (str.length === 1) { + return ""; + } + str = str.substring(1); + } + while (str.endsWith(char)) { + if (str.length === 1) { + return ""; + } + str = str.substring(0, str.length - 1); + } + return str; +} +function randAlphaNum(length) { + let result = ""; + for (let i = 0; i < length; i++) { + result += alphanum[Math.floor(Math.random() * alphanum.length)]; + } + return result; +} + +// src/index.ts try { const apiToken = (0, import_core.getInput)("apiToken", { required: true }); const accountId = (0, import_core.getInput)("accountId", { required: true }); @@ -22068,8 +22117,32 @@ try { const directory = (0, import_core.getInput)("directory", { required: true }); const gitHubToken = (0, import_core.getInput)("gitHubToken", { required: false }); const branch = (0, import_core.getInput)("branch", { required: false }); + const production_branch = (0, import_core.getInput)("productionBranch", { required: true }); const workingDirectory = (0, import_core.getInput)("workingDirectory", { required: false }); const wranglerVersion = (0, import_core.getInput)("wranglerVersion", { required: false }); + const setProductionBranch = async () => { + const response = await (0, import_undici.fetch)( + `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}`, + { + headers: { + Authorization: `Bearer ${apiToken}`, + "Content-Type": "application/json" + }, + method: "PATCH", + body: JSON.stringify({ + production_branch + }) + } + ); + const json = await response.json(); + if (response.status !== 200) { + console.error(`Cloudflare API returned non-200: ${response.status}`); + const json2 = await response.text(); + console.error(`API returned: ${json2}`); + throw new Error("Failed to set production branch, API returned non-200"); + } + return { before_branch: json.result.latest_deployment.production_branch, current_branch: json.result.production_branch }; + }; const getProject = async () => { const response = await (0, import_undici.fetch)( `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}`, @@ -22159,11 +22232,12 @@ try { | **Last commit:** | \`${deployment.deployment_trigger.metadata.commit_hash.substring(0, 8)}\` | | **Status**: | ${status} | | **Preview URL**: | ${deployment.url} | -| **Branch Preview URL**: | ${aliasUrl} | +| **Branch Preview URL**: | ${aliasUrl ? aliasUrl : "This URL was not generated because the branch is the production branch."} | ` ).write(); }; (async () => { + const { before_branch, current_branch } = await setProductionBranch(); const project = await getProject(); const productionEnvironment = githubBranch === project.production_branch || branch === project.production_branch; const environmentName = `${projectName} (${productionEnvironment ? "Production" : "Preview"})`; @@ -22173,13 +22247,13 @@ try { gitHubDeployment = await createGitHubDeployment(octokit, productionEnvironment, environmentName); } const pagesDeployment = await createPagesDeployment(); + let alias = ""; + if (branch != production_branch) { + alias = generateURL(branch, pagesDeployment.url); + } (0, import_core.setOutput)("id", pagesDeployment.id); (0, import_core.setOutput)("url", pagesDeployment.url); (0, import_core.setOutput)("environment", pagesDeployment.environment); - let alias = pagesDeployment.url; - if (!productionEnvironment && pagesDeployment.aliases && pagesDeployment.aliases.length > 0) { - alias = pagesDeployment.aliases[0]; - } (0, import_core.setOutput)("alias", alias); await createJobSummary({ deployment: pagesDeployment, aliasUrl: alias }); if (gitHubDeployment) { @@ -22193,6 +22267,9 @@ try { octokit }); } + if (before_branch !== current_branch) { + await import_core.summary.addRaw(`Production branch change from ${before_branch} to ${current_branch} succeeded!`).write(); + } })(); } catch (thrown) { (0, import_core.setFailed)(thrown.message); diff --git a/src/generateAlias.ts b/src/generateAlias.ts new file mode 100644 index 0000000..2857395 --- /dev/null +++ b/src/generateAlias.ts @@ -0,0 +1,61 @@ +// Author: Daniel Walsh (https://github.com/WalshyDev) +// Source: https://community.cloudflare.com/t/algorithm-to-generate-a-preview-dns-subdomain-from-a-branch-name/477633/2 +// License: ? +// +// Modified by: Michael Schnerring + +const invalidCharsRegex = /[^a-z0-9-]/g +const maxAliasLength = 28 +const alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789' +export const generateURL = (branch: string, URL: string): string => { + const generatedBranch = generateBranchAlias(branch) + if (!generatedBranch) { + return "" + } + const url = URL.split("."); + url[0] = "https://" + branch; + return url.join(".") +} +function generateBranchAlias(branch: string): string | undefined { + let normalised = branch.toLowerCase().replace(invalidCharsRegex, '-') + + if (normalised.length > maxAliasLength) { + normalised = normalised.substring(0, maxAliasLength) + } + + normalised = trim(normalised, '-') + + if (normalised === '') { + return `branch-${randAlphaNum(10)}` + } + + return normalised +} + +function trim(str: string, char: string): string { + while (str.startsWith(char)) { + if (str.length === 1) { + return '' + } + str = str.substring(1) + } + + while (str.endsWith(char)) { + if (str.length === 1) { + return '' + } + str = str.substring(0, str.length - 1) + } + + return str +} + +function randAlphaNum(length: number): string { + let result = '' + + for (let i = 0; i < length; i++) { + result += alphanum[Math.floor(Math.random() * alphanum.length)] + } + + return result +} diff --git a/src/index.ts b/src/index.ts index a63f38e..f3881e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import shellac from "shellac"; import { fetch } from "undici"; import { env } from "process"; import path from "node:path"; +import { generateURL } from "./generateAlias"; type Octokit = ReturnType; @@ -15,8 +16,32 @@ try { const directory = getInput("directory", { required: true }); const gitHubToken = getInput("gitHubToken", { required: false }); const branch = getInput("branch", { required: false }); + const production_branch = getInput("productionBranch", { required: true }); const workingDirectory = getInput("workingDirectory", { required: false }); const wranglerVersion = getInput("wranglerVersion", { required: false }); + const setProductionBranch = async (): Promise<{ before_branch: string, current_branch: string }> => { + const response = await fetch( + `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}`, + { + headers: { + Authorization: `Bearer ${apiToken}`, + "Content-Type": "application/json", + }, + method: "PATCH", + body: JSON.stringify({ + production_branch: production_branch, + }), + } + ); + const json: any = await response.json(); + if (response.status !== 200) { + console.error(`Cloudflare API returned non-200: ${response.status}`); + const json = await response.text(); + console.error(`API returned: ${json}`); + throw new Error("Failed to set production branch, API returned non-200"); + } + return { before_branch: json.result.latest_deployment.production_branch, current_branch: json.result.production_branch }; + } const getProject = async () => { const response = await fetch( @@ -109,7 +134,7 @@ try { }); }; - const createJobSummary = async ({ deployment, aliasUrl }: { deployment: Deployment; aliasUrl: string }) => { + const createJobSummary = async ({ deployment, aliasUrl }: { deployment: Deployment; aliasUrl: string | undefined }) => { const deployStage = deployment.stages.find((stage) => stage.name === "deploy"); let status = "⚡️ Deployment in progress..."; @@ -118,7 +143,6 @@ try { } else if (deployStage?.status === "failure") { status = "🚫 Deployment failed"; } - await summary .addRaw( ` @@ -129,13 +153,14 @@ try { | **Last commit:** | \`${deployment.deployment_trigger.metadata.commit_hash.substring(0, 8)}\` | | **Status**: | ${status} | | **Preview URL**: | ${deployment.url} | -| **Branch Preview URL**: | ${aliasUrl} | +| **Branch Preview URL**: | ${aliasUrl ? aliasUrl : "This URL was not generated because the branch is the production branch."} | ` ) .write(); }; (async () => { + const { before_branch, current_branch } = await setProductionBranch(); const project = await getProject(); const productionEnvironment = githubBranch === project.production_branch || branch === project.production_branch; @@ -149,14 +174,17 @@ try { } const pagesDeployment = await createPagesDeployment(); + + let alias = ""; + + if (branch != production_branch) { + alias = generateURL(branch, pagesDeployment.url); + } + setOutput("id", pagesDeployment.id); setOutput("url", pagesDeployment.url); setOutput("environment", pagesDeployment.environment); - let alias = pagesDeployment.url; - if (!productionEnvironment && pagesDeployment.aliases && pagesDeployment.aliases.length > 0) { - alias = pagesDeployment.aliases[0]; - } setOutput("alias", alias); await createJobSummary({ deployment: pagesDeployment, aliasUrl: alias }); @@ -173,6 +201,10 @@ try { octokit, }); } + + if (before_branch !== current_branch) { + await summary.addRaw(`Production branch change from ${before_branch} to ${current_branch} succeeded!`).write(); + } })(); } catch (thrown) { setFailed(thrown.message);