diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce561fc9..c2e4c2fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,13 +1,20 @@ name: unit-tests -on: [pull_request, push] +on: + pull_request: + branches: + - dev + - releases/v3 + push: + branches: + - dev jobs: - unit-test: + unit-tests: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v1 + - name: Checkout + uses: actions/checkout@v1 - - name: Install and Test - run: | - npm install - npm run-script test + - name: Install and Test + run: | + npm install + npm run-script test diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 90e0ca78..b88ad5df 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -2,17 +2,108 @@ name: integration-tests on: schedule: - cron: 10 15 * * 0-6 + push: + branches: + - dev + - releases/v3 + - releases/v3-test + jobs: - integration-test: + # Deploys using checkout@v1 with an ACCESS_TOKEN. + integration-checkout-v1: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v1 + - name: Checkout + uses: actions/checkout@v1 - - name: Build and Deploy - uses: JamesIves/github-pages-deploy-action@releases/v3 - with: - ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} - BRANCH: gh-pages - FOLDER: integration - BASE_BRANCH: dev + - name: Build and Deploy + uses: JamesIves/github-pages-deploy-action@releases/v3-test + with: + ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + BRANCH: gh-pages + FOLDER: integration + BASE_BRANCH: dev + TARGET_FOLDER: montezuma + + # Deploys using checkout@v2 with a GITHUB_TOKEN. + integration-checkout-v2: + needs: integration-checkout-v1 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Build and Deploy + uses: JamesIves/github-pages-deploy-action@releases/v3-test + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: gh-pages + FOLDER: integration + BASE_BRANCH: dev + TARGET_FOLDER: montezuma2 + + + # Deploys using a container that requires you to install rsync. + integration-container: + needs: integration-checkout-v2 + runs-on: ubuntu-latest + container: + image: ruby:2.6 + env: + LANG: C.UTF-8 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install rsync + run: | + apt-get update && apt-get install -y rsync + + - name: Build and Deploy + uses: JamesIves/github-pages-deploy-action@releases/v3-test + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: gh-pages + FOLDER: integration + BASE_BRANCH: dev + TARGET_FOLDER: montezuma2 + + # Deploys using the CLEAN option. + integration-clean: + needs: [integration-checkout-v1, integration-checkout-v2, integration-container] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Build and Deploy + uses: JamesIves/github-pages-deploy-action@releases/v3-test + with: + ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + BRANCH: gh-pages + FOLDER: integration + BASE_BRANCH: dev + CLEAN: true + + # Deploys to a branch that doesn't exist. + integration-branch-creation: + needs: [integration-checkout-v1, integration-checkout-v2] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Build and Deploy + uses: JamesIves/github-pages-deploy-action@releases/v3-test + with: + ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + BRANCH: integration-test-delete-test + FOLDER: integration + BASE_BRANCH: dev + CLEAN: true + + - name: Cleanup Generated Branch + uses: dawidd6/action-delete-branch@v2.0.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branches: integration-test-delete-test \ No newline at end of file diff --git a/README.md b/README.md index 54ebf137..3d2ef11f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # GitHub Pages Deploy Action :rocket: -[![Build Status](https://github.com/JamesIves/github-pages-deploy-action/workflows/unit-tests/badge.svg)](https://github.com/JamesIves/github-pages-deploy-action/actions) [![Actions Status](https://github.com/JamesIves/github-pages-deploy-action/workflows/integration-tests/badge.svg)](https://github.com/JamesIves/github-pages-deploy-action/actions) [![View Action](https://img.shields.io/badge/view-action-blue.svg?logo=github&color=orange)](https://github.com/marketplace/actions/deploy-to-github-pages) [![Version](https://img.shields.io/github/v/release/JamesIves/github-pages-deploy-action.svg?logo=github)](https://github.com/JamesIves/github-pages-deploy-action/releases) +[![Build Status](https://github.com/JamesIves/github-pages-deploy-action/workflows/unit-tests/badge.svg)](https://github.com/JamesIves/github-pages-deploy-action/actions) [![Actions Status](https://github.com/JamesIves/github-pages-deploy-action/workflows/integration-tests/badge.svg)](https://github.com/JamesIves/github-pages-deploy-action/actions) [![View Action](https://img.shields.io/badge/action-marketplace-blue.svg?logo=github&color=orange)](https://github.com/marketplace/actions/deploy-to-github-pages) [![Version](https://img.shields.io/github/v/release/JamesIves/github-pages-deploy-action.svg?logo=github)](https://github.com/JamesIves/github-pages-deploy-action/releases) This [GitHub action](https://github.com/features/actions) will handle the deploy process of your project to [GitHub Pages](https://pages.github.com/). It can be configured to upload your production-ready code into any branch you'd like, including `gh-pages` and `docs`. +![Example Screenshot](screenshot.png) + ## Getting Started :airplane: You can include the action in your workflow to trigger on any event that [GitHub actions supports](https://help.github.com/en/articles/events-that-trigger-workflows). If the remote branch that you wish to deploy to doesn't already exist the action will create it for you. Your workflow will also need to include the `actions/checkout@v1` step before this workflow runs in order for the deployment to work. @@ -94,6 +96,19 @@ jobs:

+#### Using a Container 📦 + +If you use a [container](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idcontainer) in your workflow you may need to run an additional step to install `rsync` as this action depends on it. You can view an example of this below. + +```yml +- name: Install rsync + run: | + apt-get update && apt-get install -y rsync + +- name: Deploy + uses: JamesIves/github-pages-deploy-action@releases/v3 +``` + ## Configuration 📁 The `with` portion of the workflow **must** be configured before the action will work. You can add these in the `with` section found in the examples above. Any `secrets` must be referenced using the bracket syntax and stored in the GitHub repositories `Settings/Secrets` menu. You can learn more about setting environment variables with GitHub actions [here](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idstepsenv). @@ -116,5 +131,3 @@ With the action correctly configured you should see the workflow trigger the dep ### Additional Build Files This action maintains the full Git history of the deployment branch. Therefore if you're using a custom domain and require a `CNAME` file, or if you require the use of a `.nojekyll` file, you can safely commit these files directly into deployment branch without them being overridden after each deployment. - -![Example](screenshot.png) diff --git a/__tests__/execute.test.ts b/__tests__/execute.test.ts new file mode 100644 index 00000000..892e222f --- /dev/null +++ b/__tests__/execute.test.ts @@ -0,0 +1,23 @@ +import {execute} from '../src/execute'; +import {exec} from '@actions/exec'; + +jest.mock('@actions/exec', () => ({ + exec: jest.fn() +})) + +describe('execute', () => { + describe('execute', () => { + it('should be called with the correct arguments', async() => { + await execute('echo Montezuma', './') + + expect(exec).toBeCalledWith( + "echo Montezuma", [], { + cwd: "./", + listeners: { + stdout: expect.any(Function) + } + } + ) + }); + }) +}) \ No newline at end of file diff --git a/__tests__/git.test.ts b/__tests__/git.test.ts index 8590cae8..7e671fcb 100644 --- a/__tests__/git.test.ts +++ b/__tests__/git.test.ts @@ -5,11 +5,11 @@ process.env["GITHUB_SHA"] = "123"; import _ from "lodash"; import { action } from "../src/constants"; import { deploy, generateBranch, init, switchToBaseBranch } from "../src/git"; -import { execute } from "../src/util"; +import { execute } from "../src/execute"; const originalAction = _.cloneDeep(action); -jest.mock("../src/util", () => ({ +jest.mock("../src/execute", () => ({ execute: jest.fn() })); @@ -30,7 +30,7 @@ describe("git", () => { }); const call = await init(); - expect(execute).toBeCalledTimes(3); + expect(execute).toBeCalledTimes(4); expect(call).toBe("Initialization step complete..."); }); @@ -46,7 +46,7 @@ describe("git", () => { const call = await init(); - expect(execute).toBeCalledTimes(3); + expect(execute).toBeCalledTimes(4); expect(call).toBe("Initialization step complete..."); }); @@ -109,7 +109,7 @@ describe("git", () => { const call = await init(); - expect(execute).toBeCalledTimes(3); + expect(execute).toBeCalledTimes(4); expect(call).toBe("Initialization step complete..."); }); }); diff --git a/__tests__/util.test.ts b/__tests__/util.test.ts index 7a020b09..cf304bd5 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -1,23 +1,21 @@ -import {execute} from '../src/util'; -import {exec} from '@actions/exec'; +import {isNullOrUndefined} from '../src/util'; -jest.mock('@actions/exec', () => ({ - exec: jest.fn() -})) describe('util', () => { - describe('execute', () => { - it('should be called with the correct arguements', async() => { - await execute('echo Montezuma', './') - - expect(exec).toBeCalledWith( - "echo Montezuma", [], { - cwd: "./", - listeners: { - stdout: expect.any(Function) - } - } - ) + describe('isNullOrUndefined', () => { + it('should return true if the value is null', async() => { + const value = null; + expect(isNullOrUndefined(value)).toBeTruthy() + }); + + it('should return true if the value is undefined', async() => { + const value = undefined; + expect(isNullOrUndefined(value)).toBeTruthy() + }); + + it('should return false if the value is defined', async() => { + const value = 'montezuma'; + expect(isNullOrUndefined(value)).toBeFalsy() }); }) }) \ No newline at end of file diff --git a/lib/execute.js b/lib/execute.js new file mode 100644 index 00000000..34b45f36 --- /dev/null +++ b/lib/execute.js @@ -0,0 +1,33 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const exec_1 = require("@actions/exec"); +/** Wrapper around the GitHub toolkit exec command which returns the output. + * Also allows you to easily toggle the current working directory. + * @param {String} cmd = The command to execute. + * @param {String} cwd - The current working directory. + * @returns {Promise} - The output from the command. + */ +function execute(cmd, cwd) { + return __awaiter(this, void 0, void 0, function* () { + let output = ""; + yield exec_1.exec(cmd, [], { + cwd, + listeners: { + stdout: (data) => { + output += data.toString().trim(); + } + } + }); + return Promise.resolve(output); + }); +} +exports.execute = execute; diff --git a/lib/git.js b/lib/git.js index c78f0178..771179dd 100644 --- a/lib/git.js +++ b/lib/git.js @@ -17,6 +17,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(require("@actions/core")); +const execute_1 = require("./execute"); const util_1 = require("./util"); const constants_1 = require("./constants"); /** Generates the branch if it doesn't exist on the remote. @@ -25,15 +26,17 @@ const constants_1 = require("./constants"); function init() { return __awaiter(this, void 0, void 0, function* () { try { - if (!constants_1.action.accessToken && !constants_1.action.gitHubToken) { + if (util_1.isNullOrUndefined(constants_1.action.accessToken) && + util_1.isNullOrUndefined(constants_1.action.gitHubToken)) { return core.setFailed("You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy."); } if (constants_1.action.build.startsWith("/") || constants_1.action.build.startsWith("./")) { return core.setFailed(`The deployment folder cannot be prefixed with '/' or './'. Instead reference the folder name directly.`); } - yield util_1.execute(`git init`, constants_1.workspace); - yield util_1.execute(`git config user.name ${constants_1.action.name}`, constants_1.workspace); - yield util_1.execute(`git config user.email ${constants_1.action.email}`, constants_1.workspace); + yield execute_1.execute(`git init`, constants_1.workspace); + yield execute_1.execute(`git config user.name ${constants_1.action.name}`, constants_1.workspace); + yield execute_1.execute(`git config user.email ${constants_1.action.email}`, constants_1.workspace); + yield execute_1.execute(`git fetch`, constants_1.workspace); } catch (error) { core.setFailed(`There was an error initializing the repository: ${error}`); @@ -49,9 +52,7 @@ exports.init = init; */ function switchToBaseBranch() { return __awaiter(this, void 0, void 0, function* () { - yield util_1.execute(constants_1.action.baseBranch - ? `git switch ${constants_1.action.baseBranch}` - : `git checkout --progress --force ${constants_1.action.defaultBranch}`, constants_1.workspace); + yield execute_1.execute(`git checkout --progress --force ${constants_1.action.baseBranch ? constants_1.action.baseBranch : constants_1.action.defaultBranch}`, constants_1.workspace); return Promise.resolve("Switched to the base branch..."); }); } @@ -64,11 +65,11 @@ function generateBranch() { try { console.log(`Creating ${constants_1.action.branch} branch... 🔧`); yield switchToBaseBranch(); - yield util_1.execute(`git switch --orphan ${constants_1.action.branch}`, constants_1.workspace); - yield util_1.execute(`git reset --hard`, constants_1.workspace); - yield util_1.execute(`git commit --allow-empty -m "Initial ${constants_1.action.branch} commit."`, constants_1.workspace); - yield util_1.execute(`git push ${constants_1.repositoryPath} ${constants_1.action.branch}`, constants_1.workspace); - yield util_1.execute(`git fetch`, constants_1.workspace); + yield execute_1.execute(`git checkout --orphan ${constants_1.action.branch}`, constants_1.workspace); + yield execute_1.execute(`git reset --hard`, constants_1.workspace); + yield execute_1.execute(`git commit --allow-empty -m "Initial ${constants_1.action.branch} commit."`, constants_1.workspace); + yield execute_1.execute(`git push ${constants_1.repositoryPath} ${constants_1.action.branch}`, constants_1.workspace); + yield execute_1.execute(`git fetch`, constants_1.workspace); } catch (error) { core.setFailed(`There was an error creating the deployment branch: ${error} ❌`); @@ -90,15 +91,15 @@ function deploy() { Checks to see if the remote exists prior to deploying. If the branch doesn't exist it gets created here as an orphan. */ - const branchExists = yield util_1.execute(`git ls-remote --heads ${constants_1.repositoryPath} ${constants_1.action.branch} | wc -l`, constants_1.workspace); + const branchExists = yield execute_1.execute(`git ls-remote --heads ${constants_1.repositoryPath} ${constants_1.action.branch} | wc -l`, constants_1.workspace); if (!branchExists) { console.log("Deployment branch does not exist. Creating...."); yield generateBranch(); } // Checks out the base branch to begin the deployment process. yield switchToBaseBranch(); - yield util_1.execute(`git fetch ${constants_1.repositoryPath}`, constants_1.workspace); - yield util_1.execute(`git worktree add --checkout ${temporaryDeploymentDirectory} origin/${constants_1.action.branch}`, constants_1.workspace); + yield execute_1.execute(`git fetch ${constants_1.repositoryPath}`, constants_1.workspace); + yield execute_1.execute(`git worktree add --checkout ${temporaryDeploymentDirectory} origin/${constants_1.action.branch}`, constants_1.workspace); // Ensures that items that need to be excluded from the clean job get parsed. let excludes = ""; if (constants_1.action.clean && constants_1.action.cleanExclude) { @@ -114,25 +115,25 @@ function deploy() { Pushes all of the build files into the deployment directory. Allows the user to specify the root if '.' is provided. rysync is used to prevent file duplication. */ - yield util_1.execute(`rsync -q -av --progress ${constants_1.action.build}/. ${constants_1.action.targetFolder + yield execute_1.execute(`rsync -q -av --progress ${constants_1.action.build}/. ${constants_1.action.targetFolder ? `${temporaryDeploymentDirectory}/${constants_1.action.targetFolder}` : temporaryDeploymentDirectory} ${constants_1.action.clean ? `--delete ${excludes} --exclude CNAME --exclude .nojekyll` : ""} --exclude .git --exclude .github ${constants_1.action.build === constants_1.root ? `--exclude ${temporaryDeploymentDirectory}` : ""}`, constants_1.workspace); - const hasFilesToCommit = yield util_1.execute(`git status --porcelain`, temporaryDeploymentDirectory); + const hasFilesToCommit = yield execute_1.execute(`git status --porcelain`, temporaryDeploymentDirectory); if (!hasFilesToCommit && !constants_1.isTest) { console.log("There is nothing to commit. Exiting... ✅"); return Promise.resolve(); } // Commits to GitHub. - yield util_1.execute(`git add --all .`, temporaryDeploymentDirectory); - yield util_1.execute(`git switch -c ${temporaryDeploymentBranch}`, temporaryDeploymentDirectory); - yield util_1.execute(`git commit -m "Deploying to ${constants_1.action.branch} from ${constants_1.action.baseBranch} ${process.env.GITHUB_SHA}" --quiet`, temporaryDeploymentDirectory); - yield util_1.execute(`git push --force ${constants_1.repositoryPath} ${temporaryDeploymentBranch}:${constants_1.action.branch}`, temporaryDeploymentDirectory); + yield execute_1.execute(`git add --all .`, temporaryDeploymentDirectory); + yield execute_1.execute(`git checkout -b ${temporaryDeploymentBranch}`, temporaryDeploymentDirectory); + yield execute_1.execute(`git commit -m "Deploying to ${constants_1.action.branch} from ${constants_1.action.baseBranch} ${process.env.GITHUB_SHA}" --quiet`, temporaryDeploymentDirectory); + yield execute_1.execute(`git push --force ${constants_1.repositoryPath} ${temporaryDeploymentBranch}:${constants_1.action.branch}`, temporaryDeploymentDirectory); // Cleans up temporary files/folders and restores the git state. console.log("Running post deployment cleanup jobs... 🔧"); - yield util_1.execute(`rm -rf ${temporaryDeploymentDirectory}`, constants_1.workspace); - yield util_1.execute(`git checkout --progress --force ${constants_1.action.defaultBranch}`, constants_1.workspace); + yield execute_1.execute(`rm -rf ${temporaryDeploymentDirectory}`, constants_1.workspace); + yield execute_1.execute(`git checkout --progress --force ${constants_1.action.defaultBranch}`, constants_1.workspace); return Promise.resolve("Commit step complete..."); }); } diff --git a/lib/src/constants.js b/lib/src/constants.js new file mode 100644 index 00000000..56ba5f95 --- /dev/null +++ b/lib/src/constants.js @@ -0,0 +1,43 @@ +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = __importStar(require("@actions/core")); +const github = __importStar(require("@actions/github")); +const { pusher, repository } = github.context.payload; +exports.workspace = process.env.GITHUB_WORKSPACE; +exports.folder = core.getInput("FOLDER", { required: true }); +exports.root = "."; +exports.isTest = process.env.UNIT_TEST; +// Required action data. +exports.action = { + build: exports.folder, + gitHubRepository: repository && repository.full_name + ? repository.full_name + : process.env.GITHUB_REPOSITORY, + gitHubToken: core.getInput("GITHUB_TOKEN"), + accessToken: core.getInput("ACCESS_TOKEN"), + branch: core.getInput("BRANCH"), + targetFolder: core.getInput("TARGET_FOLDER"), + baseBranch: core.getInput("BASE_BRANCH"), + defaultBranch: process.env.GITHUB_SHA ? process.env.GITHUB_SHA : "master", + name: pusher && pusher.name + ? pusher.name + : process.env.GITHUB_ACTOR + ? process.env.GITHUB_ACTOR + : "GitHub Pages Deploy Action", + email: pusher && pusher.email + ? pusher.email + : `${process.env.GITHUB_ACTOR || + "github-pages-deploy-action"}@users.noreply.github.com`, + clean: core.getInput("CLEAN"), + cleanExclude: core.getInput("CLEAN_EXCLUDE") +}; +// Repository path used for commits/pushes. +exports.repositoryPath = `https://${exports.action.accessToken || + `x-access-token:${exports.action.gitHubToken}`}@github.com/${exports.action.gitHubRepository}.git`; diff --git a/lib/src/execute.js b/lib/src/execute.js new file mode 100644 index 00000000..34b45f36 --- /dev/null +++ b/lib/src/execute.js @@ -0,0 +1,33 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const exec_1 = require("@actions/exec"); +/** Wrapper around the GitHub toolkit exec command which returns the output. + * Also allows you to easily toggle the current working directory. + * @param {String} cmd = The command to execute. + * @param {String} cwd - The current working directory. + * @returns {Promise} - The output from the command. + */ +function execute(cmd, cwd) { + return __awaiter(this, void 0, void 0, function* () { + let output = ""; + yield exec_1.exec(cmd, [], { + cwd, + listeners: { + stdout: (data) => { + output += data.toString().trim(); + } + } + }); + return Promise.resolve(output); + }); +} +exports.execute = execute; diff --git a/lib/src/git.js b/lib/src/git.js new file mode 100644 index 00000000..a9e8e152 --- /dev/null +++ b/lib/src/git.js @@ -0,0 +1,143 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = __importStar(require("@actions/core")); +const execute_1 = require("./execute"); +const util_1 = require("./util"); +const constants_1 = require("./constants"); +/** Generates the branch if it doesn't exist on the remote. + * @returns {Promise} + */ +function init() { + return __awaiter(this, void 0, void 0, function* () { + try { + if (util_1.isNullOrUndefined(constants_1.action.accessToken) && + util_1.isNullOrUndefined(constants_1.action.gitHubToken)) { + return core.setFailed("You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy."); + } + if (constants_1.action.build.startsWith("/") || constants_1.action.build.startsWith("./")) { + console.log("2"); + return core.setFailed(`The deployment folder cannot be prefixed with '/' or './'. Instead reference the folder name directly.`); + } + yield execute_1.execute(`git init`, constants_1.workspace); + yield execute_1.execute(`git config user.name ${constants_1.action.name}`, constants_1.workspace); + yield execute_1.execute(`git config user.email ${constants_1.action.email}`, constants_1.workspace); + yield execute_1.execute(`git fetch`, constants_1.workspace); + } + catch (error) { + core.setFailed(`There was an error initializing the repository: ${error}`); + } + finally { + return Promise.resolve("Initialization step complete..."); + } + }); +} +exports.init = init; +/** Switches to the base branch. + * @returns {Promise} + */ +function switchToBaseBranch() { + return __awaiter(this, void 0, void 0, function* () { + yield execute_1.execute(constants_1.action.baseBranch + ? `git switch ${constants_1.action.baseBranch}` + : `git checkout --progress --force ${constants_1.action.defaultBranch}`, constants_1.workspace); + return Promise.resolve("Switched to the base branch..."); + }); +} +exports.switchToBaseBranch = switchToBaseBranch; +/** Generates the branch if it doesn't exist on the remote. + * @returns {Promise} + */ +function generateBranch() { + return __awaiter(this, void 0, void 0, function* () { + try { + console.log(`Creating ${constants_1.action.branch} branch... 🔧`); + yield switchToBaseBranch(); + yield execute_1.execute(`git switch --orphan ${constants_1.action.branch}`, constants_1.workspace); + yield execute_1.execute(`git reset --hard`, constants_1.workspace); + yield execute_1.execute(`git commit --allow-empty -m "Initial ${constants_1.action.branch} commit."`, constants_1.workspace); + yield execute_1.execute(`git push ${constants_1.repositoryPath} ${constants_1.action.branch}`, constants_1.workspace); + yield execute_1.execute(`git fetch`, constants_1.workspace); + } + catch (error) { + core.setFailed(`There was an error creating the deployment branch: ${error} ❌`); + } + finally { + return Promise.resolve("Deployment branch creation step complete... ✅"); + } + }); +} +exports.generateBranch = generateBranch; +/** Runs the necessary steps to make the deployment. + * @returns {Promise} + */ +function deploy() { + return __awaiter(this, void 0, void 0, function* () { + const temporaryDeploymentDirectory = "gh-action-temp-deployment-folder"; + const temporaryDeploymentBranch = "gh-action-temp-deployment-branch"; + /* + Checks to see if the remote exists prior to deploying. + If the branch doesn't exist it gets created here as an orphan. + */ + const branchExists = yield execute_1.execute(`git ls-remote --heads ${constants_1.repositoryPath} ${constants_1.action.branch} | wc -l`, constants_1.workspace); + if (!branchExists) { + console.log("Deployment branch does not exist. Creating...."); + yield generateBranch(); + } + // Checks out the base branch to begin the deployment process. + yield switchToBaseBranch(); + yield execute_1.execute(`git fetch ${constants_1.repositoryPath}`, constants_1.workspace); + yield execute_1.execute(`git worktree add --checkout ${temporaryDeploymentDirectory} origin/${constants_1.action.branch}`, constants_1.workspace); + // Ensures that items that need to be excluded from the clean job get parsed. + let excludes = ""; + if (constants_1.action.clean && constants_1.action.cleanExclude) { + try { + const excludedItems = JSON.parse(constants_1.action.cleanExclude); + excludedItems.forEach((item) => (excludes += `--exclude ${item} `)); + } + catch (_a) { + console.log("There was an error parsing your CLEAN_EXCLUDE items. Please refer to the README for more details. ❌"); + } + } + /* + Pushes all of the build files into the deployment directory. + Allows the user to specify the root if '.' is provided. + rysync is used to prevent file duplication. */ + yield execute_1.execute(`rsync -q -av --progress ${constants_1.action.build}/. ${constants_1.action.targetFolder + ? `${temporaryDeploymentDirectory}/${constants_1.action.targetFolder}` + : temporaryDeploymentDirectory} ${constants_1.action.clean + ? `--delete ${excludes} --exclude CNAME --exclude .nojekyll` + : ""} --exclude .git --exclude .github ${constants_1.action.build === constants_1.root ? `--exclude ${temporaryDeploymentDirectory}` : ""}`, constants_1.workspace); + const hasFilesToCommit = yield execute_1.execute(`git status --porcelain`, temporaryDeploymentDirectory); + if (!hasFilesToCommit && !constants_1.isTest) { + console.log("There is nothing to commit. Exiting... ✅"); + return Promise.resolve(); + } + // Commits to GitHub. + yield execute_1.execute(`git add --all .`, temporaryDeploymentDirectory); + yield execute_1.execute(`git switch -c ${temporaryDeploymentBranch}`, temporaryDeploymentDirectory); + yield execute_1.execute(`git commit -m "Deploying to ${constants_1.action.branch} from ${constants_1.action.baseBranch} ${process.env.GITHUB_SHA}" --quiet`, temporaryDeploymentDirectory); + yield execute_1.execute(`git push --force ${constants_1.repositoryPath} ${temporaryDeploymentBranch}:${constants_1.action.branch}`, temporaryDeploymentDirectory); + // Cleans up temporary files/folders and restores the git state. + console.log("Running post deployment cleanup jobs... 🔧"); + yield execute_1.execute(`rm -rf ${temporaryDeploymentDirectory}`, constants_1.workspace); + yield execute_1.execute(`git checkout --progress --force ${constants_1.action.defaultBranch}`, constants_1.workspace); + return Promise.resolve("Commit step complete..."); + }); +} +exports.deploy = deploy; diff --git a/lib/src/main.js b/lib/src/main.js new file mode 100644 index 00000000..f9741828 --- /dev/null +++ b/lib/src/main.js @@ -0,0 +1,36 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = __importStar(require("@actions/core")); +const git_1 = require("./git"); +/** Initializes and runs the action. */ +(function () { + return __awaiter(this, void 0, void 0, function* () { + try { + yield git_1.init(); + yield git_1.deploy(); + } + catch (error) { + console.log("The deployment encountered an error. ❌"); + core.setFailed(error.message); + } + finally { + console.log("Completed Deployment ✅"); + } + }); +})(); diff --git a/lib/src/util.js b/lib/src/util.js new file mode 100644 index 00000000..b256ea40 --- /dev/null +++ b/lib/src/util.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** Utility function that checks to see if a value is undefined or not. + * @param {*} value = The value to check. + * @returns {boolean} + */ +function isNullOrUndefined(value) { + return typeof value === "undefined" || value === null || value === ""; +} +exports.isNullOrUndefined = isNullOrUndefined; diff --git a/lib/util.js b/lib/util.js index 34b45f36..b256ea40 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,33 +1,10 @@ "use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; Object.defineProperty(exports, "__esModule", { value: true }); -const exec_1 = require("@actions/exec"); -/** Wrapper around the GitHub toolkit exec command which returns the output. - * Also allows you to easily toggle the current working directory. - * @param {String} cmd = The command to execute. - * @param {String} cwd - The current working directory. - * @returns {Promise} - The output from the command. +/** Utility function that checks to see if a value is undefined or not. + * @param {*} value = The value to check. + * @returns {boolean} */ -function execute(cmd, cwd) { - return __awaiter(this, void 0, void 0, function* () { - let output = ""; - yield exec_1.exec(cmd, [], { - cwd, - listeners: { - stdout: (data) => { - output += data.toString().trim(); - } - } - }); - return Promise.resolve(output); - }); +function isNullOrUndefined(value) { + return typeof value === "undefined" || value === null || value === ""; } -exports.execute = execute; +exports.isNullOrUndefined = isNullOrUndefined; diff --git a/screenshot.png b/screenshot.png index 4ddabd02..2f62d3d5 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/src/execute.ts b/src/execute.ts new file mode 100644 index 00000000..a8fa2abd --- /dev/null +++ b/src/execute.ts @@ -0,0 +1,22 @@ +import { exec } from "@actions/exec"; + +/** Wrapper around the GitHub toolkit exec command which returns the output. + * Also allows you to easily toggle the current working directory. + * @param {String} cmd = The command to execute. + * @param {String} cwd - The current working directory. + * @returns {Promise} - The output from the command. + */ +export async function execute(cmd: string, cwd: string): Promise { + let output = ""; + + await exec(cmd, [], { + cwd, + listeners: { + stdout: (data: Buffer) => { + output += data.toString().trim(); + } + } + }); + + return Promise.resolve(output); +} diff --git a/src/git.ts b/src/git.ts index ae3ba639..f6c324b0 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,5 +1,6 @@ import * as core from "@actions/core"; -import { execute } from "./util"; +import { execute } from "./execute"; +import { isNullOrUndefined } from "./util"; import { workspace, action, root, repositoryPath, isTest } from "./constants"; /** Generates the branch if it doesn't exist on the remote. @@ -7,7 +8,10 @@ import { workspace, action, root, repositoryPath, isTest } from "./constants"; */ export async function init(): Promise { try { - if (!action.accessToken && !action.gitHubToken) { + if ( + isNullOrUndefined(action.accessToken) && + isNullOrUndefined(action.gitHubToken) + ) { return core.setFailed( "You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy." ); @@ -22,6 +26,7 @@ export async function init(): Promise { await execute(`git init`, workspace); await execute(`git config user.name ${action.name}`, workspace); await execute(`git config user.email ${action.email}`, workspace); + await execute(`git fetch`, workspace); } catch (error) { core.setFailed(`There was an error initializing the repository: ${error}`); } finally { @@ -32,11 +37,11 @@ export async function init(): Promise { /** Switches to the base branch. * @returns {Promise} */ -export async function switchToBaseBranch() { +export async function switchToBaseBranch(): Promise { await execute( - action.baseBranch - ? `git switch ${action.baseBranch}` - : `git checkout --progress --force ${action.defaultBranch}`, + `git checkout --progress --force ${ + action.baseBranch ? action.baseBranch : action.defaultBranch + }`, workspace ); @@ -50,7 +55,7 @@ export async function generateBranch(): Promise { try { console.log(`Creating ${action.branch} branch... 🔧`); await switchToBaseBranch(); - await execute(`git switch --orphan ${action.branch}`, workspace); + await execute(`git checkout --orphan ${action.branch}`, workspace); await execute(`git reset --hard`, workspace); await execute( `git commit --allow-empty -m "Initial ${action.branch} commit."`, @@ -141,7 +146,7 @@ export async function deploy(): Promise { // Commits to GitHub. await execute(`git add --all .`, temporaryDeploymentDirectory); await execute( - `git switch -c ${temporaryDeploymentBranch}`, + `git checkout -b ${temporaryDeploymentBranch}`, temporaryDeploymentDirectory ); await execute( diff --git a/src/util.ts b/src/util.ts index a8fa2abd..0ba0bae4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,22 +1,7 @@ -import { exec } from "@actions/exec"; - -/** Wrapper around the GitHub toolkit exec command which returns the output. - * Also allows you to easily toggle the current working directory. - * @param {String} cmd = The command to execute. - * @param {String} cwd - The current working directory. - * @returns {Promise} - The output from the command. +/** Utility function that checks to see if a value is undefined or not. + * @param {*} value = The value to check. + * @returns {boolean} */ -export async function execute(cmd: string, cwd: string): Promise { - let output = ""; - - await exec(cmd, [], { - cwd, - listeners: { - stdout: (data: Buffer) => { - output += data.toString().trim(); - } - } - }); - - return Promise.resolve(output); +export function isNullOrUndefined(value: any): boolean { + return typeof value === "undefined" || value === null || value === ""; } diff --git a/yarn.lock b/yarn.lock index 079d0b2a..07c2e730 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,18 +3,18 @@ "@actions/core@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.0.tgz#aa5f52b26c362c821d41557e599371a42f6c0b3d" + version "1.2.1" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.1.tgz#867e92da94d80f743e6e0503c668af832465080a" "@actions/exec@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.0.2.tgz#80ae9c2ea0bf5d0046a9f73d2a1b15bddfff0311" + version "1.0.3" + resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.0.3.tgz#b967f8700d6ff011dcc91243b58bafc1bb9ab95f" dependencies: "@actions/io" "^1.0.1" "@actions/github@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@actions/github/-/github-2.0.0.tgz#5b066b1a8747bbf48d47a058d9c241a2e37d5ee7" + version "2.0.1" + resolved "https://registry.yarnpkg.com/@actions/github/-/github-2.0.1.tgz#2870f56c28f042effc04e9ebf0df1f1af23715f7" dependencies: "@octokit/graphql" "^4.3.1" "@octokit/rest" "^16.15.0" @@ -395,8 +395,8 @@ jest-diff "^24.3.0" "@types/node@>= 8", "@types/node@^13.1.2": - version "13.1.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.4.tgz#4cfd90175a200ee9b02bd6b1cd19bc349741607e" + version "13.1.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.6.tgz#076028d0b0400be8105b89a0a55550c86684ffec" "@types/stack-utils@^1.0.1": version "1.0.1"