Initial commit

This commit is contained in:
Greg Brimble 2022-05-09 22:24:08 +01:00 committed by Greg Brimble
commit 752c4fc911
16 changed files with 17923 additions and 0 deletions

23
.github/workflows/example.yml vendored Normal file
View file

@ -0,0 +1,23 @@
on: [push]
jobs:
example:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Example
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Publish to Cloudflare Pages
uses: ./
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: 468cce80dfb431f3a4058e6dc6031a44
projectName: github-actions-example
directory: example
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
id: publish
- name: Deployment URL
run: echo "${{ steps.publish.outputs.url }}"

178
.gitignore vendored Normal file
View file

@ -0,0 +1,178 @@
# Created by https://www.toptal.com/developers/gitignore/api/node,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=node,macos
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node,macos

4
.husky/pre-commit Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run build && git add index.js

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Cloudflare
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

39
README.md Normal file
View file

@ -0,0 +1,39 @@
# Cloudflare Pages GitHub Action
GitHub Action for creating Cloudflare Pages deployments, using the new [Direct Upload](https://developers.cloudflare.com/pages/platform/direct-upload/) feature and [Wrangler](https://developers.cloudflare.com/pages/platform/direct-upload/#wrangler-cli) integration.
## Usage
1. Create an API token in the Cloudflare dashboard with the "Cloudflare Pages — Edit" permission.
1. Add that API token as a secret to your GitHub repository, `CLOUDFLARE_API_TOKEN`.
1. Create a `.github/workflows/publish.yml` file in your repository:
```yml
on: [push]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Publish to Cloudflare Pages
steps:
- name: Checkout
uses: actions/checkout@v3
# Run a build step here if your project requires
- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: YOUR_ACCOUNT_ID
projectName: YOUR_PROJECT_NAME
directory: YOUR_ASSET_DIRECTORY
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
```
1. Replace `YOUR_ACCOUNT_ID`, `YOUR_PROJECT_NAME` and `YOUR_ASSET_DIRECTORY` with the appropriate values to your Pages project.
More information can be found on [our guide for making Direct Upload deployments with continous integration](https://developers.cloudflare.com/pages/how-to/use-direct-upload-with-continuous-integration/#use-github-actions).

24
action.yml Normal file
View file

@ -0,0 +1,24 @@
name: "Cloudflare Pages GitHub Action"
description: "Publish to Cloudflare Pages"
branding:
icon: "upload-cloud"
color: "orange"
inputs:
apiToken:
description: "Cloudflare API Token"
required: true
accountId:
description: "Cloudflare Account ID"
required: true
projectName:
description: "The name of the Pages project to upload to"
required: true
directory:
description: "The directory of static assets to upload"
required: true
gitHubToken:
description: "GitHub Token"
required: true
runs:
using: "node16"
main: "index.js"

View file

@ -0,0 +1 @@
<svg width="25" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M22.95 10.817a4.366 4.366 0 0 0-3-1.02A7.234 7.234 0 0 0 6.085 8.075 5.116 5.116 0 0 0 2.45 9.272 5.475 5.475 0 0 0 .5 13.408a5.344 5.344 0 0 0 5.332 5.331h4.418v-1.5H5.82A3.838 3.838 0 0 1 2 13.408a3.959 3.959 0 0 1 1.414-2.988 3.62 3.62 0 0 1 3.049-.792l.651.113.195-.632a5.732 5.732 0 0 1 11.207 1.523l.024.855.845-.136a3.105 3.105 0 0 1 2.593.606A3.044 3.044 0 0 1 23 14.262a2.981 2.981 0 0 1-2.977 2.977h-.049c-.091.002-.641.01-3.724.01v1.5c2.895 0 3.588-.007 3.773-.01a4.517 4.517 0 0 0 2.927-7.923v.001Z" fill="#fff"/><path d="M22.95 10.817a4.366 4.366 0 0 0-3-1.02A7.234 7.234 0 0 0 6.085 8.075 5.116 5.116 0 0 0 2.45 9.272 5.475 5.475 0 0 0 .5 13.408a5.344 5.344 0 0 0 5.332 5.331h4.418v-1.5H5.82A3.838 3.838 0 0 1 2 13.408a3.959 3.959 0 0 1 1.414-2.988 3.62 3.62 0 0 1 3.049-.792l.651.113.195-.632a5.732 5.732 0 0 1 11.207 1.523l.024.855.845-.136a3.105 3.105 0 0 1 2.593.606A3.044 3.044 0 0 1 23 14.262a2.981 2.981 0 0 1-2.977 2.977h-.049c-.091.002-.641.01-3.724.01v1.5c2.895 0 3.588-.007 3.773-.01a4.517 4.517 0 0 0 2.927-7.923v.001Z" fill="url(#b)"/><path d="M22.95 10.817a4.366 4.366 0 0 0-3-1.02A7.234 7.234 0 0 0 6.085 8.075 5.116 5.116 0 0 0 2.45 9.272 5.475 5.475 0 0 0 .5 13.408a5.344 5.344 0 0 0 5.332 5.331h4.418v-1.5H5.82A3.838 3.838 0 0 1 2 13.408a3.959 3.959 0 0 1 1.414-2.988 3.62 3.62 0 0 1 3.049-.792l.651.113.195-.632a5.732 5.732 0 0 1 11.207 1.523l.024.855.845-.136a3.105 3.105 0 0 1 2.593.606A3.044 3.044 0 0 1 23 14.262a2.981 2.981 0 0 1-2.977 2.977h-.049c-.091.002-.641.01-3.724.01v1.5c2.895 0 3.588-.007 3.773-.01a4.517 4.517 0 0 0 2.927-7.923v.001Z" fill="url(#c)"/><path d="m16.926 14.184-3.691-3.75-3.69 3.75 1.069 1.052 1.871-1.902v5.429h1.5v-5.429l1.872 1.902 1.07-1.052Z" fill="#fff"/><path d="m16.926 14.184-3.691-3.75-3.69 3.75 1.069 1.052 1.871-1.902v5.429h1.5v-5.429l1.872 1.902 1.07-1.052Z" fill="url(#d)"/><path d="m16.926 14.184-3.691-3.75-3.69 3.75 1.069 1.052 1.871-1.902v5.429h1.5v-5.429l1.872 1.902 1.07-1.052Z" fill="url(#e)"/></g><defs><linearGradient id="b" x1="12.5" y1="3.565" x2="12.5" y2="18.763" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="12.5" y1="3.565" x2="12.5" y2="18.763" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="12.5" y1="3.565" x2="12.5" y2="18.763" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><linearGradient id="e" x1="12.5" y1="3.565" x2="12.5" y2="18.763" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><clipPath id="a"><path fill="#fff" transform="translate(.5)" d="M0 0h24v24H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1 @@
<svg width="21" height="22" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M20.125.5H.875L0 1.375v19.25l.875.875h19.25l.875-.875V1.375L20.125.5Zm-.875 19.25H1.75V2.25h17.5v17.5ZM10.062 8.908 3.939 5.313v2.066l3.927 2.307-3.928 2.302v2.075l6.126-3.6V8.909Zm6.563 6.03h-6.563v1.75h6.563v-1.75Z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M20.125.5H.875L0 1.375v19.25l.875.875h19.25l.875-.875V1.375L20.125.5Zm-.875 19.25H1.75V2.25h17.5v17.5ZM10.062 8.908 3.939 5.313v2.066l3.927 2.307-3.928 2.302v2.075l6.126-3.6V8.909Zm6.563 6.03h-6.563v1.75h6.563v-1.75Z" fill="url(#a)"/><path fill-rule="evenodd" clip-rule="evenodd" d="M20.125.5H.875L0 1.375v19.25l.875.875h19.25l.875-.875V1.375L20.125.5Zm-.875 19.25H1.75V2.25h17.5v17.5ZM10.062 8.908 3.939 5.313v2.066l3.927 2.307-3.928 2.302v2.075l6.126-3.6V8.909Zm6.563 6.03h-6.563v1.75h6.563v-1.75Z" fill="url(#b)"/><defs><linearGradient id="a" x1="10.5" y1=".5" x2="10.5" y2="21.5" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="10.5" y1=".5" x2="10.5" y2="21.5" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
example/assets/world.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

50
example/index.html Normal file
View file

@ -0,0 +1,50 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Direct Upload Demo | Cloudflare Pages</title>
<link href="style.css" rel="stylesheet" />
</head>
<body>
<div class="container">
<div class="header">
<h3>Example project</h3>
<h1>Welcome to Cloudflare Pages</h1>
<p>
Upload your prebuilt assets directly to Pages and deploy <br />
your project to the Cloudflare edge within seconds.
</p>
<p>Create new deployments using:</p>
<section class="options">
<img src="assets/terminal.svg" />
<a
href="https://dash.cloudflare.com/?to=/:account/pages/new/wrangler-guide"
target="_blank"
> Wrangler CLI</a
>
<img src="assets/cloud-upload.svg" />
<a
href="https://dash.cloudflare.com/?to=/:account/pages/new/upload"
target="_blank"
> Dashboard</a
>
</section>
</div>
<section class="learn-more">
<a
href="https://developers.cloudflare.com/pages/platform/direct-uploads"
>
<button>Learn more</button>
</a>
</section>
<div class="logo">
<a href="https://pages.cloudflare.com/" target="_blank">
<img src="assets/cloudflare-pages.svg"
/></a>
</div>
</div>
</body>
</html>

159
example/style.css Normal file
View file

@ -0,0 +1,159 @@
* {
box-sizing: border-box;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, "Helvetica Neue", Arial, sans-serif;
margin: auto;
}
.bg-image {
position: absolute;
bottom: -88%;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 165px 0 124px;
width: 100%;
margin: auto;
border: #fbab40;
height: 100vh;
background-color: #242628;
background-image: url(./assets/world.svg);
background-repeat: no-repeat;
background-position-x: center;
background-position-y: 70px;
background-origin: padding-box;
}
.header h3 {
text-align: center;
color: #ff9e40;
font-size: 24px;
font-style: normal;
font-weight: 600;
margin: 0;
line-height: 36px;
}
.header {
display: flex;
position: relative;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0px;
font-style: normal;
}
.header > h1 {
font-style: normal;
font-weight: 600;
font-size: 48px;
line-height: 150%;
display: flex;
align-items: center;
text-align: center;
color: #ffffff;
}
.header > p {
font-style: normal;
font-weight: 400;
font-size: 24px;
line-height: 150%;
text-align: center;
color: #ffffff;
flex-grow: 0;
margin: 16px 0px;
}
.options {
display: flex;
flex-direction: row;
}
.options > a {
color: #ffffff;
text-decoration: underline;
margin: 0px 8px;
font-weight: 400;
font-size: 24px;
line-height: 150%;
}
.options > a:first-of-type {
margin-right: 30px;
}
.logo {
margin-top: 75px;
}
.logo > img {
width: 160px;
height: 64px;
}
.learn-more > a > button {
background: #f6821f;
border-radius: 4px;
border: none;
width: 180px;
margin-top: 78px;
height: 55px;
color: #ffffff;
text-align: center;
font-weight: 600;
font-size: 16px;
line-height: 19px;
background: rgba(246, 130, 31, 1);
box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.25);
cursor: pointer;
}
@media screen and (max-width: 1024px) {
.container {
background-size: contain;
background-position-y: center;
}
}
@media screen and (max-width: 600px) {
.container{
background-size: contain;
background-position-y: center;
}
pre {
font-size: 0.8rem;
padding: 8px 12px;
width: 100%;
}
body {
width: 100vw;
}
.header img {
margin-left: 0;
}
.installation-steps {
width: 100%;
box-sizing: border-box;
}
h1 {
font-size: 1.6rem;
}
ol {
padding-left: 20px;
}
li {
margin-bottom: 5px;
}
}

16192
index.js Normal file

File diff suppressed because one or more lines are too long

1047
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

35
package.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "pages-action",
"version": "1.0.0",
"description": "Publish to Cloudflare Pages",
"main": "index.js",
"scripts": {
"build": "npx esbuild src/index.ts --bundle --platform=node --target=es2021 --outfile=index.js",
"prepare": "husky install"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cloudflare/pages-action.git"
},
"keywords": [
"Cloudflare Pages",
"GitHub Actions"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/cloudflare/pages-action/issues"
},
"homepage": "https://github.com/cloudflare/pages-action#readme",
"dependencies": {
"@actions/core": "^1.8.0",
"@actions/github": "^5.0.1",
"shellac": "^0.7.0",
"undici": "^5.1.1"
},
"devDependencies": {
"esbuild": "^0.14.38",
"husky": "^8.0.1",
"prettier": "^2.6.2",
"typescript": "^4.6.4"
}
}

147
src/index.ts Normal file
View file

@ -0,0 +1,147 @@
import { getInput, setOutput, setFailed } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import shellac from "shellac";
import { fetch } from "undici";
// TODO: Confirm types
interface Stage {
name: string;
started_on: null | string;
ended_on: null | string;
status: string;
}
interface Deployment {
id: string;
short_id: string;
project_id: string;
project_name: string;
environment: string;
url: string;
created_on: string;
modified_on: string;
latest_stage: Stage;
deployment_trigger: {
type: string;
metadata: {
branch: string;
commit_hash: string;
commit_message: string;
commit_dirty: boolean;
};
};
stages: Stage[];
build_config: {
build_command: null | string;
destination_dir: null | string;
root_dir: null | string;
web_analytics_tag: null | string;
web_analytics_token: null | string;
fast_builds: boolean;
};
env_vars: unknown;
kv_namespaces: Record<string, { namespace_id: string }>;
aliases: null | string[];
is_skipped: boolean;
production_branch: string;
}
try {
const apiToken = getInput("apiToken", { required: true });
const accountId = getInput("accountId", { required: true });
const projectName = getInput("projectName", { required: true });
const directory = getInput("directory", { required: true });
const gitHubToken = getInput("gitHubToken", { required: true });
const octokit = getOctokit(gitHubToken);
const createPagesDeployment = async () => {
// TODO: Replace this with an API call to wrangler so we can get back a full deployment response object
await shellac`
$ export CLOUDFLARE_API_TOKEN="${apiToken}"
if ${accountId} {
$ export CLOUDFLARE_ACCOUNT_ID="${accountId}"
}
$$ npx wrangler@2 pages publish "${directory}" --project-name="${projectName}"
`;
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}/deployments`,
{ headers: { Authorization: `Bearer ${apiToken}` } }
);
const {
result: [deployment],
} = (await response.json()) as { result: Deployment[] };
return deployment;
};
const createGitHubDeployment = async () => {
const deployment = await octokit.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.ref,
auto_merge: false,
description: "Cloudflare Pages",
required_contexts: [],
});
if (deployment.status === 201) {
return deployment.data;
}
};
const createGitHubDeploymentStatus = async ({
id,
url,
environmentName,
productionEnvironment,
}: {
id: number;
url: string;
environmentName: string;
productionEnvironment: boolean;
}) => {
await octokit.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: id,
// @ts-ignore
environment: environmentName,
environment_url: url,
production_environment: productionEnvironment,
log_url: `https://dash.cloudflare.com/${accountId}/pages/view/${projectName}/${id}`,
description: "Cloudflare Pages",
state: "success",
});
};
(async () => {
const gitHubDeployment = await createGitHubDeployment();
const pagesDeployment = await createPagesDeployment();
setOutput("id", pagesDeployment.id);
setOutput("url", pagesDeployment.url);
setOutput("environment", pagesDeployment.environment);
const url = new URL(pagesDeployment.url);
const productionEnvironment = pagesDeployment.environment === "production";
const environmentName = productionEnvironment
? "Production"
: `Preview (${url.host.split(".")[0]})`;
if (gitHubDeployment) {
await createGitHubDeploymentStatus({
id: gitHubDeployment.id,
url: pagesDeployment.url,
environmentName,
productionEnvironment,
});
}
})();
} catch (thrown) {
setFailed(thrown.message);
}