Skip to content

Commit 972f901

Browse files
authored
Add delete functionality (#2)
* Add delete functionality * Add checkout to e2e job * Add dist
1 parent d583fe9 commit 972f901

File tree

7 files changed

+82
-8
lines changed

7 files changed

+82
-8
lines changed

.github/workflows/ci.yaml

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
branches: [ master ]
88

99
jobs:
10-
test:
10+
test-unit:
1111

1212
runs-on: ubuntu-latest
1313

@@ -23,6 +23,11 @@ jobs:
2323
node-version: ${{ matrix.node-version }}
2424
- run: yarn
2525
- run: yarn test
26+
27+
test-e2e:
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v3
2631
- run: docker compose up -d
2732
env:
2833
PUBLIC_KEY: ${{secrets.PUBLIC_KEY}}
@@ -48,7 +53,7 @@ jobs:
4853
- run: ./tests/e2e/verify-basic.sh
4954
shell: bash
5055

51-
# Basic Test with Password Login
56+
# Basic Test with Password Login, and Delete
5257
- run: ./tests/e2e/setup.sh
5358
env:
5459
PRIVATE_KEY: ${{secrets.PRIVATE_KEY}}
@@ -64,5 +69,6 @@ jobs:
6469
./dist/ => ./dist/
6570
ignore: |
6671
*.txt
72+
delete: 'true'
6773
- run: ./tests/e2e/verify-basic.sh
6874
shell: bash

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ This action uploads files to a remote server using the SFTP protocol. Unlike oth
2929
| uploads | true | The folders to upload. In the format of `folder/ => upload_folder/`
3030
| ignore | false | A list of glob patterns for files that should be ignored. (Like the patterns you would find in .gitignore)
3131
| dry-run | false | If true, outputs the results of the upload, without actually uploading. |
32+
| delete | false | If true, any existing files in the remote upload directories are deleted. |
3233

3334
## Examples
3435

@@ -77,5 +78,22 @@ with:
7778
./src/ => ./www/src/
7879
```
7980
81+
### Upload multiples folders and delete existing files in those folders
82+
83+
```yaml
84+
uses: actions/checkout@v3
85+
uses: Dylan700/sftp-upload-action@latest
86+
with:
87+
server: sftp.server.com
88+
username: jason-bourne
89+
key: ${{secrets.key}}
90+
passphrase: ${{secrets.passphrase}}
91+
port: 22
92+
uploads: |
93+
./html/ => ./www/public_html/
94+
./src/ => ./www/src/
95+
delete: 'true'
96+
```
97+
8098
## Contributions
8199
Contributions are welcome! If you have something to add or fix, just make a pull request to be reviewed.

action.yml

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ inputs:
3434
ignore:
3535
description: a list of files to ignore using glob patterns
3636
required: false
37+
delete:
38+
description: If true, any existing files in the remote upload directories are deleted.
39+
required: false
40+
default: 'false'
3741
runs:
3842
using: 'node16'
3943
main: 'dist/index.js'

dist/index.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { debug, getBooleanInput, getInput, setFailed } from "@actions/core"
1+
import { debug, getBooleanInput, getInput, setFailed, warning } from "@actions/core"
22
import * as Client from "ssh2-sftp-client"
33
import Upload from "./types/Upload"
44
import minimatch, { MinimatchOptions } from "minimatch"
@@ -43,6 +43,17 @@ function is_uploadable(file: string, patterns: string[]){
4343
return !patterns.some(p => minimatch(file, p, minimatch_options))
4444
}
4545

46+
// recursively delete a remote folder
47+
async function delete_folder(sftp: Client, dir: string){
48+
debug(`Deleting existing files for ${dir}...`)
49+
try{
50+
await sftp.rmdir(dir, true)
51+
debug(`${dir} has been deleted.`)
52+
}catch(e: any){
53+
warning(`Unable to delete existing files for ${dir} before upload.`)
54+
}
55+
}
56+
4657
async function main(sftp: Client){
4758
try {
4859
const server: string = getInput("server")
@@ -54,7 +65,7 @@ async function main(sftp: Client){
5465
const isDryRun: boolean = getBooleanInput("dry-run")
5566
const uploads: Upload[] = parse_uploads(getInput("uploads"))
5667
const ignored: string[] = parse_ignored(getInput("ignore"))
57-
68+
const shouldDelete: boolean = getBooleanInput("delete")
5869
debug(`Connecting to ${server} as ${username} on port ${port}`)
5970

6071
await sftp.connect({
@@ -69,6 +80,7 @@ async function main(sftp: Client){
6980
debug("Preparing upload...")
7081
for(const upload of uploads) {
7182
debug(`Processing ${upload.from} to ${upload.to}`)
83+
shouldDelete ? await delete_folder(sftp, upload.to) : null
7284
await sftp.uploadDir(upload.from, upload.to, {
7385
filter: file => {
7486
if(is_uploadable(file, ignored)){
@@ -95,4 +107,4 @@ async function main(sftp: Client){
95107
}
96108
}
97109

98-
export { main, parse_uploads, parse_ignored, is_uploadable }
110+
export { main, parse_uploads, parse_ignored, is_uploadable, delete_folder }

tests/unit/delete_folder.test.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { delete_folder } from "../../src/main"
2+
import Client from "ssh2-sftp-client"
3+
import * as core from "@actions/core"
4+
5+
jest.mock("ssh2-sftp-client")
6+
jest.mock("@actions/core")
7+
8+
const sftp = new Client()
9+
10+
describe("delete_folder", () => {
11+
it("displays a warning when files cannot be deleted", async () => {
12+
sftp.rmdir.mockImplementationOnce(() => {throw new Error("Can't delete")})
13+
await delete_folder(sftp, "my_folder/here/")
14+
expect(core.warning).toBeCalled()
15+
})
16+
17+
it("attempts to remove the directory recursively", async () => {
18+
await delete_folder(sftp, "my_folder/here/")
19+
expect(sftp.rmdir).toBeCalledWith("my_folder/here/", true)
20+
})
21+
})

tests/unit/main.test.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const inputs: any = {
1616
"uploads": "here/ => there/",
1717
"ignore": "*.yaml",
1818
"passphrase": undefined,
19-
"key": undefined
19+
"key": undefined,
20+
"delete": false
2021
}
2122

2223
const testFiles: string[] = [
@@ -38,6 +39,7 @@ describe("main", () => {
3839

3940
beforeEach(() => {
4041
inputs["dry-run"] = false
42+
inputs["delete"] = false
4143
})
4244

4345
it("disconnects from sftp when finished", async () => {
@@ -83,8 +85,19 @@ describe("main", () => {
8385
})
8486

8587
it("calls setFailed if sftp can't connect", async () => {
86-
sftp.connect.mockImplementation(() => {throw new Error()})
88+
sftp.connect.mockImplementationOnce(() => {throw new Error()})
8789
await main(sftp)
8890
expect(core.setFailed).toBeCalled()
8991
})
92+
93+
it("uploads folders once", async () => {
94+
await main(sftp)
95+
expect(sftp.uploadDir).toBeCalledTimes(1)
96+
})
97+
98+
it("attempts to delete existing files when delete is true", async () => {
99+
inputs["delete"] = true
100+
await main(sftp)
101+
expect(sftp.rmdir).toBeCalledTimes(1)
102+
})
90103
})

0 commit comments

Comments
 (0)