├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── bin ├── deploy-ftp ├── deploy-sftp └── parse.js ├── examples ├── basic │ ├── deploy.js │ └── dist │ │ ├── css │ │ ├── reset.css │ │ └── reset.map │ │ ├── images │ │ └── logo.png │ │ ├── index.html │ │ └── js │ │ ├── app.js │ │ ├── app.map │ │ ├── vendor.js │ │ └── vendor.map └── webpack │ ├── deploy.js │ ├── dist │ ├── css │ │ ├── reset.css │ │ └── reset.map │ ├── images │ │ └── logo.png │ ├── index.html │ └── js │ │ ├── app.js │ │ ├── app.map │ │ ├── vendor.js │ │ └── vendor.map │ └── webpack.conf.js ├── lib ├── deploy.js ├── ftp.js ├── index.js ├── sftp.js └── util.js ├── package.json ├── pic.png ├── plugins ├── ftp-webpack-plugin.js ├── sftp-webpack-plugin.js └── webpack-plugin.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | # remove any whitespace characters preceding newline characters 10 | trim_trailing_whitespace = true 11 | # ensure file ends with a newline when saving 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Xiao Yann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Deploy-kit 2 | 3 | A nice deployment tool supports ftp and sftp. Used directly or with built-in plugins, such as webpack-plugin. 4 | 5 | ![image](https://raw.githubusercontent.com/xiaoyann/deploy-kit/master/pic.png) 6 | 7 | ``` 8 | yarn add deploy-kit --dev 9 | ``` 10 | 11 | ## Basic usage 12 | 13 | ```js 14 | const client = require('deploy-kit') 15 | 16 | client.sftp({ 17 | // sever account, address, port 18 | server: 'user:pwd@10.133.3.4:22', 19 | // deploy all files in the directory 20 | workspace: __dirname + '/dist', 21 | // ignore the matched files (glob pattern: https://github.com/isaacs/node-glob#glob-primer) 22 | // support array of glob pattern 23 | ignore: '**/*.map', 24 | // where the files are placed on the server 25 | deployTo: '/data1/htdocs/testapp', 26 | // you can specify different place for each file 27 | rules: [ 28 | { 29 | test: /dist\/(.*)$/, 30 | // $1, $2... means the parenthesized substring matches 31 | // [$n] will be replaced with matched string 32 | dest: 'public/static/[$1]' 33 | }, 34 | { 35 | test: /views\/((?:[^/]+\/)*?[^\/]+).html$/, 36 | dest: 'app/views/[$1].phtml' 37 | } 38 | ] 39 | }) 40 | .exec() 41 | ``` 42 | 43 | or use ftp 44 | 45 | ```js 46 | client.ftp({ 47 | ... 48 | }) 49 | .exec() 50 | ``` 51 | 52 | #### options: 53 | 54 | option | type | description 55 | -------- | ----- | --------- 56 | server | string | server info includes username, password, address, and port. e.g. `user:pwd@10.113.8.5:22` 57 | workspace | string | deploy all files in the directory 58 | ignore | string or array of string | ignore the matched files (glob pattern: https://github.com/isaacs/node-glob#glob-primer) 59 | deployTo | string | where the files are placed. 60 | rules | array of rule | rule use to speicify different place for each file. each rule has a `test` and a `dest` property. 61 | 62 | #### about rule: 63 | 64 | ```js 65 | { 66 | test: /dist\/(.*)$/, 67 | // $1, $2... means the parenthesized substring matches 68 | // [$n] will be replaced with matched string 69 | dest: 'public/static/[$1]' 70 | } 71 | ``` 72 | 73 | `test` property is a RegExp pattern, use to match specified file with realpath of the file. `dest` property is the custom filename of matched file. you can extract any part from realpath as a part of `dest` by parenthesis. 74 | 75 | ## Command Line Interface(CLI) 76 | 77 | ``` 78 | $ ./bin/deploy-sftp --server user:pwd@server_address:port --ignore **/*.map ./dist /data1/htdocs/testapp 79 | ``` 80 | 81 | ``` 82 | $ ./bin/deploy-ftp ... 83 | ``` 84 | 85 | #### cli options: 86 | ``` 87 | 88 | Usage: deploy-sftp [options] [workspace] [deployTo] 89 | 90 | 91 | Options: 92 | 93 | -V, --version output the version number 94 | -c, --config use configuration from this file 95 | -s, --server
server account, address. (e.g. user:pwd@address:port) 96 | -i, --ignore ignore the matched files 97 | -h, --help output usage information 98 | 99 | Examples: 100 | 101 | // use configuration from a file 102 | $ deploy-sftp --config deploy.js 103 | // deploy files in ./dist to /data1/htdocs/testapp on 10.13.1.2 104 | $ deploy-sftp -s user:pwd@10.13.1.2:22 --i *.map ./dist /data1/htdocs/testapp 105 | 106 | version: 3.1.0 107 | ``` 108 | 109 | #### using config file 110 | 111 | You can use configuration file instead of cli args. Just create a `deploy.js` file in the root directory of your project and exports your configuration like this: 112 | 113 | ```js 114 | module.exports = { 115 | server: '', 116 | workspace: '', 117 | ignore: '', 118 | deployTo: '', 119 | rules: [] 120 | } 121 | ``` 122 | 123 | Runing directly without any arg. 124 | 125 | ``` 126 | $ ./bin/deploy-sftp 127 | ``` 128 | 129 | If you prefer to place the configuration file in another place, you can use `-c, --config ` option like this: 130 | 131 | ``` 132 | $ ./bin/deploy-sftp --config ./config/your_conf.js 133 | ``` 134 | 135 | ## Used with plugins 136 | 137 | each plugin can called like this: 138 | 139 | ```js 140 | new DeployPlugin([config]) 141 | ``` 142 | 143 | `config` is optional. if you omitted the `config`, Deploy-kit will automatic load the `deploy.js` from the `process.cwd()`. 144 | 145 | 146 | * sftp-webpack-plugin 147 | 148 | 149 | ```js 150 | const DeployPlugin = require('deploy-kit/plugins/sftp-webpack-plugin') 151 | 152 | // webpack configuration 153 | moudle.exports = { 154 | ... 155 | plugins: [ 156 | new DeployPlugin() 157 | ] 158 | } 159 | ``` 160 | > todos: 161 | 162 | * sftp-gulp-plugin 163 | * ftp-gulp-plugin 164 | * sftp-fis-plugin 165 | 166 | ## How to write a plugin 167 | 168 | > todo 169 | -------------------------------------------------------------------------------- /bin/deploy-ftp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const client = require('../lib') 4 | const config = require('./parse') 5 | 6 | // start deploy 7 | client.ftp(config).exec() 8 | -------------------------------------------------------------------------------- /bin/deploy-sftp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const client = require('../lib') 4 | const config = require('./parse') 5 | 6 | // start deploy 7 | client.sftp(config).exec() 8 | -------------------------------------------------------------------------------- /bin/parse.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const program = require('commander') 4 | const client = require('../lib') 5 | const package = require('../package.json') 6 | const cwd = process.cwd() 7 | 8 | 9 | // display usage 10 | program 11 | .version(package.version) 12 | .usage('[options] [workspace] [deployTo]') 13 | .option('-c, --config ', 'use configuration from this file') 14 | .option('-s, --server
', 'server account, address. (e.g. user:pwd@address:port)') 15 | .option('-i, --ignore ', 'ignore the matched files') 16 | 17 | 18 | // display examples 19 | program.on('--help', function(){ 20 | console.log() 21 | console.log(' Examples:') 22 | console.log() 23 | console.log(' // use configuration from a file') 24 | console.log(' $ deploy --config deploy.js') 25 | console.log(' // deploy files in ./dist to /data1/htdocs/testapp on 10.13.1.2') 26 | console.log(' $ deploy -s user:pwd@10.13.1.2:22 --i *.map ./dist /data1/htdocs/testapp') 27 | console.log() 28 | console.log(' version: ' + package.version) 29 | }) 30 | 31 | 32 | program.parse(process.argv) 33 | 34 | 35 | const config = { 36 | // server account, address, port 37 | // e.g. username:password@server_address:port 38 | server: '', 39 | // deploy all files in the directory 40 | workspace: '', 41 | // ignore the matched files 42 | // e.g. dist/**/*.js' 43 | ignore: '', 44 | // destination 45 | // e.g. /data1/htdocs/mizar/haha 46 | deployTo: '', 47 | rules: [ 48 | // { 49 | // test: /dist\/(.*)$/, 50 | // release: 'public/static/[$1]' 51 | // }, 52 | ] 53 | } 54 | 55 | 56 | // read configuration from local file 57 | function readConfig() { 58 | let options = {} 59 | let cFile = '' 60 | 61 | if (program.config) { 62 | cFile = path.resolve(cwd, program.config) 63 | if (fs.existsSync(cFile)) { 64 | Object.assign(config, require(cFile)) 65 | } else { 66 | console.warn(`Cannot find configuration file ${program.config}`) 67 | process.exit() 68 | } 69 | } else { 70 | cFile = path.resolve(cwd, 'deploy.js') 71 | } 72 | 73 | if (fs.existsSync(cFile)) { 74 | options = require(cFile) 75 | } 76 | 77 | ;['server', 'ignore'].forEach(function(name) { 78 | if (typeof program[name] !== 'undefined') { 79 | options[name] = program[name] 80 | } 81 | }) 82 | 83 | if (program.args[0]) { 84 | options.workspace = path.resolve(cwd, program.args[0]) 85 | } 86 | 87 | if (program.args[1]) { 88 | options.deployTo = program.args[1] 89 | } 90 | 91 | return options 92 | } 93 | 94 | 95 | Object.assign(config, readConfig()) 96 | 97 | 98 | // validate required options 99 | ;['server', 'workspace', 'deployTo'].some(function(name) { 100 | if (!config[name]) { 101 | console.log(`${name} required, please check your configuration.`) 102 | process.exit() 103 | } 104 | }) 105 | 106 | module.exports = config 107 | -------------------------------------------------------------------------------- /examples/basic/deploy.js: -------------------------------------------------------------------------------- 1 | const client = require('deploy-kit') 2 | 3 | client.sftp({ 4 | server: 'user:pwd@server_address:port', 5 | workspace: __dirname + '/dist', 6 | deployTo: '/data1/htdocs/test-app', 7 | ignore: 'dist/**/*.map', 8 | rules: [ 9 | { 10 | test: /dist\/(.*)$/, 11 | dest: 'public/static/[$1]' 12 | }, 13 | { 14 | test: /views\/((?:[^/]+\/)*?[^\/]+).html$/, 15 | dest: 'app/views/[$1].phtml' 16 | } 17 | ] 18 | }) 19 | .exec() 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/basic/dist/css/reset.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/basic/dist/css/reset.css -------------------------------------------------------------------------------- /examples/basic/dist/css/reset.map: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/basic/dist/css/reset.map -------------------------------------------------------------------------------- /examples/basic/dist/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/basic/dist/images/logo.png -------------------------------------------------------------------------------- /examples/basic/dist/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/basic/dist/index.html -------------------------------------------------------------------------------- /examples/basic/dist/js/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/basic/dist/js/app.js -------------------------------------------------------------------------------- /examples/basic/dist/js/app.map: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/basic/dist/js/app.map -------------------------------------------------------------------------------- /examples/basic/dist/js/vendor.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/basic/dist/js/vendor.js -------------------------------------------------------------------------------- /examples/basic/dist/js/vendor.map: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/basic/dist/js/vendor.map -------------------------------------------------------------------------------- /examples/webpack/deploy.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: 'user:pwd@server_address:port', 3 | workspace: __dirname + '/dist', 4 | deployTo: '/data1/htdocs/test-app', 5 | ignore: 'dist/**/*.map', 6 | rules: [ 7 | { 8 | test: /dist\/(.*)$/, 9 | dest: 'public/static/[$1]' 10 | }, 11 | { 12 | test: /views\/((?:[^/]+\/)*?[^\/]+).html$/, 13 | dest: 'app/views/[$1].phtml' 14 | } 15 | ] 16 | } 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/webpack/dist/css/reset.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/webpack/dist/css/reset.css -------------------------------------------------------------------------------- /examples/webpack/dist/css/reset.map: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/webpack/dist/css/reset.map -------------------------------------------------------------------------------- /examples/webpack/dist/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/webpack/dist/images/logo.png -------------------------------------------------------------------------------- /examples/webpack/dist/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/webpack/dist/index.html -------------------------------------------------------------------------------- /examples/webpack/dist/js/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/webpack/dist/js/app.js -------------------------------------------------------------------------------- /examples/webpack/dist/js/app.map: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/webpack/dist/js/app.map -------------------------------------------------------------------------------- /examples/webpack/dist/js/vendor.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/webpack/dist/js/vendor.js -------------------------------------------------------------------------------- /examples/webpack/dist/js/vendor.map: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/examples/webpack/dist/js/vendor.map -------------------------------------------------------------------------------- /examples/webpack/webpack.conf.js: -------------------------------------------------------------------------------- 1 | const DeployPlugin = require('deploy-kit/plugins/sftp-webpack-plugin') 2 | 3 | const webpackConfig = { 4 | plugins: [ 5 | new DeployPlugin() 6 | ] 7 | } 8 | 9 | -------------------------------------------------------------------------------- /lib/deploy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Deploy is a base class. 3 | * This class must be inherited and implement the initClient, upload and end method before use. 4 | */ 5 | 6 | const fs = require('fs') 7 | const path = require('path') 8 | const minimatch = require("minimatch") 9 | const util = require('./util') 10 | 11 | function upload(client, files, callback) { 12 | if (files.length === 0) { 13 | return callback(null) 14 | } 15 | const file = files.shift() 16 | client.upload(file, function(err) { 17 | if (err) { 18 | util.failure(file) 19 | callback(err) 20 | } else { 21 | util.success(file) 22 | if (files.length > 0) { 23 | upload(client, files, callback) 24 | } else { 25 | callback(null) 26 | } 27 | } 28 | }) 29 | } 30 | 31 | function customDest(file, rule) { 32 | const pattern = rule.test 33 | const matched = file.filepath.match(pattern) 34 | if (matched) { 35 | file.dest = rule.dest.replace(/\[\$(\d+)\]/g, function(m, idx) { 36 | return matched[idx] 37 | }) 38 | } 39 | } 40 | 41 | function takeServerInfo(info) { 42 | const matched = /^([^\:]+)\:([^\:]+)@(.*?)(?:\:(\d+))?$/.exec(info) 43 | const options = { 44 | username: matched[1], 45 | password: matched[2], 46 | host: matched[3] 47 | } 48 | if (matched[4]) { 49 | options.port = matched[4] 50 | } 51 | return options 52 | } 53 | 54 | function readConfig() { 55 | let options = {} 56 | let cFile = path.resolve(process.cwd(), 'deploy.js') 57 | if (fs.existsSync(cFile)) { 58 | options = require(cFile) 59 | } 60 | return options 61 | } 62 | 63 | 64 | // Example: 65 | // new Deploy({ 66 | // server: 'xiaofeng:xiaofeng@10.13.3.4:22', 67 | // workspace: __dirname + '/dist', 68 | // ignore: 'dist/**/*.js', 69 | // deployTo: '/data1/htdocs/mizar/haha', 70 | // rules: [ 71 | // { 72 | // test: /dist\/(.*)$/, 73 | // release: 'public/static/[$1]' 74 | // }, 75 | // { 76 | // test: /dist\/(.*)$/, 77 | // release: 'public/static/[$1]' 78 | // } 79 | // ] 80 | // }) 81 | function Deploy(options) { 82 | if (!options) { 83 | options = readConfig() 84 | } 85 | 86 | this.workspace = options.workspace 87 | this.deployTo = options.deployTo 88 | this.ignore = util.isString(options.ignore) ? [options.ignore] : options.ignore || [] 89 | this.rules = [] 90 | 91 | if (options.rules) { 92 | options.rules.map(this.match.bind(this)) 93 | } 94 | 95 | this.initClient(takeServerInfo(options.server)) 96 | } 97 | 98 | // 匹配文件 99 | Deploy.prototype.match = function(rule) { 100 | this.rules.push({ 101 | test: rule.test, 102 | dest: path.join(this.deployTo, rule.dest) 103 | }) 104 | } 105 | 106 | // 开始上传 107 | // @file.filename 本地文件名称 108 | // @file.filepath 本地文件真实路径 109 | // @file.content 文件内容 110 | // @file.dest 远程文件名称 111 | Deploy.prototype.exec = function(files) { 112 | const self = this 113 | 114 | function callback(err) { 115 | self.end() 116 | if (err) throw err 117 | } 118 | 119 | function startUpload(files) { 120 | const startTime = Date.now() 121 | upload(self, self.process(files), function(err) { 122 | const times = (Date.now() - startTime) / 1000 123 | util.notice(`Times: ${times}s Files: ${files.length}`) 124 | callback(err) 125 | }) 126 | } 127 | 128 | if (files) { 129 | // 过滤被忽略的文件 130 | files = files.filter(function(file) { 131 | return !self.ignore.some(function(pattern) { 132 | return minimatch(file.filename, pattern) 133 | }) 134 | }) 135 | startUpload(files) 136 | } 137 | else { 138 | util.pickFiles(['**'], { 139 | cwd: this.workspace, 140 | nodir: true, 141 | realpath: true, 142 | ignore: this.ignore 143 | }, function(err, files) { 144 | if (err) { 145 | callback(err) 146 | } else { 147 | startUpload(files) 148 | } 149 | }) 150 | } 151 | } 152 | 153 | // 格式化文件 154 | Deploy.prototype.process = function(files) { 155 | const rules = this.rules 156 | const workspace = this.workspace 157 | const deployTo = this.deployTo 158 | 159 | return files.map(function(file) { 160 | let filename 161 | 162 | if (typeof file === 'string') { 163 | filename = file 164 | file = {} 165 | } else { 166 | filename = file.filename 167 | } 168 | 169 | filename = filename.replace(workspace, '') 170 | 171 | let filepath = path.join(workspace, filename) 172 | let dest = path.join(deployTo, filename) 173 | 174 | file.filename = filename 175 | file.filepath = filepath 176 | file.dest = dest 177 | 178 | // 应用自定义规则,修改远程文件名称 179 | rules.forEach(function(rule) { 180 | customDest(file, rule) 181 | }) 182 | 183 | return file 184 | }) 185 | } 186 | 187 | module.exports = Deploy 188 | -------------------------------------------------------------------------------- /lib/ftp.js: -------------------------------------------------------------------------------- 1 | const Ftp = require('ftp') 2 | const Deploy = require('./deploy') 3 | const inherits = require('util').inherits 4 | 5 | inherits(FtpDeploy, Deploy) 6 | 7 | function FtpDeploy(options) { 8 | Deploy.call(this, options) 9 | } 10 | 11 | FtpDeploy.prototype.initClient = function(options) { 12 | this.client = new Ftp() 13 | this.client.connect({ 14 | user: options.username, 15 | password: options.password, 16 | host: options.host, 17 | port: options.port || 21 18 | }) 19 | } 20 | 21 | /** 22 | * file: { 23 | * filename: '', 24 | * filepath: '', 25 | * content: '', 26 | * dest: '' 27 | * } 28 | */ 29 | FtpDeploy.prototype.upload = function(file, callback) { 30 | this.client.put(typeof file.content !== 'undefined' ? file.content : file.filepath, file.dest, callback) 31 | } 32 | 33 | FtpDeploy.prototype.end = function() { 34 | this.client.end() 35 | } 36 | 37 | module.exports = FtpDeploy 38 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const Sftp = require('./sftp') 2 | const Ftp = require('./ftp') 3 | 4 | /** 5 | * usage: 6 | * const deploy = require('deploy-kit') 7 | * deploy.sftp({ 8 | * ... 9 | * }) 10 | */ 11 | 12 | exports.sftp = function(options) { 13 | return new Sftp(options) 14 | } 15 | 16 | exports.ftp = function(options) { 17 | return new Ftp(options) 18 | } 19 | -------------------------------------------------------------------------------- /lib/sftp.js: -------------------------------------------------------------------------------- 1 | const Scp2 = require('scp2').Client 2 | const Deploy = require('./deploy') 3 | const inherits = require('util').inherits 4 | 5 | inherits(SftpDeploy, Deploy) 6 | 7 | function SftpDeploy(options) { 8 | Deploy.call(this, options) 9 | } 10 | 11 | SftpDeploy.prototype.initClient = function(options) { 12 | this.client = new Scp2() 13 | this.client.defaults(options) 14 | } 15 | 16 | SftpDeploy.prototype.upload = function(file, callback) { 17 | if (typeof file.content !== 'undefined') { 18 | const options = { 19 | source: file.filepath, 20 | destination: file.dest, 21 | content: file.content, 22 | attrs: file.stats || {} 23 | } 24 | this.client.write(options, callback) 25 | } else { 26 | this.client.upload(file.filepath, file.dest, callback) 27 | } 28 | } 29 | 30 | SftpDeploy.prototype.end = function() { 31 | this.client.close() 32 | } 33 | 34 | module.exports = SftpDeploy 35 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const glob = require('glob') 3 | const async = require('async') 4 | 5 | exports.isString = function(obj) { 6 | return typeof obj === 'string' 7 | } 8 | 9 | // match files by glob 10 | // (https://github.com/isaacs/node-glob) 11 | exports.pickFiles = function(patterns, options, callback) { 12 | async.map( 13 | patterns, 14 | function(pattern, next) { 15 | glob(pattern, options, next) 16 | }, 17 | function(err, results) { 18 | if (err) { 19 | callback(err) 20 | } else { 21 | const files = results.reduce(function(files, item) { 22 | return files.concat(item) 23 | }) 24 | callback(null, files) 25 | } 26 | } 27 | ) 28 | } 29 | 30 | // display success message when upload success 31 | exports.success = function(file) { 32 | file.to = file.to || file.dest 33 | var desc = chalk.cyan(`${file.filename} ${chalk.white('===>')} ${file.to}`) 34 | console.log(chalk.greenBright('upload success :'), desc) 35 | } 36 | 37 | // display failure message when upload failure 38 | exports.failure = function(file) { 39 | file.to = file.to || file.dest 40 | var desc = chalk.yellow(`${file.filename} ${chalk.white('===>')} ${file.to}`) 41 | console.log(chalk.redBright('upload failure :'), desc) 42 | } 43 | 44 | // display notice message 45 | exports.notice = function(message) { 46 | console.log(message) 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deploy-kit", 3 | "version": "3.5.0", 4 | "description": "A nice deployment tool supports ftp and sftp. Used directly or with built-in plugins, such as webpack-plugin.", 5 | "main": "lib/index.js", 6 | "bin": { 7 | "deploy-ftp": "bin/deploy-ftp", 8 | "deploy-sftp": "bin/deploy-sftp" 9 | }, 10 | "repository": "git+https://github.com/xiaoyann/deploy-kit.git", 11 | "keywords": [ 12 | "deploy", 13 | "ftp", 14 | "webpack", 15 | "sftp", 16 | "upload" 17 | ], 18 | "files": [ 19 | "bin", 20 | "lib", 21 | "plugins" 22 | ], 23 | "author": "xiaoyann", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/xiaoyann/deploy-kit/issues" 27 | }, 28 | "homepage": "https://github.com/xiaoyann/deploy-kit#readme", 29 | "dependencies": { 30 | "async": "^2.5.0", 31 | "chalk": "^2.0.1", 32 | "commander": "^2.11.0", 33 | "ftp": "^0.3.10", 34 | "glob": "^7.1.2", 35 | "minimatch": "^3.0.4", 36 | "scp2": "^0.5.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyann/deploy-kit/df1fe0ac2dba02d78661ffbbe7c92e4c23aaed03/pic.png -------------------------------------------------------------------------------- /plugins/ftp-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | const client = require('../lib') 2 | const inherits = require('util').inherits 3 | const DeployPlugin = require('./webpack-plugin') 4 | 5 | inherits(FtpDeployPlugin, DeployPlugin) 6 | 7 | function FtpDeployPlugin(options) { 8 | this.client = client.ftp(options) 9 | DeployPlugin.call(options) 10 | } 11 | 12 | module.exports = FtpDeployPlugin 13 | -------------------------------------------------------------------------------- /plugins/sftp-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | const client = require('../lib') 2 | const inherits = require('util').inherits 3 | const DeployPlugin = require('./webpack-plugin') 4 | 5 | inherits(SftpDeployPlugin, DeployPlugin) 6 | 7 | function SftpDeployPlugin(options) { 8 | this.client = client.sftp(options) 9 | DeployPlugin.call(options) 10 | } 11 | 12 | module.exports = SftpDeployPlugin 13 | -------------------------------------------------------------------------------- /plugins/webpack-plugin.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | 3 | function md5(content) { 4 | return crypto.createHash('md5').update(content).digest('hex') 5 | } 6 | 7 | const cacheStore = {} 8 | 9 | function DeployPlugin(options) {} 10 | 11 | DeployPlugin.prototype.apply = function(compiler) { 12 | const self = this 13 | compiler.plugin('done', function(stats) { 14 | const files = [] 15 | const assets = stats.compilation.assets 16 | 17 | Object.keys(assets).forEach(function(filename) { 18 | const file = assets[filename] 19 | const size = file.size() 20 | const source = file.source() 21 | const hash = md5(source) 22 | const cache = cacheStore[filename] 23 | if (cache !== hash) { 24 | files.push({ 25 | size: size, 26 | filename: filename, 27 | content: new Buffer(source, 'utf-8'), 28 | stats: {} 29 | }) 30 | cacheStore[filename] = hash 31 | } 32 | }) 33 | 34 | self.client.exec(files) 35 | }) 36 | } 37 | 38 | module.exports = DeployPlugin 39 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-styles@^3.1.0: 6 | version "3.1.0" 7 | resolved "http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750" 8 | dependencies: 9 | color-convert "^1.0.0" 10 | 11 | asn1@~0.2.0: 12 | version "0.2.3" 13 | resolved "http://registry.npm.taobao.org/asn1/download/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" 14 | 15 | async@^2.5.0: 16 | version "2.5.0" 17 | resolved "http://registry.npm.taobao.org/async/download/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" 18 | dependencies: 19 | lodash "^4.14.0" 20 | 21 | async@~0.9.0: 22 | version "0.9.2" 23 | resolved "http://registry.npm.taobao.org/async/download/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" 24 | 25 | balanced-match@^1.0.0: 26 | version "1.0.0" 27 | resolved "http://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 28 | 29 | brace-expansion@^1.1.7: 30 | version "1.1.8" 31 | resolved "http://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" 32 | dependencies: 33 | balanced-match "^1.0.0" 34 | concat-map "0.0.1" 35 | 36 | chalk@^2.0.1: 37 | version "2.0.1" 38 | resolved "http://registry.npm.taobao.org/chalk/download/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d" 39 | dependencies: 40 | ansi-styles "^3.1.0" 41 | escape-string-regexp "^1.0.5" 42 | supports-color "^4.0.0" 43 | 44 | color-convert@^1.0.0: 45 | version "1.9.0" 46 | resolved "http://registry.npm.taobao.org/color-convert/download/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" 47 | dependencies: 48 | color-name "^1.1.1" 49 | 50 | color-name@^1.1.1: 51 | version "1.1.2" 52 | resolved "http://registry.npm.taobao.org/color-name/download/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" 53 | 54 | commander@^2.11.0: 55 | version "2.11.0" 56 | resolved "http://registry.npm.taobao.org/commander/download/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" 57 | 58 | concat-map@0.0.1: 59 | version "0.0.1" 60 | resolved "http://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 61 | 62 | core-util-is@~1.0.0: 63 | version "1.0.2" 64 | resolved "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 65 | 66 | escape-string-regexp@^1.0.5: 67 | version "1.0.5" 68 | resolved "http://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 69 | 70 | fs.realpath@^1.0.0: 71 | version "1.0.0" 72 | resolved "http://registry.npm.taobao.org/fs.realpath/download/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 73 | 74 | ftp@^0.3.10: 75 | version "0.3.10" 76 | resolved "http://registry.npm.taobao.org/ftp/download/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" 77 | dependencies: 78 | readable-stream "1.1.x" 79 | xregexp "2.0.0" 80 | 81 | glob@^7.1.2: 82 | version "7.1.2" 83 | resolved "http://registry.npm.taobao.org/glob/download/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 84 | dependencies: 85 | fs.realpath "^1.0.0" 86 | inflight "^1.0.4" 87 | inherits "2" 88 | minimatch "^3.0.4" 89 | once "^1.3.0" 90 | path-is-absolute "^1.0.0" 91 | 92 | glob@~7.0.3: 93 | version "7.0.6" 94 | resolved "http://registry.npm.taobao.org/glob/download/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" 95 | dependencies: 96 | fs.realpath "^1.0.0" 97 | inflight "^1.0.4" 98 | inherits "2" 99 | minimatch "^3.0.2" 100 | once "^1.3.0" 101 | path-is-absolute "^1.0.0" 102 | 103 | has-flag@^2.0.0: 104 | version "2.0.0" 105 | resolved "http://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" 106 | 107 | inflight@^1.0.4: 108 | version "1.0.6" 109 | resolved "http://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 110 | dependencies: 111 | once "^1.3.0" 112 | wrappy "1" 113 | 114 | inherits@2, inherits@~2.0.1: 115 | version "2.0.3" 116 | resolved "http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 117 | 118 | isarray@0.0.1: 119 | version "0.0.1" 120 | resolved "http://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 121 | 122 | lodash@^4.14.0: 123 | version "4.17.4" 124 | resolved "http://registry.npm.taobao.org/lodash/download/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 125 | 126 | lodash@~4.11.1: 127 | version "4.11.2" 128 | resolved "http://registry.npm.taobao.org/lodash/download/lodash-4.11.2.tgz#d6b4338b110a58e21dae5cebcfdbbfd2bc4cdb3b" 129 | 130 | minimatch@^3.0.2, minimatch@^3.0.4: 131 | version "3.0.4" 132 | resolved "http://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 133 | dependencies: 134 | brace-expansion "^1.1.7" 135 | 136 | once@^1.3.0: 137 | version "1.4.0" 138 | resolved "http://registry.npm.taobao.org/once/download/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 139 | dependencies: 140 | wrappy "1" 141 | 142 | path-is-absolute@^1.0.0: 143 | version "1.0.1" 144 | resolved "http://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 145 | 146 | readable-stream@1.1.x: 147 | version "1.1.14" 148 | resolved "http://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 149 | dependencies: 150 | core-util-is "~1.0.0" 151 | inherits "~2.0.1" 152 | isarray "0.0.1" 153 | string_decoder "~0.10.x" 154 | 155 | readable-stream@~1.0.0: 156 | version "1.0.34" 157 | resolved "http://registry.npm.taobao.org/readable-stream/download/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" 158 | dependencies: 159 | core-util-is "~1.0.0" 160 | inherits "~2.0.1" 161 | isarray "0.0.1" 162 | string_decoder "~0.10.x" 163 | 164 | scp2@^0.5.0: 165 | version "0.5.0" 166 | resolved "http://registry.npm.taobao.org/scp2/download/scp2-0.5.0.tgz#64ee74bc3685f3a4c6290f2da8c1e3b4eef92e8d" 167 | dependencies: 168 | async "~0.9.0" 169 | glob "~7.0.3" 170 | lodash "~4.11.1" 171 | ssh2 "~0.4.10" 172 | 173 | ssh2-streams@~0.0.22: 174 | version "0.0.23" 175 | resolved "http://registry.npm.taobao.org/ssh2-streams/download/ssh2-streams-0.0.23.tgz#aeef30831bb5fc4af6aa3f6d0a261a413531612b" 176 | dependencies: 177 | asn1 "~0.2.0" 178 | readable-stream "~1.0.0" 179 | streamsearch "~0.1.2" 180 | 181 | ssh2@~0.4.10: 182 | version "0.4.15" 183 | resolved "http://registry.npm.taobao.org/ssh2/download/ssh2-0.4.15.tgz#07c6f4106d9f7b6ea6e4df636c6c53f1f9817ff8" 184 | dependencies: 185 | readable-stream "~1.0.0" 186 | ssh2-streams "~0.0.22" 187 | 188 | streamsearch@~0.1.2: 189 | version "0.1.2" 190 | resolved "http://registry.npm.taobao.org/streamsearch/download/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 191 | 192 | string_decoder@~0.10.x: 193 | version "0.10.31" 194 | resolved "http://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 195 | 196 | supports-color@^4.0.0: 197 | version "4.2.0" 198 | resolved "http://registry.npm.taobao.org/supports-color/download/supports-color-4.2.0.tgz#ad986dc7eb2315d009b4d77c8169c2231a684037" 199 | dependencies: 200 | has-flag "^2.0.0" 201 | 202 | wrappy@1: 203 | version "1.0.2" 204 | resolved "http://registry.npm.taobao.org/wrappy/download/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 205 | 206 | xregexp@2.0.0: 207 | version "2.0.0" 208 | resolved "http://registry.npm.taobao.org/xregexp/download/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" 209 | --------------------------------------------------------------------------------