diff --git a/README.md b/README.md index cbec9fc..b57fa3c 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,33 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` + +### When pushed to master, push the contents of `/public/site` to the `www` folder on the `gh-pages` branch on the same repo + +```yml +name: Deploy to GitHub Pages +on: + push: + branches: + - master + +jobs: + deploy: + name: Deploy to GitHub Pages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + - name: Deploy + uses: s0/git-publish-subdir-action@develop + env: + REPO: self + BRANCH: gh-pages + FOLDER: public/site + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TARGET_DIR: www +``` + ### When pushed to master, run a build step, then push `/build` to the `gh-pages` branch on another repo on GitHub ```yml @@ -174,6 +201,7 @@ All configuration options are passed in via `env`, as environment variables. | `CLEAR_GLOBS_FILE` | An optional path to a file to use as a list of globs defining which files to delete when clearing the target branch. | No | | `COMMIT_NAME` | The username the autogenerated commit will use. If unset, uses the commit pusher's username. | No | | `COMMIT_EMAIL` | The email the autogenerated commit will use. If unset, uses the commit pusher's email. | No | +| `TARGET_DIR` | An optional string to change the directory where the files are copied to. | No | ### Custom commit messages @@ -226,6 +254,13 @@ everything, and you will need to specify exactly what needs to be deleted.** !.git ``` +1. Default behaviour with a custom target directory: + + ``` + target_dir/**/* + !.git + ``` + 1. Delete everything except the `.git` and `foobar` folder: ``` @@ -329,4 +364,4 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX= -----END OPENSSH PRIVATE KEY----- -``` \ No newline at end of file +``` diff --git a/action/dist/index.js b/action/dist/index.js index b103528..d453fdc 100644 --- a/action/dist/index.js +++ b/action/dist/index.js @@ -12280,6 +12280,10 @@ const main = async ({ env = process.env, log, }) => { .filter((s) => s !== ''); return globList; } + else if (env.TARGET_DIR) { + log.log(`##[info] Removing all files from target dir ${env.TARGET_DIR} on target branch`); + return [`${env.TARGET_DIR}/**/*`, '!.git']; + } else { // Remove all files log.log(`##[info] Removing all files from target branch`); @@ -12297,9 +12301,16 @@ const main = async ({ env = process.env, log, }) => { await fs_1.promises.unlink(entry); } const folder = path.resolve(process.cwd(), config.folder); + const destinationFolder = env.TARGET_DIR ? env.TARGET_DIR : './'; + // Make sure the destination folder exists + await (0, io_1.mkdirP)(path.resolve(REPO_TEMP, destinationFolder)); log.log(`##[info] Copying all files from ${folder}`); // TODO: replace this copy with a node implementation - await (0, exports.exec)(`cp -rT "${folder}"/ ./`, { log, env: childEnv, cwd: REPO_TEMP }); + await (0, exports.exec)(`cp -rT "${folder}"/ ${destinationFolder}`, { + log, + env: childEnv, + cwd: REPO_TEMP, + }); await (0, exports.exec)(`git add -A .`, { log, env: childEnv, cwd: REPO_TEMP }); const message = config.message .replace(/\{target\-branch\}/g, config.branch) diff --git a/action/src/index.ts b/action/src/index.ts index da4049e..de95f11 100644 --- a/action/src/index.ts +++ b/action/src/index.ts @@ -143,6 +143,10 @@ export interface EnvironmentVariables { GITHUB_EVENT_PATH?: string; /** The name of the person / app that that initiated the workflow */ GITHUB_ACTOR?: string; + /** + * An optional string to change the directory where the files are copied to + */ + TARGET_DIR?: string; } declare global { @@ -547,6 +551,11 @@ export const main = async ({ .map((s) => s.trim()) .filter((s) => s !== ''); return globList; + } else if (env.TARGET_DIR) { + log.log( + `##[info] Removing all files from target dir ${env.TARGET_DIR} on target branch` + ); + return [`${env.TARGET_DIR}/**/*`, '!.git']; } else { // Remove all files log.log(`##[info] Removing all files from target branch`); @@ -564,9 +573,18 @@ export const main = async ({ await fs.unlink(entry); } const folder = path.resolve(process.cwd(), config.folder); + const destinationFolder = env.TARGET_DIR ? env.TARGET_DIR : './'; + + // Make sure the destination folder exists + await mkdirP(path.resolve(REPO_TEMP, destinationFolder)); + log.log(`##[info] Copying all files from ${folder}`); // TODO: replace this copy with a node implementation - await exec(`cp -rT "${folder}"/ ./`, { log, env: childEnv, cwd: REPO_TEMP }); + await exec(`cp -rT "${folder}"/ ${destinationFolder}`, { + log, + env: childEnv, + cwd: REPO_TEMP, + }); await exec(`git add -A .`, { log, env: childEnv, cwd: REPO_TEMP }); const message = config.message .replace(/\{target\-branch\}/g, config.branch) diff --git a/action/test/specs/__snapshots__/ssh-target-dir-exists.spec.ts.snap b/action/test/specs/__snapshots__/ssh-target-dir-exists.spec.ts.snap new file mode 100644 index 0000000..5539ef1 --- /dev/null +++ b/action/test/specs/__snapshots__/ssh-target-dir-exists.spec.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Deploy to a branch on a custom dir that exists 1`] = ` +"msg:Update master to output generated at +tree:91548ed23fe65d56dfcbb58357a11c32c9b0b95d +author:s0 +msg:initial +tree:6ceb2d248e9b9c62291e9d1a5c4afeca8a49b2c8 +author:Test User " +`; diff --git a/action/test/specs/__snapshots__/ssh-target-dir-no-exists.spec.ts.snap b/action/test/specs/__snapshots__/ssh-target-dir-no-exists.spec.ts.snap new file mode 100644 index 0000000..4b90f23 --- /dev/null +++ b/action/test/specs/__snapshots__/ssh-target-dir-no-exists.spec.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Deploy to a branch on a custom dir that does not exist 1`] = ` +"msg:Update master to output generated at +tree:91548ed23fe65d56dfcbb58357a11c32c9b0b95d +author:s0 +msg:initial +tree:2ba0f344a4227360745f8b743b28af58d010c2f4 +author:Test User " +`; diff --git a/action/test/specs/ssh-target-dir-exists.spec.ts b/action/test/specs/ssh-target-dir-exists.spec.ts new file mode 100644 index 0000000..2f820b4 --- /dev/null +++ b/action/test/specs/ssh-target-dir-exists.spec.ts @@ -0,0 +1,92 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { mkdirP } from '@actions/io'; + +import * as util from '../util'; +import { prepareTestFolders } from '../util/io'; +import { listTree } from '../util/git'; + +it('Deploy to a branch on a custom dir that exists', async () => { + const folders = await prepareTestFolders({ __filename }); + + // Create empty repo + await util.wrappedExec('git init --bare', { cwd: folders.repoDir }); + + // Clone repo, and create an initial commit + await util.wrappedExec(`git clone "${folders.repoDir}" clone`, { + cwd: folders.workDir, + }); + await fs.writeFile(path.join(folders.repoCloneDir, 'initial1'), 'foobar1'); + await fs.writeFile(path.join(folders.repoCloneDir, 'initial2'), 'foobar2'); + await mkdirP(path.join(folders.repoCloneDir, 'folder')); + await fs.writeFile(path.join(folders.repoCloneDir, 'folder', 'a'), 'foobar1'); + await fs.writeFile(path.join(folders.repoCloneDir, 'folder', 'b'), 'foobar2'); + await mkdirP(path.join(folders.repoCloneDir, 'custom', 'b')); + await fs.writeFile(path.join(folders.repoCloneDir, 'custom', 'a'), 'foobar1'); + await fs.writeFile( + path.join(folders.repoCloneDir, 'custom', 'b', 'c'), + 'foobar1' + ); + await util.wrappedExec(`git add -A .`, { cwd: folders.repoCloneDir }); + await util.wrappedExec(`git config user.name "Test User"`, { + cwd: folders.repoCloneDir, + }); + await util.wrappedExec(`git config user.email "test@example.com"`, { + cwd: folders.repoCloneDir, + }); + await util.wrappedExec(`git commit -m initial`, { + cwd: folders.repoCloneDir, + }); + await util.wrappedExec(`git push origin master`, { + cwd: folders.repoCloneDir, + }); + + // Create dummy data + await mkdirP(path.join(folders.dataDir, 'dummy')); + await fs.writeFile(path.join(folders.dataDir, 'dummy', 'baz'), 'foobar'); + await fs.writeFile(path.join(folders.dataDir, 'dummy', '.bat'), 'foobar'); + + // Run Action + await util.runWithGithubEnv( + path.basename(__filename), + { + REPO: folders.repoUrl, + BRANCH: 'master', + FOLDER: folders.dataDir, + SSH_PRIVATE_KEY: (await fs.readFile(util.SSH_PRIVATE_KEY)).toString(), + KNOWN_HOSTS_FILE: util.KNOWN_HOSTS, + TARGET_DIR: 'custom', + }, + 's0/test', + {}, + 's0' + ); + + // Check that the list of files in the root of the target repo is as expected + expect(await listTree(folders.repoDir)).toEqual([ + '.', + 'custom', + 'custom/dummy', + 'custom/dummy/.bat', + 'custom/dummy/baz', + 'folder', + 'folder/a', + 'folder/b', + 'initial1', + 'initial2', + ]); + + // Check that the log of the repo is as expected + // (check tree-hash, commit message, and author) + const log = ( + await util.exec( + 'git log --pretty="format:msg:%s%ntree:%T%nauthor:%an <%ae>" master', + { + cwd: folders.repoDir, + } + ) + ).stdout; + const sha = await util.getRepoSha(); + const cleanedLog = log.replace(sha, ''); + expect(cleanedLog).toMatchSnapshot(); +}); diff --git a/action/test/specs/ssh-target-dir-no-exists.spec.ts b/action/test/specs/ssh-target-dir-no-exists.spec.ts new file mode 100644 index 0000000..39e009b --- /dev/null +++ b/action/test/specs/ssh-target-dir-no-exists.spec.ts @@ -0,0 +1,86 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { mkdirP } from '@actions/io'; + +import * as util from '../util'; +import { prepareTestFolders } from '../util/io'; +import { listTree } from '../util/git'; + +it('Deploy to a branch on a custom dir that does not exist', async () => { + const folders = await prepareTestFolders({ __filename }); + + // Create empty repo + await util.wrappedExec('git init --bare', { cwd: folders.repoDir }); + + // Clone repo, and create an initial commit + await util.wrappedExec(`git clone "${folders.repoDir}" clone`, { + cwd: folders.workDir, + }); + await fs.writeFile(path.join(folders.repoCloneDir, 'initial1'), 'foobar1'); + await fs.writeFile(path.join(folders.repoCloneDir, 'initial2'), 'foobar2'); + await mkdirP(path.join(folders.repoCloneDir, 'folder')); + await fs.writeFile(path.join(folders.repoCloneDir, 'folder', 'a'), 'foobar1'); + await fs.writeFile(path.join(folders.repoCloneDir, 'folder', 'b'), 'foobar2'); + await util.wrappedExec(`git add -A .`, { cwd: folders.repoCloneDir }); + await util.wrappedExec(`git config user.name "Test User"`, { + cwd: folders.repoCloneDir, + }); + await util.wrappedExec(`git config user.email "test@example.com"`, { + cwd: folders.repoCloneDir, + }); + await util.wrappedExec(`git commit -m initial`, { + cwd: folders.repoCloneDir, + }); + await util.wrappedExec(`git push origin master`, { + cwd: folders.repoCloneDir, + }); + + // Create dummy data + await mkdirP(path.join(folders.dataDir, 'dummy')); + await fs.writeFile(path.join(folders.dataDir, 'dummy', 'baz'), 'foobar'); + await fs.writeFile(path.join(folders.dataDir, 'dummy', '.bat'), 'foobar'); + + // Run Action + await util.runWithGithubEnv( + path.basename(__filename), + { + REPO: folders.repoUrl, + BRANCH: 'master', + FOLDER: folders.dataDir, + SSH_PRIVATE_KEY: (await fs.readFile(util.SSH_PRIVATE_KEY)).toString(), + KNOWN_HOSTS_FILE: util.KNOWN_HOSTS, + TARGET_DIR: 'custom', + }, + 's0/test', + {}, + 's0' + ); + + // Check that the list of files in the root of the target repo is as expected + expect(await listTree(folders.repoDir)).toEqual([ + '.', + 'custom', + 'custom/dummy', + 'custom/dummy/.bat', + 'custom/dummy/baz', + 'folder', + 'folder/a', + 'folder/b', + 'initial1', + 'initial2', + ]); + + // Check that the log of the repo is as expected + // (check tree-hash, commit message, and author) + const log = ( + await util.exec( + 'git log --pretty="format:msg:%s%ntree:%T%nauthor:%an <%ae>" master', + { + cwd: folders.repoDir, + } + ) + ).stdout; + const sha = await util.getRepoSha(); + const cleanedLog = log.replace(sha, ''); + expect(cleanedLog).toMatchSnapshot(); +});