mirror of
https://github.com/JamesIves/github-pages-deploy-action.git
synced 2023-12-15 20:03:39 +08:00
Test current code base as an integration test for PRs and pushes (#505)
* Add a build step to create lib and node_modules artifact * Run integration test with built dist and current SHA as base For pull requests, the github.sha is the sha of the merge to the target branch, not the head of the PR. Special case that. * Use v2 checkout, and DRY_RUN for the integration test. I also made the branches more generic, as there are now more of them. * Fix #536, don't push at all on dryRun Also add tests for dryRun and singleCommit and generateBranch code flows. * Try to fix dryRun on new remote branches, refactor fetch * Try to fix dryRun, only fetch if origin branch exists * Refactor worktree setup to include branch generation and setup for singleCommit This is a continuation of the no-checkout work, and sadly suggested pretty intensive changes. * Set up git config to fix tests, also make debugging easier * Add matrix for existing and non-existing branch * Add matrix for singleCommit and not * Drop GITHUB_TOKEN, add DRY_RUN to action.yml * When deploying existing branch, add a modifcation and deploy again * Force branch checkout to work in redeployment scenarios * Make singleCommit easier to see in job descriptions * Review comments * Add a test-only property to action to test code paths with remote branch. * Introduce TestFlag enum to signal different test scenarios to unit tests * Fix util.test.ts
This commit is contained in:
parent
ac885860a8
commit
4e40ddd3f5
@ -55,7 +55,8 @@
|
|||||||
"@typescript-eslint/type-annotation-spacing": "error",
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
"@typescript-eslint/unbound-method": "error",
|
"@typescript-eslint/unbound-method": "error",
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-shadow": ["error", { "builtinGlobals": false, "hoist": "all", "allow": ["Status"] }]
|
"no-shadow": "off", // replaced by ts-eslint rule below
|
||||||
|
"@typescript-eslint/no-shadow": "error"
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"node": true,
|
"node": true,
|
||||||
|
85
.github/workflows/build.yml
vendored
85
.github/workflows/build.yml
vendored
@ -2,8 +2,8 @@ name: unit-tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- 'dev*'
|
||||||
- releases/v3
|
- 'releases/v*'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
@ -14,11 +14,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: actions/setup-node@v1.4.4
|
- uses: actions/setup-node@v1.4.4
|
||||||
with:
|
with:
|
||||||
node-version: '10.15.1'
|
node-version: 'v12.18.4'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Install Yarn
|
- name: Install Yarn
|
||||||
@ -34,3 +34,80 @@ jobs:
|
|||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v1.4.4
|
||||||
|
with:
|
||||||
|
node-version: 'v12.18.4'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install Yarn
|
||||||
|
run: npm install -g yarn
|
||||||
|
|
||||||
|
- name: Build lib
|
||||||
|
run: |
|
||||||
|
yarn install
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
- name: Rebuild production node_modules
|
||||||
|
run: |
|
||||||
|
yarn install --production
|
||||||
|
ls node_modules
|
||||||
|
|
||||||
|
- name: artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: |
|
||||||
|
lib
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
integration:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
branch: ["gh-pages", "no-pages"]
|
||||||
|
commit: ["singleCommit", "add commits"]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v1.4.4
|
||||||
|
with:
|
||||||
|
node-version: 'v12.18.4'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Download artifact
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
FOLDER: integration
|
||||||
|
BRANCH: ${{ matrix.branch }}
|
||||||
|
SINGLE_COMMIT: ${{ matrix.commit == 'singleCommit' }}
|
||||||
|
DRY_RUN: true
|
||||||
|
|
||||||
|
- name: Tweak content to publish to existing branch
|
||||||
|
if: ${{ matrix.branch == 'gh-pages' }}
|
||||||
|
run: |
|
||||||
|
echo "<!-- just sayin -->" >> integration/index.html
|
||||||
|
|
||||||
|
- name: Deploy with modifications to existing branch
|
||||||
|
uses: ./
|
||||||
|
if: ${{ matrix.branch == 'gh-pages' }}
|
||||||
|
with:
|
||||||
|
FOLDER: integration
|
||||||
|
BRANCH: ${{ matrix.branch }}
|
||||||
|
SINGLE_COMMIT: ${{ matrix.commit == 'singleCommit' }}
|
||||||
|
DRY_RUN: true
|
||||||
|
@ -1,2 +1 @@
|
|||||||
process.env.UNIT_TEST = 'true'
|
|
||||||
process.env.ACTIONS_STEP_DEBUG = 'false'
|
process.env.ACTIONS_STEP_DEBUG = 'false'
|
@ -4,9 +4,9 @@ process.env['INPUT_FOLDER'] = 'build'
|
|||||||
process.env['GITHUB_SHA'] = '123'
|
process.env['GITHUB_SHA'] = '123'
|
||||||
|
|
||||||
import {mkdirP, rmRF} from '@actions/io'
|
import {mkdirP, rmRF} from '@actions/io'
|
||||||
import {action, Status} from '../src/constants'
|
import {action, Status, TestFlag} from '../src/constants'
|
||||||
import {execute} from '../src/execute'
|
import {execute} from '../src/execute'
|
||||||
import {deploy, generateBranch, init} from '../src/git'
|
import {deploy, init} from '../src/git'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
const originalAction = JSON.stringify(action)
|
const originalAction = JSON.stringify(action)
|
||||||
@ -46,11 +46,11 @@ describe('git', () => {
|
|||||||
token: '123',
|
token: '123',
|
||||||
branch: 'branch',
|
branch: 'branch',
|
||||||
folder: '.',
|
folder: '.',
|
||||||
isTest: true,
|
|
||||||
pusher: {
|
pusher: {
|
||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
}
|
},
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -63,49 +63,6 @@ describe('git', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('generateBranch', () => {
|
|
||||||
it('should execute five commands', async () => {
|
|
||||||
Object.assign(action, {
|
|
||||||
silent: false,
|
|
||||||
token: '123',
|
|
||||||
branch: 'branch',
|
|
||||||
folder: '.',
|
|
||||||
pusher: {
|
|
||||||
name: 'asd',
|
|
||||||
email: 'as@cat'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await generateBranch(action)
|
|
||||||
expect(execute).toBeCalledTimes(5)
|
|
||||||
})
|
|
||||||
|
|
||||||
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,
|
|
||||||
token: '123',
|
|
||||||
branch: 'branch',
|
|
||||||
folder: '.',
|
|
||||||
pusher: {
|
|
||||||
name: 'asd',
|
|
||||||
email: 'as@cat'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
await generateBranch(action)
|
|
||||||
} catch (error) {
|
|
||||||
expect(error.message).toBe(
|
|
||||||
'There was an error creating the deployment branch: Mocked throw ❌'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('deploy', () => {
|
describe('deploy', () => {
|
||||||
it('should execute commands', async () => {
|
it('should execute commands', async () => {
|
||||||
Object.assign(action, {
|
Object.assign(action, {
|
||||||
@ -116,24 +73,19 @@ describe('git', () => {
|
|||||||
pusher: {
|
pusher: {
|
||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
}
|
},
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await deploy(action)
|
const response = await deploy(action)
|
||||||
|
|
||||||
// Includes the call to generateBranch
|
// Includes the call to generateWorktree
|
||||||
expect(execute).toBeCalledTimes(10)
|
expect(execute).toBeCalledTimes(11)
|
||||||
expect(execute).toHaveBeenNthCalledWith(
|
|
||||||
9,
|
|
||||||
expect.not.stringContaining('--dry-run'),
|
|
||||||
expect.anything(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
expect(response).toBe(Status.SUCCESS)
|
expect(response).toBe(Status.SUCCESS)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should push with --dry-run', async () => {
|
it('should not push when asked to dryRun', async () => {
|
||||||
Object.assign(action, {
|
Object.assign(action, {
|
||||||
silent: false,
|
silent: false,
|
||||||
dryRun: true,
|
dryRun: true,
|
||||||
@ -143,19 +95,14 @@ describe('git', () => {
|
|||||||
pusher: {
|
pusher: {
|
||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
}
|
},
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await deploy(action)
|
const response = await deploy(action)
|
||||||
|
|
||||||
// Includes the call to generateBranch
|
// Includes the call to generateWorktree
|
||||||
expect(execute).toBeCalledTimes(10)
|
expect(execute).toBeCalledTimes(10)
|
||||||
expect(execute).toHaveBeenNthCalledWith(
|
|
||||||
9,
|
|
||||||
expect.stringContaining('--dry-run'),
|
|
||||||
expect.anything(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
expect(response).toBe(Status.SUCCESS)
|
expect(response).toBe(Status.SUCCESS)
|
||||||
})
|
})
|
||||||
@ -172,13 +119,61 @@ describe('git', () => {
|
|||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
},
|
},
|
||||||
clean: true
|
clean: true,
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
})
|
})
|
||||||
|
|
||||||
await deploy(action)
|
await deploy(action)
|
||||||
|
|
||||||
// Includes the call to generateBranch
|
// Includes the call to generateWorktree
|
||||||
expect(execute).toBeCalledTimes(16)
|
expect(execute).toBeCalledTimes(10)
|
||||||
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute commands with single commit toggled and existing branch', async () => {
|
||||||
|
Object.assign(action, {
|
||||||
|
silent: false,
|
||||||
|
folder: 'other',
|
||||||
|
folderPath: 'other',
|
||||||
|
branch: 'branch',
|
||||||
|
token: '123',
|
||||||
|
singleCommit: true,
|
||||||
|
pusher: {
|
||||||
|
name: 'asd',
|
||||||
|
email: 'as@cat'
|
||||||
|
},
|
||||||
|
clean: true,
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES | TestFlag.HAS_REMOTE_BRANCH
|
||||||
|
})
|
||||||
|
|
||||||
|
await deploy(action)
|
||||||
|
|
||||||
|
// Includes the call to generateWorktree
|
||||||
|
expect(execute).toBeCalledTimes(9)
|
||||||
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute commands with single commit and dryRun toggled', async () => {
|
||||||
|
Object.assign(action, {
|
||||||
|
silent: false,
|
||||||
|
folder: 'other',
|
||||||
|
folderPath: 'other',
|
||||||
|
branch: 'branch',
|
||||||
|
gitHubToken: '123',
|
||||||
|
singleCommit: true,
|
||||||
|
dryRun: true,
|
||||||
|
pusher: {
|
||||||
|
name: 'asd',
|
||||||
|
email: 'as@cat'
|
||||||
|
},
|
||||||
|
clean: true,
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
|
})
|
||||||
|
|
||||||
|
await deploy(action)
|
||||||
|
|
||||||
|
// Includes the call to generateWorktree
|
||||||
|
expect(execute).toBeCalledTimes(9)
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -193,7 +188,8 @@ describe('git', () => {
|
|||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
},
|
},
|
||||||
clean: true
|
clean: true,
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
})
|
})
|
||||||
|
|
||||||
fs.createWriteStream('assets/.nojekyll')
|
fs.createWriteStream('assets/.nojekyll')
|
||||||
@ -201,13 +197,13 @@ describe('git', () => {
|
|||||||
|
|
||||||
const response = await deploy(action)
|
const response = await deploy(action)
|
||||||
|
|
||||||
// Includes the call to generateBranch
|
// Includes the call to generateWorktree
|
||||||
expect(execute).toBeCalledTimes(10)
|
expect(execute).toBeCalledTimes(11)
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
expect(response).toBe(Status.SUCCESS)
|
expect(response).toBe(Status.SUCCESS)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should execute commands with clean options, ommits sha commit message', async () => {
|
it('should execute commands with clean options, commits sha commit message', async () => {
|
||||||
process.env.GITHUB_SHA = ''
|
process.env.GITHUB_SHA = ''
|
||||||
Object.assign(action, {
|
Object.assign(action, {
|
||||||
silent: false,
|
silent: false,
|
||||||
@ -221,13 +217,14 @@ describe('git', () => {
|
|||||||
},
|
},
|
||||||
clean: true,
|
clean: true,
|
||||||
cleanExclude: '["cat", "montezuma"]',
|
cleanExclude: '["cat", "montezuma"]',
|
||||||
workspace: 'other'
|
workspace: 'other',
|
||||||
|
isTest: TestFlag.NONE
|
||||||
})
|
})
|
||||||
|
|
||||||
await deploy(action)
|
await deploy(action)
|
||||||
|
|
||||||
// Includes the call to generateBranch
|
// Includes the call to generateWorktree
|
||||||
expect(execute).toBeCalledTimes(10)
|
expect(execute).toBeCalledTimes(8)
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -243,13 +240,14 @@ describe('git', () => {
|
|||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
},
|
},
|
||||||
clean: true,
|
clean: true,
|
||||||
cleanExclude: ['cat', 'montezuma']
|
cleanExclude: ['cat', 'montezuma'],
|
||||||
|
isTest: TestFlag.NONE
|
||||||
})
|
})
|
||||||
|
|
||||||
await deploy(action)
|
await deploy(action)
|
||||||
|
|
||||||
// Includes the call to generateBranch
|
// Includes the call to generateWorktree
|
||||||
expect(execute).toBeCalledTimes(10)
|
expect(execute).toBeCalledTimes(8)
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -263,13 +261,13 @@ describe('git', () => {
|
|||||||
clean: true,
|
clean: true,
|
||||||
targetFolder: 'new_folder',
|
targetFolder: 'new_folder',
|
||||||
commitMessage: 'Hello!',
|
commitMessage: 'Hello!',
|
||||||
isTest: true,
|
isTest: TestFlag.NONE,
|
||||||
cleanExclude: '["cat, "montezuma"]' // There is a syntax errror in the string.
|
cleanExclude: '["cat, "montezuma"]' // There is a syntax errror in the string.
|
||||||
})
|
})
|
||||||
|
|
||||||
await deploy(action)
|
await deploy(action)
|
||||||
|
|
||||||
expect(execute).toBeCalledTimes(10)
|
expect(execute).toBeCalledTimes(8)
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
expect(mkdirP).toBeCalledTimes(1)
|
expect(mkdirP).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
@ -284,11 +282,11 @@ describe('git', () => {
|
|||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
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.
|
isTest: TestFlag.NONE // Setting this flag to None means there will never be anything to commit and the action will exit early.
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await deploy(action)
|
const response = await deploy(action)
|
||||||
expect(execute).toBeCalledTimes(10)
|
expect(execute).toBeCalledTimes(8)
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
expect(response).toBe(Status.SKIPPED)
|
expect(response).toBe(Status.SKIPPED)
|
||||||
})
|
})
|
||||||
@ -306,7 +304,8 @@ describe('git', () => {
|
|||||||
pusher: {
|
pusher: {
|
||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
}
|
},
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -5,7 +5,7 @@ process.env['GITHUB_SHA'] = '123'
|
|||||||
process.env['INPUT_DEBUG'] = 'debug'
|
process.env['INPUT_DEBUG'] = 'debug'
|
||||||
|
|
||||||
import '../src/main'
|
import '../src/main'
|
||||||
import {action} from '../src/constants'
|
import {action, TestFlag} from '../src/constants'
|
||||||
import run from '../src/lib'
|
import run from '../src/lib'
|
||||||
import {execute} from '../src/execute'
|
import {execute} from '../src/execute'
|
||||||
import {rmRF} from '@actions/io'
|
import {rmRF} from '@actions/io'
|
||||||
@ -44,11 +44,11 @@ describe('main', () => {
|
|||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
},
|
},
|
||||||
isTest: false,
|
isTest: TestFlag.NONE,
|
||||||
debug: true
|
debug: true
|
||||||
})
|
})
|
||||||
await run(action)
|
await run(action)
|
||||||
expect(execute).toBeCalledTimes(12)
|
expect(execute).toBeCalledTimes(10)
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
expect(exportVariable).toBeCalledTimes(1)
|
expect(exportVariable).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
@ -62,10 +62,11 @@ describe('main', () => {
|
|||||||
pusher: {
|
pusher: {
|
||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
}
|
},
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
})
|
})
|
||||||
await run(action)
|
await run(action)
|
||||||
expect(execute).toBeCalledTimes(12)
|
expect(execute).toBeCalledTimes(13)
|
||||||
expect(rmRF).toBeCalledTimes(1)
|
expect(rmRF).toBeCalledTimes(1)
|
||||||
expect(exportVariable).toBeCalledTimes(1)
|
expect(exportVariable).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
@ -80,7 +81,7 @@ describe('main', () => {
|
|||||||
name: 'asd',
|
name: 'asd',
|
||||||
email: 'as@cat'
|
email: 'as@cat'
|
||||||
},
|
},
|
||||||
isTest: true
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
})
|
})
|
||||||
await run(action)
|
await run(action)
|
||||||
expect(execute).toBeCalledTimes(0)
|
expect(execute).toBeCalledTimes(0)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {ActionInterface} from '../src/constants'
|
import {ActionInterface, TestFlag} from '../src/constants'
|
||||||
import {
|
import {
|
||||||
isNullOrUndefined,
|
isNullOrUndefined,
|
||||||
generateTokenType,
|
generateTokenType,
|
||||||
@ -39,7 +39,8 @@ describe('util', () => {
|
|||||||
folder: 'build',
|
folder: 'build',
|
||||||
token: null,
|
token: null,
|
||||||
ssh: true,
|
ssh: true,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
expect(generateTokenType(action)).toEqual('SSH Deploy Key')
|
expect(generateTokenType(action)).toEqual('SSH Deploy Key')
|
||||||
})
|
})
|
||||||
@ -51,7 +52,8 @@ describe('util', () => {
|
|||||||
folder: 'build',
|
folder: 'build',
|
||||||
token: '123',
|
token: '123',
|
||||||
ssh: null,
|
ssh: null,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
expect(generateTokenType(action)).toEqual('Deploy Token')
|
expect(generateTokenType(action)).toEqual('Deploy Token')
|
||||||
})
|
})
|
||||||
@ -63,7 +65,8 @@ describe('util', () => {
|
|||||||
folder: 'build',
|
folder: 'build',
|
||||||
token: null,
|
token: null,
|
||||||
ssh: null,
|
ssh: null,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
expect(generateTokenType(action)).toEqual('…')
|
expect(generateTokenType(action)).toEqual('…')
|
||||||
})
|
})
|
||||||
@ -78,7 +81,8 @@ describe('util', () => {
|
|||||||
folder: 'build',
|
folder: 'build',
|
||||||
token: null,
|
token: null,
|
||||||
ssh: true,
|
ssh: true,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
expect(generateRepositoryPath(action)).toEqual(
|
expect(generateRepositoryPath(action)).toEqual(
|
||||||
'git@github.com:JamesIves/github-pages-deploy-action'
|
'git@github.com:JamesIves/github-pages-deploy-action'
|
||||||
@ -93,7 +97,8 @@ describe('util', () => {
|
|||||||
folder: 'build',
|
folder: 'build',
|
||||||
token: '123',
|
token: '123',
|
||||||
ssh: null,
|
ssh: null,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
expect(generateRepositoryPath(action)).toEqual(
|
expect(generateRepositoryPath(action)).toEqual(
|
||||||
'https://x-access-token:123@github.com/JamesIves/github-pages-deploy-action.git'
|
'https://x-access-token:123@github.com/JamesIves/github-pages-deploy-action.git'
|
||||||
@ -110,7 +115,8 @@ describe('util', () => {
|
|||||||
workspace: 'src/',
|
workspace: 'src/',
|
||||||
folder: 'build',
|
folder: 'build',
|
||||||
token: 'anothersecret123333',
|
token: 'anothersecret123333',
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
const string = `This is an error message! It contains ${action.token} and ${action.repositoryPath} and ${action.token} again!`
|
const string = `This is an error message! It contains ${action.token} and ${action.repositoryPath} and ${action.token} again!`
|
||||||
@ -128,7 +134,8 @@ describe('util', () => {
|
|||||||
workspace: 'src/',
|
workspace: 'src/',
|
||||||
folder: 'build',
|
folder: 'build',
|
||||||
token: 'anothersecret123333',
|
token: 'anothersecret123333',
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env['RUNNER_DEBUG'] = '1'
|
process.env['RUNNER_DEBUG'] = '1'
|
||||||
@ -149,7 +156,8 @@ describe('util', () => {
|
|||||||
folder: 'build',
|
folder: 'build',
|
||||||
token: null,
|
token: null,
|
||||||
ssh: null,
|
ssh: null,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
expect(generateFolderPath(action)).toEqual('src/build')
|
expect(generateFolderPath(action)).toEqual('src/build')
|
||||||
})
|
})
|
||||||
@ -161,7 +169,8 @@ describe('util', () => {
|
|||||||
folder: '/home/user/repo/build',
|
folder: '/home/user/repo/build',
|
||||||
token: null,
|
token: null,
|
||||||
ssh: null,
|
ssh: null,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
expect(generateFolderPath(action)).toEqual('/home/user/repo/build')
|
expect(generateFolderPath(action)).toEqual('/home/user/repo/build')
|
||||||
})
|
})
|
||||||
@ -173,7 +182,8 @@ describe('util', () => {
|
|||||||
folder: './build',
|
folder: './build',
|
||||||
token: null,
|
token: null,
|
||||||
ssh: null,
|
ssh: null,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
expect(generateFolderPath(action)).toEqual('src/build')
|
expect(generateFolderPath(action)).toEqual('src/build')
|
||||||
})
|
})
|
||||||
@ -185,7 +195,8 @@ describe('util', () => {
|
|||||||
folder: '~/repo/build',
|
folder: '~/repo/build',
|
||||||
token: null,
|
token: null,
|
||||||
ssh: null,
|
ssh: null,
|
||||||
silent: false
|
silent: false,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
process.env.HOME = '/home/user'
|
process.env.HOME = '/home/user'
|
||||||
expect(generateFolderPath(action)).toEqual('/home/user/repo/build')
|
expect(generateFolderPath(action)).toEqual('/home/user/repo/build')
|
||||||
@ -199,7 +210,8 @@ describe('util', () => {
|
|||||||
repositoryPath: undefined,
|
repositoryPath: undefined,
|
||||||
branch: 'branch',
|
branch: 'branch',
|
||||||
folder: 'build',
|
folder: 'build',
|
||||||
workspace: 'src/'
|
workspace: 'src/',
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -218,7 +230,8 @@ describe('util', () => {
|
|||||||
token: '',
|
token: '',
|
||||||
branch: 'branch',
|
branch: 'branch',
|
||||||
folder: 'build',
|
folder: 'build',
|
||||||
workspace: 'src/'
|
workspace: 'src/',
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -237,7 +250,8 @@ describe('util', () => {
|
|||||||
token: '123',
|
token: '123',
|
||||||
branch: '',
|
branch: '',
|
||||||
folder: 'build',
|
folder: 'build',
|
||||||
workspace: 'src/'
|
workspace: 'src/',
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -254,7 +268,8 @@ describe('util', () => {
|
|||||||
token: '123',
|
token: '123',
|
||||||
branch: 'branch',
|
branch: 'branch',
|
||||||
folder: '',
|
folder: '',
|
||||||
workspace: 'src/'
|
workspace: 'src/',
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -273,7 +288,8 @@ describe('util', () => {
|
|||||||
token: '123',
|
token: '123',
|
||||||
branch: 'branch',
|
branch: 'branch',
|
||||||
folder: 'notARealFolder',
|
folder: 'notARealFolder',
|
||||||
workspace: '.'
|
workspace: '.',
|
||||||
|
isTest: TestFlag.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
35
__tests__/worktree.error.test.ts
Normal file
35
__tests__/worktree.error.test.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {TestFlag} from '../src/constants'
|
||||||
|
import {execute} from '../src/execute'
|
||||||
|
import {generateWorktree} from '../src/worktree'
|
||||||
|
|
||||||
|
jest.mock('../src/execute', () => ({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
__esModule: true,
|
||||||
|
execute: jest.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('generateWorktree', () => {
|
||||||
|
it('should catch when a function throws an error', async () => {
|
||||||
|
;(execute as jest.Mock).mockImplementationOnce(() => {
|
||||||
|
throw new Error('Mocked throw')
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await generateWorktree(
|
||||||
|
{
|
||||||
|
workspace: 'somewhere',
|
||||||
|
singleCommit: false,
|
||||||
|
branch: 'gh-pages',
|
||||||
|
folder: '',
|
||||||
|
silent: true,
|
||||||
|
isTest: TestFlag.HAS_CHANGED_FILES
|
||||||
|
},
|
||||||
|
'worktree',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toBe(
|
||||||
|
'There was an error creating the worktree: Mocked throw ❌'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
195
__tests__/worktree.test.ts
Normal file
195
__tests__/worktree.test.ts
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import {rmRF} from '@actions/io'
|
||||||
|
import {TestFlag} from '../src/constants'
|
||||||
|
import {generateWorktree} from '../src/worktree'
|
||||||
|
import {execute} from '../src/execute'
|
||||||
|
import fs from 'fs'
|
||||||
|
import os from 'os'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
jest.mock('@actions/core', () => ({
|
||||||
|
setFailed: jest.fn(),
|
||||||
|
getInput: jest.fn(),
|
||||||
|
isDebug: jest.fn(),
|
||||||
|
info: jest.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test generateWorktree against a known git repository.
|
||||||
|
The upstream repository `origin` is set up once for the test suite,
|
||||||
|
and for each test run, a new clone is created.
|
||||||
|
|
||||||
|
See workstree.error.test.ts for testing mocked errors from git.*/
|
||||||
|
|
||||||
|
describe('generateWorktree', () => {
|
||||||
|
let tempdir: string | null = null
|
||||||
|
let clonedir: string | null = null
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Set up origin repository
|
||||||
|
const silent = true
|
||||||
|
tempdir = fs.mkdtempSync(path.join(os.tmpdir(), 'gh-deploy-'))
|
||||||
|
const origin = path.join(tempdir, 'origin')
|
||||||
|
await execute('git init origin', tempdir, silent)
|
||||||
|
await execute('git config user.email "you@example.com"', origin, silent)
|
||||||
|
await execute('git config user.name "Jane Doe"', origin, silent)
|
||||||
|
await execute('git checkout -b main', origin, silent)
|
||||||
|
fs.writeFileSync(path.join(origin, 'f1'), 'hello world\n')
|
||||||
|
await execute('git add .', origin, silent)
|
||||||
|
await execute('git commit -mc0', origin, silent)
|
||||||
|
fs.writeFileSync(path.join(origin, 'f1'), 'hello world\nand planets\n')
|
||||||
|
await execute('git add .', origin, silent)
|
||||||
|
await execute('git commit -mc1', origin, silent)
|
||||||
|
await execute('git checkout --orphan gh-pages', origin, silent)
|
||||||
|
await execute('git reset --hard', origin, silent)
|
||||||
|
await fs.promises.writeFile(path.join(origin, 'gh1'), 'pages content\n')
|
||||||
|
await execute('git add .', origin, silent)
|
||||||
|
await execute('git commit -mgh0', origin, silent)
|
||||||
|
await fs.promises.writeFile(
|
||||||
|
path.join(origin, 'gh1'),
|
||||||
|
'pages content\ngoes on\n'
|
||||||
|
)
|
||||||
|
await execute('git add .', origin, silent)
|
||||||
|
await execute('git commit -mgh1', origin, silent)
|
||||||
|
})
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Clone origin to our workspace for each test
|
||||||
|
const silent = true
|
||||||
|
clonedir = path.join(tempdir as string, 'clone')
|
||||||
|
await execute('git init clone', tempdir as string, silent)
|
||||||
|
await execute('git config user.email "you@example.com"', clonedir, silent)
|
||||||
|
await execute('git config user.name "Jane Doe"', clonedir, silent)
|
||||||
|
await execute(
|
||||||
|
`git remote add origin ${path.join(tempdir as string, 'origin')}`,
|
||||||
|
clonedir,
|
||||||
|
silent
|
||||||
|
)
|
||||||
|
await execute('git fetch --depth=1 origin main', clonedir, silent)
|
||||||
|
await execute('git checkout main', clonedir, silent)
|
||||||
|
})
|
||||||
|
afterEach(async () => {
|
||||||
|
// Tear down workspace
|
||||||
|
await rmRF(clonedir as string)
|
||||||
|
})
|
||||||
|
afterAll(async () => {
|
||||||
|
// Tear down origin repository
|
||||||
|
if (tempdir) {
|
||||||
|
await rmRF(tempdir)
|
||||||
|
// console.log(tempdir)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
describe('with existing branch and new commits', () => {
|
||||||
|
it('should check out the latest commit', async () => {
|
||||||
|
const workspace = clonedir as string
|
||||||
|
await generateWorktree(
|
||||||
|
{
|
||||||
|
workspace,
|
||||||
|
singleCommit: false,
|
||||||
|
branch: 'gh-pages',
|
||||||
|
folder: '',
|
||||||
|
silent: true,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
|
},
|
||||||
|
'worktree',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
const dirEntries = await fs.promises.readdir(
|
||||||
|
path.join(workspace, 'worktree')
|
||||||
|
)
|
||||||
|
expect(dirEntries.sort((a, b) => a.localeCompare(b))).toEqual([
|
||||||
|
'.git',
|
||||||
|
'gh1'
|
||||||
|
])
|
||||||
|
const commitMessages = await execute(
|
||||||
|
'git log --format=%s',
|
||||||
|
path.join(workspace, 'worktree'),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
expect(commitMessages).toBe('gh1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('with missing branch and new commits', () => {
|
||||||
|
it('should create initial commit', async () => {
|
||||||
|
const workspace = clonedir as string
|
||||||
|
await generateWorktree(
|
||||||
|
{
|
||||||
|
workspace,
|
||||||
|
singleCommit: false,
|
||||||
|
branch: 'no-pages',
|
||||||
|
folder: '',
|
||||||
|
silent: true,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
|
},
|
||||||
|
'worktree',
|
||||||
|
false
|
||||||
|
)
|
||||||
|
const dirEntries = await fs.promises.readdir(
|
||||||
|
path.join(workspace, 'worktree')
|
||||||
|
)
|
||||||
|
expect(dirEntries).toEqual(['.git'])
|
||||||
|
const commitMessages = await execute(
|
||||||
|
'git log --format=%s',
|
||||||
|
path.join(workspace, 'worktree'),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
expect(commitMessages).toBe('Initial no-pages commit')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('with existing branch and singleCommit', () => {
|
||||||
|
it('should check out the latest commit', async () => {
|
||||||
|
const workspace = clonedir as string
|
||||||
|
await generateWorktree(
|
||||||
|
{
|
||||||
|
workspace,
|
||||||
|
singleCommit: true,
|
||||||
|
branch: 'gh-pages',
|
||||||
|
folder: '',
|
||||||
|
silent: true,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
|
},
|
||||||
|
'worktree',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
const dirEntries = await fs.promises.readdir(
|
||||||
|
path.join(workspace, 'worktree')
|
||||||
|
)
|
||||||
|
expect(dirEntries.sort((a, b) => a.localeCompare(b))).toEqual([
|
||||||
|
'.git',
|
||||||
|
'gh1'
|
||||||
|
])
|
||||||
|
expect(async () => {
|
||||||
|
await execute(
|
||||||
|
'git log --format=%s',
|
||||||
|
path.join(workspace, 'worktree'),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}).rejects.toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('with missing branch and singleCommit', () => {
|
||||||
|
it('should create initial commit', async () => {
|
||||||
|
const workspace = clonedir as string
|
||||||
|
await generateWorktree(
|
||||||
|
{
|
||||||
|
workspace,
|
||||||
|
singleCommit: true,
|
||||||
|
branch: 'no-pages',
|
||||||
|
folder: '',
|
||||||
|
silent: true,
|
||||||
|
isTest: TestFlag.NONE
|
||||||
|
},
|
||||||
|
'worktree',
|
||||||
|
false
|
||||||
|
)
|
||||||
|
const dirEntries = await fs.promises.readdir(
|
||||||
|
path.join(workspace, 'worktree')
|
||||||
|
)
|
||||||
|
expect(dirEntries).toEqual(['.git'])
|
||||||
|
expect(async () => {
|
||||||
|
await execute(
|
||||||
|
'git log --format=%s',
|
||||||
|
path.join(workspace, 'worktree'),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}).rejects.toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -50,6 +50,10 @@ inputs:
|
|||||||
description: "If you need to use CLEAN but you would like to preserve certain files or folders you can use this option. This should be formatted as an array but stored as a string."
|
description: "If you need to use CLEAN but you would like to preserve certain files or folders you can use this option. This should be formatted as an array but stored as a string."
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
DRY_RUN:
|
||||||
|
description: "Do not actually push back, but use `--dry-run` on `git push` invocations insead."
|
||||||
|
required: false
|
||||||
|
|
||||||
GIT_CONFIG_NAME:
|
GIT_CONFIG_NAME:
|
||||||
description: "Allows you to customize the name that is attached to the GitHub config which is used when pushing the deployment commits. If this is not included it will use the name in the GitHub context, followed by the name of the action."
|
description: "Allows you to customize the name that is attached to the GitHub config which is used when pushing the deployment commits. If this is not included it will use the name in the GitHub context, followed by the name of the action."
|
||||||
required: false
|
required: false
|
||||||
|
@ -4,6 +4,13 @@ import {isNullOrUndefined} from './util'
|
|||||||
|
|
||||||
const {pusher, repository} = github.context.payload
|
const {pusher, repository} = github.context.payload
|
||||||
|
|
||||||
|
/* Flags to signal different scenarios to test cases */
|
||||||
|
export enum TestFlag {
|
||||||
|
NONE = 0,
|
||||||
|
HAS_CHANGED_FILES = 1 << 1, // Assume changes to commit
|
||||||
|
HAS_REMOTE_BRANCH = 1 << 2 // Assume remote repository has existing commits
|
||||||
|
}
|
||||||
|
|
||||||
/* For more information please refer to the README: https://github.com/JamesIves/github-pages-deploy-action */
|
/* For more information please refer to the README: https://github.com/JamesIves/github-pages-deploy-action */
|
||||||
export interface ActionInterface {
|
export interface ActionInterface {
|
||||||
/** The branch that the action should deploy to. */
|
/** The branch that the action should deploy to. */
|
||||||
@ -22,8 +29,8 @@ export interface ActionInterface {
|
|||||||
folder: string
|
folder: string
|
||||||
/** The auto generated folder path. */
|
/** The auto generated folder path. */
|
||||||
folderPath?: string
|
folderPath?: string
|
||||||
/** Determines if the action is running in test mode or not. */
|
/** Determines test scenarios the action is running in. */
|
||||||
isTest?: boolean | null
|
isTest: TestFlag
|
||||||
/** The git config name. */
|
/** The git config name. */
|
||||||
name?: string
|
name?: string
|
||||||
/** The repository path, for example JamesIves/github-pages-deploy-action. */
|
/** The repository path, for example JamesIves/github-pages-deploy-action. */
|
||||||
@ -62,6 +69,7 @@ export interface NodeActionInterface {
|
|||||||
ssh?: boolean | null
|
ssh?: boolean | null
|
||||||
/** The folder where your deployment project lives. */
|
/** The folder where your deployment project lives. */
|
||||||
workspace: string
|
workspace: string
|
||||||
|
isTest: TestFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Required action data that gets initialized when running within the GitHub Actions environment. */
|
/* Required action data that gets initialized when running within the GitHub Actions environment. */
|
||||||
@ -76,9 +84,7 @@ export const action: ActionInterface = {
|
|||||||
? getInput('CLEAN').toLowerCase() === 'true'
|
? getInput('CLEAN').toLowerCase() === 'true'
|
||||||
: false,
|
: false,
|
||||||
cleanExclude: getInput('CLEAN_EXCLUDE'),
|
cleanExclude: getInput('CLEAN_EXCLUDE'),
|
||||||
isTest: process.env.UNIT_TEST
|
isTest: TestFlag.NONE,
|
||||||
? process.env.UNIT_TEST.toLowerCase() === 'true'
|
|
||||||
: false,
|
|
||||||
email: !isNullOrUndefined(getInput('GIT_CONFIG_EMAIL'))
|
email: !isNullOrUndefined(getInput('GIT_CONFIG_EMAIL'))
|
||||||
? getInput('GIT_CONFIG_EMAIL')
|
? getInput('GIT_CONFIG_EMAIL')
|
||||||
: pusher && pusher.email
|
: pusher && pusher.email
|
||||||
@ -115,7 +121,7 @@ export const action: ActionInterface = {
|
|||||||
/** Types for the required action parameters. */
|
/** Types for the required action parameters. */
|
||||||
export type RequiredActionParameters = Pick<
|
export type RequiredActionParameters = Pick<
|
||||||
ActionInterface,
|
ActionInterface,
|
||||||
'token' | 'ssh' | 'branch' | 'folder'
|
'token' | 'ssh' | 'branch' | 'folder' | 'isTest'
|
||||||
>
|
>
|
||||||
|
|
||||||
/** Status codes for the action. */
|
/** Status codes for the action. */
|
||||||
|
123
src/git.ts
123
src/git.ts
@ -1,8 +1,9 @@
|
|||||||
import {info} from '@actions/core'
|
import {info} from '@actions/core'
|
||||||
import {mkdirP, rmRF} from '@actions/io'
|
import {mkdirP, rmRF} from '@actions/io'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import {ActionInterface, Status} from './constants'
|
import {ActionInterface, Status, TestFlag} from './constants'
|
||||||
import {execute} from './execute'
|
import {execute} from './execute'
|
||||||
|
import {generateWorktree} from './worktree'
|
||||||
import {isNullOrUndefined, suppressSensitiveInformation} from './util'
|
import {isNullOrUndefined, suppressSensitiveInformation} from './util'
|
||||||
|
|
||||||
/* Initializes git in the workspace. */
|
/* Initializes git in the workspace. */
|
||||||
@ -33,41 +34,6 @@ export async function init(action: ActionInterface): Promise<void | Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Generates the branch if it doesn't exist on the remote. */
|
|
||||||
export async function generateBranch(action: ActionInterface): Promise<void> {
|
|
||||||
try {
|
|
||||||
info(`Creating the ${action.branch} branch…`)
|
|
||||||
|
|
||||||
await execute(
|
|
||||||
`git checkout --orphan ${action.branch}`,
|
|
||||||
action.workspace,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
await execute(`git reset --hard`, action.workspace, action.silent)
|
|
||||||
await execute(
|
|
||||||
`git commit --no-verify --allow-empty -m "Initial ${action.branch} commit"`,
|
|
||||||
action.workspace,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
const dry = action.dryRun ? '--dry-run ' : ''
|
|
||||||
await execute(
|
|
||||||
`git push --force ${dry}${action.repositoryPath} ${action.branch}`,
|
|
||||||
action.workspace,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
await execute(`git fetch`, action.workspace, action.silent)
|
|
||||||
|
|
||||||
info(`Created the ${action.branch} branch… 🔧`)
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(
|
|
||||||
`There was an error creating the deployment branch: ${suppressSensitiveInformation(
|
|
||||||
error.message,
|
|
||||||
action
|
|
||||||
)} ❌`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Runs the necessary steps to make the deployment. */
|
/* Runs the necessary steps to make the deployment. */
|
||||||
export async function deploy(action: ActionInterface): Promise<Status> {
|
export async function deploy(action: ActionInterface): Promise<Status> {
|
||||||
const temporaryDeploymentDirectory =
|
const temporaryDeploymentDirectory =
|
||||||
@ -75,7 +41,6 @@ export async function deploy(action: ActionInterface): Promise<Status> {
|
|||||||
const temporaryDeploymentBranch = `github-pages-deploy-action/${Math.random()
|
const temporaryDeploymentBranch = `github-pages-deploy-action/${Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
.substr(2, 9)}`
|
.substr(2, 9)}`
|
||||||
const dry = action.dryRun ? '--dry-run ' : ''
|
|
||||||
|
|
||||||
info('Starting to commit changes…')
|
info('Starting to commit changes…')
|
||||||
|
|
||||||
@ -86,31 +51,16 @@ export async function deploy(action: ActionInterface): Promise<Status> {
|
|||||||
process.env.GITHUB_SHA ? ` from @ ${process.env.GITHUB_SHA}` : ''
|
process.env.GITHUB_SHA ? ` from @ ${process.env.GITHUB_SHA}` : ''
|
||||||
} 🚀`
|
} 🚀`
|
||||||
|
|
||||||
/*
|
// Checks to see if the remote exists prior to deploying.
|
||||||
Checks to see if the remote exists prior to deploying.
|
const branchExists =
|
||||||
If the branch doesn't exist it gets created here as an orphan.
|
action.isTest & TestFlag.HAS_REMOTE_BRANCH ||
|
||||||
*/
|
(await execute(
|
||||||
const branchExists = await execute(
|
|
||||||
`git ls-remote --heads ${action.repositoryPath} ${action.branch} | wc -l`,
|
`git ls-remote --heads ${action.repositoryPath} ${action.branch} | wc -l`,
|
||||||
action.workspace,
|
action.workspace,
|
||||||
action.silent
|
action.silent
|
||||||
)
|
))
|
||||||
|
|
||||||
if (!branchExists && !action.isTest) {
|
await generateWorktree(action, temporaryDeploymentDirectory, branchExists)
|
||||||
await generateBranch(action)
|
|
||||||
} else {
|
|
||||||
await execute(
|
|
||||||
`git fetch --no-recurse-submodules --depth=1 origin ${action.branch}`,
|
|
||||||
action.workspace,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
await execute(
|
|
||||||
`git worktree add --checkout ${temporaryDeploymentDirectory} origin/${action.branch}`,
|
|
||||||
action.workspace,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 = ''
|
||||||
@ -166,13 +116,23 @@ export async function deploy(action: ActionInterface): Promise<Status> {
|
|||||||
action.silent
|
action.silent
|
||||||
)
|
)
|
||||||
|
|
||||||
const hasFilesToCommit = await execute(
|
// Use git status to check if we have something to commit.
|
||||||
`git status --porcelain`,
|
// Special case is singleCommit with existing history, when
|
||||||
|
// we're really interested if the diff against the upstream branch
|
||||||
|
// changed.
|
||||||
|
const checkGitStatus =
|
||||||
|
branchExists && action.singleCommit
|
||||||
|
? `git diff origin/${action.branch}`
|
||||||
|
: `git status --porcelain`
|
||||||
|
const hasFilesToCommit =
|
||||||
|
action.isTest & TestFlag.HAS_CHANGED_FILES ||
|
||||||
|
(await execute(
|
||||||
|
checkGitStatus,
|
||||||
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
||||||
action.silent
|
action.silent
|
||||||
)
|
))
|
||||||
|
|
||||||
if (!hasFilesToCommit && !action.isTest) {
|
if (!hasFilesToCommit) {
|
||||||
return Status.SKIPPED
|
return Status.SKIPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,49 +152,16 @@ export async function deploy(action: ActionInterface): Promise<Status> {
|
|||||||
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
||||||
action.silent
|
action.silent
|
||||||
)
|
)
|
||||||
|
if (!action.dryRun) {
|
||||||
await execute(
|
await execute(
|
||||||
`git push --force ${dry}${action.repositoryPath} ${temporaryDeploymentBranch}:${action.branch}`,
|
`git push --force ${action.repositoryPath} ${temporaryDeploymentBranch}:${action.branch}`,
|
||||||
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
||||||
action.silent
|
action.silent
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
info(`Changes committed to the ${action.branch} branch… 📦`)
|
info(`Changes committed to the ${action.branch} branch… 📦`)
|
||||||
|
|
||||||
if (action.singleCommit) {
|
|
||||||
await execute(
|
|
||||||
`git fetch ${action.repositoryPath}`,
|
|
||||||
action.workspace,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
await execute(
|
|
||||||
`git checkout --orphan ${action.branch}-temp`,
|
|
||||||
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
await execute(
|
|
||||||
`git add --all .`,
|
|
||||||
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
await execute(
|
|
||||||
`git commit -m "${commitMessage}" --quiet --no-verify`,
|
|
||||||
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
await execute(
|
|
||||||
`git branch -M ${action.branch}-temp ${action.branch}`,
|
|
||||||
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
await execute(
|
|
||||||
`git push origin ${action.branch} ${dry}--force`,
|
|
||||||
`${action.workspace}/${temporaryDeploymentDirectory}`,
|
|
||||||
action.silent
|
|
||||||
)
|
|
||||||
|
|
||||||
info('Cleared git history… 🚿')
|
|
||||||
}
|
|
||||||
|
|
||||||
return Status.SUCCESS
|
return Status.SUCCESS
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
85
src/worktree.ts
Normal file
85
src/worktree.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import {info} from '@actions/core'
|
||||||
|
import {ActionInterface} from './constants'
|
||||||
|
import {execute} from './execute'
|
||||||
|
import {suppressSensitiveInformation} from './util'
|
||||||
|
|
||||||
|
export class GitCheckout {
|
||||||
|
orphan = false
|
||||||
|
commitish?: string | null = null
|
||||||
|
branch: string
|
||||||
|
constructor(branch: string) {
|
||||||
|
this.branch = branch
|
||||||
|
}
|
||||||
|
toString(): string {
|
||||||
|
return [
|
||||||
|
'git',
|
||||||
|
'checkout',
|
||||||
|
this.orphan ? '--orphan' : '-B',
|
||||||
|
this.branch,
|
||||||
|
this.commitish || ''
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Generate the worktree and set initial content if it exists */
|
||||||
|
|
||||||
|
export async function generateWorktree(
|
||||||
|
action: ActionInterface,
|
||||||
|
worktreedir: string,
|
||||||
|
branchExists: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
info('Creating worktree…')
|
||||||
|
|
||||||
|
if (branchExists) {
|
||||||
|
await execute(
|
||||||
|
`git fetch --no-recurse-submodules --depth=1 origin ${action.branch}`,
|
||||||
|
action.workspace,
|
||||||
|
action.silent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await execute(
|
||||||
|
`git worktree add --no-checkout --detach ${worktreedir}`,
|
||||||
|
action.workspace,
|
||||||
|
action.silent
|
||||||
|
)
|
||||||
|
const checkout = new GitCheckout(action.branch)
|
||||||
|
if (branchExists) {
|
||||||
|
// There's existing data on the branch to check out
|
||||||
|
checkout.commitish = `origin/${action.branch}`
|
||||||
|
}
|
||||||
|
if (!branchExists || action.singleCommit) {
|
||||||
|
// Create a new history if we don't have the branch, or if we want to reset it
|
||||||
|
checkout.orphan = true
|
||||||
|
}
|
||||||
|
await execute(
|
||||||
|
checkout.toString(),
|
||||||
|
`${action.workspace}/${worktreedir}`,
|
||||||
|
action.silent
|
||||||
|
)
|
||||||
|
if (!branchExists) {
|
||||||
|
info(`Created the ${action.branch} branch… 🔧`)
|
||||||
|
// Our index is in HEAD state, reset
|
||||||
|
await execute(
|
||||||
|
'git reset --hard',
|
||||||
|
`${action.workspace}/${worktreedir}`,
|
||||||
|
action.silent
|
||||||
|
)
|
||||||
|
if (!action.singleCommit) {
|
||||||
|
// New history isn't singleCommit, create empty initial commit
|
||||||
|
await execute(
|
||||||
|
`git commit --no-verify --allow-empty -m "Initial ${action.branch} commit"`,
|
||||||
|
`${action.workspace}/${worktreedir}`,
|
||||||
|
action.silent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`There was an error creating the worktree: ${suppressSensitiveInformation(
|
||||||
|
error.message,
|
||||||
|
action
|
||||||
|
)} ❌`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user