Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 6253555
Showing
6 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.DS_Store | ||
Thumbs.db | ||
node_modules | ||
.ftppass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.npmignore | ||
.ftppass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Copyright (c) 2012 Damon Oehlman <damon.oehlman@sidelab.com> | ||
|
||
Permission is hereby granted, free of charge, to any person | ||
obtaining a copy of this software and associated documentation | ||
files (the "Software"), to deal in the Software without | ||
restriction, including without limitation the rights to use, | ||
copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the | ||
Software is furnished to do so, subject to the following | ||
conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# grunt-ftp-deploy | ||
|
||
This is a [grunt](https://github.com/gruntjs/grunt) task for code deployment over the _ftp_ protocol. | ||
|
||
These days _git_ is not only our goto code management tool but in many cases our deployment tool as well. But there are many cases where _git_ is not really fit for deployment: | ||
|
||
- we deploy to servers with only _ftp_ access | ||
- the production code is a result of a build process producing files that we do not necessarily track with _git_ | ||
|
||
This is why a _grunt_ task like this would be very useful. | ||
|
||
For simplicity purposes this task avoids deleting any files and it is not trying to do any size or time stamp comparison. It simply transfers all the files (and folder structure) from your dev location to a location on your server. | ||
|
||
## Usage | ||
|
||
To use this task you will need to include the following configuration in your _grunt_ file: | ||
|
||
```json | ||
ftp-deploy: { | ||
build: { | ||
auth: { | ||
host: 'server.com', | ||
port: 21, | ||
user: 'myUserName' | ||
}, | ||
src: 'build', | ||
dest: '/path/to/destination/folder' | ||
} | ||
} | ||
``` | ||
|
||
and load the task: | ||
|
||
```javascript | ||
grunt.loadNpmTasks('grunt-ftp-deploy'); | ||
``` | ||
|
||
The parameters in our configuration are: | ||
|
||
- **host** - the name or the IP address of the server we are deploying to | ||
- **port** - the port that the _ftp_ service is running on | ||
- **user** - the username we authenticate ourselves with | ||
- **src** - the source location, the local folder that we are transferring to the server | ||
- **dest** - the destination location, the folder on the server we are deploying to | ||
|
||
## Password management | ||
|
||
There are two ways we can provide the password for the _ftp_ authentication: | ||
|
||
- as an optional argument to the task | ||
- stored in a text file named `.ftppass` | ||
|
||
The first method takes precedence over the second one. | ||
|
||
## Dependencies | ||
|
||
This task is built by taking advantage of the great work of Sergi Mansilla and his [jsftp](https://github.com/sergi/jsftp) _node.js_ module. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "grunt-ftp-deploy", | ||
"description": "Deployment over FTP", | ||
"version": "0.0.1", | ||
"homepage": "https://github.com/zonak/grunt-ftp-deploy", | ||
"author": { | ||
"name": "Zoran Nakev", | ||
"email": "zoran.nakev@gmail.com" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/zonak/grunt-ftp-deploy.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/zonak/grunt-ftp-deploy/issues" | ||
}, | ||
"licenses": [ | ||
{ | ||
"type": "MIT", | ||
"url": "https://github.com/zonak/grunt-ftp-deploy/blob/master/LICENSE-MIT" | ||
} | ||
], | ||
"engines": { | ||
"node": "*" | ||
}, | ||
"dependencies": { | ||
"jsftp": "0.4.x", | ||
"grunt": "0.3.x" | ||
}, | ||
"keywords": [ | ||
"gruntplugin" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// | ||
// Grunt Task File | ||
// --------------- | ||
// | ||
// Task: FTP Deploy | ||
// Description: Deploy code over FTP | ||
// Dependencies: jsftp | ||
// | ||
|
||
module.exports = function(grunt) { | ||
|
||
var async = grunt.util.async; | ||
var log = grunt.log; | ||
var _ = grunt.util._; | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var Ftp = require('jsftp'); | ||
|
||
var toTransfer; | ||
var ftp; | ||
var localRoot; | ||
var remoteRoot; | ||
var currPath; | ||
var user; | ||
var pass; | ||
|
||
// A method for parsing the source location and storing the information into a suitably formated object | ||
function dirParseSync(startDir, result) { | ||
var files; | ||
var i; | ||
var tmpPath; | ||
var currFile; | ||
|
||
// initialize the `result` object if it is the first iteration | ||
if (result === undefined) { | ||
result = {'/': []}; | ||
} | ||
|
||
// check if `startDir` is a valid location | ||
if (!fs.existsSync(startDir)) { | ||
grunt.warn(startDir + 'is not an existing location'); | ||
} | ||
|
||
// iterate throught the contents of the `startDir` location of the current iteration | ||
files = fs.readdirSync(startDir); | ||
for (i = 0; i < files.length; i++) { | ||
currFile = fs.lstatSync(startDir + path.sep + files[i]); | ||
if (currFile.isDirectory()) { | ||
tmpPath = path.relative(localRoot, startDir + path.sep + files[i]); | ||
if (!_.has(result, tmpPath)) { | ||
result[tmpPath] = []; | ||
} | ||
dirParseSync(startDir + path.sep + files[i], result); | ||
} else { | ||
tmpPath = path.relative(localRoot, startDir); | ||
if (!tmpPath.length) { | ||
tmpPath = '/'; | ||
} | ||
result[tmpPath].push(files[i]); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
// A method for changing the remote working directory and creating one if it doesn't already exist | ||
function ftpCwd(inPath, cb) { | ||
ftp.raw.cwd(inPath, function(err) { | ||
if(err){ | ||
ftp.raw.mkd(inPath, function(err) { | ||
if(err) { | ||
log.error('Error creating new remote folder ' + inPath + ' --> ' + err); | ||
cb(err); | ||
} else { | ||
log.ok('New remote folder created ' + inPath.yellow); | ||
ftpCwd(inPath, cb); | ||
} | ||
}); | ||
} else { | ||
cb(null); | ||
} | ||
}); | ||
} | ||
|
||
// A method for uploading a single file | ||
function ftpPut(inFilename, cb) { | ||
ftp.put(inFilename, grunt.file.read(localRoot + path.sep + currPath + path.sep + inFilename), function(err) { | ||
if(err) { | ||
log.error('Cannot upload file: ' + inFilename + ' --> ' + err); | ||
cb(err); | ||
} else { | ||
log.ok('Uploaded file: ' + inFilename.green + ' to: ' + currPath.yellow); | ||
cb(null); | ||
} | ||
}); | ||
} | ||
|
||
// A method that processes a location - changes to a fodler and uploads all respective files | ||
function ftpProcessLocation (inPath, cb) { | ||
if (!toTransfer[inPath]) { | ||
cb(new Error('Data for ' + inPath + ' not found')); | ||
} | ||
|
||
ftpCwd(remoteRoot + path.sep + inPath, function (err) { | ||
var files; | ||
|
||
if (err) { | ||
grunt.warn('Could not switch to remote folder!'); | ||
} | ||
|
||
currPath = inPath; | ||
files = toTransfer[inPath]; | ||
|
||
async.forEach(files, ftpPut, function (err) { | ||
if (err) { | ||
grunt.warn('Failed uploading files!'); | ||
} | ||
cb(null); | ||
}); | ||
}); | ||
} | ||
|
||
// The main grunt task | ||
grunt.registerMultiTask('ftp-deploy', 'Deploy code over FTP', function() { | ||
var done = this.async(); | ||
|
||
// Init | ||
ftp = new Ftp({ | ||
host: this.data.auth.host, | ||
port: this.data.auth.port | ||
}); | ||
localRoot = this.file.src[0]; | ||
remoteRoot = this.file.dest; | ||
user = this.data.auth.user; | ||
pass = null; | ||
ftp.useList = true; | ||
toTransfer = dirParseSync(localRoot); | ||
|
||
// If there is a password provided as an argument | ||
if (this.args.length) { | ||
pass = this.args[0]; | ||
} else if (fs.existsSync('.ftppass')) { // If there is a `.ftppass` file found in the root of the project get the password from there | ||
pass = grunt.file.read('.ftppass'); | ||
} | ||
|
||
// Checking if we have all the necessary credentilas before we proceed | ||
if (user == null || pass == null) { | ||
grunt.warn('Username or Password not found!'); | ||
} | ||
|
||
// Authentication and main processing of files | ||
ftp.auth(user, pass, function(err) { | ||
var locations = _.keys(toTransfer); | ||
if (err) { | ||
grunt.warn('Authentication ' + err); | ||
} | ||
|
||
// Iterating through all location from the `localRoot` in parallel | ||
async.forEachSeries(locations, ftpProcessLocation, function() { | ||
ftp.raw.quit(function(err) { | ||
if (err) { | ||
log.error(err); | ||
} else { | ||
log.ok('FTP upload done!'); | ||
} | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
if (grunt.errors) { | ||
return false; | ||
} | ||
}); | ||
}; |