diff --git a/.gitignore b/.gitignore
index c4fa1f63..c8f8d743 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,8 @@ pids
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
-coverage
\ No newline at end of file
+coverage
+
+# vs code
+.history
+.idea
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 79a4ea4f..3a0d6580 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,20 +13,13 @@ services:
- mysql
node_js:
- - "6"
+ - "8.10.0"
+ - "7.5"
+ - "7"
+ - "6.9.5"
- "6.1"
- - "5"
- - "5.11"
- - "5.10"
- - "5.9"
- - "5.8"
- - "5.7"
- - "5.6"
- - "5.5"
- - "5.4"
- - "5.3"
- - "5.2"
- - "5.1"
+ - "6"
+
cache:
directories:
- node_modules
@@ -35,4 +28,4 @@ before_install:
- "test ! -d node_modules || npm prune"
- "test ! -d node_modules || npm rebuild"
script: "npm run-script coverage"
-after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls"
\ No newline at end of file
+after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..c6903211
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,67 @@
+# Changelog for code-push-server
+
+## 0.5.x
+
+## 新特性
+- 针对文本增量更新进行优化,使用google `diff-match-patch` 算法计算差异
+ - react-native-code-push Android客户端适配,需要合并https://github.com/Microsoft/react-native-code-push/pull/1393, 才能正常使用文本增量更新功能。
+ - react-native-code-push iOS客户端适配 (需要合并https://github.com/Microsoft/react-native-code-push/pull/1399)
+ - react-native-code-push Windows客户端适配 (进行中)
+
+## fixbug
+
+- 修复统计数据激活数
+- 修复灰度发布bug
+- rollback后增加计算和最后一次增量更新版本
+
+## 如何升级到该版本
+
+### 升级数据库
+
+`$ npm run upgrade`
+
+or
+
+`$ code-push-server-db upgrade`
+
+
+## 0.4.x
+
+### 新特性
+
+- targetBinaryVersion 支持正则匹配, `deployments_versions`新增字段`min_version`,`max_version`
+ - `*` 匹配所有版本
+ - `1.2.3` 匹配特定版本`1.2.3`
+ - `1.2`/`1.2.*` 匹配所有1.2补丁版本
+ - `>=1.2.3<1.3.7`
+ - `~1.2.3` 匹配`>=1.2.3<1.3.0`
+ - `^1.2.3` 匹配`>=1.2.3<2.0.0`
+- 添加docker编排服务部署,更新文档
+- Support Tencent cloud cos storageType
+
+## 如何升级到该版本
+
+- 升级数据库
+`$ ./bin/db upgrade`
+or
+`$ mysql codepush < ./sql/codepush-v0.4.0-patch.sql`
+
+- 处理存量数据
+``` shell
+ $ git clone https://github.com/lisong/tools
+ $ cd tools
+ $ npm i
+ $ vim ./bin/fixMinMaxVersion //修改数据配置
+ $ node ./bin/fixMinMaxVersion //出现提示 success
+```
+
+## 0.3.x
+
+- 支持灰度发布
+- 适配`code-push app add` 命令,应用不再以名字区分平台,而是以类型区分平台
+ - 数据库表apps新增字段`os`,`platform`
+- 完善`code-push release/release-react/release-cordova` 命令
+ - 数据库表packages新增`is_disabled`,`rollout`字段
+- 适配`code-push patch`命令
+- 新增`log_report_download`,`log_report_deploy`日志表
+- 升级npm依赖包
diff --git a/Makefile b/Makefile
index 0ef8c45e..7e59757b 100644
--- a/Makefile
+++ b/Makefile
@@ -8,18 +8,20 @@ test: test-integration
test-integration:
@echo "\nRunning integration tests..."
+ @NODE_ENV=test CONFIG_FILE=${ROOT}/config/config.test.js mocha test/api/init
@NODE_ENV=test PORT=3000 HOST=127.0.0.1 CONFIG_FILE=${ROOT}/config/config.test.js node bin/www &
@NODE_ENV=test CONFIG_FILE=${ROOT}/config/config.test.js mocha \
- test/api/init test/api/users test/api/auth test/api/account test/api/accessKeys test/api/sessions test/api/apps test/api/index --recursive --timeout 15000
+ test/api/users test/api/auth test/api/account test/api/accessKeys test/api/apps test/api/index --recursive --timeout 15000
coverage:
@echo "\n\nRunning coverage report..."
rm -rf coverage
# @NODE_ENV=test CONFIG_FILE=${ROOT}/config/config.test.js ./node_modules/istanbul/lib/cli.js cover --report lcovonly --dir coverage/core ./node_modules/.bin/_mocha \
# test/unit -- -R spec --recursive --timeout 15000
+ @NODE_ENV=test CONFIG_FILE=${ROOT}/config/config.test.js mocha test/api/init
@NODE_ENV=test PORT=3000 HOST=127.0.0.1 CONFIG_FILE=${ROOT}/config/config.test.js node bin/www &
@NODE_ENV=test CONFIG_FILE=${ROOT}/config/config.test.js ./node_modules/istanbul/lib/cli.js cover --report lcovonly --dir coverage/api ./node_modules/.bin/_mocha \
- test/api/init test/api/users test/api/auth test/api/account test/api/accessKeys test/api/sessions test/api/apps test/api/index -- -R spec --recursive --timeout 15000
+ test/api/users test/api/auth test/api/account test/api/accessKeys test/api/apps test/api/index -- -R spec --recursive --timeout 15000
@NODE_ENV=test CONFIG_FILE=${ROOT}/config/config.test.js ./node_modules/istanbul/lib/cli.js report
.PHONY: coverage
\ No newline at end of file
diff --git a/README.md b/README.md
index 9b4cd3ac..1611e8db 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# CodePush Server [source](https://github.com/lisong/code-push-server)
+[](https://nodei.co/npm/code-push-server/)
+
[](https://npmjs.org/package/code-push-server)
[](https://nodejs.org/en/download/)
[](https://travis-ci.org/lisong/code-push-server)
@@ -9,233 +11,87 @@
[](https://snyk.io/test/npm/code-push-server)
[](https://spdx.org/licenses/MIT)
-CodePush Server is a CodePush progam server! microsoft CodePush cloud is slow in China, we can use this to build our's. I use [qiniu](http://www.qiniu.com/) to store the files, because it's simple and quick! Or you can use local storage, just modify config.js file, it's simple configure.
+CodePush Server is a CodePush progam server! microsoft CodePush cloud is slow in China, we can use this to build our's. I use [qiniu](http://www.qiniu.com/) to store the files, because it's simple and quick! Or you can use [local/s3/oss/tencentcloud] storage, just modify config.js file, it's simple configure.
+
-## qq交流群 535491067
+## Support Storage mode
+
+- local *storage bundle file in local machine*
+- qiniu *storage bundle file in [qiniu](http://www.qiniu.com/)*
+- s3 *storage bundle file in [aws](https://aws.amazon.com/)*
+- oss *storage bundle file in [aliyun](https://www.aliyun.com/product/oss)*
+- tencentcloud *storage bundle file in [tencentcloud](https://cloud.tencent.com/product/cos)*
## 正确使用code-push热更新
-- 苹果允许使用热更新[Apple's developer agreement](https://developer.apple.com/programs/ios/information/iOS_Program_Information_4_3_15.pdf), 但是规定不能弹框提示用户更新,影响用户体验。 而Google Play恰好相反,必须弹框告知用户更新。然而中国的android市场都必须关闭更新弹框,否则会在审核应用时以“请上传最新版本的二进制应用包”驳回应用。
+- 苹果App允许使用热更新[Apple's developer agreement](https://developer.apple.com/programs/ios/information/iOS_Program_Information_4_3_15.pdf), 为了不影响用户体验,规定必须使用静默更新。 Google Play不能使用静默更新,必须弹框告知用户App有更新。中国的android市场必须采用静默更新(如果弹框提示,App会被“请上传最新版本的二进制应用包”原因驳回)。
- react-native 不同平台bundle包不一样,在使用code-push-server的时候必须创建不同的应用来区分(eg. CodePushDemo-ios 和 CodePushDemo-android)
- react-native-code-push只更新资源文件,不会更新java和Objective C,所以npm升级依赖包版本的时候,如果依赖包使用的本地化实现, 这时候必须更改应用版本号(ios修改Info.plist中的CFBundleShortVersionString, android修改build.gradle中的versionName), 然后重新编译app发布到应用商店。
- 推荐使用code-push release-react 命令发布应用,该命令合并了打包和发布命令(eg. code-push release-react CodePushDemo-ios ios -d Production)
+- 每次向App Store提交新的版本时,也应该基于该提交版本同时向code-push-server发布一个初始版本。(因为后面每次向code-push-server发布版本时,code-puse-server都会和初始版本比较,生成补丁版本)
-## EXAMPLE
-codepush.19910225.com 只是一个测试server,不要将自己生产环境的项目放在上面,服务器的宽带只有1M,而且服务没有做负载均衡和监控,稳定性不能保证,烦请大家自己搭建自己的服务。
-### shell命令行端
+### shell login
```shell
-$ code-push login http://codepush.19910225.com:8080 #登录
+$ code-push login http://api.code-push.com #登录
```
-### [web](http://codepush-managerment.19910225.com:8080)
+### [web](http://www.code-push.com)
-访问:http://codepush-managerment.19910225.com:8080
+访问:http://www.code-push.com
-### 客户端eg.
+### client eg.
[ReactNative CodePushDemo](https://github.com/lisong/code-push-demo-app)
[Cordova CodePushDemo](https://github.com/lisong/code-push-cordova-demo-app)
-## INSTALL FROM NPM PACKAGE
-
-```shell
-$ npm install code-push-server -g
-$ code-push-server-db init --dbhost localhost --dbuser root --dbpassword #初始化mysql数据库
-$ code-push-server #启动服务 浏览器中打开 http://127.0.0.1:3000
-```
-
-## INSTALL FROM SOURCE CODE
-
-```shell
-$ git clone https://github.com/lisong/code-push-server.git
-$ cd code-push-server
-$ npm install
-$ ./bin/db init --dbhost localhost --dbuser root --dbpassword #初始化mysql数据库
-$ ./bin/www #启动服务 浏览器中打开 http://127.0.0.1:3000
-```
-
-## CONFIG
-```shell
-$ vim config/config.js
-```
-请检查如下配置是否和你的环境一致,尤其是downloadUrl参数
-
-```
- db: {
- username: "root",
- password: null,
- database: "codepush",
- host: "127.0.0.1",
- dialect: "mysql"
- },
- //七牛云存储配置 当storageType为qiniu时需要配置
- qiniu: {
- accessKey: "",
- secretKey: "",
- bucketName: "",
- downloadUrl: "" //文件下载域名地址
- },
- //阿里云存储配置 当storageType为oss时需要配置
- oss: {
- accessKeyId: "",
- secretAccessKey: "",
- endpoint: "",
- bucketName: "",
- prefix: "", // 对象Key的前缀,允许放到子文件夹里面
- downloadUrl: "", // 文件下载域名地址,需要包含前缀
- },
- //文件存储在本地配置 当storageType为local时需要配置
- local: {
- storageDir: "/Users/tablee/workspaces/storage",
- //文件下载地址 CodePush Server 地址 + '/download' download对应app.js里面的地址
- downloadUrl: "http://localhost:3000/download",
- // public static download spacename.
- public: '/download'
- },
- jwt: {
- // 登录jwt签名密钥,必须更改,否则有安全隐患,可以使用随机生成的字符串
- // Recommended: 63 random alpha-numeric characters
- // Generate using: https://www.grc.com/passwords.htm
- tokenSecret: 'INSERT_RANDOM_TOKEN_KEY'
- },
- common: {
- dataDir: "/Users/tablee/workspaces/data",
- //选择存储类型,目前支持local,oss,qiniu,s3配置
- storageType: "local"
- },
-```
-read [config.js](https://github.com/lisong/code-push-server/blob/master/config/config.js)
-
-
-## Storage mode [local/qiniu/s3]
-
-- 配置local存储,修改config/config.js中storageType值为local,配置中local下面storageDir和downloadUrl,如果不在同一台机器上,downloadUrl请指定域名或者ip地址
-
-
-## RUN
-
-```shell
-$ node ./bin/www # or code-push-server
-```
-
-or point config file and ENV
-
-```shell
-$ CONFIG_FILE=/path/to/config.js NODE_ENV=production node ./bin/www # or CONFIG_FILE=/path/to/config.js NODE_ENV=production code-push-server
-```
-
-notice. you have to change `tokenSecret` in config.js for security.
-
-## Default listen Host/Port 0.0.0.0/3000
-you can change like this.
-
-```shell
-$ PORT=3000 HOST=127.0.0.1 NODE_ENV=production node ./bin/www # or PORT=3000 HOST=127.0.0.1 NODE_ENV=production code-push-server
-```
-
-## [code-push-cli](https://github.com/Microsoft/code-push)
-Use code-push-cli manager CodePushServer
-
-```shell
-$ npm install code-push-cli@latest -g
-$ code-push login http://127.0.0.1:3000 #login in browser account:admin password:123456
-```
-
-change admin password eg.
-
-```shell
-$ curl -X PATCH -H "Authorization: Bearer mytoken" -H "Accept: application/json" -H "Content-Type:application/json" -d '{"oldPassword":"123456","newPassword":"654321"}' http://127.0.0.1:3000/users/password
-```
+## HOW TO INSTALL code-push-server
-## [react-native-code-push](https://github.com/Microsoft/react-native-code-push) for react-native
+- [docker](https://github.com/lisong/code-push-server/blob/master/docker/README.md) (recommend)
+- [manual operation](https://github.com/lisong/code-push-server/blob/master/docs/README.md)
-```shell
-$ cd /path/to/project
-$ npm install react-native-code-push@latest --save
-```
+## DEFAULT ACCOUNT AND PASSWORD
-## config react-native project
-Follow the react-native-code-push docs, addition iOS add a new entry named CodePushServerURL, whose value is the key of ourself CodePushServer URL. Andriod use the new CodePush constructor in MainApplication point CodePushServerUrl
+- account: `admin`
+- password: `123456`
-iOS eg. in file Info.plist
+## HOW TO USE
-```xml
-...
-CodePushDeploymentKey
-YourCodePushKey
-CodePushServerURL
-YourCodePushServerUrl
-...
-```
+- [normal](https://github.com/lisong/code-push-server/blob/master/docs/react-native-code-push.md)
+- [react-native-code-push](https://github.com/Microsoft/react-native-code-push)
+- [code-push](https://github.com/Microsoft/code-push)
-Android eg. in file MainApplication.java
-
-```java
-@Override
-protected List getPackages() {
- return Arrays.asList(
- new MainReactPackage(),
- new CodePush(
- "YourKey",
- MainApplication.this,
- BuildConfig.DEBUG,
- "YourCodePushServerUrl"
- )
- );
-}
-```
+## ISSUES
-## [cordova-plugin-code-push](https://github.com/Microsoft/cordova-plugin-code-push) for cordova
+[code-push-server normal solution](https://github.com/lisong/code-push-server/issues/135)
-```shell
-$ cd /path/to/project
-$ cordova plugin add cordova-plugin-code-push@latest --save
-```
-
-## config cordova project
+[An unknown error occurred](https://github.com/lisong/code-push-server/issues?utf8=%E2%9C%93&q=unknown)
-edit config.xml. add code below.
+[modify password](https://github.com/lisong/code-push-server/issues/43)
-```xml
-
-
-
-
-
-
-
-
-```
-## Production Manage
-use [pm2](http://pm2.keymetrics.io/) to manage process.
+# UPDATE TIME LINE
-```shell
-$ npm install pm2 -g
-$ cp config/config.js /path/to/production/config.js
-$ vim /path/to/production/config.js #configure your env.
-$ cp docs/process.yml /path/to/production/process.yml
-$ vim /path/to/production/process.yml #configure your env.
-$ pm2 start /path/to/production/process.yml
-```
+- targetBinaryVersion support
+ - `*`
+ - `1.2.3`
+ - `1.2`/`1.2.*`
+ - `1.2.3 - 1.2.7`
+ - `>=1.2.3 <1.2.7`
+ - `~1.2.3`
+ - `^1.2.3`
-## Use [CodePush Web](https://github.com/lisong/code-push-web) manage apps
-add codePushWebUrl config in ./config/config.js
+## Advance Feature
-eg.
+> use google diff-match-patch calculate text file diff patch
-```json
-...
-"common": {
- "codePushWebUrl": "Your CodePush Web address",
-}
-...
-```
+- support iOS and Android
+- use `"react-native-code-push": "git+https://git@github.com/lisong/react-native-code-push.git"` instead `"react-native-code-push": "x.x.x"` in `package.json`
+- change `apps`.`is_use_diff_text` to `1` in mysql codepush database
## License
MIT License [read](https://github.com/lisong/code-push-server/blob/master/LICENSE)
diff --git a/app.js b/app.js
index 707c45ec..ee93e3cc 100644
--- a/app.js
+++ b/app.js
@@ -1,7 +1,6 @@
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
-var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var helmet = require('helmet');
@@ -10,27 +9,23 @@ var _ = require('lodash');
var fs = require('fs');
var routes = require('./routes/index');
+var indexV1 = require('./routes/indexV1');
var auth = require('./routes/auth');
var accessKeys = require('./routes/accessKeys');
-var sessions = require('./routes/sessions');
var account = require('./routes/account');
var users = require('./routes/users');
var apps = require('./routes/apps');
+var AppError = require('./core/app-error');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:app");
var app = express();
app.use(helmet());
app.disable('x-powered-by');
-
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
-// uncomment after placing your favicon in /public
-//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
-if (app.get('env') === 'development') {
- app.use(logger('dev'));
-} else if (app.get('env') === 'production'){
- app.use(logger('combined',{skip: function (req, res) { return res.statusCode < 400 }}));
-}
+app.use(log4js.connectLogger(log4js.getLogger("http"), {level: log4js.levels.INFO, nolog:'\\.gif|\\.jpg|\\.js|\\.css$' }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
@@ -38,61 +33,90 @@ app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
//use nginx in production
-if (app.get('env') === 'development') {
+//if (app.get('env') === 'development') {
+ log.debug("set Access-Control Header");
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
- res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
+ res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, X-CodePush-Plugin-Version, X-CodePush-Plugin-Name, X-CodePush-SDK-Version");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,PATCH,DELETE,OPTIONS");
+ log.debug("use set Access-Control Header");
next();
});
-}
+//}
-if (_.get(config, 'common.storageType') === 'local'
- && _.get(config, 'local.storageDir')
- ) {
- if (!fs.existsSync(_.get(config, 'local.storageDir'))) {
- var dir = _.get(config, 'local.storageDir');
- throw new Error(`Please create dir ${dir}`);
+log.debug("config common.storageType value: " + _.get(config, 'common.storageType'));
+
+if (_.get(config, 'common.storageType') === 'local') {
+ var localStorageDir = _.get(config, 'local.storageDir');
+ if (localStorageDir) {
+
+ log.debug("config common.storageDir value: " + localStorageDir);
+
+ if (!fs.existsSync(localStorageDir)) {
+ var e = new Error(`Please create dir ${localStorageDir}`);
+ log.error(e);
+ throw e;
+ }
+ try {
+ log.debug('checking storageDir fs.W_OK | fs.R_OK');
+ fs.accessSync(localStorageDir, fs.W_OK | fs.R_OK);
+ log.debug('storageDir fs.W_OK | fs.R_OK is ok');
+ } catch (e) {
+ log.error(e);
+ throw e;
+ }
+ log.debug("static download uri value: " + _.get(config, 'local.public', '/download'));
+ app.use(_.get(config, 'local.public', '/download'), express.static(localStorageDir));
+ } else {
+ log.error('please config local storageDir');
}
- app.use(_.get(config, 'local.public', '/download'), express.static(_.get(config, 'local.storageDir')));
}
app.use('/', routes);
+app.use('/v0.1/public/codepush', indexV1);
app.use('/auth', auth);
app.use('/accessKeys', accessKeys);
-app.use('/sessions', sessions);
app.use('/account', account);
app.use('/users', users);
app.use('/apps', apps);
-// catch 404 and forward to error handler
-app.use(function(req, res, next) {
- var err = new Error('Not Found');
- err.status = 404;
- next(err);
-});
-
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
- app.use(function(err, req, res) {
+ app.use(function(req, res, next) {
+ var err = new AppError.NotFound();
+ res.status(err.status || 404);
+ res.render('error', {
+ message: err.message,
+ error: err
+ });
+ log.error(err);
+ });
+ app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
+ log.error(err);
});
-}
-
-// production error handler
-// no stacktraces leaked to user
-app.use(function(err, req, res) {
- res.status(err.status || 500);
- res.render('error', {
- message: err.message,
- error: {}
+} else {
+ app.use(function(req, res, next) {
+ var e = new AppError.NotFound();
+ res.status(404).send(e.message);
+ log.debug(e);
});
-});
-
+ // production error handler
+ // no stacktraces leaked to user
+ app.use(function(err, req, res, next) {
+ if (err instanceof AppError.AppError) {
+ res.send(err.message);
+ log.debug(err);
+ } else {
+ res.status(err.status || 500).send(err.message);
+ log.error(err);
+ }
+ });
+}
module.exports = app;
diff --git a/appveyor.yml b/appveyor.yml
index 8f45ad6b..977b5a1e 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -3,9 +3,7 @@ init:
environment:
matrix:
- - nodejs_version: "6"
- - nodejs_version: "6.1"
- - nodejs_version: "5"
+ - nodejs_version: "8.10.0"
services:
- mysql
@@ -25,4 +23,4 @@ test_script:
- set CONFIG_FILE=config/config.testwin.js
- node_modules\.bin\npm run test-win
build: off
-version: "{build}"
\ No newline at end of file
+version: "{build}"
diff --git a/bin/db b/bin/db
index 3e916651..005b8810 100755
--- a/bin/db
+++ b/bin/db
@@ -6,12 +6,24 @@
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
-var mysql = require('mysql');
+var mysql = require('mysql2');
var Promise = require("bluebird");
+var common = require("../core/utils/common");
+var constConfig = require('../core/const');
var yargs = require('yargs')
.usage('Usage: $0 [options]')
- .command('init', '初始化数据库')
- .command('upgrade', '升级数据库')
+ .command('init', '初始化数据库', {
+ dbpassword: {
+ alias: 'dbpassword',
+ type: 'string'
+ }
+ })
+ .command('upgrade', '升级数据库', {
+ dbpassword: {
+ alias: 'dbpassword',
+ type: 'string'
+ }
+ })
.example('$0 init --dbname codepush --dbhost localhost --dbuser root --dbpassword 123456 --dbport 3306 --force', '初始化code-push-server数据库')
.example('$0 upgrade --dbname codepush --dbhost localhost --dbuser root --dbpassword 123456 --dbport 3306', '升级code-push-server数据库')
.default({dbname: 'codepush', dbhost: 'localhost', dbuser: 'root', dbpassword: null})
@@ -23,10 +35,7 @@ var dbname = argv.dbname ? argv.dbname : 'codepush';
var dbhost = argv.dbhost ? argv.dbhost : 'localhost';
var dbuser = argv.dbuser ? argv.dbuser : 'root';
var dbport = argv.dbport ? argv.dbport : 3306;
-var dbpassword = null;
-if (_.isString(argv.dbpassword) || _.isNumber(argv.dbpassword)) {
- dbpassword = argv.dbpassword;
-}
+var dbpassword = argv.dbpassword;
if (command === 'init') {
var connection2;
@@ -69,37 +78,50 @@ if (command === 'init') {
if(connection2) connection2.end()
});
} else if (command == 'upgrade'){
- var connection = mysql.createConnection({
- host: dbhost,
- user: dbuser,
- password: dbpassword,
- database: dbname,
- multipleStatements: true,
- port: dbport
- });
- Promise.promisifyAll(connection);
- connection.connect();
+ try {
+ var connection = mysql.createConnection({
+ host: dbhost,
+ user: dbuser,
+ password: dbpassword,
+ database: dbname,
+ multipleStatements: true,
+ port: dbport
+ });
+ Promise.promisifyAll(connection);
+ connection.connect()
+ } catch(e) {
+ console.error('connect mysql error, check params',e);
+ return;
+ }
+
return Promise.coroutine(function*(val){
var version_no = '0.0.1';
- try {
- var rs = yield connection.queryAsync('select `version` from `versions` where `type`=1 limit 1');
- version_no = _.get(rs,'0.version', '0.0.1');
- } catch (e) {
- }
- if (version_no == '0.2.15') {
+ var rs = yield connection.queryAsync('select `version` from `versions` where `type`=1 limit 1');
+ version_no = _.get(rs,'0.version', '0.0.1');
+ if (version_no == constConfig.CURRENT_DB_VERSION) {
console.log('Everything up-to-date.');
- process.exit(1);
+ process.exit(0);
}
var allSqlFile = [
- {version:'0.2.14', 'path':path.resolve(__dirname, '../sql/codepush-v0.2.14.sql')},
- {version:'0.2.15', 'path':path.resolve(__dirname, '../sql/codepush-v0.2.15.sql')}
+ {version:'0.2.14', 'path':path.resolve(__dirname, '../sql/codepush-v0.2.14-patch.sql')},
+ {version:'0.2.15', 'path':path.resolve(__dirname, '../sql/codepush-v0.2.15-patch.sql')},
+ {version:'0.3.0', 'path':path.resolve(__dirname, '../sql/codepush-v0.3.0-patch.sql')},
+ {version:'0.4.0', 'path':path.resolve(__dirname, '../sql/codepush-v0.4.0-patch.sql')},
+ {version:'0.5.0', 'path':path.resolve(__dirname, '../sql/codepush-v0.5.0-patch.sql')}
];
for (var i = 0; i < allSqlFile.length; i++) {
if(!_.gt(allSqlFile[i]['version'], version_no)) {
continue;
}
- var sql = fs.readFileSync(allSqlFile[i]['path'], 'utf-8');
- yield connection.queryAsync(sql);
+ try {
+ var sql = fs.readFileSync(allSqlFile[i]['path'], 'utf-8');
+ console.log('exec sql file:' + allSqlFile[i]['path']);
+ yield connection.queryAsync(sql);
+ console.log('success exec sql file:' + allSqlFile[i]['path']);
+ } catch (e) {
+ console.error('error exec sql file:' + allSqlFile[i]['path']);
+ throw e;
+ }
}
})()
.then(function(){
diff --git a/bin/www b/bin/www
index 8203dcb6..4f086a7b 100755
--- a/bin/www
+++ b/bin/www
@@ -4,19 +4,36 @@
* Module dependencies.
*/
-var app = require('../app');
-var debug = require('debug')('codepush:server');
+var log4js = require('log4js');
var http = require('http');
var validator = require('validator')
var _ = require('lodash')
+var config = require('../core/config');
+var constConfig = require('../core/const');
+log4js.configure(_.get(config, 'log4js', {
+ appenders: {console: { type: 'console'}},
+ categories : { default: { appenders: ['console'], level: 'info' }}
+}));
+var log = log4js.getLogger("startup")
+
+var app = require('../app');
+
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
+log.debug('port '+ port);
+
var host = null;
-if (process.env.HOST && validator.isIP(process.env.HOST)) {
- host = process.env.HOST;
+if (process.env.HOST) {
+ log.debug('process.env.HOST '+ process.env.HOST);
+ if (validator.isIP(process.env.HOST)) {
+ log.trace(process.env.HOST + ' valid');
+ host = process.env.HOST;
+ } else {
+ log.warn('process.env.HOST '+ process.env.HOST + ' invalid, use 0.0.0.0 instead');
+ }
}
app.set('port', port);
@@ -32,8 +49,8 @@ var server = http.createServer(app);
var models = require('../models');
models.Versions.findOne({where:{type:1}})
.then(function(v){
- if (!v || v.get('version') != '0.2.15') {
- throw new Error(`Please upgrade your database. usage bin/db upgrade or code-push-server-db upgrade`);
+ if (!v || v.get('version') != constConfig.CURRENT_DB_VERSION) {
+ throw new Error('Please upgrade your database. usage `npm run upgrade` or `code-push-server-db upgrade`');
}
server.listen(port, host);
server.on('error', onError);
@@ -42,9 +59,9 @@ models.Versions.findOne({where:{type:1}})
})
.catch(function(e){
if (_.startsWith(e.message, 'ER_NO_SUCH_TABLE')) {
- console.error(`Please upgrade your database. usage bin/db upgrade or code-push-server-db upgrade`);
+ log.error(new Error(`Please upgrade your database. usage bin/db upgrade or code-push-server-db upgrade`));
} else {
- console.error(e);
+ log.error(e);
}
process.exit(1);
});
@@ -85,11 +102,11 @@ function onError(error) {
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
- console.error(bind + ' requires elevated privileges');
+ log.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
- console.error(bind + ' is already in use');
+ log.error(bind + ' is already in use');
process.exit(1);
break;
default:
@@ -106,5 +123,5 @@ function onListening() {
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
- debug('Listening on ' + bind);
+ log.info('Listening on ' + bind);
}
diff --git a/config/config.js b/config/config.js
index 75254bc7..0789b5ee 100644
--- a/config/config.js
+++ b/config/config.js
@@ -1,3 +1,5 @@
+var os = require('os');
+
var config = {};
config.development = {
// Config for database, only support mysql.
@@ -7,7 +9,9 @@ config.development = {
database: process.env.DATA_BASE || "codepush",
host: process.env.RDS_HOST || "127.0.0.1",
port: process.env.RDS_PORT || 3306,
- dialect: "mysql"
+ dialect: "mysql",
+ logging: false,
+ operatorsAliases: false,
},
// Config for qiniu (http://www.qiniu.com/) cloud storage when storageType value is "qiniu".
qiniu: {
@@ -16,8 +20,19 @@ config.development = {
bucketName: "",
downloadUrl: "" // Binary files download host address.
},
+ // Config for upyun (https://www.upyun.com/) storage when storageType value is "upyun"
+ upyun: {
+ storageDir: process.env.UPYUN_STORAGE_DIR,
+ serviceName: process.env.UPYUN_SERVICE_NAME,
+ operatorName: process.env.UPYUN_OPERATOR_NAME,
+ operatorPass: process.env.UPYUN_OPERATOR_PASS,
+ downloadUrl: process.env.DOWNLOAD_URL,
+ },
// Config for Amazon s3 (https://aws.amazon.com/cn/s3/) storage when storageType value is "s3".
s3: {
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
+ sessionToken: process.env.AWS_SESSION_TOKEN, //(optional)
bucketName: process.env.BUCKET_NAME,
region: process.env.REGION,
downloadUrl: process.env.DOWNLOAD_URL, // binary files download host address.
@@ -31,14 +46,22 @@ config.development = {
prefix: "", // Key prefix in object key
downloadUrl: "", // binary files download host address.
},
+ // Config for tencentyun COS (https://cloud.tencent.com/product/cos) when storageType value is "oss".
+ tencentcloud: {
+ accessKeyId: "",
+ secretAccessKey: "",
+ bucketName: "",
+ region: "",
+ downloadUrl: "", // binary files download host address.
+ },
// Config for local storage when storageType value is "local".
local: {
// Binary files storage dir, Do not use tmpdir and it's public download dir.
storageDir: process.env.STORAGE_DIR || "/Users/tablee/workspaces/storage",
// Binary files download host address which Code Push Server listen to. the files storage in storageDir.
- downloadUrl: process.env.LOCAL_DOWNLOAD_URL || "http://localhost:3000/download",
+ downloadUrl: process.env.LOCAL_DOWNLOAD_URL || "http://127.0.0.1:3000/download",
// public static download spacename.
- public: process.env.PUBLIC || '/download'
+ public: '/download'
},
jwt: {
// Recommended: 63 random alpha-numeric characters
@@ -53,19 +76,21 @@ config.development = {
*/
tryLoginTimes: 0,
// CodePush Web(https://github.com/lisong/code-push-web) login address.
- //codePushWebUrl: "http://localhost:3001/login",
+ //codePushWebUrl: "http://127.0.0.1:3001/login",
// create patch updates's number. default value is 3
diffNums: 3,
// data dir for caclulate diff files. it's optimization.
- dataDir: process.env.DATA_DIR || "/Users/tablee/workspaces/data",
- // storageType which is your binary package files store. options value is ("local" | "qiniu" | "s3")
+ dataDir: process.env.DATA_DIR || os.tmpdir(),
+ // storageType which is your binary package files store. options value is ("local" | "qiniu" | "s3"| "oss" || "tencentcloud")
storageType: process.env.STORAGE_TYPE || "local",
// options value is (true | false), when it's true, it will cache updateCheck results in redis.
- updateCheckCache: false
+ updateCheckCache: false,
+ // options value is (true | false), when it's true, it will cache rollout results in redis
+ rolloutClientUniqueIdCache: false,
},
// Config for smtp email,register module need validate user email project source https://github.com/nodemailer/nodemailer
smtpConfig:{
- host: "smtp.mxhichina.com",
+ host: "smtp.aliyun.com",
port: 465,
secure: true,
auth: {
@@ -97,5 +122,15 @@ config.development = {
}
}
}
+
+config.development.log4js = {
+ appenders: {console: { type: 'console'}},
+ categories : {
+ "default": { appenders: ['console'], level:'error'},
+ "startup": { appenders: ['console'], level:'info'},
+ "http": { appenders: ['console'], level:'info'}
+ }
+}
+
config.production = Object.assign({}, config.development);
module.exports = config;
diff --git a/config/config.test.js b/config/config.test.js
index 4e0722be..22990cff 100644
--- a/config/config.test.js
+++ b/config/config.test.js
@@ -8,11 +8,13 @@ config.test = {
database: "codepush_test",
host: "127.0.0.1",
port: 3306,
- dialect: "mysql"
+ dialect: "mysql",
+ logging: false,
+ operatorsAliases: false,
},
local: {
storageDir: os.tmpdir(),
- downloadUrl: "http://localhost:3000/download",
+ downloadUrl: "http://127.0.0.1:3000/download",
public: '/download'
},
jwt: {
@@ -23,7 +25,8 @@ config.test = {
diffNums: 3,
dataDir: os.tmpdir(),
storageType: "local",
- updateCheckCache: true
+ updateCheckCache: true,
+ rolloutClientUniqueIdCache: false,
},
smtpConfig: false,
redis: {
@@ -45,4 +48,12 @@ config.test = {
}
}
}
+config.test.log4js = {
+ appenders: {console: { type: 'console'}},
+ categories : {
+ "default": { appenders: ['console'], level:'error'},
+ "startup": { appenders: ['console'], level:'info'},
+ "http": { appenders: ['console'], level:'info'}
+ }
+}
module.exports = config;
diff --git a/config/config.testwin.js b/config/config.testwin.js
index 24d017cd..2f5fa96d 100644
--- a/config/config.testwin.js
+++ b/config/config.testwin.js
@@ -8,11 +8,13 @@ config.test = {
database: "codepush_test",
host: "127.0.0.1",
port: 3306,
- dialect: "mysql"
+ dialect: "mysql",
+ logging: false,
+ operatorsAliases: false,
},
local: {
storageDir: os.tmpdir(),
- downloadUrl: "http://localhost:3000/download",
+ downloadUrl: "http://127.0.0.1:3000/download",
public: '/download'
},
jwt: {
@@ -23,7 +25,8 @@ config.test = {
diffNums: 3,
dataDir: os.tmpdir(),
storageType: "local",
- updateCheckCache: true
+ updateCheckCache: true,
+ rolloutClientUniqueIdCache: false,
},
smtpConfig: false,
redis: {
@@ -46,4 +49,12 @@ config.test = {
}
}
}
+config.test.log4js = {
+ appenders: {console: { type: 'console'}},
+ categories : {
+ "default": { appenders: ['console'], level:'error'},
+ "startup": { appenders: ['console'], level:'info'},
+ "http": { appenders: ['console'], level:'info'}
+ }
+}
module.exports = config;
diff --git a/core/app-error.js b/core/app-error.js
new file mode 100644
index 00000000..178a573b
--- /dev/null
+++ b/core/app-error.js
@@ -0,0 +1,35 @@
+var util = require('util')
+
+var AppError = function (msg, constr) {
+ if(msg) {
+ msg = msg.toString();
+ }
+ Error.captureStackTrace(this, constr || this)
+ this.message = msg || 'Error'
+ this.name = 'AppError'
+ this.status = 200
+}
+util.inherits(AppError, Error)
+
+var NotFoundError = function(msg) {
+ NotFoundError.super_.call(this, msg, this.constructor)
+ this.message = msg || 'Not Found';
+ this.name = 'NotFoundError'
+ this.status = 404
+}
+util.inherits(NotFoundError, AppError)
+
+var UnauthorizedError = function(msg) {
+ UnauthorizedError.super_.call(this, msg, this.constructor)
+ this.message = msg || `401 Unauthorized`;
+ this.name = 'UnauthorizedError'
+ this.status = 401
+}
+util.inherits(UnauthorizedError, AppError)
+
+module.exports = {
+ AppError: AppError,
+ NotFound: NotFoundError,
+ Unauthorized: UnauthorizedError
+}
+
diff --git a/core/config.js b/core/config.js
index ca9f46f5..a67eae86 100644
--- a/core/config.js
+++ b/core/config.js
@@ -1,11 +1,17 @@
-var env = process.env.NODE_ENV || 'development';
+var env = process.env.NODE_ENV || 'development';
var _ = require('lodash');
var path = require('path');
-var config = {};
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:config");
+var CONFIG_PATH = path.join(__dirname, '../config/config.js');
if (process.env.CONFIG_FILE) {
- var CONFIG_PATH = path.join(__dirname, path.relative(__dirname, process.env.CONFIG_FILE));
- config = _.get(require(CONFIG_PATH), env);
-} else {
- config = _.get(require('../config/config.js'), env);
+ CONFIG_PATH = path.join(__dirname, path.relative(__dirname, process.env.CONFIG_FILE));
+ log.info(`process.env.CONFIG_FILE value: ${process.env.CONFIG_FILE}`)
+}
+log.info(`use config file ${CONFIG_PATH}`)
+log.info(`use env ${env}`)
+var config = _.get(require(CONFIG_PATH), env);
+if (_.isEmpty(config)) {
+ throw new Error(`config is {}, check the env and config`);
}
module.exports = config;
diff --git a/core/const.js b/core/const.js
new file mode 100644
index 00000000..f1d9b23b
--- /dev/null
+++ b/core/const.js
@@ -0,0 +1,49 @@
+function define(name, value) {
+ Object.defineProperty(exports, name, {
+ value: value,
+ enumerable: true
+ });
+}
+
+//定义支持的平台
+define("IOS", 1);
+define("IOS_NAME", 'iOS');
+define("ANDROID", 2);
+define("ANDROID_NAME", 'Android');
+define("WINDOWS", 3);
+define("WINDOWS_NAME", 'Windows');
+
+//定义支持的应用类型
+define("REACT_NATIVE", 1);
+define("REACT_NATIVE_NAME", 'React-Native');
+define("CORDOVA", 2);
+define("CORDOVA_NAME", 'Cordova');
+
+define("PRODUCTION", 'Production');
+define("STAGING", 'Staging');
+
+
+define("IS_MANDATORY_YES", 1);
+define("IS_MANDATORY_NO", 0);
+
+
+define("IS_DISABLED_YES", 1);
+define("IS_DISABLED_NO", 0);
+
+
+define("RELEAS_EMETHOD_PROMOTE", 'Promote');
+define("RELEAS_EMETHOD_UPLOAD", 'Upload');
+
+define("DEPLOYMENT_SUCCEEDED", 1);
+define("DEPLOYMENT_FAILED", 2);
+
+define("DIFF_MANIFEST_FILE_NAME", 'hotcodepush.json');
+
+//文本文件是否使用google diff-match-patch 计算差异
+define("IS_USE_DIFF_TEXT_NO", 0);
+define("IS_USE_DIFF_TEXT_YES", 1);
+
+
+define("CURRENT_DB_VERSION", '0.5.0');
+
+
diff --git a/core/middleware.js b/core/middleware.js
index 48e79d1c..fc4054f2 100644
--- a/core/middleware.js
+++ b/core/middleware.js
@@ -4,63 +4,67 @@ var Promise = require('bluebird');
var security = require('../core/utils/security');
var models = require('../models');
var moment = require('moment');
+var AppError = require('./app-error')
var middleware = module.exports
-const UNAUTHORIZED_TEXT = `401 Unauthorized`;
-
var checkAuthToken = function (authToken) {
var objToken = security.parseToken(authToken);
return models.Users.findOne({
where: {identical: objToken.identical}
})
- .then(function(users) {
+ .then((users) => {
if (_.isEmpty(users)) {
- throw new Error(UNAUTHORIZED_TEXT);
+ throw new AppError.Unauthorized();
}
+ var Sequelize = require('sequelize');
return models.UserTokens.findOne({
- where: {tokens: authToken, uid: users.id, expires_at: { gt: moment().format('YYYY-MM-DD HH:mm:ss') }}
+ where: {tokens: authToken, uid: users.id, expires_at: { [Sequelize.Op.gt]: moment().format('YYYY-MM-DD HH:mm:ss') }}
})
- .then(function(tokenInfo){
+ .then((tokenInfo) => {
if (_.isEmpty(tokenInfo)){
- throw new Error(UNAUTHORIZED_TEXT)
+ throw new AppError.Unauthorized()
}
return users;
})
- }).then(function (users) {
+ }).then((users) => {
return users;
})
}
var checkAccessToken = function (accessToken) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
if (_.isEmpty(accessToken)) {
- throw new Error(UNAUTHORIZED_TEXT);
+ return reject(new AppError.Unauthorized());
}
var config = require('../core/config');
var tokenSecret = _.get(config, 'jwt.tokenSecret');
var jwt = require('jsonwebtoken');
- var authData = jwt.verify(accessToken, tokenSecret);
+ try {
+ var authData = jwt.verify(accessToken, tokenSecret);
+ } catch (e) {
+ return reject(new AppError.Unauthorized());
+ }
var uid = _.get(authData, 'uid', null);
var hash = _.get(authData, 'hash', null);
if (parseInt(uid) > 0) {
return models.Users.findOne({
where: {id: uid}
})
- .then(function(users) {
+ .then((users) => {
if (_.isEmpty(users)) {
- throw new Error(UNAUTHORIZED_TEXT);
+ throw new AppError.Unauthorized();
}
if (!_.eq(hash, security.md5(users.get('ack_code')))){
- throw new Error(UNAUTHORIZED_TEXT);
+ throw new AppError.Unauthorized();
}
resolve(users);
})
- .catch(function (e) {
+ .catch((e) => {
reject(e);
});
} else {
- throw new Error(UNAUTHORIZED_TEXT);
+ reject(new AppError.Unauthorized());
}
});
}
@@ -70,38 +74,47 @@ middleware.checkToken = function(req, res, next) {
var authType = 1;
var authToken = null;
if (_.eq(authArr[0], 'Bearer')) {
- authType = 1;
authToken = authArr[1]; //Bearer
+ if (authToken && authToken.length > 64) {
+ authType = 2;
+ } else {
+ authType = 1;
+ }
} else if(_.eq(authArr[0], 'Basic')) {
authType = 2;
var b = new Buffer(authArr[1], 'base64');
var user = _.split(b.toString(), ':');
authToken = _.get(user, '1');
- } else {
- authType = 2;
- authToken = _.trim(_.trimStart(_.get(req, 'query.access_token', null)));
}
- if (authType == 1) {
+ if (authToken && authType == 1) {
checkAuthToken(authToken)
- .then(function(users) {
+ .then((users) => {
req.users = users;
next();
return users;
})
- .catch(function (e) {
- res.status(401).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(e.status || 404).send(e.message);
+ } else {
+ next(e);
+ }
});
- } else if (authType == 2) {
+ } else if (authToken && authType == 2) {
checkAccessToken(authToken)
- .then(function(users) {
+ .then((users) => {
req.users = users;
next();
return users;
})
- .catch(function (e) {
- res.status(401).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(e.status || 404).send(e.message);
+ } else {
+ next(e);
+ }
});
} else {
- res.status(401).send(UNAUTHORIZED_TEXT);
+ res.send(new AppError.Unauthorized(`Auth type not supported.`));
}
};
diff --git a/core/services/account-manager.js b/core/services/account-manager.js
index 4b12cd81..0eae4a9e 100644
--- a/core/services/account-manager.js
+++ b/core/services/account-manager.js
@@ -8,6 +8,9 @@ var factory = require('../utils/factory');
var moment = require('moment');
var EmailManager = require('./email-manager');
var config = require('../config');
+var AppError = require('../app-error');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:AccountManager");
var proto = module.exports = function (){
function AccountManager() {
@@ -19,19 +22,26 @@ var proto = module.exports = function (){
proto.collaboratorCan = function(uid, appName) {
return this.getCollaborator(uid, appName)
- .then(function (data) {
+ .then((data) => {
if (!data) {
- throw new Error(`App ${appName} not exists.`);
+ log.debug(`collaboratorCan App ${appName} not exists.`);
+ throw new AppError.AppError(`App ${appName} not exists.`);
}
+ log.debug('collaboratorCan yes');
return data;
});
};
proto.ownerCan = function(uid, appName) {
return this.getCollaborator(uid, appName)
- .then(function (data) {
- if (!data || !_.eq(_.get(data,'roles'), 'Owner') ) {
- throw new Error("Permission Deny!");
+ .then((data) => {
+ if (!data) {
+ log.debug(`ownerCan App ${appName} not exists.`);
+ throw new AppError.AppError(`App ${appName} not exists.`);
+ }
+ if (!_.eq(_.get(data,'roles'), 'Owner') ) {
+ log.debug(`ownerCan Permission Deny, You are not owner!`);
+ throw new AppError.AppError("Permission Deny, You are not owner!");
}
return data;
});
@@ -43,9 +53,9 @@ proto.getCollaborator = function (uid, appName) {
proto.findUserByEmail = function (email) {
return models.Users.findOne({where: {email: email}})
- .then(function (data) {
+ .then((data) => {
if (_.isEmpty(data)) {
- throw new Error(email + " does not exist.");
+ throw new AppError.AppError(email + " does not exist.");
} else {
return data;
}
@@ -54,16 +64,14 @@ proto.findUserByEmail = function (email) {
proto.getAllAccessKeyByUid = function (uid) {
return models.UserTokens.findAll({where: {uid: uid}, order:[['id', 'DESC']]})
- .then(function (tokens) {
+ .then((tokens) => {
return _.map(tokens, function(v){
return {
- id: v.id + "",
name: '(hidden)',
createdTime: parseInt(moment(v.created_at).format('x')),
createdBy: v.created_by,
expires: parseInt(moment(v.expires_at).format('x')),
friendlyName: v.name,
- isSession: v.is_session == 0 ? false : true,
description: v.description,
};
});
@@ -76,13 +84,12 @@ proto.isExsitAccessKeyName = function (uid, friendlyName) {
});
};
-proto.createAccessKey = function (uid, newAccessKey, isSession, ttl, friendlyName, createdBy, description) {
+proto.createAccessKey = function (uid, newAccessKey, ttl, friendlyName, createdBy, description) {
return models.UserTokens.create({
uid: uid,
name: friendlyName,
tokens: newAccessKey,
description: description,
- is_session: isSession ? 1 : 0,
created_by: createdBy,
expires_at: moment().add(ttl/1000, 'seconds').format('YYYY-MM-DD HH:mm:ss'),
created_at: moment().format('YYYY-MM-DD HH:mm:ss'),
@@ -93,10 +100,10 @@ const LOGIN_LIMIT_PRE = 'LOGIN_LIMIT_PRE_';
proto.login = function (account, password) {
if (_.isEmpty(account)) {
- return Promise.reject(new Error("请您输入邮箱地址"))
+ return Promise.reject(new AppError.AppError("请您输入邮箱地址"))
}
if (_.isEmpty(password)) {
- return Promise.reject(new Error("请您输入密码"))
+ return Promise.reject(new AppError.AppError("请您输入密码"))
}
var where = {};
if (validator.isEmail(account)) {
@@ -106,45 +113,47 @@ proto.login = function (account, password) {
}
var tryLoginTimes = _.get(config, 'common.tryLoginTimes', 0);
return models.Users.findOne({where: where})
- .then(function(users) {
+ .then((users) => {
if (_.isEmpty(users)) {
- throw new Error("您输入的邮箱或密码有误");
+ throw new AppError.AppError("您输入的邮箱或密码有误");
}
return users;
})
- .then(function (users) {
+ .then((users) => {
if (tryLoginTimes > 0) {
var loginKey = `${LOGIN_LIMIT_PRE}${users.id}`;
var client = factory.getRedisClient("default");
return client.getAsync(loginKey)
- .then(function (loginErrorTimes) {
+ .then((loginErrorTimes) => {
if (loginErrorTimes > tryLoginTimes) {
- throw new Error(`您输入密码错误次数超过限制,帐户已经锁定`);
+ throw new AppError.AppError(`您输入密码错误次数超过限制,帐户已经锁定`);
}
return users;
- });
+ })
+ .finally(() => client.quit());
} else {
return users;
}
})
- .then(function (users) {
+ .then((users) => {
if (!security.passwordVerifySync(password, users.password)) {
if (tryLoginTimes > 0) {
var loginKey = `${LOGIN_LIMIT_PRE}${users.id}`;
var client = factory.getRedisClient("default");
client.existsAsync(loginKey)
- .then(function (isExists) {
+ .then((isExists) => {
if (!isExists) {
var expires = moment().endOf('day').format('X') - moment().format('X');
return client.setexAsync(loginKey, expires, 0);
}
return isExists;
})
- .then(function () {
+ .then(() => {
return client.incrAsync(loginKey);
- });
+ })
+ .finally(() => client.quit());
}
- throw new Error("您输入的邮箱或密码有误");
+ throw new AppError.AppError("您输入的邮箱或密码有误");
} else {
return users;
}
@@ -157,23 +166,25 @@ const EXPIRED_SPEED = 10;
proto.sendRegisterCode = function (email) {
if (_.isEmpty(email)) {
- return Promise.reject(new Error("请您输入邮箱地址"));
+ return Promise.reject(new AppError.AppError("请您输入邮箱地址"));
}
return models.Users.findOne({where: {email: email}})
- .then(function (u) {
+ .then((u) => {
if (u) {
- throw new Error(`"${email}" 已经注册过,请更换邮箱注册`);
+ throw new AppError.AppError(`"${email}" 已经注册过,请更换邮箱注册`);
}
})
- .then(function () {
+ .then(() => {
//将token临时存储到redis
var token = security.randToken(40);
- return factory.getRedisClient("default").setexAsync(`${REGISTER_CODE}${security.md5(email)}`, EXPIRED, token)
- .then(function () {
+ var client = factory.getRedisClient("default");
+ return client.setexAsync(`${REGISTER_CODE}${security.md5(email)}`, EXPIRED, token)
+ .then(() => {
return token;
- });
+ })
+ .finally(() => client.quit());
})
- .then(function (token) {
+ .then((token) => {
//将token发送到用户邮箱
var emailManager = new EmailManager();
return emailManager.sendRegisterCode(email, token);
@@ -182,28 +193,29 @@ proto.sendRegisterCode = function (email) {
proto.checkRegisterCode = function (email, token) {
return models.Users.findOne({where: {email: email}})
- .then(function (u) {
+ .then((u) => {
if (u) {
- throw new Error(`"${email}" 已经注册过,请更换邮箱注册`);
+ throw new AppError.AppError(`"${email}" 已经注册过,请更换邮箱注册`);
}
})
- .then(function () {
+ .then(() => {
var registerKey = `${REGISTER_CODE}${security.md5(email)}`;
var client = factory.getRedisClient("default");
return client.getAsync(registerKey)
- .then(function (storageToken) {
+ .then((storageToken) => {
if (_.isEmpty(storageToken)) {
- throw new Error(`验证码已经失效,请您重新获取`);
+ throw new AppError.AppError(`验证码已经失效,请您重新获取`);
}
if (!_.eq(token, storageToken)) {
client.ttlAsync(registerKey)
- .then(function (ttl) {
+ .then((ttl) => {
if (ttl > 0) {
return client.expireAsync(registerKey, ttl - EXPIRED_SPEED);
}
return ttl;
})
- throw new Error(`您输入的验证码不正确,请重新输入`);
+ .finally(() => client.quit());
+ throw new AppError.AppError(`您输入的验证码不正确,请重新输入`);
}
return storageToken;
})
@@ -212,12 +224,12 @@ proto.checkRegisterCode = function (email, token) {
proto.register = function (email, password) {
return models.Users.findOne({where: {email: email}})
- .then(function (u) {
+ .then((u) => {
if (u) {
- throw new Error(`"${email}" 已经注册过,请更换邮箱注册`);
+ throw new AppError.AppError(`"${email}" 已经注册过,请更换邮箱注册`);
}
})
- .then(function () {
+ .then(() => {
var identical = security.randToken(9);
return models.Users.create({
email: email,
@@ -229,19 +241,19 @@ proto.register = function (email, password) {
proto.changePassword = function (uid, oldPassword, newPassword) {
if (!_.isString(newPassword) || newPassword.length < 6) {
- return Promise.reject(new Error("请您输入6~20位长度的新密码"));
+ return Promise.reject(new AppError.AppError("请您输入6~20位长度的新密码"));
}
return models.Users.findOne({where: {id: uid}})
- .then(function (u) {
+ .then((u) => {
if (!u) {
- throw new Error(`未找到用户信息`);
+ throw new AppError.AppError(`未找到用户信息`);
}
return u;
})
- .then(function (u) {
+ .then((u) => {
var isEq = security.passwordVerifySync(oldPassword, u.get('password'));
if (!isEq) {
- throw new Error(`您输入的旧密码不正确,请重新输入`);
+ throw new AppError.AppError(`您输入的旧密码不正确,请重新输入`);
}
u.set('password', security.passwordHashSync(newPassword));
u.set('ack_code', security.randToken(5));
diff --git a/core/services/app-manager.js b/core/services/app-manager.js
index cceeda01..b3a56811 100644
--- a/core/services/app-manager.js
+++ b/core/services/app-manager.js
@@ -3,6 +3,7 @@ var Promise = require('bluebird');
var models = require('../../models');
var _ = require('lodash');
var security = require('../../core/utils/security');
+var AppError = require('../app-error');
var proto = module.exports = function (){
function AppManager() {
@@ -16,21 +17,24 @@ proto.findAppByName = function (uid, appName) {
return models.Apps.findOne({where: {name: appName, uid: uid}});
};
-proto.addApp = function (uid, appName, identical) {
- return models.sequelize.transaction(function (t) {
+proto.addApp = function (uid, appName, os, platform, identical) {
+ return models.sequelize.transaction((t) => {
return models.Apps.create({
name: appName,
- uid: uid
+ uid: uid,
+ os: os,
+ platform: platform
},{
transaction: t
})
- .then(function (apps) {
+ .then((apps) => {
+ var constName = require('../const');
var appId = apps.id;
var deployments = [];
var deploymentKey = security.randToken(28) + identical;
deployments.push({
appid: appId,
- name: 'Production',
+ name: constName.PRODUCTION,
last_deployment_version_id: 0,
label_id: 0,
deployment_key: deploymentKey
@@ -38,7 +42,7 @@ proto.addApp = function (uid, appName, identical) {
deploymentKey = security.randToken(28) + identical;
deployments.push({
appid: appId,
- name: 'Staging',
+ name: constName.STAGING,
last_deployment_version_id: 0,
label_id: 0,
deployment_key: deploymentKey
@@ -52,7 +56,7 @@ proto.addApp = function (uid, appName, identical) {
};
proto.deleteApp = function (appId) {
- return models.sequelize.transaction(function (t) {
+ return models.sequelize.transaction((t) => {
return Promise.all([
models.Apps.destroy({where: {id: appId}, transaction: t}),
models.Collaborators.destroy({where: {appid: appId}, transaction: t}),
@@ -63,16 +67,16 @@ proto.deleteApp = function (appId) {
proto.modifyApp = function (appId, params) {
return models.Apps.update(params, {where: {id:appId}})
- .spread(function (affectedCount, affectedRows) {
+ .spread((affectedCount, affectedRows) => {
if (!_.gt(affectedCount, 0)) {
- throw Error('modify errors');
+ throw AppError.AppError('modify errors');
}
return affectedCount;
});
};
proto.transferApp = function (appId, fromUid, toUid) {
- return models.sequelize.transaction(function (t) {
+ return models.sequelize.transaction((t) => {
return Promise.all([
models.Apps.update({uid: toUid}, {where: {id: appId}, transaction: t}),
models.Collaborators.destroy({where: {appid: appId, uid: fromUid}, transaction: t}),
@@ -85,18 +89,32 @@ proto.transferApp = function (appId, fromUid, toUid) {
proto.listApps = function (uid) {
const self = this;
return models.Collaborators.findAll({where : {uid: uid}})
- .then(function(data){
+ .then((data) => {
if (_.isEmpty(data)){
return [];
} else {
- var appIds = _.map(data, function(v){ return v.appid });
- return models.Apps.findAll({where: {id: {in: appIds}}});
+ var appIds = _.map(data, (v) => { return v.appid });
+ var Sequelize = require('sequelize');
+ return models.Apps.findAll({where: {id: {[Sequelize.Op.in]: appIds}}});
}
})
- .then(function (appInfos) {
- var rs = Promise.map(_.values(appInfos), function(v){
+ .then((appInfos) => {
+ var rs = Promise.map(_.values(appInfos), (v) => {
return self.getAppDetailInfo(v, uid)
- .then(function (info) {
+ .then((info) => {
+ var constName = require('../const');
+ if (info.os == constName.IOS) {
+ info.os = constName.IOS_NAME;
+ } else if (info.os == constName.ANDROID) {
+ info.os = constName.ANDROID_NAME;
+ } else if (info.os == constName.WINDOWS) {
+ info.os = constName.WINDOWS_NAME;
+ }
+ if (info.platform == constName.REACT_NATIVE) {
+ info.platform = constName.REACT_NATIVE_NAME;
+ } else if (info.platform == constName.CORDOVA) {
+ info.platform = constName.CORDOVA_NAME;
+ }
return info;
});
});
@@ -110,11 +128,11 @@ proto.getAppDetailInfo = function (appInfo, currentUid) {
models.Deployments.findAll({where: {appid: appId}}),
models.Collaborators.findAll({where: {appid: appId}}),
])
- .spread(function (deploymentInfos, collaboratorInfos) {
+ .spread((deploymentInfos, collaboratorInfos) => {
return Promise.props({
- collaborators: Promise.reduce(collaboratorInfos, function (allCol, collaborator) {
+ collaborators: Promise.reduce(collaboratorInfos, (allCol, collaborator) => {
return models.Users.findOne({where: {id: collaborator.get('uid')}})
- .then(function (u) {
+ .then((u) => {
var isCurrentAccount = false;
if (_.eq(u.get('id'), currentUid)) {
isCurrentAccount = true;
@@ -124,11 +142,13 @@ proto.getAppDetailInfo = function (appInfo, currentUid) {
});
}, {}),
- deployments: _.map(deploymentInfos, function (item) {
+ deployments: _.map(deploymentInfos, (item) => {
return _.get(item, 'name');
}),
-
- name: appInfo.get('name')
+ os: appInfo.get('os'),
+ platform: appInfo.get('platform'),
+ name: appInfo.get('name'),
+ id: appInfo.get('id')
});
});
};
diff --git a/core/services/client-manager.js b/core/services/client-manager.js
index 8e27ae79..87a16625 100644
--- a/core/services/client-manager.js
+++ b/core/services/client-manager.js
@@ -4,6 +4,11 @@ var models = require('../../models');
var _ = require('lodash');
var common = require('../utils/common');
var factory = require('../utils/factory');
+var AppError = require('../app-error');
+var config = require('../config');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:ClientManager");
+var Sequelize = require('sequelize');
var proto = module.exports = function (){
function ClientManager() {
@@ -14,6 +19,7 @@ var proto = module.exports = function (){
};
const UPDATE_CHECK = "UPDATE_CHECK";
+const CHOSEN_MAN = "CHOSEN_MAN";
const EXPIRED = 600;
proto.getUpdateCheckCacheKey = function(deploymentKey, appVersion, label, packageHash) {
@@ -21,39 +27,43 @@ proto.getUpdateCheckCacheKey = function(deploymentKey, appVersion, label, packag
}
proto.clearUpdateCheckCache = function(deploymentKey, appVersion, label, packageHash) {
+ log.debug('clear cache Deployments key:', deploymentKey);
let redisCacheKey = this.getUpdateCheckCacheKey(deploymentKey, appVersion, label, packageHash);
var client = factory.getRedisClient("default");
return client.keysAsync(redisCacheKey)
- .then(function(data) {
+ .then((data) => {
if (_.isArray(data)) {
- return Promise.map(data, function(key){
+ return Promise.map(data, (key) => {
return client.delAsync(key);
});
}
return null;
- });
+ })
+ .finally(() => client.quit());
}
-proto.updateCheckFromCache = function(deploymentKey, appVersion, label, packageHash) {
+proto.updateCheckFromCache = function(deploymentKey, appVersion, label, packageHash, clientUniqueId) {
const self = this;
- var updateCheckCache = _.get(require('../config'), 'common.updateCheckCache', false);
+ var updateCheckCache = _.get(config, 'common.updateCheckCache', false);
if (updateCheckCache === false) {
return self.updateCheck(deploymentKey, appVersion, label, packageHash);
}
let redisCacheKey = self.getUpdateCheckCacheKey(deploymentKey, appVersion, label, packageHash);
var client = factory.getRedisClient("default");
return client.getAsync(redisCacheKey)
- .then(function(data){
+ .then((data) => {
if (data) {
try {
+ log.debug('updateCheckFromCache read from catch');
var obj = JSON.parse(data);
return obj;
} catch (e) {
}
}
- return self.updateCheck(deploymentKey, appVersion, label, packageHash)
- .then(function(rs){
+ return self.updateCheck(deploymentKey, appVersion, label, packageHash, clientUniqueId)
+ .then((rs) => {
try {
+ log.debug('updateCheckFromCache read from db');
var strRs = JSON.stringify(rs);
client.setexAsync(redisCacheKey, EXPIRED, strRs);
} catch (e) {
@@ -61,61 +71,137 @@ proto.updateCheckFromCache = function(deploymentKey, appVersion, label, packageH
return rs;
});
})
+ .finally(() => client.quit());
+}
+
+proto.getChosenManCacheKey = function(packageId, rollout, clientUniqueId) {
+ return [CHOSEN_MAN, packageId, rollout, clientUniqueId].join(':');
+}
+
+proto.random = function(rollout) {
+ var r = Math.ceil(Math.random()*10000);
+ if (r < rollout * 100) {
+ return Promise.resolve(true);
+ } else {
+ return Promise.resolve(false);
+ }
+}
+
+proto.chosenMan = function (packageId, rollout, clientUniqueId) {
+ var self = this;
+ if (rollout >= 100) {
+ return Promise.resolve(true);
+ }
+ var rolloutClientUniqueIdCache = _.get(config, 'common.rolloutClientUniqueIdCache', false);
+ if (rolloutClientUniqueIdCache === false) {
+ return self.random(rollout);
+ } else {
+ var client = factory.getRedisClient("default");
+ var redisCacheKey = self.getChosenManCacheKey(packageId, rollout, clientUniqueId);
+ return client.getAsync(redisCacheKey)
+ .then((data) => {
+ if (data == 1) {
+ return true;
+ } else if (data == 2) {
+ return false;
+ } else {
+ return self.random(rollout)
+ .then((r)=>{
+ return client.setexAsync(redisCacheKey, 60*60*24*7, r ? 1:2)
+ .then(()=>{
+ return r;
+ });
+ });
+ }
+ })
+ .finally(() => client.quit());
+ }
}
-proto.updateCheck = function(deploymentKey, appVersion, label, packageHash) {
+proto.updateCheck = function(deploymentKey, appVersion, label, packageHash, clientUniqueId) {
var rs = {
+ packageId: 0,
downloadURL: "",
+ downloadUrl: "",
description: "",
isAvailable: false,
+ isDisabled: true,
isMandatory: false,
appVersion: appVersion,
+ targetBinaryRange: "",
packageHash: "",
label: "",
packageSize: 0,
updateAppVersion: false,
- shouldRunBinaryVersion: false
+ shouldRunBinaryVersion: false,
+ rollout: 100
};
+ var self = this;
if (_.isEmpty(deploymentKey) || _.isEmpty(appVersion)) {
- return Promise.reject(new Error("please input deploymentKey and appVersion"))
+ return Promise.reject(new AppError.AppError("please input deploymentKey and appVersion"))
}
return models.Deployments.findOne({where: {deployment_key: deploymentKey}})
- .then(function (dep) {
+ .then((dep) => {
if (_.isEmpty(dep)) {
- throw new Error('does not found deployment');
+ throw new AppError.AppError('Not found deployment, check deployment key is right.');
}
- return models.DeploymentsVersions.findOne({where: {deployment_id: dep.id, app_version: appVersion}});
+ var version = common.parseVersion(appVersion);
+ return models.DeploymentsVersions.findAll({where: {
+ deployment_id: dep.id,
+ min_version: { [Sequelize.Op.lte]: version },
+ max_version: { [Sequelize.Op.gt]: version }
+ }})
+ .then((deploymentsVersionsMore) => {
+ var distance = 0;
+ var item = null;
+ _.map(deploymentsVersionsMore, function(value, index) {
+ if (index == 0) {
+ item = value;
+ distance = value.max_version - value.min_version;
+ } else {
+ if (distance > (value.max_version - value.min_version)) {
+ distance = value.max_version - value.min_version;
+ item = value;
+ }
+ }
+ });
+ log.debug(item);
+ return item;
+ });
})
- .then(function (deploymentsVersions) {
+ .then((deploymentsVersions) => {
var packageId = _.get(deploymentsVersions, 'current_package_id', 0);
if (_.eq(packageId, 0) ) {
return;
}
return models.Packages.findById(packageId)
- .then(function (packages) {
+ .then((packages) => {
if (packages
&& _.eq(packages.deployment_id, deploymentsVersions.deployment_id)
&& !_.eq(packages.package_hash, packageHash)) {
- rs.downloadURL = common.getBlobDownloadUrl(_.get(packages, 'blob_url'));
+ rs.packageId = packageId;
+ rs.targetBinaryRange = deploymentsVersions.app_version;
+ rs.downloadUrl = rs.downloadURL = common.getBlobDownloadUrl(_.get(packages, 'blob_url'));
rs.description = _.get(packages, 'description', '');
- rs.isAvailable = true;
+ rs.isAvailable = _.eq(packages.is_disabled, 1) ? false : true;
+ rs.isDisabled = _.eq(packages.is_disabled, 1) ? true : false;
rs.isMandatory = _.eq(packages.is_mandatory, 1) ? true : false;
rs.appVersion = appVersion;
rs.packageHash = _.get(packages, 'package_hash', '');
rs.label = _.get(packages, 'label', '');
rs.packageSize = _.get(packages, 'size', 0);
- rs.shouldRunBinaryVersion = false;
+ rs.rollout = _.get(packages, 'rollout', 100);
}
return packages;
})
-
- .then(function (packages) {
- //差异化更新
+ .then((packages) => {
+ //增量更新
if (!_.isEmpty(packages) && !_.eq(_.get(packages, 'package_hash', ""), packageHash)) {
return models.PackagesDiff.findOne({where: {package_id:packages.id, diff_against_package_hash: packageHash}})
- .then(function (diffPackage) {
+ .then((diffPackage) => {
if (!_.isEmpty(diffPackage)) {
rs.downloadURL = common.getBlobDownloadUrl(_.get(diffPackage, 'diff_blob_url'));
+ rs.downloadUrl = common.getBlobDownloadUrl(_.get(diffPackage, 'diff_blob_url'));
rs.packageSize = _.get(diffPackage, 'diff_size', 0);
}
return;
@@ -125,25 +211,25 @@ proto.updateCheck = function(deploymentKey, appVersion, label, packageHash) {
}
});
})
- .then(function () {
+ .then(() => {
return rs;
});
};
proto.getPackagesInfo = function (deploymentKey, label) {
if (_.isEmpty(deploymentKey) || _.isEmpty(label)) {
- return Promise.reject(new Error("please input deploymentKey and appVersion"))
+ return Promise.reject(new AppError.AppError("please input deploymentKey and label"))
}
return models.Deployments.findOne({where: {deployment_key: deploymentKey}})
- .then(function (dep) {
+ .then((dep) => {
if (_.isEmpty(dep)) {
- throw new Error('does not found deployment');
+ throw new AppError.AppError('does not found deployment');
}
return models.Packages.findOne({where: {deployment_id: dep.id, label: label}});
})
- .then(function (packages) {
+ .then((packages) => {
if (_.isEmpty(packages)) {
- throw new Error('does not found packages');
+ throw new AppError.AppError('does not found packages');
}
return packages;
});
@@ -151,27 +237,83 @@ proto.getPackagesInfo = function (deploymentKey, label) {
proto.reportStatusDownload = function(deploymentKey, label, clientUniqueId) {
return this.getPackagesInfo(deploymentKey, label)
- .then(function (packages) {
- return models.PackagesMetrics.addOneOnDownloadById(packages.id);
+ .then((packages) => {
+ return Promise.all([
+ models.PackagesMetrics.findOne({where: {package_id: packages.id}})
+ .then((metrics)=>{
+ if (metrics) {
+ return metrics.increment('downloaded');
+ }
+ return;
+ }),
+ models.LogReportDownload.create({
+ package_id: packages.id,
+ client_unique_id: clientUniqueId
+ })
+ ]);
});
};
proto.reportStatusDeploy = function (deploymentKey, label, clientUniqueId, others) {
return this.getPackagesInfo(deploymentKey, label)
- .then(function (packages) {
- var status = _.get(others, "status");
+ .then((packages) => {
+ var constConfig = require('../const');
+ var statusText = _.get(others, "status");
+ var status = 0;
+ if (_.eq(statusText, "DeploymentSucceeded")) {
+ status = constConfig.DEPLOYMENT_SUCCEEDED;
+ } else if (_.eq(statusText, "DeploymentFailed")) {
+ status = constConfig.DEPLOYMENT_FAILED;
+ }
var packageId = packages.id;
- if (_.eq(status, "DeploymentSucceeded")) {
- return Promise.all([
- models.PackagesMetrics.addOneOnInstalledById(packageId),
- models.PackagesMetrics.addOneOnActiveById(packageId),
- ]);
- } else if (_.eq(status, "DeploymentFailed")) {
+ var previous_deployment_key = _.get(others, 'previousDeploymentKey');
+ var previous_label = _.get(others, 'previousLabelOrAppVersion');
+ if (status > 0) {
return Promise.all([
- models.PackagesMetrics.addOneOnInstalledById(packageId),
- models.PackagesMetrics.addOneOnFailedById(packageId)
- ]);
- }else {
+ models.LogReportDeploy.create({
+ package_id: packageId,
+ client_unique_id: clientUniqueId,
+ previous_label: previous_label,
+ previous_deployment_key: previous_deployment_key,
+ status: status
+ }),
+ models.PackagesMetrics.findOne({where: {package_id: packageId}})
+ .then((metrics)=>{
+ if (_.isEmpty(metrics)) {
+ return;
+ }
+ if (_.eq(status, constConfig.DEPLOYMENT_SUCCEEDED)) {
+ return metrics.increment(['installed', 'active'],{by: 1});
+ } else {
+ return metrics.increment(['installed', 'failed'],{by: 1});
+ }
+ })
+ ])
+ .then(()=>{
+ if (previous_deployment_key && previous_label) {
+ return models.Deployments.findOne({where: {deployment_key: previous_deployment_key}})
+ .then((dep)=>{
+ if (_.isEmpty(dep)) {
+ return;
+ }
+ return models.Packages.findOne({where: {deployment_id: dep.id, label: previous_label}})
+ .then((p)=>{
+ if (_.isEmpty(p)) {
+ return;
+ }
+ return models.PackagesMetrics.findOne({where:{package_id: p.id}});
+ });
+ })
+ .then((metrics)=>{
+ if (metrics) {
+ return metrics.decrement('active');
+ }
+ return;
+ });
+ }
+ return;
+ });
+ } else {
return;
}
});
diff --git a/core/services/collaborators.js b/core/services/collaborators.js
index 49d19300..b2371103 100644
--- a/core/services/collaborators.js
+++ b/core/services/collaborators.js
@@ -1,6 +1,7 @@
'use strict';
var models = require('../../models');
var _ = require('lodash');
+var AppError = require('../app-error');
var proto = module.exports = function (){
function Collaborators() {
@@ -12,16 +13,17 @@ var proto = module.exports = function (){
proto.listCollaborators = function (appId) {
return models.Collaborators.findAll({where: {appid: appId}})
- .then(function (data) {
+ .then((data) => {
return _.reduce(data, function(result, value, key) {
(result['uids'] || (result['uids'] = [])).push(value.uid);
result[value.uid] = value;
return result;
}, []);
})
- .then(function (coInfo) {
- return models.Users.findAll({where: {id: {in: coInfo.uids}}})
- .then(function (data2) {
+ .then((coInfo) => {
+ var Sequelize = require('sequelize');
+ return models.Users.findAll({where: {id: {[Sequelize.Op.in]: coInfo.uids}}})
+ .then((data2) => {
return _.reduce(data2, function (result, value, key) {
var permission = "";
if (!_.isEmpty(coInfo[value.id])) {
@@ -36,7 +38,7 @@ proto.listCollaborators = function (appId) {
proto.addCollaborator = function (appId, uid) {
return models.Collaborators.findOne({where: {appid: appId, uid: uid}})
- .then(function (data) {
+ .then((data) => {
if (_.isEmpty(data)){
return models.Collaborators.create({
appid: appId,
@@ -44,16 +46,16 @@ proto.addCollaborator = function (appId, uid) {
roles: "Collaborator"
});
}else {
- throw new Error('user already is Collaborator.');
+ throw new AppError.AppError('user already is Collaborator.');
}
});
};
proto.deleteCollaborator = function (appId, uid) {
return models.Collaborators.findOne({where: {appid: appId, uid: uid}})
- .then(function (data) {
+ .then((data) => {
if (_.isEmpty(data)){
- throw new Error('user is not a Collaborator');
+ throw new AppError.AppError('user is not a Collaborator');
}else {
return models.Collaborators.destroy({where: {id: data.id}});
}
diff --git a/core/services/datacenter-manager.js b/core/services/datacenter-manager.js
index ab1309d5..c59d29ff 100644
--- a/core/services/datacenter-manager.js
+++ b/core/services/datacenter-manager.js
@@ -8,6 +8,10 @@ var security = require('../utils/security');
var common = require('../utils/common');
const MANIFEST_FILE_NAME = 'manifest.json';
const CONTENTS_NAME = 'contents';
+var AppError = require('../app-error');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:DataCenterManager");
+var path = require('path');
var proto = module.exports = function (){
function DataCenterManager() {
@@ -27,21 +31,21 @@ proto.getDataDir = function () {
proto.hasPackageStoreSync = function (packageHash) {
var dataDir = this.getDataDir();
- var packageHashPath = `${dataDir}/${packageHash}`;
- var manifestFile = `${packageHashPath}/${MANIFEST_FILE_NAME}`;
- var contentPath = `${packageHashPath}/${CONTENTS_NAME}`;
+ var packageHashPath = path.join(dataDir, packageHash);
+ var manifestFile = path.join(packageHashPath, MANIFEST_FILE_NAME);
+ var contentPath = path.join(packageHashPath, CONTENTS_NAME);
return fs.existsSync(manifestFile) && fs.existsSync(contentPath);
}
proto.getPackageInfo = function (packageHash) {
if (this.hasPackageStoreSync(packageHash)){
var dataDir = this.getDataDir();
- var packageHashPath = `${dataDir}/${packageHash}`;
- var manifestFile = `${packageHashPath}/${MANIFEST_FILE_NAME}`;
- var contentPath = `${packageHashPath}/${CONTENTS_NAME}`;
+ var packageHashPath = path.join(dataDir, packageHash);
+ var manifestFile = path.join(packageHashPath, MANIFEST_FILE_NAME);
+ var contentPath = path.join(packageHashPath, CONTENTS_NAME);
return this.buildPackageInfo(packageHash, packageHashPath, contentPath, manifestFile);
} else {
- throw new Error('can\'t get PackageInfo');
+ throw new AppError.AppError('can\'t get PackageInfo');
}
}
@@ -56,52 +60,64 @@ proto.buildPackageInfo = function (packageHash, packageHashPath, contentPath, ma
proto.validateStore = function (providePackageHash) {
var dataDir = this.getDataDir();
- var packageHashPath = `${dataDir}/${providePackageHash}`;
- var manifestFile = `${packageHashPath}/${MANIFEST_FILE_NAME}`;
- var contentPath = `${packageHashPath}/${CONTENTS_NAME}`;
+ var packageHashPath = path.join(dataDir, providePackageHash);
+ var manifestFile = path.join(packageHashPath, MANIFEST_FILE_NAME);
+ var contentPath = path.join(packageHashPath, CONTENTS_NAME);
if (!this.hasPackageStoreSync(providePackageHash)) {
+ log.debug(`validateStore providePackageHash not exist`);
return Promise.resolve(false);
}
return security.calcAllFileSha256(contentPath)
- .then(function (manifestJson) {
+ .then((manifestJson) => {
var packageHash = security.packageHashSync(manifestJson);
+ log.debug(`validateStore packageHash:`, packageHash);
try {
var manifestJsonLocal = JSON.parse(fs.readFileSync(manifestFile));
}catch(e) {
+ log.debug(`validateStore manifestFile contents invilad`);
return false;
}
var packageHashLocal = security.packageHashSync(manifestJsonLocal);
+ log.debug(`validateStore packageHashLocal:`, packageHashLocal);
if (_.eq(providePackageHash, packageHash) && _.eq(providePackageHash, packageHashLocal)) {
+ log.debug(`validateStore store files is ok`);
return true;
}
+ log.debug(`validateStore store files broken`);
return false;
});
}
proto.storePackage = function (sourceDst, force) {
+ log.debug(`storePackage sourceDst:`, sourceDst);
if (_.isEmpty(force)){
force = false;
}
var self = this;
return security.calcAllFileSha256(sourceDst)
- .then(function (manifestJson) {
+ .then((manifestJson) => {
var packageHash = security.packageHashSync(manifestJson);
+ log.debug('storePackage manifestJson packageHash:', packageHash);
var dataDir = self.getDataDir();
- var packageHashPath = `${dataDir}/${packageHash}`;
- var manifestFile = `${packageHashPath}/${MANIFEST_FILE_NAME}`;
- var contentPath = `${packageHashPath}/${CONTENTS_NAME}`;
- if (!force && self.hasPackageStoreSync(packageHash)) {
- return self.buildPackageInfo(packageHash, packageHashPath, contentPath, manifestFile);
- } else {
- return common.createEmptyFolder(packageHashPath)
- .then(function(){
- return common.move(sourceDst, contentPath)
- .then(function () {
- var manifestString = JSON.stringify(manifestJson);
- fs.writeFileSync(manifestFile, manifestString);
- return self.buildPackageInfo(packageHash, packageHashPath, contentPath, manifestFile);
+ var packageHashPath = path.join(dataDir, packageHash);
+ var manifestFile = path.join(packageHashPath, MANIFEST_FILE_NAME);
+ var contentPath = path.join(packageHashPath, CONTENTS_NAME);
+ return self.validateStore(packageHash)
+ .then((isValidate) => {
+ if (!force && isValidate) {
+ return self.buildPackageInfo(packageHash, packageHashPath, contentPath, manifestFile);
+ } else {
+ log.debug(`storePackage cover from sourceDst:`, sourceDst);
+ return common.createEmptyFolder(packageHashPath)
+ .then(() => {
+ return common.copy(sourceDst, contentPath)
+ .then(() => {
+ var manifestString = JSON.stringify(manifestJson);
+ fs.writeFileSync(manifestFile, manifestString);
+ return self.buildPackageInfo(packageHash, packageHashPath, contentPath, manifestFile);
+ });
});
- });
- }
+ }
+ });
});
}
diff --git a/core/services/deployments.js b/core/services/deployments.js
index 570e605f..6e0b19ea 100644
--- a/core/services/deployments.js
+++ b/core/services/deployments.js
@@ -6,6 +6,9 @@ var common = require('../../core/utils/common');
var PackageManager = require('./package-manager');
var _ = require('lodash');
var moment = require('moment');
+var AppError = require('../app-error');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:deployments");
var proto = module.exports = function (){
function Deployments() {
@@ -21,9 +24,9 @@ proto.getAllPackageIdsByDeploymentsId = function(deploymentsId) {
proto.existDeloymentName = function (appId, name) {
return models.Deployments.findOne({where: {appid: appId, name: name}})
- .then(function (data) {
+ .then((data) => {
if (!_.isEmpty(data)){
- throw new Error(name + " name does Exist!")
+ throw new AppError.AppError(name + " name does Exist!")
} else {
return data;
}
@@ -33,12 +36,12 @@ proto.existDeloymentName = function (appId, name) {
proto.addDeloyment = function (name, appId, uid) {
var self = this;
return models.Users.findById(uid)
- .then(function (user) {
+ .then((user) => {
if (_.isEmpty(user)) {
- throw new Error('can\'t find user');
+ throw new AppError.AppError('can\'t find user');
}
return self.existDeloymentName(appId, name)
- .then(function () {
+ .then(() => {
var identical = user.identical;
var deploymentKey = security.randToken(28) + identical;
return models.Deployments.create({
@@ -54,16 +57,16 @@ proto.addDeloyment = function (name, appId, uid) {
proto.renameDeloymentByName = function (deploymentName, appId, newName) {
return this.existDeloymentName(appId, newName)
- .then(function () {
+ .then(() => {
return models.Deployments.update(
{name: newName},
{where: {name: deploymentName,appid: appId}}
)
- .spread(function (affectedCount, affectedRow) {
+ .spread((affectedCount, affectedRow) => {
if (_.gt(affectedCount, 0)) {
return {name: newName};
} else {
- throw new Error(`does not find the deployment "${deploymentName}"`);
+ throw new AppError.AppError(`does not find the deployment "${deploymentName}"`);
}
});
});
@@ -73,16 +76,17 @@ proto.deleteDeloymentByName = function (deploymentName, appId) {
return models.Deployments.destroy({
where: {name: deploymentName, appid: appId}
})
- .then(function (rowNum) {
+ .then((rowNum) => {
if (_.gt(rowNum, 0)) {
return {name: `${deploymentName}`};
} else {
- throw new Error(`does not find the deployment "${deploymentName}"`);
+ throw new AppError.AppError(`does not find the deployment "${deploymentName}"`);
}
});
};
proto.findDeloymentByName = function (deploymentName, appId) {
+ log.debug(`findDeloymentByName name:${deploymentName},appId: ${appId}`);
return models.Deployments.findOne({
where: {name: deploymentName, appid: appId}
});
@@ -92,16 +96,16 @@ proto.findPackagesAndOtherInfos = function (packageId) {
return models.Packages.findOne({
where: {id: packageId}
})
- .then(function (packageInfo) {
+ .then((packageInfo) => {
if (!packageInfo) {
return null;
}
return Promise.props({
packageInfo: packageInfo,
packageDiffMap: models.PackagesDiff.findAll({where: {package_id: packageId}})
- .then(function(diffs){
+ .then((diffs) => {
if (diffs.length > 0) {
- return _.reduce(diffs, function(result, v){
+ return _.reduce(diffs, (result, v) => {
result[_.get(v, 'diff_against_package_hash')] = {
size: _.get(v, 'diff_size'),
url: common.getBlobDownloadUrl(_.get(v, 'diff_blob_url')),
@@ -120,7 +124,7 @@ proto.findPackagesAndOtherInfos = function (packageId) {
proto.findDeloymentsPackages = function (deploymentsVersionsId) {
var self = this;
return models.DeploymentsVersions.findOne({where: {id: deploymentsVersionsId}})
- .then(function(deploymentsVersionsInfo) {
+ .then((deploymentsVersionsInfo) => {
if (deploymentsVersionsInfo) {
return self.findPackagesAndOtherInfos(deploymentsVersionsInfo.current_package_id);
}
@@ -155,59 +159,64 @@ proto.formatPackage = function(packageVersion) {
proto.listDeloyments = function (appId) {
var self = this;
return models.Deployments.findAll({where: {appid: appId}})
- .then(function(deploymentsInfos){
+ .then((deploymentsInfos) => {
if (_.isEmpty(deploymentsInfos)) {
return [];
}
- return Promise.map(deploymentsInfos, function (v) {
- return Promise.props({
- createdTime: parseInt(moment(v.created_at).format('x')),
- id: `${v.id}`,
- key: v.deployment_key,
- name: v.name,
- package: self.findDeloymentsPackages([v.last_deployment_version_id]).then(self.formatPackage)
- });
+ return Promise.map(deploymentsInfos, (v) => {
+ return self.listDeloyment(v);
})
});
};
+proto.listDeloyment = function (deploymentInfo) {
+ const self = this;
+ return Promise.props({
+ createdTime: parseInt(moment(deploymentInfo.created_at).format('x')),
+ id: `${deploymentInfo.id}`,
+ key: deploymentInfo.deployment_key,
+ name: deploymentInfo.name,
+ package: self.findDeloymentsPackages([deploymentInfo.last_deployment_version_id]).then(self.formatPackage)
+ });
+}
+
proto.getDeploymentHistory = function (deploymentId) {
var self = this;
return models.DeploymentsHistory.findAll({where: {deployment_id: deploymentId}, order: [['id','desc']], limit: 15})
- .then(function(history) {
- return _.map(history, function(v){ return v.package_id});
+ .then((history) => {
+ return _.map(history, (v) => { return v.package_id});
})
- .then(function(packageIds){
- return Promise.map(packageIds, function(v) {
+ .then((packageIds) => {
+ return Promise.map(packageIds, (v) => {
return self.findPackagesAndOtherInfos(v).then(self.formatPackage);
});
});
};
proto.deleteDeploymentHistory = function(deploymentId) {
- return models.sequelize.transaction(function (t) {
+ return models.sequelize.transaction((t) => {
return Promise.all([
models.Deployments.update(
{last_deployment_version_id:0,label_id:0},
{where: {id: deploymentId},transaction: t}
),
models.DeploymentsHistory.findAll({where: {deployment_id: deploymentId}, order: [['id','desc']], limit: 1000})
- .then(function(rs){
- return Promise.map(rs, function(v){
+ .then((rs) => {
+ return Promise.map(rs, (v) => {
return v.destroy({transaction: t});
});
}),
models.DeploymentsVersions.findAll({where: {deployment_id: deploymentId}, order: [['id','desc']], limit: 1000})
- .then(function(rs) {
- return Promise.map(rs, function(v){
+ .then((rs) => {
+ return Promise.map(rs, (v) => {
return v.destroy({transaction: t});
});
}),
models.Packages.findAll({where: {deployment_id: deploymentId}, order: [['id','desc']], limit: 1000})
- .then(function(rs) {
- return Promise.map(rs, function(v){
+ .then((rs) => {
+ return Promise.map(rs, (v) => {
return v.destroy({transaction: t})
- .then(function(){
+ .then(() => {
return Promise.all([
models.PackagesMetrics.destroy({where: {package_id: v.get('id')},transaction: t}),
models.PackagesDiff.destroy({where: {package_id: v.get('id')},transaction: t})
diff --git a/core/services/email-manager.js b/core/services/email-manager.js
index 1194f246..2ed3ef67 100644
--- a/core/services/email-manager.js
+++ b/core/services/email-manager.js
@@ -17,9 +17,9 @@ var proto = module.exports = function (){
};
proto.sendMail = function (options) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
if(!_.get(options, 'to')) {
- return reject(new Error("to是必传参数"));
+ return reject(new AppError.AppError("to是必传参数"));
}
var smtpConfig = _.get(config, 'smtpConfig');
if (!smtpConfig) {
diff --git a/core/services/package-manager.js b/core/services/package-manager.js
index 68cc4128..fe259c7c 100644
--- a/core/services/package-manager.js
+++ b/core/services/package-manager.js
@@ -11,6 +11,10 @@ var slash = require("slash");
var common = require('../utils/common');
var os = require('os');
var path = require('path');
+var AppError = require('../app-error');
+var constConfig = require('../const');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:PackageManager");
var proto = module.exports = function (){
function PackageManager() {
@@ -20,84 +24,99 @@ var proto = module.exports = function (){
return PackageManager;
};
-proto.getMetricsbyPackageId= function(packageId) {
+proto.getMetricsbyPackageId = function(packageId) {
return models.PackagesMetrics.findOne({where: {package_id: packageId}});
}
+proto.findPackageInfoByDeploymentIdAndLabel = function (deploymentId, label) {
+ return models.Packages.findOne({where: {deployment_id: deploymentId, label:label}});
+}
+
+proto.findLatestPackageInfoByDeployVersion = function (deploymentsVersionsId) {
+ return models.DeploymentsVersions.findById(deploymentsVersionsId)
+ .then((deploymentsVersions)=>{
+ if (!deploymentsVersions || deploymentsVersions.current_package_id < 0) {
+ var e = new AppError.AppError("not found last packages");
+ log.debug(e);
+ throw e;
+ }
+ return models.Packages.findById(deploymentsVersions.current_package_id);
+ });
+}
+
proto.parseReqFile = function (req) {
- return new Promise(function (resolve, reject) {
+ log.debug('parseReqFile');
+ return new Promise((resolve, reject) => {
var form = new formidable.IncomingForm();
- form.parse(req, function(err, fields, files) {
+ form.maxFieldsSize = 200 * 1024 * 1024;
+ form.parse(req, (err, fields, files) => {
if (err) {
- reject(new Error("upload error"));
+ log.debug('parseReqFile:', err);
+ reject(new AppError.AppError("upload error"));
} else {
- if (_.isEmpty(fields.packageInfo) || _.isEmpty(files.package)) {
- reject(new Error("upload info lack"));
+ log.debug('parseReqFile fields:', fields);
+ log.debug('parseReqFile file location:', _.get(files,'package.path'));
+ if (_.isEmpty(fields.packageInfo) || _.isEmpty(_.get(files,'package'))) {
+ log.debug('parseReqFile upload info lack');
+ reject(new AppError.AppError("upload info lack"));
} else {
- resolve({packageInfo:JSON.parse(fields.packageInfo), package: files.package});
+ log.debug('parseReqFile is ok');
+ resolve({packageInfo: JSON.parse(fields.packageInfo), package: files.package});
}
}
});
});
};
-proto.getDeploymentsVersions = function (deploymentId, appVersion) {
- return models.DeploymentsVersions.findOne({
- where: {deployment_id: deploymentId, app_version: appVersion}
+proto.createDeploymentsVersionIfNotExist = function (deploymentId, appVersion, minVersion, maxVersion, t) {
+ return models.DeploymentsVersions.findOrCreate({
+ where: {deployment_id: deploymentId, app_version: appVersion, min_version:minVersion, max_version:maxVersion},
+ defaults: {current_package_id: 0},
+ transaction: t
+ })
+ .spread((data, created)=>{
+ if (created) {
+ log.debug(`createDeploymentsVersionIfNotExist findOrCreate version ${appVersion}`);
+ }
+ log.debug(`createDeploymentsVersionIfNotExist version data:`, data.get());
+ return data;
});
};
-proto.existPackageHashAndCreateVersions = function (deploymentId, appVersion, packageHash) {
- return this.getDeploymentsVersions(deploymentId, appVersion)
- .then(function (data) {
- if (_.isEmpty(data)){
- return models.DeploymentsVersions.create({
- deployment_id: deploymentId,
- app_version: appVersion,
- }).then(function () {
- return false;
- });
- } else {
- var packageId = data.current_package_id;
- if (_.gt(packageId, 0)) {
- return models.Packages.findById(packageId)
- .then(function (data) {
- if (_.eq(_.get(data,"package_hash"), packageHash)){
- return true;
- }else {
- return false;
- }
- });
- }else {
- return false
- }
+proto.isMatchPackageHash = function (packageId, packageHash) {
+ if (_.lt(packageId, 0)) {
+ log.debug(`isMatchPackageHash packageId is 0`);
+ return Promise.resolve(false);
+ }
+ return models.Packages.findById(packageId)
+ .then((data) => {
+ if (data && _.eq(data.get('package_hash'), packageHash)){
+ log.debug(`isMatchPackageHash data:`, data.get());
+ log.debug(`isMatchPackageHash packageHash exist`);
+ return true;
+ }else {
+ log.debug(`isMatchPackageHash package is null`);
+ return false;
}
});
};
proto.createPackage = function (deploymentId, appVersion, packageHash, manifestHash, blobHash, params) {
- var releaseMethod = params.releaseMethod || 'Upload';
+ var releaseMethod = params.releaseMethod || constConfig.RELEAS_EMETHOD_UPLOAD;
var releaseUid = params.releaseUid || 0;
- var isMandatory = params.isMandatory ? 1 : 0;
+ var isMandatory = params.isMandatory || 0;
var size = params.size || 0;
+ var rollout = params.rollout || 100;
var description = params.description || "";
var originalLabel = params.originalLabel || "";
+ var isDisabled = params.isDisabled || 0;
var originalDeployment = params.originalDeployment || "";
+ var self = this;
return models.Deployments.generateLabelId(deploymentId)
- .then(function (labelId) {
- return models.sequelize.transaction(function (t) {
- return models.DeploymentsVersions.findOne({where: {deployment_id: deploymentId, app_version: appVersion}})
- .then(function (deploymentsVersions) {
- if (!deploymentsVersions) {
- return models.DeploymentsVersions.create({
- current_package_id: 0,
- deployment_id: deploymentId,
- app_version: appVersion
- },{transaction: t});
- }
- return deploymentsVersions;
- })
- .then(function(deploymentsVersions) {
+ .then((labelId) => {
+ return models.sequelize.transaction((t) => {
+ return self.createDeploymentsVersionIfNotExist(deploymentId, appVersion, params.min_version, params.max_version, t)
+ .then((deploymentsVersions) => {
return models.Packages.create({
deployment_version_id: deploymentsVersions.id,
deployment_id: deploymentId,
@@ -110,10 +129,12 @@ proto.createPackage = function (deploymentId, appVersion, packageHash, manifestH
label: "v" + labelId,
released_by: releaseUid,
is_mandatory: isMandatory,
+ is_disabled: isDisabled,
+ rollout: rollout,
original_label: originalLabel,
original_deployment: originalDeployment
},{transaction: t})
- .then(function (packages) {
+ .then((packages) => {
deploymentsVersions.set('current_package_id', packages.id);
return Promise.all([
deploymentsVersions.save({transaction: t}),
@@ -130,9 +151,7 @@ proto.createPackage = function (deploymentId, appVersion, packageHash, manifestH
{transaction: t}
)
])
- .then(function () {
- return packages;
- });
+ .then(() => packages);
});
});
});
@@ -142,15 +161,15 @@ proto.createPackage = function (deploymentId, appVersion, packageHash, manifestH
proto.downloadPackageAndExtract = function (workDirectoryPath, packageHash, blobHash) {
var dataCenterManager = require('./datacenter-manager')();
return dataCenterManager.validateStore(packageHash)
- .then(function (isValidate) {
+ .then((isValidate) => {
if (isValidate) {
return dataCenterManager.getPackageInfo(packageHash);
} else {
var downloadURL = common.getBlobDownloadUrl(blobHash);
- return common.createFileFromRequest(downloadURL, `${workDirectoryPath}/${blobHash}`)
- .then(function (download) {
- return common.unzipFile(`${workDirectoryPath}/${blobHash}`, `${workDirectoryPath}/current`)
- .then(function (outputPath) {
+ return common.createFileFromRequest(downloadURL, path.join(workDirectoryPath, blobHash))
+ .then((download) => {
+ return common.unzipFile(path.join(workDirectoryPath, blobHash), path.join(workDirectoryPath, 'current'))
+ .then((outputPath) => {
return dataCenterManager.storePackage(outputPath, true);
});
});
@@ -159,29 +178,37 @@ proto.downloadPackageAndExtract = function (workDirectoryPath, packageHash, blob
}
proto.zipDiffPackage = function (fileName, files, baseDirectoryPath, hotCodePushFile) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
var zipFile = new yazl.ZipFile();
var writeStream = fs.createWriteStream(fileName);
- writeStream.on('error', function (error) {
+ writeStream.on('error', (error) => {
reject(error);
})
zipFile.outputStream.pipe(writeStream)
- .on("error", function (error) {
+ .on("error", (error) => {
reject(error);
})
- .on("close", function () {
+ .on("close", () => {
resolve({ isTemporary: true, path: fileName });
});
for (var i = 0; i < files.length; ++i) {
var file = files[i];
- zipFile.addFile(`${baseDirectoryPath}/${file}`, slash(file));
+ zipFile.addFile(path.join(baseDirectoryPath, file), slash(file));
}
- zipFile.addFile(hotCodePushFile, 'hotcodepush.json');
+ zipFile.addFile(hotCodePushFile, constConfig.DIFF_MANIFEST_FILE_NAME);
zipFile.end();
});
}
-proto.generateOneDiffPackage = function (workDirectoryPath, packageId, dataCenter, diffPackageHash, diffManifestBlobHash) {
+proto.generateOneDiffPackage = function (
+ workDirectoryPath,
+ packageId,
+ originDataCenter,
+ oldPackageDataCenter,
+ diffPackageHash,
+ diffManifestBlobHash,
+ isUseDiffText
+) {
var self = this;
return models.PackagesDiff.findOne({
where:{
@@ -189,29 +216,59 @@ proto.generateOneDiffPackage = function (workDirectoryPath, packageId, dataCente
diff_against_package_hash: diffPackageHash
}
})
- .then(function (diffPackage) {
+ .then((diffPackage) => {
if (!_.isEmpty(diffPackage)) {
return;
}
+ log.debug('originDataCenter', originDataCenter);
+ log.debug('oldPackageDataCenter', oldPackageDataCenter);
var downloadURL = common.getBlobDownloadUrl(diffManifestBlobHash);
- return common.createFileFromRequest(downloadURL, `${workDirectoryPath}/${diffManifestBlobHash}`)
- .then(function(){
- var originContentPath = dataCenter.contentPath;
- var originManifestJson = JSON.parse(fs.readFileSync(dataCenter.manifestFilePath, "utf8"))
- var diffManifestJson = JSON.parse(fs.readFileSync(`${workDirectoryPath}/${diffManifestBlobHash}`, "utf8"))
+ return common.createFileFromRequest(downloadURL, path.join(workDirectoryPath,diffManifestBlobHash))
+ .then(() => {
+ var dataCenterContentPath = path.join(workDirectoryPath, 'dataCenter');
+ common.copySync(originDataCenter.contentPath, dataCenterContentPath);
+ var oldPackageDataCenterContentPath = oldPackageDataCenter.contentPath;
+ var originManifestJson = JSON.parse(fs.readFileSync(originDataCenter.manifestFilePath, "utf8"))
+ var diffManifestJson = JSON.parse(fs.readFileSync(path.join(workDirectoryPath, diffManifestBlobHash), "utf8"))
var json = common.diffCollectionsSync(originManifestJson, diffManifestJson);
var files = _.concat(json.diff, json.collection1Only);
- var hotcodepush = {deletedFiles: json.collection2Only};
- var hotCodePushFile = `${workDirectoryPath}/${diffManifestBlobHash}_hotcodepush`;
+ var hotcodepush = {deletedFiles: json.collection2Only, patchedFiles:[]};
+ if (isUseDiffText == constConfig.IS_USE_DIFF_TEXT_YES) {
+ //使用google diff-match-patch
+ _.forEach(json.diff, function(tmpFilePath) {
+ var dataCenterContentPathTmpFilePath = path.join(dataCenterContentPath, tmpFilePath);
+ var oldPackageDataCenterContentPathTmpFilePath = path.join(oldPackageDataCenterContentPath, tmpFilePath);
+ if (
+ fs.existsSync(dataCenterContentPathTmpFilePath)
+ && fs.existsSync(oldPackageDataCenterContentPathTmpFilePath)
+ && common.detectIsTextFile(dataCenterContentPathTmpFilePath)
+ && common.detectIsTextFile(oldPackageDataCenterContentPathTmpFilePath)
+ ) {
+ var textOld = fs.readFileSync(oldPackageDataCenterContentPathTmpFilePath, 'utf-8');
+ var textNew = fs.readFileSync(dataCenterContentPathTmpFilePath, 'utf-8');
+ if (!textOld || !textNew) {
+ return;
+ }
+ var DiffMatchPatch = require('diff-match-patch');
+ var dmp = new DiffMatchPatch();
+ var patchs = dmp.patch_make(textOld, textNew);
+ var patchText = dmp.patch_toText(patchs);
+ if (patchText && patchText.length < _.parseInt(textNew.length * 0.8)) {
+ fs.writeFileSync(dataCenterContentPathTmpFilePath, patchText);
+ hotcodepush.patchedFiles.push(tmpFilePath);
+ }
+ }
+ });
+ }
+ var hotCodePushFile = path.join(workDirectoryPath,`${diffManifestBlobHash}_hotcodepush`);;
fs.writeFileSync(hotCodePushFile, JSON.stringify(hotcodepush));
- var fileName = `${workDirectoryPath}/${diffManifestBlobHash}.zip`;
-
- return self.zipDiffPackage(fileName, files, originContentPath, hotCodePushFile)
- .then(function (data) {
+ var fileName = path.join(workDirectoryPath,`${diffManifestBlobHash}.zip`);;
+ return self.zipDiffPackage(fileName, files, dataCenterContentPath, hotCodePushFile)
+ .then((data) => {
return security.qetag(data.path)
- .then(function (diffHash) {
+ .then((diffHash) => {
return common.uploadFileToStorage(diffHash, fileName)
- .then(function () {
+ .then(() => {
var stats = fs.statSync(fileName);
return models.PackagesDiff.create({
package_id: packageId,
@@ -226,41 +283,38 @@ proto.generateOneDiffPackage = function (workDirectoryPath, packageId, dataCente
});
};
-proto.createDiffPackagesByLastNums = function (packageId, num) {
+proto.createDiffPackagesByLastNums = function (appId, originalPackage, num) {
var self = this;
- return models.Packages.findById(packageId)
- .then(function (originalPackage) {
- if (_.isEmpty(originalPackage)) {
- throw Error('can\'t find Package');
- }
- return Promise.all([
- models.Packages.findAll({
- where:{
- deployment_version_id: originalPackage.deployment_version_id,
- id: {$lt: packageId}},
- order: [['id','desc']],
- limit: num
- }),
- models.Packages.findAll({
- where:{
- deployment_version_id: originalPackage.deployment_version_id,
- id: {$lt: packageId}},
- order: [['id','asc']],
- limit: 2
- })
- ])
- .spread(function (lastNumsPackages, basePackages) {
- return _.unionBy(lastNumsPackages, basePackages, 'id');
- })
- .then(function(lastNumsPackages){
- return self.createDiffPackages(originalPackage, lastNumsPackages);
- });
+ var Sequelize = require('sequelize');
+ var packageId = originalPackage.id;
+ return Promise.all([
+ models.Packages.findAll({
+ where:{
+ deployment_version_id: originalPackage.deployment_version_id,
+ id: {[Sequelize.Op.lt]: packageId}},
+ order: [['id','desc']],
+ limit: num
+ }),
+ models.Packages.findAll({
+ where:{
+ deployment_version_id: originalPackage.deployment_version_id,
+ id: {[Sequelize.Op.lt]: packageId}},
+ order: [['id','asc']],
+ limit: 2
+ }),
+ models.Apps.findById(appId),
+ ])
+ .spread((lastNumsPackages, basePackages, appInfo) => {
+ return [_.uniqBy(_.unionBy(lastNumsPackages, basePackages, 'id'), 'package_hash'), appInfo];
+ })
+ .spread((lastNumsPackages, appInfo) => {
+ return self.createDiffPackages(originalPackage, lastNumsPackages, _.get(appInfo, 'is_use_diff_text', constConfig.IS_USE_DIFF_TEXT_NO));
});
};
-proto.createDiffPackages = function (originalPackage, destPackages) {
+proto.createDiffPackages = function (originalPackage, destPackages, isUseDiffText) {
if (!_.isArray(destPackages)) {
- return Promise.reject(new Error('第二个参数必须是数组'));
+ return Promise.reject(new AppError.AppError('第二个参数必须是数组'));
}
if (destPackages.length <= 0) {
return null;
@@ -270,198 +324,302 @@ proto.createDiffPackages = function (originalPackage, destPackages) {
var manifest_blob_url = _.get(originalPackage, 'manifest_blob_url');
var blob_url = _.get(originalPackage, 'blob_url');
var workDirectoryPath = path.join(os.tmpdir(), 'codepush_' + security.randToken(32));
+ log.debug('workDirectoryPath', workDirectoryPath);
return common.createEmptyFolder(workDirectoryPath)
- .then(function(){
- return self.downloadPackageAndExtract(workDirectoryPath, package_hash, blob_url)
- })
- .then(function (dataCenter) {
- return Promise.map(destPackages, function (v) {
- return self.generateOneDiffPackage(workDirectoryPath, originalPackage.id, dataCenter, v.package_hash, v.manifest_blob_url);
- });
- })
- .finally(function () {
- common.deleteFolderSync(workDirectoryPath);
- });
+ .then(() => self.downloadPackageAndExtract(workDirectoryPath, package_hash, blob_url))
+ .then((originDataCenter) => Promise.map(destPackages,
+ (v) => {
+ var diffWorkDirectoryPath = path.join(workDirectoryPath, _.get(v, 'package_hash'));
+ common.createEmptyFolderSync(diffWorkDirectoryPath);
+ return self.downloadPackageAndExtract(diffWorkDirectoryPath, _.get(v, 'package_hash'), _.get(v, 'blob_url'))
+ .then((oldPackageDataCenter) =>
+ self.generateOneDiffPackage(
+ diffWorkDirectoryPath,
+ originalPackage.id,
+ originDataCenter,
+ oldPackageDataCenter,
+ v.package_hash,
+ v.manifest_blob_url,
+ isUseDiffText
+ )
+ )
+ }
+ ))
+ .finally(() => common.deleteFolderSync(workDirectoryPath));
}
-proto.releasePackage = function (deploymentId, packageInfo, fileType, filePath, releaseUid, pubType) {
+proto.releasePackage = function (appId, deploymentId, packageInfo, filePath, releaseUid) {
var self = this;
var appVersion = packageInfo.appVersion;
- if (!/^([0-9.]+)$/.test(appVersion)) {
- return Promise.reject(new Error(`targetBinaryVersion ${appVersion} not support.`))
+ var versionInfo = common.validatorVersion(appVersion);
+ if (!versionInfo[0]) {
+ log.debug(`releasePackage targetBinaryVersion ${appVersion} not support.`);
+ return Promise.reject(new AppError.AppError(`targetBinaryVersion ${appVersion} not support.`))
}
- var description = packageInfo.description;
- var isMandatory = packageInfo.isMandatory;
- var directoryPath = path.join(os.tmpdir(), 'codepush_' + security.randToken(32));
+ var description = packageInfo.description; //描述
+ var isDisabled = packageInfo.isDisabled; //是否立刻下载
+ var rollout = packageInfo.rollout; //灰度百分比
+ var isMandatory = packageInfo.isMandatory; //是否强制更新,无法跳过
+ var tmpDir = os.tmpdir();
+ var directoryPathParent = path.join(tmpDir, 'codepuh_' + security.randToken(32));
+ var directoryPath = path.join(directoryPathParent, 'current');
+ log.debug(`releasePackage generate an random dir path: ${directoryPath}`);
return Promise.all([
security.qetag(filePath),
common.createEmptyFolder(directoryPath)
- .then(function () {
- if (fileType == "application/zip") {
- return common.unzipFile(filePath, directoryPath)
- } else {
- throw new Error("上传的文件格式不对");
- }
+ .then(() => {
+ return common.unzipFile(filePath, directoryPath)
})
])
- .spread(function(blobHash) {
- return security.isAndroidPackage(directoryPath)
- .then(function (type) {
- if (type === 1) {
- //android
- if (pubType !== 'android' ) {
- throw new Error("it must be publish it by android type");
- }
- } else if (type === 2) {
- //ios
- if (pubType !== 'ios'){
- throw new Error("it must be publish it by ios type");
+ .spread((blobHash) => {
+ return security.uploadPackageType(directoryPath)
+ .then((type) => {
+ return models.Apps.findById(appId).then((appInfo)=>{
+ if (type > 0 && appInfo.os > 0 && appInfo.os != type) {
+ var e = new AppError.AppError("it must be publish it by ios type");
+ log.debug(e);
+ throw e;
+ } else {
+ //不验证
+ log.debug(`Unknown package type:`, type, ',db os:', appInfo.os);
}
- } else {
- //不验证
- }
- })
- .then(function(){
- return blobHash;
- })
+ return blobHash;
+ });
+ });
})
- .then(function(blobHash) {
+ .then((blobHash) => {
var dataCenterManager = require('./datacenter-manager')();
return dataCenterManager.storePackage(directoryPath)
- .then(function (dataCenter) {
+ .then((dataCenter) => {
var packageHash = dataCenter.packageHash;
var manifestFile = dataCenter.manifestFilePath;
- return self.existPackageHashAndCreateVersions(deploymentId, appVersion, packageHash)
- .then(function (isExist) {
+ return models.DeploymentsVersions.findOne({where: {deployment_id: deploymentId, app_version:appVersion}})
+ .then((deploymentsVersions) => {
+ if (!deploymentsVersions) {
+ return false;
+ }
+ return self.isMatchPackageHash(deploymentsVersions.get('current_package_id'), packageHash);
+ })
+ .then((isExist) => {
if (isExist){
- throw new Error("The uploaded package is identical to the contents of the specified deployment's current release.");
+ var e = new AppError.AppError("The uploaded package is identical to the contents of the specified deployment's current release.");
+ log.debug(e.message);
+ throw e;
}
return security.qetag(manifestFile);
})
- .then(function (manifestHash) {
+ .then((manifestHash) => {
return Promise.all([
common.uploadFileToStorage(manifestHash, manifestFile),
common.uploadFileToStorage(blobHash, filePath)
])
- .then(function () {
- return [packageHash, manifestHash, blobHash];
- });
- });
+ .then(() => [packageHash, manifestHash, blobHash]);
+ })
});
})
- .spread(function (packageHash, manifestHash, blobHash) {
+ .spread((packageHash, manifestHash, blobHash) => {
var stats = fs.statSync(filePath);
var params = {
- releaseMethod: 'Upload',
+ releaseMethod: constConfig.RELEAS_EMETHOD_UPLOAD,
releaseUid: releaseUid,
- isMandatory: isMandatory,
+ isMandatory: isMandatory ? constConfig.IS_MANDATORY_YES : constConfig.IS_MANDATORY_NO,
+ isDisabled: isDisabled ? constConfig.IS_DISABLED_YES : constConfig.IS_DISABLED_NO,
+ rollout: rollout,
size: stats.size,
- description: description
+ description: description,
+ min_version: versionInfo[1],
+ max_version: versionInfo[2],
}
return self.createPackage(deploymentId, appVersion, packageHash, manifestHash, blobHash, params);
})
- .finally(function () {
- common.deleteFolderSync(directoryPath);
- })
+ .finally(() => common.deleteFolderSync(directoryPathParent))
};
-proto.modifyReleasePackage = function(deploymentId, deploymentVersionId, packageInfo) {
- var appVersion = _.get(packageInfo, 'appVersion');
- var description = _.get(packageInfo, 'description');
- var isMandatory = _.get(packageInfo, 'isMandatory');
- var isDisabled = _.get(packageInfo, 'isDisabled');
- return models.DeploymentsVersions.findById(deploymentVersionId)
- .then(function(deploymentsVersions){
- if (_.isBoolean(isDisabled)) {
- throw new Error(`--disabled -x function is not implements`);
- }
- if (!appVersion) {
- if (!/^([0-9.]+)$/.test(appVersion)) {
- return Promise.reject(new Error(`targetBinaryVersion ${appVersion} not support.`))
- }
- return models.DeploymentsVersions.findOne({deployment_id: deploymentId, app_version: appVersion})
- .then(function(d){
- if (d) {
- throw new Error(`version ${appVersion} already exist`);
- }
- });
+proto.modifyReleasePackage = function(packageId, params) {
+ var appVersion = _.get(params, 'appVersion');
+ var description = _.get(params, 'description');
+ var isMandatory = _.get(params, 'isMandatory');
+ var isDisabled = _.get(params, 'isDisabled');
+ var rollout = _.get(params, 'rollout');
+ return models.Packages.findById(packageId)
+ .then((packageInfo) => {
+ if (!packageInfo) {
+ throw new AppError.AppError(`packageInfo not found`);
}
- if(!deploymentsVersions) {
- throw new Error(`packages were not found in db`);
+ if (!_.isNull(appVersion)) {
+ var versionInfo = common.validatorVersion(appVersion);
+ if (!versionInfo[0]) {
+ throw new AppError.AppError(`--targetBinaryVersion ${appVersion} not support.`);
+ }
+ return Promise.all([
+ models.DeploymentsVersions.findOne({where: {deployment_id:packageInfo.deployment_id, app_version:appVersion}}),
+ models.DeploymentsVersions.findById(packageInfo.deployment_version_id)
+ ])
+ .spread((v1, v2) => {
+ if (v1 && !_.eq(v1.id, v2.id)) {
+ log.debug(v1);
+ throw new AppError.AppError(`${appVersion} already exist.`);
+ }
+ if (!v2) {
+ throw new AppError.AppError(`packages not found.`);
+ }
+ return models.DeploymentsVersions.update({
+ app_version:appVersion,
+ min_version:versionInfo[1],
+ max_version:versionInfo[2]
+ },{where: {id:v2.id}});
+ })
+ .then(()=>{
+ return packageInfo
+ });
}
+ return packageInfo;
})
- .then(function(){
-
+ .then((packageInfo) => {
+ var new_params = {
+ description: description || packageInfo.description,
+ };
+ if (_.isInteger(rollout)) {
+ new_params.rollout = rollout;
+ }
+ if (_.isBoolean(isMandatory)) {
+ new_params.is_mandatory = isMandatory ? constConfig.IS_MANDATORY_YES : constConfig.IS_MANDATORY_NO;
+ }
+ if (_.isBoolean(isDisabled)) {
+ new_params.is_disabled = isDisabled ? constConfig.IS_DISABLED_YES : constConfig.IS_DISABLED_NO;
+ }
+ return models.Packages.update(new_params,{where: {id: packageId}});
});
};
-proto.promotePackage = function (sourceDeploymentId, destDeploymentId, promoteUid) {
+proto.promotePackage = function (sourceDeploymentInfo, destDeploymentInfo, params) {
var self = this;
- return models.Deployments.findById(sourceDeploymentId)
- .then(function (sourceDeployment) {
- var lastDeploymentVersionId = _.get(sourceDeployment, 'last_deployment_version_id', 0);
- if (_.lte(lastDeploymentVersionId, 0)) {
- throw new Error('does not exist last_deployment_version_id.');
- }
- return models.DeploymentsVersions.findById(lastDeploymentVersionId)
- .then(function (deploymentsVersions) {
- var packageId = _.get(deploymentsVersions, 'current_package_id', 0);
- if (_.lte(packageId, 0)) {
- throw new Error('does not exist packages.');
+ var appVersion = _.get(params,'appVersion', null);
+ var label = _.get(params,'label', null);
+ return new Promise((resolve, reject) => {
+ if (label) {
+ return models.Packages.findOne({where: {deployment_id: sourceDeploymentInfo.id, label:label}})
+ .then((sourcePack)=>{
+ if (!sourcePack) {
+ throw new AppError.AppError('label does not exist.');
+ }
+ return models.DeploymentsVersions.findById(sourcePack.deployment_version_id)
+ .then((deploymentsVersions)=>{
+ if (!deploymentsVersions) {
+ throw new AppError.AppError('deploymentsVersions does not exist.');
+ }
+ resolve([sourcePack, deploymentsVersions]);
+ });
+ })
+ .catch((e) => {
+ reject(e);
+ });
+ } else {
+ var lastDeploymentVersionId = _.get(sourceDeploymentInfo, 'last_deployment_version_id', 0);
+ if (_.lte(lastDeploymentVersionId, 0)) {
+ throw new AppError.AppError(`does not exist last_deployment_version_id.`);
}
- return models.Packages.findById(packageId)
- .then(function (packages) {
- if (!packages) {
- throw new Error('does not exist packages.');
+ return models.DeploymentsVersions.findById(lastDeploymentVersionId)
+ .then((deploymentsVersions)=>{
+ var sourcePackId = _.get(deploymentsVersions, 'current_package_id', 0);
+ if (_.lte(sourcePackId, 0)) {
+ throw new AppError.AppError(`packageInfo not found.`);
}
- return self.existPackageHashAndCreateVersions(destDeploymentId, deploymentsVersions.app_version, packages.package_hash)
- .then(function (isExist) {
- if (isExist){
- throw new Error("The uploaded package is identical to the contents of the specified deployment's current release.");
+ return models.Packages.findById(sourcePackId)
+ .then((sourcePack) =>{
+ if (!sourcePack) {
+ throw new AppError.AppError(`packageInfo not found.`);
}
- })
- .then(function () {
- return [sourceDeployment, deploymentsVersions, packages];
+ resolve([sourcePack, deploymentsVersions]);
});
+ })
+ .catch((e) => {
+ reject(e);
});
+ }
+ })
+ .spread((sourcePack, deploymentsVersions)=>{
+ var appFinalVersion = appVersion || deploymentsVersions.app_version;
+ log.debug('sourcePack',sourcePack);
+ log.debug('deploymentsVersions',deploymentsVersions);
+ log.debug('appFinalVersion', appFinalVersion);
+ return models.DeploymentsVersions.findOne({where: {
+ deployment_id:destDeploymentInfo.id,
+ app_version: appFinalVersion,
+ }})
+ .then((destDeploymentsVersions)=>{
+ if (!destDeploymentsVersions) {
+ return false;
+ }
+ return self.isMatchPackageHash(destDeploymentsVersions.get('current_package_id'), sourcePack.package_hash);
+ })
+ .then((isExist) => {
+ if (isExist){
+ throw new AppError.AppError("The uploaded package is identical to the contents of the specified deployment's current release.");
+ }
+ return [sourcePack, deploymentsVersions, appFinalVersion];
});
})
- .spread(function (sourceDeployment, deploymentsVersions, packages) {
- var params = {
- releaseMethod: 'Promote',
- releaseUid: promoteUid,
- isMandatory: packages.is_mandatory == 1 ? true : false,
- size: packages.size,
- description: packages.description,
- originalLabel: packages.label,
- originalDeployment: sourceDeployment.name
+ .spread((sourcePack, deploymentsVersions, appFinalVersion) => {
+ var versionInfo = common.validatorVersion(appFinalVersion);
+ if (!versionInfo[0]) {
+ log.debug(`targetBinaryVersion ${appVersion} not support.`);
+ throw new AppError.AppError(`targetBinaryVersion ${appVersion} not support.`);
+ }
+ var create_params = {
+ releaseMethod: constConfig.RELEAS_EMETHOD_PROMOTE,
+ releaseUid: params.promoteUid || 0,
+ rollout: params.rollout || 100,
+ size: sourcePack.size,
+ description: params.description || sourcePack.description,
+ originalLabel: sourcePack.label,
+ originalDeployment: sourceDeploymentInfo.name,
+ min_version: versionInfo[1],
+ max_version: versionInfo[2],
};
- return self.createPackage(destDeploymentId, deploymentsVersions.app_version, packages.package_hash, packages.manifest_blob_url, packages.blob_url, params);
+ if (_.isBoolean(params.isMandatory)) {
+ create_params.isMandatory = params.isMandatory ? constConfig.IS_MANDATORY_YES : constConfig.IS_MANDATORY_NO;
+ } else {
+ create_params.isMandatory = sourcePack.is_mandatory
+ }
+ if (_.isBoolean(params.isDisabled)) {
+ create_params.isDisabled = params.isDisabled ? constConfig.IS_DISABLED_YES : constConfig.IS_DISABLED_NO;
+ } else {
+ create_params.isDisabled = sourcePack.is_disabled
+ }
+ return self.createPackage(
+ destDeploymentInfo.id,
+ appFinalVersion,
+ sourcePack.package_hash,
+ sourcePack.manifest_blob_url,
+ sourcePack.blob_url,
+ create_params
+ );
});
};
proto.rollbackPackage = function (deploymentVersionId, targetLabel, rollbackUid) {
var self = this;
return models.DeploymentsVersions.findById(deploymentVersionId)
- .then(function(deploymentsVersions){
+ .then((deploymentsVersions) => {
if (!deploymentsVersions) {
- throw new Error("您之前还没有发布过版本");
+ throw new AppError.AppError("您之前还没有发布过版本");
}
return models.Packages.findById(deploymentsVersions.current_package_id)
- .then(function (currentPackageInfo){
+ .then((currentPackageInfo) => {
if (targetLabel) {
return models.Packages.findAll({where: {deployment_version_id: deploymentVersionId, label: targetLabel}, limit: 1})
- .then(function(rollbackPackageInfos) {
+ .then((rollbackPackageInfos) => {
return [currentPackageInfo, rollbackPackageInfos]
});
} else {
return self.getCanRollbackPackages(deploymentVersionId)
- .then(function(rollbackPackageInfos) {
+ .then((rollbackPackageInfos) => {
return [currentPackageInfo, rollbackPackageInfos]
});
}
})
- .spread(function (currentPackageInfo, rollbackPackageInfos){
+ .spread((currentPackageInfo, rollbackPackageInfos) => {
if (currentPackageInfo && rollbackPackageInfos.length > 0) {
for (var i = rollbackPackageInfos.length - 1; i >= 0; i--) {
if (rollbackPackageInfos[i].package_hash != currentPackageInfo.package_hash) {
@@ -469,17 +627,21 @@ proto.rollbackPackage = function (deploymentVersionId, targetLabel, rollbackUid)
}
}
}
- throw new Error("没有可供回滚的版本");
+ throw new AppError.AppError("没有可供回滚的版本");
})
- .then(function(rollbackPackage){
+ .then((rollbackPackage) => {
var params = {
releaseMethod: 'Rollback',
releaseUid: rollbackUid,
- isMandatory: rollbackPackage.is_mandatory == 1 ? true : false,
+ isMandatory: rollbackPackage.is_mandatory,
+ isDisabled: rollbackPackage.is_disabled,
+ rollout: rollbackPackage.rollout,
size: rollbackPackage.size,
description: rollbackPackage.description,
originalLabel: rollbackPackage.label,
- originalDeployment: ''
+ originalDeployment: '',
+ min_version: deploymentsVersions.min_version,
+ max_version: deploymentsVersions.max_version,
};
return self.createPackage(deploymentsVersions.deployment_id,
deploymentsVersions.app_version,
@@ -493,7 +655,11 @@ proto.rollbackPackage = function (deploymentVersionId, targetLabel, rollbackUid)
}
proto.getCanRollbackPackages = function (deploymentVersionId) {
+ var Sequelize = require('sequelize');
return models.Packages.findAll({
- where: {deployment_version_id: deploymentVersionId, release_method: {$in: ['Upload', 'Promote'] }}, order: [['id','desc']], limit: 2
+ where: {
+ deployment_version_id: deploymentVersionId,
+ release_method: {[Sequelize.Op.in]: [constConfig.RELEAS_EMETHOD_UPLOAD, constConfig.RELEAS_EMETHOD_PROMOTE] }
+ }, order: [['id','desc']], limit: 2
});
}
diff --git a/core/utils/common.js b/core/utils/common.js
index be0daefa..6d221535 100644
--- a/core/utils/common.js
+++ b/core/utils/common.js
@@ -2,18 +2,94 @@
var Promise = require('bluebird');
var fs = require("fs");
var fsextra = require("fs-extra");
-var unzip = require('node-unzip-2');
+var extract = require('extract-zip')
var config = require('../config');
var _ = require('lodash');
+var validator = require('validator');
var qiniu = require("qiniu");
+var upyun = require('upyun');
var common = {};
+var AppError = require('../app-error');
+var jschardet = require("jschardet");
+var log4js = require('log4js');
+var path = require('path');
+var log = log4js.getLogger("cps:utils:common");
module.exports = common;
+common.detectIsTextFile = function(filePath) {
+ var fd = fs.openSync(filePath, 'r');
+ var buffer = new Buffer(4096);
+ fs.readSync(fd, buffer, 0, 4096, 0);
+ fs.closeSync(fd);
+ var rs = jschardet.detect(buffer);
+ log.debug('detectIsTextFile:', filePath, rs);
+ if (rs.confidence == 1) {
+ return true;
+ }
+ return false;
+}
+
+common.parseVersion = function (versionNo) {
+ var version = '0';
+ var data = null;
+ if (data = versionNo.match(/^([0-9]{1,3}).([0-9]{1,5}).([0-9]{1,10})$/)) {
+ // "1.2.3"
+ version = data[1] + _.padStart(data[2], 5, '0') + _.padStart(data[3], 10, '0');
+ } else if (data = versionNo.match(/^([0-9]{1,3}).([0-9]{1,5})$/)) {
+ // "1.2"
+ version = data[1] + _.padStart(data[2], 5, '0') + _.padStart('0', 10, '0');
+ }
+ return version;
+};
+
+common.validatorVersion = function (versionNo) {
+ var flag = false;
+ var min = '0';
+ var max = '9999999999999999999';
+ var data = null;
+ if (versionNo == "*") {
+ // "*"
+ flag = true;
+ } else if (data = versionNo.match(/^([0-9]{1,3}).([0-9]{1,5}).([0-9]{1,10})$/)) {
+ // "1.2.3"
+ flag = true;
+ min = data[1] + _.padStart(data[2], 5, '0') + _.padStart(data[3], 10, '0');
+ max = data[1] + _.padStart(data[2], 5, '0') + _.padStart((parseInt(data[3])+1), 10, '0');
+ } else if (data = versionNo.match(/^([0-9]{1,3}).([0-9]{1,5})(\.\*){0,1}$/)) {
+ // "1.2" "1.2.*"
+ flag = true;
+ min = data[1] + _.padStart(data[2], 5, '0') + _.padStart('0', 10, '0');
+ max = data[1] + _.padStart((parseInt(data[2])+1), 5, '0') + _.padStart('0', 10, '0');
+ } else if (data = versionNo.match(/^\~([0-9]{1,3}).([0-9]{1,5}).([0-9]{1,10})$/)) {
+ //"~1.2.3"
+ flag = true;
+ min = data[1] + _.padStart(data[2], 5, '0') + _.padStart(data[3], 10, '0');
+ max = data[1] + _.padStart((parseInt(data[2])+1), 5, '0') + _.padStart('0', 10, '0');
+ } else if (data = versionNo.match(/^\^([0-9]{1,3}).([0-9]{1,5}).([0-9]{1,10})$/)) {
+ //"^1.2.3"
+ flag = true;
+ min = data[1] + _.padStart(data[2], 5, '0') + _.padStart(data[3], 10, '0');
+ max = _.toString((parseInt(data[1])+1)) + _.padStart(0, 5, '0') + _.padStart('0', 10, '0');
+ } else if (data = versionNo.match(/^([0-9]{1,3}).([0-9]{1,5}).([0-9]{1,10})\s?-\s?([0-9]{1,3}).([0-9]{1,5}).([0-9]{1,10})$/)) {
+ // "1.2.3 - 1.2.7"
+ flag = true;
+ min = data[1] + _.padStart(data[2], 5, '0') + _.padStart(data[3], 10, '0');
+ max = data[4] + _.padStart(data[5], 5, '0') + _.padStart((parseInt(data[6])+1), 10, '0');
+ } else if (data = versionNo.match(/^>=([0-9]{1,3}).([0-9]{1,5}).([0-9]{1,10})\s?<([0-9]{1,3}).([0-9]{1,5}).([0-9]{1,10})$/)) {
+ // ">=1.2.3 <1.2.7"
+ flag = true;
+ min = data[1] + _.padStart(data[2], 5, '0') + _.padStart(data[3], 10, '0');
+ max = data[4] + _.padStart(data[5], 5, '0') + _.padStart(data[6], 10, '0');
+ }
+ return [flag, min, max];
+};
+
common.createFileFromRequest = function (url, filePath) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
fs.exists(filePath, function (exists) {
if (!exists) {
var request = require('request');
+ log.debug(`createFileFromRequest url:${url}`)
request(url).on('error', function (error) {
reject(error);
})
@@ -36,25 +112,48 @@ common.createFileFromRequest = function (url, filePath) {
}
});
});
-}
+};
+
+common.copySync = function (sourceDst, targertDst) {
+ return fsextra.copySync(sourceDst, targertDst, {overwrite: true});
+};
+
+common.copy = function (sourceDst, targertDst) {
+ return new Promise((resolve, reject) => {
+ fsextra.copy(sourceDst, targertDst, {overwrite: true}, function (err) {
+ if (err) {
+ log.error(err);
+ reject(err);
+ } else {
+ log.debug(`copy success sourceDst:${sourceDst} targertDst:${targertDst}`);
+ resolve();
+ }
+ });
+ });
+};
common.move = function (sourceDst, targertDst) {
- return new Promise(function (resolve, reject) {
- fsextra.move(sourceDst, targertDst, {clobber: true, limit: 16}, function (err) {
+ return new Promise((resolve, reject) => {
+ fsextra.move(sourceDst, targertDst, {overwrite: true}, function (err) {
if (err) {
- return reject(err);
+ log.error(err);
+ reject(err);
+ } else {
+ log.debug(`move success sourceDst:${sourceDst} targertDst:${targertDst}`);
+ resolve();
}
- resolve();
});
});
};
common.deleteFolder = function (folderPath) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
fsextra.remove(folderPath, function (err) {
if (err) {
+ log.error(err);
reject(err);
}else {
+ log.debug(`deleteFolder delete ${folderPath} success.`);
resolve(null);
}
});
@@ -66,13 +165,18 @@ common.deleteFolderSync = function (folderPath) {
};
common.createEmptyFolder = function (folderPath) {
- return common.deleteFolder(folderPath)
- .then(function (data) {
- fsextra.mkdirs(folderPath, function (err) {
- if (err) {
- throw err;
- }
- return folderPath;
+ return new Promise((resolve, reject) => {
+ log.debug(`createEmptyFolder Create dir ${folderPath}`);
+ return common.deleteFolder(folderPath)
+ .then((data) => {
+ fsextra.mkdirs(folderPath, (err) => {
+ if (err) {
+ log.error(err);
+ reject(new AppError.AppError(err.message));
+ } else {
+ resolve(folderPath);
+ }
+ });
});
});
};
@@ -83,107 +187,165 @@ common.createEmptyFolderSync = function (folderPath) {
};
common.unzipFile = function (zipFile, outputPath) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
try {
- fs.exists(zipFile, function(exists){
- if (!exists) {
- reject(new Error("zipfile not found!"))
- }
- var readStream = fs.createReadStream(zipFile);
- var extract = unzip.Extract({ path: outputPath });
- readStream.pipe(extract);
- extract.on("close", function () {
- resolve(outputPath);
- });
- })
+ log.debug(`unzipFile check zipFile ${zipFile} fs.R_OK`);
+ fs.accessSync(zipFile, fs.R_OK);
+ log.debug(`Pass unzipFile file ${zipFile}`);
} catch (e) {
- reject(e)
+ log.error(e);
+ return reject(new AppError.AppError(e.message))
}
+ extract(zipFile, {dir: outputPath}, function(err){
+ if (err) {
+ log.error(err);
+ reject(new AppError.AppError(`it's not a zipFile`))
+ } else {
+ log.debug(`unzipFile success`);
+ resolve(outputPath);
+ }
+ });
});
};
-common.uptoken = function (bucket, key) {
- var putPolicy = new qiniu.rs.PutPolicy(bucket+":"+key);
- return putPolicy.token();
+common.getUploadTokenQiniu = function (mac, bucket, key) {
+ var options = {
+ scope: bucket + ":" + key
+ }
+ var putPolicy = new qiniu.rs.PutPolicy(options);
+ return putPolicy.uploadToken(mac);
};
common.uploadFileToStorage = function (key, filePath) {
- if (_.get(config, 'common.storageType') === 'local') {
+ var storageType = _.get(config, 'common.storageType');
+ if ( storageType === 'local') {
return common.uploadFileToLocal(key, filePath);
- } else if (_.get(config, 'common.storageType') === 's3') {
+ } else if (storageType === 's3') {
return common.uploadFileToS3(key, filePath);
- } else if (_.get(config, 'common.storageType') === 'oss') {
+ } else if (storageType === 'oss') {
return common.uploadFileToOSS(key, filePath);
+ } else if (storageType === 'qiniu') {
+ return common.uploadFileToQiniu(key, filePath);
+ } else if (storageType === 'upyun') {
+ return common.uploadFileToUpyun(key, filePath);
+ } else if (storageType === 'tencentcloud') {
+ return common.uploadFileToTencentCloud(key, filePath);
}
- return common.uploadFileToQiniu(key, filePath);
+ throw new AppError.AppError(`${storageType} storageType does not support.`);
};
common.uploadFileToLocal = function (key, filePath) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
var storageDir = _.get(config, 'local.storageDir');
if (!storageDir) {
- throw new Error('please set config local storageDir');
+ throw new AppError.AppError('please set config local storageDir');
+ }
+ if (key.length < 3) {
+ log.error(`generate key is too short, key value:${key}`);
+ throw new AppError.AppError('generate key is too short.');
+ }
+ try {
+ log.debug(`uploadFileToLocal check directory ${storageDir} fs.R_OK`);
+ fs.accessSync(storageDir, fs.W_OK);
+ log.debug(`uploadFileToLocal directory ${storageDir} fs.R_OK is ok`);
+ } catch (e) {
+ log.error(e);
+ throw new AppError.AppError(e.message);
}
- if (!fs.existsSync(storageDir)) {
- throw new Error(`please create dir ${storageDir}`);
+ var subDir = key.substr(0, 2).toLowerCase();
+ var finalDir = path.join(storageDir, subDir);
+ var fileName = path.join(finalDir, key);
+ if (fs.existsSync(fileName)) {
+ return resolve(key);
}
- fs.accessSync(storageDir, fs.W_OK);
var stats = fs.statSync(storageDir);
if (!stats.isDirectory()) {
- throw new Error(`${storageDir} must be directory`);
+ var e = new AppError.AppError(`${storageDir} must be directory`);
+ log.error(e);
+ throw e;
+ }
+ if (!fs.existsSync(`${finalDir}`)) {
+ fs.mkdirSync(`${finalDir}`);
+ log.debug(`uploadFileToLocal mkdir:${finalDir}`);
+ }
+ try {
+ fs.accessSync(filePath, fs.R_OK);
+ } catch (e) {
+ log.error(e);
+ throw new AppError.AppError(e.message);
}
- fs.accessSync(filePath, fs.R_OK);
stats = fs.statSync(filePath);
if (!stats.isFile()) {
- throw new Error(`${filePath} must be file`);
+ var e = new AppError.AppError(`${filePath} must be file`);
+ log.error(e);
+ throw e;
}
- fsextra.copy(filePath, `${storageDir}/${key}`, {clobber: true, limit: 16}, function (err) {
+ fsextra.copy(filePath, fileName,(err) => {
if (err) {
- return reject(err);
+ log.error(new AppError.AppError(err.message));
+ return reject(new AppError.AppError(err.message));
}
+ log.debug(`uploadFileToLocal copy file ${key} success.`);
resolve(key);
});
});
};
-common.getDownloadUrl = function () {
- if (_.get(config, 'common.storageType') === 'local') {
- return _.get(config, 'local.downloadUrl');
- } else if (_.get(config, 'common.storageType') === 's3') {
- return _.get(config, 's3.downloadUrl');
- } else if (_.get(config, 'common.storageType') === 'oss') {
- return _.get(config, 'oss.downloadUrl');
- }
- return _.get(config, 'qiniu.downloadUrl');
-}
-
common.getBlobDownloadUrl = function (blobUrl) {
- return `${common.getDownloadUrl()}/${blobUrl}`
+ var fileName = blobUrl;
+ var storageType = _.get(config, 'common.storageType');
+ var downloadUrl = _.get(config, `${storageType}.downloadUrl`);
+ if ( storageType === 'local') {
+ fileName = blobUrl.substr(0, 2).toLowerCase() + '/' + blobUrl;
+ }
+ if (!validator.isURL(downloadUrl)) {
+ var e = new AppError.AppError(`Please config ${storageType}.downloadUrl in config.js`);
+ log.error(e);
+ throw e;
+ }
+ return `${downloadUrl}/${fileName}`
};
+
common.uploadFileToQiniu = function (key, filePath) {
- return new Promise(function (resolve, reject) {
- qiniu.conf.ACCESS_KEY = _.get(config, "qiniu.accessKey");
- qiniu.conf.SECRET_KEY = _.get(config, "qiniu.secretKey");
- var bucket = _.get(config, "qiniu.bucketName", "jukang");
- var client = new qiniu.rs.Client();
- client.stat(bucket, key, function(err, ret) {
- if (!err) {
- resolve(ret.hash);
+ return new Promise((resolve, reject) => {
+ var accessKey = _.get(config, "qiniu.accessKey");
+ var secretKey = _.get(config, "qiniu.secretKey");
+ var bucket = _.get(config, "qiniu.bucketName", "");
+ var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
+ var conf = new qiniu.conf.Config();
+ var bucketManager = new qiniu.rs.BucketManager(mac, conf);
+ bucketManager.stat(bucket, key, (respErr, respBody, respInfo) => {
+ if (respErr) {
+ log.debug('uploadFileToQiniu file stat:', respErr);
+ return reject(new AppError.AppError(respErr.message));
+ }
+ log.debug('uploadFileToQiniu file stat respBody:', respBody);
+ log.debug('uploadFileToQiniu file stat respInfo:', respInfo);
+ if (respInfo.statusCode == 200) {
+ resolve(respBody.hash);
} else {
try {
- var uptoken = common.uptoken(bucket, key);
+ var uploadToken = common.getUploadTokenQiniu(mac, bucket, key);
} catch (e) {
- reject(e);
+ return reject(new AppError.AppError(e.message));
}
- var extra = new qiniu.io.PutExtra();
- qiniu.io.putFile(uptoken, key, filePath, extra, function(err, ret) {
- if(!err) {
- // 上传成功, 处理返回值
- resolve(ret.hash);
- } else {
+ var formUploader = new qiniu.form_up.FormUploader(conf);
+ var putExtra = new qiniu.form_up.PutExtra();
+ formUploader.putFile(uploadToken, key, filePath, putExtra, (respErr, respBody, respInfo) => {
+ if(respErr) {
+ log.error('uploadFileToQiniu putFile:', respErr);
// 上传失败, 处理返回代码
- reject(new Error(JSON.stringify(err)));
+ return reject(new AppError.AppError(JSON.stringify(respErr)));
+ } else {
+ log.debug('uploadFileToQiniu putFile respBody:', respBody);
+ log.debug('uploadFileToQiniu putFile respInfo:', respInfo);
+ // 上传成功, 处理返回值
+ if (respInfo.statusCode == 200) {
+ return resolve(respBody.hash);
+ } else {
+ return reject(new AppError.AppError(respBody.error));
+ }
}
});
}
@@ -191,24 +353,64 @@ common.uploadFileToQiniu = function (key, filePath) {
});
};
+common.uploadFileToUpyun = function (key, filePath) {
+ var serviceName = _.get(config, "upyun.serviceName");
+ var operatorName = _.get(config, "upyun.operatorName");
+ var operatorPass = _.get(config, "upyun.operatorPass", "");
+ var storageDir = _.get(config, "upyun.storageDir", "");
+ var service = new upyun.Service(serviceName, operatorName, operatorPass);
+ var client = new upyun.Client(service);
+ return (
+ new Promise((resolve, reject) => {
+ client.makeDir(storageDir).then(result => {
+ if(!storageDir) {
+ reject(new AppError.AppError('Please config the upyun remoteDir!'));
+ return;
+ }
+ let remotePath = storageDir + '/' + key;
+ log.debug('uploadFileToUpyun remotePath:', remotePath);
+ log.debug('uploadFileToUpyun mkDir result:', result);
+ client.putFile(remotePath, fs.createReadStream(filePath)).then(data => {
+ log.debug('uploadFileToUpyun putFile response:', data);
+ if(data) {
+ resolve(key)
+ } else {
+ log.debug('uploadFileToUpyun putFile failed!', data);
+ reject(new AppError.AppError('Upload file to upyun failed!'));
+ }
+ }).catch(e1 => {
+ log.debug('uploadFileToUpyun putFile exception e1:', e1);
+ reject(new AppError.AppError(JSON.stringify(e1)));
+ })
+ }).catch(e => {
+ log.debug('uploadFileToUpyun putFile exception e:', e);
+ reject(new AppError.AppError(JSON.stringify(e)));
+ });
+ })
+ );
+};
+
common.uploadFileToS3 = function (key, filePath) {
var AWS = require('aws-sdk');
return (
- new Promise(function(resolve, reject) {
+ new Promise((resolve, reject) => {
AWS.config.update({
+ accessKeyId: _.get(config, 's3.accessKeyId'),
+ secretAccessKey: _.get(config, 's3.secretAccessKey'),
+ sessionToken: _.get(config, 's3.sessionToken'),
region: _.get(config, 's3.region')
});
var s3 = new AWS.S3({
params: {Bucket: _.get(config, 's3.bucketName')}
});
- fs.readFile(filePath, function(err, data) {
+ fs.readFile(filePath, (err, data) => {
s3.upload({
Key: key,
Body: data,
ACL:'public-read',
- }, function(err, response) {
+ }, (err, response) => {
if(err) {
- reject(new Error(JSON.stringify(err)));
+ reject(new AppError.AppError(JSON.stringify(err)));
} else {
resolve(response.ETag)
}
@@ -226,23 +428,51 @@ common.uploadFileToOSS = function (key, filePath) {
endpoint: _.get(config, 'oss.endpoint'),
apiVersion: '2013-10-15',
}));
+ if (!_.isEmpty(_.get(config, 'oss.prefix', ""))) {
+ key = `${_.get(config, 'oss.prefix')}/${key}`;
+ }
var upload = ossStream.upload({
Bucket: _.get(config, 'oss.bucketName'),
- Key: `${_.get(config, 'oss.prefix')}/${key}`,
+ Key: key,
});
- return new Promise(function (resolve, reject) {
- upload.on('error', function (error) {
+ return new Promise((resolve, reject) => {
+ upload.on('error', (error) => {
+ log.debug("uploadFileToOSS", error);
reject(error);
});
- upload.on('uploaded', function (details) {
+ upload.on('uploaded', (details) => {
+ log.debug("uploadFileToOSS", details);
resolve(details.ETag);
});
fs.createReadStream(filePath).pipe(upload);
});
};
+common.uploadFileToTencentCloud = function (key, filePath) {
+ return new Promise((resolve, reject) => {
+ var COS = require('cos-nodejs-sdk-v5');
+ var cosIn = new COS({
+ SecretId: _.get(config, 'tencentcloud.accessKeyId'),
+ SecretKey: _.get(config, 'tencentcloud.secretAccessKey')
+ });
+ cosIn.sliceUploadFile({
+ Bucket: _.get(config, 'tencentcloud.bucketName'),
+ Region: _.get(config, 'tencentcloud.region'),
+ Key: key,
+ FilePath: filePath
+ }, function (err, data) {
+ log.debug("uploadFileToTencentCloud", err, data);
+ if (err) {
+ reject(new AppError.AppError(JSON.stringify(err)));
+ }else {
+ resolve(data.Key);
+ }
+ });
+ });
+}
+
common.diffCollectionsSync = function (collection1, collection2) {
var diffFiles = [];
var collection1Only = [];
diff --git a/core/utils/security.js b/core/utils/security.js
index edb3e730..a4054acd 100644
--- a/core/utils/security.js
+++ b/core/utils/security.js
@@ -5,6 +5,9 @@ var fs = require('fs');
var Promise = require('bluebird');
var qetag = require('../utils/qetag');
var _ = require('lodash');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:utils:security");
+var AppError = require('../app-error');
var randToken = require('rand-token').generator({
chars: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
@@ -37,14 +40,14 @@ security.parseToken = function(token) {
}
security.fileSha256 = function (file) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
var rs = fs.createReadStream(file);
var hash = crypto.createHash('sha256');
rs.on('data', hash.update.bind(hash));
- rs.on('error', function(e){
+ rs.on('error', (e) => {
reject(e);
});
- rs.on('end', function () {
+ rs.on('end', () => {
resolve(hash.digest('hex'));
});
});
@@ -58,39 +61,47 @@ security.stringSha256Sync = function (contents) {
security.packageHashSync = function (jsonData) {
var sortedArr = security.sortJsonToArr(jsonData);
- var manifestData = _.map(sortedArr, function(v){
+ var manifestData = _.filter(sortedArr, (v) => {
+ return !security.isPackageHashIgnored(v.path);
+ }).map((v) => {
return v.path + ':' + v.hash;
});
+ log.debug('packageHashSync manifestData:', manifestData);
var manifestString = JSON.stringify(manifestData.sort());
manifestString = _.replace(manifestString, /\\\//g, '/');
+ log.debug('packageHashSync manifestString:', manifestString);
return security.stringSha256Sync(manifestString);
}
//参数为buffer或者readableStream或者文件路径
security.qetag = function (buffer) {
- return new Promise(function (resolve, reject) {
- qetag(buffer, resolve);
- });
-}
-
-security.qetagString = function (contents) {
- return new Promise(function (resolve, reject) {
- var Readable = require('stream').Readable
- var buffer = new Readable
- buffer.push(contents)
- buffer.push(null)
- qetag(buffer, resolve);
+ if (typeof buffer === 'string') {
+ try {
+ log.debug(`Check upload file ${buffer} fs.R_OK`);
+ fs.accessSync(buffer, fs.R_OK);
+ log.debug(`Pass upload file ${buffer}`);
+ } catch (e) {
+ log.error(e);
+ return Promise.reject(new AppError.AppError(e.message))
+ }
+ }
+ log.debug(`generate file identical`)
+ return new Promise((resolve, reject) => {
+ qetag(buffer, (data)=>{
+ log.debug('identical:', data);
+ resolve(data)
+ });
});
}
security.sha256AllFiles = function (files) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
var results = {};
var length = files.length;
var count = 0;
- files.forEach(function (file) {
+ files.forEach((file) => {
security.fileSha256(file)
- .then(function (hash) {
+ .then((hash) => {
results[file] = hash;
count++;
if (count == length) {
@@ -101,58 +112,104 @@ security.sha256AllFiles = function (files) {
});
}
-security.isAndroidPackage = function (directoryPath) {
- return new Promise(function (resolve, reject) {
- var recursiveFs = require("recursive-fs");
+security.uploadPackageType = function (directoryPath) {
+ return new Promise((resolve, reject) => {
+ var recursive = require("recursive-readdir");
var path = require('path');
var slash = require("slash");
- recursiveFs.readdirr(directoryPath, function (error, directories, files) {
- if (error) {
- reject(error);
+ recursive(directoryPath, (err, files) => {
+ if (err) {
+ log.error(new AppError.AppError(err.message));
+ reject(new AppError.AppError(err.message));
} else {
if (files.length == 0) {
- reject(new Error("empty files"));
+ log.debug(`uploadPackageType empty files`);
+ reject(new AppError.AppError("empty files"));
} else {
+ var constName = require('../const');
const AREGEX=/android\.bundle/
const AREGEX_IOS=/main\.jsbundle/
- var isAndroid = 0;
+ var packageType = 0;
_.forIn(files, function (value) {
if (AREGEX.test(value)) {
- isAndroid = 1;
+ packageType = constName.ANDROID;
return false;
}
if (AREGEX_IOS.test(value)) {
- isAndroid = 2;
+ packageType = constName.IOS;
return false;
}
});
- resolve(isAndroid);
+ log.debug(`uploadPackageType packageType: ${packageType}`);
+ resolve(packageType);
}
}
});
});
}
+// some files are ignored in calc hash in client sdk
+// https://github.com/Microsoft/react-native-code-push/pull/974/files#diff-21b650f88429c071b217d46243875987R15
+security.isHashIgnored = function (relativePath) {
+ if (!relativePath) {
+ return true;
+ }
+
+ const IgnoreMacOSX = '__MACOSX/';
+ const IgnoreDSStore = '.DS_Store';
+
+ return relativePath.startsWith(IgnoreMacOSX)
+ || relativePath === IgnoreDSStore
+ || relativePath.endsWith(IgnoreDSStore);
+}
+
+security.isPackageHashIgnored = function (relativePath) {
+ if (!relativePath) {
+ return true;
+ }
+
+ // .codepushrelease contains code sign JWT
+ // it should be ignored in package hash but need to be included in package manifest
+ const IgnoreCodePushMetadata = '.codepushrelease';
+ return relativePath === IgnoreCodePushMetadata
+ || relativePath.endsWith(IgnoreCodePushMetadata)
+ || security.isHashIgnored(relativePath);
+}
+
+
security.calcAllFileSha256 = function (directoryPath) {
- return new Promise(function (resolve, reject) {
- var recursiveFs = require("recursive-fs");
+ return new Promise((resolve, reject) => {
+ var recursive = require("recursive-readdir");
var path = require('path');
var slash = require("slash");
- recursiveFs.readdirr(directoryPath, function (error, directories, files) {
+ recursive(directoryPath, (error, files) => {
if (error) {
- reject(error);
+ log.error(error);
+ reject(new AppError.AppError(error.message));
} else {
+ // filter files that should be ignored
+ files = files.filter((file) => {
+ var relative = path.relative(directoryPath, file);
+ return !security.isHashIgnored(relative);
+ });
+
if (files.length == 0) {
- reject(new Error("empty files"));
+ log.debug(`calcAllFileSha256 empty files in directoryPath:`, directoryPath);
+ reject(new AppError.AppError("empty files"));
}else {
security.sha256AllFiles(files)
- .then(function (results) {
+ .then((results) => {
var data = {};
- _.forIn(results, function (value, key) {
+ _.forIn(results, (value, key) => {
var relativePath = path.relative(directoryPath, key);
+ var matchresult = relativePath.match(/(\/|\\).*/);
+ if (matchresult) {
+ relativePath = path.join('CodePush', matchresult[0]);
+ }
relativePath = slash(relativePath);
data[relativePath] = value;
});
+ log.debug(`calcAllFileSha256 files:`, data);
resolve(data);
});
}
@@ -163,8 +220,8 @@ security.calcAllFileSha256 = function (directoryPath) {
security.sortJsonToArr = function (json) {
var rs = [];
- _.forIn(json, function (value, key) {
+ _.forIn(json, (value, key) => {
rs.push({path:key, hash: value})
});
- return _.sortBy(rs, function(o) { return o.path; });
+ return _.sortBy(rs, (o) => o.path);
}
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 00000000..d1fb6da1
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,137 @@
+# docker 部署 code-push-server
+
+>该文档用于描述docker部署code-push-server,实例包含三个部分
+
+- code-push-server部分
+ - 更新包默认采用`local`存储(即存储在本地机器上)。使用docker volume存储方式,容器销毁不会导致数据丢失,除非人为删除volume。
+ - 内部使用pm2 cluster模式管理进程,默认开启进程数为cpu数,可以根据自己机器配置设置docker-compose.yml文件中deploy参数。
+ - docker-compose.yml只提供了应用的一部分参数设置,如需要设置其他配置,可以修改文件config.js。
+- mysql部分
+ - 数据使用docker volume存储方式,容器销毁不会导致数据丢失,除非人为删除volume。
+ - 应用请勿使用root用户,为了安全可以创建权限相对较小的权限供code-push-server使用,只需要给予`select,update,insert`权限即可。初始化数据库需要使用root或有建表权限用户
+- redis部分
+ - `tryLoginTimes` 登录错误次数限制
+ - `updateCheckCache` 提升应用性能
+ - `rolloutClientUniqueIdCache` 灰度发布
+
+## 安装docker
+
+参考docker官方安装教程
+
+- [>>mac点这里](https://docs.docker.com/docker-for-mac/install/)
+- [>>windows点这里](https://docs.docker.com/docker-for-windows/install/)
+- [>>linux点这里](https://docs.docker.com/install/linux/docker-ce/ubuntu/)
+
+
+`$ docker info` 能成功输出相关信息,则安装成功,才能继续下面步骤
+
+## 启动swarm
+
+```shell
+$ sudo docker swarm init
+```
+
+
+## 获取代码
+
+```shell
+$ git clone https://github.com/lisong/code-push-server.git
+$ cd code-push-server/docker
+```
+
+## 修改配置文件
+
+```shell
+$ vim docker-compose.yml
+```
+
+*将`DOWNLOAD_URL`中`YOU_MACHINE_IP`替换成本机外网ip或者域名*
+
+*将`MYSQL_HOST`中`YOU_MACHINE_IP`替换成本机内网ip*
+
+*将`REDIS_HOST`中`YOU_MACHINE_IP`替换成本机内网ip*
+
+## jwt.tokenSecret修改
+
+> code-push-server 验证登录验证方式使用的json web token加密方式,该对称加密算法是公开的,所以修改config.js中tokenSecret值很重要。
+
+*非常重要!非常重要! 非常重要!*
+
+> 可以打开连接`https://www.grc.com/passwords.htm`获取 `63 random alpha-numeric characters`类型的随机生成数作为密钥
+
+## 部署
+
+```shell
+$ sudo docker stack deploy -c docker-compose.yml code-push-server
+```
+
+> 如果网速不佳,需要漫长而耐心的等待。。。去和妹子聊会天吧^_^
+
+
+## 查看进展
+
+```shell
+$ sudo docker service ls
+$ sudo docker service ps code-push-server_db
+$ sudo docker service ps code-push-server_redis
+$ sudo docker service ps code-push-server_server
+```
+
+> 确认`CURRENT STATE` 为 `Running about ...`, 则已经部署完成
+
+## 访问接口简单验证
+
+`$ curl -I http://YOUR_CODE_PUSH_SERVER_IP:3000/`
+
+返回`200 OK`
+
+```http
+HTTP/1.1 200 OK
+X-DNS-Prefetch-Control: off
+X-Frame-Options: SAMEORIGIN
+Strict-Transport-Security: max-age=15552000; includeSubDomains
+X-Download-Options: noopen
+X-Content-Type-Options: nosniff
+X-XSS-Protection: 1; mode=block
+Content-Type: text/html; charset=utf-8
+Content-Length: 592
+ETag: W/"250-IiCMcM1ZUFSswSYCU0KeFYFEMO8"
+Date: Sat, 25 Aug 2018 15:45:46 GMT
+Connection: keep-alive
+```
+
+## 浏览器登录
+
+> 默认用户名:admin 密码:123456 记得要修改默认密码哦
+> 如果登录连续输错密码超过一定次数,会限定无法再登录. 需要清空redis缓存
+
+```shell
+$ redis-cli -p6388 # 进入redis
+> flushall
+> quit
+```
+
+
+## 查看服务日志
+
+```shell
+$ sudo docker service logs code-push-server_server
+$ sudo docker service logs code-push-server_db
+$ sudo docker service logs code-push-server_redis
+```
+
+## 查看存储 `docker volume ls`
+
+DRIVER | VOLUME NAME | 描述
+------ | ----- | -------
+local | code-push-server_data-mysql | 数据库存储数据目录
+local | code-push-server_data-storage | 存储打包文件目录
+local | code-push-server_data-tmp | 用于计算更新包差异文件临时目录
+local | code-push-server_data-redis | redis落地数据
+
+## 销毁退出应用
+
+```bash
+$ sudo docker stack rm code-push-server
+$ sudo docker swarm leave --force
+```
diff --git a/docker/code-push-server/Dockerfile b/docker/code-push-server/Dockerfile
new file mode 100644
index 00000000..004406ba
--- /dev/null
+++ b/docker/code-push-server/Dockerfile
@@ -0,0 +1,10 @@
+FROM node:8.11.4-alpine
+
+RUN npm config set registry https://registry.npm.taobao.org/ \
+&& npm i -g code-push-server@0.5.2 pm2@latest --no-optional
+
+COPY ./process.json /process.json
+
+EXPOSE 3000
+
+CMD ["pm2-docker", "start", "/process.json"]
diff --git a/docker/code-push-server/process.json b/docker/code-push-server/process.json
new file mode 100644
index 00000000..493010d3
--- /dev/null
+++ b/docker/code-push-server/process.json
@@ -0,0 +1,11 @@
+{
+ "apps" : [
+ {
+ "name" : "code-push-server",
+ "max_memory_restart" : "500M",
+ "script" : "code-push-server",
+ "instances" : "max", //开启实例数量,max为cpu核数
+ "exec_mode" : "cluster", //集群模式,最大提升网站并发
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docker/config.js b/docker/config.js
new file mode 100644
index 00000000..f78fc3b6
--- /dev/null
+++ b/docker/config.js
@@ -0,0 +1,93 @@
+var config = {};
+config.development = {
+ // Config for database, only support mysql.
+ db: {
+ username: process.env.MYSQL_USERNAME,
+ password: process.env.MYSQL_PASSWORD,
+ database: process.env.MYSQL_DATABASE,
+ host: process.env.MYSQL_HOST,
+ port: process.env.MYSQL_PORT || 3306,
+ dialect: "mysql",
+ logging: false,
+ operatorsAliases: false,
+ },
+ // Config for local storage when storageType value is "local".
+ local: {
+ // Binary files storage dir, Do not use tmpdir and it's public download dir.
+ storageDir: process.env.STORAGE_DIR,
+ // Binary files download host address which Code Push Server listen to. the files storage in storageDir.
+ downloadUrl: process.env.DOWNLOAD_URL,
+ // public static download spacename.
+ public: '/download'
+ },
+ jwt: {
+ // Recommended: 63 random alpha-numeric characters
+ // Generate using: https://www.grc.com/passwords.htm
+ tokenSecret: 'INSERT_RANDOM_TOKEN_KEY'
+ },
+ common: {
+ /*
+ * tryLoginTimes is control login error times to avoid force attack.
+ * if value is 0, no limit for login auth, it may not safe for account. when it's a number, it means you can
+ * try that times today. but it need config redis server.
+ */
+ tryLoginTimes: 4,
+ // CodePush Web(https://github.com/lisong/code-push-web) login address.
+ //codePushWebUrl: "http://127.0.0.1:3001/login",
+ // create patch updates's number. default value is 3
+ diffNums: 3,
+ // data dir for caclulate diff files. it's optimization.
+ dataDir: process.env.DATA_DIR,
+ // storageType which is your binary package files store. options value is ("local" | "qiniu" | "s3")
+ storageType: "local",
+ // options value is (true | false), when it's true, it will cache updateCheck results in redis.
+ updateCheckCache: false,
+ // options value is (true | false), when it's true, it will cache rollout results in redis
+ rolloutClientUniqueIdCache: false,
+ },
+ // Config for smtp email,register module need validate user email project source https://github.com/nodemailer/nodemailer
+ smtpConfig:{
+ host: "smtp.aliyun.com",
+ port: 465,
+ secure: true,
+ auth: {
+ user: "",
+ pass: ""
+ }
+ },
+ // Config for redis (register module, tryLoginTimes module)
+ redis: {
+ default: {
+ host: process.env.REDIS_HOST,
+ port: process.env.REDIS_PORT || 6379,
+ retry_strategy: function (options) {
+ if (options.error.code === 'ECONNREFUSED') {
+ // End reconnecting on a specific error and flush all commands with a individual error
+ return new Error('The server refused the connection');
+ }
+ if (options.total_retry_time > 1000 * 60 * 60) {
+ // End reconnecting after a specific timeout and flush all commands with a individual error
+ return new Error('Retry time exhausted');
+ }
+ if (options.times_connected > 10) {
+ // End reconnecting with built in error
+ return undefined;
+ }
+ // reconnect after
+ return Math.max(options.attempt * 100, 3000);
+ }
+ }
+ }
+}
+
+config.development.log4js = {
+ appenders: {console: { type: 'console'}},
+ categories : {
+ "default": { appenders: ['console'], level:'error'},
+ "startup": { appenders: ['console'], level:'info'},
+ "http": { appenders: ['console'], level:'info'}
+ }
+}
+
+config.production = Object.assign({}, config.development);
+module.exports = config;
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 00000000..2b625f8f
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,63 @@
+version: "3.7"
+services:
+ server:
+ image: tablee/code-push-server:v0.5.2
+ volumes:
+ - data-storage:/data/storage
+ - data-tmp:/data/tmp
+ - ./config.js:/config.js
+ environment:
+ DOWNLOAD_URL: "http://YOU_MACHINE_IP:3000/download"
+ MYSQL_HOST: "YOU_MACHINE_IP"
+ MYSQL_PORT: "3308"
+ MYSQL_USERNAME: "codepush"
+ MYSQL_PASSWORD: "123456"
+ MYSQL_DATABASE: "codepush"
+ STORAGE_DIR: "/data/storage"
+ DATA_DIR: "/data/tmp"
+ NODE_ENV: "production"
+ CONFIG_FILE: "/config.js"
+ REDIS_HOST: "YOU_MACHINE_IP"
+ REDIS_PORT: "6388"
+ deploy:
+ resources:
+ limits:
+ cpus: "2"
+ memory: 1000M
+ restart_policy:
+ condition: on-failure
+ ports:
+ - "3000:3000"
+ networks:
+ - servernet
+ depends_on:
+ - db
+ - redis
+ db:
+ image: mysql:5.7.23
+ volumes:
+ - data-mysql:/var/lib/mysql
+ - ./sql/codepush-all.sql:/docker-entrypoint-initdb.d/codepush-all.sql
+ ports:
+ - "3308:3306"
+ environment:
+ MYSQL_ALLOW_EMPTY_PASSWORD: "On"
+ networks:
+ - dbnet
+ redis:
+ image: redis:4.0.11-alpine
+ volumes:
+ - data-redis:/data
+ ports:
+ - "6388:6379"
+ networks:
+ - redisnet
+networks:
+ servernet:
+ dbnet:
+ redisnet:
+volumes:
+ data-storage:
+ data-tmp:
+ data-mysql:
+ data-redis:
diff --git a/sql/codepush-v0.0.1.sql b/docker/sql/codepush-all.sql
similarity index 64%
rename from sql/codepush-v0.0.1.sql
rename to docker/sql/codepush-all.sql
index ee7ed007..7bf4047d 100644
--- a/sql/codepush-v0.0.1.sql
+++ b/docker/sql/codepush-all.sql
@@ -1,17 +1,25 @@
-DROP TABLE IF EXISTS `apps`;
-CREATE TABLE `apps` (
+CREATE DATABASE IF NOT EXISTS `codepush`;
+
+GRANT SELECT,UPDATE,INSERT ON `codepush`.* TO 'codepush'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
+
+flush privileges;
+
+use `codepush`;
+CREATE TABLE IF NOT EXISTS `apps` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`uid` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `os` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `platform` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `is_use_diff_text` tinyint(3) unsigned NOT NULL DEFAULT '0',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`(12))
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `collaborators`;
-CREATE TABLE `collaborators` (
+CREATE TABLE IF NOT EXISTS `collaborators` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`appid` int(10) unsigned NOT NULL DEFAULT '0',
`uid` bigint(20) unsigned NOT NULL DEFAULT '0',
@@ -25,8 +33,7 @@ CREATE TABLE `collaborators` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `deployments`;
-CREATE TABLE `deployments` (
+CREATE TABLE IF NOT EXISTS `deployments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`appid` int(10) unsigned NOT NULL DEFAULT '0',
`name` varchar(20) NOT NULL DEFAULT '',
@@ -42,8 +49,7 @@ CREATE TABLE `deployments` (
KEY `idx_deploymentkey` (`deployment_key`(40))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `deployments_history`;
-CREATE TABLE `deployments_history` (
+CREATE TABLE IF NOT EXISTS `deployments_history` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`deployment_id` int(11) unsigned NOT NULL DEFAULT '0',
`package_id` int(10) unsigned NOT NULL DEFAULT '0',
@@ -54,20 +60,23 @@ CREATE TABLE `deployments_history` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `deployments_versions`;
-CREATE TABLE `deployments_versions` (
+CREATE TABLE IF NOT EXISTS `deployments_versions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`deployment_id` int(11) unsigned NOT NULL DEFAULT '0',
- `app_version` varchar(14) NOT NULL DEFAULT '',
+ `app_version` varchar(100) NOT NULL DEFAULT '',
`current_package_id` int(10) unsigned NOT NULL DEFAULT '0',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NULL DEFAULT NULL,
+ `deleted_at` timestamp NULL DEFAULT NULL,
+ `min_version` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `max_version` bigint(20) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
- UNIQUE KEY `idx_did_appversion` (`deployment_id`,`app_version`)
+ KEY `idx_did_minversion` (`deployment_id`,`min_version`),
+ KEY `idx_did_maxversion` (`deployment_id`,`max_version`),
+ KEY `idx_did_appversion` (`deployment_id`,`app_version`(30))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `packages`;
-CREATE TABLE `packages` (
+CREATE TABLE IF NOT EXISTS `packages` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`deployment_version_id` int(10) unsigned NOT NULL DEFAULT '0',
`deployment_id` int(10) unsigned NOT NULL DEFAULT '0',
@@ -82,14 +91,17 @@ CREATE TABLE `packages` (
`original_deployment` varchar(20) NOT NULL DEFAULT '',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NULL DEFAULT NULL,
- `released_by` bigint(20) unsigned NOT NULL,
+ `released_by` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `is_mandatory` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `is_disabled` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `rollout` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_deploymentid_label` (`deployment_id`,`label`(8)),
KEY `idx_versions_id` (`deployment_version_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `packages_diff`;
-CREATE TABLE `packages_diff` (
+CREATE TABLE IF NOT EXISTS `packages_diff` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`package_id` int(11) unsigned NOT NULL DEFAULT '0',
`diff_against_package_hash` varchar(64) NOT NULL DEFAULT '',
@@ -97,12 +109,12 @@ CREATE TABLE `packages_diff` (
`diff_size` int(11) unsigned NOT NULL DEFAULT '0',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NULL DEFAULT NULL,
+ `deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_packageid_hash` (`package_id`,`diff_against_package_hash`(40))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `packages_metrics`;
-CREATE TABLE `packages_metrics` (
+CREATE TABLE IF NOT EXISTS `packages_metrics` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`package_id` int(10) unsigned NOT NULL DEFAULT '0',
`active` int(10) unsigned NOT NULL DEFAULT '0',
@@ -111,12 +123,12 @@ CREATE TABLE `packages_metrics` (
`installed` int(10) unsigned NOT NULL DEFAULT '0',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NULL DEFAULT NULL,
+ `deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
- UNIQUE KEY `udx_packageid` (`package_id`)
+ KEY `idx_packageid` (`package_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `user_tokens`;
-CREATE TABLE `user_tokens` (
+CREATE TABLE IF NOT EXISTS `user_tokens` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uid` bigint(20) unsigned NOT NULL DEFAULT '0',
`name` varchar(50) NOT NULL DEFAULT '',
@@ -132,8 +144,7 @@ CREATE TABLE `user_tokens` (
KEY `idx_tokens` (`tokens`) KEY_BLOCK_SIZE=16
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `users`;
-CREATE TABLE `users` (
+CREATE TABLE IF NOT EXISTS `users` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL DEFAULT '',
`password` varchar(255) NOT NULL DEFAULT '',
@@ -150,4 +161,38 @@ CREATE TABLE `users` (
INSERT INTO `users` (`id`, `username`, `password`, `email`, `identical`, `ack_code`, `updated_at`, `created_at`)
VALUES
- (1,'admin','$2a$12$mvUY9kTqW4kSoGuZFDW0sOSgKmNY8SPHVyVrSckBTLtXKf6vKX3W.','lisong2010@gmail.com','4ksvOXqog','oZmGE','2016-11-14 10:46:55','2016-02-29 21:24:49');
+ (1,'admin','$2a$12$mvUY9kTqW4kSoGuZFDW0sOSgKmNY8SPHVyVrSckBTLtXKf6vKX3W.','lisong2010@gmail.com','4ksvOXqog','oZmGE','2016-11-14 10:46:55','2016-02-29 21:24:49');
+
+
+CREATE TABLE IF NOT EXISTS `versions` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '1.DBversion',
+ `version` varchar(10) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `udx_type` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+LOCK TABLES `versions` WRITE;
+INSERT INTO `versions` (`id`, `type`, `version`)
+VALUES
+ (1,1,'0.5.0');
+UNLOCK TABLES;
+
+CREATE TABLE IF NOT EXISTS `log_report_deploy` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `status` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `package_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `client_unique_id` varchar(100) NOT NULL DEFAULT '',
+ `previous_label` varchar(20) NOT NULL DEFAULT '',
+ `previous_deployment_key` varchar(64) NOT NULL DEFAULT '',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `log_report_download` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `package_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `client_unique_id` varchar(100) NOT NULL DEFAULT '',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..9419e94d
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,238 @@
+
+## INSTALL NODE AND NPM
+
+[see](https://nodejs.org/en/download/)
+
+> (chosen latest LTS version)
+
+## INSTALL PM2
+
+```bash
+$ sudo npm i -g pm2
+```
+
+## INSTALL MYSQL
+
+- [Linux](https://dev.mysql.com/doc/refman/8.0/en/linux-installation.html)
+- [macOS](https://dev.mysql.com/doc/refman/8.0/en/osx-installation.html)
+- [Microsoft Windows](https://dev.mysql.com/doc/refman/8.0/en/windows-installation.html)
+- [Others](https://dev.mysql.com/doc/refman/8.0/en/installing.html)
+
+> notice. mysql8.x default auth caching_sha2_pasword not support in node-mysql2 see [issue](https://github.com/mysqljs/mysql/pull/1962)
+
+
+
+## GET code-push-server FROM NPM
+
+```shell
+$ npm install code-push-server@latest -g
+```
+
+
+## GET code-push-server FROM SOURCE CODE
+
+```shell
+$ git clone https://github.com/lisong/code-push-server.git
+$ cd code-push-server
+$ npm install
+```
+
+## INIT DATABASE
+
+```shell
+$ code-push-server-db init --dbhost "your mysql host" --dbport "your mysql port" --dbuser "your mysql user" --dbpassword "your mysql password"
+```
+
+or from source code
+
+```shell
+$ ./bin/db init --dbhost "your mysql host" --dbport "your mysql port" --dbuser "your mysql user" --dbpassword "your mysql password"
+```
+
+> output: success
+
+## CONFIGURE for code-push-server
+
+save the file [config.js](https://github.com/lisong/code-push-server/blob/master/config/config.js)
+
+some config have to change:
+
+- `local`.`storageDir` change to your directory,make sure have read/write permissions.
+- `local`.`downloadUrl` replace `127.0.0.1` to your machine ip.
+- `common`.`dataDir` change to your directory,make sure have read/write permissions.
+- `jwt`.`tokenSecret` get the random string from `https://www.grc.com/passwords.htm`, and replace the value `INSERT_RANDOM_TOKEN_KEY`.
+- `db` config: `username`,`password`,`host`,`port` change your own's
+
+## CONFIGURE for pm2
+
+save the file [process.json](https://github.com/lisong/code-push-server/blob/master/docs/process.json)
+
+some config have to change:
+
+- `script` if you install code-push-server from npm use `code-push-server`,or use `"your source code dir"/bin/www`
+- `CONFIG_FILE` above config.js file path,use absolute path.
+
+## START SERVICE
+
+```shell
+$ pm2 start process.json
+```
+
+## RESTART SERVICE
+
+```shell
+$ pm2 restart process.json
+```
+
+## STOP SERVICE
+
+```shell
+$ pm2 stop process.json
+```
+
+## CHECK SERVICE IS OK
+
+```shell
+$ curl -I http://YOUR_CODE_PUSH_SERVER_IP:3000/
+```
+
+> return httpCode `200 OK`
+
+```http
+HTTP/1.1 200 OK
+X-DNS-Prefetch-Control: off
+X-Frame-Options: SAMEORIGIN
+Strict-Transport-Security: max-age=15552000; includeSubDomains
+X-Download-Options: noopen
+X-Content-Type-Options: nosniff
+X-XSS-Protection: 1; mode=block
+Content-Type: text/html; charset=utf-8
+Content-Length: 592
+ETag: W/"250-IiCMcM1ZUFSswSYCU0KeFYFEMO8"
+Date: Sat, 25 Aug 2018 15:45:46 GMT
+Connection: keep-alive
+```
+
+
+## Use redis impove concurrent and security
+
+> config redis in config.js
+
+- `updateCheckCache`
+- `rolloutClientUniqueIdCache`
+- `tryLoginTimes`
+
+
+## UPGRADE
+
+*from npm package*
+
+```shell
+$ npm install -g code-push-server@latest
+$ code-push-server-db upgrade --dbhost "your mysql host" --dbport "your mysql port" --dbuser "your mysql user" --dbpassword "your mysql password" # upgrade codepush database
+$ pm2 restart code-push-server # restart service
+```
+
+*from source code*
+
+```shell
+$ cd /path/to/code-push-server
+$ git pull --rebase origin master
+$ ./bin/db upgrade --dbhost "your mysql host" --dbport "your mysql port" --dbuser "your mysql user" --dbpassword "your mysql password"
+# upgrade codepush database
+$ pm2 restart code-push-server # restart service
+```
+
+
+## view pm2 logs
+
+```shell
+$ pm2 ls
+$ pm2 show code-push-server
+$ tail -f "output file path"
+```
+
+
+## Support Storage mode
+
+- local (default)
+- qiniu (qiniu)
+- s3 (aws)
+- oss (aliyun)
+- tencentcloud
+
+## Default listen Host/Port 0.0.0.0/3000
+
+> you can change it in process.json, env: PORT,HOST
+
+
+## [code-push-cli](https://github.com/Microsoft/code-push)
+
+> Use code-push-cli manager CodePushServer
+
+```shell
+$ npm install code-push-cli@latest -g
+$ code-push login http://YOU_SERVICE_IP:3000 #login in browser account:admin password:123456
+```
+
+> change admin password eg.
+
+```shell
+$ curl -X PATCH -H "Authorization: Bearer mytoken" -H "Accept: application/json" -H "Content-Type:application/json" -d '{"oldPassword":"123456","newPassword":"654321"}' http://YOU_SERVICE_IP:3000/users/password
+```
+
+
+## config react-native project
+
+> Follow the react-native-code-push docs, addition iOS add a new entry named CodePushServerURL, whose value is the key of ourself CodePushServer URL. Android use the new CodePush constructor in MainApplication point CodePushServerUrl
+
+iOS eg. in file Info.plist
+
+```xml
+...
+CodePushDeploymentKey
+YourCodePushKey
+CodePushServerURL
+YourCodePushServerUrl
+...
+```
+
+Android eg. in file MainApplication.java
+
+```java
+@Override
+protected List getPackages() {
+ return Arrays.asList(
+ new MainReactPackage(),
+ new CodePush(
+ "YourKey",
+ MainApplication.this,
+ BuildConfig.DEBUG,
+ "YourCodePushServerUrl"
+ )
+ );
+}
+```
+
+
+## [cordova-plugin-code-push](https://github.com/Microsoft/cordova-plugin-code-push) for cordova
+
+```shell
+$ cd /path/to/project
+$ cordova plugin add cordova-plugin-code-push@latest --save
+```
+
+## config cordova project
+
+edit config.xml. add code below.
+
+```xml
+
+
+
+
+
+
+
+
+```
diff --git a/docs/process.json b/docs/process.json
new file mode 100644
index 00000000..8ef0e3e1
--- /dev/null
+++ b/docs/process.json
@@ -0,0 +1,24 @@
+{
+ "apps" : [
+ {
+ "name" : "code-push-server",
+ "max_memory_restart" : "300M",
+ "script" : "/path/to/code-push-server/bin/www",
+ "instances" : "max", //开启实例数量,max为cpu核数
+ "exec_mode" : "cluster", //集群模式,最大提升网站并发
+ "env" : {
+ "NODE_ENV" : "production",
+ "PORT" : 3000,
+ "CONFIG_FILE" : "/path/to/production/config.js"
+
+ // Must set add config when STORAGE_TYPE is upyun
+ // "STORAGE_TYPE" : "upyun",
+ // "DOWNLOAD_URL" : "",
+ // "UPYUN_STORAGE_DIR" : "",
+ // "UPYUN_SERVICE_NAME" : "",
+ // "UPYUN_OPERATOR_NAME" : "",
+ // "UPYUN_OPERATOR_PASS" : ""
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/process.yml b/docs/process.yml
deleted file mode 100644
index 045427b5..00000000
--- a/docs/process.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-apps:
- - script : /path/to/code-push-server/bin/www
- name : code-push-server
- watch : false
- env :
- NODE_ENV: production
- PORT: 3001
- CONFIG_FILE: /path/to/production/config.js
\ No newline at end of file
diff --git a/docs/react-native-code-push.md b/docs/react-native-code-push.md
index e2008ae8..0b70bf37 100644
--- a/docs/react-native-code-push.md
+++ b/docs/react-native-code-push.md
@@ -36,26 +36,23 @@ $ npm install code-push-cli@latest -g
```shell
$ react-native init CodePushDemo #初始化一个react-native项目
$ cd CodePushDemo
-$ npm install --save react-native-code-push #安装react-native-code-push
+$ npm install --save react-native-code-push@latest #安装react-native-code-push
$ react-native link react-native-code-push #连接到项目中,提示输入配置可以先行忽略
```
-#### 4. [code-push-server](https://github.com/lisong/code-push-server) 微软云服务在中国太慢,可以用它搭建自己的服务端。具体配置参考该项目
+#### 4. [code-push-server](https://github.com/lisong/code-push-server) 微软云服务在中国太慢,可以用它搭建自己的服务端。
-```shell
-$ npm install code-push-server -g
-$ code-push-server-db init --dbhost localhost --dbuser root --dbpassword #初始化数据库
-$ code-push-server #启动服务 浏览器中打开 http://127.0.0.1:3000
-```
+- [docker](https://github.com/lisong/code-push-server/blob/master/docker/README.md) (recommend)
+- [manual operation](https://github.com/lisong/code-push-server/blob/master/docs/README.md)
## 创建服务端应用
基于code-push-server服务
```shell
-$ code-push login http://127.0.0.1:3000 #浏览器中登录获取token,用户名:admin, 密码:123456
-$ code-push app add CodePushDemo-ios #创建iOS版, 获取Production DeploymentKey
-$ code-push app add CodePushDemo-android #创建android版,获取获取Production DeploymentKey
+$ code-push login http://YOUR_CODE_PUSH_SERVER_IP:3000 #浏览器中登录获取token,用户名:admin, 密码:123456
+$ code-push app add CodePushDemoiOS ios react-native #创建iOS版, 获取Production DeploymentKey
+$ code-push app add CodePushDemoAndroid android react-native #创建android版,获取获取Production DeploymentKey
```
## 配置CodePushDemo react-native项目
@@ -66,7 +63,7 @@ $ code-push app add CodePushDemo-android #创建android版,获取获取Product
1. `CodePushDeploymentKey`值设置为CodePushDemo-ios的Production DeploymentKey值。
-2. `CodePushServerURL`值设置为code-push-server服务地址 http://127.0.0.1:3000/ 不在同一台机器的时候,请将127.0.0.1改成外网ip或者域名地址。
+2. `CodePushServerURL`值设置为code-push-server服务地址 http://YOUR_CODE_PUSH_SERVER_IP:3000/ 不在同一台机器的时候,请将YOUR_CODE_PUSH_SERVER_IP改成外网ip或者域名地址。
3. 将默认版本号1.0改成三位1.0.0
@@ -85,12 +82,10 @@ $ code-push app add CodePushDemo-android #创建android版,获取获取Product
1. `YourKey`替换成CodePushDemo-android的Production DeploymentKey值
-2. `YourCodePushServerUrl`值设置为code-push-server服务地址 http://127.0.0.1:3000/ 不在同一台机器的时候,请将127.0.0.1改成外网ip或者域名地址。
+2. `YourCodePushServerUrl`值设置为code-push-server服务地址 http://YOUR_CODE_PUSH_SERVER_IP:3000/ 不在同一台机器的时候,请将YOUR_CODE_PUSH_SERVER_IP改成外网ip或者域名地址。
3. 将默认版本号1.0改成三位1.0.0
-4. android模拟器和code-push-server在同一台机器上时,需要额外运行命令`adb reverse tcp:3000 tcp:3000` 代理端口,否则无法访问127.0.0.1:3000端口
-
```java
@Override
protected List getPackages() {
@@ -108,7 +103,7 @@ protected List getPackages() {
## 添加更新检查
-可以参考[demo.js](https://github.com/lisong/code-push-demo-app/blob/master/demo.js)
+可以参考[code-push-demo-app](https://github.com/lisong/code-push-demo-app/)
可以在入口componentDidMount添加
```javascript
@@ -124,16 +119,6 @@ CodePush.sync({
import CodePush from "react-native-code-push"
```
-> notice:
->
-> demo.js中用到ECMAScript中Decorators语法,需要安装`$ npm install babel-preset-react-native-stage-0 --save`,
-> 同时在.babelrc中添加'react-native-stage-0/decorator-support'.
->
-> eg.
-> {
-> "presets": ["react-native", "react-native-stage-0/decorator-support"]
-> }
-
## 运行CodePushDemo react-native项目
#### iOS
@@ -163,15 +148,11 @@ $ code-push release-react CodePushDemo-ios ios -d Production #iOS版
$ code-push release-react CodePushDemo-android android -d Production #android版
```
+## 例子
-## 注意事项
+[code-push-demo-app](https://github.com/lisong/code-push-demo-app)
-- 苹果允许使用热更新[Apple's developer agreement](https://developer.apple.com/programs/ios/information/iOS_Program_Information_4_3_15.pdf), 但是规定不能弹框提示用户更新,影响用户体验。 而Google Play恰好相反,必须弹框告知用户更新。然而中国的android市场都必须关闭更新弹框,否则会在审核应用时以“请上传最新版本的二进制应用包”驳回应用。
-- react-native 不同平台bundle包不一样,在使用code-push-server的时候必须创建不同的应用来区分(eg. CodePushDemo-ios 和 CodePushDemo-android)
-- react-native-code-push只更新资源文件,不会更新java和Objective C,所以npm升级依赖包版本的时候,如果依赖包使用的本地化实现, 这时候必须更改应用版本号(ios修改Info.plist中的CFBundleShortVersionString, android修改build.gradle中的versionName), 然后重新编译app发布到应用商店。
-- 推荐使用code-push release-react 命令发布应用,该命令合并了打包和发布命令(eg. code-push release-react CodePushDemo-ios ios -d Production)
-## 例子
+### 更多信息参考[code-push-server](https://github.com/lisong/code-push-server)
-[code-push-demo-app](https://github.com/lisong/code-push-demo-app)
diff --git a/models/apps.js b/models/apps.js
index 3261bc6e..00e0372f 100644
--- a/models/apps.js
+++ b/models/apps.js
@@ -10,6 +10,9 @@ module.exports = function(sequelize, DataTypes) {
},
name: DataTypes.STRING,
uid: DataTypes.BIGINT(20),
+ os: DataTypes.INTEGER(3),
+ platform: DataTypes.INTEGER(3),
+ is_use_diff_text: DataTypes.INTEGER(3),
created_at: DataTypes.DATE,
updated_at: DataTypes.DATE,
}, {
diff --git a/models/collaborators.js b/models/collaborators.js
index 6440b358..c5f8d268 100644
--- a/models/collaborators.js
+++ b/models/collaborators.js
@@ -16,17 +16,14 @@ module.exports = function(sequelize, DataTypes) {
}, {
tableName: 'collaborators',
underscored: true,
- paranoid: true,
- classMethods: {
- findByAppNameAndUid: function(uid, appName) {
- var sql = "SELECT b.* FROM `apps` as a left join `collaborators` as b on (a.id = b.appid) where a.name= :appName and b.uid = :uid and a.`deleted_at` IS NULL and b.`deleted_at` IS NULL limit 0,1";
- return sequelize.query(sql, { replacements: { appName: appName, uid: uid }, model: Collaborators})
- .then(function(data) {
- return data.pop();
- });
- }
- }
-
+ paranoid: true
});
+ Collaborators.findByAppNameAndUid = function (uid, appName) {
+ var sql = "SELECT b.* FROM `apps` as a left join `collaborators` as b on (a.id = b.appid) where a.name= :appName and b.uid = :uid and a.`deleted_at` IS NULL and b.`deleted_at` IS NULL limit 0,1";
+ return sequelize.query(sql, { replacements: { appName: appName, uid: uid }, model: Collaborators})
+ .then(function(data) {
+ return data.pop();
+ });
+ };
return Collaborators;
};
diff --git a/models/deployments.js b/models/deployments.js
index 3726c27a..fade4b93 100644
--- a/models/deployments.js
+++ b/models/deployments.js
@@ -1,6 +1,7 @@
"use strict";
var _ = require('lodash');
+var AppError = require('../core/app-error');
module.exports = function(sequelize, DataTypes) {
var Deployments = sequelize.define("Deployments", {
@@ -21,25 +22,24 @@ module.exports = function(sequelize, DataTypes) {
}, {
tableName: 'deployments',
underscored: true,
- paranoid: true,
- classMethods: {
- generateLabelId: function(deploymentId) {
- var self = this;
- return sequelize.transaction(function (t) {
- return self.findById(deploymentId, {transaction: t,lock: t.LOCK.UPDATE}).then(function (data) {
- if (_.isEmpty(data)){
- throw new Error("does not find deployment");
- }
- data.label_id = data.label_id + 1;
- return data.save({transaction: t})
- .then(function (data) {
- return data.label_id;
- });
- });
- });
- }
- }
+ paranoid: true
});
+ Deployments.generateLabelId = function(deploymentId) {
+ var self = this;
+ return sequelize.transaction(function (t) {
+ return self.findById(deploymentId, {transaction: t,lock: t.LOCK.UPDATE}).then(function (data) {
+ if (_.isEmpty(data)){
+ throw new AppError.AppError("does not find deployment");
+ }
+ data.label_id = data.label_id + 1;
+ return data.save({transaction: t})
+ .then(function (data) {
+ return data.label_id;
+ });
+ });
+ });
+ };
+
return Deployments;
};
diff --git a/models/deployments_versions.js b/models/deployments_versions.js
index d42d06dc..41105ec8 100644
--- a/models/deployments_versions.js
+++ b/models/deployments_versions.js
@@ -11,6 +11,8 @@ module.exports = function(sequelize, DataTypes) {
deployment_id: DataTypes.INTEGER(10),
app_version: DataTypes.STRING,
current_package_id: DataTypes.INTEGER(10),
+ min_version: DataTypes.BIGINT(20),
+ max_version: DataTypes.BIGINT(20),
created_at: DataTypes.DATE,
updated_at: DataTypes.DATE,
}, {
diff --git a/models/log_report_deploy.js b/models/log_report_deploy.js
new file mode 100644
index 00000000..f8c39d5e
--- /dev/null
+++ b/models/log_report_deploy.js
@@ -0,0 +1,24 @@
+"use strict";
+
+module.exports = function(sequelize, DataTypes) {
+ var LogReportDeploy = sequelize.define("LogReportDeploy", {
+ id: {
+ type: DataTypes.BIGINT(20),
+ allowNull: false,
+ autoIncrement: true,
+ primaryKey: true
+ },
+ status: DataTypes.INTEGER(3),
+ package_id : DataTypes.INTEGER(10),
+ client_unique_id : DataTypes.STRING,
+ previous_label : DataTypes.STRING,
+ previous_deployment_key : DataTypes.STRING,
+ created_at: DataTypes.DATE,
+ }, {
+ tableName: 'log_report_deploy',
+ underscored: true,
+ updatedAt: false,
+ paranoid: true
+ });
+ return LogReportDeploy;
+};
diff --git a/models/log_report_download.js b/models/log_report_download.js
new file mode 100644
index 00000000..62966cf9
--- /dev/null
+++ b/models/log_report_download.js
@@ -0,0 +1,21 @@
+"use strict";
+
+module.exports = function(sequelize, DataTypes) {
+ var LogReportDownload = sequelize.define("LogReportDownload", {
+ id: {
+ type: DataTypes.BIGINT(20),
+ allowNull: false,
+ autoIncrement: true,
+ primaryKey: true
+ },
+ package_id : DataTypes.INTEGER(10),
+ client_unique_id : DataTypes.STRING,
+ created_at: DataTypes.DATE,
+ }, {
+ tableName: 'log_report_download',
+ underscored: true,
+ updatedAt: false,
+ paranoid: true
+ });
+ return LogReportDownload;
+};
diff --git a/models/packages.js b/models/packages.js
index 6ec423db..989eb32a 100644
--- a/models/packages.js
+++ b/models/packages.js
@@ -21,6 +21,8 @@ module.exports = function(sequelize, DataTypes) {
original_deployment: DataTypes.STRING,
released_by: DataTypes.STRING,
is_mandatory: DataTypes.INTEGER(3),
+ is_disabled: DataTypes.INTEGER(3),
+ rollout: DataTypes.INTEGER(3),
created_at: DataTypes.DATE,
updated_at: DataTypes.DATE,
}, {
diff --git a/models/packages_metrics.js b/models/packages_metrics.js
index 86a1acff..d95bce92 100644
--- a/models/packages_metrics.js
+++ b/models/packages_metrics.js
@@ -20,41 +20,7 @@ module.exports = function(sequelize, DataTypes) {
}, {
tableName: 'packages_metrics',
underscored: true,
- paranoid: true,
- classMethods: {
- addOne : function (packageId, fieldName) {
- var self = this;
- var sql = 'UPDATE packages_metrics SET `' + fieldName + '`=`' + fieldName + '` + 1 WHERE package_id = :package_id';
- return sequelize.query(sql, { replacements: { package_id: packageId}})
- .spread(function(results, metadata) {
- if (_.eq(results.affectedRows, 0)) {
- var params = {
- package_id: packageId,
- active: 0,
- downloaded: 0,
- failed: 0,
- installed: 0,
- };
- params[fieldName] = 1;
- return self.create(params);
- }else {
- return true;
- }
- });
- },
- addOneOnDownloadById: function (packageId) {
- return this.addOne(packageId, 'downloaded');
- },
- addOneOnFailedById: function (packageId) {
- return this.addOne(packageId, 'failed');
- },
- addOneOnInstalledById: function (packageId) {
- return this.addOne(packageId, 'installed');
- },
- addOneOnActiveById: function (packageId) {
- return this.addOne(packageId, 'active');
- },
- }
+ paranoid: true
});
return PackagesMetrics;
};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..79668d14
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3860 @@
+{
+ "name": "code-push-server",
+ "version": "0.5.4",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@types/babel-types": {
+ "version": "7.0.1",
+ "resolved": "http://registry.npm.taobao.org/@types/babel-types/download/@types/babel-types-7.0.1.tgz",
+ "integrity": "sha1-FAXlOWloxDAplLAWHOQFtyuHQlc="
+ },
+ "@types/babylon": {
+ "version": "6.16.2",
+ "resolved": "http://registry.npm.taobao.org/@types/babylon/download/@types/babylon-6.16.2.tgz",
+ "integrity": "sha1-BizmO2k9mvHCRvWu35KLycMFicg=",
+ "requires": {
+ "@types/babel-types": "*"
+ }
+ },
+ "@types/geojson": {
+ "version": "1.0.6",
+ "resolved": "http://registry.npm.taobao.org/@types/geojson/download/@types/geojson-1.0.6.tgz",
+ "integrity": "sha1-PgKXJyjGkkjCrwjWCkjLuGgP/98="
+ },
+ "@types/node": {
+ "version": "9.4.7",
+ "resolved": "http://registry.npm.taobao.org/@types/node/download/@types/node-9.4.7.tgz",
+ "integrity": "sha1-V9gc2YcZ3yyd4Rjy1fOxEg3NcnU="
+ },
+ "abbrev": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/abbrev/download/abbrev-1.1.1.tgz",
+ "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg="
+ },
+ "accepts": {
+ "version": "1.3.5",
+ "resolved": "http://registry.npm.taobao.org/accepts/download/accepts-1.3.5.tgz",
+ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+ "requires": {
+ "mime-types": "~2.1.18",
+ "negotiator": "0.6.1"
+ }
+ },
+ "acorn": {
+ "version": "3.3.0",
+ "resolved": "http://registry.npm.taobao.org/acorn/download/acorn-3.3.0.tgz",
+ "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo="
+ },
+ "acorn-globals": {
+ "version": "3.1.0",
+ "resolved": "http://registry.npm.taobao.org/acorn-globals/download/acorn-globals-3.1.0.tgz",
+ "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=",
+ "requires": {
+ "acorn": "^4.0.4"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "4.0.13",
+ "resolved": "http://registry.npm.taobao.org/acorn/download/acorn-4.0.13.tgz",
+ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
+ }
+ }
+ },
+ "address": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/address/download/address-1.0.3.tgz",
+ "integrity": "sha1-tfUGMfjWzsi9IMljljr7VeBsvOk="
+ },
+ "agentkeepalive": {
+ "version": "3.3.0",
+ "resolved": "http://registry.npm.taobao.org/agentkeepalive/download/agentkeepalive-3.3.0.tgz",
+ "integrity": "sha1-bV3lgpr9O+JxIgGjknX9EcZRhXw=",
+ "requires": {
+ "humanize-ms": "^1.2.1"
+ }
+ },
+ "ajv": {
+ "version": "5.5.2",
+ "resolved": "http://registry.npm.taobao.org/ajv/download/ajv-5.5.2.tgz",
+ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+ "requires": {
+ "co": "^4.6.0",
+ "fast-deep-equal": "^1.0.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.3.0"
+ }
+ },
+ "align-text": {
+ "version": "0.1.4",
+ "resolved": "http://registry.npm.taobao.org/align-text/download/align-text-0.1.4.tgz",
+ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+ "requires": {
+ "kind-of": "^3.0.2",
+ "longest": "^1.0.1",
+ "repeat-string": "^1.5.2"
+ }
+ },
+ "aliyun-oss-upload-stream": {
+ "version": "1.3.0",
+ "resolved": "http://registry.npm.taobao.org/aliyun-oss-upload-stream/download/aliyun-oss-upload-stream-1.3.0.tgz",
+ "integrity": "sha1-ODAbGfA0QGhDjrY5d6DNldYEcMA="
+ },
+ "aliyun-sdk": {
+ "version": "1.11.10",
+ "resolved": "http://registry.npm.taobao.org/aliyun-sdk/download/aliyun-sdk-1.11.10.tgz",
+ "integrity": "sha1-ZDvH+GDrv08F7mmfoGp05eQR8lA=",
+ "requires": {
+ "node_memcached": "1.1.3",
+ "pomelo-protobuf": "^0.4.0",
+ "protobufjs": "^4.1.2",
+ "xml2js": "0.4.4",
+ "xmlbuilder": "^2.4.5"
+ }
+ },
+ "ambi": {
+ "version": "2.5.0",
+ "resolved": "http://registry.npm.taobao.org/ambi/download/ambi-2.5.0.tgz",
+ "integrity": "sha1-fI43K+SIkRV+fOoBy2+RQ9H3QiA=",
+ "requires": {
+ "editions": "^1.1.1",
+ "typechecker": "^4.3.0"
+ },
+ "dependencies": {
+ "typechecker": {
+ "version": "4.5.0",
+ "resolved": "http://registry.npm.taobao.org/typechecker/download/typechecker-4.5.0.tgz",
+ "integrity": "sha1-w4KSAJeBI2S7r0WVsKtliCRBF6Y=",
+ "requires": {
+ "editions": "^1.3.4"
+ }
+ }
+ }
+ },
+ "amdefine": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/amdefine/download/amdefine-1.0.1.tgz",
+ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ },
+ "ansicolors": {
+ "version": "0.2.1",
+ "resolved": "http://registry.npm.taobao.org/ansicolors/download/ansicolors-0.2.1.tgz",
+ "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8="
+ },
+ "any-promise": {
+ "version": "1.3.0",
+ "resolved": "http://registry.npm.taobao.org/any-promise/download/any-promise-1.3.0.tgz",
+ "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "http://registry.npm.taobao.org/argparse/download/argparse-1.0.10.tgz",
+ "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=",
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ },
+ "dependencies": {
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ }
+ }
+ },
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+ },
+ "asap": {
+ "version": "2.0.6",
+ "resolved": "http://registry.npm.taobao.org/asap/download/asap-2.0.6.tgz",
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+ },
+ "ascli": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/ascli/download/ascli-1.0.1.tgz",
+ "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=",
+ "requires": {
+ "colour": "~0.7.1",
+ "optjs": "~3.2.2"
+ }
+ },
+ "asn1": {
+ "version": "0.2.3",
+ "resolved": "http://registry.npm.taobao.org/asn1/download/asn1-0.2.3.tgz",
+ "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
+ },
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ },
+ "async": {
+ "version": "1.5.2",
+ "resolved": "http://registry.npm.taobao.org/async/download/async-1.5.2.tgz",
+ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "http://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "aws-sdk": {
+ "version": "2.211.0",
+ "resolved": "http://registry.npm.taobao.org/aws-sdk/download/aws-sdk-2.211.0.tgz",
+ "integrity": "sha1-kCt1Jedv+fpyzQOLc7ikfIxnGmI=",
+ "requires": {
+ "buffer": "4.9.1",
+ "events": "^1.1.1",
+ "jmespath": "0.15.0",
+ "querystring": "0.2.0",
+ "sax": "1.2.1",
+ "url": "0.10.3",
+ "uuid": "3.1.0",
+ "xml2js": "0.4.17",
+ "xmlbuilder": "4.2.1"
+ },
+ "dependencies": {
+ "sax": {
+ "version": "1.2.1",
+ "resolved": "http://registry.npm.taobao.org/sax/download/sax-1.2.1.tgz",
+ "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
+ },
+ "xml2js": {
+ "version": "0.4.17",
+ "resolved": "http://registry.npm.taobao.org/xml2js/download/xml2js-0.4.17.tgz",
+ "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=",
+ "requires": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "^4.1.0"
+ }
+ },
+ "xmlbuilder": {
+ "version": "4.2.1",
+ "resolved": "http://registry.npm.taobao.org/xmlbuilder/download/xmlbuilder-4.2.1.tgz",
+ "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=",
+ "requires": {
+ "lodash": "^4.0.0"
+ }
+ }
+ }
+ },
+ "aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "http://registry.npm.taobao.org/aws-sign2/download/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+ },
+ "aws4": {
+ "version": "1.6.0",
+ "resolved": "http://registry.npm.taobao.org/aws4/download/aws4-1.6.0.tgz",
+ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
+ },
+ "axios": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
+ "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
+ "requires": {
+ "follow-redirects": "1.5.10",
+ "is-buffer": "^2.0.2"
+ },
+ "dependencies": {
+ "is-buffer": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
+ "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
+ }
+ }
+ },
+ "babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "http://registry.npm.taobao.org/babel-runtime/download/babel-runtime-6.26.0.tgz",
+ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+ "requires": {
+ "core-js": "^2.4.0",
+ "regenerator-runtime": "^0.11.0"
+ }
+ },
+ "babel-types": {
+ "version": "6.26.0",
+ "resolved": "http://registry.npm.taobao.org/babel-types/download/babel-types-6.26.0.tgz",
+ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.4",
+ "to-fast-properties": "^1.0.3"
+ }
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "resolved": "http://registry.npm.taobao.org/babylon/download/babylon-6.18.0.tgz",
+ "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM="
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "base-64": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
+ "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
+ },
+ "base64-js": {
+ "version": "1.2.3",
+ "resolved": "http://registry.npm.taobao.org/base64-js/download/base64-js-1.2.3.tgz",
+ "integrity": "sha1-+xNmgjPZYUz1+0vOlam6QJbN+AE="
+ },
+ "base64url": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/base64url/download/base64url-2.0.0.tgz",
+ "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs="
+ },
+ "basic-auth": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/basic-auth/download/basic-auth-2.0.0.tgz",
+ "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=",
+ "requires": {
+ "safe-buffer": "5.1.1"
+ }
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/bcrypt-pbkdf/download/bcrypt-pbkdf-1.0.1.tgz",
+ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+ "optional": true,
+ "requires": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "http://registry.npm.taobao.org/bcryptjs/download/bcryptjs-2.4.3.tgz",
+ "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
+ },
+ "bluebird": {
+ "version": "3.5.1",
+ "resolved": "http://registry.npm.taobao.org/bluebird/download/bluebird-3.5.1.tgz",
+ "integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk="
+ },
+ "body-parser": {
+ "version": "1.18.2",
+ "resolved": "http://registry.npm.taobao.org/body-parser/download/body-parser-1.18.2.tgz",
+ "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
+ "requires": {
+ "bytes": "3.0.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.1",
+ "http-errors": "~1.6.2",
+ "iconv-lite": "0.4.19",
+ "on-finished": "~2.3.0",
+ "qs": "6.5.1",
+ "raw-body": "2.3.2",
+ "type-is": "~1.6.15"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "boom": {
+ "version": "4.3.1",
+ "resolved": "http://registry.npm.taobao.org/boom/download/boom-4.3.1.tgz",
+ "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
+ "requires": {
+ "hoek": "4.x.x"
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "http://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz",
+ "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.0",
+ "resolved": "http://registry.npm.taobao.org/browser-stdout/download/browser-stdout-1.3.0.tgz",
+ "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
+ "dev": true
+ },
+ "buffer": {
+ "version": "4.9.1",
+ "resolved": "http://registry.npm.taobao.org/buffer/download/buffer-4.9.1.tgz",
+ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4",
+ "isarray": "^1.0.0"
+ }
+ },
+ "buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "http://registry.npm.taobao.org/buffer-crc32/download/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
+ },
+ "buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/buffer-equal-constant-time/download/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+ },
+ "bufferview": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/bufferview/download/bufferview-1.0.1.tgz",
+ "integrity": "sha1-ev10pF+Tf6QiodM4wIu/3HbNcl0="
+ },
+ "bytebuffer": {
+ "version": "4.1.0",
+ "resolved": "http://registry.npm.taobao.org/bytebuffer/download/bytebuffer-4.1.0.tgz",
+ "integrity": "sha1-TFgmngUqseSx9/82T9+zzogpBqo=",
+ "requires": {
+ "bufferview": "~1",
+ "long": "~2 >=2.3.0"
+ }
+ },
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
+ },
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
+ },
+ "camelize": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/camelize/download/camelize-1.0.0.tgz",
+ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+ },
+ "cardinal": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/cardinal/download/cardinal-1.0.0.tgz",
+ "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=",
+ "requires": {
+ "ansicolors": "~0.2.1",
+ "redeyed": "~1.0.0"
+ }
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "resolved": "http://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "center-align": {
+ "version": "0.1.3",
+ "resolved": "http://registry.npm.taobao.org/center-align/download/center-align-0.1.3.tgz",
+ "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
+ "requires": {
+ "align-text": "^0.1.3",
+ "lazy-cache": "^1.0.3"
+ }
+ },
+ "character-parser": {
+ "version": "2.2.0",
+ "resolved": "http://registry.npm.taobao.org/character-parser/download/character-parser-2.2.0.tgz",
+ "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=",
+ "requires": {
+ "is-regex": "^1.0.3"
+ }
+ },
+ "charenc": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+ "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
+ },
+ "circular-json": {
+ "version": "0.5.5",
+ "resolved": "http://registry.npm.taobao.org/circular-json/download/circular-json-0.5.5.tgz",
+ "integrity": "sha1-ZBgu81kELTfNjnZ/yd6Hix6UR9M="
+ },
+ "clean-css": {
+ "version": "3.4.28",
+ "resolved": "http://registry.npm.taobao.org/clean-css/download/clean-css-3.4.28.tgz",
+ "integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=",
+ "requires": {
+ "commander": "2.8.x",
+ "source-map": "0.4.x"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.8.1",
+ "resolved": "http://registry.npm.taobao.org/commander/download/commander-2.8.1.tgz",
+ "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
+ "requires": {
+ "graceful-readlink": ">= 1.0.0"
+ }
+ },
+ "source-map": {
+ "version": "0.4.4",
+ "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.4.4.tgz",
+ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "cliui": {
+ "version": "3.2.0",
+ "resolved": "http://registry.npm.taobao.org/cliui/download/cliui-3.2.0.tgz",
+ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "cls-bluebird": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/cls-bluebird/download/cls-bluebird-2.1.0.tgz",
+ "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=",
+ "requires": {
+ "is-bluebird": "^1.0.2",
+ "shimmer": "^1.1.0"
+ }
+ },
+ "co": {
+ "version": "4.6.0",
+ "resolved": "http://registry.npm.taobao.org/co/download/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+ },
+ "coffee-script": {
+ "version": "1.12.7",
+ "resolved": "http://registry.npm.taobao.org/coffee-script/download/coffee-script-1.12.7.tgz",
+ "integrity": "sha1-wF2uDLeVkdBbMHCoQzqYyaiczFM="
+ },
+ "colour": {
+ "version": "0.7.1",
+ "resolved": "http://registry.npm.taobao.org/colour/download/colour-0.7.1.tgz",
+ "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g="
+ },
+ "combined-stream": {
+ "version": "1.0.6",
+ "resolved": "http://registry.npm.taobao.org/combined-stream/download/combined-stream-1.0.6.tgz",
+ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "http://registry.npm.taobao.org/component-emitter/download/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "http://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "concat-stream": {
+ "version": "1.6.0",
+ "resolved": "http://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.0.tgz",
+ "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+ "requires": {
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "configstore": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
+ "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==",
+ "requires": {
+ "dot-prop": "^4.1.0",
+ "graceful-fs": "^4.1.2",
+ "make-dir": "^1.0.0",
+ "unique-string": "^1.0.0",
+ "write-file-atomic": "^2.0.0",
+ "xdg-basedir": "^3.0.0"
+ }
+ },
+ "constantinople": {
+ "version": "3.1.2",
+ "resolved": "http://registry.npm.taobao.org/constantinople/download/constantinople-3.1.2.tgz",
+ "integrity": "sha1-1F7XJPV9PRBQABen06iJwTga5kc=",
+ "requires": {
+ "@types/babel-types": "^7.0.0",
+ "@types/babylon": "^6.16.2",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0"
+ }
+ },
+ "content-disposition": {
+ "version": "0.5.2",
+ "resolved": "http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz",
+ "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
+ },
+ "content-security-policy-builder": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/content-security-policy-builder/download/content-security-policy-builder-2.0.0.tgz",
+ "integrity": "sha1-h0mh1UL8voIjcoHqn3Fs5os5TdI="
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz",
+ "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js="
+ },
+ "cookie": {
+ "version": "0.3.1",
+ "resolved": "http://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz",
+ "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+ },
+ "cookie-parser": {
+ "version": "1.4.3",
+ "resolved": "http://registry.npm.taobao.org/cookie-parser/download/cookie-parser-1.4.3.tgz",
+ "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=",
+ "requires": {
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6"
+ }
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "http://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+ },
+ "cookiejar": {
+ "version": "2.1.1",
+ "resolved": "http://registry.npm.taobao.org/cookiejar/download/cookiejar-2.1.1.tgz",
+ "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=",
+ "dev": true
+ },
+ "core-js": {
+ "version": "2.5.3",
+ "resolved": "http://registry.npm.taobao.org/core-js/download/core-js-2.5.3.tgz",
+ "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "cos-nodejs-sdk-v5": {
+ "version": "2.4.10",
+ "resolved": "https://registry.npmjs.org/cos-nodejs-sdk-v5/-/cos-nodejs-sdk-v5-2.4.10.tgz",
+ "integrity": "sha512-espPlCyyOc4vBKKI6Mwb5ZKzjYDgd0GOK4H0msiryupc3wlQCfPqJM+PubVlUEssqK58wIiV9Bq4Bo/ts2irjg==",
+ "requires": {
+ "configstore": "^3.1.2",
+ "qcloudapi-sdk": "^0.2.0",
+ "request": "^2.81.0",
+ "xml2js": "^0.4.19"
+ },
+ "dependencies": {
+ "xml2js": {
+ "version": "0.4.19",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
+ "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
+ "requires": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~9.0.1"
+ }
+ },
+ "xmlbuilder": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
+ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
+ }
+ }
+ },
+ "crc32": {
+ "version": "0.2.2",
+ "resolved": "http://registry.npm.taobao.org/crc32/download/crc32-0.2.2.tgz",
+ "integrity": "sha1-etIg1v/c0Rn5/BJ6d3LKzqOQpLo="
+ },
+ "cross-spawn": {
+ "version": "5.1.0",
+ "resolved": "http://registry.npm.taobao.org/cross-spawn/download/cross-spawn-5.1.0.tgz",
+ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "crypt": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
+ },
+ "cryptiles": {
+ "version": "3.1.2",
+ "resolved": "http://registry.npm.taobao.org/cryptiles/download/cryptiles-3.1.2.tgz",
+ "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
+ "requires": {
+ "boom": "5.x.x"
+ },
+ "dependencies": {
+ "boom": {
+ "version": "5.2.0",
+ "resolved": "http://registry.npm.taobao.org/boom/download/boom-5.2.0.tgz",
+ "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=",
+ "requires": {
+ "hoek": "4.x.x"
+ }
+ }
+ }
+ },
+ "crypto-random-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
+ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
+ },
+ "csextends": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/csextends/download/csextends-1.1.1.tgz",
+ "integrity": "sha1-zFPBNJ+vfwrmzfb2xKTZFW08TsE=",
+ "requires": {
+ "coffee-script": "^1.12.5"
+ }
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "http://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "dasherize": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/dasherize/download/dasherize-2.0.0.tgz",
+ "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg="
+ },
+ "date-format": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/date-format/download/date-format-1.2.0.tgz",
+ "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg="
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz",
+ "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "http://registry.npm.taobao.org/deep-is/download/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "default-user-agent": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/default-user-agent/download/default-user-agent-1.0.0.tgz",
+ "integrity": "sha1-FsRu/cq6PtxF8k8r1IaLAbfCrcY=",
+ "requires": {
+ "os-name": "~1.0.3"
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ },
+ "denque": {
+ "version": "1.2.3",
+ "resolved": "http://registry.npm.taobao.org/denque/download/denque-1.2.3.tgz",
+ "integrity": "sha1-mMUMjdjN+uMYzFhZzI7j2g+bDMI="
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "http://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+ },
+ "diff": {
+ "version": "3.2.0",
+ "resolved": "http://registry.npm.taobao.org/diff/download/diff-3.2.0.tgz",
+ "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=",
+ "dev": true
+ },
+ "diff-match-patch": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.1.tgz",
+ "integrity": "sha512-A0QEhr4PxGUMEtKxd6X+JLnOTFd3BfIPSDpsc4dMvj+CbSaErDwTpoTo/nFJDMSrjxLW4BiNq+FbNisAAHhWeQ=="
+ },
+ "digest-header": {
+ "version": "0.0.1",
+ "resolved": "http://registry.npm.taobao.org/digest-header/download/digest-header-0.0.1.tgz",
+ "integrity": "sha1-Ecz23uxXZqw3l0TZAcEsuklRS+Y=",
+ "requires": {
+ "utility": "0.1.11"
+ }
+ },
+ "dns-prefetch-control": {
+ "version": "0.1.0",
+ "resolved": "http://registry.npm.taobao.org/dns-prefetch-control/download/dns-prefetch-control-0.1.0.tgz",
+ "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI="
+ },
+ "doctypes": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/doctypes/download/doctypes-1.1.0.tgz",
+ "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
+ },
+ "dont-sniff-mimetype": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/dont-sniff-mimetype/download/dont-sniff-mimetype-1.0.0.tgz",
+ "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g="
+ },
+ "dot-prop": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
+ "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
+ "requires": {
+ "is-obj": "^1.0.0"
+ }
+ },
+ "dot-qs": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/dot-qs/-/dot-qs-0.2.0.tgz",
+ "integrity": "sha1-02UX/iS3zaYfznpQJqACSvr1pDk="
+ },
+ "dottie": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/dottie/download/dottie-2.0.0.tgz",
+ "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA="
+ },
+ "double-ended-queue": {
+ "version": "2.1.0-0",
+ "resolved": "http://registry.npm.taobao.org/double-ended-queue/download/double-ended-queue-2.1.0-0.tgz",
+ "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw="
+ },
+ "eachr": {
+ "version": "2.0.4",
+ "resolved": "http://registry.npm.taobao.org/eachr/download/eachr-2.0.4.tgz",
+ "integrity": "sha1-Rm98qhBwj2EFCeMsgHqv5X/BIr8=",
+ "requires": {
+ "typechecker": "^2.0.8"
+ }
+ },
+ "ecc-jsbn": {
+ "version": "0.1.1",
+ "resolved": "http://registry.npm.taobao.org/ecc-jsbn/download/ecc-jsbn-0.1.1.tgz",
+ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+ "optional": true,
+ "requires": {
+ "jsbn": "~0.1.0"
+ }
+ },
+ "ecdsa-sig-formatter": {
+ "version": "1.0.9",
+ "resolved": "http://registry.npm.taobao.org/ecdsa-sig-formatter/download/ecdsa-sig-formatter-1.0.9.tgz",
+ "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=",
+ "requires": {
+ "base64url": "^2.0.0",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "editions": {
+ "version": "1.3.4",
+ "resolved": "http://registry.npm.taobao.org/editions/download/editions-1.3.4.tgz",
+ "integrity": "sha1-NmLLWSNHwxaOuOSYoP9zJx1n9Qs="
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+ },
+ "entities": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/entities/download/entities-1.1.1.tgz",
+ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "http://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "http://registry.npm.taobao.org/esutils/download/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
+ },
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "http://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+ },
+ "events": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/events/download/events-1.1.1.tgz",
+ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
+ },
+ "execa": {
+ "version": "0.7.0",
+ "resolved": "http://registry.npm.taobao.org/execa/download/execa-0.7.0.tgz",
+ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+ "requires": {
+ "cross-spawn": "^5.0.1",
+ "get-stream": "^3.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "expect-ct": {
+ "version": "0.1.0",
+ "resolved": "http://registry.npm.taobao.org/expect-ct/download/expect-ct-0.1.0.tgz",
+ "integrity": "sha1-UnNWeN4YUwiQ2Ne5XwrGNkCVgJQ="
+ },
+ "express": {
+ "version": "4.16.3",
+ "resolved": "http://registry.npm.taobao.org/express/download/express-4.16.3.tgz",
+ "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
+ "requires": {
+ "accepts": "~1.3.5",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.18.2",
+ "content-disposition": "0.5.2",
+ "content-type": "~1.0.4",
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.1.1",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.2",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.3",
+ "qs": "6.5.1",
+ "range-parser": "~1.2.0",
+ "safe-buffer": "5.1.1",
+ "send": "0.16.2",
+ "serve-static": "1.13.2",
+ "setprototypeof": "1.1.0",
+ "statuses": "~1.4.0",
+ "type-is": "~1.6.16",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz",
+ "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY="
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.1",
+ "resolved": "http://registry.npm.taobao.org/extend/download/extend-3.0.1.tgz",
+ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
+ },
+ "extendr": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/extendr/download/extendr-2.1.0.tgz",
+ "integrity": "sha1-MBqgu+pWX00tyPVw8qImEahSe1Y=",
+ "requires": {
+ "typechecker": "~2.0.1"
+ },
+ "dependencies": {
+ "typechecker": {
+ "version": "2.0.8",
+ "resolved": "http://registry.npm.taobao.org/typechecker/download/typechecker-2.0.8.tgz",
+ "integrity": "sha1-6D2oS7ZMWEzLNFg4V2xAsDN9uC4="
+ }
+ }
+ },
+ "extract-opts": {
+ "version": "2.2.0",
+ "resolved": "http://registry.npm.taobao.org/extract-opts/download/extract-opts-2.2.0.tgz",
+ "integrity": "sha1-H6KOunNSxttID4hc63GkaBC+bX0=",
+ "requires": {
+ "typechecker": "~2.0.1"
+ },
+ "dependencies": {
+ "typechecker": {
+ "version": "2.0.8",
+ "resolved": "http://registry.npm.taobao.org/typechecker/download/typechecker-2.0.8.tgz",
+ "integrity": "sha1-6D2oS7ZMWEzLNFg4V2xAsDN9uC4="
+ }
+ }
+ },
+ "extract-zip": {
+ "version": "1.6.6",
+ "resolved": "http://registry.npm.taobao.org/extract-zip/download/extract-zip-1.6.6.tgz",
+ "integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=",
+ "requires": {
+ "concat-stream": "1.6.0",
+ "debug": "2.6.9",
+ "mkdirp": "0.5.0",
+ "yauzl": "2.4.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "http://registry.npm.taobao.org/extsprintf/download/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+ },
+ "fast-deep-equal": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-1.1.0.tgz",
+ "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/fast-json-stable-stringify/download/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "http://registry.npm.taobao.org/fast-levenshtein/download/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "fd-slicer": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/fd-slicer/download/fd-slicer-1.0.1.tgz",
+ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
+ "requires": {
+ "pend": "~1.2.0"
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.1.tgz",
+ "integrity": "sha1-7r9O2EAHnIP0JJA4ydcDAIMBsQU=",
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.2",
+ "statuses": "~1.4.0",
+ "unpipe": "~1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/find-up/download/find-up-3.0.0.tgz",
+ "integrity": "sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=",
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ }
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "http://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "2.3.2",
+ "resolved": "http://registry.npm.taobao.org/form-data/download/form-data-2.3.2.tgz",
+ "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "formidable": {
+ "version": "1.2.1",
+ "resolved": "http://registry.npm.taobao.org/formidable/download/formidable-1.2.1.tgz",
+ "integrity": "sha1-cPt8oCkO5v+WEJBBX0s989IIJlk="
+ },
+ "formstream": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/formstream/download/formstream-1.1.0.tgz",
+ "integrity": "sha1-UfOXDyYTbrCtRDBN5M67UCB7RHk=",
+ "requires": {
+ "destroy": "^1.0.4",
+ "mime": "^1.3.4",
+ "pause-stream": "~0.0.11"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "http://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz",
+ "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE="
+ }
+ }
+ },
+ "forwarded": {
+ "version": "0.1.2",
+ "resolved": "http://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz",
+ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+ },
+ "frameguard": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/frameguard/download/frameguard-3.0.0.tgz",
+ "integrity": "sha1-e8rUae57lukdEs6zlZx4I1qScuk="
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "http://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+ },
+ "fs-extra": {
+ "version": "7.0.0",
+ "resolved": "http://registry.npm.taobao.org/fs-extra/download/fs-extra-7.0.0.tgz",
+ "integrity": "sha1-jMP0fOB+97NZOhG5+yRffjTAQdY=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/fs.realpath/download/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz",
+ "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0="
+ },
+ "generate-function": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/generate-function/download/generate-function-2.0.0.tgz",
+ "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ="
+ },
+ "generic-pool": {
+ "version": "3.4.2",
+ "resolved": "http://registry.npm.taobao.org/generic-pool/download/generic-pool-3.4.2.tgz",
+ "integrity": "sha1-kv9xllINZwg5pnMICSoSqt8valk="
+ },
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/get-caller-file/download/get-caller-file-1.0.3.tgz",
+ "integrity": "sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o="
+ },
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/get-stream/download/get-stream-3.0.0.tgz",
+ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "http://registry.npm.taobao.org/getpass/download/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "glob": {
+ "version": "5.0.15",
+ "resolved": "http://registry.npm.taobao.org/glob/download/glob-5.0.15.tgz",
+ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+ "requires": {
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "2 || 3",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.11",
+ "resolved": "http://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+ },
+ "graceful-readlink": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/graceful-readlink/download/graceful-readlink-1.0.1.tgz",
+ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
+ },
+ "growl": {
+ "version": "1.9.2",
+ "resolved": "http://registry.npm.taobao.org/growl/download/growl-1.9.2.tgz",
+ "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
+ "dev": true
+ },
+ "handlebars": {
+ "version": "4.0.11",
+ "resolved": "http://registry.npm.taobao.org/handlebars/download/handlebars-4.0.11.tgz",
+ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=",
+ "dev": true,
+ "requires": {
+ "async": "^1.4.0",
+ "optimist": "^0.6.1",
+ "source-map": "^0.4.4",
+ "uglify-js": "^2.6"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.4.4",
+ "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.4.4.tgz",
+ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+ "dev": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "har-schema": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/har-schema/download/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+ },
+ "har-validator": {
+ "version": "5.0.3",
+ "resolved": "http://registry.npm.taobao.org/har-validator/download/har-validator-5.0.3.tgz",
+ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
+ "requires": {
+ "ajv": "^5.1.0",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "has": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/has/download/has-1.0.1.tgz",
+ "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
+ "requires": {
+ "function-bind": "^1.0.2"
+ }
+ },
+ "has-flag": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz",
+ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+ "dev": true
+ },
+ "hawk": {
+ "version": "6.0.2",
+ "resolved": "http://registry.npm.taobao.org/hawk/download/hawk-6.0.2.tgz",
+ "integrity": "sha1-r02RTrBl+bXOTZ0RwcshJu7MMDg=",
+ "requires": {
+ "boom": "4.x.x",
+ "cryptiles": "3.x.x",
+ "hoek": "4.x.x",
+ "sntp": "2.x.x"
+ }
+ },
+ "he": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/he/download/he-1.1.1.tgz",
+ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+ "dev": true
+ },
+ "helmet": {
+ "version": "3.12.0",
+ "resolved": "http://registry.npm.taobao.org/helmet/download/helmet-3.12.0.tgz",
+ "integrity": "sha1-IJjjXPTlHGTC8dOGcLfTgqN32Sw=",
+ "requires": {
+ "dns-prefetch-control": "0.1.0",
+ "dont-sniff-mimetype": "1.0.0",
+ "expect-ct": "0.1.0",
+ "frameguard": "3.0.0",
+ "helmet-csp": "2.7.0",
+ "hide-powered-by": "1.0.0",
+ "hpkp": "2.0.0",
+ "hsts": "2.1.0",
+ "ienoopen": "1.0.0",
+ "nocache": "2.0.0",
+ "referrer-policy": "1.1.0",
+ "x-xss-protection": "1.1.0"
+ }
+ },
+ "helmet-csp": {
+ "version": "2.7.0",
+ "resolved": "http://registry.npm.taobao.org/helmet-csp/download/helmet-csp-2.7.0.tgz",
+ "integrity": "sha1-eTQJRhfR/re7LcQ7t9nogw93RxY=",
+ "requires": {
+ "camelize": "1.0.0",
+ "content-security-policy-builder": "2.0.0",
+ "dasherize": "2.0.0",
+ "lodash.reduce": "4.6.0",
+ "platform": "1.3.5"
+ }
+ },
+ "hide-powered-by": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/hide-powered-by/download/hide-powered-by-1.0.0.tgz",
+ "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys="
+ },
+ "hmacsha1": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/hmacsha1/-/hmacsha1-1.0.0.tgz",
+ "integrity": "sha1-wbeuA6TqEWNICQrxT4FIwSk4qRc="
+ },
+ "hoek": {
+ "version": "4.2.1",
+ "resolved": "http://registry.npm.taobao.org/hoek/download/hoek-4.2.1.tgz",
+ "integrity": "sha1-ljRQKqEsRF3Vp8VzS1cruHOKrLs="
+ },
+ "hpkp": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/hpkp/download/hpkp-2.0.0.tgz",
+ "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI="
+ },
+ "hsts": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/hsts/download/hsts-2.1.0.tgz",
+ "integrity": "sha1-y9bJGKI4X+4d1WgL+ys6GUwBIcw="
+ },
+ "http-errors": {
+ "version": "1.6.2",
+ "resolved": "http://registry.npm.taobao.org/http-errors/download/http-errors-1.6.2.tgz",
+ "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
+ "requires": {
+ "depd": "1.1.1",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.0.3",
+ "statuses": ">= 1.3.1 < 2"
+ },
+ "dependencies": {
+ "depd": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/depd/download/depd-1.1.1.tgz",
+ "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
+ }
+ }
+ },
+ "http-signature": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/http-signature/download/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "http://registry.npm.taobao.org/humanize-ms/download/humanize-ms-1.2.1.tgz",
+ "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
+ "requires": {
+ "ms": "^2.0.0"
+ }
+ },
+ "i18n": {
+ "version": "0.8.3",
+ "resolved": "http://registry.npm.taobao.org/i18n/download/i18n-0.8.3.tgz",
+ "integrity": "sha1-LYzxwkciYCwgQdAbpq5eqlE4jw4=",
+ "requires": {
+ "debug": "*",
+ "make-plural": "^3.0.3",
+ "math-interval-parser": "^1.1.0",
+ "messageformat": "^0.3.1",
+ "mustache": "*",
+ "sprintf-js": ">=1.0.3"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.19",
+ "resolved": "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.19.tgz",
+ "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs="
+ },
+ "ieee754": {
+ "version": "1.1.10",
+ "resolved": "http://registry.npm.taobao.org/ieee754/download/ieee754-1.1.10.tgz",
+ "integrity": "sha1-cZpvewJoMeZL24OLDeG7ACm79xY="
+ },
+ "ienoopen": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/ienoopen/download/ienoopen-1.0.0.tgz",
+ "integrity": "sha1-NGpCj0dKrI9QzzeE6i0PFvYr2ms="
+ },
+ "ignorefs": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/ignorefs/download/ignorefs-1.2.0.tgz",
+ "integrity": "sha1-2ln7hYl25KXkNwLM0fKC/byeV1Y=",
+ "requires": {
+ "editions": "^1.3.3",
+ "ignorepatterns": "^1.1.0"
+ }
+ },
+ "ignorepatterns": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/ignorepatterns/download/ignorepatterns-1.1.0.tgz",
+ "integrity": "sha1-rI9DbyI5td+2bV8NOpBKh6xnzF4="
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "http://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "invert-kv": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/invert-kv/download/invert-kv-1.0.0.tgz",
+ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
+ },
+ "ipaddr.js": {
+ "version": "1.6.0",
+ "resolved": "http://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.6.0.tgz",
+ "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs="
+ },
+ "is-bluebird": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/is-bluebird/download/is-bluebird-1.0.2.tgz",
+ "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI="
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "http://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz",
+ "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4="
+ },
+ "is-expression": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/is-expression/download/is-expression-3.0.0.tgz",
+ "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=",
+ "requires": {
+ "acorn": "~4.0.2",
+ "object-assign": "^4.0.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "4.0.13",
+ "resolved": "http://registry.npm.taobao.org/acorn/download/acorn-4.0.13.tgz",
+ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
+ }
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
+ },
+ "is-promise": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/is-promise/download/is-promise-2.1.0.tgz",
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
+ },
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "http://registry.npm.taobao.org/is-regex/download/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "requires": {
+ "has": "^1.0.1"
+ }
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/is-stream/download/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/is-typedarray/download/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "http://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "istanbul": {
+ "version": "0.4.5",
+ "resolved": "http://registry.npm.taobao.org/istanbul/download/istanbul-0.4.5.tgz",
+ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1.0.x",
+ "async": "1.x",
+ "escodegen": "1.8.x",
+ "esprima": "2.7.x",
+ "glob": "^5.0.15",
+ "handlebars": "^4.0.1",
+ "js-yaml": "3.x",
+ "mkdirp": "0.5.x",
+ "nopt": "3.x",
+ "once": "1.x",
+ "resolve": "1.1.x",
+ "supports-color": "^3.1.0",
+ "which": "^1.1.1",
+ "wordwrap": "^1.0.0"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.0.9",
+ "resolved": "http://registry.npm.taobao.org/abbrev/download/abbrev-1.0.9.tgz",
+ "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=",
+ "dev": true
+ },
+ "escodegen": {
+ "version": "1.8.1",
+ "resolved": "http://registry.npm.taobao.org/escodegen/download/escodegen-1.8.1.tgz",
+ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
+ "dev": true,
+ "requires": {
+ "esprima": "^2.7.1",
+ "estraverse": "^1.9.1",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1",
+ "source-map": "~0.2.0"
+ }
+ },
+ "esprima": {
+ "version": "2.7.3",
+ "resolved": "http://registry.npm.taobao.org/esprima/download/esprima-2.7.3.tgz",
+ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+ "dev": true
+ },
+ "estraverse": {
+ "version": "1.9.3",
+ "resolved": "http://registry.npm.taobao.org/estraverse/download/estraverse-1.9.3.tgz",
+ "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.1.7",
+ "resolved": "http://registry.npm.taobao.org/resolve/download/resolve-1.1.7.tgz",
+ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.2.0",
+ "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.2.0.tgz",
+ "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ },
+ "supports-color": {
+ "version": "3.2.3",
+ "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz",
+ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+ "dev": true,
+ "requires": {
+ "has-flag": "^1.0.0"
+ }
+ }
+ }
+ },
+ "jmespath": {
+ "version": "0.15.0",
+ "resolved": "http://registry.npm.taobao.org/jmespath/download/jmespath-0.15.0.tgz",
+ "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
+ },
+ "js-stringify": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/js-stringify/download/js-stringify-1.0.2.tgz",
+ "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds="
+ },
+ "js-yaml": {
+ "version": "3.11.0",
+ "resolved": "http://registry.npm.taobao.org/js-yaml/download/js-yaml-3.11.0.tgz",
+ "integrity": "sha1-WXwai9VxUvJtYizkEXhRpR9euu8=",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "dependencies": {
+ "esprima": {
+ "version": "4.0.0",
+ "resolved": "http://registry.npm.taobao.org/esprima/download/esprima-4.0.0.tgz",
+ "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=",
+ "dev": true
+ }
+ }
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "http://registry.npm.taobao.org/jsbn/download/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "optional": true
+ },
+ "jschardet": {
+ "version": "1.6.0",
+ "resolved": "http://registry.npm.taobao.org/jschardet/download/jschardet-1.6.0.tgz",
+ "integrity": "sha1-x9GnHtz/KDnbL57DD8XV69PBpng="
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "http://registry.npm.taobao.org/json-schema/download/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+ },
+ "json-schema-traverse": {
+ "version": "0.3.1",
+ "resolved": "http://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.3.1.tgz",
+ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "http://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "json3": {
+ "version": "3.3.2",
+ "resolved": "http://registry.npm.taobao.org/json3/download/json3-3.3.2.tgz",
+ "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
+ "dev": true
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "http://registry.npm.taobao.org/jsonfile/download/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "jsonwebtoken": {
+ "version": "8.2.0",
+ "resolved": "http://registry.npm.taobao.org/jsonwebtoken/download/jsonwebtoken-8.2.0.tgz",
+ "integrity": "sha1-aQ7DqefpXiiENHzj6eudOJqlmLM=",
+ "requires": {
+ "jws": "^3.1.4",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "xtend": "^4.0.1"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "http://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz",
+ "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo="
+ }
+ }
+ },
+ "jsprim": {
+ "version": "1.4.1",
+ "resolved": "http://registry.npm.taobao.org/jsprim/download/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ }
+ },
+ "jstransformer": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/jstransformer/download/jstransformer-1.0.0.tgz",
+ "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=",
+ "requires": {
+ "is-promise": "^2.0.0",
+ "promise": "^7.0.1"
+ }
+ },
+ "jwa": {
+ "version": "1.1.5",
+ "resolved": "http://registry.npm.taobao.org/jwa/download/jwa-1.1.5.tgz",
+ "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=",
+ "requires": {
+ "base64url": "2.0.0",
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.9",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "jws": {
+ "version": "3.1.4",
+ "resolved": "http://registry.npm.taobao.org/jws/download/jws-3.1.4.tgz",
+ "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=",
+ "requires": {
+ "base64url": "^2.0.0",
+ "jwa": "^1.1.4",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ },
+ "lazy-cache": {
+ "version": "1.0.4",
+ "resolved": "http://registry.npm.taobao.org/lazy-cache/download/lazy-cache-1.0.4.tgz",
+ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
+ },
+ "lcid": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/lcid/download/lcid-1.0.0.tgz",
+ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+ "requires": {
+ "invert-kv": "^1.0.0"
+ }
+ },
+ "levn": {
+ "version": "0.3.0",
+ "resolved": "http://registry.npm.taobao.org/levn/download/levn-0.3.0.tgz",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ }
+ },
+ "linkify-it": {
+ "version": "2.0.3",
+ "resolved": "http://registry.npm.taobao.org/linkify-it/download/linkify-it-2.0.3.tgz",
+ "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=",
+ "requires": {
+ "uc.micro": "^1.0.1"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/locate-path/download/locate-path-3.0.0.tgz",
+ "integrity": "sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=",
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.5",
+ "resolved": "http://registry.npm.taobao.org/lodash/download/lodash-4.17.5.tgz",
+ "integrity": "sha1-maktZcAnLevoyWtgV7yPv6O+1RE="
+ },
+ "lodash._baseassign": {
+ "version": "3.2.0",
+ "resolved": "http://registry.npm.taobao.org/lodash._baseassign/download/lodash._baseassign-3.2.0.tgz",
+ "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
+ "dev": true,
+ "requires": {
+ "lodash._basecopy": "^3.0.0",
+ "lodash.keys": "^3.0.0"
+ }
+ },
+ "lodash._basecopy": {
+ "version": "3.0.1",
+ "resolved": "http://registry.npm.taobao.org/lodash._basecopy/download/lodash._basecopy-3.0.1.tgz",
+ "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
+ "dev": true
+ },
+ "lodash._basecreate": {
+ "version": "3.0.3",
+ "resolved": "http://registry.npm.taobao.org/lodash._basecreate/download/lodash._basecreate-3.0.3.tgz",
+ "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=",
+ "dev": true
+ },
+ "lodash._getnative": {
+ "version": "3.9.1",
+ "resolved": "http://registry.npm.taobao.org/lodash._getnative/download/lodash._getnative-3.9.1.tgz",
+ "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
+ "dev": true
+ },
+ "lodash._isiterateecall": {
+ "version": "3.0.9",
+ "resolved": "http://registry.npm.taobao.org/lodash._isiterateecall/download/lodash._isiterateecall-3.0.9.tgz",
+ "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
+ "dev": true
+ },
+ "lodash.create": {
+ "version": "3.1.1",
+ "resolved": "http://registry.npm.taobao.org/lodash.create/download/lodash.create-3.1.1.tgz",
+ "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=",
+ "dev": true,
+ "requires": {
+ "lodash._baseassign": "^3.0.0",
+ "lodash._basecreate": "^3.0.0",
+ "lodash._isiterateecall": "^3.0.0"
+ }
+ },
+ "lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "http://registry.npm.taobao.org/lodash.includes/download/lodash.includes-4.3.0.tgz",
+ "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
+ },
+ "lodash.isarguments": {
+ "version": "3.1.0",
+ "resolved": "http://registry.npm.taobao.org/lodash.isarguments/download/lodash.isarguments-3.1.0.tgz",
+ "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
+ "dev": true
+ },
+ "lodash.isarray": {
+ "version": "3.0.4",
+ "resolved": "http://registry.npm.taobao.org/lodash.isarray/download/lodash.isarray-3.0.4.tgz",
+ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
+ "dev": true
+ },
+ "lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "http://registry.npm.taobao.org/lodash.isboolean/download/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
+ },
+ "lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "http://registry.npm.taobao.org/lodash.isinteger/download/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
+ },
+ "lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "http://registry.npm.taobao.org/lodash.isnumber/download/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "http://registry.npm.taobao.org/lodash.isplainobject/download/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+ },
+ "lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "http://registry.npm.taobao.org/lodash.isstring/download/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+ },
+ "lodash.keys": {
+ "version": "3.1.2",
+ "resolved": "http://registry.npm.taobao.org/lodash.keys/download/lodash.keys-3.1.2.tgz",
+ "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
+ "dev": true,
+ "requires": {
+ "lodash._getnative": "^3.0.0",
+ "lodash.isarguments": "^3.0.0",
+ "lodash.isarray": "^3.0.0"
+ }
+ },
+ "lodash.once": {
+ "version": "4.1.1",
+ "resolved": "http://registry.npm.taobao.org/lodash.once/download/lodash.once-4.1.1.tgz",
+ "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+ },
+ "lodash.reduce": {
+ "version": "4.6.0",
+ "resolved": "http://registry.npm.taobao.org/lodash.reduce/download/lodash.reduce-4.6.0.tgz",
+ "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs="
+ },
+ "log4js": {
+ "version": "3.0.5",
+ "resolved": "http://registry.npm.taobao.org/log4js/download/log4js-3.0.5.tgz",
+ "integrity": "sha1-uAFGv+utaLQw1PNWlVbYpu3+8wM=",
+ "requires": {
+ "circular-json": "^0.5.5",
+ "date-format": "^1.2.0",
+ "debug": "^3.1.0",
+ "rfdc": "^1.1.2",
+ "streamroller": "0.7.0"
+ }
+ },
+ "long": {
+ "version": "2.4.0",
+ "resolved": "http://registry.npm.taobao.org/long/download/long-2.4.0.tgz",
+ "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8="
+ },
+ "longest": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/longest/download/longest-1.0.1.tgz",
+ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
+ },
+ "lru-cache": {
+ "version": "4.1.3",
+ "resolved": "http://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.3.tgz",
+ "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=",
+ "requires": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "make-dir": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ },
+ "make-plural": {
+ "version": "3.0.6",
+ "resolved": "http://registry.npm.taobao.org/make-plural/download/make-plural-3.0.6.tgz",
+ "integrity": "sha1-IDOgO6wpC487uRJY9lud9+iwHKc=",
+ "requires": {
+ "minimist": "^1.2.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "optional": true
+ }
+ }
+ },
+ "markdown-it": {
+ "version": "8.4.1",
+ "resolved": "http://registry.npm.taobao.org/markdown-it/download/markdown-it-8.4.1.tgz",
+ "integrity": "sha1-IG/lmw5OG3inxzJQr5s0pK0Kr0Q=",
+ "requires": {
+ "argparse": "^1.0.7",
+ "entities": "~1.1.1",
+ "linkify-it": "^2.0.0",
+ "mdurl": "^1.0.1",
+ "uc.micro": "^1.0.5"
+ }
+ },
+ "math-interval-parser": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/math-interval-parser/download/math-interval-parser-1.1.0.tgz",
+ "integrity": "sha1-2+2lsGsySZc8bfYXD94jhvCv2JM=",
+ "requires": {
+ "xregexp": "^2.0.0"
+ }
+ },
+ "md5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
+ "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
+ "requires": {
+ "charenc": "~0.0.1",
+ "crypt": "~0.0.1",
+ "is-buffer": "~1.1.1"
+ }
+ },
+ "mdurl": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/mdurl/download/mdurl-1.0.1.tgz",
+ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "http://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+ },
+ "mem": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/mem/download/mem-1.1.0.tgz",
+ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
+ "requires": {
+ "mimic-fn": "^1.0.0"
+ }
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+ },
+ "messageformat": {
+ "version": "0.3.1",
+ "resolved": "http://registry.npm.taobao.org/messageformat/download/messageformat-0.3.1.tgz",
+ "integrity": "sha1-5Y//gkXps5cXmeW0PbWLPpQX9aI=",
+ "requires": {
+ "async": "~1.5.2",
+ "glob": "~6.0.4",
+ "make-plural": "~3.0.3",
+ "nopt": "~3.0.6",
+ "watchr": "~2.4.13"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "6.0.4",
+ "resolved": "http://registry.npm.taobao.org/glob/download/glob-6.0.4.tgz",
+ "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
+ "requires": {
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "2 || 3",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
+ }
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+ },
+ "mime-db": {
+ "version": "1.33.0",
+ "resolved": "http://registry.npm.taobao.org/mime-db/download/mime-db-1.33.0.tgz",
+ "integrity": "sha1-o0kgUKXLm2NFBUHjnZeI0icng9s="
+ },
+ "mime-types": {
+ "version": "2.1.18",
+ "resolved": "http://registry.npm.taobao.org/mime-types/download/mime-types-2.1.18.tgz",
+ "integrity": "sha1-bzI/YKg9ERRvgx/xH9ZuL+VQO7g=",
+ "requires": {
+ "mime-db": "~1.33.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/mimic-fn/download/mimic-fn-1.2.0.tgz",
+ "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI="
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "http://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz",
+ "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ },
+ "mkdirp": {
+ "version": "0.5.0",
+ "resolved": "http://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.0.tgz",
+ "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=",
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "mocha": {
+ "version": "3.5.3",
+ "resolved": "http://registry.npm.taobao.org/mocha/download/mocha-3.5.3.tgz",
+ "integrity": "sha1-HgSA/jbS2lhY0etqzDhBiybqog0=",
+ "dev": true,
+ "requires": {
+ "browser-stdout": "1.3.0",
+ "commander": "2.9.0",
+ "debug": "2.6.8",
+ "diff": "3.2.0",
+ "escape-string-regexp": "1.0.5",
+ "glob": "7.1.1",
+ "growl": "1.9.2",
+ "he": "1.1.1",
+ "json3": "3.3.2",
+ "lodash.create": "3.1.1",
+ "mkdirp": "0.5.1",
+ "supports-color": "3.1.2"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.9.0",
+ "resolved": "http://registry.npm.taobao.org/commander/download/commander-2.9.0.tgz",
+ "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
+ "dev": true,
+ "requires": {
+ "graceful-readlink": ">= 1.0.0"
+ }
+ },
+ "debug": {
+ "version": "2.6.8",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.8.tgz",
+ "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.1",
+ "resolved": "http://registry.npm.taobao.org/glob/download/glob-7.1.1.tgz",
+ "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.2",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "http://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "supports-color": {
+ "version": "3.1.2",
+ "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-3.1.2.tgz",
+ "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=",
+ "dev": true,
+ "requires": {
+ "has-flag": "^1.0.0"
+ }
+ }
+ }
+ },
+ "moment": {
+ "version": "2.21.0",
+ "resolved": "http://registry.npm.taobao.org/moment/download/moment-2.21.0.tgz",
+ "integrity": "sha1-KhFLUdKm7J5tg8+AP4OKh42KAjo="
+ },
+ "moment-timezone": {
+ "version": "0.5.14",
+ "resolved": "http://registry.npm.taobao.org/moment-timezone/download/moment-timezone-0.5.14.tgz",
+ "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=",
+ "requires": {
+ "moment": ">= 2.9.0"
+ }
+ },
+ "morgan": {
+ "version": "1.9.0",
+ "resolved": "http://registry.npm.taobao.org/morgan/download/morgan-1.9.0.tgz",
+ "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=",
+ "requires": {
+ "basic-auth": "~2.0.0",
+ "debug": "2.6.9",
+ "depd": "~1.1.1",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "mustache": {
+ "version": "2.3.0",
+ "resolved": "http://registry.npm.taobao.org/mustache/download/mustache-2.3.0.tgz",
+ "integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA="
+ },
+ "mysql2": {
+ "version": "1.5.2",
+ "resolved": "http://registry.npm.taobao.org/mysql2/download/mysql2-1.5.2.tgz",
+ "integrity": "sha1-5OBzg5uxCXJmmyQOx3Bh1/9Vz1k=",
+ "requires": {
+ "cardinal": "1.0.0",
+ "denque": "^1.1.1",
+ "generate-function": "^2.0.0",
+ "iconv-lite": "^0.4.18",
+ "long": "^4.0.0",
+ "lru-cache": "^4.1.1",
+ "named-placeholders": "1.1.1",
+ "object-assign": "^4.1.1",
+ "readable-stream": "2.3.2",
+ "safe-buffer": "^5.0.1",
+ "seq-queue": "0.0.5",
+ "sqlstring": "^2.2.0"
+ },
+ "dependencies": {
+ "long": {
+ "version": "4.0.0",
+ "resolved": "http://registry.npm.taobao.org/long/download/long-4.0.0.tgz",
+ "integrity": "sha1-mntxz7fTYaGU6lVSQckvdGjVvyg="
+ },
+ "lru-cache": {
+ "version": "4.1.2",
+ "resolved": "http://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.2.tgz",
+ "integrity": "sha1-RSNLLm4vKzPaElYkxGZJKaAiTD8=",
+ "requires": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "process-nextick-args": {
+ "version": "1.0.7",
+ "resolved": "http://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-1.0.7.tgz",
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ },
+ "readable-stream": {
+ "version": "2.3.2",
+ "resolved": "http://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.2.tgz",
+ "integrity": "sha1-WgTfBeT1f+Pw3Gj90R3FyXx+b00=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~1.0.6",
+ "safe-buffer": "~5.1.0",
+ "string_decoder": "~1.0.0",
+ "util-deprecate": "~1.0.1"
+ }
+ }
+ }
+ },
+ "named-placeholders": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/named-placeholders/download/named-placeholders-1.1.1.tgz",
+ "integrity": "sha1-O3oNJiA910s6nfTJz7gnsvuQfmQ=",
+ "requires": {
+ "lru-cache": "2.5.0"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "2.5.0",
+ "resolved": "http://registry.npm.taobao.org/lru-cache/download/lru-cache-2.5.0.tgz",
+ "integrity": "sha1-2COIrpyWC+y+oMc7uet5tsbOmus="
+ }
+ }
+ },
+ "negotiator": {
+ "version": "0.6.1",
+ "resolved": "http://registry.npm.taobao.org/negotiator/download/negotiator-0.6.1.tgz",
+ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
+ },
+ "nocache": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/nocache/download/nocache-2.0.0.tgz",
+ "integrity": "sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA="
+ },
+ "node_memcached": {
+ "version": "1.1.3",
+ "resolved": "http://registry.npm.taobao.org/node_memcached/download/node_memcached-1.1.3.tgz",
+ "integrity": "sha1-icFSr4itKIF/ANiRyZBFHV1xLqg=",
+ "requires": {
+ "debug": "^2.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "nodemailer": {
+ "version": "4.6.3",
+ "resolved": "http://registry.npm.taobao.org/nodemailer/download/nodemailer-4.6.3.tgz",
+ "integrity": "sha1-w7fpf7cvRtSkdcQGoV7SOkXbzdw="
+ },
+ "nopt": {
+ "version": "3.0.6",
+ "resolved": "http://registry.npm.taobao.org/nopt/download/nopt-3.0.6.tgz",
+ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+ "requires": {
+ "abbrev": "1"
+ }
+ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "http://registry.npm.taobao.org/npm-run-path/download/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/number-is-nan/download/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+ },
+ "oauth-sign": {
+ "version": "0.8.2",
+ "resolved": "http://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.8.2.tgz",
+ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "http://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "on-headers": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/on-headers/download/on-headers-1.0.1.tgz",
+ "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c="
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "http://registry.npm.taobao.org/once/download/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "optimist": {
+ "version": "0.6.1",
+ "resolved": "http://registry.npm.taobao.org/optimist/download/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "dev": true,
+ "requires": {
+ "minimist": "~0.0.1",
+ "wordwrap": "~0.0.2"
+ },
+ "dependencies": {
+ "wordwrap": {
+ "version": "0.0.3",
+ "resolved": "http://registry.npm.taobao.org/wordwrap/download/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+ "dev": true
+ }
+ }
+ },
+ "optionator": {
+ "version": "0.8.2",
+ "resolved": "http://registry.npm.taobao.org/optionator/download/optionator-0.8.2.tgz",
+ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+ "dev": true,
+ "requires": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.4",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "wordwrap": "~1.0.0"
+ }
+ },
+ "optjs": {
+ "version": "3.2.2",
+ "resolved": "http://registry.npm.taobao.org/optjs/download/optjs-3.2.2.tgz",
+ "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4="
+ },
+ "os-locale": {
+ "version": "1.4.0",
+ "resolved": "http://registry.npm.taobao.org/os-locale/download/os-locale-1.4.0.tgz",
+ "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+ "requires": {
+ "lcid": "^1.0.0"
+ }
+ },
+ "os-name": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/os-name/download/os-name-1.0.3.tgz",
+ "integrity": "sha1-GzefZINa98Wn9JizV8uVIVwVnt8=",
+ "requires": {
+ "osx-release": "^1.0.0",
+ "win-release": "^1.0.0"
+ }
+ },
+ "osx-release": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/osx-release/download/osx-release-1.1.0.tgz",
+ "integrity": "sha1-8heRGigTaUmvG/kwiyQeJzfTzWw=",
+ "requires": {
+ "minimist": "^1.1.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ }
+ }
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+ },
+ "p-limit": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/p-limit/download/p-limit-2.0.0.tgz",
+ "integrity": "sha1-5iTtVO6MRgp3izyfNnBJb/ileuw=",
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/p-locate/download/p-locate-3.0.0.tgz",
+ "integrity": "sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=",
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/p-try/download/p-try-2.0.0.tgz",
+ "integrity": "sha1-hQgLuHxkaI+keZb+j3376CEXYLE="
+ },
+ "parseurl": {
+ "version": "1.3.2",
+ "resolved": "http://registry.npm.taobao.org/parseurl/download/parseurl-1.3.2.tgz",
+ "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/path-exists/download/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "http://registry.npm.taobao.org/path-key/download/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+ },
+ "path-parse": {
+ "version": "1.0.5",
+ "resolved": "http://registry.npm.taobao.org/path-parse/download/path-parse-1.0.5.tgz",
+ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "http://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+ },
+ "pause-stream": {
+ "version": "0.0.11",
+ "resolved": "http://registry.npm.taobao.org/pause-stream/download/pause-stream-0.0.11.tgz",
+ "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
+ "requires": {
+ "through": "~2.3"
+ }
+ },
+ "pend": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/pend/download/pend-1.2.0.tgz",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
+ },
+ "platform": {
+ "version": "1.3.5",
+ "resolved": "http://registry.npm.taobao.org/platform/download/platform-1.3.5.tgz",
+ "integrity": "sha1-+2lYxpbgfikY0u7aDwvJRI1zNEQ="
+ },
+ "pomelo-protobuf": {
+ "version": "0.4.0",
+ "resolved": "http://registry.npm.taobao.org/pomelo-protobuf/download/pomelo-protobuf-0.4.0.tgz",
+ "integrity": "sha1-5F6aCkRusYZn4MbhPutT1Hrdvag="
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "http://registry.npm.taobao.org/prelude-ls/download/prelude-ls-1.1.2.tgz",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o="
+ },
+ "promise": {
+ "version": "7.3.1",
+ "resolved": "http://registry.npm.taobao.org/promise/download/promise-7.3.1.tgz",
+ "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=",
+ "requires": {
+ "asap": "~2.0.3"
+ }
+ },
+ "protobufjs": {
+ "version": "4.1.3",
+ "resolved": "http://registry.npm.taobao.org/protobufjs/download/protobufjs-4.1.3.tgz",
+ "integrity": "sha1-jjbRsCJsu2jWR+S0TCoUTzfyd54=",
+ "requires": {
+ "ascli": "~1",
+ "bytebuffer": "~4 >=4.1",
+ "glob": "^5.0.10",
+ "yargs": "^3.10.0"
+ },
+ "dependencies": {
+ "yargs": {
+ "version": "3.32.0",
+ "resolved": "http://registry.npm.taobao.org/yargs/download/yargs-3.32.0.tgz",
+ "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=",
+ "requires": {
+ "camelcase": "^2.0.1",
+ "cliui": "^3.0.3",
+ "decamelize": "^1.1.1",
+ "os-locale": "^1.4.0",
+ "string-width": "^1.0.1",
+ "window-size": "^0.1.4",
+ "y18n": "^3.2.0"
+ }
+ }
+ }
+ },
+ "proxy-addr": {
+ "version": "2.0.3",
+ "resolved": "http://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.3.tgz",
+ "integrity": "sha1-NV8mJQWmIWRrMTCnKOtkfiIFU0E=",
+ "requires": {
+ "forwarded": "~0.1.2",
+ "ipaddr.js": "1.6.0"
+ }
+ },
+ "pseudomap": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
+ },
+ "pug": {
+ "version": "2.0.1",
+ "resolved": "http://registry.npm.taobao.org/pug/download/pug-2.0.1.tgz",
+ "integrity": "sha1-J8FRYStT1ymr6OgoWqxryJNFtdA=",
+ "requires": {
+ "pug-code-gen": "^2.0.1",
+ "pug-filters": "^3.0.1",
+ "pug-lexer": "^4.0.0",
+ "pug-linker": "^3.0.5",
+ "pug-load": "^2.0.11",
+ "pug-parser": "^5.0.0",
+ "pug-runtime": "^2.0.4",
+ "pug-strip-comments": "^1.0.3"
+ }
+ },
+ "pug-attrs": {
+ "version": "2.0.3",
+ "resolved": "http://registry.npm.taobao.org/pug-attrs/download/pug-attrs-2.0.3.tgz",
+ "integrity": "sha1-owlflw5kFR972tlX7vVftdeQXRU=",
+ "requires": {
+ "constantinople": "^3.0.1",
+ "js-stringify": "^1.0.1",
+ "pug-runtime": "^2.0.4"
+ }
+ },
+ "pug-code-gen": {
+ "version": "2.0.1",
+ "resolved": "http://registry.npm.taobao.org/pug-code-gen/download/pug-code-gen-2.0.1.tgz",
+ "integrity": "sha1-CVHsgyJddNjPxHan+Zolm199BQw=",
+ "requires": {
+ "constantinople": "^3.0.1",
+ "doctypes": "^1.1.0",
+ "js-stringify": "^1.0.1",
+ "pug-attrs": "^2.0.3",
+ "pug-error": "^1.3.2",
+ "pug-runtime": "^2.0.4",
+ "void-elements": "^2.0.1",
+ "with": "^5.0.0"
+ }
+ },
+ "pug-error": {
+ "version": "1.3.2",
+ "resolved": "http://registry.npm.taobao.org/pug-error/download/pug-error-1.3.2.tgz",
+ "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY="
+ },
+ "pug-filters": {
+ "version": "3.0.1",
+ "resolved": "http://registry.npm.taobao.org/pug-filters/download/pug-filters-3.0.1.tgz",
+ "integrity": "sha1-Fj73O/ux8VRNAysrQPRRMOtS3Ms=",
+ "requires": {
+ "clean-css": "^3.3.0",
+ "constantinople": "^3.0.1",
+ "jstransformer": "1.0.0",
+ "pug-error": "^1.3.2",
+ "pug-walk": "^1.1.7",
+ "resolve": "^1.1.6",
+ "uglify-js": "^2.6.1"
+ }
+ },
+ "pug-lexer": {
+ "version": "4.0.0",
+ "resolved": "http://registry.npm.taobao.org/pug-lexer/download/pug-lexer-4.0.0.tgz",
+ "integrity": "sha1-IQwYRX7y4XYCQnQMXmR715TOwng=",
+ "requires": {
+ "character-parser": "^2.1.1",
+ "is-expression": "^3.0.0",
+ "pug-error": "^1.3.2"
+ }
+ },
+ "pug-linker": {
+ "version": "3.0.5",
+ "resolved": "http://registry.npm.taobao.org/pug-linker/download/pug-linker-3.0.5.tgz",
+ "integrity": "sha1-npp65ABWgtAn3uuWsAD4juuDoC8=",
+ "requires": {
+ "pug-error": "^1.3.2",
+ "pug-walk": "^1.1.7"
+ }
+ },
+ "pug-load": {
+ "version": "2.0.11",
+ "resolved": "http://registry.npm.taobao.org/pug-load/download/pug-load-2.0.11.tgz",
+ "integrity": "sha1-5kjlftET/iwfRdV4WOorrWvAFSc=",
+ "requires": {
+ "object-assign": "^4.1.0",
+ "pug-walk": "^1.1.7"
+ }
+ },
+ "pug-parser": {
+ "version": "5.0.0",
+ "resolved": "http://registry.npm.taobao.org/pug-parser/download/pug-parser-5.0.0.tgz",
+ "integrity": "sha1-45Stmz/KkxI5QK/4hcBuRKt+aOQ=",
+ "requires": {
+ "pug-error": "^1.3.2",
+ "token-stream": "0.0.1"
+ }
+ },
+ "pug-runtime": {
+ "version": "2.0.4",
+ "resolved": "http://registry.npm.taobao.org/pug-runtime/download/pug-runtime-2.0.4.tgz",
+ "integrity": "sha1-4XjhvaaKsujArPybztLFT9iM61g="
+ },
+ "pug-strip-comments": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/pug-strip-comments/download/pug-strip-comments-1.0.3.tgz",
+ "integrity": "sha1-8VWVkiBu3G+FMQ2s9K+0igJa9Z8=",
+ "requires": {
+ "pug-error": "^1.3.2"
+ }
+ },
+ "pug-walk": {
+ "version": "1.1.7",
+ "resolved": "http://registry.npm.taobao.org/pug-walk/download/pug-walk-1.1.7.tgz",
+ "integrity": "sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM="
+ },
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "http://registry.npm.taobao.org/punycode/download/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+ },
+ "qcloudapi-sdk": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/qcloudapi-sdk/-/qcloudapi-sdk-0.2.0.tgz",
+ "integrity": "sha512-Hy6a+95zDheaeMbA/N/7AEuAoOhwjR1POPI9xCwfsPc3b24ZzyH1M4PKgO9sq7Rq62+14ZTessHMdXJ+6NaeQg==",
+ "requires": {
+ "dot-qs": "^0.2.0",
+ "object-assign": "^3.0.0",
+ "request": ">= 2.33.0"
+ },
+ "dependencies": {
+ "object-assign": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+ "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="
+ }
+ }
+ },
+ "qiniu": {
+ "version": "7.1.3",
+ "resolved": "http://registry.npm.taobao.org/qiniu/download/qiniu-7.1.3.tgz",
+ "integrity": "sha1-Zk2FDSvmndSMEldV2O1XTUh/kzI=",
+ "requires": {
+ "agentkeepalive": "3.3.0",
+ "crc32": "0.2.2",
+ "encodeurl": "^1.0.1",
+ "formstream": "1.1.0",
+ "mime": "1.3.6",
+ "tunnel-agent": "0.6.0",
+ "urllib": "2.22.0"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "1.3.6",
+ "resolved": "http://registry.npm.taobao.org/mime/download/mime-1.3.6.tgz",
+ "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA="
+ }
+ }
+ },
+ "qs": {
+ "version": "6.5.1",
+ "resolved": "http://registry.npm.taobao.org/qs/download/qs-6.5.1.tgz",
+ "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg="
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "http://registry.npm.taobao.org/querystring/download/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
+ },
+ "rand-token": {
+ "version": "0.4.0",
+ "resolved": "http://registry.npm.taobao.org/rand-token/download/rand-token-0.4.0.tgz",
+ "integrity": "sha1-GlZbatEtkt1LMMTE5ZReiqJKWzs="
+ },
+ "range-parser": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/range-parser/download/range-parser-1.2.0.tgz",
+ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
+ },
+ "raw-body": {
+ "version": "2.3.2",
+ "resolved": "http://registry.npm.taobao.org/raw-body/download/raw-body-2.3.2.tgz",
+ "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
+ "requires": {
+ "bytes": "3.0.0",
+ "http-errors": "1.6.2",
+ "iconv-lite": "0.4.19",
+ "unpipe": "1.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.5",
+ "resolved": "http://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.5.tgz",
+ "integrity": "sha1-tPhQA6k4y7bsvOKhJPsQEr0ag40=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.0.3",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "recursive-readdir": {
+ "version": "2.2.2",
+ "resolved": "http://registry.npm.taobao.org/recursive-readdir/download/recursive-readdir-2.2.2.tgz",
+ "integrity": "sha1-mUb7MnThYo3m42svZxSVO0hFCU8=",
+ "requires": {
+ "minimatch": "3.0.4"
+ }
+ },
+ "redeyed": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/redeyed/download/redeyed-1.0.1.tgz",
+ "integrity": "sha1-6WwZO0DAgWsArshCaY5hGF5VSYo=",
+ "requires": {
+ "esprima": "~3.0.0"
+ },
+ "dependencies": {
+ "esprima": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/esprima/download/esprima-3.0.0.tgz",
+ "integrity": "sha1-U88kes2ncxPlUcOqLnM0LT+099k="
+ }
+ }
+ },
+ "redis": {
+ "version": "2.8.0",
+ "resolved": "http://registry.npm.taobao.org/redis/download/redis-2.8.0.tgz",
+ "integrity": "sha1-ICKI4/WMSfYHnZevehDhMDrhSwI=",
+ "requires": {
+ "double-ended-queue": "^2.1.0-0",
+ "redis-commands": "^1.2.0",
+ "redis-parser": "^2.6.0"
+ }
+ },
+ "redis-commands": {
+ "version": "1.3.5",
+ "resolved": "http://registry.npm.taobao.org/redis-commands/download/redis-commands-1.3.5.tgz",
+ "integrity": "sha1-RJWIlBTx6IYmEYCxRC5ylWAtg6I="
+ },
+ "redis-parser": {
+ "version": "2.6.0",
+ "resolved": "http://registry.npm.taobao.org/redis-parser/download/redis-parser-2.6.0.tgz",
+ "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs="
+ },
+ "referrer-policy": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/referrer-policy/download/referrer-policy-1.1.0.tgz",
+ "integrity": "sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk="
+ },
+ "regenerator-runtime": {
+ "version": "0.11.1",
+ "resolved": "http://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk="
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "http://registry.npm.taobao.org/repeat-string/download/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
+ },
+ "request": {
+ "version": "2.85.0",
+ "resolved": "http://registry.npm.taobao.org/request/download/request-2.85.0.tgz",
+ "integrity": "sha1-WgNhWkfGFCCz65m326IE+DYD4fo=",
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.6.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.5",
+ "extend": "~3.0.1",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.1",
+ "har-validator": "~5.0.3",
+ "hawk": "~6.0.2",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.17",
+ "oauth-sign": "~0.8.2",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.1",
+ "safe-buffer": "^5.1.1",
+ "stringstream": "~0.0.5",
+ "tough-cookie": "~2.3.3",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.1.0"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "http://registry.npm.taobao.org/require-directory/download/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/require-main-filename/download/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
+ },
+ "resolve": {
+ "version": "1.5.0",
+ "resolved": "http://registry.npm.taobao.org/resolve/download/resolve-1.5.0.tgz",
+ "integrity": "sha1-HwmsznlsmnYlefMbLBzEw83fnzY=",
+ "requires": {
+ "path-parse": "^1.0.5"
+ }
+ },
+ "retry-as-promised": {
+ "version": "2.3.2",
+ "resolved": "http://registry.npm.taobao.org/retry-as-promised/download/retry-as-promised-2.3.2.tgz",
+ "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=",
+ "requires": {
+ "bluebird": "^3.4.6",
+ "debug": "^2.6.9"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "rfdc": {
+ "version": "1.1.2",
+ "resolved": "http://registry.npm.taobao.org/rfdc/download/rfdc-1.1.2.tgz",
+ "integrity": "sha1-5uctdPXcOd6PU49l4Aw2wYAY40k="
+ },
+ "right-align": {
+ "version": "0.1.3",
+ "resolved": "http://registry.npm.taobao.org/right-align/download/right-align-0.1.3.tgz",
+ "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
+ "requires": {
+ "align-text": "^0.1.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "resolved": "http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.1.tgz",
+ "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM="
+ },
+ "safefs": {
+ "version": "3.2.2",
+ "resolved": "http://registry.npm.taobao.org/safefs/download/safefs-3.2.2.tgz",
+ "integrity": "sha1-gXDBRE1wOOCMrqBaN0+uL6NJ4Vw=",
+ "requires": {
+ "graceful-fs": "*"
+ }
+ },
+ "sax": {
+ "version": "0.6.1",
+ "resolved": "http://registry.npm.taobao.org/sax/download/sax-0.6.1.tgz",
+ "integrity": "sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk="
+ },
+ "scandirectory": {
+ "version": "2.5.0",
+ "resolved": "http://registry.npm.taobao.org/scandirectory/download/scandirectory-2.5.0.tgz",
+ "integrity": "sha1-bOA/VKCQtmjjy+2/IO354xBZPnI=",
+ "requires": {
+ "ignorefs": "^1.0.0",
+ "safefs": "^3.1.2",
+ "taskgroup": "^4.0.5"
+ }
+ },
+ "semver": {
+ "version": "5.5.0",
+ "resolved": "http://registry.npm.taobao.org/semver/download/semver-5.5.0.tgz",
+ "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs="
+ },
+ "send": {
+ "version": "0.16.2",
+ "resolved": "http://registry.npm.taobao.org/send/download/send-0.16.2.tgz",
+ "integrity": "sha1-bsyh4PjBVtFBWXVZhI32RzCmu8E=",
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "destroy": "~1.0.4",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "~1.6.2",
+ "mime": "1.4.1",
+ "ms": "2.0.0",
+ "on-finished": "~2.3.0",
+ "range-parser": "~1.2.0",
+ "statuses": "~1.4.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "mime": {
+ "version": "1.4.1",
+ "resolved": "http://registry.npm.taobao.org/mime/download/mime-1.4.1.tgz",
+ "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY="
+ }
+ }
+ },
+ "seq-queue": {
+ "version": "0.0.5",
+ "resolved": "http://registry.npm.taobao.org/seq-queue/download/seq-queue-0.0.5.tgz",
+ "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4="
+ },
+ "sequelize": {
+ "version": "4.37.1",
+ "resolved": "http://registry.npm.taobao.org/sequelize/download/sequelize-4.37.1.tgz",
+ "integrity": "sha1-F6qX8mm3YhAVxz53qmshNPRdp9c=",
+ "requires": {
+ "bluebird": "^3.5.0",
+ "cls-bluebird": "^2.1.0",
+ "debug": "^3.1.0",
+ "depd": "^1.1.0",
+ "dottie": "^2.0.0",
+ "generic-pool": "^3.4.0",
+ "inflection": "1.12.0",
+ "lodash": "^4.17.1",
+ "moment": "^2.20.0",
+ "moment-timezone": "^0.5.14",
+ "retry-as-promised": "^2.3.2",
+ "semver": "^5.5.0",
+ "terraformer-wkt-parser": "^1.1.2",
+ "toposort-class": "^1.0.1",
+ "uuid": "^3.2.1",
+ "validator": "^9.4.1",
+ "wkx": "^0.4.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz",
+ "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "inflection": {
+ "version": "1.12.0",
+ "resolved": "http://registry.npm.taobao.org/inflection/download/inflection-1.12.0.tgz",
+ "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY="
+ },
+ "uuid": {
+ "version": "3.2.1",
+ "resolved": "http://registry.npm.taobao.org/uuid/download/uuid-3.2.1.tgz",
+ "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ="
+ },
+ "validator": {
+ "version": "9.4.1",
+ "resolved": "http://registry.npm.taobao.org/validator/download/validator-9.4.1.tgz",
+ "integrity": "sha1-q/Rm05i1Yc0kMFARLG/x3mzBJmM="
+ }
+ }
+ },
+ "serve-favicon": {
+ "version": "2.4.5",
+ "resolved": "http://registry.npm.taobao.org/serve-favicon/download/serve-favicon-2.4.5.tgz",
+ "integrity": "sha1-SdmkaGMVOpJAaRyJPSsOfYXW1DY=",
+ "requires": {
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "ms": "2.0.0",
+ "parseurl": "~1.3.2",
+ "safe-buffer": "5.1.1"
+ }
+ },
+ "serve-static": {
+ "version": "1.13.2",
+ "resolved": "http://registry.npm.taobao.org/serve-static/download/serve-static-1.13.2.tgz",
+ "integrity": "sha1-CV6Ecv1bRiN9tQzkhqQ/S4bGzsE=",
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.2",
+ "send": "0.16.2"
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/set-blocking/download/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+ },
+ "setprototypeof": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.0.3.tgz",
+ "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/shebang-command/download/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/shebang-regex/download/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+ },
+ "shimmer": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npm.taobao.org/shimmer/download/shimmer-1.2.0.tgz",
+ "integrity": "sha1-+Wb3VVeJdj502IQRk2haXnhzZmU="
+ },
+ "should": {
+ "version": "11.2.1",
+ "resolved": "http://registry.npm.taobao.org/should/download/should-11.2.1.tgz",
+ "integrity": "sha1-kPVRRVUtAc/CAGZuToGKHJZw7aI=",
+ "dev": true,
+ "requires": {
+ "should-equal": "^1.0.0",
+ "should-format": "^3.0.2",
+ "should-type": "^1.4.0",
+ "should-type-adaptors": "^1.0.1",
+ "should-util": "^1.0.0"
+ }
+ },
+ "should-equal": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/should-equal/download/should-equal-1.0.1.tgz",
+ "integrity": "sha1-C26VFvJgGp+wuy3MNpr6HH4gCvc=",
+ "dev": true,
+ "requires": {
+ "should-type": "^1.0.0"
+ }
+ },
+ "should-format": {
+ "version": "3.0.3",
+ "resolved": "http://registry.npm.taobao.org/should-format/download/should-format-3.0.3.tgz",
+ "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
+ "dev": true,
+ "requires": {
+ "should-type": "^1.3.0",
+ "should-type-adaptors": "^1.0.1"
+ }
+ },
+ "should-type": {
+ "version": "1.4.0",
+ "resolved": "http://registry.npm.taobao.org/should-type/download/should-type-1.4.0.tgz",
+ "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
+ "dev": true
+ },
+ "should-type-adaptors": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/should-type-adaptors/download/should-type-adaptors-1.1.0.tgz",
+ "integrity": "sha1-QB5/M7VTMDOUTVzYvytlAneS4no=",
+ "dev": true,
+ "requires": {
+ "should-type": "^1.3.0",
+ "should-util": "^1.0.0"
+ }
+ },
+ "should-util": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/should-util/download/should-util-1.0.0.tgz",
+ "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "http://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/slash/download/slash-2.0.0.tgz",
+ "integrity": "sha1-3lUoUaF1nfOo8gZTVEL17E3eq0Q="
+ },
+ "sntp": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/sntp/download/sntp-2.1.0.tgz",
+ "integrity": "sha1-LGzsFP7cIiJznK+bXD2F0cxaLMg=",
+ "requires": {
+ "hoek": "4.x.x"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.1.1.tgz",
+ "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw="
+ },
+ "sqlstring": {
+ "version": "2.3.1",
+ "resolved": "http://registry.npm.taobao.org/sqlstring/download/sqlstring-2.3.1.tgz",
+ "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
+ },
+ "sshpk": {
+ "version": "1.14.1",
+ "resolved": "http://registry.npm.taobao.org/sshpk/download/sshpk-1.14.1.tgz",
+ "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=",
+ "requires": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "tweetnacl": "~0.14.0"
+ }
+ },
+ "statuses": {
+ "version": "1.4.0",
+ "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.4.0.tgz",
+ "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic="
+ },
+ "streamroller": {
+ "version": "0.7.0",
+ "resolved": "http://registry.npm.taobao.org/streamroller/download/streamroller-0.7.0.tgz",
+ "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=",
+ "requires": {
+ "date-format": "^1.2.0",
+ "debug": "^3.1.0",
+ "mkdirp": "^0.5.1",
+ "readable-stream": "^2.3.0"
+ },
+ "dependencies": {
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "http://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ }
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.0.3.tgz",
+ "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "stringstream": {
+ "version": "0.0.5",
+ "resolved": "http://registry.npm.taobao.org/stringstream/download/stringstream-0.0.5.tgz",
+ "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/strip-eof/download/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
+ },
+ "superagent": {
+ "version": "3.8.2",
+ "resolved": "http://registry.npm.taobao.org/superagent/download/superagent-3.8.2.tgz",
+ "integrity": "sha1-5KEbnQR/fT7+s7vlNtnsACHRZAM=",
+ "dev": true,
+ "requires": {
+ "component-emitter": "^1.2.0",
+ "cookiejar": "^2.1.0",
+ "debug": "^3.1.0",
+ "extend": "^3.0.0",
+ "form-data": "^2.3.1",
+ "formidable": "^1.1.1",
+ "methods": "^1.1.1",
+ "mime": "^1.4.1",
+ "qs": "^6.5.1",
+ "readable-stream": "^2.0.5"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz",
+ "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "http://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz",
+ "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=",
+ "dev": true
+ }
+ }
+ },
+ "supertest": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/supertest/download/supertest-3.0.0.tgz",
+ "integrity": "sha1-jUu2j9GDDuBwM7HFpamkAhyWUpY=",
+ "dev": true,
+ "requires": {
+ "methods": "~1.1.2",
+ "superagent": "^3.0.0"
+ }
+ },
+ "supervisor": {
+ "version": "0.12.0",
+ "resolved": "http://registry.npm.taobao.org/supervisor/download/supervisor-0.12.0.tgz",
+ "integrity": "sha1-3n5jNwFbKRhRwQ81OMSn8EkX7ME=",
+ "dev": true
+ },
+ "taskgroup": {
+ "version": "4.3.1",
+ "resolved": "http://registry.npm.taobao.org/taskgroup/download/taskgroup-4.3.1.tgz",
+ "integrity": "sha1-feGT/r12gnPEV3MElwJNUSwnkVo=",
+ "requires": {
+ "ambi": "^2.2.0",
+ "csextends": "^1.0.3"
+ }
+ },
+ "terraformer": {
+ "version": "1.0.8",
+ "resolved": "http://registry.npm.taobao.org/terraformer/download/terraformer-1.0.8.tgz",
+ "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=",
+ "requires": {
+ "@types/geojson": "^1.0.0"
+ }
+ },
+ "terraformer-wkt-parser": {
+ "version": "1.1.2",
+ "resolved": "http://registry.npm.taobao.org/terraformer-wkt-parser/download/terraformer-wkt-parser-1.1.2.tgz",
+ "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=",
+ "requires": {
+ "terraformer": "~1.0.5"
+ }
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "http://registry.npm.taobao.org/through/download/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+ },
+ "to-fast-properties": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-1.0.3.tgz",
+ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
+ },
+ "token-stream": {
+ "version": "0.0.1",
+ "resolved": "http://registry.npm.taobao.org/token-stream/download/token-stream-0.0.1.tgz",
+ "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo="
+ },
+ "toposort-class": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/toposort-class/download/toposort-class-1.0.1.tgz",
+ "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg="
+ },
+ "tough-cookie": {
+ "version": "2.3.4",
+ "resolved": "http://registry.npm.taobao.org/tough-cookie/download/tough-cookie-2.3.4.tgz",
+ "integrity": "sha1-7GDO44rGdQY//JelwYlwV47oNlU=",
+ "requires": {
+ "punycode": "^1.4.1"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "http://registry.npm.taobao.org/punycode/download/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+ }
+ }
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "http://registry.npm.taobao.org/tunnel-agent/download/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "http://registry.npm.taobao.org/tweetnacl/download/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "optional": true
+ },
+ "type-check": {
+ "version": "0.3.2",
+ "resolved": "http://registry.npm.taobao.org/type-check/download/type-check-0.3.2.tgz",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2"
+ }
+ },
+ "type-is": {
+ "version": "1.6.16",
+ "resolved": "http://registry.npm.taobao.org/type-is/download/type-is-1.6.16.tgz",
+ "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=",
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.18"
+ }
+ },
+ "typechecker": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/typechecker/download/typechecker-2.1.0.tgz",
+ "integrity": "sha1-0cIJOlT/ihn1jP+HfuqlTyJC04M="
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "http://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+ },
+ "uc.micro": {
+ "version": "1.0.5",
+ "resolved": "http://registry.npm.taobao.org/uc.micro/download/uc.micro-1.0.5.tgz",
+ "integrity": "sha1-DGXxX4FaoItWCmHOi023/8P0U3Y="
+ },
+ "uglify-js": {
+ "version": "2.8.29",
+ "resolved": "http://registry.npm.taobao.org/uglify-js/download/uglify-js-2.8.29.tgz",
+ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
+ "requires": {
+ "source-map": "~0.5.1",
+ "uglify-to-browserify": "~1.0.0",
+ "yargs": "~3.10.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "1.2.1",
+ "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-1.2.1.tgz",
+ "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/cliui/download/cliui-2.1.0.tgz",
+ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+ "requires": {
+ "center-align": "^0.1.1",
+ "right-align": "^0.1.1",
+ "wordwrap": "0.0.2"
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ },
+ "window-size": {
+ "version": "0.1.0",
+ "resolved": "http://registry.npm.taobao.org/window-size/download/window-size-0.1.0.tgz",
+ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0="
+ },
+ "wordwrap": {
+ "version": "0.0.2",
+ "resolved": "http://registry.npm.taobao.org/wordwrap/download/wordwrap-0.0.2.tgz",
+ "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8="
+ },
+ "yargs": {
+ "version": "3.10.0",
+ "resolved": "http://registry.npm.taobao.org/yargs/download/yargs-3.10.0.tgz",
+ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
+ "requires": {
+ "camelcase": "^1.0.2",
+ "cliui": "^2.1.0",
+ "decamelize": "^1.0.0",
+ "window-size": "0.1.0"
+ }
+ }
+ }
+ },
+ "uglify-to-browserify": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/uglify-to-browserify/download/uglify-to-browserify-1.0.2.tgz",
+ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
+ "optional": true
+ },
+ "unique-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
+ "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
+ "requires": {
+ "crypto-random-string": "^1.0.0"
+ }
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "http://registry.npm.taobao.org/universalify/download/universalify-0.1.2.tgz",
+ "integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY="
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ },
+ "upyun": {
+ "version": "3.3.9",
+ "resolved": "https://registry.npmjs.org/upyun/-/upyun-3.3.9.tgz",
+ "integrity": "sha512-UqvSKvvFgbQwLI+yjTO0WUnmS2i2KIvX4N2Je6ZTRN4cja17DHX7ARd13DO6h65/JOQJPs/Nua2zlnixNFIdWA==",
+ "requires": {
+ "axios": "^0.18.0",
+ "base-64": "^0.1.0",
+ "form-data": "^2.1.4",
+ "hmacsha1": "^1.0.0",
+ "is-promise": "^2.1.0",
+ "md5": "^2.2.1",
+ "mime-types": "^2.1.15"
+ }
+ },
+ "url": {
+ "version": "0.10.3",
+ "resolved": "http://registry.npm.taobao.org/url/download/url-0.10.3.tgz",
+ "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ }
+ },
+ "urllib": {
+ "version": "2.22.0",
+ "resolved": "http://registry.npm.taobao.org/urllib/download/urllib-2.22.0.tgz",
+ "integrity": "sha1-KWXcSuEnpvtpW32yfTGE8X2Cy0I=",
+ "requires": {
+ "any-promise": "^1.3.0",
+ "content-type": "^1.0.2",
+ "debug": "^2.6.0",
+ "default-user-agent": "^1.0.0",
+ "digest-header": "^0.0.1",
+ "ee-first": "~1.1.1",
+ "humanize-ms": "^1.2.0",
+ "iconv-lite": "^0.4.15",
+ "qs": "^6.4.0",
+ "statuses": "^1.3.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "utility": {
+ "version": "0.1.11",
+ "resolved": "http://registry.npm.taobao.org/utility/download/utility-0.1.11.tgz",
+ "integrity": "sha1-/eYM+bTkdRlHoM9dEEzik2ciZxU=",
+ "requires": {
+ "address": ">=0.0.1"
+ }
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+ },
+ "uuid": {
+ "version": "3.1.0",
+ "resolved": "http://registry.npm.taobao.org/uuid/download/uuid-3.1.0.tgz",
+ "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ="
+ },
+ "validator": {
+ "version": "10.6.0",
+ "resolved": "http://registry.npm.taobao.org/validator/download/validator-10.6.0.tgz",
+ "integrity": "sha1-qb3OaFs8PoSA5+u7nrlcVM2XM7A="
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "http://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "http://registry.npm.taobao.org/verror/download/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "void-elements": {
+ "version": "2.0.1",
+ "resolved": "http://registry.npm.taobao.org/void-elements/download/void-elements-2.0.1.tgz",
+ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
+ },
+ "watchr": {
+ "version": "2.4.13",
+ "resolved": "http://registry.npm.taobao.org/watchr/download/watchr-2.4.13.tgz",
+ "integrity": "sha1-10hHu01vkPYf4sdPn2hmKqDgdgE=",
+ "requires": {
+ "eachr": "^2.0.2",
+ "extendr": "^2.1.0",
+ "extract-opts": "^2.2.0",
+ "ignorefs": "^1.0.0",
+ "safefs": "^3.1.2",
+ "scandirectory": "^2.5.0",
+ "taskgroup": "^4.2.0",
+ "typechecker": "^2.0.8"
+ }
+ },
+ "which": {
+ "version": "1.3.0",
+ "resolved": "http://registry.npm.taobao.org/which/download/which-1.3.0.tgz",
+ "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=",
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/which-module/download/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
+ },
+ "win-release": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npm.taobao.org/win-release/download/win-release-1.1.1.tgz",
+ "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=",
+ "requires": {
+ "semver": "^5.0.1"
+ }
+ },
+ "window-size": {
+ "version": "0.1.4",
+ "resolved": "http://registry.npm.taobao.org/window-size/download/window-size-0.1.4.tgz",
+ "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY="
+ },
+ "with": {
+ "version": "5.1.1",
+ "resolved": "http://registry.npm.taobao.org/with/download/with-5.1.1.tgz",
+ "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=",
+ "requires": {
+ "acorn": "^3.1.0",
+ "acorn-globals": "^3.0.0"
+ }
+ },
+ "wkx": {
+ "version": "0.4.4",
+ "resolved": "http://registry.npm.taobao.org/wkx/download/wkx-0.4.4.tgz",
+ "integrity": "sha1-z3UbZy5LReFi+f0wEkh45z2WybI=",
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "wordwrap": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npm.taobao.org/wordwrap/download/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npm.taobao.org/wrappy/download/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "write-file-atomic": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz",
+ "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==",
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "x-xss-protection": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npm.taobao.org/x-xss-protection/download/x-xss-protection-1.1.0.tgz",
+ "integrity": "sha1-TxiYwzLesefyvhKA77PixT1pwac="
+ },
+ "xdg-basedir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
+ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ="
+ },
+ "xml2js": {
+ "version": "0.4.4",
+ "resolved": "http://registry.npm.taobao.org/xml2js/download/xml2js-0.4.4.tgz",
+ "integrity": "sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0=",
+ "requires": {
+ "sax": "0.6.x",
+ "xmlbuilder": ">=1.0.0"
+ }
+ },
+ "xmlbuilder": {
+ "version": "2.6.5",
+ "resolved": "http://registry.npm.taobao.org/xmlbuilder/download/xmlbuilder-2.6.5.tgz",
+ "integrity": "sha1-b/etYPty0idk8AehZLd/K/FABSY=",
+ "requires": {
+ "lodash": "^3.5.0"
+ },
+ "dependencies": {
+ "lodash": {
+ "version": "3.10.1",
+ "resolved": "http://registry.npm.taobao.org/lodash/download/lodash-3.10.1.tgz",
+ "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
+ }
+ }
+ },
+ "xregexp": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/xregexp/download/xregexp-2.0.0.tgz",
+ "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "resolved": "http://registry.npm.taobao.org/xtend/download/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
+ },
+ "y18n": {
+ "version": "3.2.1",
+ "resolved": "http://registry.npm.taobao.org/y18n/download/y18n-3.2.1.tgz",
+ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
+ },
+ "yallist": {
+ "version": "2.1.2",
+ "resolved": "http://registry.npm.taobao.org/yallist/download/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
+ },
+ "yargs": {
+ "version": "12.0.1",
+ "resolved": "http://registry.npm.taobao.org/yargs/download/yargs-12.0.1.tgz",
+ "integrity": "sha1-ZDLlYSO7Tnw1YhFUAemDdAYCYcI=",
+ "requires": {
+ "cliui": "^4.0.0",
+ "decamelize": "^2.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^2.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^10.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+ },
+ "cliui": {
+ "version": "4.1.0",
+ "resolved": "http://registry.npm.taobao.org/cliui/download/cliui-4.1.0.tgz",
+ "integrity": "sha1-NIQi2+gtgAswIu709qwQvy5NG0k=",
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/decamelize/download/decamelize-2.0.0.tgz",
+ "integrity": "sha1-ZW17vICUxMeI6lPFhAkIycfQY8c=",
+ "requires": {
+ "xregexp": "4.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+ },
+ "os-locale": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npm.taobao.org/os-locale/download/os-locale-2.1.0.tgz",
+ "integrity": "sha1-QrwpAKa1uL0XN2yOiCtlr8zyS/I=",
+ "requires": {
+ "execa": "^0.7.0",
+ "lcid": "^1.0.0",
+ "mem": "^1.1.0"
+ }
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-2.1.1.tgz",
+ "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
+ "xregexp": {
+ "version": "4.0.0",
+ "resolved": "http://registry.npm.taobao.org/xregexp/download/xregexp-4.0.0.tgz",
+ "integrity": "sha1-5pgYneSd0qGMxWh7BeF8jkOUMCA="
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "10.1.0",
+ "resolved": "http://registry.npm.taobao.org/yargs-parser/download/yargs-parser-10.1.0.tgz",
+ "integrity": "sha1-cgImW4n36eny5XZeD+c1qQXtuqg=",
+ "requires": {
+ "camelcase": "^4.1.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "4.1.0",
+ "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-4.1.0.tgz",
+ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
+ }
+ }
+ },
+ "yauzl": {
+ "version": "2.4.1",
+ "resolved": "http://registry.npm.taobao.org/yauzl/download/yauzl-2.4.1.tgz",
+ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
+ "requires": {
+ "fd-slicer": "~1.0.1"
+ }
+ },
+ "yazl": {
+ "version": "2.4.3",
+ "resolved": "http://registry.npm.taobao.org/yazl/download/yazl-2.4.3.tgz",
+ "integrity": "sha1-7CblzIfVYBud+EMtvdPNLlFzoHE=",
+ "requires": {
+ "buffer-crc32": "~0.2.3"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 68ec8f2e..1e3fc19a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "code-push-server",
"description": "CodePush service is hotupdate services which adapter react-native-code-push and cordova-plugin-code-push",
- "version": "0.2.14",
+ "version": "0.5.4",
"license": "MIT",
"repository": {
"type": "git",
@@ -25,60 +25,71 @@
"code-push-server-db": "./bin/db"
},
"engines": {
- "node": ">= 5.0",
+ "node": ">= 6.0",
"npm": ">= 3.10.8"
},
"scripts": {
+ "dev": "supervisor ./bin/www",
"start": "node ./bin/www",
+ "init": "node ./bin/db init",
+ "upgrade": "node ./bin/db upgrade",
"test": "make test",
- "test-win": "mocha test/api/init test/api/users test/api/auth test/api/account test/api/accessKeys test/api/sessions test/api/apps test/api/index --recursive --timeout 15000",
+ "test-win": "mocha test/api/init test/api/users test/api/auth test/api/account test/api/accessKeys test/api/apps test/api/index --recursive --timeout 15000",
"coverage": "make coverage"
},
"dependencies": {
"aliyun-oss-upload-stream": "^1.3.0",
- "aliyun-sdk": "^1.9.17",
+ "aliyun-sdk": "^1.11.10",
"aws-sdk": "^2.7.0",
"bcryptjs": "^2.3.0",
"bluebird": "^3.4.1",
"body-parser": "^1.15.2",
"cookie-parser": "^1.4.3",
- "debug": "^2.3.3",
+ "cos-nodejs-sdk-v5": "^2.4.10",
+ "debug": "^3.1.0",
+ "diff-match-patch": "^1.0.1",
"express": "^4.14.0",
- "formidable": "^1.0.17",
- "fs-extra": "^1.0.0",
+ "extract-zip": "^1.6.0",
+ "formidable": "^1.2.1",
+ "fs-extra": "^7.0.0",
"helmet": "^3.1.0",
- "jsonwebtoken": "^7.1.7",
- "lodash": "^4.5.1",
+ "i18n": "^0.8.3",
+ "jschardet": "^1.6.0",
+ "jsonwebtoken": "^8.2.0",
+ "lodash": "^4.17.5",
+ "log4js": "^3.0.5",
"markdown-it": "^8.0.1",
"moment": "^2.14.1",
"morgan": "^1.7.0",
- "mysql": "^2.10.2",
- "node-unzip-2": "^0.2.1",
- "nodemailer": "^2.5.0",
- "pug": "^2.0.0-beta6",
- "qiniu": "^6.1.10",
- "rand-token": "^0.2.1",
- "recursive-fs": "^1.0.0",
+ "mysql2": "^1.3.5",
+ "nodemailer": "^4.0.1",
+ "pug": "^2.0.1",
+ "qiniu": "^7.1.3",
+ "upyun": "^3.3.9",
+ "rand-token": "^0.4.0",
+ "recursive-readdir": "^2.1.1",
"redis": "^2.6.2",
"request": "^2.72.0",
- "sequelize": "^3.24.5",
- "serve-favicon": "~2.3.0",
- "slash": "^1.0.0",
- "validator": "^6.1.0",
- "yargs": "^6.2.0",
+ "sequelize": "^4.37.1",
+ "serve-favicon": "^2.4.0",
+ "slash": "^2.0.0",
+ "validator": "^10.6.0",
+ "yargs": "^12.0.1",
"yazl": "^2.3.0"
},
"devDependencies": {
"istanbul": "^0.4.5",
- "mocha": "^3.0.2",
- "should": "^11.1.0",
- "supertest": "^2.0.0"
+ "mocha": "^3.2.0",
+ "should": "^11.2.0",
+ "supertest": "^3.0.0",
+ "supervisor": "^0.12.0"
},
"files": [
"bin",
"config",
"core",
"docs",
+ "docker",
"models",
"public",
"routes",
diff --git a/routes/accessKeys.js b/routes/accessKeys.js
index 66f9806c..b7239b41 100644
--- a/routes/accessKeys.js
+++ b/routes/accessKeys.js
@@ -6,111 +6,79 @@ var security = require('../core/utils/security');
var models = require('../models');
var middleware = require('../core/middleware');
var accountManager = require('../core/services/account-manager')();
+var AppError = require('../core/app-error')
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:accessKey");
-router.get('/', middleware.checkToken, function(req, res) {
+router.get('/', middleware.checkToken, (req, res, next) => {
+ log.debug('request get acceesKeys')
var uid = req.users.id;
accountManager.getAllAccessKeyByUid(uid)
- .then(function(accessKeys){
- res.send({accessKeys:accessKeys});
+ .then((accessKeys) => {
+ log.debug('acceesKeys:', accessKeys)
+ res.send({accessKeys: accessKeys});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ next(e);
});
});
-router.post('/', middleware.checkToken, function(req, res) {
+router.post('/', middleware.checkToken, (req, res, next) => {
var uid = req.users.id;
var identical = req.users.identical;
var createdBy = _.trim(req.body.createdBy);
var friendlyName = _.trim(req.body.friendlyName);
- var isSession = req.body.isSession;
var ttl = parseInt(req.body.ttl);
var description = _.trim(req.body.description);
+ log.debug(req.body)
var newAccessKey = security.randToken(28).concat(identical);
return accountManager.isExsitAccessKeyName(uid, friendlyName)
- .then(function (data) {
+ .then((data) => {
if (!_.isEmpty(data)) {
- throw Error(`The access key "${friendlyName}" already exists.`);
+ throw new AppError.AppError(`The access key "${friendlyName}" already exists.`);
}
})
- .then(function () {
- return accountManager.createAccessKey(uid, newAccessKey, isSession, ttl, friendlyName, createdBy, description);
+ .then(() => {
+ return accountManager.createAccessKey(uid, newAccessKey, ttl, friendlyName, createdBy, description);
})
- .then(function(newToken){
+ .then((newToken) => {
var moment = require("moment");
var info = {
name : newToken.tokens,
createdTime : parseInt(moment(newToken.created_at).format('x')),
createdBy : newToken.created_by,
expires : parseInt(moment(newToken.expires_at).format('x')),
- isSession: newToken.is_session == 1 ? true :false,
description : newToken.description,
friendlyName: newToken.name,
};
+ log.debug(info);
res.send({accessKey:info});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ log.debug(e)
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
-router.delete('/:name', middleware.checkToken, function(req, res){
+router.delete('/:name', middleware.checkToken, (req, res, next) => {
var name = _.trim(decodeURI(req.params.name));
var uid = req.users.id;
return models.UserTokens.destroy({where: {name:name, uid: uid}})
- .then(function(rowNum){
+ .then((rowNum) => {
+ log.debug('delete acceesKey:', name)
res.send({friendlyName:name});
})
- .catch(function (e) {
- res.status(406).send(e.message);
- });
-});
-
-router.patch('/:name', middleware.checkToken, function(req, res){
- var name = _.trim(decodeURI(req.params.name));
- var friendlyName = _.trim(req.body.friendlyName);
- var ttl = _.trim(req.body.ttl);
- var uid = req.users.id;
- return Promise.all([
- models.UserTokens.findOne({where: {name:name, uid: uid}}),
- accountManager.isExsitAccessKeyName(uid, friendlyName),
- ])
- .spread(function (token, token2) {
- if (_.isEmpty(token)) {
- throw new Error(`The access key "${name}" does not exist.`);
- }
- if (!_.isEmpty(token2)) {
- throw new Error(`The access key "${friendlyName}" already exists.`);
- }
- return token;
- })
- .then(function (token) {
- var moment = require('moment');
- if (ttl > 0 || ttl < 0) {
- var newExp = moment(token.get('expires_at')).add(ttl/1000, 'seconds').format('YYYY-MM-DD HH:mm:ss');
- token.set('expires_at', newExp);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ log.debug(e)
+ res.status(406).send(e.message);
+ } else {
+ next(e);
}
- if (friendlyName.length > 0) {
- token.set('name', friendlyName);
- }
- return token.save();
- })
- .then(function (token) {
- var info = {
- name : '(hidden)',
- isSession: token.is_session == 1 ? true :false,
- createdTime : token.created_at,
- createdBy : token.created_by,
- description : token.description,
- expires : token.expires_at,
- friendlyName: token.name,
- id: token.id + ""
- };
- res.send({accessKey: info});
- })
- .catch(function (e) {
- res.status(406).send(e.message);
});
});
-
module.exports = router;
diff --git a/routes/account.js b/routes/account.js
index 93a333a6..edae56f1 100644
--- a/routes/account.js
+++ b/routes/account.js
@@ -4,14 +4,16 @@ var models = require('../models');
var _ = require('lodash');
var security = require('../core/utils/security');
var middleware = require('../core/middleware');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:account");
-router.get('/', middleware.checkToken, function(req, res) {
+router.get('/', middleware.checkToken, (req, res) => {
var userInfo = {
email:req.users.email,
- id:req.users.identical,
linkedProviders: [],
name:req.users.username,
};
+ log.debug(userInfo);
res.send({account:userInfo});
});
diff --git a/routes/apps.js b/routes/apps.js
index 4577599d..8fd7d832 100644
--- a/routes/apps.js
+++ b/routes/apps.js
@@ -9,87 +9,124 @@ var Deployments = require('../core/services/deployments');
var Collaborators = require('../core/services/collaborators');
var AppManager = require('../core/services/app-manager');
var PackageManager = require('../core/services/package-manager');
+var AppError = require('../core/app-error');
var common = require('../core/utils/common');
var config = require('../core/config');
const REGEX = /^(\w+)(-android|-ios)$/;
const REGEX_ANDROID = /^(\w+)(-android)$/;
const REGEX_IOS = /^(\w+)(-ios)$/;
-const OLD_REGEX_ANDROID = /^(android_)/;
-const OLD_REGEX_IOS = /^(ios_)/;
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:apps");
-router.get('/',
- middleware.checkToken, function(req, res) {
+router.get('/', middleware.checkToken, (req, res, next) => {
var uid = req.users.id;
var appManager = new AppManager();
appManager.listApps(uid)
- .then(function (data) {
+ .then((data) => {
res.send({apps: data});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.get('/:appName/deployments',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var uid = req.users.id;
var appName = _.trim(req.params.appName);
var deployments = new Deployments();
accountManager.collaboratorCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
return deployments.listDeloyments(col.appid);
})
- .then(function (data) {
+ .then((data) => {
res.send({deployments: data});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
+ });
+});
+
+router.get('/:appName/deployments/:deploymentName',
+ middleware.checkToken, (req, res, next) => {
+ var uid = req.users.id;
+ var appName = _.trim(req.params.appName);
+ var deploymentName = _.trim(req.params.deploymentName);
+ var deployments = new Deployments();
+ accountManager.collaboratorCan(uid, appName)
+ .then((col) => {
+ return deployments.findDeloymentByName(deploymentName, col.appid)
+ })
+ .then((deploymentInfo) => {
+ if (_.isEmpty(deploymentInfo)) {
+ throw new AppError.AppError("does not find the deployment");
+ }
+ res.send({deployment: deployments.listDeloyment(deploymentInfo)});
+ return true;
+ })
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.post('/:appName/deployments',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var uid = req.users.id;
var appName = _.trim(req.params.appName);
var name = req.body.name;
var deployments = new Deployments();
accountManager.ownerCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
return deployments.addDeloyment(name, col.appid, uid);
})
- .then(function (data) {
+ .then((data) => {
res.send({deployment: {name: data.name, key: data.deployment_key}});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.get('/:appName/deployments/:deploymentName/metrics',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var uid = req.users.id;
var appName = _.trim(req.params.appName);
var deploymentName = _.trim(req.params.deploymentName);
var deployments = new Deployments();
var packageManager = new PackageManager();
-
accountManager.collaboratorCan(uid, appName)
- .then(function(col) {
+ .then((col) => {
return deployments.findDeloymentByName(deploymentName, col.appid)
- .then(function (deploymentInfo) {
+ .then((deploymentInfo) => {
if (_.isEmpty(deploymentInfo)) {
- throw new Error("does not find the deployment");
+ throw new AppError.AppError("does not find the deployment");
}
return deploymentInfo;
})
})
- .then(function(deploymentInfo) {
+ .then((deploymentInfo) => {
return deployments.getAllPackageIdsByDeploymentsId(deploymentInfo.id);
})
- .then(function(packagesInfos) {
- return Promise.reduce(packagesInfos, function (result, v) {
+ .then((packagesInfos) => {
+ return Promise.reduce(packagesInfos, (result, v) => {
return packageManager.getMetricsbyPackageId(v.get('id'))
- .then(function (metrics) {
+ .then((metrics) => {
if (metrics) {
result[v.get('label')] = {
active: metrics.get('active'),
@@ -102,148 +139,167 @@ router.get('/:appName/deployments/:deploymentName/metrics',
});
}, {});
})
- .then(function(rs) {
+ .then((rs) => {
res.send({"metrics": rs});
})
- .catch(function(e){
- res.send({"metrics": null});
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.send({"metrics": null});
+ } else {
+ next(e);
+ }
});
});
router.get('/:appName/deployments/:deploymentName/history',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var uid = req.users.id;
var appName = _.trim(req.params.appName);
var deploymentName = _.trim(req.params.deploymentName);
var deployments = new Deployments();
accountManager.collaboratorCan(uid, appName)
- .then(function(col){
+ .then((col) => {
return deployments.findDeloymentByName(deploymentName, col.appid)
- .then(function (deploymentInfo) {
+ .then((deploymentInfo) => {
if (_.isEmpty(deploymentInfo)) {
- throw new Error("does not find the deployment");
+ throw new AppError.AppError("does not find the deployment");
}
return deploymentInfo;
});
})
- .then(function(deploymentInfo) {
+ .then((deploymentInfo) => {
return deployments.getDeploymentHistory(deploymentInfo.id);
})
- .then(function (rs) {
- res.send({history: rs});
+ .then((rs) => {
+ res.send({history: _.pullAll(rs, [null, false])});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.delete('/:appName/deployments/:deploymentName/history',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var uid = req.users.id;
var appName = _.trim(req.params.appName);
var deploymentName = _.trim(req.params.deploymentName);
var deployments = new Deployments();
accountManager.ownerCan(uid, appName)
- .then(function(col){
+ .then((col) => {
return deployments.findDeloymentByName(deploymentName, col.appid)
- .then(function (deploymentInfo) {
+ .then((deploymentInfo) => {
if (_.isEmpty(deploymentInfo)) {
- throw new Error("does not find the deployment");
+ throw new AppError.AppError("does not find the deployment");
}
return deploymentInfo;
});
})
- .then(function(deploymentInfo) {
+ .then((deploymentInfo) => {
return deployments.deleteDeploymentHistory(deploymentInfo.id);
})
- .then(function (rs) {
+ .then((rs) => {
res.send("ok");
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.patch('/:appName/deployments/:deploymentName',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var name = req.body.name;
var appName = _.trim(req.params.appName);
var deploymentName = _.trim(req.params.deploymentName);
var uid = req.users.id;
var deployments = new Deployments();
accountManager.ownerCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
return deployments.renameDeloymentByName(deploymentName, col.appid, name);
})
- .then(function (data) {
+ .then((data) => {
res.send({deployment: data});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.delete('/:appName/deployments/:deploymentName',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var appName = _.trim(req.params.appName);
var deploymentName = _.trim(req.params.deploymentName);
var uid = req.users.id;
var deployments = new Deployments();
accountManager.ownerCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
return deployments.deleteDeloymentByName(deploymentName, col.appid);
})
- .then(function (data) {
+ .then((data) => {
res.send({deployment: data});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.post('/:appName/deployments/:deploymentName/release',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var appName = _.trim(req.params.appName);
var deploymentName = _.trim(req.params.deploymentName);
var uid = req.users.id;
var deployments = new Deployments();
var packageManager = new PackageManager();
accountManager.collaboratorCan(uid, appName)
- .then(function (col) {
- var pubType = '';
- if (REGEX_ANDROID.test(appName)) {
- pubType = 'android';
- } else if (REGEX_IOS.test(appName)) {
- pubType = 'ios';
- } else {
- throw new Error(`you have to rename app name, eg. Demo-android Demo-ios`);
- }
+ .then((col) => {
+ log.debug(col);
return deployments.findDeloymentByName(deploymentName, col.appid)
- .then(function (deploymentInfo) {
+ .then((deploymentInfo) => {
if (_.isEmpty(deploymentInfo)) {
- throw new Error("does not find the deployment");
+ log.debug(`does not find the deployment`);
+ throw new AppError.AppError("does not find the deployment");
}
return packageManager.parseReqFile(req)
- .then(function (data) {
- return packageManager.releasePackage(deploymentInfo.id, data.packageInfo, data.package.type, data.package.path, uid, pubType)
- .finally(function () {
+ .then((data) => {
+ if (data.package.type != "application/zip") {
+ log.debug(`upload file type is invlidate`, data.package);
+ throw new AppError.AppError("upload file type is invalidate");
+ }
+ log.debug('packageInfo:', data.packageInfo);
+ return packageManager.releasePackage(deploymentInfo.appid, deploymentInfo.id, data.packageInfo, data.package.path, uid)
+ .finally(() => {
common.deleteFolderSync(data.package.path);
});
})
- .then(function (packages) {
+ .then((packages) => {
if (packages) {
- Promise.delay(2000)
- .then(function () {
- packageManager.createDiffPackagesByLastNums(packages.id, _.get(config, 'common.diffNums', 1))
- .catch(function(e){
- console.log(e);
+ Promise.delay(1000)
+ .then(() => {
+ packageManager.createDiffPackagesByLastNums(deploymentInfo.appid, packages, _.get(config, 'common.diffNums', 1))
+ .catch((e) => {
+ log.error(e);
});
});
}
//clear cache if exists.
if (_.get(config, 'common.updateCheckCache', false) !== false) {
Promise.delay(2500)
- .then(function () {
+ .then(() => {
var ClientManager = require('../core/services/client-manager');
var clientManager = new ClientManager();
clientManager.clearUpdateCheckCache(deploymentInfo.deployment_key, '*', '*', '*');
@@ -253,43 +309,80 @@ router.post('/:appName/deployments/:deploymentName/release',
});
});
})
- .then(function (data) {
- res.send("");
+ .then(() => {
+ res.send('{"msg": "succeed"}');
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.patch('/:appName/deployments/:deploymentName/release',
- middleware.checkToken, function (req, res) {
- return res.status(406).send('Not supported currently');
+ middleware.checkToken, (req, res, next) => {
+ log.debug('req.body', req.body);
var appName = _.trim(req.params.appName);
var deploymentName = _.trim(req.params.deploymentName);
var uid = req.users.id;
var deployments = new Deployments();
var packageManager = new PackageManager();
+ var label = _.get(req, 'body.packageInfo.label');
accountManager.collaboratorCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
return deployments.findDeloymentByName(deploymentName, col.appid)
- .then(function (deploymentInfo) {
+ .then((deploymentInfo) => {
if (_.isEmpty(deploymentInfo)) {
- throw new Error("does not find the deployment");
+ throw new AppError.AppError("does not find the deployment");
}
- var label = deploymentInfo.label;
- var deploymentVersionId = deploymentInfo.last_deployment_version_id;
- return packageManager.modifyReleasePackage(deploymentInfo.id, deploymentVersionId, _.get(req, 'body.packageInfo'));
+ if (label) {
+ return packageManager.findPackageInfoByDeploymentIdAndLabel(deploymentInfo.id, label)
+ .then((data)=>{
+ return [deploymentInfo, data];
+ });
+ } else {
+ var deploymentVersionId = deploymentInfo.last_deployment_version_id;
+ return packageManager.findLatestPackageInfoByDeployVersion(deploymentVersionId)
+ .then((data)=>{
+ return [deploymentInfo, data];
+ });;
+ }
+ })
+ .spread((deploymentInfo, packageInfo)=>{
+ if (!packageInfo) {
+ throw new AppError.AppError("does not find the packageInfo");
+ }
+ return packageManager.modifyReleasePackage(packageInfo.id, _.get(req, 'body.packageInfo'))
+ .then(()=>{
+ //clear cache if exists.
+ if (_.get(config, 'common.updateCheckCache', false) !== false) {
+ Promise.delay(2500)
+ .then(() => {
+ var ClientManager = require('../core/services/client-manager');
+ var clientManager = new ClientManager();
+ clientManager.clearUpdateCheckCache(deploymentInfo.deployment_key, '*', '*', '*');
+ });
+ }
+ });
});
- }).then(function (data) {
+ }).then((data) => {
res.send("");
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
+
router.post('/:appName/deployments/:sourceDeploymentName/promote/:destDeploymentName',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
+ log.debug('req.body:', req.body);
var appName = _.trim(req.params.appName);
var sourceDeploymentName = _.trim(req.params.sourceDeploymentName);
var destDeploymentName = _.trim(req.params.destDeploymentName);
@@ -297,55 +390,61 @@ router.post('/:appName/deployments/:sourceDeploymentName/promote/:destDeployment
var packageManager = new PackageManager();
var deployments = new Deployments();
accountManager.collaboratorCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
var appId = col.appid;
return Promise.all([
deployments.findDeloymentByName(sourceDeploymentName, appId),
deployments.findDeloymentByName(destDeploymentName, appId)
])
- .spread(function (sourceDeploymentInfo, destDeploymentInfo) {
+ .spread((sourceDeploymentInfo, destDeploymentInfo) => {
if (!sourceDeploymentInfo) {
- throw new Error(`${sourceDeploymentName} does not exist.`);
+ throw new AppError.AppError(`${sourceDeploymentName} does not exist.`);
}
if (!destDeploymentInfo) {
- throw new Error(`${destDeploymentName} does not exist.`);
+ throw new AppError.AppError(`${destDeploymentName} does not exist.`);
+ }
+ return [sourceDeploymentInfo, destDeploymentInfo];
+ })
+ .spread((sourceDeploymentInfo, destDeploymentInfo) => {
+ var params = _.get(req.body, 'packageInfo', {});
+ _.set(params, 'promoteUid', uid);
+ return [packageManager.promotePackage(sourceDeploymentInfo, destDeploymentInfo, params),destDeploymentInfo];
+ })
+ .spread((packages, destDeploymentInfo) => {
+ if (packages) {
+ Promise.delay(1000)
+ .then(() => {
+ packageManager.createDiffPackagesByLastNums(destDeploymentInfo.appid, packages, _.get(config, 'common.diffNums', 1))
+ .catch((e) => {
+ log.error(e);
+ });
+ });
}
//clear cache if exists.
if (_.get(config, 'common.updateCheckCache', false) !== false) {
Promise.delay(2500)
- .then(function () {
+ .then(() => {
var ClientManager = require('../core/services/client-manager');
var clientManager = new ClientManager();
clientManager.clearUpdateCheckCache(destDeploymentInfo.deployment_key, '*', '*', '*');
});
}
- return [sourceDeploymentInfo.id, destDeploymentInfo.id];
+ return packages;
})
- .spread(function (sourceDeploymentId, destDeploymentId) {
- return packageManager.promotePackage(sourceDeploymentId, destDeploymentId, uid);
- });
})
- .then(function (packages) {
- if (packages) {
- Promise.delay(2000)
- .then(function () {
- packageManager.createDiffPackagesByLastNums(packages.id, _.get(config, 'common.diffNums', 1))
- .catch(function(e){
- console.log(e);
- });
- });
- }
- return null;
- })
- .then(function () {
- res.send('ok');
+ .then((packages) => {
+ res.send({package:packages});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
-var rollbackCb = function (req, res) {
+var rollbackCb = function (req, res, next) {
var appName = _.trim(req.params.appName);
var deploymentName = _.trim(req.params.deploymentName);
var uid = req.users.id;
@@ -353,26 +452,42 @@ var rollbackCb = function (req, res) {
var deployments = new Deployments();
var packageManager = new PackageManager();
accountManager.collaboratorCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
return deployments.findDeloymentByName(deploymentName, col.appid);
})
- .then(function(dep){
- //clear cache if exists.
- if (_.get(config, 'common.updateCheckCache', false) !== false) {
- Promise.delay(2500)
- .then(function () {
- var ClientManager = require('../core/services/client-manager');
- var clientManager = new ClientManager();
- clientManager.clearUpdateCheckCache(dep.deployment_key, '*', '*', '*');
- });
- }
- return packageManager.rollbackPackage(dep.last_deployment_version_id, targetLabel, uid);
+ .then((dep) => {
+ return packageManager.rollbackPackage(dep.last_deployment_version_id, targetLabel, uid)
+ .then((packageInfo)=>{
+ if (packageInfo) {
+ Promise.delay(1000)
+ .then(() => {
+ packageManager.createDiffPackagesByLastNums(dep.appid, packageInfo, 1)
+ .catch((e) => {
+ log.error(e);
+ });
+ });
+ }
+ //clear cache if exists.
+ if (_.get(config, 'common.updateCheckCache', false) !== false) {
+ Promise.delay(2500)
+ .then(() => {
+ var ClientManager = require('../core/services/client-manager');
+ var clientManager = new ClientManager();
+ clientManager.clearUpdateCheckCache(dep.deployment_key, '*', '*', '*');
+ });
+ }
+ return packageInfo;
+ });
})
- .then(function () {
+ .then(() => {
res.send('ok');
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
};
@@ -383,15 +498,16 @@ router.post('/:appName/deployments/:deploymentName/rollback/:label',
middleware.checkToken, rollbackCb);
router.get('/:appName/collaborators',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var appName = _.trim(req.params.appName);
var uid = req.users.id;
var collaborators = new Collaborators();
- accountManager.collaboratorCan(uid, appName).then(function (col) {
+ accountManager.collaboratorCan(uid, appName)
+ .then((col) => {
return collaborators.listCollaborators(col.appid);
})
- .then(function (data) {
- rs = _.reduce(data, function (result, value, key) {
+ .then((data) => {
+ rs = _.reduce(data, (result, value, key) => {
if (_.eq(key, req.users.email)) {
value.isCurrentAccount = true;
}else {
@@ -402,13 +518,17 @@ router.get('/:appName/collaborators',
},{});
res.send({collaborators: rs});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.post('/:appName/collaborators/:email',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var appName = _.trim(req.params.appName);
var email = _.trim(req.params.email);
var uid = req.users.id;
@@ -417,21 +537,26 @@ router.post('/:appName/collaborators/:email',
}
var collaborators = new Collaborators();
accountManager.ownerCan(uid, appName)
- .then(function (col) {
- return accountManager.findUserByEmail(email).then(function (data) {
+ .then((col) => {
+ return accountManager.findUserByEmail(email)
+ .then((data) => {
return collaborators.addCollaborator(col.appid, data.id);
});
})
- .then(function (data) {
+ .then((data) => {
res.send(data);
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.delete('/:appName/collaborators/:email',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var appName = _.trim(req.params.appName);
var email = _.trim(decodeURI(req.params.email));
var uid = req.users.id;
@@ -440,42 +565,51 @@ router.delete('/:appName/collaborators/:email',
}
var collaborators = new Collaborators();
accountManager.ownerCan(uid, appName)
- .then(function (col) {
- return accountManager.findUserByEmail(email).then(function (data) {
+ .then((col) => {
+ return accountManager.findUserByEmail(email)
+ .then((data) => {
if (_.eq(data.id, uid)) {
- throw new Error("can't delete yourself!");
+ throw new AppError.AppError("can't delete yourself!");
} else {
return collaborators.deleteCollaborator(col.appid, data.id);
}
});
})
- .then(function () {
+ .then(() => {
res.send("");
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.delete('/:appName',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var appName = _.trim(req.params.appName);
var uid = req.users.id;
var appManager = new AppManager();
accountManager.ownerCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
return appManager.deleteApp(col.appid);
})
- .then(function (data) {
+ .then((data) => {
res.send(data);
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
router.patch('/:appName',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var newAppName = _.trim(req.body.name);
var appName = _.trim(req.params.appName);
var uid = req.users.id;
@@ -484,37 +618,30 @@ router.patch('/:appName',
} else {
var appManager = new AppManager();
return accountManager.ownerCan(uid, appName)
- .then(function (col) {
- if (REGEX_ANDROID.test(appName) || OLD_REGEX_ANDROID.test(appName)) {
- if (!REGEX_ANDROID.test(newAppName)) {
- throw new Error(`new appName have to point -android suffix! eg. Demo-android`);
- }
- } else if (REGEX_IOS.test(appName) || OLD_REGEX_IOS.test(appName)) {
- if (!REGEX_IOS.test(newAppName)) {
- throw new Error(`new appName have to point -ios suffix! eg. Demo-ios`);
- }
- } else {
- throw new Error(`appName have to point -android or -ios suffix! eg. ${appName}-android ${appName}-ios`);
- }
+ .then((col) => {
return appManager.findAppByName(uid, newAppName)
- .then(function (appInfo) {
+ .then((appInfo) => {
if (!_.isEmpty(appInfo)){
- throw new Error(newAppName + " Exist!");
+ throw new AppError.AppError(newAppName + " Exist!");
}
return appManager.modifyApp(col.appid, {name: newAppName});
});
})
- .then(function () {
+ .then(() => {
res.send("");
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
}
});
router.post('/:appName/transfer/:email',
- middleware.checkToken, function (req, res) {
+ middleware.checkToken, (req, res, next) => {
var appName = _.trim(req.params.appName);
var email = _.trim(req.params.email);
var uid = req.users.id;
@@ -522,49 +649,78 @@ router.post('/:appName/transfer/:email',
return res.status(406).send("Invalid Email!");
}
return accountManager.ownerCan(uid, appName)
- .then(function (col) {
+ .then((col) => {
return accountManager.findUserByEmail(email)
- .then(function (data) {
+ .then((data) => {
if (_.eq(data.id, uid)) {
- throw new Error("You can't transfer to yourself!");
+ throw new AppError.AppError("You can't transfer to yourself!");
}
var appManager = new AppManager();
return appManager.transferApp(col.appid, uid, data.id);
});
})
- .then(function (data) {
+ .then((data) => {
res.send(data);
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
-router.post('/', middleware.checkToken, function (req, res) {
+router.post('/', middleware.checkToken, (req, res, next) => {
+ log.debug("addApp params:",req.body);
+ var constName = require('../core/const');
var appName = req.body.name;
- var uid = req.users.id;
- var appManager = new AppManager();
if (_.isEmpty(appName)) {
return res.status(406).send("Please input name!");
}
+ var osName = _.toLower(req.body.os);
+ var os;
+ if (osName == _.toLower(constName.IOS_NAME)) {
+ os = constName.IOS;
+ } else if (osName == _.toLower(constName.ANDROID_NAME)) {
+ os = constName.ANDROID;
+ } else if (osName == _.toLower(constName.WINDOWS_NAME)) {
+ os = constName.WINDOWS;
+ } else {
+ return res.status(406).send("Please input os [iOS|Android|Windows]!");
+ }
+ var platformName = _.toLower(req.body.platform);
+ var platform;
+ if (platformName == _.toLower(constName.REACT_NATIVE_NAME)) {
+ platform = constName.REACT_NATIVE;
+ } else if (platformName == _.toLower(constName.CORDOVA_NAME)) {
+ platform = constName.CORDOVA;
+ } else {
+ return res.status(406).send("Please input platform [React-Native|Cordova]!");
+ }
+ var manuallyProvisionDeployments = req.body.manuallyProvisionDeployments;
+ var uid = req.users.id;
+ var appManager = new AppManager();
+
appManager.findAppByName(uid, appName)
- .then(function (appInfo) {
+ .then((appInfo) => {
if (!_.isEmpty(appInfo)){
- throw new Error(appName + " Exist!");
- }
- if (!REGEX.test(appName)) {
- throw new Error(`appName have to point -android or -ios suffix! eg. ${appName}-android ${appName}-ios`);
+ throw new AppError.AppError(appName + " Exist!");
}
- return appManager.addApp(uid, appName, req.users.identical)
- .then(function () {
+ return appManager.addApp(uid, appName, os, platform, req.users.identical)
+ .then(() => {
return {name: appName, collaborators: {[req.users.email]: {permission: "Owner"}}};
});
})
- .then(function (data) {
+ .then((data) => {
res.send({app: data});
})
- .catch(function (e) {
- res.status(406).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(406).send(e.message);
+ } else {
+ next(e);
+ }
});
});
diff --git a/routes/auth.js b/routes/auth.js
index 5f805b86..7bdd96c2 100644
--- a/routes/auth.js
+++ b/routes/auth.js
@@ -1,66 +1,68 @@
var express = require('express');
var router = express.Router();
var _ = require('lodash');
-var security = require('../core/utils/security');
-var accountManager = require('../core/services/account-manager')();
+var config = require('../core/config');
+var validator = require('validator');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:auth");
-router.get('/login', function(req, res) {
- var config = require('../core/config');
+router.get('/password', (req, res) => {
+ res.render('auth/password', { title: 'CodePushServer' });
+});
+
+router.get('/login', (req, res) => {
var codePushWebUrl = _.get(config, 'common.codePushWebUrl');
- var isRedirect = false;
- if (codePushWebUrl) {
- var validator = require('validator');
- if (validator.isURL(codePushWebUrl)){
- isRedirect = true;
- }
- }
- if (isRedirect) {
+ if (codePushWebUrl && validator.isURL(codePushWebUrl)) {
+ log.debug(`login redirect:${codePushWebUrl}`);
res.redirect(`${codePushWebUrl}/login`);
} else {
res.render('auth/login', { title: 'CodePushServer' });
}
});
-router.get('/link', function(req, res) {
+router.get('/link', (req, res) => {
res.redirect(`/auth/login`);
});
-router.get('/register', function(req, res) {
- var config = require('../core/config');
+router.get('/register', (req, res) => {
var codePushWebUrl = _.get(config, 'common.codePushWebUrl');
var isRedirect = false;
- if (codePushWebUrl) {
- var validator = require('validator');
- if (validator.isURL(codePushWebUrl)){
- isRedirect = true;
- }
- }
- if (isRedirect) {
+ if (codePushWebUrl && validator.isURL(codePushWebUrl)) {
+ log.debug(`register redirect:${codePushWebUrl}`);
res.redirect(`${codePushWebUrl}/register`);
} else {
res.render('auth/login', { title: 'CodePushServer' });
}
});
-router.post('/logout', function (req, res) {
+router.post('/logout', (req, res) => {
res.send("ok");
});
-router.post('/login', function(req, res) {
+router.post('/login', (req, res, next) => {
+ var AppError = require('../core/app-error');
+ var accountManager = require('../core/services/account-manager')();
+ var security = require('../core/utils/security');
var account = _.trim(req.body.account);
var password = _.trim(req.body.password);
- var config = require('../core/config');
var tokenSecret = _.get(config, 'jwt.tokenSecret');
+ log.debug(`login:${account}`);
accountManager.login(account, password)
- .then(function (users) {
+ .then((users) => {
var jwt = require('jsonwebtoken');
return jwt.sign({ uid: users.id, hash: security.md5(users.ack_code), expiredIn: 7200 }, tokenSecret);
})
- .then(function (token) {
+ .then((token) => {
+ log.debug(token);
res.send({status:'OK', results: {tokens: token}});
})
- .catch(function (e) {
- res.send({status:'ERROR', errorMessage: e.message});
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ log.debug(e);
+ res.send({status:'ERROR', errorMessage: e.message});
+ } else {
+ next(e);
+ }
});
});
diff --git a/routes/index.js b/routes/index.js
index 97f2911f..d070b551 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -1,75 +1,106 @@
var express = require('express');
var router = express.Router();
var Promise = require('bluebird');
+var AppError = require('../core/app-error');
var middleware = require('../core/middleware');
var ClientManager = require('../core/services/client-manager');
var _ = require('lodash');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:index");
-router.get('/', function(req, res) {
+router.get('/', (req, res, next) => {
res.render('index', { title: 'CodePushServer' });
});
-router.get('/README.md', function(req, res) {
+router.get('/README.md', (req, res, next) => {
var MarkdownIt = require('markdown-it');
const path = require('path');
const fs = require('fs');
const readFile = Promise.promisify(fs.readFile);
const README = path.join(__dirname, '../README.md');
readFile(README, { encoding: 'utf8' })
- .then(source=>{
+ .then(source => {
var md = new MarkdownIt();
res.send(md.render(source));
})
.catch(e=>{
- res.send(e.message);
+ if (e instanceof AppError.AppError) {
+ res.send(e.message);
+ } else {
+ next(e);
+ }
});
-
});
-router.get('/tokens', function(req, res) {
+router.get('/tokens', (req, res) => {
res.render('tokens', { title: '获取token' });
});
-router.get('/updateCheck', function(req, res){
+router.get('/updateCheck', (req, res, next) => {
var deploymentKey = _.get(req, "query.deploymentKey");
var appVersion = _.get(req, "query.appVersion");
var label = _.get(req, "query.label");
var packageHash = _.get(req, "query.packageHash")
+ var clientUniqueId = _.get(req, "query.clientUniqueId")
var clientManager = new ClientManager();
- clientManager.updateCheckFromCache(deploymentKey, appVersion, label, packageHash)
- .then(function (rs) {
+ log.debug('req.query', req.query);
+ clientManager.updateCheckFromCache(deploymentKey, appVersion, label, packageHash, clientUniqueId)
+ .then((rs) => {
+ //灰度检测
+ return clientManager.chosenMan(rs.packageId, rs.rollout, clientUniqueId)
+ .then((data)=>{
+ if (!data) {
+ rs.isAvailable = false;
+ return rs;
+ }
+ return rs;
+ });
+ })
+ .then((rs) => {
+ delete rs.packageId;
+ delete rs.rollout;
res.send({"updateInfo":rs});
})
- .catch(function (e) {
- res.status(404).send(e.message);
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(404).send(e.message);
+ } else {
+ next(e);
+ }
});
});
-router.post('/reportStatus/download', function(req, res){
+router.post('/reportStatus/download', (req, res) => {
+ log.debug('req.body', req.body);
var clientUniqueId = _.get(req, "body.clientUniqueId");
var label = _.get(req, "body.label");
var deploymentKey = _.get(req, "body.deploymentKey");
var clientManager = new ClientManager();
clientManager.reportStatusDownload(deploymentKey, label, clientUniqueId)
- .catch(function (e) {
- console.log(e);
+ .catch((err) => {
+ if (!err instanceof AppError.AppError) {
+ console.error(err.stack)
+ }
});
res.send('OK');
});
-router.post('/reportStatus/deploy', function(req, res){
+router.post('/reportStatus/deploy', (req, res) => {
+ log.debug('req.body', req.body);
var clientUniqueId = _.get(req, "body.clientUniqueId");
var label = _.get(req, "body.label");
var deploymentKey = _.get(req, "body.deploymentKey");
var clientManager = new ClientManager();
clientManager.reportStatusDeploy(deploymentKey, label, clientUniqueId, req.body)
- .catch(function (e) {
- console.log(e);
- });;
+ .catch((err) => {
+ if (!err instanceof AppError.AppError) {
+ console.error(err.stack)
+ }
+ });
res.send('OK');
});
-router.get('/authenticated', middleware.checkToken, function (req, res) {
+router.get('/authenticated', middleware.checkToken, (req, res) => {
return res.send({authenticated: true});
})
diff --git a/routes/indexV1.js b/routes/indexV1.js
new file mode 100644
index 00000000..1720f1a8
--- /dev/null
+++ b/routes/indexV1.js
@@ -0,0 +1,89 @@
+var express = require('express');
+var router = express.Router();
+var Promise = require('bluebird');
+var AppError = require('../core/app-error');
+var middleware = require('../core/middleware');
+var ClientManager = require('../core/services/client-manager');
+var _ = require('lodash');
+var log4js = require('log4js');
+var log = log4js.getLogger("cps:indexV1");
+
+router.get('/update_check', (req, res, next) => {
+ var deploymentKey = _.get(req, "query.deployment_key");
+ var appVersion = _.get(req, "query.app_version");
+ var label = _.get(req, "query.label");
+ var packageHash = _.get(req, "query.package_hash")
+ var isCompanion = _.get(req, "query.is_companion")
+ var clientUniqueId = _.get(req, "query.client_unique_id")
+ var clientManager = new ClientManager();
+ log.debug('req.query', req.query);
+ clientManager.updateCheckFromCache(deploymentKey, appVersion, label, packageHash, clientUniqueId)
+ .then((rs) => {
+ //灰度检测
+ return clientManager.chosenMan(rs.packageId, rs.rollout, clientUniqueId)
+ .then((data)=>{
+ if (!data) {
+ rs.isAvailable = false;
+ return rs;
+ }
+ return rs;
+ });
+ })
+ .then((rs) => {
+ delete rs.packageId;
+ delete rs.rollout;
+ var update_info = {
+ download_url : rs.downloadUrl,
+ description : rs.description,
+ is_available : rs.isAvailable,
+ is_disabled : rs.isDisabled,
+ target_binary_range: rs.targetBinaryRange,
+ label: rs.label,
+ package_hash: rs.packageHash,
+ package_size: rs.packageSize,
+ should_run_binary_version: rs.shouldRunBinaryVersion,
+ update_app_version: rs.updateAppVersion,
+ is_mandatory: rs.isMandatory,
+ };
+ res.send({"update_info": update_info});
+ })
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.status(404).send(e.message);
+ } else {
+ next(e);
+ }
+ });
+});
+
+router.post('/report_status/download', (req, res) => {
+ log.debug('req.body', req.body);
+ var clientUniqueId = _.get(req, "body.client_unique_id");
+ var label = _.get(req, "body.label");
+ var deploymentKey = _.get(req, "body.deployment_key");
+ var clientManager = new ClientManager();
+ clientManager.reportStatusDownload(deploymentKey, label, clientUniqueId)
+ .catch((err) => {
+ if (!err instanceof AppError.AppError) {
+ console.error(err.stack)
+ }
+ });
+ res.send('OK');
+});
+
+router.post('/report_status/deploy', (req, res) => {
+ log.debug('req.body', req.body);
+ var clientUniqueId = _.get(req, "body.client_unique_id");
+ var label = _.get(req, "body.label");
+ var deploymentKey = _.get(req, "body.deployment_key");
+ var clientManager = new ClientManager();
+ clientManager.reportStatusDeploy(deploymentKey, label, clientUniqueId, req.body)
+ .catch((err) => {
+ if (!err instanceof AppError.AppError) {
+ console.error(err.stack)
+ }
+ });
+ res.send('OK');
+});
+
+module.exports = router;
diff --git a/routes/sessions.js b/routes/sessions.js
deleted file mode 100644
index 3c1f5927..00000000
--- a/routes/sessions.js
+++ /dev/null
@@ -1,19 +0,0 @@
-var express = require('express');
-var router = express.Router();
-var _ = require('lodash');
-var models = require('../models');
-var middleware = require('../core/middleware');
-
-router.delete('/:machineName', middleware.checkToken, function(req, res){
- var machineName = _.trim(decodeURI(req.params.machineName));
- var uid = req.users.id;
- models.UserTokens.destroy({where: {created_by:machineName, uid: uid}})
- .then(function(rowNum){
- res.send("");
- })
- .catch(function (e) {
- res.status(406).send(e.message);
- });
-});
-
-module.exports = router;
diff --git a/routes/users.js b/routes/users.js
index b0dc7f3c..c4664f3f 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -5,83 +5,103 @@ var _ = require('lodash');
var models = require('../models');
var middleware = require('../core/middleware');
var AccountManager = require('../core/services/account-manager');
+var AppError = require('../core/app-error')
-router.get('/', middleware.checkToken, function(req, res) {
+router.get('/', middleware.checkToken, (req, res) => {
res.send({ title: 'CodePushServer' });
});
-router.post('/', function (req, res) {
+router.post('/', (req, res, next) => {
var email = _.trim(_.get(req, 'body.email'));
var token = _.trim(_.get(req, 'body.token'));
var password = _.trim(_.get(req, 'body.password'));
var accountManager = new AccountManager();
return accountManager.checkRegisterCode(email, token)
- .then(function (u) {
+ .then((u) => {
if (_.isString(password) && password.length < 6) {
- throw new Error('请您输入6~20位长度的密码');
+ throw new AppError.AppError('请您输入6~20位长度的密码');
}
return accountManager.register(email, password);
})
- .then(function () {
+ .then(() => {
res.send({status: "OK"});
})
- .catch(function (e) {
- res.send({status: "ERROR", message: e.message});
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.send({status: "ERROR", message: e.message});
+ } else {
+ next(e);
+ }
});
-
});
-router.get('/exists', function (req, res) {
+router.get('/exists', (req, res, next) => {
var email = _.trim(_.get(req, 'query.email'));
models.Users.findOne({where: {email: email}})
- .then(function (u) {
+ .then((u) => {
if (!email) {
- throw new Error(`请您输入邮箱地址`);
+ throw new AppError.AppError(`请您输入邮箱地址`);
}
res.send({status: "OK", exists: u ? true : false});
})
- .catch(function (e) {
- res.send({status: "ERROR", message: e.message});
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.send({status: "ERROR", message: e.message});
+ } else {
+ next(e);
+ }
});
});
-router.post('/registerCode', function (req, res) {
+router.post('/registerCode', (req, res, next) => {
var email = _.get(req, 'body.email');
var accountManager = new AccountManager();
return accountManager.sendRegisterCode(email)
- .then(function () {
+ .then(() => {
res.send({status: "OK"});
})
- .catch(function (e) {
- res.send({status: "ERROR", message: e.message});
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.send({status: "ERROR", message: e.message});
+ } else {
+ next(e);
+ }
});
});
-router.get('/registerCode/exists', function (req, res) {
+router.get('/registerCode/exists', (req, res, next) => {
var email = _.trim(_.get(req, 'query.email'));
var token = _.trim(_.get(req, 'query.token'));
var accountManager = new AccountManager();
return accountManager.checkRegisterCode(email, token)
- .then(function () {
+ .then(() => {
res.send({status: "OK"});
})
- .catch(function (e) {
- res.send({status: "ERROR", message: e.message});
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.send({status: "ERROR", message: e.message});
+ } else {
+ next(e);
+ }
});
});
//修改密码
-router.patch('/password', middleware.checkToken, function(req, res) {
+router.patch('/password', middleware.checkToken, (req, res, next) => {
var oldPassword = _.trim(_.get(req, 'body.oldPassword'));
var newPassword = _.trim(_.get(req, 'body.newPassword'));
var uid = req.users.id;
var accountManager = new AccountManager();
return accountManager.changePassword(uid, oldPassword, newPassword)
- .then(function () {
+ .then(() => {
res.send({status: "OK"});
})
- .catch(function (e) {
- res.send({status: "ERROR", message: e.message});
+ .catch((e) => {
+ if (e instanceof AppError.AppError) {
+ res.send({status: "ERROR", message: e.message});
+ } else {
+ next(e);
+ }
});
});
diff --git a/sql/codepush-all.sql b/sql/codepush-all.sql
index e20dfe06..7e2d8776 100644
--- a/sql/codepush-all.sql
+++ b/sql/codepush-all.sql
@@ -1,17 +1,18 @@
-DROP TABLE IF EXISTS `apps`;
-CREATE TABLE `apps` (
+CREATE TABLE IF NOT EXISTS `apps` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`uid` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `os` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `platform` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `is_use_diff_text` tinyint(3) unsigned NOT NULL DEFAULT '0',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`(12))
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `collaborators`;
-CREATE TABLE `collaborators` (
+CREATE TABLE IF NOT EXISTS `collaborators` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`appid` int(10) unsigned NOT NULL DEFAULT '0',
`uid` bigint(20) unsigned NOT NULL DEFAULT '0',
@@ -24,9 +25,7 @@ CREATE TABLE `collaborators` (
KEY `idx_uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-DROP TABLE IF EXISTS `deployments`;
-CREATE TABLE `deployments` (
+CREATE TABLE IF NOT EXISTS `deployments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`appid` int(10) unsigned NOT NULL DEFAULT '0',
`name` varchar(20) NOT NULL DEFAULT '',
@@ -42,8 +41,7 @@ CREATE TABLE `deployments` (
KEY `idx_deploymentkey` (`deployment_key`(40))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `deployments_history`;
-CREATE TABLE `deployments_history` (
+CREATE TABLE IF NOT EXISTS `deployments_history` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`deployment_id` int(11) unsigned NOT NULL DEFAULT '0',
`package_id` int(10) unsigned NOT NULL DEFAULT '0',
@@ -53,22 +51,23 @@ CREATE TABLE `deployments_history` (
KEY `idx_deployment_id` (`deployment_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-DROP TABLE IF EXISTS `deployments_versions`;
-CREATE TABLE `deployments_versions` (
+CREATE TABLE IF NOT EXISTS `deployments_versions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`deployment_id` int(11) unsigned NOT NULL DEFAULT '0',
- `app_version` varchar(14) NOT NULL DEFAULT '',
+ `app_version` varchar(100) NOT NULL DEFAULT '',
`current_package_id` int(10) unsigned NOT NULL DEFAULT '0',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
+ `min_version` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `max_version` bigint(20) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
- KEY `idx_did_appversion` (`deployment_id`,`app_version`)
+ KEY `idx_did_minversion` (`deployment_id`,`min_version`),
+ KEY `idx_did_maxversion` (`deployment_id`,`max_version`),
+ KEY `idx_did_appversion` (`deployment_id`,`app_version`(30))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `packages`;
-CREATE TABLE `packages` (
+CREATE TABLE IF NOT EXISTS `packages` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`deployment_version_id` int(10) unsigned NOT NULL DEFAULT '0',
`deployment_id` int(10) unsigned NOT NULL DEFAULT '0',
@@ -83,16 +82,17 @@ CREATE TABLE `packages` (
`original_deployment` varchar(20) NOT NULL DEFAULT '',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NULL DEFAULT NULL,
- `released_by` bigint(20) unsigned NOT NULL,
+ `released_by` bigint(20) unsigned NOT NULL DEFAULT '0',
`is_mandatory` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `is_disabled` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `rollout` tinyint(3) unsigned NOT NULL DEFAULT '0',
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_deploymentid_label` (`deployment_id`,`label`(8)),
KEY `idx_versions_id` (`deployment_version_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `packages_diff`;
-CREATE TABLE `packages_diff` (
+CREATE TABLE IF NOT EXISTS `packages_diff` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`package_id` int(11) unsigned NOT NULL DEFAULT '0',
`diff_against_package_hash` varchar(64) NOT NULL DEFAULT '',
@@ -105,8 +105,7 @@ CREATE TABLE `packages_diff` (
KEY `idx_packageid_hash` (`package_id`,`diff_against_package_hash`(40))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `packages_metrics`;
-CREATE TABLE `packages_metrics` (
+CREATE TABLE IF NOT EXISTS `packages_metrics` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`package_id` int(10) unsigned NOT NULL DEFAULT '0',
`active` int(10) unsigned NOT NULL DEFAULT '0',
@@ -120,8 +119,7 @@ CREATE TABLE `packages_metrics` (
KEY `idx_packageid` (`package_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `user_tokens`;
-CREATE TABLE `user_tokens` (
+CREATE TABLE IF NOT EXISTS `user_tokens` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uid` bigint(20) unsigned NOT NULL DEFAULT '0',
`name` varchar(50) NOT NULL DEFAULT '',
@@ -137,8 +135,7 @@ CREATE TABLE `user_tokens` (
KEY `idx_tokens` (`tokens`) KEY_BLOCK_SIZE=16
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `users`;
-CREATE TABLE `users` (
+CREATE TABLE IF NOT EXISTS `users` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL DEFAULT '',
`password` varchar(255) NOT NULL DEFAULT '',
@@ -157,9 +154,7 @@ INSERT INTO `users` (`id`, `username`, `password`, `email`, `identical`, `ack_co
VALUES
(1,'admin','$2a$12$mvUY9kTqW4kSoGuZFDW0sOSgKmNY8SPHVyVrSckBTLtXKf6vKX3W.','lisong2010@gmail.com','4ksvOXqog','oZmGE','2016-11-14 10:46:55','2016-02-29 21:24:49');
-
-DROP TABLE IF EXISTS `versions`;
-CREATE TABLE `versions` (
+CREATE TABLE IF NOT EXISTS `versions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '1.DBversion',
`version` varchar(10) NOT NULL DEFAULT '',
@@ -170,5 +165,24 @@ CREATE TABLE `versions` (
LOCK TABLES `versions` WRITE;
INSERT INTO `versions` (`id`, `type`, `version`)
VALUES
- (1,1,'0.2.15');
-UNLOCK TABLES;
\ No newline at end of file
+ (1,1,'0.5.0');
+UNLOCK TABLES;
+
+CREATE TABLE IF NOT EXISTS `log_report_deploy` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `status` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `package_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `client_unique_id` varchar(100) NOT NULL DEFAULT '',
+ `previous_label` varchar(20) NOT NULL DEFAULT '',
+ `previous_deployment_key` varchar(64) NOT NULL DEFAULT '',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `log_report_download` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `package_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `client_unique_id` varchar(100) NOT NULL DEFAULT '',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/sql/codepush-v0.2.14.sql b/sql/codepush-v0.2.14-patch.sql
similarity index 100%
rename from sql/codepush-v0.2.14.sql
rename to sql/codepush-v0.2.14-patch.sql
diff --git a/sql/codepush-v0.2.15.sql b/sql/codepush-v0.2.15-patch.sql
similarity index 100%
rename from sql/codepush-v0.2.15.sql
rename to sql/codepush-v0.2.15-patch.sql
diff --git a/sql/codepush-v0.3.0-patch.sql b/sql/codepush-v0.3.0-patch.sql
new file mode 100644
index 00000000..bd672fe5
--- /dev/null
+++ b/sql/codepush-v0.3.0-patch.sql
@@ -0,0 +1,25 @@
+ALTER TABLE `apps` ADD `os` TINYINT UNSIGNED NOT NULL DEFAULT 0;
+ALTER TABLE `apps` ADD `platform` TINYINT UNSIGNED NOT NULL DEFAULT 0;
+ALTER TABLE `packages` ADD `is_disabled` TINYINT UNSIGNED NOT NULL DEFAULT 0;
+ALTER TABLE `packages` ADD `rollout` TINYINT UNSIGNED NOT NULL DEFAULT 100;
+
+CREATE TABLE `log_report_deploy` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `status` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `package_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `client_unique_id` varchar(100) NOT NULL DEFAULT '',
+ `previous_label` varchar(20) NOT NULL DEFAULT '',
+ `previous_deployment_key` varchar(64) NOT NULL DEFAULT '',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `log_report_download` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `package_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `client_unique_id` varchar(100) NOT NULL DEFAULT '',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+UPDATE `versions` SET `version` = '0.3.0' WHERE `type` = '1';
\ No newline at end of file
diff --git a/sql/codepush-v0.4.0-patch.sql b/sql/codepush-v0.4.0-patch.sql
new file mode 100644
index 00000000..a8933f94
--- /dev/null
+++ b/sql/codepush-v0.4.0-patch.sql
@@ -0,0 +1,9 @@
+ALTER TABLE `deployments_versions` ADD `min_version` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0';
+ALTER TABLE `deployments_versions` ADD `max_version` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0';
+ALTER TABLE `deployments_versions` ADD INDEX `idx_did_min_version` (`deployment_id`, `min_version`);
+ALTER TABLE `deployments_versions` ADD INDEX `idx_did_maxversion` (`deployment_id`, `max_version`);
+ALTER TABLE `deployments_versions` CHANGE `app_version` `app_version` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '';
+ALTER TABLE `deployments_versions` DROP INDEX `idx_did_appversion`;
+ALTER TABLE `deployments_versions` ADD INDEX `idx_did_appversion` (`deployment_id`, `app_version` (30));
+
+UPDATE `versions` SET `version` = '0.4.0' WHERE `type` = '1';
\ No newline at end of file
diff --git a/sql/codepush-v0.5.0-patch.sql b/sql/codepush-v0.5.0-patch.sql
new file mode 100644
index 00000000..09b822d3
--- /dev/null
+++ b/sql/codepush-v0.5.0-patch.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `apps` ADD `is_use_diff_text` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0';
+
+UPDATE `versions` SET `version` = '0.5.0' WHERE `type` = '1';
\ No newline at end of file
diff --git a/test/api/accessKeys/accessKeys.test.js b/test/api/accessKeys/accessKeys.test.js
index 83122f8d..69dc2fd6 100644
--- a/test/api/accessKeys/accessKeys.test.js
+++ b/test/api/accessKeys/accessKeys.test.js
@@ -30,14 +30,14 @@ describe('api/accessKeys/accessKeys.test.js', function() {
it('should create accessKeys successful', function(done) {
request.post(`/accessKeys`)
.set('Authorization', `Basic ${authToken}`)
- .send({createdBy: 'tablee', friendlyName: friendlyName, isSession: false, ttl: 30*24*60*60})
+ .send({createdBy: 'tablee', friendlyName: friendlyName, ttl: 30*24*60*60})
.end(function(err, res) {
should.not.exist(err);
res.status.should.equal(200);
var rs = JSON.parse(res.text);
rs.should.have.properties('accessKey');
rs.accessKey.should.have.properties(['name', 'createdTime', 'createdBy',
- 'expires', 'isSession', 'description', 'friendlyName']);
+ 'expires', 'description', 'friendlyName']);
done();
});
});
@@ -45,7 +45,7 @@ describe('api/accessKeys/accessKeys.test.js', function() {
it('should not create accessKeys successful when friendlyName exist', function(done) {
request.post(`/accessKeys`)
.set('Authorization', `Basic ${authToken}`)
- .send({createdBy: 'tablee', friendlyName: friendlyName, isSession: true, ttl: 30*24*60*60})
+ .send({createdBy: 'tablee', friendlyName: friendlyName, ttl: 30*24*60*60})
.end(function(err, res) {
should.not.exist(err);
res.status.should.equal(406);
@@ -68,84 +68,13 @@ describe('api/accessKeys/accessKeys.test.js', function() {
rs.accessKeys.should.be.an.instanceOf(Array);
rs.accessKeys.should.matchEach(function(it) {
return it.should.have.properties(['name', 'createdTime', 'createdBy',
- 'expires', 'isSession', 'description', 'friendlyName']);
+ 'expires', 'description', 'friendlyName']);
});
done();
});
});
});
- describe('modify accessKeys', function(done) {
- it('should modify accessKeys add ttl successful', function(done) {
- request.patch(`/accessKeys/${encodeURI(friendlyName)}`)
- .set('Authorization', `Basic ${authToken}`)
- .send({ttl: 7*24*60*60*1000})
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(200);
- var rs = JSON.parse(res.text);
- rs.should.have.properties('accessKey');
- rs.accessKey.should.have.properties(['name', 'createdTime', 'createdBy',
- 'expires', 'isSession', 'description', 'friendlyName']);
- done();
- });
- });
-
- it('should modify accessKeys substact ttl successful', function(done) {
- request.patch(`/accessKeys/${encodeURI(friendlyName)}`)
- .set('Authorization', `Basic ${authToken}`)
- .send({ttl: -7*24*60*60*1000})
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(200);
- var rs = JSON.parse(res.text);
- rs.should.have.properties('accessKey');
- rs.accessKey.should.have.properties(['name', 'createdTime', 'createdBy',
- 'expires', 'isSession', 'description', 'friendlyName']);
- done();
- });
- });
-
- it('should not modify accessKeys friendlyName successful when friendlyName exists', function(done) {
- request.patch(`/accessKeys/${encodeURI(friendlyName)}`)
- .set('Authorization', `Basic ${authToken}`)
- .send({friendlyName: friendlyName})
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(406);
- res.text.should.equal(`The access key "${friendlyName}" already exists.`);
- done();
- });
- });
-
- it('should not modify accessKeys friendlyName successful when friendlyName invalid', function(done) {
- request.patch(`/accessKeys/${encodeURI(newFriendlyName)}`)
- .set('Authorization', `Basic ${authToken}`)
- .send({friendlyName: newFriendlyName})
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(406);
- res.text.should.equal(`The access key "${newFriendlyName}" does not exist.`);
- done();
- });
- });
-
- it('should modify accessKeys friendlyName successful', function(done) {
- request.patch(`/accessKeys/${encodeURI(friendlyName)}`)
- .set('Authorization', `Basic ${authToken}`)
- .send({friendlyName: newFriendlyName})
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(200);
- var rs = JSON.parse(res.text);
- rs.should.have.properties('accessKey');
- rs.accessKey.should.have.properties(['name', 'createdTime', 'createdBy',
- 'expires', 'isSession', 'description', 'friendlyName']);
- done();
- });
- });
- });
-
describe('delete accessKeys', function(done) {
it('should delete accessKeys successful', function(done) {
request.delete(`/accessKeys/${encodeURI(newFriendlyName)}`)
diff --git a/test/api/account/account.test.js b/test/api/account/account.test.js
index 83ebb2ce..62e262a3 100644
--- a/test/api/account/account.test.js
+++ b/test/api/account/account.test.js
@@ -35,7 +35,7 @@ describe('api/account/account.test.js', function() {
res.status.should.equal(200);
var rs = JSON.parse(res.text);
rs.should.have.properties('account');
- rs.account.should.have.properties(['email', 'id', 'linkedProviders', 'name']);
+ rs.account.should.have.properties(['email', 'linkedProviders', 'name']);
done();
});
});
diff --git a/test/api/apps/apps.test.js b/test/api/apps/apps.test.js
index 4628b7a9..d393a42f 100644
--- a/test/api/apps/apps.test.js
+++ b/test/api/apps/apps.test.js
@@ -13,8 +13,8 @@ describe('api/apps/apps.test.js', function() {
var authToken;
var machineName = `Login-${Math.random()}`;
var friendlyName = `Login-${Math.random()}`;
- var appName = 'test-ios';
- var newAppName = 'newtest-ios';
+ var appName = 'test';
+ var newAppName = 'newtest';
var bearerToken;
var testDeployment = 'test';
var newTestDeployment = 'newtest';
@@ -38,14 +38,14 @@ describe('api/apps/apps.test.js', function() {
it('should create accessKeys successful', function(done) {
request.post(`/accessKeys`)
.set('Authorization', `Basic ${authToken}`)
- .send({createdBy: machineName, friendlyName: friendlyName, isSession: true, ttl: 30*24*60*60})
+ .send({createdBy: machineName, friendlyName: friendlyName, ttl: 30*24*60*60})
.end(function(err, res) {
should.not.exist(err);
res.status.should.equal(200);
var rs = JSON.parse(res.text);
rs.should.have.properties('accessKey');
rs.accessKey.should.have.properties(['name', 'createdTime', 'createdBy',
- 'expires', 'isSession', 'description', 'friendlyName']);
+ 'expires', 'description', 'friendlyName']);
bearerToken = _.get(rs, 'accessKey.name');
done();
});
@@ -65,23 +65,10 @@ describe('api/apps/apps.test.js', function() {
});
});
- it('should not add apps successful when appName is invalid', function(done) {
- var appName = 'test';
- request.post(`/apps`)
- .set('Authorization', `Bearer ${bearerToken}`)
- .send({name: appName})
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(406);
- res.text.should.equal(`appName have to point -android or -ios suffix! eg. ${appName}-android ${appName}-ios`);
- done();
- });
- });
-
it('should add apps successful', function(done) {
request.post(`/apps`)
.set('Authorization', `Bearer ${bearerToken}`)
- .send({name: appName})
+ .send({name: appName, os:'iOS', platform:'React-Native'})
.end(function(err, res) {
should.not.exist(err);
res.status.should.equal(200);
@@ -95,7 +82,7 @@ describe('api/apps/apps.test.js', function() {
it('should not add apps successful when appName exists', function(done) {
request.post(`/apps`)
.set('Authorization', `Bearer ${bearerToken}`)
- .send({name: appName})
+ .send({name: appName, os:'iOS', platform:'React-Native'})
.end(function(err, res) {
should.not.exist(err);
res.status.should.equal(406);
@@ -429,19 +416,6 @@ describe('api/apps/apps.test.js', function() {
});
});
- it(`should not rename apps successful when new name invalid`, function(done) {
- var newAppName = 'newtest';
- request.patch(`/apps/${appName}`)
- .set('Authorization', `Bearer ${bearerToken}`)
- .send({name: newAppName})
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(406);
- res.text.should.equal(`new appName have to point -ios suffix! eg. Demo-ios`);
- done();
- });
- });
-
it(`should rename apps successful`, function(done) {
request.patch(`/apps/${appName}`)
.set('Authorization', `Bearer ${bearerToken}`)
diff --git a/test/api/apps/release.test.js b/test/api/apps/release.test.js
index dd6bdd9c..00eb698d 100644
--- a/test/api/apps/release.test.js
+++ b/test/api/apps/release.test.js
@@ -35,14 +35,14 @@ describe('api/apps/release.test.js', function() {
it('should create accessKeys successful', function(done) {
request.post(`/accessKeys`)
.set('Authorization', `Basic ${authToken}`)
- .send({createdBy: machineName, friendlyName: friendlyName, isSession: true, ttl: 30*24*60*60})
+ .send({createdBy: machineName, friendlyName: friendlyName, ttl: 30*24*60*60})
.end(function(err, res) {
should.not.exist(err);
res.status.should.equal(200);
var rs = JSON.parse(res.text);
rs.should.have.properties('accessKey');
rs.accessKey.should.have.properties(['name', 'createdTime', 'createdBy',
- 'expires', 'isSession', 'description', 'friendlyName']);
+ 'expires', 'description', 'friendlyName']);
bearerToken = _.get(rs, 'accessKey.name');
done();
});
@@ -53,7 +53,7 @@ describe('api/apps/release.test.js', function() {
it('should add apps successful', function(done) {
request.post(`/apps`)
.set('Authorization', `Bearer ${bearerToken}`)
- .send({name: appName})
+ .send({name: appName, os:'iOS', platform:'React-Native'})
.end(function(err, res) {
should.not.exist(err);
res.status.should.equal(200);
@@ -99,7 +99,6 @@ describe('api/apps/release.test.js', function() {
.set('Authorization', `Bearer ${bearerToken}`)
.send()
.end(function(err, res) {
- should.not.exist(err);
res.status.should.equal(200);
done();
});
diff --git a/test/api/auth/auth.test.js b/test/api/auth/auth.test.js
index 06311a30..0263639a 100644
--- a/test/api/auth/auth.test.js
+++ b/test/api/auth/auth.test.js
@@ -10,7 +10,7 @@ describe('api/auth/test.js', function() {
describe('sign in view', function(done) {
it('should show sign in redirect view successful', function(done) {
- _.set(config, 'common.codePushWebUrl', 'http://localhost:3001')
+ _.set(config, 'common.codePushWebUrl', 'http://127.0.0.1:3001')
request.get('/auth/login')
.send()
.end(function(err, res) {
@@ -34,7 +34,7 @@ describe('api/auth/test.js', function() {
describe('sign up view', function(done) {
it('should show sign up redirect view successful', function(done) {
- _.set(config, 'common.codePushWebUrl', 'http://localhost:3001')
+ _.set(config, 'common.codePushWebUrl', 'http://127.0.0.1:3001')
request.get('/auth/register')
.send()
.end(function(err, res) {
diff --git a/test/api/index/index.test.js b/test/api/index/index.test.js
index 0f8576a6..51f51289 100644
--- a/test/api/index/index.test.js
+++ b/test/api/index/index.test.js
@@ -117,7 +117,7 @@ describe('api/index/index.test.js', function() {
.end(function(err, res) {
should.not.exist(err);
res.status.should.equal(404);
- res.text.should.equal(`does not found deployment`);
+ res.text.should.equal(`Not found deployment, check deployment key is right.`);
done();
});
});
diff --git a/test/api/init/database.js b/test/api/init/database.js
index 90b7238e..a32c0aea 100644
--- a/test/api/init/database.js
+++ b/test/api/init/database.js
@@ -1,5 +1,5 @@
var config = require('../../../core/config');
-var mysql = require('mysql');
+var mysql = require('mysql2');
var redis = require('redis');
var should = require('should');
var fs = require('fs');
@@ -12,10 +12,11 @@ describe('api/init/database.js', function() {
var connection = mysql.createConnection({
host: config.db.host,
user: config.db.username,
- password: config.db.password
+ password: config.db.password,
+ multipleStatements: true
});
connection.connect();
- connection.query(`CREATE DATABASE IF NOT EXISTS ${config.db.database}`, function(err, rows, fields) {
+ connection.query(`DROP DATABASE IF EXISTS ${config.db.database};CREATE DATABASE IF NOT EXISTS ${config.db.database}`, function(err, rows, fields) {
should.not.exist(err);
done();
});
diff --git a/test/api/sessions/sessions.test.js b/test/api/sessions/sessions.test.js
deleted file mode 100644
index 3e7821a6..00000000
--- a/test/api/sessions/sessions.test.js
+++ /dev/null
@@ -1,58 +0,0 @@
-var app = require('../../../app');
-var request = require('supertest')(app);
-var should = require("should");
-var security = require('../../../core/utils/security');
-var factory = require('../../../core/utils/factory');
-var _ = require('lodash');
-
-describe('api/sessions/sessions.test.js', function() {
- var account = '522539441@qq.com';
- var password = '123456';
- var authToken;
- var friendlyName = 'sessions';
- var machineName = 'tablee.hosts';
- before(function(done){
- request.post('/auth/login')
- .send({
- account: account,
- password: password
- })
- .end(function(err, res) {
- should.not.exist(err);
- var rs = JSON.parse(res.text);
- rs.should.containEql({status:"OK"});
- authToken = (new Buffer(`auth:${_.get(rs, 'results.tokens')}`)).toString('base64');
- done();
- });
- });
-
- describe('create accessKeys', function() {
- it('should create accessKeys successful', function(done) {
- request.post(`/accessKeys`)
- .set('Authorization', `Basic ${authToken}`)
- .send({createdBy: machineName, friendlyName: friendlyName, isSession: true, ttl: 30*24*60*60})
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(200);
- var rs = JSON.parse(res.text);
- rs.should.have.properties('accessKey');
- rs.accessKey.should.have.properties(['name', 'createdTime', 'createdBy',
- 'expires', 'isSession', 'description', 'friendlyName']);
- done();
- });
- });
- });
-
- describe('delete sessions', function() {
- it('should delete sessions successful', function(done) {
- request.delete(`/sessions/${encodeURI(machineName)}`)
- .set('Authorization', `Basic ${authToken}`)
- .send()
- .end(function(err, res) {
- should.not.exist(err);
- res.status.should.equal(200);
- done();
- });
- });
- });
-});
diff --git a/test/api/users/users.test.js b/test/api/users/users.test.js
index 57d87109..2b936dbf 100644
--- a/test/api/users/users.test.js
+++ b/test/api/users/users.test.js
@@ -78,7 +78,8 @@ describe('api/users/users.test.js', function() {
.then(function (t) {
storageToken = t;
done();
- });
+ })
+ .finally(() => client.quit());
});
it('should not check register code successful when email already exists', function(done) {
@@ -131,7 +132,8 @@ describe('api/users/users.test.js', function() {
.then(function (t) {
storageToken = t;
done();
- });
+ })
+ .finally(() => client.quit());
});
it('should not sign up successful when password length invalid', function(done) {
@@ -174,10 +176,13 @@ describe('api/users/users.test.js', function() {
it('should not change password successful when authToken invalid', function(done) {
request.patch(`/users/password`)
+ .set('Authorization', `Basic 11345`)
.send({oldPassword: password, newPassword: newPassword})
.end(function(err, res) {
should.not.exist(err);
- res.status.should.equal(401);
+ var rs = JSON.parse(res.text);
+ res.status.should.equal(200);
+ rs.should.containEql({status:401});
done();
});
});
diff --git a/views/auth/password.pug b/views/auth/password.pug
new file mode 100644
index 00000000..47fa302c
--- /dev/null
+++ b/views/auth/password.pug
@@ -0,0 +1,57 @@
+extends ../layout
+
+block content
+ .container(style="margin-top:30px;")
+ form#form.col-md-5.col-md-offset-3(method="post")
+ .form-group
+ label.sr-only(for="inputEmail") 邮箱地址/用户名
+ input#inputEmail.form-control(type="text" name="account" placeholder="邮箱地址/用户名" required autofocus)
+ .form-group
+ .col-md-10(style="margin-left:-15px;")
+ label.sr-only(for="inputToken") token
+ input#inputToken.form-control(type="text" name="token" placeholder="token" required)
+ .col-md-2
+ a.form-control.btn.btn-link(style="margin-bottom:15px;" target="_blank" href="/auth/login") 获取token
+ .form-group
+ label.sr-only(for="inputPassword") 原密码
+ input#inputPassword.form-control(type="password" name="oldPassword" placeholder="原密码" required)
+ .form-group
+ label.sr-only(for="inputNewPassword") 新密码
+ input#inputNewPassword.form-control(type="password" name="newPassword" placeholder="新密码" required)
+ .form-group
+ a#submitBtn.btn.btn-lg.btn-primary.btn-block 修改密码
+
+block js
+ script().
+ var submit = false;
+ $('#submitBtn').on('click', function () {
+ if (submit) {
+ return ;
+ }
+ var token = $('#inputToken').val();
+ var oldPassword = $('#inputPassword').val();
+ var newPassword = $('#inputNewPassword').val();
+ submit = true;
+ $.ajax({
+ type: 'patch',
+ data: JSON.stringify({oldPassword:oldPassword,newPassword:newPassword}),
+ contentType: 'application/json;charset=utf-8',
+ headers: {
+ Authorization : 'Bearer '+token
+ },
+ url: '/users/password',
+ dataType: 'json',
+ success: function (data) {
+ if (data.status == "OK") {
+ alert("修改成功");
+ location.href = '/auth/login';
+ } else if (data.status == 401) {
+ alert('token invalid');
+ } else {
+ alert(data.message);
+ }
+ submit = false;
+ }
+ });
+ });
+
diff --git a/views/index.pug b/views/index.pug
index c97cb43a..800da9bd 100644
--- a/views/index.pug
+++ b/views/index.pug
@@ -7,4 +7,5 @@ block content
h1(style="text-align: center;")= title
p(style="text-align: center;") Welcome to #{title}
.site-notice
- a.btn.btn-primary(href="/auth/login" type="button") 登录
\ No newline at end of file
+ a.btn.btn-primary(href="/auth/login" type="button") 登录
+ a.btn.btn-primary.col-md-offset-1(href="/auth/password" type="button") 修改密码
\ No newline at end of file
diff --git a/views/layout.pug b/views/layout.pug
index a0ba1682..85d88861 100644
--- a/views/layout.pug
+++ b/views/layout.pug
@@ -2,6 +2,8 @@ doctype html
html
head
title= title
+ meta(name="keywords" content="code-push-server,code-push,react-native,cordova")
+ meta(name="description" content="CodePush service is hotupdate services which adapter react-native-code-push and cordova-plugin-code-push")
link(rel='stylesheet', href='/js/bootstrap-3.3.7/css/bootstrap.min.css')
block css
body
diff --git a/views/tokens.pug b/views/tokens.pug
index 81829751..ad770b56 100644
--- a/views/tokens.pug
+++ b/views/tokens.pug
@@ -53,7 +53,10 @@ block js
$.ajax({
type: 'post',
data: postParams,
- url: '/accessKeys?access_token='+access_token,
+ headers: {
+ Authorization : 'Bearer '+access_token
+ },
+ url: '/accessKeys',
dataType: 'json',
success: function (data) {
submit = false;