Merge branch 'dev' into releases/v3

This commit is contained in:
James Ives 2020-10-17 15:19:50 -04:00
commit e45a9df141
18 changed files with 906 additions and 1127 deletions

View File

@ -1,6 +1,6 @@
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/es6"],
"plugins": ["jest", "@typescript-eslint", "github"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
@ -19,13 +19,17 @@
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-ignore": "error",
"@typescript-eslint/ban-ts-comment": "error",
"camelcase": "off",
"@typescript-eslint/camelcase": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "default",
"format": ["camelCase", "UPPER_CASE", "StrictPascalCase"]
}
],
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "off",
@ -50,11 +54,12 @@
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error",
"no-console": "off"
"no-console": "off",
"no-shadow": ["error", { "builtinGlobals": false, "hoist": "all", "allow": ["Status"] }]
},
"env": {
"node": true,
"es6": true,
"jest/globals": true
}
}
}

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [JamesIves]

View File

@ -6,7 +6,7 @@ labels:
---
<!-- Please check the FAQ before posting an issue: https://github.com/JamesIves/github-pages-deploy-action/wiki -->
<!-- Please check the Q&A before posting an issue: https://github.com/JamesIves/github-pages-deploy-action/discussions?discussions_q=category%3AQ%26A -->
**Describe the bug**
<!-- Please provide a clear and concise description of what the bug is. -->

View File

@ -1,18 +0,0 @@
---
name: Feature Request
about: If you'd like to make a suggestion please fill out the form below.
labels:
- feature request
---
<!-- Please check the FAQ before posting an issue: https://github.com/JamesIves/github-pages-deploy-action/wiki -->
**Is your feature request related to a problem? Please describe.**
<!-- Please provide a clear and concise description of what the problem is. Please be sure to read the README first! -->
**Describe the solution you'd like**
<!-- Please provide a clear and concise description of what you want to happen. -->
**Additional Comments**
<!-- Add any other context about the feature request here. -->

View File

@ -1,18 +0,0 @@
---
name: Support
about: If you're having problems setting up the action you can make a request for support here.
labels:
- support
---
<!-- Please check the FAQ before posting an issue: https://github.com/JamesIves/github-pages-deploy-action/wiki -->
**Describe the Issue**
<!-- Please provide a clear and concise description of what the problem is. Please be sure to read the README first! -->
**Logs**
<!-- Please provide your deployment logs and a link or sample to/of your workflow. If the error message isn't revealing the problem please set ACTIONS_STEP_DEBUG to true in your repository's secrets menu and run the workflow again. -->
**Additional Comments**
<!-- Add any other context about the issue here. -->

10
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,10 @@
blank_issues_enabled: false
contact_links:
- name: Feature Request and Ideas
url: https://github.com/JamesIves/github-pages-deploy-action/discussions?discussions_q=category%3AIdeas
about: If you have an idea or would like to make a feature request please open a discussion thread.
- name: Support and Questions
url: https://github.com/JamesIves/github-pages-deploy-action/discussions?discussions_q=category%3AQ%26A
about: Need help or just have a general question? Please open a discussion thread.

8
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
time: "10:00"
open-pull-requests-limit: 10

2
.nvmrc
View File

@ -1 +1 @@
v10.19.0
v12.18.4

View File

@ -1,7 +1,7 @@
# Contributing ✏️
When contributing to this repository, please first discuss the change you wish to make via issue,
[email, or any other method with the owners of this repository](https://jamesiv.es) before making a change.
[email, or any other method with the owners of this repository](https://jamesiv.es) before making a change. If you are planning to work on an issue that already exists please let us know before writing any code incase it's already in flight!
## Before Making a Pull Request 🎒

View File

@ -17,16 +17,16 @@
<img src="https://github.com/JamesIves/github-pages-deploy-action/workflows/integration-tests/badge.svg">
</a>
<a href="https://github.com/marketplace/actions/deploy-to-github-pages">
<img src="https://img.shields.io/badge/action-marketplace-blue.svg?logo=github&color=orange">
<a href="https://codecov.io/gh/JamesIves/github-pages-deploy-action/branch/dev">
<img src="https://codecov.io/gh/JamesIves/github-pages-deploy-action/branch/dev/graph/badge.svg">
</a>
<a href="https://github.com/JamesIves/github-pages-deploy-action/releases">
<img src="https://img.shields.io/github/v/release/JamesIves/github-pages-deploy-action.svg?logo=github">
</a>
<a href="https://codecov.io/gh/JamesIves/github-pages-deploy-action/branch/dev">
<img src="https://codecov.io/gh/JamesIves/github-pages-deploy-action/branch/dev/graph/badge.svg">
<a href="https://github.com/marketplace/actions/deploy-to-github-pages">
<img src="https://img.shields.io/badge/action-marketplace-blue.svg?logo=github&color=orange">
</a>
</p>
@ -62,7 +62,7 @@ jobs:
npm run build
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.1
uses: JamesIves/github-pages-deploy-action@3.6.2
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages # The branch the action should deploy to.
@ -83,21 +83,20 @@ It's recommended that you use [Dependabot](https://dependabot.com/github-actions
#### Install as a Node Module 📦
If you'd like to use the functionality provided by this action in your own action you can install it using [yarn](https://yarnpkg.com/) by running the following command.
If you'd like to use the functionality provided by this action in your own action you can install it using [yarn](https://yarnpkg.com/) or [npm](https://www.npmjs.com/get-npm) by running the following commands. It's available on both the [npm](https://www.npmjs.com/package/@jamesives/github-pages-deploy-action) and [GitHub registry](https://github.com/JamesIves/github-pages-deploy-action/packages/229985).
```
yarn add @jamesives/github-pages-deploy-action
```
```
npm install @jamesives/github-pages-deploy-action
```
It can then be imported into your project like so.
```javascript
import run, {
init,
deploy,
generateBranch,
ActionInterface
} from "github-pages-deploy-action";
import run from "github-pages-deploy-action";
```
Calling the functions directly will require you to pass in an object containing the variables found in the configuration section, you'll also need to provide a `workspace` with a path to your project.
@ -110,11 +109,12 @@ run({
branch: "gh-pages",
folder: "build",
repositoryName: "JamesIves/github-pages-deploy-action",
silent: true,
workspace: "src/project/location",
});
```
For more information regarding the [action interface please click here](https://github.com/JamesIves/github-pages-deploy-action/blob/dev/src/constants.ts#L7). You can find the npm registry listing for the module [here](https://www.npmjs.com/package/@jamesives/github-pages-deploy-action), and the GitHub registry listing [here](https://github.com/JamesIves/github-pages-deploy-action/packages/229985).
For more information regarding the [action interface please click here](https://github.com/JamesIves/github-pages-deploy-action/blob/dev/src/constants.ts#L7).
## Configuration 📁
@ -135,7 +135,7 @@ In addition to the deployment options you must also configure the following.
| Key | Value Information | Type | Required |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- |
| `BRANCH` | This is the branch you wish to deploy to, for example `gh-pages` or `docs`. | `with` | **Yes** |
| `FOLDER` | The folder in your repository that you want to deploy. If your build script compiles into a directory named `build` you'd put it here. **Folder paths cannot have a leading `/` or `./`**. If you wish to deploy the root directory you can place a `.` here. | `with` | **Yes** |
| `FOLDER` | The folder in your repository that you want to deploy. If your build script compiles into a directory named `build` you'd put it here. If you wish to deploy the root directory you can place a `.` here. You can also utilize absolute file paths by appending `~` to your folder path. | `with` | **Yes** |
#### Optional Choices
@ -174,7 +174,7 @@ The action will export an environment variable called `DEPLOYMENT_STATUS` that y
If you'd prefer to use an SSH deploy key as opposed to a token you must first generate a new SSH key by running the following terminal command, replacing the email with one connected to your GitHub account.
```bash
ssh-keygen -t rsa -b 4096 -C "youremailhere@example.com" -N ""
ssh-keygen -t rsa -m pem -b 4096 -C "youremailhere@example.com" -N ""
```
Once you've generated the key pair you must add the contents of the public key within your repository's [deploy keys menu](https://developer.github.com/v3/guides/managing-deploy-keys/). You can find this option by going to `Settings > Deploy Keys`, you can name the public key whatever you want, but you **do** need to give it write access. Afterwards add the contents of the private key to the `Settings > Secrets` menu as `DEPLOY_KEY`.
@ -183,12 +183,12 @@ With this configured you must add the `ssh-agent` step to your workflow and set
```yml
- name: Install SSH Client 🔑
uses: webfactory/ssh-agent@v0.2.0
uses: webfactory/ssh-agent@v0.4.1
with:
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.1
uses: JamesIves/github-pages-deploy-action@3.6.2
with:
SSH: true
BRANCH: gh-pages
@ -224,7 +224,7 @@ jobs:
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.1
uses: JamesIves/github-pages-deploy-action@3.6.2
with:
BASE_BRANCH: master
BRANCH: gh-pages
@ -291,7 +291,7 @@ jobs:
name: site
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.1
uses: JamesIves/github-pages-deploy-action@3.6.2
with:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
BRANCH: gh-pages
@ -313,7 +313,7 @@ If you use a [container](https://help.github.com/en/actions/automating-your-work
apt-get update && apt-get install -y rsync
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.1
uses: JamesIves/github-pages-deploy-action@3.6.2
```
---
@ -329,3 +329,11 @@ If you wish to remove these files you must go into the deployment branch directl
### Debugging 🐝
If you'd like to enable action debugging you can set the `ACTIONS_STEP_DEBUG` environment variable to true within the [Settings/Secrets](https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets) menu. If you're using this action in your own project as a node module via yarn or npm **you may expose your secrets if you toggle this on in a production environment**. You can learn more about debugging GitHub actions [here](https://github.com/actions/toolkit/blob/master/docs/action-debugging.md).
---
## Support 💖
This project would not be possible without all of our fantastic [contributors](https://github.com/JamesIves/github-pages-deploy-action/graphs/contributors).
If you'd like to support the maintenance and upkeep of this project you can [donate via GitHub Sponsors](https://github.com/sponsors/JamesIves). This project is distributed under the [MIT](https://github.com/JamesIves/github-pages-deploy-action/blob/dev/LICENSE) license.

View File

@ -23,6 +23,7 @@ jest.mock('@actions/io', () => ({
}))
jest.mock('../src/execute', () => ({
__esModule: true,
execute: jest.fn()
}))
@ -32,216 +33,6 @@ describe('git', () => {
})
describe('init', () => {
it('should execute commands if a GitHub token is provided', async () => {
Object.assign(action, {
silent: false,
repositoryPath: 'JamesIves/github-pages-deploy-action',
folder: 'assets',
branch: 'branch',
gitHubToken: '123',
pusher: {
name: 'asd',
email: 'as@cat'
}
})
await init(action)
expect(execute).toBeCalledTimes(6)
})
it('should execute commands if an Access Token is provided', async () => {
Object.assign(action, {
silent: false,
repositoryPath: 'JamesIves/github-pages-deploy-action',
folder: 'assets',
branch: 'branch',
accessToken: '123',
pusher: {
name: 'asd',
email: 'as@cat'
}
})
await init(action)
expect(execute).toBeCalledTimes(6)
})
it('should execute commands if SSH is true', async () => {
Object.assign(action, {
silent: false,
repositoryPath: 'JamesIves/github-pages-deploy-action',
folder: 'assets',
branch: 'branch',
ssh: true,
pusher: {
name: 'asd',
email: 'as@cat'
}
})
await init(action)
expect(execute).toBeCalledTimes(6)
})
it('should fail if there is no provided GitHub Token, Access Token or SSH bool', async () => {
Object.assign(action, {
silent: false,
repositoryPath: null,
folder: 'assets',
branch: 'branch',
pusher: {
name: 'asd',
email: 'as@cat'
}
})
try {
await init(action)
} catch (e) {
expect(execute).toBeCalledTimes(0)
expect(e.message).toMatch(
'There was an error initializing the repository: No deployment token/method was provided. You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true. ❌'
)
}
})
it('should fail if access token is defined but it is an empty string', async () => {
Object.assign(action, {
silent: false,
repositoryPath: null,
folder: 'assets',
branch: 'branch',
pusher: {
name: 'asd',
email: 'as@cat'
},
accessToken: ''
})
try {
await init(action)
} catch (e) {
expect(execute).toBeCalledTimes(0)
expect(e.message).toMatch(
'There was an error initializing the repository: No deployment token/method was provided. You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true. ❌'
)
}
})
it('should fail if there is no folder', async () => {
Object.assign(action, {
silent: false,
repositoryPath: 'JamesIves/github-pages-deploy-action',
gitHubToken: '123',
branch: 'branch',
pusher: {
name: 'asd',
email: 'as@cat'
},
folder: null,
ssh: true
})
try {
await init(action)
} catch (e) {
expect(execute).toBeCalledTimes(0)
expect(e.message).toMatch(
'There was an error initializing the repository: You must provide the action with a folder to deploy. ❌'
)
}
})
it('should fail if there is no provided repository path', async () => {
Object.assign(action, {
silent: true,
repositoryPath: null,
folder: 'assets',
branch: 'branch',
pusher: {
name: 'asd',
email: 'as@cat'
},
gitHubToken: '123',
accessToken: null,
ssh: null
})
try {
await init(action)
} catch (e) {
expect(execute).toBeCalledTimes(0)
expect(e.message).toMatch(
'There was an error initializing the repository: No deployment token/method was provided. You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true. '
)
}
})
it('should fail if the build folder begins with a /', async () => {
Object.assign(action, {
silent: false,
accessToken: '123',
repositoryPath: 'JamesIves/github-pages-deploy-action',
branch: 'branch',
folder: '/',
pusher: {
name: 'asd',
email: 'as@cat'
}
})
try {
await init(action)
} catch (e) {
expect(execute).toBeCalledTimes(0)
expect(e.message).toMatch(
"There was an error initializing the repository: Incorrectly formatted build folder. The deployment folder cannot be prefixed with '/' or './'. Instead reference the folder name directly. ❌"
)
}
})
it('should fail if the build folder begins with a ./', async () => {
Object.assign(action, {
silent: false,
accessToken: '123',
branch: 'branch',
folder: './',
pusher: {
name: 'asd',
email: 'as@cat'
}
})
try {
await init(action)
} catch (e) {
expect(execute).toBeCalledTimes(0)
expect(e.message).toMatch(
"There was an error initializing the repository: Incorrectly formatted build folder. The deployment folder cannot be prefixed with '/' or './'. Instead reference the folder name directly. ❌"
)
}
})
it('should not fail if root is used', async () => {
Object.assign(action, {
silent: false,
repositoryPath: 'JamesIves/github-pages-deploy-action',
accessToken: '123',
branch: 'branch',
folder: '.',
root: '.',
pusher: {
name: 'asd',
email: 'as@cat'
}
})
await init(action)
expect(execute).toBeCalledTimes(6)
})
it('should stash changes if preserve is true', async () => {
Object.assign(action, {
silent: false,
@ -258,9 +49,36 @@ describe('git', () => {
})
await init(action)
expect(execute).toBeCalledTimes(7)
})
it('should catch when a function throws an error', async () => {
;(execute as jest.Mock).mockImplementationOnce(() => {
throw new Error('Mocked throw')
})
Object.assign(action, {
silent: false,
repositoryPath: 'JamesIves/github-pages-deploy-action',
accessToken: '123',
branch: 'branch',
folder: '.',
preserve: true,
isTest: true,
pusher: {
name: 'asd',
email: 'as@cat'
}
})
try {
await init(action)
} catch (error) {
expect(error.message).toBe(
'There was an error initializing the repository: Mocked throw ❌'
)
}
})
})
describe('generateBranch', () => {
@ -280,11 +98,15 @@ describe('git', () => {
expect(execute).toBeCalledTimes(6)
})
it('should fail if there is no branch', async () => {
it('should catch when a function throws an error', async () => {
;(execute as jest.Mock).mockImplementationOnce(() => {
throw new Error('Mocked throw')
})
Object.assign(action, {
silent: false,
accessToken: '123',
branch: null,
branch: 'branch',
folder: '.',
pusher: {
name: 'asd',
@ -294,9 +116,9 @@ describe('git', () => {
try {
await generateBranch(action)
} catch (e) {
expect(e.message).toMatch(
'There was an error creating the deployment branch: Branch is required. ❌'
} catch (error) {
expect(error.message).toBe(
'There was an error creating the deployment branch: There was an error switching to the base branch: Mocked throw ❌ ❌'
)
}
})
@ -336,15 +158,17 @@ describe('git', () => {
expect(execute).toBeCalledTimes(1)
})
it('should fail if one of the required parameters is not available', async () => {
it('should catch when a function throws an error', async () => {
;(execute as jest.Mock).mockImplementationOnce(() => {
throw new Error('Mocked throw')
})
Object.assign(action, {
silent: false,
baseBranch: '123',
accessToken: null,
gitHubToken: null,
ssh: null,
accessToken: '123',
branch: 'branch',
folder: null,
folder: '.',
pusher: {
name: 'asd',
email: 'as@cat'
@ -353,10 +177,9 @@ describe('git', () => {
try {
await switchToBaseBranch(action)
} catch (e) {
expect(execute).toBeCalledTimes(0)
expect(e.message).toMatch(
'There was an error switching to the base branch: No deployment token/method was provided. You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true. ❌'
} catch (error) {
expect(error.message).toBe(
'There was an error switching to the base branch: Mocked throw ❌'
)
}
})
@ -388,6 +211,7 @@ describe('git', () => {
Object.assign(action, {
silent: false,
folder: 'assets',
folderPath: 'assets',
branch: 'branch',
gitHubToken: '123',
lfs: true,
@ -407,26 +231,33 @@ describe('git', () => {
expect(response).toBe(Status.SUCCESS)
})
it('should not ignore CNAME or nojekyll if they exist in the deployment folder', async () => {
it('should appropriately move along if git stash errors', async () => {
;(execute as jest.Mock).mockImplementation(cmd => {
if (cmd === 'git stash apply') {
// Mocks the case where git stash apply errors.
throw new Error()
}
})
Object.assign(action, {
silent: false,
folder: 'assets',
folderPath: 'assets',
branch: 'branch',
gitHubToken: '123',
lfs: true,
preserve: true,
isTest: true,
pusher: {
name: 'asd',
email: 'as@cat'
},
clean: true
}
})
const response = await deploy(action)
fs.createWriteStream('assets/.nojekyll')
fs.createWriteStream('assets/CNAME')
// Includes the call to generateBranch
expect(execute).toBeCalledTimes(12)
expect(execute).toBeCalledTimes(14)
expect(rmRF).toBeCalledTimes(1)
expect(response).toBe(Status.SUCCESS)
})
@ -434,14 +265,16 @@ describe('git', () => {
it('should execute commands with single commit toggled', async () => {
Object.assign(action, {
silent: false,
folder: 'assets',
folder: 'other',
folderPath: 'other',
branch: 'branch',
gitHubToken: '123',
singleCommit: true,
pusher: {
name: 'asd',
email: 'as@cat'
}
},
clean: true
})
await deploy(action)
@ -451,11 +284,37 @@ describe('git', () => {
expect(rmRF).toBeCalledTimes(1)
})
it('should not ignore CNAME or nojekyll if they exist in the deployment folder', async () => {
Object.assign(action, {
silent: false,
folder: 'assets',
folderPath: 'assets',
branch: 'branch',
gitHubToken: '123',
pusher: {
name: 'asd',
email: 'as@cat'
},
clean: true
})
fs.createWriteStream('assets/.nojekyll')
fs.createWriteStream('assets/CNAME')
const response = await deploy(action)
// Includes the call to generateBranch
expect(execute).toBeCalledTimes(12)
expect(rmRF).toBeCalledTimes(1)
expect(response).toBe(Status.SUCCESS)
})
it('should execute commands with clean options, ommits sha commit message', async () => {
process.env.GITHUB_SHA = ''
Object.assign(action, {
silent: false,
folder: 'assets',
folder: 'other',
folderPath: 'other',
branch: 'branch',
gitHubToken: '123',
pusher: {
@ -463,7 +322,8 @@ describe('git', () => {
email: 'as@cat'
},
clean: true,
cleanExclude: '["cat", "montezuma"]'
cleanExclude: '["cat", "montezuma"]',
workspace: 'other'
})
await deploy(action)
@ -477,6 +337,7 @@ describe('git', () => {
Object.assign(action, {
silent: false,
folder: 'assets',
folderPath: 'assets',
branch: 'branch',
gitHubToken: '123',
pusher: {
@ -534,28 +395,28 @@ describe('git', () => {
expect(response).toBe(Status.SKIPPED)
})
it('should throw an error if one of the required parameters is not available', async () => {
it('should catch when a function throws an error', async () => {
;(execute as jest.Mock).mockImplementationOnce(() => {
throw new Error('Mocked throw')
})
Object.assign(action, {
silent: false,
folder: 'assets',
branch: 'branch',
ssh: null,
accessToken: null,
gitHubToken: null,
gitHubToken: '123',
lfs: true,
pusher: {
name: 'asd',
email: 'as@cat'
},
isTest: false // Setting this env variable to false means there will never be anything to commit and the action will exit early.
}
})
try {
await deploy(action)
} catch (e) {
expect(execute).toBeCalledTimes(1)
expect(rmRF).toBeCalledTimes(1)
expect(e.message).toMatch(
'The deploy step encountered an error: No deployment token/method was provided. You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true. ❌'
} catch (error) {
expect(error.message).toBe(
'The deploy step encountered an error: Mocked throw ❌'
)
}
})

View File

@ -1,8 +1,11 @@
import {ActionInterface} from '../src/constants'
import {
isNullOrUndefined,
generateTokenType,
generateRepositoryPath,
suppressSensitiveInformation
generateFolderPath,
suppressSensitiveInformation,
checkParameters
} from '../src/util'
describe('util', () => {
@ -21,13 +24,17 @@ describe('util', () => {
const value = 'montezuma'
expect(isNullOrUndefined(value)).toBeFalsy()
})
it('should return false if the value is empty string', async () => {
const value = ''
expect(isNullOrUndefined(value)).toBeTruthy()
})
})
describe('generateTokenType', () => {
it('should return ssh if ssh is provided', async () => {
const action = {
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
@ -41,7 +48,6 @@ describe('util', () => {
it('should return access token if access token is provided', async () => {
const action = {
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
@ -55,7 +61,6 @@ describe('util', () => {
it('should return github token if github token is provided', async () => {
const action = {
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: '123',
@ -69,7 +74,6 @@ describe('util', () => {
it('should return ... if no token is provided', async () => {
const action = {
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
@ -86,7 +90,6 @@ describe('util', () => {
const action = {
repositoryName: 'JamesIves/github-pages-deploy-action',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
@ -103,7 +106,6 @@ describe('util', () => {
const action = {
repositoryName: 'JamesIves/github-pages-deploy-action',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
@ -120,7 +122,6 @@ describe('util', () => {
const action = {
repositoryName: 'JamesIves/github-pages-deploy-action',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: '123',
@ -140,7 +141,6 @@ describe('util', () => {
repositoryPath:
'https://x-access-token:supersecret999%%%@github.com/anothersecret123333',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
accessToken: 'supersecret999%%%',
@ -160,7 +160,6 @@ describe('util', () => {
repositoryPath:
'https://x-access-token:supersecret999%%%@github.com/anothersecret123333',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
accessToken: 'supersecret999%%%',
@ -177,4 +176,154 @@ describe('util', () => {
})
})
})
describe('generateFolderPath', () => {
it('should return absolute path if folder name is provided', () => {
const action = {
branch: '123',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
accessToken: null,
ssh: null,
silent: false
}
expect(generateFolderPath(action)).toEqual('src/build')
})
it('should return original path if folder name begins with /', () => {
const action = {
branch: '123',
workspace: 'src/',
folder: '/home/user/repo/build',
gitHubToken: null,
accessToken: null,
ssh: null,
silent: false
}
expect(generateFolderPath(action)).toEqual('/home/user/repo/build')
})
it('should process as relative path if folder name begins with ./', () => {
const action = {
branch: '123',
workspace: 'src/',
folder: './build',
gitHubToken: null,
accessToken: null,
ssh: null,
silent: false
}
expect(generateFolderPath(action)).toEqual('src/build')
})
it('should return absolute path if folder name begins with ~', () => {
const action = {
branch: '123',
workspace: 'src/',
folder: '~/repo/build',
gitHubToken: null,
accessToken: null,
ssh: null,
silent: false
}
process.env.HOME = '/home/user'
expect(generateFolderPath(action)).toEqual('/home/user/repo/build')
})
})
describe('hasRequiredParameters', () => {
it('should fail if there is no provided GitHub Token, Access Token or SSH bool', () => {
const action = {
silent: false,
repositoryPath: undefined,
branch: 'branch',
folder: 'build',
workspace: 'src/'
}
try {
checkParameters(action)
} catch (e) {
expect(e.message).toMatch(
'No deployment token/method was provided. You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true.'
)
}
})
it('should fail if access token is defined but it is an empty string', () => {
const action = {
silent: false,
repositoryPath: undefined,
accessToken: '',
branch: 'branch',
folder: 'build',
workspace: 'src/'
}
try {
checkParameters(action)
} catch (e) {
expect(e.message).toMatch(
'No deployment token/method was provided. You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true.'
)
}
})
it('should fail if there is no branch', () => {
const action = {
silent: false,
repositoryPath: undefined,
accessToken: '123',
branch: '',
folder: 'build',
workspace: 'src/'
}
try {
checkParameters(action)
} catch (e) {
expect(e.message).toMatch('Branch is required.')
}
})
it('should fail if there is no folder', () => {
const action = {
silent: false,
repositoryPath: undefined,
gitHubToken: '123',
branch: 'branch',
folder: '',
workspace: 'src/'
}
try {
checkParameters(action)
} catch (e) {
expect(e.message).toMatch(
'You must provide the action with a folder to deploy.'
)
}
})
it('should fail if the folder does not exist in the tree', () => {
const action: ActionInterface = {
silent: false,
repositoryPath: undefined,
gitHubToken: '123',
branch: 'branch',
folder: 'notARealFolder',
workspace: '.'
}
try {
action.folderPath = generateFolderPath(action)
checkParameters(action)
} catch (e) {
expect(e.message).toMatch(
`The directory you're trying to deploy named notARealFolder doesn't exist. Please double check the path and any prerequisite build scripts and try again. ❗`
)
}
})
})
})

View File

@ -2,12 +2,12 @@
"name": "@jamesives/github-pages-deploy-action",
"description": "GitHub action for building a project and deploying it to GitHub pages.",
"author": "James Ives <iam@jamesiv.es> (https://jamesiv.es)",
"version": "3.6.1",
"version": "3.6.2",
"license": "MIT",
"main": "lib/lib.js",
"types": "lib/lib.d.ts",
"scripts": {
"build": "rm -rf lib && tsc --declaration",
"build": "rimraf lib && tsc --declaration",
"test": "jest",
"lint": "eslint src/**/*.ts",
"format": "prettier --write './**/*.ts'"
@ -40,14 +40,15 @@
},
"devDependencies": {
"@types/jest": "26.0.14",
"@types/node": "14.11.2",
"eslint": "7.9.0",
"eslint-plugin-github": "3.4.1",
"eslint-plugin-jest": "24.0.2",
"@types/node": "14.11.8",
"eslint": "7.11.0",
"eslint-plugin-github": "4.1.1",
"eslint-plugin-jest": "24.1.0",
"eslint-plugin-prettier": "^3.1.2",
"jest": "25.5.4",
"jest-circus": "26.4.2",
"jest-circus": "26.5.3",
"prettier": "2.1.2",
"rimraf": "^3.0.2",
"ts-jest": "25.5.1",
"typescript": "3.9.7"
}

View File

@ -24,6 +24,8 @@ export interface ActionInterface {
email?: string
/** The folder to deploy. */
folder: string
/** The auto generated folder path. */
folderPath?: string
/** GitHub deployment token. */
gitHubToken?: string | null
/** Determines if the action is running in test mode or not. */
@ -38,8 +40,6 @@ export interface ActionInterface {
repositoryName?: string
/** The fully qualified repositpory path, this gets auto generated if repositoryName is provided. */
repositoryPath?: string
/** The root directory where your project lives. */
root?: string
/** Wipes the commit history from the deployment branch in favor of a single commit. */
singleCommit?: boolean | null
/** Determines if the action should run in silent mode or not. */
@ -54,6 +54,26 @@ export interface ActionInterface {
workspace: string
}
/** The minimum required values to run the action as a node module. */
export interface NodeActionInterface {
/** Deployment access token. */
accessToken?: string | null
/** The branch that the action should deploy to. */
branch: string
/** The folder to deploy. */
folder: string
/** GitHub deployment token. */
gitHubToken?: string | null
/** The repository path, for example JamesIves/github-pages-deploy-action. */
repositoryName: string
/** Determines if the action should run in silent mode or not. */
silent: boolean
/** Set to true if you're using an ssh client in your build step. */
ssh?: boolean | null
/** The folder where your deployment project lives. */
workspace: string
}
/* Required action data that gets initialized when running within the GitHub Actions environment. */
export const action: ActionInterface = {
accessToken: getInput('ACCESS_TOKEN'),
@ -95,7 +115,6 @@ export const action: ActionInterface = {
: repository && repository.full_name
? repository.full_name
: process.env.GITHUB_REPOSITORY,
root: '.',
singleCommit: !isNullOrUndefined(getInput('SINGLE_COMMIT'))
? getInput('SINGLE_COMMIT').toLowerCase() === 'true'
: false,
@ -109,6 +128,12 @@ export const action: ActionInterface = {
workspace: process.env.GITHUB_WORKSPACE || ''
}
/** Types for the required action parameters. */
export type RequiredActionParameters = Pick<
ActionInterface,
'accessToken' | 'gitHubToken' | 'ssh' | 'branch' | 'folder'
>
/** Status codes for the action. */
export enum Status {
SUCCESS = 'success',

View File

@ -3,17 +3,11 @@ import {mkdirP, rmRF} from '@actions/io'
import fs from 'fs'
import {ActionInterface, Status} from './constants'
import {execute} from './execute'
import {
hasRequiredParameters,
isNullOrUndefined,
suppressSensitiveInformation
} from './util'
import {isNullOrUndefined, suppressSensitiveInformation} from './util'
/* Initializes git in the workspace. */
export async function init(action: ActionInterface): Promise<void | Error> {
try {
hasRequiredParameters(action)
info(`Deploying using ${action.tokenType}… 🔑`)
info('Configuring git…')
@ -72,8 +66,6 @@ export async function switchToBaseBranch(
action: ActionInterface
): Promise<void> {
try {
hasRequiredParameters(action)
await execute(
`git checkout --progress --force ${
action.baseBranch ? action.baseBranch : action.defaultBranch
@ -94,8 +86,6 @@ export async function switchToBaseBranch(
/* Generates the branch if it doesn't exist on the remote. */
export async function generateBranch(action: ActionInterface): Promise<void> {
try {
hasRequiredParameters(action)
info(`Creating the ${action.branch} branch…`)
await switchToBaseBranch(action)
@ -139,8 +129,6 @@ export async function deploy(action: ActionInterface): Promise<Status> {
info('Starting to commit changes…')
try {
hasRequiredParameters(action)
const commitMessage = !isNullOrUndefined(action.commitMessage)
? (action.commitMessage as string)
: `Deploying to ${action.branch} from ${action.baseBranch} ${
@ -185,10 +173,6 @@ export async function deploy(action: ActionInterface): Promise<Status> {
try {
await execute(`git stash apply`, action.workspace, action.silent)
if (action.isTest) {
throw new Error()
}
} catch {
info('Unable to apply from stash, continuing…')
}
@ -229,22 +213,24 @@ export async function deploy(action: ActionInterface): Promise<Status> {
Allows the user to specify the root if '.' is provided.
rsync is used to prevent file duplication. */
await execute(
`rsync -q -av --checksum --progress ${action.folder}/. ${
`rsync -q -av --checksum --progress ${action.folderPath}/. ${
action.targetFolder
? `${temporaryDeploymentDirectory}/${action.targetFolder}`
: temporaryDeploymentDirectory
} ${
action.clean
? `--delete ${excludes} ${
!fs.existsSync(`${action.folder}/CNAME`) ? '--exclude CNAME' : ''
!fs.existsSync(`${action.folderPath}/CNAME`)
? '--exclude CNAME'
: ''
} ${
!fs.existsSync(`${action.folder}/.nojekyll`)
!fs.existsSync(`${action.folderPath}/.nojekyll`)
? '--exclude .nojekyll'
: ''
}`
: ''
} --exclude .ssh --exclude .git --exclude .github ${
action.folder === action.root
action.folderPath === action.workspace
? `--exclude ${temporaryDeploymentDirectory}`
: ''
}`,

View File

@ -1,14 +1,19 @@
import {exportVariable, info, setFailed} from '@actions/core'
import {action, ActionInterface, Status} from './constants'
import {deploy, generateBranch, init} from './git'
import {generateRepositoryPath, generateTokenType} from './util'
import {ActionInterface, Status, NodeActionInterface} from './constants'
import {deploy, init} from './git'
import {
generateFolderPath,
checkParameters,
generateRepositoryPath,
generateTokenType
} from './util'
/** Initializes and runs the action.
*
* @param {object} configuration - The action configuration.
*/
export default async function run(
configuration: ActionInterface
configuration: ActionInterface | NodeActionInterface
): Promise<void> {
let status: Status = Status.RUNNING
@ -17,20 +22,24 @@ export default async function run(
GitHub Pages Deploy Action 🚀
🚀 Getting Started Guide: https://github.com/marketplace/actions/deploy-to-github-pages
FAQ/Wiki: https://github.com/JamesIves/github-pages-deploy-action/wiki
🔧 Support: https://github.com/JamesIves/github-pages-deploy-action/issues
Contribute: https://github.com/JamesIves/github-pages-deploy-action/blob/dev/CONTRIBUTING.md
Discussions / Q&A: https://github.com/JamesIves/github-pages-deploy-action/discussions
🔧 Report a Bug: https://github.com/JamesIves/github-pages-deploy-action/issues
📣 Maintained by James Ives (https://jamesiv.es)`)
📣 Maintained by James Ives: https://jamesiv.es
💖 Support: https://github.com/sponsors/JamesIves`)
info('Checking configuration and starting deployment… 🚦')
const settings = {
...action,
const settings: ActionInterface = {
...configuration
}
// Defines the repository paths and token types.
// Defines the repository/folder paths and token types.
// Also verifies that the action has all of the required parameters.
settings.folderPath = generateFolderPath(settings)
checkParameters(settings)
settings.repositoryPath = generateRepositoryPath(settings)
settings.tokenType = generateTokenType(settings)
@ -53,5 +62,3 @@ export default async function run(
exportVariable('DEPLOYMENT_STATUS', status)
}
}
export {init, deploy, generateBranch, ActionInterface}

View File

@ -1,6 +1,9 @@
import {existsSync} from 'fs'
import path from 'path'
import {isDebug} from '@actions/core'
import {ActionInterface} from './constants'
import {ActionInterface, RequiredActionParameters} from './constants'
/* Replaces all instances of a match in a string. */
const replaceAll = (input: string, find: string, replace: string): string =>
input.split(find).join(replace)
@ -26,31 +29,46 @@ export const generateRepositoryPath = (action: ActionInterface): string =>
action.accessToken || `x-access-token:${action.gitHubToken}`
}@github.com/${action.repositoryName}.git`
/* Genetate absolute folder path by the provided folder name */
export const generateFolderPath = (action: ActionInterface): string => {
const folderName = action['folder']
return path.isAbsolute(folderName)
? folderName
: folderName.startsWith('~')
? folderName.replace('~', process.env.HOME as string)
: path.join(action.workspace, folderName)
}
/* Checks for the required tokens and formatting. Throws an error if any case is matched. */
export const hasRequiredParameters = (action: ActionInterface): void => {
if (
(isNullOrUndefined(action.accessToken) &&
isNullOrUndefined(action.gitHubToken) &&
isNullOrUndefined(action.ssh)) ||
isNullOrUndefined(action.repositoryPath) ||
(action.accessToken && action.accessToken === '')
) {
const hasRequiredParameters = <K extends keyof RequiredActionParameters>(
action: ActionInterface,
params: K[]
): boolean => {
const nonNullParams = params.filter(
param => !isNullOrUndefined(action[param])
)
return Boolean(nonNullParams.length)
}
/* Verifies the action has the required parameters to run, otherwise throw an error. */
export const checkParameters = (action: ActionInterface): void => {
if (!hasRequiredParameters(action, ['accessToken', 'gitHubToken', 'ssh'])) {
throw new Error(
'No deployment token/method was provided. You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true.'
)
}
if (isNullOrUndefined(action.branch)) {
if (!hasRequiredParameters(action, ['branch'])) {
throw new Error('Branch is required.')
}
if (!action.folder || isNullOrUndefined(action.folder)) {
if (!hasRequiredParameters(action, ['folder'])) {
throw new Error('You must provide the action with a folder to deploy.')
}
if (action.folder.startsWith('/') || action.folder.startsWith('./')) {
if (!existsSync(action.folderPath as string)) {
throw new Error(
"Incorrectly formatted build folder. The deployment folder cannot be prefixed with '/' or './'. Instead reference the folder name directly."
`The directory you're trying to deploy named ${action.folderPath} doesn't exist. Please double check the path and any prerequisite build scripts and try again. ❗`
)
}
}

1226
yarn.lock

File diff suppressed because it is too large Load Diff