├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── ftp.yml │ └── ftps.yml ├── .gitignore ├── LICENSE ├── README.md ├── action.yml ├── dist └── index.js ├── images ├── ftp-deploy-logo-full.png └── ftp-deploy-logo-small.png ├── migration.md ├── package-lock.json ├── package.json ├── project.code-workspace ├── src ├── main.test.ts ├── main.ts └── parse.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "jest", 4 | "@typescript-eslint" 5 | ], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "ecmaVersion": 8, 9 | "sourceType": "module", 10 | "project": "./tsconfig.json" 11 | }, 12 | "rules": { 13 | "eslint-comments/no-use": "off", 14 | "import/no-namespace": "off", 15 | "no-unused-vars": "off", 16 | "@typescript-eslint/no-unused-vars": "error", 17 | "@typescript-eslint/explicit-member-accessibility": [ 18 | "error", 19 | { 20 | "accessibility": "no-public" 21 | } 22 | ], 23 | "@typescript-eslint/no-require-imports": "error", 24 | "@typescript-eslint/array-type": "error", 25 | "@typescript-eslint/await-thenable": "error", 26 | "@typescript-eslint/func-call-spacing": [ 27 | "error", 28 | "never" 29 | ], 30 | "@typescript-eslint/no-array-constructor": "error", 31 | "@typescript-eslint/no-empty-interface": "error", 32 | "@typescript-eslint/no-extraneous-class": "error", 33 | "@typescript-eslint/no-for-in-array": "error", 34 | "@typescript-eslint/no-inferrable-types": "error", 35 | "@typescript-eslint/no-misused-new": "error", 36 | "@typescript-eslint/no-namespace": "error", 37 | "@typescript-eslint/no-non-null-assertion": "warn", 38 | "@typescript-eslint/no-unnecessary-qualifier": "error", 39 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 40 | "@typescript-eslint/no-useless-constructor": "error", 41 | "@typescript-eslint/no-var-requires": "error", 42 | "@typescript-eslint/prefer-for-of": "warn", 43 | "@typescript-eslint/prefer-function-type": "warn", 44 | "@typescript-eslint/prefer-includes": "error", 45 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 46 | "@typescript-eslint/promise-function-async": "error", 47 | "@typescript-eslint/require-array-sort-compare": "error", 48 | "@typescript-eslint/restrict-plus-operands": "error", 49 | "@typescript-eslint/type-annotation-spacing": "error" 50 | }, 51 | "env": { 52 | "node": true, 53 | "es6": true, 54 | "jest/globals": true 55 | } 56 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Bug Description** 11 | A clear and concise description of what the bug is. 12 | 13 | **My Action Config** 14 | ```yaml 15 | on: push 16 | name: Publish Website 17 | jobs: 18 | web-deploy: 19 | name: 🚀 Deploy website every commit 20 | # !!!!!!! TODO Fill Out !!!!!!! 21 | ``` 22 | 23 | **My Action Log** 24 | ``` 25 | # Paste Log here 26 | # you may want enable verbose logging with log-level: verbose 27 | ``` 28 | -------------------------------------------------------------------------------- /.github/workflows/ftp.yml: -------------------------------------------------------------------------------- 1 | name: FTP Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | deploy: 9 | name: 🚀 Deploy website every commit 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 🚚 Get latest code 13 | uses: actions/checkout@v4 14 | 15 | - name: 📂 Sync files 16 | uses: ./ 17 | with: 18 | server: ftp.samkirkland.com 19 | username: test@samkirkland.com 20 | password: ${{ secrets.ftp_password }} 21 | -------------------------------------------------------------------------------- /.github/workflows/ftps.yml: -------------------------------------------------------------------------------- 1 | name: FTPS Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | deploy: 9 | name: 🚀 Deploy website every commit 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 🚚 Get latest code 13 | uses: actions/checkout@v4 14 | 15 | - name: 📂 Sync files 16 | uses: ./ 17 | with: 18 | server: ftp.samkirkland.com 19 | username: test@samkirkland.com 20 | password: ${{ secrets.ftp_password }} 21 | protocol: ftps 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # OS metadata 94 | .DS_Store 95 | Thumbs.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 GitHub, Inc. and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | FTP Deploy Action - Continuous integration for everyone 3 |

4 | 5 | Automate deploying websites and more with this GitHub action. **It's free!** 6 | 7 | ![FTP test](https://github.com/SamKirkland/FTP-Deploy-Action/workflows/FTP%20Test/badge.svg) 8 | ![FTPS test](https://github.com/SamKirkland/FTP-Deploy-Action/workflows/FTPS%20Test/badge.svg) 9 | 10 | --- 11 | 12 | ### Usage Example 13 | Place the following in `/.github/workflows/main.yml` 14 | ```yml 15 | on: push 16 | name: 🚀 Deploy website on push 17 | jobs: 18 | web-deploy: 19 | name: 🎉 Deploy 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: 🚚 Get latest code 23 | uses: actions/checkout@v4 24 | 25 | - name: 📂 Sync files 26 | uses: SamKirkland/FTP-Deploy-Action@v4.3.5 27 | with: 28 | server: ftp.samkirkland.com 29 | username: myFtpUserName 30 | password: ${{ secrets.ftp_password }} 31 | ``` 32 | 33 | --- 34 | 35 | ### Requirements 36 | - You must have ftp access to your server. If your host allows or requires ssh please use my [web-deploy](https://github.com/SamKirkland/web-deploy) action 37 | - Some web hosts change the default port (21), check with your host for your port number 38 | 39 | --- 40 | 41 | ### Setup Steps 42 | 1. Select the repository you want to add the action to 43 | 2. Select the `Actions` tab 44 | 3. Select `Blank workflow file` or `Set up a workflow yourself`, if you don't see these options manually create a yaml file `Your_Project/.github/workflows/main.yml` 45 | 4. Paste the example above into your yaml file and save 46 | 5. Now you need to add a key to the `secrets` section in your project. To add a `secret` go to the `Settings` tab in your project then select `Secrets`. Add a new `Secret` for `password` 47 | 6. Update your yaml file settings 48 | 7. If you appreciate this github action give it a :star: or show off with one of the [badges below](#badge). 49 | 50 | --- 51 | 52 | ### Settings 53 | Keys can be added directly to your .yml config file or referenced from your project `Secrets` storage. 54 | 55 | To add a `secret` go to the `Settings` tab in your project then select `Secrets`. 56 | I strongly recommend you store your `password` as a secret. 57 | 58 | | Key Name | Required | Example | Default Value | Description | 59 | |-------------------------|----------|-------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 60 | | `server` | Yes | `ftp.samkirkland.com` | | Deployment destination server | 61 | | `username` | Yes | `username@samkirkland.com` | | FTP user name | 62 | | `password` | Yes | `CrazyUniquePassword&%123` | | FTP password, be sure to escape quotes and spaces | 63 | | `port` | No | `990` | `21` | Server port to connect to (read your web hosts docs) | 64 | | `protocol` | No | `ftps` | `ftp` | `ftp`: provides no encryption, `ftps`: full encryption newest standard (aka "explicit" ftps), `ftps-legacy`: full encryption legacy standard (aka "implicit" ftps) | 65 | | `local-dir` | No | `./myFolderToPublish/` | `./` | Folder to upload from, must end with trailing slash `/` | 66 | | `server-dir` | No | `public_html/www/` | `./` | Folder to upload to (on the server), must end with trailing slash `/` | 67 | | `state-name` | No | `folder/.sync-state.json` | `.ftp-deploy-sync-state.json` | Path and name of the state file - this file is used to track which files have been deployed | 68 | | `dry-run` | No | `true` | `false` | Prints which modifications will be made with current config options, but doesn't actually make any changes | 69 | | `dangerous-clean-slate` | No | `true` | `false` | Deletes ALL contents of server-dir, even items in excluded with 'exclude' argument | 70 | | `exclude` | No | [See Example](#exclude-files) | [See Example](#exclude-files) | An array of glob patterns, these files will not be included in the publish/delete process. [List MUST be in this format](#exclude-files). You can use [a glob tester](https://www.digitalocean.com/community/tools/glob?comments=true&glob=%2A%2A%2F.git%2A%2F%2A%2A&matches=false&tests=test%2Fsam&tests=.git%2F%0D&tests=.github%2F%0D&tests=.git%2Ftest%0D&tests=.gitattributes%0D&tests=.gitignore%0D&tests=.git%2Fconfig%0D&tests=.git%2Ftest%2Ftest&tests=.github%2Fworkflows%2Fmain.yml&tests=node_modules%2Ffolder%2F%0D&tests=node_modules%2Fotherfolder%2F%0D&tests=subfolder%2Fnode_modules%2F) to test your pattern(s). | 71 | | `log-level` | No | `minimal` | `standard` | `minimal`: only important info, `standard`: important info and basic file changes, `verbose`: print everything the script is doing | 72 | | `security` | No | `strict` | `loose` | `strict`: Reject any connection which is not authorized with the list of supplied CAs. `loose`: Allow connection even when the domain is not certificate | 73 | | `timeout` | No | `60000` | `30000` | Timeout in milliseconds for FTP operations | 74 | 75 | 76 | # Common Examples 77 | #### Build and Publish React/Angular/Vue Website 78 | Make sure you have an npm script named 'build'. This config should work for most node built websites. 79 | 80 | ```yml 81 | on: push 82 | name: 🚀 Deploy website on push 83 | jobs: 84 | web-deploy: 85 | name: 🎉 Deploy 86 | runs-on: ubuntu-latest 87 | steps: 88 | - name: 🚚 Get latest code 89 | uses: actions/checkout@v4 90 | 91 | - name: Use Node.js 16 92 | uses: actions/setup-node@v2 93 | with: 94 | node-version: '16' 95 | 96 | - name: 🔨 Build Project 97 | run: | 98 | npm install 99 | npm run build 100 | 101 | - name: 📂 Sync files 102 | uses: SamKirkland/FTP-Deploy-Action@v4.3.5 103 | with: 104 | server: ftp.samkirkland.com 105 | username: myFtpUserName 106 | password: ${{ secrets.password }} 107 | ``` 108 | 109 | #### FTPS 110 | ```yml 111 | on: push 112 | name: 🚀 Deploy website on push 113 | jobs: 114 | web-deploy: 115 | name: 🎉 Deploy 116 | runs-on: ubuntu-latest 117 | steps: 118 | - name: 🚚 Get latest code 119 | uses: actions/checkout@v4 120 | 121 | - name: 📂 Sync files 122 | uses: SamKirkland/FTP-Deploy-Action@v4.3.5 123 | with: 124 | server: ftp.samkirkland.com 125 | username: myFtpUserName 126 | password: ${{ secrets.password }} 127 | protocol: ftps 128 | port: 1234 # todo replace with your web hosts ftps port 129 | ``` 130 | 131 | #### Log only dry run: Use this option for testing 132 | Ouputs a list of files that will be created/modified to sync your source without making any actual changes 133 | ```yml 134 | on: push 135 | name: 🚀 Deploy website on push 136 | jobs: 137 | web-deploy: 138 | name: 🎉 Deploy 139 | runs-on: ubuntu-latest 140 | steps: 141 | - name: 🚚 Get latest code 142 | uses: actions/checkout@v4 143 | 144 | - name: 📂 Sync files 145 | uses: SamKirkland/FTP-Deploy-Action@v4.3.5 146 | with: 147 | server: ftp.samkirkland.com 148 | username: myFtpUserName 149 | password: ${{ secrets.password }} 150 | dry-run: true 151 | ``` 152 | 153 | #### Exclude files 154 | Excludes files 155 | ```yml 156 | on: push 157 | name: 🚀 Deploy website on push 158 | jobs: 159 | web-deploy: 160 | name: 🎉 Deploy 161 | runs-on: ubuntu-latest 162 | steps: 163 | - name: 🚚 Get latest code 164 | uses: actions/checkout@v4 165 | 166 | - name: 📂 Sync files 167 | uses: SamKirkland/FTP-Deploy-Action@v4.3.5 168 | with: 169 | server: ftp.samkirkland.com 170 | username: myFtpUserName 171 | password: ${{ secrets.password }} 172 | exclude: | 173 | **/.git* 174 | **/.git*/** 175 | **/node_modules/** 176 | fileToExclude.txt 177 | ``` 178 | 179 | `exclude` has the following default value 180 | ```yml 181 | exclude: | 182 | **/.git* 183 | **/.git*/** 184 | **/node_modules/** 185 | ``` 186 | if you overwrite the default value you will probably want to respecify them 187 | 188 | 189 | --- 190 | 191 | _Want another example? Let me know by creating a [github issue](https://github.com/SamKirkland/FTP-Deploy-Action/issues/new)_ 192 | 193 | --- 194 | 195 | ## Badge 196 | 197 | If you appreciate this github action give it a :star: or show off with one of the badges below. Feel free to edit the text or color. 198 | 199 | [Deployed with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 200 | 201 | ```md 202 | [Deployed with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 203 | ``` 204 | 205 | [Deployed with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 206 | 207 | ```md 208 | [Deployed with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 209 | ``` 210 | 211 | [Deployed with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 212 | 213 | ```md 214 | [Deployed with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 215 | ``` 216 | 217 | --- 218 | 219 | [Website Deployed for Free with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 220 | 221 | ```md 222 | [Website Deployed for Free with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 223 | ``` 224 | 225 | [Website Deployed for Free with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 226 | 227 | ```md 228 | [Website Deployed for Free with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 229 | ``` 230 | 231 | [Website Deployed for Free with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 232 | 233 | ```md 234 | [Website Deployed for Free with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) 235 | ``` 236 | 237 | --- 238 | 239 | ## FAQ 240 |
241 | How to exclude .git files from the publish 242 | 243 | Git files are excluded by default! If you customize the `exclude` option make sure you re-add the default options. 244 |
245 | 246 |
247 | How to exclude a specific file or folder 248 | 249 | You can use the `exclude` option to ignore specific files/folders from the publish. Keep in mind you will need to re-add the default exclude options if you want to keep them. For example the below option excludes all `.txt` files. 250 | 251 | ```yml 252 | exclude: 253 | - *.txt 254 | ``` 255 | 256 |
257 | 258 | 259 |
260 | How do I set a upload timeout? 261 | 262 | github has a built-in `timeout-minutes` option, see customized example below 263 | 264 | ```yaml 265 | on: push 266 | name: Publish Website 267 | jobs: 268 | web-deploy: 269 | name: web-deploy 270 | runs-on: ubuntu-latest 271 | timeout-minutes: 15 # time out after 15 minutes (default is 360 minutes) 272 | steps: 273 | .... 274 | ``` 275 |
276 | 277 | ## Debugging your config locally 278 | This action is a basic wrapper around my `@samkirkland/ftp-deploy` npm package. To test your config you can install [@samkirkland/ftp-deploy](https://github.com/SamKirkland/ftp-deploy) and then convert your config to a yml action. Settings are one-to-one, this action is only a wrapper. 279 | 280 | ## Contributing to this project 281 | To test this action locally you will need to setup **docker** and **act** to run a environment similar to the one github uses for actions. 282 | - Download/install docker for windows, make sure it is running 283 | - `choco install act-cli` install [act](https://github.com/nektos/act) 284 | - Install the npm package using `npm install --dev-only @samkirkland/ftp-deploy` 285 | - Update the `deploy` script in `package.json` with a actual server/username/password 286 | - You can run the script using the following command `npm run deploy` (run this in the folder that has the `package.json` file) 287 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "FTP Deploy" 2 | description: "Automate deploying websites and more with this GitHub action via FTP and FTPS" 3 | author: "Sam Kirkland" 4 | inputs: 5 | server: 6 | required: true 7 | description: "ftp server" 8 | username: 9 | required: true 10 | description: "ftp username" 11 | password: 12 | required: true 13 | description: "ftp password" 14 | port: 15 | required: false 16 | description: "Server port to connect to (read your web hosts docs)" 17 | protocol: 18 | required: false 19 | description: "protocol to deploy with - ftp, ftps, or ftps-legacy" 20 | local-dir: 21 | required: false 22 | description: "Folder to upload from, must end with trailing slash /" 23 | server-dir: 24 | required: false 25 | description: "Path to upload to on the server. Must end with trailing slash /" 26 | state-name: 27 | required: false 28 | description: "Path and name of the state file - this file is used to track which files have been deployed" 29 | dry-run: 30 | required: false 31 | description: "Prints which modifications will be made with current config options, but doesnt actually make any changes" 32 | dangerous-clean-slate: 33 | required: false 34 | description: "Deletes ALL contents of server-dir, even items in excluded with exclude argument" 35 | exclude: 36 | required: false 37 | description: "An array of glob patterns, these files will not be included in the publish/delete process" 38 | log-level: 39 | required: false 40 | description: "How verbose should the information be - minimal, standard, or verbose" 41 | security: 42 | required: false 43 | description: "strict or loose" 44 | timeout: 45 | required: false 46 | description: "Timeout in milliseconds for FTP operations" 47 | runs: 48 | using: "node20" 49 | main: "dist/index.js" 50 | branding: 51 | icon: "upload-cloud" 52 | color: "blue" 53 | -------------------------------------------------------------------------------- /images/ftp-deploy-logo-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamKirkland/FTP-Deploy-Action/8e83cea8672e3fbcbb9fdafff34debf6ae4c5f65/images/ftp-deploy-logo-full.png -------------------------------------------------------------------------------- /images/ftp-deploy-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamKirkland/FTP-Deploy-Action/8e83cea8672e3fbcbb9fdafff34debf6ae4c5f65/images/ftp-deploy-logo-small.png -------------------------------------------------------------------------------- /migration.md: -------------------------------------------------------------------------------- 1 | # How to migrate between versions 2 | 3 | ## Migrating from v4.1.0 to v4.2.0 4 | 5 | `v4.2.0` parses the `exclude` option in a more standard way. Going forward the `exclude` option **must** be in the following format: 6 | 7 | ```yml 8 | exclude: | 9 | **/.git* 10 | **/.git*/** 11 | **/node_modules/** 12 | fileToExclude.txt 13 | ``` 14 | 15 | ## Migrating from v3 to v4 16 | 17 | Migrating from v3 to v4 should be fairly straightforward. Version 4 was designed with speed and ease of initial setup in mind. Going forward version 4 will be the only supported version. 18 | 19 | ### Those who can't upgrade 20 | 21 | Most features have been carried forward and improved upon. However, some features did not make the cut: 22 | - **`sftp` is no longer supported**. If you have `sftp` access you are using `ssh`, that means you have access to a much more modern and capable protocol. I plan on releasing a separate github action that will deploy over `sftp`/`ssh` using `rsync`. Until then you can continue using version 3. 23 | - The `include` argument has been removed. I didn't see much need for it in the initial release. If you need this feature please create a support ticket. 24 | 25 | ### How to upgrade 26 | 27 | 1. Remove `with: fetch-depth: 2`. It is no longer needed and removing it will _slightly_ speed up deployments. 28 | 2. Change the version to `v4.X.X`, for example `SamKirkland/FTP-Deploy-Action@v4.3.5` (please check the [README](https://github.com/SamKirkland/FTP-Deploy-Action/blob/master/README.md) or the [releases page](https://github.com/SamKirkland/FTP-Deploy-Action/releases/latest) for the latest version). 29 | 3. If you have a `.git-ftp-include` file you should delete it. Version 4 tracks files differently and no longer needs this config file. 30 | 4. If you have a `.git-ftp-ignore` file, you should transfer the options to the new `exclude` argument. **Note:** version 4 excludes any `.git*` and `node_modules/` files / folders by default. 31 | 5. Update your arguments to reflect the following changes: 32 | - `ftp-server` was split into 4 arguments: 33 | - `server` 34 | - `port` 35 | - `protocol` 36 | - `server-dir` 37 | - `ftp-username` was renamed to `username`. 38 | - `ftp-password` was renamed to `password`. 39 | - `local-dir` and `server-dir` now **must** end with `/`. 40 | - `git-ftp-args` and `known-hosts` arguments were removed. 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ftp-deploy-action", 3 | "version": "4.3.5", 4 | "private": true, 5 | "description": "Automate deploying websites and more with this GitHub action", 6 | "main": "dist/index.js", 7 | "scripts": { 8 | "build": "ncc build src/main.ts --no-cache", 9 | "test": "jest", 10 | "lint": "eslint src/**/*.ts" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/SamKirkland/FTP-Deploy-Action.git" 15 | }, 16 | "keywords": [ 17 | "ftp", 18 | "website deploy", 19 | "continuous integration", 20 | "ftps" 21 | ], 22 | "author": "Sam Kirkland", 23 | "license": "MIT", 24 | "dependencies": { 25 | "@actions/core": "^1.9.1", 26 | "@samkirkland/ftp-deploy": "^1.2.4", 27 | "@types/jest": "^29.4.1", 28 | "jest": "^29.5.0", 29 | "ts-jest": "^29.0.5", 30 | "ts-node-dev": "^2.0.0" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^20.11.24", 34 | "@typescript-eslint/eslint-plugin": "^5.33.1", 35 | "@typescript-eslint/parser": "^5.33.1", 36 | "@vercel/ncc": "^0.34.0", 37 | "eslint": "^8.22.0", 38 | "eslint-plugin-jest": "^26.8.7", 39 | "typescript": "^4.7.4" 40 | }, 41 | "jest": { 42 | "preset": "ts-jest" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /project.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.exclude": { 9 | "**/node_modules": true 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/main.test.ts: -------------------------------------------------------------------------------- 1 | import { optionalBoolean, optionalInt, optionalLogLevel, optionalProtocol, optionalSecurity, optionalString } from "./parse"; 2 | 3 | describe("boolean", () => { 4 | test("false", () => { 5 | expect(optionalBoolean("test", "false")).toBe(false); 6 | }); 7 | 8 | test("FALSE", () => { 9 | expect(optionalBoolean("test", "FALSE")).toBe(false); 10 | }); 11 | 12 | test("true", () => { 13 | expect(optionalBoolean("test", "true")).toBe(true); 14 | }); 15 | 16 | test("TRUE", () => { 17 | expect(optionalBoolean("test", "TRUE")).toBe(true); 18 | }); 19 | 20 | test("optional", () => { 21 | expect(optionalBoolean("test", "")).toBe(undefined); 22 | }); 23 | }); 24 | 25 | describe("string", () => { 26 | test("empty", () => { 27 | expect(optionalString("")).toBe(undefined); 28 | }); 29 | 30 | test("populated", () => { 31 | expect(optionalString("test")).toBe("test"); 32 | }); 33 | }); 34 | 35 | describe("int", () => { 36 | test("empty", () => { 37 | expect(optionalInt("test", "")).toBe(undefined); 38 | }); 39 | 40 | test("0", () => { 41 | expect(optionalInt("test", "0")).toBe(0); 42 | }); 43 | 44 | test("1", () => { 45 | expect(optionalInt("test", "1")).toBe(1); 46 | }); 47 | 48 | test("500", () => { 49 | expect(optionalInt("test", "500")).toBe(500); 50 | }); 51 | 52 | test("non-int", () => { 53 | expect(() => optionalInt("test", "12.345")).toThrow(); 54 | }); 55 | }); 56 | 57 | describe("protocol", () => { 58 | test("empty", () => { 59 | expect(optionalProtocol("test", "")).toBe(undefined); 60 | }); 61 | 62 | test("ftp", () => { 63 | expect(optionalProtocol("test", "ftp")).toBe("ftp"); 64 | }); 65 | 66 | test("ftps", () => { 67 | expect(optionalProtocol("test", "ftps")).toBe("ftps"); 68 | }); 69 | 70 | test("ftps-legacy", () => { 71 | expect(optionalProtocol("test", "ftps-legacy")).toBe("ftps-legacy"); 72 | }); 73 | }); 74 | 75 | describe("log level", () => { 76 | test("empty", () => { 77 | expect(optionalLogLevel("test", "")).toBe(undefined); 78 | }); 79 | 80 | test("minimal", () => { 81 | expect(optionalLogLevel("test", "minimal")).toBe("minimal"); 82 | }); 83 | 84 | test("standard", () => { 85 | expect(optionalLogLevel("test", "standard")).toBe("standard"); 86 | }); 87 | 88 | test("verbose", () => { 89 | expect(optionalLogLevel("test", "verbose")).toBe("verbose"); 90 | }); 91 | }); 92 | 93 | describe("security", () => { 94 | test("empty", () => { 95 | expect(optionalSecurity("test", "")).toBe(undefined); 96 | }); 97 | 98 | test("loose", () => { 99 | expect(optionalSecurity("test", "loose")).toBe("loose"); 100 | }); 101 | 102 | test("strict", () => { 103 | expect(optionalSecurity("test", "strict")).toBe("strict"); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from "@actions/core"; 2 | import { deploy } from "@samkirkland/ftp-deploy"; 3 | import { IFtpDeployArguments } from "@samkirkland/ftp-deploy/dist/types"; 4 | import { optionalInt, optionalProtocol, optionalString, optionalBoolean, optionalStringArray, optionalLogLevel, optionalSecurity } from "./parse"; 5 | 6 | async function runDeployment() { 7 | try { 8 | const args: IFtpDeployArguments = { 9 | server: core.getInput("server", { required: true }), 10 | username: core.getInput("username", { required: true }), 11 | password: core.getInput("password", { required: true }), 12 | port: optionalInt("port", core.getInput("port")), 13 | protocol: optionalProtocol("protocol", core.getInput("protocol")), 14 | "local-dir": optionalString(core.getInput("local-dir")), 15 | "server-dir": optionalString(core.getInput("server-dir")), 16 | "state-name": optionalString(core.getInput("state-name")), 17 | "dry-run": optionalBoolean("dry-run", core.getInput("dry-run")), 18 | "dangerous-clean-slate": optionalBoolean("dangerous-clean-slate", core.getInput("dangerous-clean-slate")), 19 | "exclude": optionalStringArray("exclude", core.getMultilineInput("exclude")), 20 | "log-level": optionalLogLevel("log-level", core.getInput("log-level")), 21 | "security": optionalSecurity("security", core.getInput("security")), 22 | "timeout": optionalInt("timeout", core.getInput("timeout")) 23 | }; 24 | 25 | await deploy(args); 26 | } 27 | catch (error: any) { 28 | core.setFailed(error); 29 | } 30 | } 31 | 32 | runDeployment(); 33 | -------------------------------------------------------------------------------- /src/parse.ts: -------------------------------------------------------------------------------- 1 | export function optionalString(rawValue: string): string | undefined { 2 | if (rawValue.length === 0) { 3 | return undefined; 4 | } 5 | 6 | return rawValue; 7 | } 8 | 9 | export function optionalBoolean(argumentName: string, rawValue: string): boolean | undefined { 10 | if (rawValue.length === 0) { 11 | return undefined; 12 | } 13 | 14 | const cleanValue = rawValue.toLowerCase(); 15 | if (cleanValue === "true") { 16 | return true; 17 | } 18 | if (cleanValue === "false") { 19 | return false; 20 | } 21 | 22 | throw new Error(`${argumentName}: invalid parameter - please use a boolean, you provided "${rawValue}". Try true or false instead.`); 23 | } 24 | 25 | export function optionalProtocol(argumentName: string, rawValue: string): "ftp" | "ftps" | "ftps-legacy" | undefined { 26 | if (rawValue.length === 0) { 27 | return undefined; 28 | } 29 | 30 | const cleanValue = rawValue.toLowerCase(); 31 | if (cleanValue === "ftp") { 32 | return "ftp"; 33 | } 34 | if (cleanValue === "ftps") { 35 | return "ftps"; 36 | } 37 | if (cleanValue === "ftps-legacy") { 38 | return "ftps-legacy"; 39 | } 40 | 41 | throw new Error(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "ftp", "ftps", or "ftps-legacy" instead.`); 42 | } 43 | 44 | export function optionalLogLevel(argumentName: string, rawValue: string): "minimal" | "standard" | "verbose" | undefined { 45 | if (rawValue.length === 0) { 46 | return undefined; 47 | } 48 | 49 | const cleanValue = rawValue.toLowerCase(); 50 | if (cleanValue === "minimal") { 51 | return "minimal"; 52 | } 53 | if (cleanValue === "standard") { 54 | return "standard"; 55 | } 56 | if (cleanValue === "verbose") { 57 | return "verbose"; 58 | } 59 | 60 | throw new Error(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "minimal", "standard", or "verbose" instead.`); 61 | } 62 | 63 | export function optionalSecurity(argumentName: string, rawValue: string): "loose" | "strict" | undefined { 64 | if (rawValue.length === 0) { 65 | return undefined; 66 | } 67 | 68 | const cleanValue = rawValue.toLowerCase(); 69 | if (cleanValue === "loose") { 70 | return "loose"; 71 | } 72 | if (cleanValue === "strict") { 73 | return "strict"; 74 | } 75 | 76 | throw new Error(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "loose" or "strict" instead.`); 77 | } 78 | 79 | export function optionalInt(argumentName: string, rawValue: string): number | undefined { 80 | if (rawValue.length === 0) { 81 | return undefined; 82 | } 83 | 84 | const valueAsNumber = parseFloat(rawValue); 85 | 86 | if (Number.isInteger(valueAsNumber)) { 87 | return valueAsNumber; 88 | } 89 | 90 | throw new Error(`${argumentName}: invalid parameter - you provided "${rawValue}". Try a whole number (no decimals) instead like 1234`); 91 | } 92 | 93 | export function optionalStringArray(argumentName: string, rawValue: string[]): string[] | undefined { 94 | if (rawValue.length === 0) { 95 | return undefined; 96 | } 97 | 98 | if (typeof rawValue === "string") { 99 | throw new Error(`${argumentName}: invalid parameter - you provided "${rawValue}". This option expects an list in the EXACT format described in the readme`); 100 | } 101 | 102 | return rawValue; 103 | } 104 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "Node", 4 | "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "outDir": "./dist", /* Redirect output structure to the directory. */ 7 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 8 | "strict": true, /* Enable all strict type-checking options. */ 9 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 10 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 11 | "noEmit": true 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | "**/.test.ts" 16 | ] 17 | } --------------------------------------------------------------------------------