Compare commits

..

3 Commits

Author SHA1 Message Date
Sam Kirkland
b7930b6a2e v3.0.0-patch 2022-12-15 09:52:44 -06:00
SamKirkland
f31cc90480 debian:10-slim 2021-08-16 11:19:15 -05:00
SamKirkland
346a843f1e switching docker to ubuntu:20.04 2021-08-16 10:59:10 -05:00
12 changed files with 2061 additions and 2699 deletions

View File

@@ -5,11 +5,11 @@ jobs:
name: FTP-Deploy-Action name: FTP-Deploy-Action
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.1.0 - uses: actions/checkout@master
with: with:
fetch-depth: 2 fetch-depth: 2
- name: FTP-Deploy-Action - name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@master uses: SamKirkland/FTP-Deploy-Action-Typescript@master
with: with:
ftp-server: ftp://ftp.samkirkland.com/ ftp-server: ftp://ftp.samkirkland.com/
ftp-username: ${{ secrets.ftp_username }} ftp-username: ${{ secrets.ftp_username }}

View File

@@ -5,11 +5,11 @@ jobs:
name: FTP-Deploy-Action name: FTP-Deploy-Action
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.1.0 - uses: actions/checkout@master
with: with:
fetch-depth: 2 fetch-depth: 2
- name: FTP-Deploy-Action - name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@master uses: SamKirkland/FTP-Deploy-Action-Typescript@master
with: with:
# deploy to a folder named "sftp-deploy-test.samkirkland.com" on my server # 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/ ftp-server: sftp://ftp.samkirkland.com:7822/home/samkirkland/sftp-deploy-test.samkirkland.com/

203
README.md
View File

@@ -4,13 +4,11 @@
Automate deploying websites and more with this GitHub action Automate deploying websites and more with this GitHub action
![Action](images/action-preview.gif)
![Test FTP Deploy](https://github.com/SamKirkland/FTP-Deploy-Action/workflows/Test%20FTP%20Deploy/badge.svg) ![Test SFTP Deploy](https://github.com/SamKirkland/FTP-Deploy-Action/workflows/Test%20SFTP%20Deploy/badge.svg) ![Test FTP Deploy](https://github.com/SamKirkland/FTP-Deploy-Action/workflows/Test%20FTP%20Deploy/badge.svg) ![Test SFTP Deploy](https://github.com/SamKirkland/FTP-Deploy-Action/workflows/Test%20SFTP%20Deploy/badge.svg)
--- ### Usage Example (Your_Project/.github/workflows/main.yml)
### Usage Example
Place the following in `Your_Project/.github/workflows/main.yml`
```yml ```yml
on: push on: push
name: Publish Website name: Publish Website
@@ -19,21 +17,18 @@ jobs:
name: FTP-Deploy-Action name: FTP-Deploy-Action
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.1.0 - uses: actions/checkout@master
with: with:
fetch-depth: 2 fetch-depth: 2
- name: FTP-Deploy-Action - name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@3.1.1 uses: SamKirkland/FTP-Deploy-Action@3.0.0
with: with:
ftp-server: ftp://ftp.samkirkland.com/ ftp-server: ftp://ftp.samkirkland.com/
ftp-username: myFtpUserName ftp-username: myFtpUserName
ftp-password: ${{ secrets.FTP_PASSWORD }} ftp-password: ${{ secrets.FTP_PASSWORD }}
``` ```
--- #### Setup Steps
### Setup Steps
1. Select the repository you want to add the action to 1. Select the repository you want to add the action to
2. Select the `Actions` tab 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` 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`
@@ -41,14 +36,8 @@ 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` 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 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 ### 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. Keys can be added directly to your .yml config file or referenced from your project `Secrets` storage.
@@ -62,7 +51,6 @@ I recommend you store your `ftp-password` as a secret.
| `ftp-password` | Yes | CrazyUniquePassword&%123 | | FTP account password | | `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 | | `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 | | `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` #### 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. 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.
@@ -74,7 +62,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 | | `--dry-run` | Does not upload or delete anything, but tries to get the .git-ftp.log file from remote host |
| `--silent` | Be silent | | `--silent` | Be silent |
| `--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` | | `--all` | Transfer all files, even seemingly the same as the target site (default is differences only) |
| `--lock` | Locks remote files from being modified while a deployment is running | | `--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 | | `--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 | | `--key` | SSH private key file name for SFTP |
@@ -83,6 +71,7 @@ Below is an incomplete list of commonly used args:
| `--insecure` | Don't verify server's certificate | | `--insecure` | Don't verify server's certificate |
| `--cacert <file>` | Use as CA certificate store. Useful when a server has a self-signed certificate | | `--cacert <file>` | Use as CA certificate store. Useful when a server has a self-signed certificate |
### Ignore specific files when deploying ### 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. 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: Here are some glob pattern examples:
@@ -160,10 +149,7 @@ 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. 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) 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) ### FTP (File Transfer Protocol)
@@ -179,11 +165,11 @@ jobs:
name: FTP-Deploy-Action name: FTP-Deploy-Action
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.1.0 - uses: actions/checkout@master
with: with:
fetch-depth: 2 fetch-depth: 2
- name: FTP-Deploy-Action - name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@3.1.1 uses: SamKirkland/FTP-Deploy-Action@3.0.0
with: with:
ftp-server: ftp://ftp.samkirkland.com/ ftp-server: ftp://ftp.samkirkland.com/
ftp-username: myFtpUserName ftp-username: myFtpUserName
@@ -195,7 +181,7 @@ jobs:
Use the legacy FTP over a secure encrypted connection. Use the legacy FTP over a secure encrypted connection.
Notes about ftps: Notes about sftp:
- Most hosts don't offer FTPS, it's more common on windows/.net hosts and less common on linux hosting - 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` - 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`
@@ -207,12 +193,12 @@ jobs:
name: FTP-Deploy-Action name: FTP-Deploy-Action
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.1.0 - uses: actions/checkout@master
with: with:
fetch-depth: 2 fetch-depth: 2
- name: FTP-Deploy-Action - name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@3.1.1 uses: SamKirkland/FTP-Deploy-Action@3.0.0
with: with:
ftp-server: ftps://ftp.samkirkland.com:21/ ftp-server: ftps://ftp.samkirkland.com:21/
ftp-username: myFTPSUsername ftp-username: myFTPSUsername
@@ -231,21 +217,6 @@ 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 - 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 - 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 ```yml
on: push on: push
name: Publish Website over SFTP name: Publish Website over SFTP
@@ -254,24 +225,23 @@ jobs:
name: FTP-Deploy-Action name: FTP-Deploy-Action
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.1.0 - uses: actions/checkout@master
with: with:
fetch-depth: 2 fetch-depth: 2
- name: FTP-Deploy-Action - name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@3.1.1 uses: SamKirkland/FTP-Deploy-Action@3.0.0
with: with:
ftp-server: sftp://ftp.samkirkland.com:7280/ ftp-server: sftp://ftp.samkirkland.com:7280/
ftp-username: mySFTPUsername ftp-username: mySFTPUsername
ftp-password: ${{ secrets.SFTP_PASSWORD }} ftp-password: ${{ secrets.SFTP_PASSWORD }}
git-ftp-args: --insecure # if your certificate is setup correctly this can be removed (see known-hosts argument) git-ftp-args: --insecure # if your certificate is setup correctly this can be removed
``` ```
### Build and Publish React/Angular/Vue Website ### 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. 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 ```yml
on: push on: push
name: Build and Publish Front End Framework Website name: Build and Publish Front End Framework Website
@@ -280,7 +250,7 @@ jobs:
name: FTP-Deploy-Action name: FTP-Deploy-Action
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.1.0 - uses: actions/checkout@master
with: with:
fetch-depth: 2 fetch-depth: 2
@@ -298,12 +268,12 @@ jobs:
run: ls run: ls
- name: FTP-Deploy-Action - name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@3.1.1 uses: SamKirkland/FTP-Deploy-Action@3.0.0
with: with:
ftp-server: ftp://ftp.samkirkland.com/ ftp-server: ftp://ftp.samkirkland.com/
ftp-username: myFTPUsername ftp-username: myFTPUsername
ftp-password: ${{ secrets.FTP_PASSWORD }} ftp-password: ${{ secrets.FTP_PASSWORD }}
local-dir: build # This folder is NOT going to upload by default unless you add it to .git-ftp-include local-dir: build
``` ```
### Log only dry run: Use this mode for testing ### Log only dry run: Use this mode for testing
@@ -316,12 +286,12 @@ jobs:
name: FTP-Deploy-Action name: FTP-Deploy-Action
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.1.0 - uses: actions/checkout@master
with: with:
fetch-depth: 2 fetch-depth: 2
- name: FTP-Deploy-Action - name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@3.1.1 uses: SamKirkland/FTP-Deploy-Action@3.0.0
with: with:
ftp-server: ftp://ftp.samkirkland.com/ ftp-server: ftp://ftp.samkirkland.com/
ftp-username: myFTPUsername ftp-username: myFTPUsername
@@ -329,113 +299,36 @@ jobs:
git-ftp-args: --dry-run 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 ## FAQ
<details> 1. `Can't access remote 'sftp://', exiting...` or `Can't access remote 'ftps://', exiting...`
<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 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 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 :( * **Fix 3:** If you are using sftp or ftps you should add `git-ftp-args: --insecure`, most hosts setup certificates incorrectly :(
</details> 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 id="cant-access-remote-sftp">
<summary>Can't access remote 'sftp://', exiting...</summary>
See ["Failed to upload files"](#failed-to-upload) section above
</details>
<details id="cant-access-remote-ftps"> ### Debugging locally
<summary>Can't access remote 'ftps://', exiting...</summary> ##### Instructions for debugging on windows
- Install docker for windows
See ["Failed to upload files"](#failed-to-upload) section above - Open powershell
</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 - Navigate to the repo folder
- Run `npm install` - this will install all dependencies to build this project - Run `docker build --tag action .`
- Run `npm build` - this will build the action javascript and watch/rebuild when files change - Run `docker run action`
- 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! #### Pull Requests Welcome!

View File

@@ -13,11 +13,11 @@ inputs:
required: true required: true
local-dir: local-dir:
description: 'The local folder to copy, defaults to root project folder' description: 'The local folder to copy, defaults to root project folder'
default: ./ defaults: ./
required: false required: false
git-ftp-args: git-ftp-args:
description: 'Passes through options into git-ftp' description: 'Passes through options into git-ftp'
default: defaults:
required: false required: false
runs: runs:
using: 'docker' using: 'docker'

View File

@@ -1,18 +0,0 @@
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

717
dist/index.js vendored
View File

@@ -43,303 +43,6 @@ module.exports =
/************************************************************************/ /************************************************************************/
/******/ ({ /******/ ({
/***/ 1:
/***/ (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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
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: /***/ 9:
/***/ (function(__unusedmodule, exports, __webpack_require__) { /***/ (function(__unusedmodule, exports, __webpack_require__) {
@@ -354,20 +57,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); 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 }); Object.defineProperty(exports, "__esModule", { value: true });
const os = __importStar(__webpack_require__(87)); const os = __webpack_require__(87);
const events = __importStar(__webpack_require__(614)); const events = __webpack_require__(614);
const child = __importStar(__webpack_require__(129)); const child = __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 */ /* eslint-disable @typescript-eslint/unbound-method */
const IS_WINDOWS = process.platform === 'win32'; const IS_WINDOWS = process.platform === 'win32';
/* /*
@@ -713,16 +406,6 @@ class ToolRunner extends events.EventEmitter {
*/ */
exec() { exec() {
return __awaiter(this, void 0, void 0, function* () { 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) => { return new Promise((resolve, reject) => {
this._debug(`exec tool: ${this.toolPath}`); this._debug(`exec tool: ${this.toolPath}`);
this._debug('arguments:'); this._debug('arguments:');
@@ -811,12 +494,6 @@ class ToolRunner extends events.EventEmitter {
resolve(exitCode); resolve(exitCode);
} }
}); });
if (this.options.input) {
if (!cp.stdin) {
throw new Error('child process missing stdin');
}
cp.stdin.end(this.options.input);
}
}); });
}); });
} }
@@ -982,58 +659,30 @@ var __importStar = (this && this.__importStar) || function (mod) {
result["default"] = mod; result["default"] = mod;
return result; return result;
}; };
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(__webpack_require__(470)); const core = __importStar(__webpack_require__(470));
const exec = __importStar(__webpack_require__(986)); 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() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const userArguments = getUserArguments();
try { try {
const userArguments = getUserArguments();
yield configureHost(userArguments);
yield syncFiles(userArguments); yield syncFiles(userArguments);
console.log("✅ Deploy Complete"); console.log("✅ Deploy Complete");
} }
catch (error) { catch (error) {
console.error(errorDeploying); console.error("⚠️ Error deploying");
core.setFailed(error.message); core.setFailed(error.message);
} }
}); });
} }
run(); 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() { function getUserArguments() {
return { return {
ftp_server: core.getInput("ftp-server", { required: true }), ftp_server: core.getInput("ftp-server", { required: true }),
ftp_username: core.getInput("ftp-username", { required: true }), ftp_username: core.getInput("ftp-username", { required: true }),
ftp_password: core.getInput("ftp-password", { required: true }), ftp_password: core.getInput("ftp-password", { required: true }),
local_dir: withDefault(core.getInput("local-dir"), "./"), 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) { function withDefault(value, defaultValue) {
@@ -1049,32 +698,18 @@ function syncFiles(args) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
yield core.group("Uploading files", () => __awaiter(this, void 0, void 0, function* () { yield core.group("Uploading files", () => __awaiter(this, void 0, void 0, function* () {
return yield exec.exec("git ftp push", [ 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}`);
"--force",
"--auto-init",
"--verbose",
`--syncroot=${args.local_dir}`,
`--user=${args.ftp_username}`,
`--passwd=${args.ftp_password}`,
args.gitFtpArgs,
args.ftp_server
]);
})); }));
} }
catch (error) { catch (error) {
console.error("⚠️ Failed to upload files");
core.setFailed(error.message); core.setFailed(error.message);
throw error;
} }
}); });
} }
/***/ }),
/***/ 357:
/***/ (function(module) {
module.exports = require("assert");
/***/ }), /***/ }),
/***/ 431: /***/ 431:
@@ -1082,24 +717,17 @@ module.exports = require("assert");
"use strict"; "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 }); Object.defineProperty(exports, "__esModule", { value: true });
const os = __importStar(__webpack_require__(87)); const os = __webpack_require__(87);
/** /**
* Commands * Commands
* *
* Command Format: * Command Format:
* ::name key=value,key=value::message * ##[name key=value;key=value]message
* *
* Examples: * Examples:
* ::warning::This is the message * ##[warning]This is the user warning message
* ::set-env name=MY_VAR::some value * ##[set-secret name=mypassword]definitelyNotAPassword!
*/ */
function issueCommand(command, properties, message) { function issueCommand(command, properties, message) {
const cmd = new Command(command, properties, message); const cmd = new Command(command, properties, message);
@@ -1124,53 +752,34 @@ class Command {
let cmdStr = CMD_STRING + this.command; let cmdStr = CMD_STRING + this.command;
if (this.properties && Object.keys(this.properties).length > 0) { if (this.properties && Object.keys(this.properties).length > 0) {
cmdStr += ' '; cmdStr += ' ';
let first = true;
for (const key in this.properties) { for (const key in this.properties) {
if (this.properties.hasOwnProperty(key)) { if (this.properties.hasOwnProperty(key)) {
const val = this.properties[key]; const val = this.properties[key];
if (val) { if (val) {
if (first) { // safely append the val - avoid blowing up when attempting to
first = false; // call .replace() if message is not a string for some reason
} cmdStr += `${key}=${escape(`${val || ''}`)},`;
else {
cmdStr += ',';
}
cmdStr += `${key}=${escapeProperty(val)}`;
} }
} }
} }
} }
cmdStr += `${CMD_STRING}${escapeData(this.message)}`; 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);
return cmdStr; return cmdStr;
} }
} }
/**
* 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);
}
exports.toCommandValue = toCommandValue;
function escapeData(s) { function escapeData(s) {
return toCommandValue(s) return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A');
.replace(/%/g, '%25')
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A');
} }
function escapeProperty(s) { function escape(s) {
return toCommandValue(s) return s
.replace(/%/g, '%25')
.replace(/\r/g, '%0D') .replace(/\r/g, '%0D')
.replace(/\n/g, '%0A') .replace(/\n/g, '%0A')
.replace(/:/g, '%3A') .replace(/]/g, '%5D')
.replace(/,/g, '%2C'); .replace(/;/g, '%3B');
} }
//# sourceMappingURL=command.js.map //# sourceMappingURL=command.js.map
@@ -1190,17 +799,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); 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 }); Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = __webpack_require__(431); const command_1 = __webpack_require__(431);
const os = __importStar(__webpack_require__(87)); const os = __webpack_require__(87);
const path = __importStar(__webpack_require__(622)); const path = __webpack_require__(622);
/** /**
* The code to exit an action * The code to exit an action
*/ */
@@ -1221,13 +823,11 @@ var ExitCode;
/** /**
* Sets env variable for this action and future actions in the job * Sets env variable for this action and future actions in the job
* @param name the name of the variable to set * @param name the name of the variable to set
* @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify * @param val the value of the variable
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function exportVariable(name, val) { function exportVariable(name, val) {
const convertedVal = command_1.toCommandValue(val); process.env[name] = val;
process.env[name] = convertedVal; command_1.issueCommand('set-env', { name }, val);
command_1.issueCommand('set-env', { name }, convertedVal);
} }
exports.exportVariable = exportVariable; exports.exportVariable = exportVariable;
/** /**
@@ -1266,22 +866,12 @@ exports.getInput = getInput;
* Sets the value of an output. * Sets the value of an output.
* *
* @param name name of the output to set * @param name name of the output to set
* @param value value to store. Non-string values will be converted to a string via JSON.stringify * @param value value to store
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) { function setOutput(name, value) {
command_1.issueCommand('set-output', { name }, value); command_1.issueCommand('set-output', { name }, value);
} }
exports.setOutput = setOutput; 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 // Results
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@@ -1298,13 +888,6 @@ exports.setFailed = setFailed;
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// Logging Commands // 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 * Writes debug message to user log
* @param message debug message * @param message debug message
@@ -1315,18 +898,18 @@ function debug(message) {
exports.debug = debug; exports.debug = debug;
/** /**
* Adds an error issue * Adds an error issue
* @param message error issue message. Errors will be converted to string via toString() * @param message error issue message
*/ */
function error(message) { function error(message) {
command_1.issue('error', message instanceof Error ? message.toString() : message); command_1.issue('error', message);
} }
exports.error = error; exports.error = error;
/** /**
* Adds an warning issue * Adds an warning issue
* @param message warning issue message. Errors will be converted to string via toString() * @param message warning issue message
*/ */
function warning(message) { function warning(message) {
command_1.issue('warning', message instanceof Error ? message.toString() : message); command_1.issue('warning', message);
} }
exports.warning = warning; exports.warning = warning;
/** /**
@@ -1384,9 +967,8 @@ exports.group = group;
* Saves state for current action, the state can only be retrieved by this action's post job execution. * 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 name name of the state to store
* @param value value to store. Non-string values will be converted to a string via JSON.stringify * @param value value to store
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function saveState(name, value) { function saveState(name, value) {
command_1.issueCommand('save-state', { name }, value); command_1.issueCommand('save-state', { name }, value);
} }
@@ -1419,222 +1001,6 @@ 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: /***/ 986:
/***/ (function(__unusedmodule, exports, __webpack_require__) { /***/ (function(__unusedmodule, exports, __webpack_require__) {
@@ -1649,15 +1015,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); 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 }); Object.defineProperty(exports, "__esModule", { value: true });
const tr = __importStar(__webpack_require__(9)); const tr = __webpack_require__(9);
/** /**
* Exec a command. * Exec a command.
* Output will be streamed to the live console. * Output will be streamed to the live console.

BIN
images/action-preview.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

3723
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,12 +5,13 @@
"description": "TypeScript template action", "description": "TypeScript template action",
"main": "dist/main.js", "main": "dist/main.js",
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=10.0.0"
}, },
"scripts": { "scripts": {
"build": "ncc build src/main.ts -o dist --watch", "build:dev": "tsc",
"build-docker": "docker build --tag action .", "build:production": "ncc build src/main.ts -o dist",
"run-docker": "act --workflows ./debug/ --secret username=UserNameHere --secret password=PasswordHere" "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"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -24,16 +25,16 @@
"author": "SamKirkland", "author": "SamKirkland",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "1.2.4", "@actions/core": "1.2.0",
"@actions/exec": "1.0.4" "@actions/exec": "1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "25.2.1", "@types/jest": "24.0.23",
"@types/node": "12.12.8", "@types/node": "12.12.8",
"@zeit/ncc": "0.22.1", "@zeit/ncc": "0.20.5",
"jest": "25.5.3", "jest": "24.9.0",
"jest-circus": "25.5.3", "jest-circus": "24.9.0",
"ts-jest": "25.4.0", "ts-jest": "24.1.0",
"typescript": "3.8.3" "typescript": "3.7.2"
} }
} }

View File

@@ -1,49 +1,23 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import fs from 'fs';
import { promisify } from 'util';
import { IActionArguments } from './types'; import { IActionArguments } from './types';
const writeFileAsync = promisify(fs.writeFile);
const errorDeploying = "⚠️ Error deploying";
async function run() { async function run() {
const userArguments = getUserArguments();
try { try {
const userArguments = getUserArguments();
await configureHost(userArguments);
await syncFiles(userArguments); await syncFiles(userArguments);
console.log("✅ Deploy Complete"); console.log("✅ Deploy Complete");
} }
catch (error) { catch (error) {
console.error(errorDeploying); console.error("⚠️ Error deploying");
core.setFailed(error.message); core.setFailed(error.message);
} }
} }
run(); 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 { function getUserArguments(): IActionArguments {
return { return {
@@ -51,8 +25,7 @@ function getUserArguments(): IActionArguments {
ftp_username: core.getInput("ftp-username", { required: true }), ftp_username: core.getInput("ftp-username", { required: true }),
ftp_password: core.getInput("ftp-password", { required: true }), ftp_password: core.getInput("ftp-password", { required: true }),
local_dir: withDefault(core.getInput("local-dir"), "./"), 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"), "")
}; };
} }
@@ -70,22 +43,12 @@ function withDefault(value: string, defaultValue: string) {
async function syncFiles(args: IActionArguments) { async function syncFiles(args: IActionArguments) {
try { try {
await core.group("Uploading files", async () => { await core.group("Uploading files", async () => {
return await exec.exec( 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}`);
"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) { catch (error) {
console.error("⚠️ Failed to upload files");
core.setFailed(error.message); core.setFailed(error.message);
throw error;
} }
} }

View File

@@ -8,9 +8,6 @@ export interface IActionArguments {
/** @default "" */ /** @default "" */
gitFtpArgs: string | undefined; gitFtpArgs: string | undefined;
/** @default "" */
knownHosts: string | undefined;
} }
/** /**
@@ -25,7 +22,7 @@ export enum gitFTPExitCode {
ErrorWhileDownloading = 5, ErrorWhileDownloading = 5,
UnknownProtocol = 6, UnknownProtocol = 6,
RemoteLocked = 7, RemoteLocked = 7,
GitRelatedError = 8, NotAGitProject = 8,
PreFTPPushHookFailed = 9, PreFTPPushHookFailed = 9,
LocalFileOperationFailed = 10 LocalFileOperationFailed = 10
} }

View File

@@ -1,6 +1,6 @@
# Migrating from v2 to v3 # Migrating from v2 to v3
`uses: actions/checkout@v2.1.0` must now have the option `fetch-depth: 2` `uses: actions/checkout@master` must now have the option `fetch-depth: 2`
Without the `fetch-depth` option diffs cannot be calculated and all files will be uploaded. Without the `fetch-depth` option diffs cannot be calculated and all files will be uploaded.