Merge branch 'dev' into releases/v3

This commit is contained in:
James Ives 2020-01-14 10:05:16 -05:00
commit 147848b4e0
19 changed files with 551 additions and 131 deletions

View File

@ -1,13 +1,20 @@
name: unit-tests name: unit-tests
on: [pull_request, push] on:
pull_request:
branches:
- dev
- releases/v3
push:
branches:
- dev
jobs: jobs:
unit-test: unit-tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Install and Test - name: Install and Test
run: | run: |
npm install npm install
npm run-script test npm run-script test

View File

@ -2,17 +2,108 @@ name: integration-tests
on: on:
schedule: schedule:
- cron: 10 15 * * 0-6 - cron: 10 15 * * 0-6
push:
branches:
- dev
- releases/v3
- releases/v3-test
jobs: jobs:
integration-test: # Deploys using checkout@v1 with an ACCESS_TOKEN.
integration-checkout-v1:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Build and Deploy - name: Build and Deploy
uses: JamesIves/github-pages-deploy-action@releases/v3 uses: JamesIves/github-pages-deploy-action@releases/v3-test
with: with:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
BRANCH: gh-pages BRANCH: gh-pages
FOLDER: integration FOLDER: integration
BASE_BRANCH: dev 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

View File

@ -1,9 +1,11 @@
# GitHub Pages Deploy Action :rocket: # 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`. 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: ## 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. 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:
</p> </p>
</details> </details>
#### 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 📁 ## 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). 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 ### 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. 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)

23
__tests__/execute.test.ts Normal file
View File

@ -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)
}
}
)
});
})
})

View File

@ -5,11 +5,11 @@ process.env["GITHUB_SHA"] = "123";
import _ from "lodash"; import _ from "lodash";
import { action } from "../src/constants"; import { action } from "../src/constants";
import { deploy, generateBranch, init, switchToBaseBranch } from "../src/git"; import { deploy, generateBranch, init, switchToBaseBranch } from "../src/git";
import { execute } from "../src/util"; import { execute } from "../src/execute";
const originalAction = _.cloneDeep(action); const originalAction = _.cloneDeep(action);
jest.mock("../src/util", () => ({ jest.mock("../src/execute", () => ({
execute: jest.fn() execute: jest.fn()
})); }));
@ -30,7 +30,7 @@ describe("git", () => {
}); });
const call = await init(); const call = await init();
expect(execute).toBeCalledTimes(3); expect(execute).toBeCalledTimes(4);
expect(call).toBe("Initialization step complete..."); expect(call).toBe("Initialization step complete...");
}); });
@ -46,7 +46,7 @@ describe("git", () => {
const call = await init(); const call = await init();
expect(execute).toBeCalledTimes(3); expect(execute).toBeCalledTimes(4);
expect(call).toBe("Initialization step complete..."); expect(call).toBe("Initialization step complete...");
}); });
@ -109,7 +109,7 @@ describe("git", () => {
const call = await init(); const call = await init();
expect(execute).toBeCalledTimes(3); expect(execute).toBeCalledTimes(4);
expect(call).toBe("Initialization step complete..."); expect(call).toBe("Initialization step complete...");
}); });
}); });

View File

@ -1,23 +1,21 @@
import {execute} from '../src/util'; import {isNullOrUndefined} from '../src/util';
import {exec} from '@actions/exec';
jest.mock('@actions/exec', () => ({
exec: jest.fn()
}))
describe('util', () => { describe('util', () => {
describe('execute', () => { describe('isNullOrUndefined', () => {
it('should be called with the correct arguements', async() => { it('should return true if the value is null', async() => {
await execute('echo Montezuma', './') const value = null;
expect(isNullOrUndefined(value)).toBeTruthy()
expect(exec).toBeCalledWith( });
"echo Montezuma", [], {
cwd: "./", it('should return true if the value is undefined', async() => {
listeners: { const value = undefined;
stdout: expect.any(Function) expect(isNullOrUndefined(value)).toBeTruthy()
} });
}
) it('should return false if the value is defined', async() => {
const value = 'montezuma';
expect(isNullOrUndefined(value)).toBeFalsy()
}); });
}) })
}) })

33
lib/execute.js Normal file
View File

@ -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;

View File

@ -17,6 +17,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const execute_1 = require("./execute");
const util_1 = require("./util"); const util_1 = require("./util");
const constants_1 = require("./constants"); const constants_1 = require("./constants");
/** Generates the branch if it doesn't exist on the remote. /** Generates the branch if it doesn't exist on the remote.
@ -25,15 +26,17 @@ const constants_1 = require("./constants");
function init() { function init() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { 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."); 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("./")) { 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.`); 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 execute_1.execute(`git init`, constants_1.workspace);
yield util_1.execute(`git config user.name ${constants_1.action.name}`, constants_1.workspace); yield execute_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 config user.email ${constants_1.action.email}`, constants_1.workspace);
yield execute_1.execute(`git fetch`, constants_1.workspace);
} }
catch (error) { catch (error) {
core.setFailed(`There was an error initializing the repository: ${error}`); core.setFailed(`There was an error initializing the repository: ${error}`);
@ -49,9 +52,7 @@ exports.init = init;
*/ */
function switchToBaseBranch() { function switchToBaseBranch() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield util_1.execute(constants_1.action.baseBranch yield execute_1.execute(`git checkout --progress --force ${constants_1.action.baseBranch ? constants_1.action.baseBranch : constants_1.action.defaultBranch}`, constants_1.workspace);
? `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..."); return Promise.resolve("Switched to the base branch...");
}); });
} }
@ -64,11 +65,11 @@ function generateBranch() {
try { try {
console.log(`Creating ${constants_1.action.branch} branch... 🔧`); console.log(`Creating ${constants_1.action.branch} branch... 🔧`);
yield switchToBaseBranch(); yield switchToBaseBranch();
yield util_1.execute(`git switch --orphan ${constants_1.action.branch}`, constants_1.workspace); yield execute_1.execute(`git checkout --orphan ${constants_1.action.branch}`, constants_1.workspace);
yield util_1.execute(`git reset --hard`, constants_1.workspace); yield execute_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 execute_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 execute_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 fetch`, constants_1.workspace);
} }
catch (error) { catch (error) {
core.setFailed(`There was an error creating the deployment branch: ${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. Checks to see if the remote exists prior to deploying.
If the branch doesn't exist it gets created here as an orphan. 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) { if (!branchExists) {
console.log("Deployment branch does not exist. Creating...."); console.log("Deployment branch does not exist. Creating....");
yield generateBranch(); yield generateBranch();
} }
// Checks out the base branch to begin the deployment process. // Checks out the base branch to begin the deployment process.
yield switchToBaseBranch(); yield switchToBaseBranch();
yield util_1.execute(`git fetch ${constants_1.repositoryPath}`, constants_1.workspace); yield execute_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 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. // Ensures that items that need to be excluded from the clean job get parsed.
let excludes = ""; let excludes = "";
if (constants_1.action.clean && constants_1.action.cleanExclude) { 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. Pushes all of the build files into the deployment directory.
Allows the user to specify the root if '.' is provided. Allows the user to specify the root if '.' is provided.
rysync is used to prevent file duplication. */ 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.targetFolder}`
: temporaryDeploymentDirectory} ${constants_1.action.clean : temporaryDeploymentDirectory} ${constants_1.action.clean
? `--delete ${excludes} --exclude CNAME --exclude .nojekyll` ? `--delete ${excludes} --exclude CNAME --exclude .nojekyll`
: ""} --exclude .git --exclude .github ${constants_1.action.build === constants_1.root ? `--exclude ${temporaryDeploymentDirectory}` : ""}`, constants_1.workspace); : ""} --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) { if (!hasFilesToCommit && !constants_1.isTest) {
console.log("There is nothing to commit. Exiting... ✅"); console.log("There is nothing to commit. Exiting... ✅");
return Promise.resolve(); return Promise.resolve();
} }
// Commits to GitHub. // Commits to GitHub.
yield util_1.execute(`git add --all .`, temporaryDeploymentDirectory); yield execute_1.execute(`git add --all .`, temporaryDeploymentDirectory);
yield util_1.execute(`git switch -c ${temporaryDeploymentBranch}`, temporaryDeploymentDirectory); yield execute_1.execute(`git checkout -b ${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 execute_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 push --force ${constants_1.repositoryPath} ${temporaryDeploymentBranch}:${constants_1.action.branch}`, temporaryDeploymentDirectory);
// Cleans up temporary files/folders and restores the git state. // Cleans up temporary files/folders and restores the git state.
console.log("Running post deployment cleanup jobs... 🔧"); console.log("Running post deployment cleanup jobs... 🔧");
yield util_1.execute(`rm -rf ${temporaryDeploymentDirectory}`, constants_1.workspace); yield execute_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(`git checkout --progress --force ${constants_1.action.defaultBranch}`, constants_1.workspace);
return Promise.resolve("Commit step complete..."); return Promise.resolve("Commit step complete...");
}); });
} }

43
lib/src/constants.js Normal file
View File

@ -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`;

33
lib/src/execute.js Normal file
View File

@ -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;

143
lib/src/git.js Normal file
View File

@ -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;

36
lib/src/main.js Normal file
View File

@ -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 ✅");
}
});
})();

10
lib/src/util.js Normal file
View File

@ -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;

View File

@ -1,33 +1,10 @@
"use strict"; "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 }); Object.defineProperty(exports, "__esModule", { value: true });
const exec_1 = require("@actions/exec"); /** Utility function that checks to see if a value is undefined or not.
/** Wrapper around the GitHub toolkit exec command which returns the output. * @param {*} value = The value to check.
* Also allows you to easily toggle the current working directory. * @returns {boolean}
* @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) { function isNullOrUndefined(value) {
return __awaiter(this, void 0, void 0, function* () { return typeof value === "undefined" || value === null || value === "";
let output = "";
yield exec_1.exec(cmd, [], {
cwd,
listeners: {
stdout: (data) => {
output += data.toString().trim();
}
}
});
return Promise.resolve(output);
});
} }
exports.execute = execute; exports.isNullOrUndefined = isNullOrUndefined;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 45 KiB

22
src/execute.ts Normal file
View File

@ -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<any> {
let output = "";
await exec(cmd, [], {
cwd,
listeners: {
stdout: (data: Buffer) => {
output += data.toString().trim();
}
}
});
return Promise.resolve(output);
}

View File

@ -1,5 +1,6 @@
import * as core from "@actions/core"; 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"; import { workspace, action, root, repositoryPath, isTest } from "./constants";
/** Generates the branch if it doesn't exist on the remote. /** 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<any> { export async function init(): Promise<any> {
try { try {
if (!action.accessToken && !action.gitHubToken) { if (
isNullOrUndefined(action.accessToken) &&
isNullOrUndefined(action.gitHubToken)
) {
return core.setFailed( return core.setFailed(
"You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy." "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<any> {
await execute(`git init`, workspace); await execute(`git init`, workspace);
await execute(`git config user.name ${action.name}`, workspace); await execute(`git config user.name ${action.name}`, workspace);
await execute(`git config user.email ${action.email}`, workspace); await execute(`git config user.email ${action.email}`, workspace);
await execute(`git fetch`, workspace);
} catch (error) { } catch (error) {
core.setFailed(`There was an error initializing the repository: ${error}`); core.setFailed(`There was an error initializing the repository: ${error}`);
} finally { } finally {
@ -32,11 +37,11 @@ export async function init(): Promise<any> {
/** Switches to the base branch. /** Switches to the base branch.
* @returns {Promise} * @returns {Promise}
*/ */
export async function switchToBaseBranch() { export async function switchToBaseBranch(): Promise<any> {
await execute( await execute(
action.baseBranch `git checkout --progress --force ${
? `git switch ${action.baseBranch}` action.baseBranch ? action.baseBranch : action.defaultBranch
: `git checkout --progress --force ${action.defaultBranch}`, }`,
workspace workspace
); );
@ -50,7 +55,7 @@ export async function generateBranch(): Promise<any> {
try { try {
console.log(`Creating ${action.branch} branch... 🔧`); console.log(`Creating ${action.branch} branch... 🔧`);
await switchToBaseBranch(); 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 reset --hard`, workspace);
await execute( await execute(
`git commit --allow-empty -m "Initial ${action.branch} commit."`, `git commit --allow-empty -m "Initial ${action.branch} commit."`,
@ -141,7 +146,7 @@ export async function deploy(): Promise<any> {
// Commits to GitHub. // Commits to GitHub.
await execute(`git add --all .`, temporaryDeploymentDirectory); await execute(`git add --all .`, temporaryDeploymentDirectory);
await execute( await execute(
`git switch -c ${temporaryDeploymentBranch}`, `git checkout -b ${temporaryDeploymentBranch}`,
temporaryDeploymentDirectory temporaryDeploymentDirectory
); );
await execute( await execute(

View File

@ -1,22 +1,7 @@
import { exec } from "@actions/exec"; /** Utility function that checks to see if a value is undefined or not.
* @param {*} value = The value to check.
/** Wrapper around the GitHub toolkit exec command which returns the output. * @returns {boolean}
* 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<any> { export function isNullOrUndefined(value: any): boolean {
let output = ""; return typeof value === "undefined" || value === null || value === "";
await exec(cmd, [], {
cwd,
listeners: {
stdout: (data: Buffer) => {
output += data.toString().trim();
}
}
});
return Promise.resolve(output);
} }

View File

@ -3,18 +3,18 @@
"@actions/core@^1.2.0": "@actions/core@^1.2.0":
version "1.2.0" version "1.2.1"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.0.tgz#aa5f52b26c362c821d41557e599371a42f6c0b3d" resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.1.tgz#867e92da94d80f743e6e0503c668af832465080a"
"@actions/exec@^1.0.2": "@actions/exec@^1.0.2":
version "1.0.2" version "1.0.3"
resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.0.2.tgz#80ae9c2ea0bf5d0046a9f73d2a1b15bddfff0311" resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.0.3.tgz#b967f8700d6ff011dcc91243b58bafc1bb9ab95f"
dependencies: dependencies:
"@actions/io" "^1.0.1" "@actions/io" "^1.0.1"
"@actions/github@^2.0.0": "@actions/github@^2.0.0":
version "2.0.0" version "2.0.1"
resolved "https://registry.yarnpkg.com/@actions/github/-/github-2.0.0.tgz#5b066b1a8747bbf48d47a058d9c241a2e37d5ee7" resolved "https://registry.yarnpkg.com/@actions/github/-/github-2.0.1.tgz#2870f56c28f042effc04e9ebf0df1f1af23715f7"
dependencies: dependencies:
"@octokit/graphql" "^4.3.1" "@octokit/graphql" "^4.3.1"
"@octokit/rest" "^16.15.0" "@octokit/rest" "^16.15.0"
@ -395,8 +395,8 @@
jest-diff "^24.3.0" jest-diff "^24.3.0"
"@types/node@>= 8", "@types/node@^13.1.2": "@types/node@>= 8", "@types/node@^13.1.2":
version "13.1.4" version "13.1.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.4.tgz#4cfd90175a200ee9b02bd6b1cd19bc349741607e" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.6.tgz#076028d0b0400be8105b89a0a55550c86684ffec"
"@types/stack-utils@^1.0.1": "@types/stack-utils@^1.0.1":
version "1.0.1" version "1.0.1"