From 55e55bbeb134b0f497698e1c1810eee72f6e72ab Mon Sep 17 00:00:00 2001 From: JoseD Marques Date: Tue, 8 Nov 2022 19:59:17 +0000 Subject: [PATCH 1/4] feat: add target_dir to options --- README.md | 37 +++++++- action/dist/index.js | 10 ++- action/src/index.ts | 16 +++- .../ssh-target-dir-exists.spec.ts.snap | 10 +++ .../ssh-target-dir-no-exists.spec.ts.snap | 10 +++ .../test/specs/ssh-target-dir-exists.spec.ts | 88 +++++++++++++++++++ .../specs/ssh-target-dir-no-exists.spec.ts | 86 ++++++++++++++++++ 7 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 action/test/specs/__snapshots__/ssh-target-dir-exists.spec.ts.snap create mode 100644 action/test/specs/__snapshots__/ssh-target-dir-no-exists.spec.ts.snap create mode 100644 action/test/specs/ssh-target-dir-exists.spec.ts create mode 100644 action/test/specs/ssh-target-dir-no-exists.spec.ts diff --git a/README.md b/README.md index cbec9fc..7217d49 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..10289fa 100644 --- a/action/dist/index.js +++ b/action/dist/index.js @@ -395,6 +395,7 @@ function copyFile(srcFile, destFile, force) { } //# sourceMappingURL=io.js.map + /***/ }), /***/ 2: @@ -12280,6 +12281,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 +12302,12 @@ 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..db4f007 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,14 @@ 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..1801905 --- /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:d019febe4c92496949f9d9eb3eff95d8435aff76 +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..6554fc2 --- /dev/null +++ b/action/test/specs/ssh-target-dir-exists.spec.ts @@ -0,0 +1,88 @@ +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')); + await fs.writeFile(path.join(folders.repoCloneDir, 'custom', 'a'), '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..d769a7f --- /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(); +}); From f42367bdbaf637d474c6747d7a5eb82e7b3b34bf Mon Sep 17 00:00:00 2001 From: Sam Lanning Date: Thu, 17 Nov 2022 22:56:21 +0000 Subject: [PATCH 2/4] Add test that shows neste files aren't deleted with TARGET_DIR --- .../specs/__snapshots__/ssh-target-dir-exists.spec.ts.snap | 2 +- action/test/specs/ssh-target-dir-exists.spec.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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 index 1801905..5539ef1 100644 --- a/action/test/specs/__snapshots__/ssh-target-dir-exists.spec.ts.snap +++ b/action/test/specs/__snapshots__/ssh-target-dir-exists.spec.ts.snap @@ -5,6 +5,6 @@ exports[`Deploy to a branch on a custom dir that exists 1`] = ` tree:91548ed23fe65d56dfcbb58357a11c32c9b0b95d author:s0 msg:initial -tree:d019febe4c92496949f9d9eb3eff95d8435aff76 +tree:6ceb2d248e9b9c62291e9d1a5c4afeca8a49b2c8 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 index 6554fc2..469d7f4 100644 --- a/action/test/specs/ssh-target-dir-exists.spec.ts +++ b/action/test/specs/ssh-target-dir-exists.spec.ts @@ -21,8 +21,9 @@ it('Deploy to a branch on a custom dir that exists', async () => { 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')); + 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, From ac9c4058dbdaafdef7e2e55532c89c9ae7279efd Mon Sep 17 00:00:00 2001 From: Sam Lanning Date: Thu, 17 Nov 2022 23:00:13 +0000 Subject: [PATCH 3/4] fix: recursively delete files in TARGET_DIR --- README.md | 2 +- action/dist/index.js | 3 +-- action/src/index.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7217d49..b57fa3c 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ everything, and you will need to specify exactly what needs to be deleted.** 1. Default behaviour with a custom target directory: ``` - target_dir/* + target_dir/**/* !.git ``` diff --git a/action/dist/index.js b/action/dist/index.js index 10289fa..7d9ce6f 100644 --- a/action/dist/index.js +++ b/action/dist/index.js @@ -395,7 +395,6 @@ function copyFile(srcFile, destFile, force) { } //# sourceMappingURL=io.js.map - /***/ }), /***/ 2: @@ -12283,7 +12282,7 @@ const main = async ({ env = process.env, log, }) => { } 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']; + return [`${env.TARGET_DIR}/**/*`, '!.git']; } else { // Remove all files diff --git a/action/src/index.ts b/action/src/index.ts index db4f007..7cc6c27 100644 --- a/action/src/index.ts +++ b/action/src/index.ts @@ -555,7 +555,7 @@ export const main = async ({ log.log( `##[info] Removing all files from target dir ${env.TARGET_DIR} on target branch` ); - return [`${env.TARGET_DIR}/*`, '!.git']; + return [`${env.TARGET_DIR}/**/*`, '!.git']; } else { // Remove all files log.log(`##[info] Removing all files from target branch`); From 65d1af0def93abd0ac54cb2be340531841dac460 Mon Sep 17 00:00:00 2001 From: Sam Lanning Date: Thu, 17 Nov 2022 23:04:41 +0000 Subject: [PATCH 4/4] chore: run lint:fix and build --- action/dist/index.js | 6 +++++- action/src/index.ts | 6 +++++- action/test/specs/ssh-target-dir-exists.spec.ts | 9 ++++++--- action/test/specs/ssh-target-dir-no-exists.spec.ts | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/action/dist/index.js b/action/dist/index.js index 7d9ce6f..d453fdc 100644 --- a/action/dist/index.js +++ b/action/dist/index.js @@ -12306,7 +12306,11 @@ const main = async ({ env = process.env, log, }) => { 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}"/ ${destinationFolder}`, { 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 7cc6c27..de95f11 100644 --- a/action/src/index.ts +++ b/action/src/index.ts @@ -580,7 +580,11 @@ export const main = async ({ log.log(`##[info] Copying all files from ${folder}`); // TODO: replace this copy with a node implementation - await exec(`cp -rT "${folder}"/ ${destinationFolder}`, { 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/ssh-target-dir-exists.spec.ts b/action/test/specs/ssh-target-dir-exists.spec.ts index 469d7f4..2f820b4 100644 --- a/action/test/specs/ssh-target-dir-exists.spec.ts +++ b/action/test/specs/ssh-target-dir-exists.spec.ts @@ -7,7 +7,7 @@ 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 }); + const folders = await prepareTestFolders({ __filename }); // Create empty repo await util.wrappedExec('git init --bare', { cwd: folders.repoDir }); @@ -23,7 +23,10 @@ it('Deploy to a branch on a custom dir that exists', async () => { 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 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, @@ -52,7 +55,7 @@ it('Deploy to a branch on a custom dir that exists', async () => { FOLDER: folders.dataDir, SSH_PRIVATE_KEY: (await fs.readFile(util.SSH_PRIVATE_KEY)).toString(), KNOWN_HOSTS_FILE: util.KNOWN_HOSTS, - TARGET_DIR: 'custom' + TARGET_DIR: 'custom', }, 's0/test', {}, diff --git a/action/test/specs/ssh-target-dir-no-exists.spec.ts b/action/test/specs/ssh-target-dir-no-exists.spec.ts index d769a7f..39e009b 100644 --- a/action/test/specs/ssh-target-dir-no-exists.spec.ts +++ b/action/test/specs/ssh-target-dir-no-exists.spec.ts @@ -7,7 +7,7 @@ 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 }); + const folders = await prepareTestFolders({ __filename }); // Create empty repo await util.wrappedExec('git init --bare', { cwd: folders.repoDir }); @@ -49,7 +49,7 @@ it('Deploy to a branch on a custom dir that does not exist', async () => { FOLDER: folders.dataDir, SSH_PRIVATE_KEY: (await fs.readFile(util.SSH_PRIVATE_KEY)).toString(), KNOWN_HOSTS_FILE: util.KNOWN_HOSTS, - TARGET_DIR: 'custom' + TARGET_DIR: 'custom', }, 's0/test', {},