mirror of
https://github.com/SamKirkland/FTP-Deploy-Action.git
synced 2026-04-10 12:32:17 +02:00
Compare commits
26 Commits
v3.0.0-pat
...
v3.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a8883459c | ||
|
|
bb9b486951 | ||
|
|
559ddbb4df | ||
|
|
da0d77ff39 | ||
|
|
1af692f7d5 | ||
|
|
ae5262e007 | ||
|
|
b698c49eac | ||
|
|
ccf756b42e | ||
|
|
f68449776c | ||
|
|
a54cf4c5e3 | ||
|
|
c42c8e46fb | ||
|
|
c926b9df00 | ||
|
|
3f7edaa478 | ||
|
|
e39df43686 | ||
|
|
586c7242ef | ||
|
|
97865be6a0 | ||
|
|
b890f82a46 | ||
|
|
c9f0bcd878 | ||
|
|
481f9001ff | ||
|
|
4938a6057e | ||
|
|
af948b8060 | ||
|
|
db3b78d8e7 | ||
|
|
034d210969 | ||
|
|
2863e02eda | ||
|
|
9315d47124 | ||
|
|
98039f1fbe |
4
.github/workflows/test-ftp-deploy.yaml
vendored
4
.github/workflows/test-ftp-deploy.yaml
vendored
@@ -5,11 +5,11 @@ jobs:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: FTP-Deploy-Action
|
||||
uses: SamKirkland/FTP-Deploy-Action-Typescript@master
|
||||
uses: SamKirkland/FTP-Deploy-Action@master
|
||||
with:
|
||||
ftp-server: ftp://ftp.samkirkland.com/
|
||||
ftp-username: ${{ secrets.ftp_username }}
|
||||
|
||||
4
.github/workflows/test-sftp-deploy.yaml
vendored
4
.github/workflows/test-sftp-deploy.yaml
vendored
@@ -5,11 +5,11 @@ jobs:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: FTP-Deploy-Action
|
||||
uses: SamKirkland/FTP-Deploy-Action-Typescript@master
|
||||
uses: SamKirkland/FTP-Deploy-Action@master
|
||||
with:
|
||||
# deploy to a folder named "sftp-deploy-test.samkirkland.com" on my server
|
||||
ftp-server: sftp://ftp.samkirkland.com:7822/home/samkirkland/sftp-deploy-test.samkirkland.com/
|
||||
|
||||
201
README.md
201
README.md
@@ -4,11 +4,13 @@
|
||||
|
||||
Automate deploying websites and more with this GitHub action
|
||||
|
||||

|
||||
|
||||
 
|
||||
|
||||
### Usage Example (Your_Project/.github/workflows/main.yml)
|
||||
---
|
||||
|
||||
### Usage Example
|
||||
|
||||
Place the following in `Your_Project/.github/workflows/main.yml`
|
||||
```yml
|
||||
on: push
|
||||
name: Publish Website
|
||||
@@ -17,18 +19,21 @@ jobs:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: FTP-Deploy-Action
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.0.0
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.1.1
|
||||
with:
|
||||
ftp-server: ftp://ftp.samkirkland.com/
|
||||
ftp-username: myFtpUserName
|
||||
ftp-password: ${{ secrets.FTP_PASSWORD }}
|
||||
```
|
||||
|
||||
#### Setup Steps
|
||||
---
|
||||
|
||||
### Setup Steps
|
||||
|
||||
1. Select the repository you want to add the action to
|
||||
2. Select the `Actions` tab
|
||||
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`
|
||||
@@ -36,8 +41,14 @@ jobs:
|
||||
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 `ftp-password`
|
||||
6. Update your yaml file settings
|
||||
|
||||
__Note: Only tracked files will be published by default. If you want to publish files that don't exist in github (example: files generated during the action run) you must add those files/folders to `.git-ftp-include`__
|
||||
|
||||
Migrating from v2? See the [migration guide](v2-v3-migration.md)
|
||||
|
||||
---
|
||||
|
||||
### Settings
|
||||
**Migrating from v2? See the [migration guide](v2-v3-migration.md)**
|
||||
|
||||
|
||||
Keys can be added directly to your .yml config file or referenced from your project `Secrets` storage.
|
||||
|
||||
@@ -51,6 +62,7 @@ I recommend you store your `ftp-password` as a secret.
|
||||
| `ftp-password` | Yes | CrazyUniquePassword&%123 | | FTP account password |
|
||||
| `local-dir` | No | deploy/ | ./ | Which local folder to deploy, path should be relative to the root and should include trailing slash. `./` is the root of the project |
|
||||
| `git-ftp-args` | No | See `git-ftp-args` section below | | Custom git-ftp arguments, this field is passed through directly into the git-ftp script |
|
||||
| `known-hosts` | No | hostname ssh-rsa AAAAB3NzaC1y ... | | The desired contents of your .ssh/known_hosts file. See [known hosts setup](#known-hosts-setup) |
|
||||
|
||||
#### Advanced options using `git-ftp-args`
|
||||
Custom arguments, this field is passed through directly into the git-ftp script. See [git-ftp's manual](https://github.com/git-ftp/git-ftp/blob/master/man/git-ftp.1.md) for all options.
|
||||
@@ -62,7 +74,7 @@ Below is an incomplete list of commonly used args:
|
||||
|------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | Does not upload or delete anything, but tries to get the .git-ftp.log file from remote host |
|
||||
| `--silent` | Be silent |
|
||||
| `--all` | Transfer all files, even seemingly the same as the target site (default is differences only) |
|
||||
| `--all` | Transfer all files, even seemingly the same as the target site (default is differences only). Note: Only files committed to github are uploaded, if you'd like to upload files generated during the action run see `.git-ftp-include` |
|
||||
| `--lock` | Locks remote files from being modified while a deployment is running |
|
||||
| `--remote-root` | Specifies the remote root directory to deploy to. The remote path in the URL is ignored |
|
||||
| `--key` | SSH private key file name for SFTP |
|
||||
@@ -71,7 +83,6 @@ Below is an incomplete list of commonly used args:
|
||||
| `--insecure` | Don't verify server's certificate |
|
||||
| `--cacert <file>` | Use as CA certificate store. Useful when a server has a self-signed certificate |
|
||||
|
||||
|
||||
### Ignore specific files when deploying
|
||||
Add patterns to `.git-ftp-ignore` and all matching file names will be ignored. The patterns are interpreted as shell glob patterns.
|
||||
Here are some glob pattern examples:
|
||||
@@ -149,7 +160,10 @@ vendor/:composer.lock
|
||||
```
|
||||
But keep in mind that this will upload all files in the vendor folder, even those that are on the server already. And it will not delete files from that directory if local files are deleted.
|
||||
|
||||
### Common Examples
|
||||
---
|
||||
|
||||
# Common Examples
|
||||
|
||||
Read more about the differences between these protocols [https://www.sftp.net/sftp-vs-ftps](https://www.sftp.net/sftp-vs-ftps)
|
||||
|
||||
### FTP (File Transfer Protocol)
|
||||
@@ -165,11 +179,11 @@ jobs:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: FTP-Deploy-Action
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.0.0
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.1.1
|
||||
with:
|
||||
ftp-server: ftp://ftp.samkirkland.com/
|
||||
ftp-username: myFtpUserName
|
||||
@@ -181,7 +195,7 @@ jobs:
|
||||
|
||||
Use the legacy FTP over a secure encrypted connection.
|
||||
|
||||
Notes about sftp:
|
||||
Notes about ftps:
|
||||
- Most hosts don't offer FTPS, it's more common on windows/.net hosts and less common on linux hosting
|
||||
- Most hosts don't have a correct certificate setup for ftp domains, [even my host doesn't do it right](https://ftp.samkirkland.com/). This means you'll likely have to add `--insecure` to `git-ftp-args`
|
||||
|
||||
@@ -193,12 +207,12 @@ jobs:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: FTP-Deploy-Action
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.0.0
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.1.1
|
||||
with:
|
||||
ftp-server: ftps://ftp.samkirkland.com:21/
|
||||
ftp-username: myFTPSUsername
|
||||
@@ -217,6 +231,21 @@ Similar in name to FTP but this protocol is entirely new and requires SSH access
|
||||
- You will need to create a **SSH** user to deploy over SFTP. Normally this is your cpanel or hosting providers username and password
|
||||
- Most web hosts change the default port (21), check with your host for your port number
|
||||
|
||||
|
||||
### Known Hosts Setup
|
||||
**Windows**
|
||||
In powershell run `ssh-keyscan -p <sshport> <hostname>` and copy the hash output
|
||||
Example for samkirkland.com `ssh-keyscan -p 7822 samkirkland.com`
|
||||
|
||||
|
||||
**Linux, or OSX (using homebrew)**
|
||||
Install the OpenSSH packages and use `ssh-keyscan <hostname>` and copy the hash output
|
||||
|
||||
Add the `known-hosts` argument with your hosts hash
|
||||
Example: `knownhosts: ssh-rsa AAAAB3Nza...H1Q5Spw==`
|
||||
|
||||
*Note: If you receive a `Connection refused` error, you must specify the ssh port to your host*
|
||||
|
||||
```yml
|
||||
on: push
|
||||
name: Publish Website over SFTP
|
||||
@@ -225,23 +254,24 @@ jobs:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: FTP-Deploy-Action
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.0.0
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.1.1
|
||||
with:
|
||||
ftp-server: sftp://ftp.samkirkland.com:7280/
|
||||
ftp-username: mySFTPUsername
|
||||
ftp-password: ${{ secrets.SFTP_PASSWORD }}
|
||||
git-ftp-args: --insecure # if your certificate is setup correctly this can be removed
|
||||
git-ftp-args: --insecure # if your certificate is setup correctly this can be removed (see known-hosts argument)
|
||||
```
|
||||
|
||||
|
||||
### Build and Publish React/Angular/Vue Website
|
||||
Make sure you have an npm script named 'build'. This config should work for most node built websites.
|
||||
If you don't commit your `build` folder to github you MUST create a `.git-ftp-include` file with the content `!build/` so the folder is always uploaded
|
||||
|
||||
> #### If you don't commit your `build` folder to github you MUST create a `.git-ftp-include` file with the content `!build/` so the folder is always uploaded!
|
||||
|
||||
```yml
|
||||
on: push
|
||||
name: Build and Publish Front End Framework Website
|
||||
@@ -250,7 +280,7 @@ jobs:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -268,12 +298,12 @@ jobs:
|
||||
run: ls
|
||||
|
||||
- name: FTP-Deploy-Action
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.0.0
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.1.1
|
||||
with:
|
||||
ftp-server: ftp://ftp.samkirkland.com/
|
||||
ftp-username: myFTPUsername
|
||||
ftp-password: ${{ secrets.FTP_PASSWORD }}
|
||||
local-dir: build
|
||||
local-dir: build # This folder is NOT going to upload by default unless you add it to .git-ftp-include
|
||||
```
|
||||
|
||||
### Log only dry run: Use this mode for testing
|
||||
@@ -286,12 +316,12 @@ jobs:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: FTP-Deploy-Action
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.0.0
|
||||
uses: SamKirkland/FTP-Deploy-Action@3.1.1
|
||||
with:
|
||||
ftp-server: ftp://ftp.samkirkland.com/
|
||||
ftp-username: myFTPUsername
|
||||
@@ -299,36 +329,113 @@ jobs:
|
||||
git-ftp-args: --dry-run
|
||||
```
|
||||
|
||||
##### Want another example? Let me know by creating a github issue
|
||||
_Want another example? Let me know by creating a github issue_
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
1. `Can't access remote 'sftp://', exiting...` or `Can't access remote 'ftps://', exiting...`
|
||||
<details>
|
||||
<summary>How to exclude .git files from the publish</summary>
|
||||
|
||||
See the [`.git-ftp-ignore`](#ignore-specific-files-when-deploying) example section
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>All files are being uploaded instead of just different files</summary>
|
||||
|
||||
By default only different files are uploaded.
|
||||
|
||||
Verify you have `with: fetch-depth: 2` in your `actions/checkout@v2.1.0` step. The last 2 checkins are required in order to determine differences
|
||||
|
||||
If you've had multiple git commits without deploying, all files will be uploaded to get back in sync
|
||||
|
||||
Verify you **don't** have the `--all` git-ftp-args flag set
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>How do I set a upload timeout?</summary>
|
||||
|
||||
github has a built-in `timeout-minutes` option, see customized example below
|
||||
|
||||
```yaml
|
||||
on: push
|
||||
name: Publish Website
|
||||
jobs:
|
||||
FTP-Deploy-Action:
|
||||
name: FTP-Deploy-Action
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15 # time out after 15 minutes (default is 360 minutes)
|
||||
steps:
|
||||
....
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Common Errors
|
||||
<details id="failed-to-upload">
|
||||
<summary>Failed to upload files</summary>
|
||||
|
||||
* **Fix 1:** Verify your login credentials are correct, download a ftp client and test with the exact same host/username/password
|
||||
* **Fix 2:** Remember if you are using SFTP or FTPS you cannot use a normal FTP account username/password. You must use a elevated account. Each host has a different process to setup a FTPS or SFTP account. Please contact your host for help.
|
||||
* **Fix 3:** If you are using sftp or ftps you should add `git-ftp-args: --insecure`, most hosts setup certificates incorrectly :(
|
||||
2. `rm: Access failed: 553 Prohibited file name: ./.ftpquota`
|
||||
* **What is happening?** The `.ftpquota` file is created by some FTP Servers and cannot be modified by the user
|
||||
* **Fix:** Add `.ftpquota` to your `.git-ftp-ignore` file
|
||||
3. How to exclude .git files from the publish
|
||||
* **Fix:** See the `.git-ftp-ignore` example above
|
||||
4. All files are being uploaded instead of just different files
|
||||
* By default only different files are uploaded.
|
||||
* Verify you have `with: fetch-depth: 2` in your `actions/checkout@master` step. The last 2 checkins are required in order to determine differences
|
||||
* If you've had multiple git commits without deploying, all files will be uploaded to get back in sync
|
||||
* Verify you **don't** have the `--all` git-ftp-args flag set
|
||||
5. How do I set a upload timeout?
|
||||
* github has a built-in `timeout-minutes` option. Place `timeout-minutes: X` before the `steps:` line. Timeout defaults to 360 minutes.
|
||||
</details>
|
||||
|
||||
<details id="cant-access-remote-sftp">
|
||||
<summary>Can't access remote 'sftp://', exiting...</summary>
|
||||
|
||||
### Debugging locally
|
||||
##### Instructions for debugging on windows
|
||||
- Install docker for windows
|
||||
- Open powershell
|
||||
See ["Failed to upload files"](#failed-to-upload) section above
|
||||
</details>
|
||||
|
||||
<details id="cant-access-remote-ftps">
|
||||
<summary>Can't access remote 'ftps://', exiting...</summary>
|
||||
|
||||
See ["Failed to upload files"](#failed-to-upload) section above
|
||||
</details>
|
||||
|
||||
<details id="files-arent_uploading">
|
||||
<summary>My files aren't uploading</summary>
|
||||
|
||||
V3+ uses github to determine when files have changes and only publish differences. This means files that aren't committed to github will not upload by default.
|
||||
|
||||
To change this behavior please see [`.git-ftp-ignore`](#ignore-specific-files-when-deploying) documentation.
|
||||
</details>
|
||||
|
||||
<details id="prohibited-file-name">
|
||||
<summary>rm: Access failed: 553 Prohibited file name: ./.ftpquota</summary>
|
||||
|
||||
The `.ftpquota` file is created by some FTP Servers and cannot be modified by the user
|
||||
|
||||
Add `.ftpquota` to your [`.git-ftp-ignore`](#ignore-specific-files-when-deploying) file
|
||||
</details>
|
||||
|
||||
<details id="ssl-peer-certificate">
|
||||
<summary>Error: SSL peer certificate or SSH remote key was not OK</summary>
|
||||
|
||||
Whitelist your host via the `known-hosts` configuration option (see [known hosts setup](#known-hosts-setup) in SFTP) or add the `--insecure` argument
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Debugging locally
|
||||
##### Instructions for debugging Windows
|
||||
- [Install docker](https://docs.docker.com/get-docker/)
|
||||
- Open powershell **as Administrator**
|
||||
- Install [act-cli](https://github.com/nektos/act#installation) by running `choco install act-cli`
|
||||
- Navigate to the repo folder
|
||||
- Run `docker build --tag action .`
|
||||
- Run `docker run action`
|
||||
- Run `npm install` - this will install all dependencies to build this project
|
||||
- Run `npm build` - this will build the action javascript and watch/rebuild when files change
|
||||
- Run `npm run build-docker` - this will build the docker container (only needs to be done once)
|
||||
- Run `npm run run-docker` - this will spin up a local copy of the action defined in `/debug/local-debug-deployment.yaml`. Update package.json to set any secret values
|
||||
|
||||
##### Instructions for debugging on linux
|
||||
- Please submit a PR for linux instructions :)
|
||||
#### Instructions for debugging on Linux
|
||||
- [Install docker](https://docs.docker.com/get-docker/) on a Debian-based distro you can run `sudo apt install docker docker.io`
|
||||
- Open the terminal
|
||||
- Install [act-cli](https://github.com/nektos/act#installation)
|
||||
- Navigate to the repo folder
|
||||
- Run `npm install` - this will install all dependencies to build this project
|
||||
- Run `npm build` - this will build the action javascript and watch/rebuild when files change
|
||||
- Run `npm run build-docker` - this will build the docker container (only needs to be done once)
|
||||
- Run `npm run run-docker` - this will spin up a local copy of the action defined in `/debug/local-debug-deployment.yaml`. Update package.json to set any secret values
|
||||
|
||||
#### Pull Requests Welcome!
|
||||
|
||||
@@ -13,11 +13,11 @@ inputs:
|
||||
required: true
|
||||
local-dir:
|
||||
description: 'The local folder to copy, defaults to root project folder'
|
||||
defaults: ./
|
||||
default: ./
|
||||
required: false
|
||||
git-ftp-args:
|
||||
description: 'Passes through options into git-ftp'
|
||||
defaults:
|
||||
default:
|
||||
required: false
|
||||
runs:
|
||||
using: 'docker'
|
||||
|
||||
18
debug/local-debug-deployment.yaml
Normal file
18
debug/local-debug-deployment.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
on: push
|
||||
name: Local Debug Deployment
|
||||
jobs:
|
||||
Local-Debug-Deployment:
|
||||
name: Local-Debug-Deployment
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: FTP-Deploy-Action
|
||||
uses: ./
|
||||
with:
|
||||
ftp-server: ftp://ftp.samkirkland.com/
|
||||
ftp-username: ${{ secrets.username }}
|
||||
ftp-password: ${{ secrets.password }}
|
||||
git-ftp-args: --dry-run
|
||||
721
dist/index.js
vendored
721
dist/index.js
vendored
@@ -43,7 +43,7 @@ module.exports =
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
||||
/***/ 9:
|
||||
/***/ 1:
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
@@ -58,9 +58,316 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const os = __webpack_require__(87);
|
||||
const events = __webpack_require__(614);
|
||||
const child = __webpack_require__(129);
|
||||
const childProcess = __webpack_require__(129);
|
||||
const path = __webpack_require__(622);
|
||||
const util_1 = __webpack_require__(669);
|
||||
const ioUtil = __webpack_require__(672);
|
||||
const exec = util_1.promisify(childProcess.exec);
|
||||
/**
|
||||
* Copies a file or folder.
|
||||
* Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js
|
||||
*
|
||||
* @param source source path
|
||||
* @param dest destination path
|
||||
* @param options optional. See CopyOptions.
|
||||
*/
|
||||
function cp(source, dest, options = {}) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const { force, recursive } = readCopyOptions(options);
|
||||
const destStat = (yield ioUtil.exists(dest)) ? yield ioUtil.stat(dest) : null;
|
||||
// Dest is an existing file, but not forcing
|
||||
if (destStat && destStat.isFile() && !force) {
|
||||
return;
|
||||
}
|
||||
// If dest is an existing directory, should copy inside.
|
||||
const newDest = destStat && destStat.isDirectory()
|
||||
? path.join(dest, path.basename(source))
|
||||
: dest;
|
||||
if (!(yield ioUtil.exists(source))) {
|
||||
throw new Error(`no such file or directory: ${source}`);
|
||||
}
|
||||
const sourceStat = yield ioUtil.stat(source);
|
||||
if (sourceStat.isDirectory()) {
|
||||
if (!recursive) {
|
||||
throw new Error(`Failed to copy. ${source} is a directory, but tried to copy without recursive flag.`);
|
||||
}
|
||||
else {
|
||||
yield cpDirRecursive(source, newDest, 0, force);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (path.relative(source, newDest) === '') {
|
||||
// a file cannot be copied to itself
|
||||
throw new Error(`'${newDest}' and '${source}' are the same file`);
|
||||
}
|
||||
yield copyFile(source, newDest, force);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.cp = cp;
|
||||
/**
|
||||
* Moves a path.
|
||||
*
|
||||
* @param source source path
|
||||
* @param dest destination path
|
||||
* @param options optional. See MoveOptions.
|
||||
*/
|
||||
function mv(source, dest, options = {}) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (yield ioUtil.exists(dest)) {
|
||||
let destExists = true;
|
||||
if (yield ioUtil.isDirectory(dest)) {
|
||||
// If dest is directory copy src into dest
|
||||
dest = path.join(dest, path.basename(source));
|
||||
destExists = yield ioUtil.exists(dest);
|
||||
}
|
||||
if (destExists) {
|
||||
if (options.force == null || options.force) {
|
||||
yield rmRF(dest);
|
||||
}
|
||||
else {
|
||||
throw new Error('Destination already exists');
|
||||
}
|
||||
}
|
||||
}
|
||||
yield mkdirP(path.dirname(dest));
|
||||
yield ioUtil.rename(source, dest);
|
||||
});
|
||||
}
|
||||
exports.mv = mv;
|
||||
/**
|
||||
* Remove a path recursively with force
|
||||
*
|
||||
* @param inputPath path to remove
|
||||
*/
|
||||
function rmRF(inputPath) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (ioUtil.IS_WINDOWS) {
|
||||
// Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another
|
||||
// program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del.
|
||||
try {
|
||||
if (yield ioUtil.isDirectory(inputPath, true)) {
|
||||
yield exec(`rd /s /q "${inputPath}"`);
|
||||
}
|
||||
else {
|
||||
yield exec(`del /f /a "${inputPath}"`);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// if you try to delete a file that doesn't exist, desired result is achieved
|
||||
// other errors are valid
|
||||
if (err.code !== 'ENOENT')
|
||||
throw err;
|
||||
}
|
||||
// Shelling out fails to remove a symlink folder with missing source, this unlink catches that
|
||||
try {
|
||||
yield ioUtil.unlink(inputPath);
|
||||
}
|
||||
catch (err) {
|
||||
// if you try to delete a file that doesn't exist, desired result is achieved
|
||||
// other errors are valid
|
||||
if (err.code !== 'ENOENT')
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
else {
|
||||
let isDir = false;
|
||||
try {
|
||||
isDir = yield ioUtil.isDirectory(inputPath);
|
||||
}
|
||||
catch (err) {
|
||||
// if you try to delete a file that doesn't exist, desired result is achieved
|
||||
// other errors are valid
|
||||
if (err.code !== 'ENOENT')
|
||||
throw err;
|
||||
return;
|
||||
}
|
||||
if (isDir) {
|
||||
yield exec(`rm -rf "${inputPath}"`);
|
||||
}
|
||||
else {
|
||||
yield ioUtil.unlink(inputPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.rmRF = rmRF;
|
||||
/**
|
||||
* Make a directory. Creates the full path with folders in between
|
||||
* Will throw if it fails
|
||||
*
|
||||
* @param fsPath path to create
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
function mkdirP(fsPath) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield ioUtil.mkdirP(fsPath);
|
||||
});
|
||||
}
|
||||
exports.mkdirP = mkdirP;
|
||||
/**
|
||||
* Returns path of a tool had the tool actually been invoked. Resolves via paths.
|
||||
* If you check and the tool does not exist, it will throw.
|
||||
*
|
||||
* @param tool name of the tool
|
||||
* @param check whether to check if tool exists
|
||||
* @returns Promise<string> path to tool
|
||||
*/
|
||||
function which(tool, check) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (!tool) {
|
||||
throw new Error("parameter 'tool' is required");
|
||||
}
|
||||
// recursive when check=true
|
||||
if (check) {
|
||||
const result = yield which(tool, false);
|
||||
if (!result) {
|
||||
if (ioUtil.IS_WINDOWS) {
|
||||
throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
// build the list of extensions to try
|
||||
const extensions = [];
|
||||
if (ioUtil.IS_WINDOWS && process.env.PATHEXT) {
|
||||
for (const extension of process.env.PATHEXT.split(path.delimiter)) {
|
||||
if (extension) {
|
||||
extensions.push(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if it's rooted, return it if exists. otherwise return empty.
|
||||
if (ioUtil.isRooted(tool)) {
|
||||
const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions);
|
||||
if (filePath) {
|
||||
return filePath;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
// if any path separators, return empty
|
||||
if (tool.includes('/') || (ioUtil.IS_WINDOWS && tool.includes('\\'))) {
|
||||
return '';
|
||||
}
|
||||
// build the list of directories
|
||||
//
|
||||
// Note, technically "where" checks the current directory on Windows. From a toolkit perspective,
|
||||
// it feels like we should not do this. Checking the current directory seems like more of a use
|
||||
// case of a shell, and the which() function exposed by the toolkit should strive for consistency
|
||||
// across platforms.
|
||||
const directories = [];
|
||||
if (process.env.PATH) {
|
||||
for (const p of process.env.PATH.split(path.delimiter)) {
|
||||
if (p) {
|
||||
directories.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
// return the first match
|
||||
for (const directory of directories) {
|
||||
const filePath = yield ioUtil.tryGetExecutablePath(directory + path.sep + tool, extensions);
|
||||
if (filePath) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
catch (err) {
|
||||
throw new Error(`which failed with message ${err.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.which = which;
|
||||
function readCopyOptions(options) {
|
||||
const force = options.force == null ? true : options.force;
|
||||
const recursive = Boolean(options.recursive);
|
||||
return { force, recursive };
|
||||
}
|
||||
function cpDirRecursive(sourceDir, destDir, currentDepth, force) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Ensure there is not a run away recursive copy
|
||||
if (currentDepth >= 255)
|
||||
return;
|
||||
currentDepth++;
|
||||
yield mkdirP(destDir);
|
||||
const files = yield ioUtil.readdir(sourceDir);
|
||||
for (const fileName of files) {
|
||||
const srcFile = `${sourceDir}/${fileName}`;
|
||||
const destFile = `${destDir}/${fileName}`;
|
||||
const srcFileStat = yield ioUtil.lstat(srcFile);
|
||||
if (srcFileStat.isDirectory()) {
|
||||
// Recurse
|
||||
yield cpDirRecursive(srcFile, destFile, currentDepth, force);
|
||||
}
|
||||
else {
|
||||
yield copyFile(srcFile, destFile, force);
|
||||
}
|
||||
}
|
||||
// Change the mode for the newly created directory
|
||||
yield ioUtil.chmod(destDir, (yield ioUtil.stat(sourceDir)).mode);
|
||||
});
|
||||
}
|
||||
// Buffered file copy
|
||||
function copyFile(srcFile, destFile, force) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if ((yield ioUtil.lstat(srcFile)).isSymbolicLink()) {
|
||||
// unlink/re-link it
|
||||
try {
|
||||
yield ioUtil.lstat(destFile);
|
||||
yield ioUtil.unlink(destFile);
|
||||
}
|
||||
catch (e) {
|
||||
// Try to override file permission
|
||||
if (e.code === 'EPERM') {
|
||||
yield ioUtil.chmod(destFile, '0666');
|
||||
yield ioUtil.unlink(destFile);
|
||||
}
|
||||
// other errors = it doesn't exist, no work to do
|
||||
}
|
||||
// Copy over symlink
|
||||
const symlinkFull = yield ioUtil.readlink(srcFile);
|
||||
yield ioUtil.symlink(symlinkFull, destFile, ioUtil.IS_WINDOWS ? 'junction' : null);
|
||||
}
|
||||
else if (!(yield ioUtil.exists(destFile)) || force) {
|
||||
yield ioUtil.copyFile(srcFile, destFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=io.js.map
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 9:
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const os = __importStar(__webpack_require__(87));
|
||||
const events = __importStar(__webpack_require__(614));
|
||||
const child = __importStar(__webpack_require__(129));
|
||||
const path = __importStar(__webpack_require__(622));
|
||||
const io = __importStar(__webpack_require__(1));
|
||||
const ioUtil = __importStar(__webpack_require__(672));
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
/*
|
||||
@@ -406,6 +713,16 @@ class ToolRunner extends events.EventEmitter {
|
||||
*/
|
||||
exec() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// root the tool path if it is unrooted and contains relative pathing
|
||||
if (!ioUtil.isRooted(this.toolPath) &&
|
||||
(this.toolPath.includes('/') ||
|
||||
(IS_WINDOWS && this.toolPath.includes('\\')))) {
|
||||
// prefer options.cwd if it is specified, however options.cwd may also need to be rooted
|
||||
this.toolPath = path.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath);
|
||||
}
|
||||
// if the tool is only a file name, then resolve it from the PATH
|
||||
// otherwise verify it exists (add extension on Windows if necessary)
|
||||
this.toolPath = yield io.which(this.toolPath, true);
|
||||
return new Promise((resolve, reject) => {
|
||||
this._debug(`exec tool: ${this.toolPath}`);
|
||||
this._debug('arguments:');
|
||||
@@ -494,6 +811,12 @@ class ToolRunner extends events.EventEmitter {
|
||||
resolve(exitCode);
|
||||
}
|
||||
});
|
||||
if (this.options.input) {
|
||||
if (!cp.stdin) {
|
||||
throw new Error('child process missing stdin');
|
||||
}
|
||||
cp.stdin.end(this.options.input);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -659,30 +982,58 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const exec = __importStar(__webpack_require__(986));
|
||||
const fs_1 = __importDefault(__webpack_require__(747));
|
||||
const util_1 = __webpack_require__(669);
|
||||
const writeFileAsync = util_1.promisify(fs_1.default.writeFile);
|
||||
const errorDeploying = "⚠️ Error deploying";
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const userArguments = getUserArguments();
|
||||
try {
|
||||
const userArguments = getUserArguments();
|
||||
yield configureHost(userArguments);
|
||||
yield syncFiles(userArguments);
|
||||
console.log("✅ Deploy Complete");
|
||||
}
|
||||
catch (error) {
|
||||
console.error("⚠️ Error deploying");
|
||||
console.error(errorDeploying);
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
run();
|
||||
function configureHost(args) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (args.knownHosts === "") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const sshFolder = `${process.env['HOME']}/.ssh`;
|
||||
yield exec.exec(`mkdir -v -p ${sshFolder}`);
|
||||
yield exec.exec(`chmod 700 ${sshFolder}`);
|
||||
writeFileAsync(`${sshFolder}/known_hosts`, args.knownHosts);
|
||||
yield exec.exec(`chmod 755 ${sshFolder}/known_hosts`);
|
||||
console.log("✅ Configured known_hosts");
|
||||
}
|
||||
catch (error) {
|
||||
console.error("⚠️ Error configuring known_hosts");
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
function getUserArguments() {
|
||||
return {
|
||||
ftp_server: core.getInput("ftp-server", { required: true }),
|
||||
ftp_username: core.getInput("ftp-username", { required: true }),
|
||||
ftp_password: core.getInput("ftp-password", { required: true }),
|
||||
local_dir: withDefault(core.getInput("local-dir"), "./"),
|
||||
gitFtpArgs: withDefault(core.getInput("git-ftp-args"), "")
|
||||
gitFtpArgs: withDefault(core.getInput("git-ftp-args"), ""),
|
||||
knownHosts: withDefault(core.getInput("known-hosts"), "")
|
||||
};
|
||||
}
|
||||
function withDefault(value, defaultValue) {
|
||||
@@ -698,18 +1049,32 @@ function syncFiles(args) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
yield core.group("Uploading files", () => __awaiter(this, void 0, void 0, function* () {
|
||||
return yield exec.exec(`git ftp push --force --auto-init --verbose --syncroot ${args.local_dir} --user ${args.ftp_username} --passwd ${args.ftp_password} ${args.gitFtpArgs} ${args.ftp_server}`);
|
||||
return yield exec.exec("git ftp push", [
|
||||
"--force",
|
||||
"--auto-init",
|
||||
"--verbose",
|
||||
`--syncroot=${args.local_dir}`,
|
||||
`--user=${args.ftp_username}`,
|
||||
`--passwd=${args.ftp_password}`,
|
||||
args.gitFtpArgs,
|
||||
args.ftp_server
|
||||
]);
|
||||
}));
|
||||
}
|
||||
catch (error) {
|
||||
console.error("⚠️ Failed to upload files");
|
||||
core.setFailed(error.message);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 357:
|
||||
/***/ (function(module) {
|
||||
|
||||
module.exports = require("assert");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 431:
|
||||
@@ -717,17 +1082,24 @@ function syncFiles(args) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const os = __webpack_require__(87);
|
||||
const os = __importStar(__webpack_require__(87));
|
||||
/**
|
||||
* Commands
|
||||
*
|
||||
* Command Format:
|
||||
* ##[name key=value;key=value]message
|
||||
* ::name key=value,key=value::message
|
||||
*
|
||||
* Examples:
|
||||
* ##[warning]This is the user warning message
|
||||
* ##[set-secret name=mypassword]definitelyNotAPassword!
|
||||
* ::warning::This is the message
|
||||
* ::set-env name=MY_VAR::some value
|
||||
*/
|
||||
function issueCommand(command, properties, message) {
|
||||
const cmd = new Command(command, properties, message);
|
||||
@@ -752,34 +1124,53 @@ class Command {
|
||||
let cmdStr = CMD_STRING + this.command;
|
||||
if (this.properties && Object.keys(this.properties).length > 0) {
|
||||
cmdStr += ' ';
|
||||
let first = true;
|
||||
for (const key in this.properties) {
|
||||
if (this.properties.hasOwnProperty(key)) {
|
||||
const val = this.properties[key];
|
||||
if (val) {
|
||||
// safely append the val - avoid blowing up when attempting to
|
||||
// call .replace() if message is not a string for some reason
|
||||
cmdStr += `${key}=${escape(`${val || ''}`)},`;
|
||||
if (first) {
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
cmdStr += ',';
|
||||
}
|
||||
cmdStr += `${key}=${escapeProperty(val)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cmdStr += CMD_STRING;
|
||||
// safely append the message - avoid blowing up when attempting to
|
||||
// call .replace() if message is not a string for some reason
|
||||
const message = `${this.message || ''}`;
|
||||
cmdStr += escapeData(message);
|
||||
cmdStr += `${CMD_STRING}${escapeData(this.message)}`;
|
||||
return cmdStr;
|
||||
}
|
||||
}
|
||||
function escapeData(s) {
|
||||
return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A');
|
||||
/**
|
||||
* Sanitizes an input into a string so it can be passed into issueCommand safely
|
||||
* @param input input to sanitize into a string
|
||||
*/
|
||||
function toCommandValue(input) {
|
||||
if (input === null || input === undefined) {
|
||||
return '';
|
||||
}
|
||||
else if (typeof input === 'string' || input instanceof String) {
|
||||
return input;
|
||||
}
|
||||
return JSON.stringify(input);
|
||||
}
|
||||
function escape(s) {
|
||||
return s
|
||||
exports.toCommandValue = toCommandValue;
|
||||
function escapeData(s) {
|
||||
return toCommandValue(s)
|
||||
.replace(/%/g, '%25')
|
||||
.replace(/\r/g, '%0D')
|
||||
.replace(/\n/g, '%0A');
|
||||
}
|
||||
function escapeProperty(s) {
|
||||
return toCommandValue(s)
|
||||
.replace(/%/g, '%25')
|
||||
.replace(/\r/g, '%0D')
|
||||
.replace(/\n/g, '%0A')
|
||||
.replace(/]/g, '%5D')
|
||||
.replace(/;/g, '%3B');
|
||||
.replace(/:/g, '%3A')
|
||||
.replace(/,/g, '%2C');
|
||||
}
|
||||
//# sourceMappingURL=command.js.map
|
||||
|
||||
@@ -799,10 +1190,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const command_1 = __webpack_require__(431);
|
||||
const os = __webpack_require__(87);
|
||||
const path = __webpack_require__(622);
|
||||
const os = __importStar(__webpack_require__(87));
|
||||
const path = __importStar(__webpack_require__(622));
|
||||
/**
|
||||
* The code to exit an action
|
||||
*/
|
||||
@@ -823,11 +1221,13 @@ var ExitCode;
|
||||
/**
|
||||
* Sets env variable for this action and future actions in the job
|
||||
* @param name the name of the variable to set
|
||||
* @param val the value of the variable
|
||||
* @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function exportVariable(name, val) {
|
||||
process.env[name] = val;
|
||||
command_1.issueCommand('set-env', { name }, val);
|
||||
const convertedVal = command_1.toCommandValue(val);
|
||||
process.env[name] = convertedVal;
|
||||
command_1.issueCommand('set-env', { name }, convertedVal);
|
||||
}
|
||||
exports.exportVariable = exportVariable;
|
||||
/**
|
||||
@@ -866,12 +1266,22 @@ exports.getInput = getInput;
|
||||
* Sets the value of an output.
|
||||
*
|
||||
* @param name name of the output to set
|
||||
* @param value value to store
|
||||
* @param value value to store. Non-string values will be converted to a string via JSON.stringify
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function setOutput(name, value) {
|
||||
command_1.issueCommand('set-output', { name }, value);
|
||||
}
|
||||
exports.setOutput = setOutput;
|
||||
/**
|
||||
* Enables or disables the echoing of commands into stdout for the rest of the step.
|
||||
* Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set.
|
||||
*
|
||||
*/
|
||||
function setCommandEcho(enabled) {
|
||||
command_1.issue('echo', enabled ? 'on' : 'off');
|
||||
}
|
||||
exports.setCommandEcho = setCommandEcho;
|
||||
//-----------------------------------------------------------------------
|
||||
// Results
|
||||
//-----------------------------------------------------------------------
|
||||
@@ -888,6 +1298,13 @@ exports.setFailed = setFailed;
|
||||
//-----------------------------------------------------------------------
|
||||
// Logging Commands
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Gets whether Actions Step Debug is on or not
|
||||
*/
|
||||
function isDebug() {
|
||||
return process.env['RUNNER_DEBUG'] === '1';
|
||||
}
|
||||
exports.isDebug = isDebug;
|
||||
/**
|
||||
* Writes debug message to user log
|
||||
* @param message debug message
|
||||
@@ -898,18 +1315,18 @@ function debug(message) {
|
||||
exports.debug = debug;
|
||||
/**
|
||||
* Adds an error issue
|
||||
* @param message error issue message
|
||||
* @param message error issue message. Errors will be converted to string via toString()
|
||||
*/
|
||||
function error(message) {
|
||||
command_1.issue('error', message);
|
||||
command_1.issue('error', message instanceof Error ? message.toString() : message);
|
||||
}
|
||||
exports.error = error;
|
||||
/**
|
||||
* Adds an warning issue
|
||||
* @param message warning issue message
|
||||
* @param message warning issue message. Errors will be converted to string via toString()
|
||||
*/
|
||||
function warning(message) {
|
||||
command_1.issue('warning', message);
|
||||
command_1.issue('warning', message instanceof Error ? message.toString() : message);
|
||||
}
|
||||
exports.warning = warning;
|
||||
/**
|
||||
@@ -967,8 +1384,9 @@ exports.group = group;
|
||||
* Saves state for current action, the state can only be retrieved by this action's post job execution.
|
||||
*
|
||||
* @param name name of the state to store
|
||||
* @param value value to store
|
||||
* @param value value to store. Non-string values will be converted to a string via JSON.stringify
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function saveState(name, value) {
|
||||
command_1.issueCommand('save-state', { name }, value);
|
||||
}
|
||||
@@ -1001,6 +1419,222 @@ module.exports = require("path");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 669:
|
||||
/***/ (function(module) {
|
||||
|
||||
module.exports = require("util");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 672:
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var _a;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const assert_1 = __webpack_require__(357);
|
||||
const fs = __webpack_require__(747);
|
||||
const path = __webpack_require__(622);
|
||||
_a = fs.promises, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink;
|
||||
exports.IS_WINDOWS = process.platform === 'win32';
|
||||
function exists(fsPath) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
yield exports.stat(fsPath);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return false;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
exports.exists = exists;
|
||||
function isDirectory(fsPath, useStat = false) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath);
|
||||
return stats.isDirectory();
|
||||
});
|
||||
}
|
||||
exports.isDirectory = isDirectory;
|
||||
/**
|
||||
* On OSX/Linux, true if path starts with '/'. On Windows, true for paths like:
|
||||
* \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases).
|
||||
*/
|
||||
function isRooted(p) {
|
||||
p = normalizeSeparators(p);
|
||||
if (!p) {
|
||||
throw new Error('isRooted() parameter "p" cannot be empty');
|
||||
}
|
||||
if (exports.IS_WINDOWS) {
|
||||
return (p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello
|
||||
); // e.g. C: or C:\hello
|
||||
}
|
||||
return p.startsWith('/');
|
||||
}
|
||||
exports.isRooted = isRooted;
|
||||
/**
|
||||
* Recursively create a directory at `fsPath`.
|
||||
*
|
||||
* This implementation is optimistic, meaning it attempts to create the full
|
||||
* path first, and backs up the path stack from there.
|
||||
*
|
||||
* @param fsPath The path to create
|
||||
* @param maxDepth The maximum recursion depth
|
||||
* @param depth The current recursion depth
|
||||
*/
|
||||
function mkdirP(fsPath, maxDepth = 1000, depth = 1) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
assert_1.ok(fsPath, 'a path argument must be provided');
|
||||
fsPath = path.resolve(fsPath);
|
||||
if (depth >= maxDepth)
|
||||
return exports.mkdir(fsPath);
|
||||
try {
|
||||
yield exports.mkdir(fsPath);
|
||||
return;
|
||||
}
|
||||
catch (err) {
|
||||
switch (err.code) {
|
||||
case 'ENOENT': {
|
||||
yield mkdirP(path.dirname(fsPath), maxDepth, depth + 1);
|
||||
yield exports.mkdir(fsPath);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
let stats;
|
||||
try {
|
||||
stats = yield exports.stat(fsPath);
|
||||
}
|
||||
catch (err2) {
|
||||
throw err;
|
||||
}
|
||||
if (!stats.isDirectory())
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.mkdirP = mkdirP;
|
||||
/**
|
||||
* Best effort attempt to determine whether a file exists and is executable.
|
||||
* @param filePath file path to check
|
||||
* @param extensions additional file extensions to try
|
||||
* @return if file exists and is executable, returns the file path. otherwise empty string.
|
||||
*/
|
||||
function tryGetExecutablePath(filePath, extensions) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let stats = undefined;
|
||||
try {
|
||||
// test file exists
|
||||
stats = yield exports.stat(filePath);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`);
|
||||
}
|
||||
}
|
||||
if (stats && stats.isFile()) {
|
||||
if (exports.IS_WINDOWS) {
|
||||
// on Windows, test for valid extension
|
||||
const upperExt = path.extname(filePath).toUpperCase();
|
||||
if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isUnixExecutable(stats)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
// try each extension
|
||||
const originalFilePath = filePath;
|
||||
for (const extension of extensions) {
|
||||
filePath = originalFilePath + extension;
|
||||
stats = undefined;
|
||||
try {
|
||||
stats = yield exports.stat(filePath);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`);
|
||||
}
|
||||
}
|
||||
if (stats && stats.isFile()) {
|
||||
if (exports.IS_WINDOWS) {
|
||||
// preserve the case of the actual file (since an extension was appended)
|
||||
try {
|
||||
const directory = path.dirname(filePath);
|
||||
const upperName = path.basename(filePath).toUpperCase();
|
||||
for (const actualName of yield exports.readdir(directory)) {
|
||||
if (upperName === actualName.toUpperCase()) {
|
||||
filePath = path.join(directory, actualName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
else {
|
||||
if (isUnixExecutable(stats)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
});
|
||||
}
|
||||
exports.tryGetExecutablePath = tryGetExecutablePath;
|
||||
function normalizeSeparators(p) {
|
||||
p = p || '';
|
||||
if (exports.IS_WINDOWS) {
|
||||
// convert slashes on Windows
|
||||
p = p.replace(/\//g, '\\');
|
||||
// remove redundant slashes
|
||||
return p.replace(/\\\\+/g, '\\');
|
||||
}
|
||||
// remove redundant slashes
|
||||
return p.replace(/\/\/+/g, '/');
|
||||
}
|
||||
// on Mac/Linux, test the execute bit
|
||||
// R W X R W X R W X
|
||||
// 256 128 64 32 16 8 4 2 1
|
||||
function isUnixExecutable(stats) {
|
||||
return ((stats.mode & 1) > 0 ||
|
||||
((stats.mode & 8) > 0 && stats.gid === process.getgid()) ||
|
||||
((stats.mode & 64) > 0 && stats.uid === process.getuid()));
|
||||
}
|
||||
//# sourceMappingURL=io-util.js.map
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 747:
|
||||
/***/ (function(module) {
|
||||
|
||||
module.exports = require("fs");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 986:
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
@@ -1015,8 +1649,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tr = __webpack_require__(9);
|
||||
const tr = __importStar(__webpack_require__(9));
|
||||
/**
|
||||
* Exec a command.
|
||||
* Output will be streamed to the live console.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
3753
package-lock.json
generated
3753
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -5,13 +5,12 @@
|
||||
"description": "TypeScript template action",
|
||||
"main": "dist/main.js",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build:dev": "tsc",
|
||||
"build:production": "ncc build src/main.ts -o dist",
|
||||
"build:docker": "docker build --tag action .",
|
||||
"run:docker": "docker run action --build-arg FTP_SERVER=example.com FTP_USERNAME=test@example.com FTP_PASSWORD=passwordExample"
|
||||
"build": "ncc build src/main.ts -o dist --watch",
|
||||
"build-docker": "docker build --tag action .",
|
||||
"run-docker": "act --workflows ./debug/ --secret username=UserNameHere --secret password=PasswordHere"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,16 +24,16 @@
|
||||
"author": "SamKirkland",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "1.2.0",
|
||||
"@actions/exec": "1.0.1"
|
||||
"@actions/core": "1.2.4",
|
||||
"@actions/exec": "1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "24.0.23",
|
||||
"@types/jest": "25.2.1",
|
||||
"@types/node": "12.12.8",
|
||||
"@zeit/ncc": "0.20.5",
|
||||
"jest": "24.9.0",
|
||||
"jest-circus": "24.9.0",
|
||||
"ts-jest": "24.1.0",
|
||||
"typescript": "3.7.2"
|
||||
"@zeit/ncc": "0.22.1",
|
||||
"jest": "25.5.3",
|
||||
"jest-circus": "25.5.3",
|
||||
"ts-jest": "25.4.0",
|
||||
"typescript": "3.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
51
src/main.ts
51
src/main.ts
@@ -1,23 +1,49 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { IActionArguments } from './types';
|
||||
|
||||
async function run() {
|
||||
const userArguments = getUserArguments();
|
||||
const writeFileAsync = promisify(fs.writeFile);
|
||||
const errorDeploying = "⚠️ Error deploying";
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const userArguments = getUserArguments();
|
||||
|
||||
await configureHost(userArguments);
|
||||
await syncFiles(userArguments);
|
||||
|
||||
console.log("✅ Deploy Complete");
|
||||
}
|
||||
catch (error) {
|
||||
console.error("⚠️ Error deploying");
|
||||
console.error(errorDeploying);
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
async function configureHost(args: IActionArguments): Promise<void> {
|
||||
if (args.knownHosts === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const sshFolder = `${process.env['HOME']}/.ssh`;
|
||||
|
||||
await exec.exec(`mkdir -v -p ${sshFolder}`);
|
||||
await exec.exec(`chmod 700 ${sshFolder}`);
|
||||
writeFileAsync(`${sshFolder}/known_hosts`, args.knownHosts);
|
||||
await exec.exec(`chmod 755 ${sshFolder}/known_hosts`);
|
||||
|
||||
console.log("✅ Configured known_hosts");
|
||||
}
|
||||
catch (error) {
|
||||
console.error("⚠️ Error configuring known_hosts");
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function getUserArguments(): IActionArguments {
|
||||
return {
|
||||
@@ -25,7 +51,8 @@ function getUserArguments(): IActionArguments {
|
||||
ftp_username: core.getInput("ftp-username", { required: true }),
|
||||
ftp_password: core.getInput("ftp-password", { required: true }),
|
||||
local_dir: withDefault(core.getInput("local-dir"), "./"),
|
||||
gitFtpArgs: withDefault(core.getInput("git-ftp-args"), "")
|
||||
gitFtpArgs: withDefault(core.getInput("git-ftp-args"), ""),
|
||||
knownHosts: withDefault(core.getInput("known-hosts"), "")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,12 +70,22 @@ function withDefault(value: string, defaultValue: string) {
|
||||
async function syncFiles(args: IActionArguments) {
|
||||
try {
|
||||
await core.group("Uploading files", async () => {
|
||||
return await exec.exec(`git ftp push --force --auto-init --verbose --syncroot ${args.local_dir} --user ${args.ftp_username} --passwd ${args.ftp_password} ${args.gitFtpArgs} ${args.ftp_server}`);
|
||||
return await exec.exec(
|
||||
"git ftp push",
|
||||
[
|
||||
"--force",
|
||||
"--auto-init",
|
||||
"--verbose",
|
||||
`--syncroot=${args.local_dir}`,
|
||||
`--user=${args.ftp_username}`,
|
||||
`--passwd=${args.ftp_password}`,
|
||||
args.gitFtpArgs!,
|
||||
args.ftp_server!
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error("⚠️ Failed to upload files");
|
||||
core.setFailed(error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ export interface IActionArguments {
|
||||
|
||||
/** @default "" */
|
||||
gitFtpArgs: string | undefined;
|
||||
|
||||
/** @default "" */
|
||||
knownHosts: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,7 +25,7 @@ export enum gitFTPExitCode {
|
||||
ErrorWhileDownloading = 5,
|
||||
UnknownProtocol = 6,
|
||||
RemoteLocked = 7,
|
||||
NotAGitProject = 8,
|
||||
GitRelatedError = 8,
|
||||
PreFTPPushHookFailed = 9,
|
||||
LocalFileOperationFailed = 10
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# Migrating from v2 to v3
|
||||
|
||||
`uses: actions/checkout@master` must now have the option `fetch-depth: 2`
|
||||
`uses: actions/checkout@v2.1.0` must now have the option `fetch-depth: 2`
|
||||
|
||||
Without the `fetch-depth` option diffs cannot be calculated and all files will be uploaded.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user