Feat: add support for checking if source folder exists (#463)

* Refactor: check parameters in `init`

* Test: add test for checking if folder exist

- rm unnecessary tests

* Test: fix wrong error message

* Refactor: mv initialization functions to `run`

Refactor: rm `action.root`, `action.rootPath`

Refactor: `generateFolderPath`, `hasRequiredParameters`

Test: rm some tests for `init`, add tests for `checkParameters`
This commit is contained in:
Exuanbo 2020-10-15 15:20:15 +01:00 committed by GitHub
parent dc96a8ff00
commit dc667b3217
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 154 additions and 304 deletions

View File

@ -32,171 +32,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 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,
@ -213,7 +48,6 @@ describe('git', () => {
})
await init(action)
expect(execute).toBeCalledTimes(7)
})
})
@ -234,27 +68,6 @@ describe('git', () => {
await generateBranch(action)
expect(execute).toBeCalledTimes(6)
})
it('should fail if there is no branch', async () => {
Object.assign(action, {
silent: false,
accessToken: '123',
branch: null,
folder: '.',
pusher: {
name: 'asd',
email: 'as@cat'
}
})
try {
await generateBranch(action)
} catch (e) {
expect(e.message).toMatch(
'There was an error creating the deployment branch: Branch is required. ❌'
)
}
})
})
describe('switchToBaseBranch', () => {
@ -290,31 +103,6 @@ describe('git', () => {
await switchToBaseBranch(action)
expect(execute).toBeCalledTimes(1)
})
it('should fail if one of the required parameters is not available', async () => {
Object.assign(action, {
silent: false,
baseBranch: '123',
accessToken: null,
gitHubToken: null,
ssh: null,
branch: 'branch',
folder: null,
pusher: {
name: 'asd',
email: 'as@cat'
}
})
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. ❌'
)
}
})
})
describe('deploy', () => {
@ -488,31 +276,5 @@ describe('git', () => {
expect(rmRF).toBeCalledTimes(1)
expect(response).toBe(Status.SKIPPED)
})
it('should throw an error if one of the required parameters is not available', async () => {
Object.assign(action, {
silent: false,
folder: 'assets',
branch: 'branch',
ssh: null,
accessToken: null,
gitHubToken: null,
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. ❌'
)
}
})
})
})

View File

@ -1,9 +1,11 @@
import {ActionInterface} from '../src/constants'
import {
isNullOrUndefined,
generateTokenType,
generateRepositoryPath,
generateFolderPath,
suppressSensitiveInformation
suppressSensitiveInformation,
checkParameters
} from '../src/util'
describe('util', () => {
@ -22,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,
@ -42,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,
@ -56,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',
@ -70,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,
@ -87,7 +90,6 @@ describe('util', () => {
const action = {
repositoryName: 'JamesIves/github-pages-deploy-action',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
@ -104,7 +106,6 @@ describe('util', () => {
const action = {
repositoryName: 'JamesIves/github-pages-deploy-action',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
@ -121,7 +122,6 @@ describe('util', () => {
const action = {
repositoryName: 'JamesIves/github-pages-deploy-action',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: '123',
@ -141,7 +141,6 @@ describe('util', () => {
repositoryPath:
'https://x-access-token:supersecret999%%%@github.com/anothersecret123333',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
accessToken: 'supersecret999%%%',
@ -161,7 +160,6 @@ describe('util', () => {
repositoryPath:
'https://x-access-token:supersecret999%%%@github.com/anothersecret123333',
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
accessToken: 'supersecret999%%%',
@ -183,7 +181,6 @@ describe('util', () => {
it('should return absolute path if folder name is provided', () => {
const action = {
branch: '123',
root: '.',
workspace: 'src/',
folder: 'build',
gitHubToken: null,
@ -191,13 +188,12 @@ describe('util', () => {
ssh: null,
silent: false
}
expect(generateFolderPath(action, 'folder')).toEqual('src/build')
expect(generateFolderPath(action)).toEqual('src/build')
})
it('should return original path if folder name begins with /', () => {
const action = {
branch: '123',
root: '.',
workspace: 'src/',
folder: '/home/user/repo/build',
gitHubToken: null,
@ -205,15 +201,12 @@ describe('util', () => {
ssh: null,
silent: false
}
expect(generateFolderPath(action, 'folder')).toEqual(
'/home/user/repo/build'
)
expect(generateFolderPath(action)).toEqual('/home/user/repo/build')
})
it('should process as relative path if folder name begins with ./', () => {
const action = {
branch: '123',
root: '.',
workspace: 'src/',
folder: './build',
gitHubToken: null,
@ -221,13 +214,12 @@ describe('util', () => {
ssh: null,
silent: false
}
expect(generateFolderPath(action, 'folder')).toEqual('src/build')
expect(generateFolderPath(action)).toEqual('src/build')
})
it('should return absolute path if folder name begins with ~', () => {
const action = {
branch: '123',
root: '.',
workspace: 'src/',
folder: '~/repo/build',
gitHubToken: null,
@ -236,9 +228,102 @@ describe('util', () => {
silent: false
}
process.env.HOME = '/home/user'
expect(generateFolderPath(action, 'folder')).toEqual(
'/home/user/repo/build'
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 ${action.folderPath} directory you're trying to deploy doesn't exist.`
)
}
})
})
})

View File

@ -24,6 +24,7 @@ export interface ActionInterface {
email?: string
/** The folder to deploy. */
folder: string
folderPath?: string
/** GitHub deployment token. */
gitHubToken?: string | null
/** Determines if the action is running in test mode or not. */
@ -38,8 +39,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. */
@ -95,7 +94,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,8 +107,9 @@ export const action: ActionInterface = {
workspace: process.env.GITHUB_WORKSPACE || ''
}
export type ActionFolders = NonNullable<
Pick<ActionInterface, 'folder' | 'root'>
export type RequiredActionParameters = Pick<
ActionInterface,
'accessToken' | 'gitHubToken' | 'ssh' | 'branch' | 'folder'
>
/** Status codes for the action. */

View File

@ -3,18 +3,11 @@ import {mkdirP, rmRF} from '@actions/io'
import fs from 'fs'
import {ActionInterface, Status} from './constants'
import {execute} from './execute'
import {
generateFolderPath,
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…')
@ -73,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
@ -95,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)
@ -131,8 +120,6 @@ export async function generateBranch(action: ActionInterface): Promise<void> {
/* Runs the necessary steps to make the deployment. */
export async function deploy(action: ActionInterface): Promise<Status> {
const folderPath = generateFolderPath(action, 'folder')
const rootPath = generateFolderPath(action, 'root')
const temporaryDeploymentDirectory =
'github-pages-deploy-action-temp-deployment-folder'
const temporaryDeploymentBranch = `github-pages-deploy-action/${Math.random()
@ -142,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} ${
@ -232,22 +217,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 ${folderPath}/. ${
`rsync -q -av --checksum --progress ${action.folderPath}/. ${
action.targetFolder
? `${temporaryDeploymentDirectory}/${action.targetFolder}`
: temporaryDeploymentDirectory
} ${
action.clean
? `--delete ${excludes} ${
!fs.existsSync(`${folderPath}/CNAME`) ? '--exclude CNAME' : ''
!fs.existsSync(`${action.folderPath}/CNAME`)
? '--exclude CNAME'
: ''
} ${
!fs.existsSync(`${folderPath}/.nojekyll`)
!fs.existsSync(`${action.folderPath}/.nojekyll`)
? '--exclude .nojekyll'
: ''
}`
: ''
} --exclude .ssh --exclude .git --exclude .github ${
folderPath === rootPath
action.folderPath === action.workspace
? `--exclude ${temporaryDeploymentDirectory}`
: ''
}`,

View File

@ -1,7 +1,12 @@
import {exportVariable, info, setFailed} from '@actions/core'
import {action, ActionInterface, Status} from './constants'
import {deploy, init} from './git'
import {generateRepositoryPath, generateTokenType} from './util'
import {
generateFolderPath,
checkParameters,
generateRepositoryPath,
generateTokenType
} from './util'
/** Initializes and runs the action.
*
@ -30,6 +35,11 @@ export default async function run(
...configuration
}
// Defines the folder paths
settings.folderPath = generateFolderPath(settings)
checkParameters(settings)
// Defines the repository paths and token types.
settings.repositoryPath = generateRepositoryPath(settings)
settings.tokenType = generateTokenType(settings)

View File

@ -1,6 +1,7 @@
import {existsSync} from 'fs'
import path from 'path'
import {isDebug} from '@actions/core'
import {ActionInterface, ActionFolders} from './constants'
import {ActionInterface, RequiredActionParameters} from './constants'
const replaceAll = (input: string, find: string, replace: string): string =>
input.split(find).join(replace)
@ -28,40 +29,46 @@ export const generateRepositoryPath = (action: ActionInterface): string =>
}@github.com/${action.repositoryName}.git`
/* Genetate absolute folder path by the provided folder name */
export const generateFolderPath = <K extends keyof ActionFolders>(
action: ActionInterface,
key: K
): string => {
const folderName = action[key]
const folderPath = path.isAbsolute(folderName)
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)
return folderPath
}
/* 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)
}
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 (!existsSync(action.folderPath as string)) {
throw new Error(
`The ${action.folderPath} directory you're trying to deploy doesn't exist. ❗`
)
}
}
/* Suppresses sensitive information from being exposed in error messages. */