├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── index.js ├── package.json └── receiver.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | node_modules/ 3 | 4 | .DS_Store 5 | *.db 6 | *.bak 7 | *.tmp 8 | *.cmd 9 | ~* 10 | 11 | upload.py -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.settings 3 | /.project 4 | /.gitignore 5 | /node_modules 6 | /test 7 | /.tmp 8 | 9 | .DS_Store 10 | *.db 11 | *.bak 12 | *.tmp 13 | *.cmd 14 | ~* 15 | 16 | upload.py -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 baidu.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fis3 http 部署插件 2 | 3 | FIS 默认的部署插件,提供本地部署以及远程upload部署能力。 4 | 5 | ## 关于文件接收器 receiver.php 6 | 7 | **此代码存在很大的安全隐患,没有做任何安全考虑,请不要部署到线上服务。** 8 | 9 | 百度内部请使用:http://agroup.baidu.com/fis/md/article/196978 10 | 11 | ## 安装 12 | 13 | 全局安装或者本地安装都可以。 14 | 15 | ``` 16 | npm install fis3-deploy-http-push --save-dev 17 | ``` 18 | 19 | ## 使用方法 20 | 21 | 也可以使用统一的 deploy 插件配置方法 22 | 23 | ```javascript 24 | fis.match('*.js', { 25 | deploy: fis.plugin('http-push', { 26 | //如果配置了receiver,fis会把文件逐个post到接收端上 27 | receiver: 'http://www.example.com:8080/receiver.php', 28 | //这个参数会跟随post请求一起发送 29 | to: '/home/fis/www', 30 | // 附加参数, 后端通过 $_POST['xx'] 获取 31 | // 如果 data 中 含有 to 这个 key, 那么上面那个to参数会覆盖掉data里面的to 32 | data: { 33 | token : 'abcdefghijk', 34 | user : 'maxming', 35 | uid : 1 36 | } 37 | }) 38 | }) 39 | ``` 40 | 41 | ## 另类使用方法 42 | 43 | 比如: 部署时需要 token 输入 44 | 45 | 举一反三 46 | 47 | ```javascript 48 | const crypto = require('crypto'); 49 | const readlineSync = require('readline-sync'); 50 | fis.match('**', { 51 | deploy: [ 52 | function (options, modified, total, next) { 53 | var token = readlineSync.question('\r\n请输入授权token : ', { 54 | hideEchoBack: true 55 | }); 56 | if (!token) { 57 | return false; 58 | } 59 | var md5 = crypto.createHash('md5'); 60 | fis.set('project.token', md5.update(token).digest('hex')); 61 | next(); 62 | }, 63 | function () { 64 | arguments[0] = { 65 | //如果配置了receiver,fis会把文件逐个post到接收端上 66 | receiver: 'http://127.0.0.1/receiver.php?debug=false', 67 | // receiver: 'http://127.0.0.1/receiver.php', 68 | //这个参数会跟随post请求一起发送 69 | to: '/home/fis/www', 70 | // to: '/Users/fis/www', 71 | // 附加参数, 后端通过 $_POST['xx'] 获取 72 | data: { 73 | token: fis.get('project.token') 74 | } 75 | }; 76 | require('fis3-deploy-http-push').apply(this, arguments); 77 | } 78 | ] 79 | }); 80 | ``` 81 | 82 | 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fis.baidu.com 3 | */ 4 | var _ = fis.util; 5 | var path = require('path'); 6 | var prompt = require('prompt'); 7 | prompt.start(); 8 | 9 | function upload(receiver, to, data, release, content, file, callback) { 10 | var subpath = file.subpath; 11 | data['to'] = _(path.join(to, release)); 12 | fis.util.upload( 13 | //url, request options, post data, file 14 | receiver, null, data, content, subpath, 15 | function(err, res) { 16 | var json = null; 17 | res = res && res.trim(); 18 | 19 | try { 20 | json = res ? JSON.parse(res) : null; 21 | } catch (e) {} 22 | 23 | if (!err && json && json.errno) { 24 | callback(json); 25 | } else if (err || !json && res != '0') { 26 | callback('upload file [' + subpath + '] to [' + to + '] by receiver [' + receiver + '] error [' + (err || res) + ']'); 27 | } else { 28 | var time = '[' + fis.log.now(true) + ']'; 29 | process.stdout.write( 30 | '\n - '.green.bold + 31 | time.grey + ' ' + 32 | subpath.replace(/^\//, '') + 33 | ' >> '.yellow.bold + 34 | to + release 35 | ); 36 | callback(); 37 | } 38 | } 39 | ); 40 | } 41 | 42 | function fetch(url, data, callback) { 43 | var endl = '\r\n'; 44 | var collect = []; 45 | var opt = {}; 46 | 47 | _.map(data, function(key, value) { 48 | collect.push(key + '=' + encodeURIComponent(value)) 49 | }); 50 | 51 | var content = collect.join('&'); 52 | opt.method = opt.method || 'POST'; 53 | opt.headers = _.assign({ 54 | 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' 55 | }, opt.headers || {}); 56 | opt = fis.util.parseUrl(url, opt); 57 | var http = opt.protocol === 'https:' ? require('https') : require('http'); 58 | var req = http.request(opt, function(res) { 59 | var status = res.statusCode; 60 | var body = ''; 61 | res 62 | .on('data', function(chunk) { 63 | body += chunk; 64 | }) 65 | .on('end', function() { 66 | if (status >= 200 && status < 300 || status === 304) { 67 | var json = null; 68 | try {json = JSON.parse(body);} catch(e) {}; 69 | 70 | if (!json || json.errno) { 71 | callback(json || 'The response is not valid json string.') 72 | } else { 73 | callback(null, json); 74 | } 75 | } else { 76 | callback(status); 77 | } 78 | }) 79 | .on('error', function(err) { 80 | callback(err.message || err); 81 | }); 82 | }); 83 | req.write(content); 84 | req.end(); 85 | } 86 | 87 | function requireEmail(authApi, validateApi, info, cb) { 88 | prompt.get({ 89 | properties: { 90 | email: { 91 | pattern: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, 92 | message: 'The specified value must be a valid email address.', 93 | description: 'Enter your email', 94 | required: true, 95 | default: info.email 96 | } 97 | } 98 | }, function(error, ret) { 99 | if (error) { 100 | return cb(error); 101 | } 102 | 103 | info.email = ret.email; 104 | deployInfo(info); 105 | 106 | fetch(authApi, { 107 | email: ret.email 108 | }, function(error, ret) { 109 | if (error) { 110 | return cb(error); 111 | } 112 | 113 | console.log('We\'re already sent the code to your email.') 114 | 115 | requireToken(validateApi, info, cb); 116 | }); 117 | }) 118 | } 119 | 120 | function requireToken(validateApi, info, cb) { 121 | prompt.get({ 122 | properties: { 123 | code: { 124 | description: 'Enter your code', 125 | required: true, 126 | hide: true 127 | } 128 | } 129 | }, function(error, ret) { 130 | if (error) { 131 | return cb(error); 132 | } 133 | 134 | info.code = ret.code; 135 | deployInfo(info); 136 | 137 | fetch(validateApi, { 138 | email: info.email, 139 | code: info.code 140 | }, function(error, ret) { 141 | if (error) { 142 | return cb(error); 143 | } 144 | 145 | info.token = ret.data.token; 146 | deployInfo(info); 147 | cb(null, info); 148 | }); 149 | }) 150 | } 151 | 152 | function getTmpFile() { 153 | return fis.project.getTempPath('deploy.json'); 154 | } 155 | 156 | function deployInfo(options) { 157 | var conf = getTmpFile(); 158 | 159 | if (arguments.length) { 160 | // setter 161 | return options && fis.util.write(conf, JSON.stringify(options, null, 2)); 162 | } else { 163 | var ret = null; 164 | 165 | try { 166 | // getter 167 | ret = fis.util.isFile(conf) ? require(conf) : null; 168 | } catch (e) { 169 | 170 | } 171 | return ret; 172 | } 173 | }; 174 | 175 | module.exports = function(options, modified, total, callback) { 176 | if (!options.to) { 177 | throw new Error('options.to is required!'); 178 | } 179 | 180 | var info = deployInfo() || {}; 181 | var to = options.to; 182 | var receiver = options.receiver; 183 | var authApi = options.authApi; 184 | var validateApi = options.validateApi; 185 | var data = options.data || {}; 186 | 187 | if (options.host) { 188 | receiver = options.receiver = options.host + '/v1/upload'; 189 | authApi = options.authApi = options.host + '/v1/authorize'; 190 | validateApi = options.validateApi = options.host + '/v1/validate'; 191 | } 192 | 193 | if (!options.receiver) { 194 | throw new Error('options.receiver is required!'); 195 | } 196 | 197 | var steps = []; 198 | 199 | modified.forEach(function(file) { 200 | steps.push(function(next) { 201 | var _upload = arguments.callee; 202 | var reTryCount = options.retry; 203 | data.email = info.email; 204 | data.token = info.token; 205 | 206 | upload(receiver, to, data, file.getHashRelease(), file.getContent(), file, function(error) { 207 | if (error) { 208 | if (error.errno === 100302 || error.errno === 100305) { 209 | // 检测到后端限制了上传,要求用户输入信息再继续。 210 | 211 | if (!authApi || !validateApi) { 212 | throw new Error('options.authApi and options.validateApi is required!'); 213 | } 214 | 215 | if (info.email) { 216 | console.error('\nToken is invalid: ', error.errmsg, '\n'); 217 | } 218 | 219 | requireEmail(authApi, validateApi, info, function(error) { 220 | if (error) { 221 | throw new Error('Auth failed! ' + error.errmsg); 222 | } else { 223 | _upload(next); 224 | } 225 | }); 226 | } else if (options.retry && !--reTryCount) { 227 | throw new Error(error.errmsg || error); 228 | } else { 229 | _upload(next); 230 | } 231 | } else { 232 | next(); 233 | } 234 | }); 235 | }); 236 | }); 237 | 238 | _.reduceRight(steps, function(next, current) { 239 | return function() { 240 | current(next); 241 | }; 242 | }, callback)(); 243 | }; 244 | 245 | module.exports.options = { 246 | // 允许重试两次。 247 | retry: 2 248 | }; 249 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fis3-deploy-http-push", 3 | "version": "2.0.8", 4 | "description": "fis3 deploy http-push plugin", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "fis", 11 | "fis-plus", 12 | "fis3", 13 | "deploy" 14 | ], 15 | "author": "FIS", 16 | "license": "BSD", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/fex-team/fis3-deploy-http-push.git" 20 | }, 21 | "dependencies": { 22 | "prompt": "1.0.0", 23 | "colors": "1.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /receiver.php: -------------------------------------------------------------------------------- 1 | 0){ 34 | header("Status: 500 Internal Server Error"); 35 | } else { 36 | if(file_exists($to)){ 37 | unlink($to); 38 | } else { 39 | $dir = dirname($to); 40 | if(!file_exists($dir)){ 41 | mkdirs($dir); 42 | } 43 | } 44 | echo move_uploaded_file($_FILES["file"]["tmp_name"], $to) ? 0 : 1; 45 | } 46 | } else { 47 | echo 'I\'m ready for that, you know.'; 48 | } 49 | --------------------------------------------------------------------------------