mirror of
				https://gitcode.com/github-mirrors/react-native-update-cli.git
				synced 2025-10-31 23:03:11 +08:00 
			
		
		
		
	initial commit
This commit is contained in:
		
							
								
								
									
										14
									
								
								.babelrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.babelrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| { | ||||
|   "plugins": [ | ||||
|     "syntax-object-rest-spread", | ||||
|     "syntax-async-functions", | ||||
|     "transform-es2015-arrow-functions", | ||||
|     "transform-async-to-generator", | ||||
|     "transform-es2015-modules-commonjs", | ||||
|     "transform-es2015-destructuring", | ||||
|     "transform-es2015-spread", | ||||
|     "transform-object-rest-spread", | ||||
|     "transform-es2015-parameters", | ||||
|     "transform-strict-mode" | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										4
									
								
								.prettierrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.prettierrc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| module.exports = { | ||||
|   trailingComma: 'all', | ||||
|   singleQuote: true, | ||||
| }; | ||||
							
								
								
									
										162
									
								
								cli.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								cli.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| { | ||||
|   "useCommand": true, | ||||
|   "defaultCommand": "help", | ||||
|   "commands": { | ||||
|     "help": { | ||||
|     }, | ||||
|  | ||||
|     "login":{ | ||||
|     }, | ||||
|     "logout": { | ||||
|     }, | ||||
|     "me": { | ||||
|     }, | ||||
|  | ||||
|     "createApp": { | ||||
|       "options": { | ||||
|         "name": { | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "platform": { | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "apps": { | ||||
|       "options": { | ||||
|         "platform": { | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "deleteApp": { | ||||
|     }, | ||||
|     "selectApp": { | ||||
|       "options": { | ||||
|         "platform": { | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     "uploadIpa": { | ||||
|     }, | ||||
|     "uploadApk": { | ||||
|     }, | ||||
|     "packages": { | ||||
|       "options": { | ||||
|         "platform": { | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     "publish": { | ||||
|       "options": { | ||||
|         "platform": { | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "name": { | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "description": { | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "metaInfo": { | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "versions": { | ||||
|       "options": { | ||||
|         "platform": { | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     "update": { | ||||
|       "options": { | ||||
|         "platform": { | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "versionId": { | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "packageId": { | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     "build": { | ||||
|       "description": "Bundle javascript and copy assets." | ||||
|     }, | ||||
|     "bundle": { | ||||
|       "description": "Bundle javascript code only.", | ||||
|       "options": { | ||||
|         "dev": { | ||||
|           "default": "false", | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "platform": { | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "bundleName":{ | ||||
|           "default": "index.bundlejs", | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "entryFile": { | ||||
|           "default": "index.js", | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "intermediaDir": { | ||||
|           "default": "build/intermedia/${platform}", | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "output": { | ||||
|           "default": "build/output/${platform}.${time}.ppk", | ||||
|           "hasValue": true | ||||
|         }, | ||||
|         "verbose": { | ||||
|  | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "release": { | ||||
|       "description": "Push builded file to server." | ||||
|     }, | ||||
|     "diff": { | ||||
|       "description": "Create diff patch", | ||||
|       "options": { | ||||
|         "output": { | ||||
|           "default": "build/output/diff", | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "diffFromApk": { | ||||
|       "description": "Create diff patch from a Android package(.apk)", | ||||
|       "options": { | ||||
|         "output": { | ||||
|           "default": "build/output/diff-${time}.apk-patch", | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "diffFromIpa": { | ||||
|       "description": "Create diff patch from a iOS package(.ipa)", | ||||
|       "options": { | ||||
|         "output": { | ||||
|           "default": "build/output/diff-${time}.ipa-patch", | ||||
|           "hasValue": true | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "globalOptions":{ | ||||
|     "no-interactive": { | ||||
|       "default": false | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										4
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/22/16. | ||||
|  */ | ||||
| module.exports = require('./lib'); | ||||
							
								
								
									
										158
									
								
								lib/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								lib/api.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| let query = function () { | ||||
|   var _ref2 = _asyncToGenerator(function* (url, options) { | ||||
|     const resp = yield fetch(url, options); | ||||
|     const json = yield resp.json(); | ||||
|     if (resp.status !== 200) { | ||||
|       throw Object.assign(new Error(json.message || json.error), { status: resp.status }); | ||||
|     } | ||||
|     return json; | ||||
|   }); | ||||
|  | ||||
|   return function query(_x, _x2) { | ||||
|     return _ref2.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let uploadFile = function () { | ||||
|   var _ref3 = _asyncToGenerator(function* (fn) { | ||||
|     var _ref4 = yield exports.post('/upload', {}); | ||||
|  | ||||
|     const url = _ref4.url, | ||||
|           fieldName = _ref4.fieldName, | ||||
|           formData = _ref4.formData; | ||||
|  | ||||
|     let realUrl = url; | ||||
|  | ||||
|     if (!/^https?\:\/\//.test(url)) { | ||||
|       realUrl = host + url; | ||||
|     } | ||||
|  | ||||
|     const fileSize = fs.statSync(fn).size; | ||||
|  | ||||
|     const bar = new _progress2.default('  Uploading [:bar] :percent :etas', { | ||||
|       complete: '=', | ||||
|       incomplete: ' ', | ||||
|       total: fileSize | ||||
|     }); | ||||
|  | ||||
|     const info = yield new Promise(function (resolve, reject) { | ||||
|       formData.file = fs.createReadStream(fn); | ||||
|  | ||||
|       formData.file.on('data', function (data) { | ||||
|         bar.tick(data.length); | ||||
|       }); | ||||
|       _request2.default.post(realUrl, { | ||||
|         formData | ||||
|       }, function (err, resp, body) { | ||||
|         if (err) { | ||||
|           return reject(err); | ||||
|         } | ||||
|         if (resp.statusCode > 299) { | ||||
|           return reject(Object.assign(new Error(body), { status: resp.statusCode })); | ||||
|         } | ||||
|         resolve(JSON.parse(body)); | ||||
|       }); | ||||
|     }); | ||||
|     return info; | ||||
|   }); | ||||
|  | ||||
|   return function uploadFile(_x3) { | ||||
|     return _ref3.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| var _request = require('request'); | ||||
|  | ||||
| var _request2 = _interopRequireDefault(_request); | ||||
|  | ||||
| var _progress = require('progress'); | ||||
|  | ||||
| var _progress2 = _interopRequireDefault(_progress); | ||||
|  | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
|  | ||||
| function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||||
|  | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/13/16. | ||||
|  */ | ||||
|  | ||||
| const fetch = require('isomorphic-fetch'); | ||||
| let host = process.env.PUSHY_REGISTRY || 'https://update.reactnative.cn/api'; | ||||
| const fs = require('fs-extra'); | ||||
|  | ||||
|  | ||||
| let session = undefined; | ||||
| let savedSession = undefined; | ||||
|  | ||||
| exports.loadSession = _asyncToGenerator(function* () { | ||||
|   if (fs.existsSync('.update')) { | ||||
|     try { | ||||
|       exports.replaceSession(JSON.parse(fs.readFileSync('.update', 'utf8'))); | ||||
|       savedSession = session; | ||||
|     } catch (e) { | ||||
|       console.error('Failed to parse file `.update`. Try to remove it manually.'); | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| }); | ||||
|  | ||||
| exports.getSession = function () { | ||||
|   return session; | ||||
| }; | ||||
|  | ||||
| exports.replaceSession = function (newSession) { | ||||
|   session = newSession; | ||||
| }; | ||||
|  | ||||
| exports.saveSession = function () { | ||||
|   // Only save on change. | ||||
|   if (session !== savedSession) { | ||||
|     const current = session; | ||||
|     const data = JSON.stringify(current, null, 4); | ||||
|     fs.writeFileSync('.update', data, 'utf8'); | ||||
|     savedSession = current; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| exports.closeSession = function () { | ||||
|   if (fs.existsSync('.update')) { | ||||
|     fs.unlinkSync('.update'); | ||||
|     savedSession = undefined; | ||||
|   } | ||||
|   session = undefined; | ||||
|   host = process.env.PUSHY_REGISTRY || 'https://update.reactnative.cn'; | ||||
| }; | ||||
|  | ||||
| function queryWithoutBody(method) { | ||||
|   return function (api) { | ||||
|     return query(host + api, { | ||||
|       method, | ||||
|       headers: { | ||||
|         'X-AccessToken': session ? session.token : '' | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| function queryWithBody(method) { | ||||
|   return function (api, body) { | ||||
|     return query(host + api, { | ||||
|       method, | ||||
|       headers: { | ||||
|         'Content-Type': 'application/json', | ||||
|         'X-AccessToken': session ? session.token : '' | ||||
|       }, | ||||
|       body: JSON.stringify(body) | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| exports.get = queryWithoutBody('GET'); | ||||
| exports.post = queryWithBody('POST'); | ||||
| exports.put = queryWithBody('PUT'); | ||||
| exports.doDelete = queryWithBody('DELETE'); | ||||
|  | ||||
| exports.uploadFile = uploadFile; | ||||
							
								
								
									
										188
									
								
								lib/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								lib/app.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.commands = exports.chooseApp = exports.listApp = undefined; | ||||
|  | ||||
| let listApp = exports.listApp = function () { | ||||
|   var _ref = _asyncToGenerator(function* (platform) { | ||||
|     var _ref2 = yield get('/app/list'); | ||||
|  | ||||
|     const data = _ref2.data; | ||||
|  | ||||
|     const list = platform ? data.filter(function (v) { | ||||
|       return v.platform === platform; | ||||
|     }) : data; | ||||
|     for (const app of list) { | ||||
|       console.log(`${app.id}) ${app.name}(${app.platform})`); | ||||
|     } | ||||
|     if (platform) { | ||||
|       console.log(`\nTotal ${list.length} ${platform} apps`); | ||||
|     } else { | ||||
|       console.log(`\nTotal ${list.length} apps`); | ||||
|     } | ||||
|     return list; | ||||
|   }); | ||||
|  | ||||
|   return function listApp(_x) { | ||||
|     return _ref.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let chooseApp = exports.chooseApp = function () { | ||||
|   var _ref3 = _asyncToGenerator(function* (platform) { | ||||
|     const list = yield listApp(platform); | ||||
|  | ||||
|     while (true) { | ||||
|       const id = yield (0, _utils.question)('Enter appId:'); | ||||
|       const app = list.find(function (v) { | ||||
|         return v.id === (id | 0); | ||||
|       }); | ||||
|       if (app) { | ||||
|         return app; | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   return function chooseApp(_x2) { | ||||
|     return _ref3.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| exports.checkPlatform = checkPlatform; | ||||
| exports.getSelectedApp = getSelectedApp; | ||||
|  | ||||
| var _utils = require('./utils'); | ||||
|  | ||||
| var _fsExtra = require('fs-extra'); | ||||
|  | ||||
| var fs = _interopRequireWildcard(_fsExtra); | ||||
|  | ||||
| function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | ||||
|  | ||||
| function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } /** | ||||
|                                                                                                                                                                                                                                                                                                                                                                                                                                                                             * Created by tdzl2003 on 2/13/16. | ||||
|                                                                                                                                                                                                                                                                                                                                                                                                                                                                             */ | ||||
|  | ||||
| var _require = require('./api'); | ||||
|  | ||||
| const post = _require.post, | ||||
|       get = _require.get, | ||||
|       doDelete = _require.doDelete; | ||||
|  | ||||
|  | ||||
| const validPlatforms = { | ||||
|   ios: 1, | ||||
|   android: 1 | ||||
| }; | ||||
|  | ||||
| function checkPlatform(platform) { | ||||
|   if (!validPlatforms[platform]) { | ||||
|     throw new Error(`Invalid platform '${platform}'`); | ||||
|   } | ||||
|   return platform; | ||||
| } | ||||
|  | ||||
| function getSelectedApp(platform) { | ||||
|   checkPlatform(platform); | ||||
|  | ||||
|   if (!fs.existsSync('update.json')) { | ||||
|     throw new Error(`App not selected. run 'pushy selectApp --platform ${platform}' first!`); | ||||
|   } | ||||
|   const updateInfo = JSON.parse(fs.readFileSync('update.json', 'utf8')); | ||||
|   if (!updateInfo[platform]) { | ||||
|     throw new Error(`App not selected. run 'pushy selectApp --platform ${platform}' first!`); | ||||
|   } | ||||
|   return updateInfo[platform]; | ||||
| } | ||||
|  | ||||
| const commands = exports.commands = { | ||||
|   createApp: function () { | ||||
|     var _ref4 = _asyncToGenerator(function* (_ref5) { | ||||
|       let options = _ref5.options; | ||||
|  | ||||
|       const name = options.name || (yield (0, _utils.question)('App Name:')); | ||||
|       const downloadUrl = options.downloadUrl; | ||||
|  | ||||
|       const platform = checkPlatform(options.platform || (yield (0, _utils.question)('Platform(ios/android):'))); | ||||
|  | ||||
|       var _ref6 = yield post('/app/create', { name, platform }); | ||||
|  | ||||
|       const id = _ref6.id; | ||||
|  | ||||
|       console.log(`Created app ${id}`); | ||||
|       yield this.selectApp({ | ||||
|         args: [id], | ||||
|         options: { platform, downloadUrl } | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     return function createApp(_x3) { | ||||
|       return _ref4.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   deleteApp: function () { | ||||
|     var _ref7 = _asyncToGenerator(function* (_ref8) { | ||||
|       let args = _ref8.args, | ||||
|           options = _ref8.options; | ||||
|       const platform = options.platform; | ||||
|  | ||||
|       const id = args[0] || chooseApp(platform); | ||||
|       if (!id) { | ||||
|         console.log('Canceled'); | ||||
|       } | ||||
|       yield doDelete(`/app/${id}`); | ||||
|       console.log('Ok.'); | ||||
|     }); | ||||
|  | ||||
|     return function deleteApp(_x4) { | ||||
|       return _ref7.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   apps: function () { | ||||
|     var _ref9 = _asyncToGenerator(function* (_ref10) { | ||||
|       let options = _ref10.options; | ||||
|       const platform = options.platform; | ||||
|  | ||||
|       listApp(platform); | ||||
|     }); | ||||
|  | ||||
|     return function apps(_x5) { | ||||
|       return _ref9.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   selectApp: function () { | ||||
|     var _ref11 = _asyncToGenerator(function* (_ref12) { | ||||
|       let args = _ref12.args, | ||||
|           options = _ref12.options; | ||||
|  | ||||
|       const platform = checkPlatform(options.platform || (yield (0, _utils.question)('Platform(ios/android):'))); | ||||
|       const id = args[0] || (yield chooseApp(platform)).id; | ||||
|  | ||||
|       let updateInfo = {}; | ||||
|       if (fs.existsSync('update.json')) { | ||||
|         try { | ||||
|           updateInfo = JSON.parse(fs.readFileSync('update.json', 'utf8')); | ||||
|         } catch (e) { | ||||
|           console.error('Failed to parse file `update.json`. Try to remove it manually.'); | ||||
|           throw e; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       var _ref13 = yield get(`/app/${id}`); | ||||
|  | ||||
|       const appKey = _ref13.appKey; | ||||
|  | ||||
|       updateInfo[platform] = { | ||||
|         appId: id, | ||||
|         appKey | ||||
|       }; | ||||
|       fs.writeFileSync('update.json', JSON.stringify(updateInfo, null, 4), 'utf8'); | ||||
|     }); | ||||
|  | ||||
|     return function selectApp(_x6) { | ||||
|       return _ref11.apply(this, arguments); | ||||
|     }; | ||||
|   }() | ||||
| }; | ||||
							
								
								
									
										597
									
								
								lib/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										597
									
								
								lib/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,597 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.commands = undefined; | ||||
|  | ||||
| var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||||
|  | ||||
| var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||||
|  | ||||
| let runReactNativeBundleCommand = function () { | ||||
|   var _ref = _asyncToGenerator(function* (bundleName, development, entryFile, outputFolder, platform, sourcemapOutput, config) { | ||||
|     let reactNativeBundleArgs = []; | ||||
|  | ||||
|     let envArgs = process.env.PUSHY_ENV_ARGS; | ||||
|  | ||||
|     if (envArgs) { | ||||
|       Array.prototype.push.apply(reactNativeBundleArgs, envArgs.trim().split(/\s+/)); | ||||
|     } | ||||
|  | ||||
|     fs.emptyDirSync(outputFolder); | ||||
|  | ||||
|     Array.prototype.push.apply(reactNativeBundleArgs, [path.join("node_modules", "react-native", "local-cli", "cli.js"), "bundle", '--assets-dest', outputFolder, '--bundle-output', path.join(outputFolder, bundleName), '--dev', development, '--entry-file', entryFile, '--platform', platform, '--reset-cache']); | ||||
|  | ||||
|     if (sourcemapOutput) { | ||||
|       reactNativeBundleArgs.push('--sourcemap-output', sourcemapOutput); | ||||
|     } | ||||
|  | ||||
|     if (config) { | ||||
|       reactNativeBundleArgs.push('--config', config); | ||||
|     } | ||||
|  | ||||
|     const reactNativeBundleProcess = spawn('node', reactNativeBundleArgs); | ||||
|     console.log(`Running bundle command: node ${reactNativeBundleArgs.join(' ')}`); | ||||
|  | ||||
|     return new Promise(function (resolve, reject) { | ||||
|       reactNativeBundleProcess.stdout.on('data', function (data) { | ||||
|         console.log(data.toString().trim()); | ||||
|       }); | ||||
|  | ||||
|       reactNativeBundleProcess.stderr.on('data', function (data) { | ||||
|         console.error(data.toString().trim()); | ||||
|       }); | ||||
|  | ||||
|       reactNativeBundleProcess.on('close', function () { | ||||
|         var _ref2 = _asyncToGenerator(function* (exitCode) { | ||||
|           if (exitCode) { | ||||
|             reject(new Error(`"react-native bundle" command exited with code ${exitCode}.`)); | ||||
|           } else { | ||||
|             if (platform === 'android') { | ||||
|               yield compileHermesByteCode(bundleName, outputFolder); | ||||
|             } | ||||
|             resolve(null); | ||||
|           } | ||||
|         }); | ||||
|  | ||||
|         return function (_x8) { | ||||
|           return _ref2.apply(this, arguments); | ||||
|         }; | ||||
|       }()); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   return function runReactNativeBundleCommand(_x, _x2, _x3, _x4, _x5, _x6, _x7) { | ||||
|     return _ref.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let compileHermesByteCode = function () { | ||||
|   var _ref3 = _asyncToGenerator(function* (bundleName, outputFolder) { | ||||
|     let enableHermes = false; | ||||
|     try { | ||||
|       const gradleConfig = yield g2js.parseFile('android/app/build.gradle'); | ||||
|       const projectConfig = gradleConfig['project.ext.react']; | ||||
|       for (const packagerConfig of projectConfig) { | ||||
|         if (packagerConfig.includes('enableHermes') && packagerConfig.includes('true')) { | ||||
|           enableHermes = true; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|     } catch (e) {} | ||||
|     if (enableHermes) { | ||||
|       console.log(`Hermes enabled, now compiling to hermes bytecode:\n`); | ||||
|       const hermesPath = fs.existsSync('node_modules/hermes-engine') ? 'node_modules/hermes-engine' : 'node_modules/hermesvm'; | ||||
|       execSync(`${hermesPath}/${getHermesOSBin()}/hermes -emit-binary -out ${outputFolder}/${bundleName} ${outputFolder}/${bundleName} -O`, { stdio: 'ignore' }); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   return function compileHermesByteCode(_x9, _x10) { | ||||
|     return _ref3.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let pack = function () { | ||||
|   var _ref4 = _asyncToGenerator(function* (dir, output) { | ||||
|     console.log('Packing'); | ||||
|     fs.ensureDirSync(path.dirname(output)); | ||||
|     yield new Promise(function (resolve, reject) { | ||||
|       var zipfile = new _yazl.ZipFile(); | ||||
|  | ||||
|       function addDirectory(root, rel) { | ||||
|         if (rel) { | ||||
|           zipfile.addEmptyDirectory(rel); | ||||
|         } | ||||
|         const childs = fs.readdirSync(root); | ||||
|         for (const name of childs) { | ||||
|           if (name === '.' || name === '..') { | ||||
|             continue; | ||||
|           } | ||||
|           const fullPath = path.join(root, name); | ||||
|           const stat = fs.statSync(fullPath); | ||||
|           if (stat.isFile()) { | ||||
|             //console.log('adding: ' + rel+name); | ||||
|             zipfile.addFile(fullPath, rel + name); | ||||
|           } else if (stat.isDirectory()) { | ||||
|             //console.log('adding: ' + rel+name+'/'); | ||||
|             addDirectory(fullPath, rel + name + '/'); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       addDirectory(dir, ''); | ||||
|  | ||||
|       zipfile.outputStream.on('error', function (err) { | ||||
|         return reject(err); | ||||
|       }); | ||||
|       zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function () { | ||||
|         resolve(); | ||||
|       }); | ||||
|       zipfile.end(); | ||||
|     }); | ||||
|     console.log('Bundled saved to: ' + output); | ||||
|   }); | ||||
|  | ||||
|   return function pack(_x11, _x12) { | ||||
|     return _ref4.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let diffFromPPK = function () { | ||||
|   var _ref5 = _asyncToGenerator(function* (origin, next, output) { | ||||
|     fs.ensureDirSync(path.dirname(output)); | ||||
|  | ||||
|     const originEntries = {}; | ||||
|     const originMap = {}; | ||||
|  | ||||
|     let originSource; | ||||
|  | ||||
|     yield enumZipEntries(origin, function (entry, zipFile) { | ||||
|       originEntries[entry.fileName] = entry; | ||||
|       if (!/\/$/.test(entry.fileName)) { | ||||
|         // isFile | ||||
|         originMap[entry.crc32] = entry.fileName; | ||||
|  | ||||
|         if (entry.fileName === 'index.bundlejs') { | ||||
|           // This is source. | ||||
|           return readEntire(entry, zipFile).then(function (v) { | ||||
|             return originSource = v; | ||||
|           }); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     originSource = originSource || new Buffer(0); | ||||
|  | ||||
|     const copies = {}; | ||||
|  | ||||
|     var zipfile = new _yazl.ZipFile(); | ||||
|  | ||||
|     const writePromise = new Promise(function (resolve, reject) { | ||||
|       zipfile.outputStream.on('error', function (err) { | ||||
|         throw err; | ||||
|       }); | ||||
|       zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function () { | ||||
|         resolve(); | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     const addedEntry = {}; | ||||
|  | ||||
|     function addEntry(fn) { | ||||
|       //console.log(fn); | ||||
|       if (!fn || addedEntry[fn]) { | ||||
|         return; | ||||
|       } | ||||
|       const base = basename(fn); | ||||
|       if (base) { | ||||
|         addEntry(base); | ||||
|       } | ||||
|       zipfile.addEmptyDirectory(fn); | ||||
|     } | ||||
|  | ||||
|     const newEntries = {}; | ||||
|  | ||||
|     yield enumZipEntries(next, function (entry, nextZipfile) { | ||||
|       newEntries[entry.fileName] = entry; | ||||
|  | ||||
|       if (/\/$/.test(entry.fileName)) { | ||||
|         // Directory | ||||
|         if (!originEntries[entry.fileName]) { | ||||
|           addEntry(entry.fileName); | ||||
|         } | ||||
|       } else if (entry.fileName === 'index.bundlejs') { | ||||
|         //console.log('Found bundle'); | ||||
|         return readEntire(entry, nextZipfile).then(function (newSource) { | ||||
|           //console.log('Begin diff'); | ||||
|           zipfile.addBuffer(diff(originSource, newSource), 'index.bundlejs.patch'); | ||||
|           //console.log('End diff'); | ||||
|         }); | ||||
|       } else { | ||||
|         // If same file. | ||||
|         const originEntry = originEntries[entry.fileName]; | ||||
|         if (originEntry && originEntry.crc32 === entry.crc32) { | ||||
|           // ignore | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|         // If moved from other place | ||||
|         if (originMap[entry.crc32]) { | ||||
|           const base = basename(entry.fileName); | ||||
|           if (!originEntries[base]) { | ||||
|             addEntry(base); | ||||
|           } | ||||
|           copies[entry.fileName] = originMap[entry.crc32]; | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|         // New file. | ||||
|         addEntry(basename(entry.fileName)); | ||||
|  | ||||
|         return new Promise(function (resolve, reject) { | ||||
|           nextZipfile.openReadStream(entry, function (err, readStream) { | ||||
|             if (err) { | ||||
|               return reject(err); | ||||
|             } | ||||
|             zipfile.addReadStream(readStream, entry.fileName); | ||||
|             readStream.on('end', function () { | ||||
|               //console.log('add finished'); | ||||
|               resolve(); | ||||
|             }); | ||||
|           }); | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     const deletes = {}; | ||||
|  | ||||
|     for (var k in originEntries) { | ||||
|       if (!newEntries[k]) { | ||||
|         console.log('Delete ' + k); | ||||
|         deletes[k] = 1; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     //console.log({copies, deletes}); | ||||
|     zipfile.addBuffer(new Buffer(JSON.stringify({ copies, deletes })), '__diff.json'); | ||||
|     zipfile.end(); | ||||
|     yield writePromise; | ||||
|   }); | ||||
|  | ||||
|   return function diffFromPPK(_x13, _x14, _x15) { | ||||
|     return _ref5.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let diffFromPackage = function () { | ||||
|   var _ref6 = _asyncToGenerator(function* (origin, next, output, originBundleName) { | ||||
|     let transformPackagePath = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : function (v) { | ||||
|       return v; | ||||
|     }; | ||||
|  | ||||
|     fs.ensureDirSync(path.dirname(output)); | ||||
|  | ||||
|     const originEntries = {}; | ||||
|     const originMap = {}; | ||||
|  | ||||
|     let originSource; | ||||
|  | ||||
|     yield enumZipEntries(origin, function (entry, zipFile) { | ||||
|       if (!/\/$/.test(entry.fileName)) { | ||||
|         const fn = transformPackagePath(entry.fileName); | ||||
|         if (!fn) { | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|         //console.log(fn); | ||||
|         // isFile | ||||
|         originEntries[fn] = entry.crc32; | ||||
|         originMap[entry.crc32] = fn; | ||||
|  | ||||
|         if (fn === originBundleName) { | ||||
|           // This is source. | ||||
|           return readEntire(entry, zipFile).then(function (v) { | ||||
|             return originSource = v; | ||||
|           }); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     originSource = originSource || new Buffer(0); | ||||
|  | ||||
|     const copies = {}; | ||||
|  | ||||
|     var zipfile = new _yazl.ZipFile(); | ||||
|  | ||||
|     const writePromise = new Promise(function (resolve, reject) { | ||||
|       zipfile.outputStream.on('error', function (err) { | ||||
|         throw err; | ||||
|       }); | ||||
|       zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function () { | ||||
|         resolve(); | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     yield enumZipEntries(next, function (entry, nextZipfile) { | ||||
|       if (/\/$/.test(entry.fileName)) { | ||||
|         // Directory | ||||
|         zipfile.addEmptyDirectory(entry.fileName); | ||||
|       } else if (entry.fileName === 'index.bundlejs') { | ||||
|         //console.log('Found bundle'); | ||||
|         return readEntire(entry, nextZipfile).then(function (newSource) { | ||||
|           //console.log('Begin diff'); | ||||
|           zipfile.addBuffer(diff(originSource, newSource), 'index.bundlejs.patch'); | ||||
|           //console.log('End diff'); | ||||
|         }); | ||||
|       } else { | ||||
|         // If same file. | ||||
|         if (originEntries[entry.fileName] === entry.crc32) { | ||||
|           copies[entry.fileName] = ''; | ||||
|           return; | ||||
|         } | ||||
|         // If moved from other place | ||||
|         if (originMap[entry.crc32]) { | ||||
|           copies[entry.fileName] = originMap[entry.crc32]; | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|         return new Promise(function (resolve, reject) { | ||||
|           nextZipfile.openReadStream(entry, function (err, readStream) { | ||||
|             if (err) { | ||||
|               return reject(err); | ||||
|             } | ||||
|             zipfile.addReadStream(readStream, entry.fileName); | ||||
|             readStream.on('end', function () { | ||||
|               //console.log('add finished'); | ||||
|               resolve(); | ||||
|             }); | ||||
|           }); | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     zipfile.addBuffer(new Buffer(JSON.stringify({ copies })), '__diff.json'); | ||||
|     zipfile.end(); | ||||
|     yield writePromise; | ||||
|   }); | ||||
|  | ||||
|   return function diffFromPackage(_x16, _x17, _x18, _x19) { | ||||
|     return _ref6.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| var _utils = require('./utils'); | ||||
|  | ||||
| var _fsExtra = require('fs-extra'); | ||||
|  | ||||
| var fs = _interopRequireWildcard(_fsExtra); | ||||
|  | ||||
| var _yazl = require('yazl'); | ||||
|  | ||||
| var _yauzl = require('yauzl'); | ||||
|  | ||||
| var _app = require('./app'); | ||||
|  | ||||
| function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | ||||
|  | ||||
| function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||||
|  | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/22/16. | ||||
|  */ | ||||
|  | ||||
| const path = require('path'); | ||||
|  | ||||
| var _require = require('child_process'); | ||||
|  | ||||
| const spawn = _require.spawn, | ||||
|       spawnSync = _require.spawnSync, | ||||
|       execSync = _require.execSync; | ||||
|  | ||||
| const g2js = require('gradle-to-js/lib/parser'); | ||||
| const os = require('os'); | ||||
|  | ||||
| var diff; | ||||
| try { | ||||
|   var bsdiff = require('node-bsdiff'); | ||||
|   diff = typeof bsdiff != 'function' ? bsdiff.diff : bsdiff; | ||||
| } catch (e) { | ||||
|   diff = function () { | ||||
|     console.warn('This function needs "node-bsdiff". Please run "npm i node-bsdiff" from your project directory first!'); | ||||
|     throw new Error('This function needs module "node-bsdiff". Please install it first.'); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| function exec(command) { | ||||
|   const commandResult = spawnSync(command, { | ||||
|     shell: true, | ||||
|     stdio: 'inherit' | ||||
|   }); | ||||
|   if (commandResult.error) { | ||||
|     throw commandResult.error; | ||||
|   } | ||||
| } | ||||
|  | ||||
| function getHermesOSBin() { | ||||
|   if (os.platform() === 'win32') return 'win64-bin'; | ||||
|   if (os.platform() === 'darwin') return 'osx-bin'; | ||||
|   if (os.platform() === 'linux') return 'linux64-bin'; | ||||
| } | ||||
|  | ||||
| function readEntire(entry, zipFile) { | ||||
|   const buffers = []; | ||||
|   return new Promise(function (resolve, reject) { | ||||
|     zipFile.openReadStream(entry, function (err, stream) { | ||||
|       stream.pipe({ | ||||
|         write(chunk) { | ||||
|           buffers.push(chunk); | ||||
|         }, | ||||
|         end() { | ||||
|           resolve(Buffer.concat(buffers)); | ||||
|         }, | ||||
|         prependListener() {}, | ||||
|         on() {}, | ||||
|         once() {}, | ||||
|         emit() {} | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| function basename(fn) { | ||||
|   const m = /^(.+\/)[^\/]+\/?$/.exec(fn); | ||||
|   return m && m[1]; | ||||
| } | ||||
|  | ||||
| function enumZipEntries(zipFn, callback) { | ||||
|   return new Promise(function (resolve, reject) { | ||||
|     (0, _yauzl.open)(zipFn, { lazyEntries: true }, function (err, zipfile) { | ||||
|       if (err) { | ||||
|         return reject(err); | ||||
|       } | ||||
|       zipfile.on('end', resolve); | ||||
|       zipfile.on('error', reject); | ||||
|       zipfile.on('entry', function (entry) { | ||||
|         const result = callback(entry, zipfile); | ||||
|         if (result && typeof result.then === 'function') { | ||||
|           result.then(function () { | ||||
|             return zipfile.readEntry(); | ||||
|           }); | ||||
|         } else { | ||||
|           zipfile.readEntry(); | ||||
|         } | ||||
|       }); | ||||
|       zipfile.readEntry(); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| const commands = exports.commands = { | ||||
|   bundle: function () { | ||||
|     var _ref7 = _asyncToGenerator(function* (_ref8) { | ||||
|       let options = _ref8.options; | ||||
|  | ||||
|       const platform = (0, _app.checkPlatform)(options.platform || (yield (0, _utils.question)('Platform(ios/android):'))); | ||||
|  | ||||
|       var _translateOptions = (0, _utils.translateOptions)(_extends({}, options, { | ||||
|         platform | ||||
|       })); | ||||
|  | ||||
|       let bundleName = _translateOptions.bundleName, | ||||
|           entryFile = _translateOptions.entryFile, | ||||
|           intermediaDir = _translateOptions.intermediaDir, | ||||
|           output = _translateOptions.output, | ||||
|           dev = _translateOptions.dev, | ||||
|           verbose = _translateOptions.verbose; | ||||
|  | ||||
|       // const sourcemapOutput = path.join(intermediaDir, bundleName + ".map"); | ||||
|  | ||||
|       const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); | ||||
|  | ||||
|       if (!platform) { | ||||
|         throw new Error('Platform must be specified.'); | ||||
|       } | ||||
|  | ||||
|       var _getRNVersion = (0, _utils.getRNVersion)(); | ||||
|  | ||||
|       const version = _getRNVersion.version, | ||||
|             major = _getRNVersion.major, | ||||
|             minor = _getRNVersion.minor; | ||||
|  | ||||
|  | ||||
|       console.log('Bundling with React Native version: ', version); | ||||
|  | ||||
|       yield runReactNativeBundleCommand(bundleName, dev, entryFile, intermediaDir, platform); | ||||
|  | ||||
|       yield pack(path.resolve(intermediaDir), realOutput); | ||||
|  | ||||
|       const v = yield (0, _utils.question)('Would you like to publish it?(Y/N)'); | ||||
|       if (v.toLowerCase() === 'y') { | ||||
|         yield this.publish({ | ||||
|           args: [realOutput], | ||||
|           options: { | ||||
|             platform | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     return function bundle(_x21) { | ||||
|       return _ref7.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|  | ||||
|   diff(_ref9) { | ||||
|     let args = _ref9.args, | ||||
|         options = _ref9.options; | ||||
|     return _asyncToGenerator(function* () { | ||||
|       var _args = _slicedToArray(args, 2); | ||||
|  | ||||
|       const origin = _args[0], | ||||
|             next = _args[1]; | ||||
|       const output = options.output; | ||||
|  | ||||
|  | ||||
|       const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); | ||||
|  | ||||
|       if (!origin || !next) { | ||||
|         console.error('pushy diff <origin> <next>'); | ||||
|         process.exit(1); | ||||
|       } | ||||
|  | ||||
|       yield diffFromPPK(origin, next, realOutput, 'index.bundlejs'); | ||||
|       console.log(`${realOutput} generated.`); | ||||
|     })(); | ||||
|   }, | ||||
|  | ||||
|   diffFromApk(_ref10) { | ||||
|     let args = _ref10.args, | ||||
|         options = _ref10.options; | ||||
|     return _asyncToGenerator(function* () { | ||||
|       var _args2 = _slicedToArray(args, 2); | ||||
|  | ||||
|       const origin = _args2[0], | ||||
|             next = _args2[1]; | ||||
|       const output = options.output; | ||||
|  | ||||
|  | ||||
|       const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); | ||||
|  | ||||
|       if (!origin || !next) { | ||||
|         console.error('pushy diffFromApk <origin> <next>'); | ||||
|         process.exit(1); | ||||
|       } | ||||
|  | ||||
|       yield diffFromPackage(origin, next, realOutput, 'assets/index.android.bundle'); | ||||
|       console.log(`${realOutput} generated.`); | ||||
|     })(); | ||||
|   }, | ||||
|  | ||||
|   diffFromIpa(_ref11) { | ||||
|     let args = _ref11.args, | ||||
|         options = _ref11.options; | ||||
|     return _asyncToGenerator(function* () { | ||||
|       var _args3 = _slicedToArray(args, 2); | ||||
|  | ||||
|       const origin = _args3[0], | ||||
|             next = _args3[1]; | ||||
|       const output = options.output; | ||||
|  | ||||
|  | ||||
|       const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); | ||||
|  | ||||
|       if (!origin || !next) { | ||||
|         console.error('pushy diffFromIpa <origin> <next>'); | ||||
|         process.exit(1); | ||||
|       } | ||||
|  | ||||
|       yield diffFromPackage(origin, next, realOutput, 'main.jsbundle', function (v) { | ||||
|         const m = /^Payload\/[^/]+\/(.+)$/.exec(v); | ||||
|         return m && m[1]; | ||||
|       }); | ||||
|  | ||||
|       console.log(`${realOutput} generated.`); | ||||
|     })(); | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										65
									
								
								lib/index.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										65
									
								
								lib/index.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| #!/usr/bin/env node | ||||
| 'use strict'; | ||||
|  | ||||
| var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||||
|  | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/13/16. | ||||
|  */ | ||||
|  | ||||
| var _require = require('./api'); | ||||
|  | ||||
| const loadSession = _require.loadSession; | ||||
|  | ||||
| const updateNotifier = require('update-notifier'); | ||||
| const pkg = require('../package.json'); | ||||
|  | ||||
| updateNotifier({ pkg }).notify(); | ||||
|  | ||||
| function printUsage(_ref) { | ||||
|   let args = _ref.args; | ||||
|  | ||||
|   // const commandName = args[0]; | ||||
|   // TODO: print usage of commandName, or print global usage. | ||||
|  | ||||
|   console.log('Usage is under development now.'); | ||||
|   console.log('Visit `https://github.com/reactnativecn/react-native-pushy` for early document.'); | ||||
|   process.exit(1); | ||||
| } | ||||
|  | ||||
| function printVersionCommand() { | ||||
|   if (process.argv.indexOf('-v') >= 0 || process.argv[2] === 'version') { | ||||
|     console.log('react-native-update-cli: ' + pkg.version); | ||||
|     try { | ||||
|       const PACKAGE_JSON_PATH = path.resolve(process.cwd(), 'node_modules', 'react-native-update', 'package.json'); | ||||
|       console.log('react-native-update: ' + require(PACKAGE_JSON_PATH).version); | ||||
|     } catch (e) { | ||||
|       console.log('react-native-update: n/a - not inside a React Native project directory'); | ||||
|     } | ||||
|     process.exit(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| const commands = _extends({}, require('./user').commands, require('./bundle').commands, require('./app').commands, require('./package').commands, require('./versions').commands, { | ||||
|   help: printUsage | ||||
| }); | ||||
|  | ||||
| function run() { | ||||
|   printVersionCommand(); | ||||
|  | ||||
|   const argv = require('cli-arguments').parse(require('../cli.json')); | ||||
|   global.NO_INTERACTIVE = argv.options['no-interactive']; | ||||
|  | ||||
|   loadSession().then(function () { | ||||
|     return commands[argv.command](argv); | ||||
|   }).catch(function (err) { | ||||
|     if (err.status === 401) { | ||||
|       console.log('Not loggined.\nRun `pushy login` at your project directory to login.'); | ||||
|       return; | ||||
|     } | ||||
|     console.error(err.stack); | ||||
|     process.exit(-1); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| run(); | ||||
							
								
								
									
										170
									
								
								lib/package.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								lib/package.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.commands = exports.choosePackage = exports.listPackage = undefined; | ||||
|  | ||||
| let listPackage = exports.listPackage = function () { | ||||
|   var _ref = _asyncToGenerator(function* (appId) { | ||||
|     var _ref2 = yield get(`/app/${appId}/package/list?limit=1000`); | ||||
|  | ||||
|     const data = _ref2.data; | ||||
|  | ||||
|  | ||||
|     const header = [{ value: 'Package Id' }, { value: 'Version' }]; | ||||
|     const rows = []; | ||||
|     for (const pkg of data) { | ||||
|       const version = pkg.version; | ||||
|  | ||||
|       let versionInfo = ''; | ||||
|       if (version) { | ||||
|         versionInfo = ` - ${version.id} ${version.hash.slice(0, 8)} ${version.name}`; | ||||
|       } else { | ||||
|         versionInfo = ' (newest)'; | ||||
|       } | ||||
|  | ||||
|       rows.push([pkg.id, `${pkg.name}(${pkg.status})${versionInfo}`]); | ||||
|     } | ||||
|  | ||||
|     console.log(Table(header, rows).render()); | ||||
|     console.log(`\nTotal ${data.length} package(s).`); | ||||
|     return data; | ||||
|   }); | ||||
|  | ||||
|   return function listPackage(_x) { | ||||
|     return _ref.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let choosePackage = exports.choosePackage = function () { | ||||
|   var _ref3 = _asyncToGenerator(function* (appId) { | ||||
|     const list = yield listPackage(appId); | ||||
|  | ||||
|     while (true) { | ||||
|       const id = yield (0, _utils.question)('Enter Package Id:'); | ||||
|       const app = list.find(function (v) { | ||||
|         return v.id === (id | 0); | ||||
|       }); | ||||
|       if (app) { | ||||
|         return app; | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   return function choosePackage(_x2) { | ||||
|     return _ref3.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| var _utils = require('./utils'); | ||||
|  | ||||
| var _app = require('./app'); | ||||
|  | ||||
| function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||||
|  | ||||
| /** | ||||
|  * Created by tdzl2003 on 4/2/16. | ||||
|  */ | ||||
|  | ||||
| var _require = require('./api'); | ||||
|  | ||||
| const get = _require.get, | ||||
|       post = _require.post, | ||||
|       uploadFile = _require.uploadFile; | ||||
|  | ||||
| const Table = require('tty-table'); | ||||
|  | ||||
| const commands = exports.commands = { | ||||
|   uploadIpa: function () { | ||||
|     var _ref4 = _asyncToGenerator(function* (_ref5) { | ||||
|       let args = _ref5.args; | ||||
|  | ||||
|       const fn = args[0]; | ||||
|       if (!fn) { | ||||
|         throw new Error('Usage: pushy uploadIpa <ipaFile>'); | ||||
|       } | ||||
|  | ||||
|       var _ref6 = yield (0, _utils.getIpaInfo)(fn); | ||||
|  | ||||
|       const versionName = _ref6.versionName, | ||||
|             buildTime = _ref6.buildTime; | ||||
|  | ||||
|       var _ref7 = yield (0, _app.getSelectedApp)('ios'); | ||||
|  | ||||
|       const appId = _ref7.appId; | ||||
|  | ||||
|       var _ref8 = yield uploadFile(fn); | ||||
|  | ||||
|       const hash = _ref8.hash; | ||||
|  | ||||
|       var _ref9 = yield post(`/app/${appId}/package/create`, { | ||||
|         name: versionName, | ||||
|         hash, | ||||
|         buildTime | ||||
|       }); | ||||
|  | ||||
|       const id = _ref9.id; | ||||
|  | ||||
|       console.log(`Ipa uploaded: ${id}`); | ||||
|     }); | ||||
|  | ||||
|     return function uploadIpa(_x3) { | ||||
|       return _ref4.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   uploadApk: function () { | ||||
|     var _ref10 = _asyncToGenerator(function* (_ref11) { | ||||
|       let args = _ref11.args; | ||||
|  | ||||
|       const fn = args[0]; | ||||
|       if (!fn) { | ||||
|         throw new Error('Usage: pushy uploadApk <apkFile>'); | ||||
|       } | ||||
|  | ||||
|       var _ref12 = yield (0, _utils.getApkInfo)(fn); | ||||
|  | ||||
|       const versionName = _ref12.versionName, | ||||
|             buildTime = _ref12.buildTime; | ||||
|  | ||||
|       var _ref13 = yield (0, _app.getSelectedApp)('android'); | ||||
|  | ||||
|       const appId = _ref13.appId; | ||||
|  | ||||
|       var _ref14 = yield uploadFile(fn); | ||||
|  | ||||
|       const hash = _ref14.hash; | ||||
|  | ||||
|       var _ref15 = yield post(`/app/${appId}/package/create`, { | ||||
|         name: versionName, | ||||
|         hash, | ||||
|         buildTime | ||||
|       }); | ||||
|  | ||||
|       const id = _ref15.id; | ||||
|  | ||||
|       console.log(`Apk uploaded: ${id}`); | ||||
|     }); | ||||
|  | ||||
|     return function uploadApk(_x4) { | ||||
|       return _ref10.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   packages: function () { | ||||
|     var _ref16 = _asyncToGenerator(function* (_ref17) { | ||||
|       let options = _ref17.options; | ||||
|  | ||||
|       const platform = (0, _app.checkPlatform)(options.platform || (yield (0, _utils.question)('Platform(ios/android):'))); | ||||
|  | ||||
|       var _ref18 = yield (0, _app.getSelectedApp)(platform); | ||||
|  | ||||
|       const appId = _ref18.appId; | ||||
|  | ||||
|       yield listPackage(appId); | ||||
|     }); | ||||
|  | ||||
|     return function packages(_x5) { | ||||
|       return _ref16.apply(this, arguments); | ||||
|     }; | ||||
|   }() | ||||
| }; | ||||
							
								
								
									
										72
									
								
								lib/user.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								lib/user.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var _utils = require('./utils'); | ||||
|  | ||||
| function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } /** | ||||
|                                                                                                                                                                                                                                                                                                                                                                                                                                                                             * Created by tdzl2003 on 2/13/16. | ||||
|                                                                                                                                                                                                                                                                                                                                                                                                                                                                             */ | ||||
|  | ||||
| var _require = require('./api'); | ||||
|  | ||||
| const post = _require.post, | ||||
|       get = _require.get, | ||||
|       replaceSession = _require.replaceSession, | ||||
|       saveSession = _require.saveSession, | ||||
|       closeSession = _require.closeSession; | ||||
|  | ||||
| const crypto = require('crypto'); | ||||
|  | ||||
| function md5(str) { | ||||
|   return crypto.createHash('md5').update(str).digest('hex'); | ||||
| } | ||||
|  | ||||
| exports.commands = { | ||||
|   login: function () { | ||||
|     var _ref = _asyncToGenerator(function* (_ref2) { | ||||
|       let args = _ref2.args; | ||||
|  | ||||
|       const email = args[0] || (yield (0, _utils.question)('email:')); | ||||
|       const pwd = args[1] || (yield (0, _utils.question)('password:', true)); | ||||
|  | ||||
|       var _ref3 = yield post('/user/login', { | ||||
|         email, | ||||
|         pwd: md5(pwd) | ||||
|       }); | ||||
|  | ||||
|       const token = _ref3.token, | ||||
|             info = _ref3.info; | ||||
|  | ||||
|       replaceSession({ token }); | ||||
|       yield saveSession(); | ||||
|       console.log(`Welcome, ${info.name}.`); | ||||
|     }); | ||||
|  | ||||
|     return function login(_x) { | ||||
|       return _ref.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   logout: function () { | ||||
|     var _ref4 = _asyncToGenerator(function* () { | ||||
|       yield closeSession(); | ||||
|       console.log('Logged out.'); | ||||
|     }); | ||||
|  | ||||
|     return function logout() { | ||||
|       return _ref4.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   me: function () { | ||||
|     var _ref5 = _asyncToGenerator(function* () { | ||||
|       const me = yield get('/user/me'); | ||||
|       for (const k in me) { | ||||
|         if (k !== 'ok') { | ||||
|           console.log(`${k}: ${me[k]}`); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     return function me() { | ||||
|       return _ref5.apply(this, arguments); | ||||
|     }; | ||||
|   }() | ||||
| }; | ||||
							
								
								
									
										123
									
								
								lib/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								lib/utils/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.getIpaInfo = exports.getApkInfo = undefined; | ||||
|  | ||||
| let getApkInfo = exports.getApkInfo = function () { | ||||
|   var _ref = _asyncToGenerator(function* (fn) { | ||||
|     const appInfoParser = new AppInfoParser(fn); | ||||
|  | ||||
|     var _ref2 = yield appInfoParser.parse(); | ||||
|  | ||||
|     const versionName = _ref2.versionName, | ||||
|           application = _ref2.application; | ||||
|  | ||||
|     let buildTime = 0; | ||||
|     if (Array.isArray(application.metaData)) { | ||||
|       for (const meta of application.metaData) { | ||||
|         if (meta.name === 'pushy_build_time') { | ||||
|           buildTime = meta.value[0]; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (buildTime == 0) { | ||||
|       throw new Error('Can not get build time for this app.'); | ||||
|     } | ||||
|     return { versionName, buildTime }; | ||||
|   }); | ||||
|  | ||||
|   return function getApkInfo(_x) { | ||||
|     return _ref.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let getIpaInfo = exports.getIpaInfo = function () { | ||||
|   var _ref3 = _asyncToGenerator(function* (fn) { | ||||
|     const appInfoParser = new AppInfoParser(fn); | ||||
|  | ||||
|     var _ref4 = yield appInfoParser.parse(); | ||||
|  | ||||
|     const versionName = _ref4.CFBundleShortVersionString; | ||||
|  | ||||
|     let buildTimeTxtBuffer = yield appInfoParser.parser.getEntry(/payload\/.+?\.app\/pushy_build_time.txt/); | ||||
|     if (!buildTimeTxtBuffer) { | ||||
|       // Not in root bundle when use `use_frameworks` | ||||
|       buildTimeTxtBuffer = yield appInfoParser.parser.getEntry(/payload\/.+?\.app\/frameworks\/react_native_update.framework\/pushy_build_time.txt/); | ||||
|     } | ||||
|     if (!buildTimeTxtBuffer) { | ||||
|       throw new Error('Can not get build time for this app.'); | ||||
|     } | ||||
|     const buildTime = buildTimeTxtBuffer.toString().replace('\n', ''); | ||||
|     return { versionName, buildTime }; | ||||
|   }); | ||||
|  | ||||
|   return function getIpaInfo(_x2) { | ||||
|     return _ref3.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| exports.question = question; | ||||
| exports.translateOptions = translateOptions; | ||||
| exports.getRNVersion = getRNVersion; | ||||
|  | ||||
| var _path = require('path'); | ||||
|  | ||||
| var path = _interopRequireWildcard(_path); | ||||
|  | ||||
| var _fsExtra = require('fs-extra'); | ||||
|  | ||||
| var fs = _interopRequireWildcard(_fsExtra); | ||||
|  | ||||
| function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | ||||
|  | ||||
| function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } /** | ||||
|                                                                                                                                                                                                                                                                                                                                                                                                                                                                             * Created by tdzl2003 on 2/13/16. | ||||
|                                                                                                                                                                                                                                                                                                                                                                                                                                                                             */ | ||||
|  | ||||
| const AppInfoParser = require('app-info-parser'); | ||||
|  | ||||
| var read = require('read'); | ||||
|  | ||||
| function question(query, password) { | ||||
|   if (NO_INTERACTIVE) { | ||||
|     return Promise.resolve(''); | ||||
|   } | ||||
|   return new Promise(function (resolve, reject) { | ||||
|     return read({ | ||||
|       prompt: query, | ||||
|       silent: password, | ||||
|       replace: password ? '*' : undefined | ||||
|     }, function (err, result) { | ||||
|       return err ? reject(err) : resolve(result); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| function translateOptions(options) { | ||||
|   const ret = {}; | ||||
|   for (let key in options) { | ||||
|     const v = options[key]; | ||||
|     if (typeof v === 'string') { | ||||
|       ret[key] = v.replace(/\$\{(\w+)\}/g, function (v, n) { | ||||
|         return options[n] || process.env[n] || v; | ||||
|       }); | ||||
|     } else { | ||||
|       ret[key] = v; | ||||
|     } | ||||
|   } | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| function getRNVersion() { | ||||
|   const version = JSON.parse(fs.readFileSync(path.resolve('node_modules/react-native/package.json'))).version; | ||||
|  | ||||
|   // We only care about major and minor version. | ||||
|   const match = /^(\d+)\.(\d+)\./.exec(version); | ||||
|   return { | ||||
|     version, | ||||
|     major: match[1] | 0, | ||||
|     minor: match[2] | 0 | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										199
									
								
								lib/versions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								lib/versions.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.commands = undefined; | ||||
|  | ||||
| let showVersion = function () { | ||||
|   var _ref = _asyncToGenerator(function* (appId, offset) { | ||||
|     var _ref2 = yield get(`/app/${appId}/version/list`); | ||||
|  | ||||
|     const data = _ref2.data, | ||||
|           count = _ref2.count; | ||||
|  | ||||
|     console.log(`Offset ${offset}`); | ||||
|     for (const version of data) { | ||||
|       let packageInfo = version.packages.slice(0, 3).map(function (v) { | ||||
|         return v.name; | ||||
|       }).join(', '); | ||||
|       const count = version.packages.length; | ||||
|       if (count > 3) { | ||||
|         packageInfo += `...and ${count - 3} more`; | ||||
|       } | ||||
|       if (count === 0) { | ||||
|         packageInfo = `(no package)`; | ||||
|       } else { | ||||
|         packageInfo = `[${packageInfo}]`; | ||||
|       } | ||||
|       console.log(`${version.id}) ${version.hash.slice(0, 8)} ${version.name} ${packageInfo}`); | ||||
|     } | ||||
|     return data; | ||||
|   }); | ||||
|  | ||||
|   return function showVersion(_x, _x2) { | ||||
|     return _ref.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let listVersions = function () { | ||||
|   var _ref3 = _asyncToGenerator(function* (appId) { | ||||
|     let offset = 0; | ||||
|     while (true) { | ||||
|       yield showVersion(appId, offset); | ||||
|       const cmd = yield (0, _utils.question)('page Up/page Down/Begin/Quit(U/D/B/Q)'); | ||||
|       switch (cmd.toLowerCase()) { | ||||
|         case 'u': | ||||
|           offset = Math.max(0, offset - 10);break; | ||||
|         case 'd': | ||||
|           offset += 10;break; | ||||
|         case 'b': | ||||
|           offset = 0;break; | ||||
|         case 'q': | ||||
|           return; | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   return function listVersions(_x3) { | ||||
|     return _ref3.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| let chooseVersion = function () { | ||||
|   var _ref4 = _asyncToGenerator(function* (appId) { | ||||
|     let offset = 0; | ||||
|     while (true) { | ||||
|       const data = yield showVersion(appId, offset); | ||||
|       const cmd = yield (0, _utils.question)('Enter versionId or page Up/page Down/Begin(U/D/B)'); | ||||
|       switch (cmd.toLowerCase()) { | ||||
|         case 'U': | ||||
|           offset = Math.max(0, offset - 10);break; | ||||
|         case 'D': | ||||
|           offset += 10;break; | ||||
|         case 'B': | ||||
|           offset = 0;break; | ||||
|         default: | ||||
|           { | ||||
|             const v = data.find(function (v) { | ||||
|               return v.id === (cmd | 0); | ||||
|             }); | ||||
|             if (v) { | ||||
|               return v; | ||||
|             } | ||||
|           } | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   return function chooseVersion(_x4) { | ||||
|     return _ref4.apply(this, arguments); | ||||
|   }; | ||||
| }(); | ||||
|  | ||||
| var _utils = require('./utils'); | ||||
|  | ||||
| var _app = require('./app'); | ||||
|  | ||||
| var _package = require('./package'); | ||||
|  | ||||
| function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||||
|  | ||||
| /** | ||||
|  * Created by tdzl2003 on 4/2/16. | ||||
|  */ | ||||
|  | ||||
| var _require = require('./api'); | ||||
|  | ||||
| const get = _require.get, | ||||
|       post = _require.post, | ||||
|       put = _require.put, | ||||
|       uploadFile = _require.uploadFile; | ||||
| const commands = exports.commands = { | ||||
|   publish: function () { | ||||
|     var _ref5 = _asyncToGenerator(function* (_ref6) { | ||||
|       let args = _ref6.args, | ||||
|           options = _ref6.options; | ||||
|  | ||||
|       const fn = args[0]; | ||||
|       const name = options.name, | ||||
|             description = options.description, | ||||
|             metaInfo = options.metaInfo; | ||||
|  | ||||
|  | ||||
|       if (!fn || !fn.endsWith('.ppk')) { | ||||
|         throw new Error('Usage: pushy publish <ppkFile> --platform ios|android'); | ||||
|       } | ||||
|  | ||||
|       const platform = (0, _app.checkPlatform)(options.platform || (yield (0, _utils.question)('Platform(ios/android):'))); | ||||
|  | ||||
|       var _ref7 = yield (0, _app.getSelectedApp)(platform); | ||||
|  | ||||
|       const appId = _ref7.appId; | ||||
|  | ||||
|       var _ref8 = yield uploadFile(fn); | ||||
|  | ||||
|       const hash = _ref8.hash; | ||||
|  | ||||
|       var _ref9 = yield post(`/app/${appId}/version/create`, { | ||||
|         name: name || (yield (0, _utils.question)('Enter version name:')) || '(未命名)', | ||||
|         hash, | ||||
|         description: description || (yield (0, _utils.question)('Enter description:')), | ||||
|         metaInfo: metaInfo || (yield (0, _utils.question)('Enter meta info:')) | ||||
|       }); | ||||
|  | ||||
|       const id = _ref9.id; | ||||
|  | ||||
|       console.log(`Version published: ${id}`); | ||||
|  | ||||
|       const v = yield (0, _utils.question)('Would you like to bind packages to this version?(Y/N)'); | ||||
|       if (v.toLowerCase() === 'y') { | ||||
|         yield this.update({ args: [], options: { versionId: id, platform } }); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     return function publish(_x5) { | ||||
|       return _ref5.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   versions: function () { | ||||
|     var _ref10 = _asyncToGenerator(function* (_ref11) { | ||||
|       let options = _ref11.options; | ||||
|  | ||||
|       const platform = (0, _app.checkPlatform)(options.platform || (yield (0, _utils.question)('Platform(ios/android):'))); | ||||
|  | ||||
|       var _ref12 = yield (0, _app.getSelectedApp)(platform); | ||||
|  | ||||
|       const appId = _ref12.appId; | ||||
|  | ||||
|       yield listVersions(appId); | ||||
|     }); | ||||
|  | ||||
|     return function versions(_x6) { | ||||
|       return _ref10.apply(this, arguments); | ||||
|     }; | ||||
|   }(), | ||||
|   update: function () { | ||||
|     var _ref13 = _asyncToGenerator(function* (_ref14) { | ||||
|       let args = _ref14.args, | ||||
|           options = _ref14.options; | ||||
|  | ||||
|       const platform = (0, _app.checkPlatform)(options.platform || (yield (0, _utils.question)('Platform(ios/android):'))); | ||||
|  | ||||
|       var _ref15 = yield (0, _app.getSelectedApp)(platform); | ||||
|  | ||||
|       const appId = _ref15.appId; | ||||
|  | ||||
|       const versionId = options.versionId || (yield chooseVersion(appId)).id; | ||||
|       const pkgId = options.packageId || (yield (0, _package.choosePackage)(appId)).id; | ||||
|       yield put(`/app/${appId}/package/${pkgId}`, { | ||||
|         versionId | ||||
|       }); | ||||
|       console.log('Ok.'); | ||||
|     }); | ||||
|  | ||||
|     return function update(_x7) { | ||||
|       return _ref13.apply(this, arguments); | ||||
|     }; | ||||
|   }() | ||||
| }; | ||||
							
								
								
									
										57
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| { | ||||
|   "name": "react-native-update-cli", | ||||
|   "version": "1.0.0", | ||||
|   "description": "Command tools for javaScript updater with `pushy` service for react native apps.", | ||||
|   "main": "index.js", | ||||
|   "bin": { | ||||
|     "pushy": "lib/index.js" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1", | ||||
|     "prepublish": "babel src --out-dir lib" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://github.com/reactnativecn/react-native-pushy-cli.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "react-native", | ||||
|     "ios", | ||||
|     "android", | ||||
|     "update" | ||||
|   ], | ||||
|   "author": "reactnativecn", | ||||
|   "license": "BSD-3-Clause", | ||||
|   "bugs": { | ||||
|     "url": "https://github.com/reactnativecn/react-native-pushy/issues" | ||||
|   }, | ||||
|   "homepage": "https://github.com/reactnativecn/react-native-pushy/tree/master/react-native-pushy-cli", | ||||
|   "dependencies": { | ||||
|     "app-info-parser": "^0.3.8", | ||||
|     "cli-arguments": "^0.2.1", | ||||
|     "fs-extra": "^8.1.0", | ||||
|     "gradle-to-js": "^2.0.0", | ||||
|     "isomorphic-fetch": "^2.2.1", | ||||
|     "progress": "^1.1.8", | ||||
|     "read": "^1.0.7", | ||||
|     "request": "^2.69.0", | ||||
|     "tty-table": "^2.7.0", | ||||
|     "update-notifier": "^4.1.0", | ||||
|     "yauzl": "^2.10.0", | ||||
|     "yazl": "2.3.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "babel-cli": "^6.5.1", | ||||
|     "babel-eslint": "^4.1.6", | ||||
|     "babel-plugin-syntax-async-functions": "^6.5.0", | ||||
|     "babel-plugin-syntax-object-rest-spread": "^6.5.0", | ||||
|     "babel-plugin-transform-async-to-generator": "^6.3.13", | ||||
|     "babel-plugin-transform-es2015-arrow-functions": "^6.5.2", | ||||
|     "babel-plugin-transform-es2015-destructuring": "^6.3.15", | ||||
|     "babel-plugin-transform-es2015-modules-commonjs": "^6.3.16", | ||||
|     "babel-plugin-transform-es2015-parameters": "^6.5.0", | ||||
|     "babel-plugin-transform-es2015-spread": "^6.5.2", | ||||
|     "babel-plugin-transform-object-rest-spread": "^6.5.0", | ||||
|     "babel-plugin-transform-strict-mode": "^6.5.2" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										132
									
								
								src/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/api.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/13/16. | ||||
|  */ | ||||
|  | ||||
| const fetch = require('isomorphic-fetch'); | ||||
| let host = process.env.PUSHY_REGISTRY || 'https://update.reactnative.cn/api'; | ||||
| const fs = require('fs-extra'); | ||||
| import request from 'request'; | ||||
| import ProgressBar from 'progress'; | ||||
|  | ||||
| let session = undefined; | ||||
| let savedSession = undefined; | ||||
|  | ||||
| exports.loadSession = async function() { | ||||
|   if (fs.existsSync('.update')) { | ||||
|     try { | ||||
|       exports.replaceSession(JSON.parse(fs.readFileSync('.update', 'utf8'))); | ||||
|       savedSession = session; | ||||
|     } catch (e) { | ||||
|       console.error('Failed to parse file `.update`. Try to remove it manually.'); | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| exports.getSession = function() { | ||||
|   return session; | ||||
| }; | ||||
|  | ||||
| exports.replaceSession = function(newSession) { | ||||
|   session = newSession; | ||||
| }; | ||||
|  | ||||
| exports.saveSession = function() { | ||||
|   // Only save on change. | ||||
|   if (session !== savedSession) { | ||||
|     const current = session; | ||||
|     const data = JSON.stringify(current, null, 4); | ||||
|     fs.writeFileSync('.update', data, 'utf8'); | ||||
|     savedSession = current; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| exports.closeSession = function() { | ||||
|   if (fs.existsSync('.update')) { | ||||
|     fs.unlinkSync('.update'); | ||||
|     savedSession = undefined; | ||||
|   } | ||||
|   session = undefined; | ||||
|   host = process.env.PUSHY_REGISTRY || 'https://update.reactnative.cn'; | ||||
| }; | ||||
|  | ||||
| async function query(url, options) { | ||||
|   const resp = await fetch(url, options); | ||||
|   const json = await resp.json(); | ||||
|   if (resp.status !== 200) { | ||||
|     throw Object.assign(new Error(json.message || json.error), { status: resp.status }); | ||||
|   } | ||||
|   return json; | ||||
| } | ||||
|  | ||||
| function queryWithoutBody(method) { | ||||
|   return function(api) { | ||||
|     return query(host + api, { | ||||
|       method, | ||||
|       headers: { | ||||
|         'X-AccessToken': session ? session.token : '', | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| function queryWithBody(method) { | ||||
|   return function(api, body) { | ||||
|     return query(host + api, { | ||||
|       method, | ||||
|       headers: { | ||||
|         'Content-Type': 'application/json', | ||||
|         'X-AccessToken': session ? session.token : '', | ||||
|       }, | ||||
|       body: JSON.stringify(body), | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| exports.get = queryWithoutBody('GET'); | ||||
| exports.post = queryWithBody('POST'); | ||||
| exports.put = queryWithBody('PUT'); | ||||
| exports.doDelete = queryWithBody('DELETE'); | ||||
|  | ||||
| async function uploadFile(fn) { | ||||
|   const { url, fieldName, formData } = await exports.post('/upload', {}); | ||||
|   let realUrl = url; | ||||
|  | ||||
|   if (!/^https?\:\/\//.test(url)) { | ||||
|     realUrl = host + url; | ||||
|   } | ||||
|  | ||||
|   const fileSize = fs.statSync(fn).size; | ||||
|  | ||||
|   const bar = new ProgressBar('  Uploading [:bar] :percent :etas', { | ||||
|     complete: '=', | ||||
|     incomplete: ' ', | ||||
|     total: fileSize, | ||||
|   }); | ||||
|  | ||||
|   const info = await new Promise((resolve, reject) => { | ||||
|     formData.file = fs.createReadStream(fn); | ||||
|  | ||||
|     formData.file.on('data', function(data) { | ||||
|       bar.tick(data.length); | ||||
|     }); | ||||
|     request.post( | ||||
|       realUrl, | ||||
|       { | ||||
|         formData, | ||||
|       }, | ||||
|       (err, resp, body) => { | ||||
|         if (err) { | ||||
|           return reject(err); | ||||
|         } | ||||
|         if (resp.statusCode > 299) { | ||||
|           return reject(Object.assign(new Error(body), { status: resp.statusCode })); | ||||
|         } | ||||
|         resolve(JSON.parse(body)); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
|   return info; | ||||
| } | ||||
|  | ||||
| exports.uploadFile = uploadFile; | ||||
							
								
								
									
										110
									
								
								src/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/app.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/13/16. | ||||
|  */ | ||||
|  | ||||
| import {question} from './utils'; | ||||
| import * as fs from 'fs-extra'; | ||||
|  | ||||
| const { | ||||
|   post, | ||||
|   get, | ||||
|   doDelete, | ||||
| } = require('./api'); | ||||
|  | ||||
| const validPlatforms = { | ||||
|   ios: 1, | ||||
|   android: 1, | ||||
| }; | ||||
|  | ||||
| export function checkPlatform(platform) { | ||||
|   if (!validPlatforms[platform]) { | ||||
|     throw new Error(`Invalid platform '${platform}'`); | ||||
|   } | ||||
|   return platform | ||||
| } | ||||
|  | ||||
| export function getSelectedApp(platform) { | ||||
|   checkPlatform(platform); | ||||
|  | ||||
|   if (!fs.existsSync('update.json')){ | ||||
|     throw new Error(`App not selected. run 'pushy selectApp --platform ${platform}' first!`); | ||||
|   } | ||||
|   const updateInfo = JSON.parse(fs.readFileSync('update.json', 'utf8')); | ||||
|   if (!updateInfo[platform]) { | ||||
|     throw new Error(`App not selected. run 'pushy selectApp --platform ${platform}' first!`); | ||||
|   } | ||||
|   return updateInfo[platform]; | ||||
| } | ||||
|  | ||||
| export async function listApp(platform){ | ||||
|   const {data} = await get('/app/list'); | ||||
|   const list = platform?data.filter(v=>v.platform===platform):data; | ||||
|   for (const app of list) { | ||||
|     console.log(`${app.id}) ${app.name}(${app.platform})`); | ||||
|   } | ||||
|   if (platform) { | ||||
|     console.log(`\nTotal ${list.length} ${platform} apps`); | ||||
|   } else { | ||||
|     console.log(`\nTotal ${list.length} apps`); | ||||
|   } | ||||
|   return list; | ||||
| } | ||||
|  | ||||
| export async function chooseApp(platform) { | ||||
|   const list = await listApp(platform); | ||||
|  | ||||
|   while (true) { | ||||
|     const id = await question('Enter appId:'); | ||||
|     const app = list.find(v=>v.id === (id|0)); | ||||
|     if (app) { | ||||
|       return app; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const commands = { | ||||
|   createApp: async function ({options}) { | ||||
|     const name = options.name || await question('App Name:'); | ||||
|     const {downloadUrl} = options; | ||||
|     const platform = checkPlatform(options.platform || await question('Platform(ios/android):')); | ||||
|     const {id} = await post('/app/create', {name, platform}); | ||||
|     console.log(`Created app ${id}`); | ||||
|     await this.selectApp({ | ||||
|       args: [id], | ||||
|       options: {platform, downloadUrl}, | ||||
|     }); | ||||
|   }, | ||||
|   deleteApp: async function ({args, options}) { | ||||
|     const {platform} = options; | ||||
|     const id = args[0] || chooseApp(platform); | ||||
|     if (!id) { | ||||
|       console.log('Canceled'); | ||||
|     } | ||||
|     await doDelete(`/app/${id}`); | ||||
|     console.log('Ok.'); | ||||
|   }, | ||||
|   apps: async function ({options}){ | ||||
|     const {platform} = options; | ||||
|     listApp(platform); | ||||
|   }, | ||||
|   selectApp: async function({args, options}) { | ||||
|     const platform = checkPlatform(options.platform || await question('Platform(ios/android):')); | ||||
|     const id = args[0] || (await chooseApp(platform)).id; | ||||
|  | ||||
|     let updateInfo = {}; | ||||
|     if (fs.existsSync('update.json')) { | ||||
|       try { | ||||
|         updateInfo = JSON.parse(fs.readFileSync('update.json', 'utf8')); | ||||
|       } catch (e) { | ||||
|         console.error('Failed to parse file `update.json`. Try to remove it manually.'); | ||||
|         throw e; | ||||
|       } | ||||
|     } | ||||
|     const {appKey} = await get(`/app/${id}`); | ||||
|     updateInfo[platform] = { | ||||
|       appId: id, | ||||
|       appKey, | ||||
|     }; | ||||
|     fs.writeFileSync('update.json', JSON.stringify(updateInfo, null, 4), 'utf8'); | ||||
|   }, | ||||
| } | ||||
							
								
								
									
										508
									
								
								src/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										508
									
								
								src/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,508 @@ | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/22/16. | ||||
|  */ | ||||
|  | ||||
| const path = require('path'); | ||||
| import { getRNVersion, translateOptions } from './utils'; | ||||
| import * as fs from 'fs-extra'; | ||||
| import { ZipFile } from 'yazl'; | ||||
| import { open as openZipFile } from 'yauzl'; | ||||
| import { question } from './utils'; | ||||
| import { checkPlatform } from './app'; | ||||
| const { spawn, spawnSync, execSync } = require('child_process'); | ||||
| const g2js = require('gradle-to-js/lib/parser'); | ||||
| const os = require('os'); | ||||
|  | ||||
| var diff; | ||||
| try { | ||||
|   var bsdiff = require('node-bsdiff'); | ||||
|   diff = typeof bsdiff != 'function' ? bsdiff.diff : bsdiff; | ||||
| } catch (e) { | ||||
|   diff = function() { | ||||
|     console.warn( | ||||
|       'This function needs "node-bsdiff". Please run "npm i node-bsdiff" from your project directory first!', | ||||
|     ); | ||||
|     throw new Error('This function needs module "node-bsdiff". Please install it first.'); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| function exec(command) { | ||||
|   const commandResult = spawnSync(command, { | ||||
|     shell: true, | ||||
|     stdio: 'inherit', | ||||
|   }); | ||||
|   if (commandResult.error) { | ||||
|     throw commandResult.error; | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function runReactNativeBundleCommand( | ||||
|   bundleName, | ||||
|   development, | ||||
|   entryFile, | ||||
|   outputFolder, | ||||
|   platform, | ||||
|   sourcemapOutput, | ||||
|   config, | ||||
| ) { | ||||
|   let reactNativeBundleArgs = []; | ||||
|  | ||||
|   let envArgs = process.env.PUSHY_ENV_ARGS; | ||||
|  | ||||
|   if (envArgs) { | ||||
|     Array.prototype.push.apply(reactNativeBundleArgs, envArgs.trim().split(/\s+/)); | ||||
|   } | ||||
|  | ||||
|   fs.emptyDirSync(outputFolder); | ||||
|  | ||||
|   Array.prototype.push.apply(reactNativeBundleArgs, [ | ||||
|     path.join("node_modules", "react-native", "local-cli", "cli.js"),  | ||||
|     "bundle", | ||||
|     '--assets-dest', | ||||
|     outputFolder, | ||||
|     '--bundle-output', | ||||
|     path.join(outputFolder, bundleName), | ||||
|     '--dev', | ||||
|     development, | ||||
|     '--entry-file', | ||||
|     entryFile, | ||||
|     '--platform', | ||||
|     platform, | ||||
|     '--reset-cache', | ||||
|   ]); | ||||
|  | ||||
|   if (sourcemapOutput) { | ||||
|     reactNativeBundleArgs.push('--sourcemap-output', sourcemapOutput); | ||||
|   } | ||||
|  | ||||
|   if (config) { | ||||
|     reactNativeBundleArgs.push('--config', config); | ||||
|   } | ||||
|  | ||||
|   const reactNativeBundleProcess = spawn('node', reactNativeBundleArgs); | ||||
|   console.log(`Running bundle command: node ${reactNativeBundleArgs.join(' ')}`); | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     reactNativeBundleProcess.stdout.on('data', data => { | ||||
|       console.log(data.toString().trim()); | ||||
|     }); | ||||
|  | ||||
|     reactNativeBundleProcess.stderr.on('data', data => { | ||||
|       console.error(data.toString().trim()); | ||||
|     }); | ||||
|  | ||||
|     reactNativeBundleProcess.on('close', async exitCode => { | ||||
|       if (exitCode) { | ||||
|         reject(new Error(`"react-native bundle" command exited with code ${exitCode}.`)); | ||||
|       } else { | ||||
|         if (platform === 'android') { | ||||
|           await compileHermesByteCode(bundleName, outputFolder); | ||||
|         } | ||||
|         resolve(null); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| function getHermesOSBin() { | ||||
|   if (os.platform() === 'win32') return 'win64-bin'; | ||||
|   if (os.platform() === 'darwin') return 'osx-bin'; | ||||
|   if (os.platform() === 'linux') return 'linux64-bin'; | ||||
| } | ||||
|  | ||||
| async function compileHermesByteCode(bundleName, outputFolder) { | ||||
|   let enableHermes = false; | ||||
|   try { | ||||
|     const gradleConfig = await g2js.parseFile('android/app/build.gradle'); | ||||
|     const projectConfig = gradleConfig['project.ext.react']; | ||||
|     for (const packagerConfig of projectConfig) { | ||||
|       if (packagerConfig.includes('enableHermes') && packagerConfig.includes('true')) { | ||||
|         enableHermes = true; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|   } catch (e) {} | ||||
|   if (enableHermes) { | ||||
|     console.log(`Hermes enabled, now compiling to hermes bytecode:\n`); | ||||
|     const hermesPath = fs.existsSync('node_modules/hermes-engine') | ||||
|       ? 'node_modules/hermes-engine' | ||||
|       : 'node_modules/hermesvm'; | ||||
|     execSync( | ||||
|       `${hermesPath}/${getHermesOSBin()}/hermes -emit-binary -out ${outputFolder}/${bundleName} ${outputFolder}/${bundleName} -O`, | ||||
|       { stdio: 'ignore' }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function pack(dir, output) { | ||||
|   console.log('Packing'); | ||||
|   fs.ensureDirSync(path.dirname(output)); | ||||
|   await new Promise((resolve, reject) => { | ||||
|     var zipfile = new ZipFile(); | ||||
|  | ||||
|     function addDirectory(root, rel) { | ||||
|       if (rel) { | ||||
|         zipfile.addEmptyDirectory(rel); | ||||
|       } | ||||
|       const childs = fs.readdirSync(root); | ||||
|       for (const name of childs) { | ||||
|         if (name === '.' || name === '..') { | ||||
|           continue; | ||||
|         } | ||||
|         const fullPath = path.join(root, name); | ||||
|         const stat = fs.statSync(fullPath); | ||||
|         if (stat.isFile()) { | ||||
|           //console.log('adding: ' + rel+name); | ||||
|           zipfile.addFile(fullPath, rel + name); | ||||
|         } else if (stat.isDirectory()) { | ||||
|           //console.log('adding: ' + rel+name+'/'); | ||||
|           addDirectory(fullPath, rel + name + '/'); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     addDirectory(dir, ''); | ||||
|  | ||||
|     zipfile.outputStream.on('error', err => reject(err)); | ||||
|     zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function() { | ||||
|       resolve(); | ||||
|     }); | ||||
|     zipfile.end(); | ||||
|   }); | ||||
|   console.log('Bundled saved to: ' + output); | ||||
| } | ||||
|  | ||||
| function readEntire(entry, zipFile) { | ||||
|   const buffers = []; | ||||
|   return new Promise((resolve, reject) => { | ||||
|     zipFile.openReadStream(entry, (err, stream) => { | ||||
|       stream.pipe({ | ||||
|         write(chunk) { | ||||
|           buffers.push(chunk); | ||||
|         }, | ||||
|         end() { | ||||
|           resolve(Buffer.concat(buffers)); | ||||
|         }, | ||||
|         prependListener() {}, | ||||
|         on() {}, | ||||
|         once() {}, | ||||
|         emit() {}, | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| function basename(fn) { | ||||
|   const m = /^(.+\/)[^\/]+\/?$/.exec(fn); | ||||
|   return m && m[1]; | ||||
| } | ||||
|  | ||||
| async function diffFromPPK(origin, next, output) { | ||||
|   fs.ensureDirSync(path.dirname(output)); | ||||
|  | ||||
|   const originEntries = {}; | ||||
|   const originMap = {}; | ||||
|  | ||||
|   let originSource; | ||||
|  | ||||
|   await enumZipEntries(origin, (entry, zipFile) => { | ||||
|     originEntries[entry.fileName] = entry; | ||||
|     if (!/\/$/.test(entry.fileName)) { | ||||
|       // isFile | ||||
|       originMap[entry.crc32] = entry.fileName; | ||||
|  | ||||
|       if (entry.fileName === 'index.bundlejs') { | ||||
|         // This is source. | ||||
|         return readEntire(entry, zipFile).then(v => (originSource = v)); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   originSource = originSource || new Buffer(0); | ||||
|  | ||||
|   const copies = {}; | ||||
|  | ||||
|   var zipfile = new ZipFile(); | ||||
|  | ||||
|   const writePromise = new Promise((resolve, reject) => { | ||||
|     zipfile.outputStream.on('error', err => { | ||||
|       throw err; | ||||
|     }); | ||||
|     zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function() { | ||||
|       resolve(); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   const addedEntry = {}; | ||||
|  | ||||
|   function addEntry(fn) { | ||||
|     //console.log(fn); | ||||
|     if (!fn || addedEntry[fn]) { | ||||
|       return; | ||||
|     } | ||||
|     const base = basename(fn); | ||||
|     if (base) { | ||||
|       addEntry(base); | ||||
|     } | ||||
|     zipfile.addEmptyDirectory(fn); | ||||
|   } | ||||
|  | ||||
|   const newEntries = {}; | ||||
|  | ||||
|   await enumZipEntries(next, (entry, nextZipfile) => { | ||||
|     newEntries[entry.fileName] = entry; | ||||
|  | ||||
|     if (/\/$/.test(entry.fileName)) { | ||||
|       // Directory | ||||
|       if (!originEntries[entry.fileName]) { | ||||
|         addEntry(entry.fileName); | ||||
|       } | ||||
|     } else if (entry.fileName === 'index.bundlejs') { | ||||
|       //console.log('Found bundle'); | ||||
|       return readEntire(entry, nextZipfile).then(newSource => { | ||||
|         //console.log('Begin diff'); | ||||
|         zipfile.addBuffer(diff(originSource, newSource), 'index.bundlejs.patch'); | ||||
|         //console.log('End diff'); | ||||
|       }); | ||||
|     } else { | ||||
|       // If same file. | ||||
|       const originEntry = originEntries[entry.fileName]; | ||||
|       if (originEntry && originEntry.crc32 === entry.crc32) { | ||||
|         // ignore | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // If moved from other place | ||||
|       if (originMap[entry.crc32]) { | ||||
|         const base = basename(entry.fileName); | ||||
|         if (!originEntries[base]) { | ||||
|           addEntry(base); | ||||
|         } | ||||
|         copies[entry.fileName] = originMap[entry.crc32]; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // New file. | ||||
|       addEntry(basename(entry.fileName)); | ||||
|  | ||||
|       return new Promise((resolve, reject) => { | ||||
|         nextZipfile.openReadStream(entry, function(err, readStream) { | ||||
|           if (err) { | ||||
|             return reject(err); | ||||
|           } | ||||
|           zipfile.addReadStream(readStream, entry.fileName); | ||||
|           readStream.on('end', () => { | ||||
|             //console.log('add finished'); | ||||
|             resolve(); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   const deletes = {}; | ||||
|  | ||||
|   for (var k in originEntries) { | ||||
|     if (!newEntries[k]) { | ||||
|       console.log('Delete ' + k); | ||||
|       deletes[k] = 1; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   //console.log({copies, deletes}); | ||||
|   zipfile.addBuffer(new Buffer(JSON.stringify({ copies, deletes })), '__diff.json'); | ||||
|   zipfile.end(); | ||||
|   await writePromise; | ||||
| } | ||||
|  | ||||
| async function diffFromPackage(origin, next, output, originBundleName, transformPackagePath = v => v) { | ||||
|   fs.ensureDirSync(path.dirname(output)); | ||||
|  | ||||
|   const originEntries = {}; | ||||
|   const originMap = {}; | ||||
|  | ||||
|   let originSource; | ||||
|  | ||||
|   await enumZipEntries(origin, (entry, zipFile) => { | ||||
|     if (!/\/$/.test(entry.fileName)) { | ||||
|       const fn = transformPackagePath(entry.fileName); | ||||
|       if (!fn) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       //console.log(fn); | ||||
|       // isFile | ||||
|       originEntries[fn] = entry.crc32; | ||||
|       originMap[entry.crc32] = fn; | ||||
|  | ||||
|       if (fn === originBundleName) { | ||||
|         // This is source. | ||||
|         return readEntire(entry, zipFile).then(v => (originSource = v)); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   originSource = originSource || new Buffer(0); | ||||
|  | ||||
|   const copies = {}; | ||||
|  | ||||
|   var zipfile = new ZipFile(); | ||||
|  | ||||
|   const writePromise = new Promise((resolve, reject) => { | ||||
|     zipfile.outputStream.on('error', err => { | ||||
|       throw err; | ||||
|     }); | ||||
|     zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function() { | ||||
|       resolve(); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   await enumZipEntries(next, (entry, nextZipfile) => { | ||||
|     if (/\/$/.test(entry.fileName)) { | ||||
|       // Directory | ||||
|       zipfile.addEmptyDirectory(entry.fileName); | ||||
|     } else if (entry.fileName === 'index.bundlejs') { | ||||
|       //console.log('Found bundle'); | ||||
|       return readEntire(entry, nextZipfile).then(newSource => { | ||||
|         //console.log('Begin diff'); | ||||
|         zipfile.addBuffer(diff(originSource, newSource), 'index.bundlejs.patch'); | ||||
|         //console.log('End diff'); | ||||
|       }); | ||||
|     } else { | ||||
|       // If same file. | ||||
|       if (originEntries[entry.fileName] === entry.crc32) { | ||||
|         copies[entry.fileName] = ''; | ||||
|         return; | ||||
|       } | ||||
|       // If moved from other place | ||||
|       if (originMap[entry.crc32]) { | ||||
|         copies[entry.fileName] = originMap[entry.crc32]; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       return new Promise((resolve, reject) => { | ||||
|         nextZipfile.openReadStream(entry, function(err, readStream) { | ||||
|           if (err) { | ||||
|             return reject(err); | ||||
|           } | ||||
|           zipfile.addReadStream(readStream, entry.fileName); | ||||
|           readStream.on('end', () => { | ||||
|             //console.log('add finished'); | ||||
|             resolve(); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   zipfile.addBuffer(new Buffer(JSON.stringify({ copies })), '__diff.json'); | ||||
|   zipfile.end(); | ||||
|   await writePromise; | ||||
| } | ||||
|  | ||||
| function enumZipEntries(zipFn, callback) { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     openZipFile(zipFn, { lazyEntries: true }, (err, zipfile) => { | ||||
|       if (err) { | ||||
|         return reject(err); | ||||
|       } | ||||
|       zipfile.on('end', resolve); | ||||
|       zipfile.on('error', reject); | ||||
|       zipfile.on('entry', entry => { | ||||
|         const result = callback(entry, zipfile); | ||||
|         if (result && typeof result.then === 'function') { | ||||
|           result.then(() => zipfile.readEntry()); | ||||
|         } else { | ||||
|           zipfile.readEntry(); | ||||
|         } | ||||
|       }); | ||||
|       zipfile.readEntry(); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export const commands = { | ||||
|   bundle: async function({ options }) { | ||||
|     const platform = checkPlatform(options.platform || (await question('Platform(ios/android):'))); | ||||
|  | ||||
|     let { bundleName, entryFile, intermediaDir, output, dev, verbose } = translateOptions({ | ||||
|       ...options, | ||||
|       platform, | ||||
|     }); | ||||
|  | ||||
|     // const sourcemapOutput = path.join(intermediaDir, bundleName + ".map"); | ||||
|  | ||||
|     const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); | ||||
|  | ||||
|     if (!platform) { | ||||
|       throw new Error('Platform must be specified.'); | ||||
|     } | ||||
|  | ||||
|     const { version, major, minor } = getRNVersion(); | ||||
|  | ||||
|     console.log('Bundling with React Native version: ', version); | ||||
|  | ||||
|     await runReactNativeBundleCommand(bundleName, dev, entryFile, intermediaDir, platform); | ||||
|  | ||||
|     await pack(path.resolve(intermediaDir), realOutput); | ||||
|  | ||||
|     const v = await question('Would you like to publish it?(Y/N)'); | ||||
|     if (v.toLowerCase() === 'y') { | ||||
|       await this.publish({ | ||||
|         args: [realOutput], | ||||
|         options: { | ||||
|           platform, | ||||
|         }, | ||||
|       }); | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   async diff({ args, options }) { | ||||
|     const [origin, next] = args; | ||||
|     const { output } = options; | ||||
|  | ||||
|     const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); | ||||
|  | ||||
|     if (!origin || !next) { | ||||
|       console.error('pushy diff <origin> <next>'); | ||||
|       process.exit(1); | ||||
|     } | ||||
|  | ||||
|     await diffFromPPK(origin, next, realOutput, 'index.bundlejs'); | ||||
|     console.log(`${realOutput} generated.`); | ||||
|   }, | ||||
|  | ||||
|   async diffFromApk({ args, options }) { | ||||
|     const [origin, next] = args; | ||||
|     const { output } = options; | ||||
|  | ||||
|     const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); | ||||
|  | ||||
|     if (!origin || !next) { | ||||
|       console.error('pushy diffFromApk <origin> <next>'); | ||||
|       process.exit(1); | ||||
|     } | ||||
|  | ||||
|     await diffFromPackage(origin, next, realOutput, 'assets/index.android.bundle'); | ||||
|     console.log(`${realOutput} generated.`); | ||||
|   }, | ||||
|  | ||||
|   async diffFromIpa({ args, options }) { | ||||
|     const [origin, next] = args; | ||||
|     const { output } = options; | ||||
|  | ||||
|     const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); | ||||
|  | ||||
|     if (!origin || !next) { | ||||
|       console.error('pushy diffFromIpa <origin> <next>'); | ||||
|       process.exit(1); | ||||
|     } | ||||
|  | ||||
|     await diffFromPackage(origin, next, realOutput, 'main.jsbundle', v => { | ||||
|       const m = /^Payload\/[^/]+\/(.+)$/.exec(v); | ||||
|       return m && m[1]; | ||||
|     }); | ||||
|  | ||||
|     console.log(`${realOutput} generated.`); | ||||
|   }, | ||||
| }; | ||||
							
								
								
									
										67
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| #!/usr/bin/env node | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/13/16. | ||||
|  */ | ||||
|  | ||||
| const {loadSession} = require('./api'); | ||||
| const updateNotifier = require('update-notifier'); | ||||
| const pkg = require('../package.json'); | ||||
|  | ||||
| updateNotifier({pkg}).notify(); | ||||
|  | ||||
| function printUsage({args}) { | ||||
|   // const commandName = args[0]; | ||||
|   // TODO: print usage of commandName, or print global usage. | ||||
|  | ||||
|   console.log('Usage is under development now.') | ||||
|   console.log('Visit `https://github.com/reactnativecn/react-native-pushy` for early document.'); | ||||
|   process.exit(1); | ||||
| } | ||||
|  | ||||
|  | ||||
| function printVersionCommand() { | ||||
|   if (process.argv.indexOf('-v') >= 0 || process.argv[2] === 'version') { | ||||
|     console.log('react-native-update-cli: ' + pkg.version); | ||||
|     try { | ||||
|       const PACKAGE_JSON_PATH = path.resolve( | ||||
|         process.cwd(), | ||||
|         'node_modules', | ||||
|         'react-native-update', | ||||
|         'package.json' | ||||
|       ); | ||||
|       console.log('react-native-update: ' + require(PACKAGE_JSON_PATH).version); | ||||
|     } catch (e) { | ||||
|       console.log('react-native-update: n/a - not inside a React Native project directory') | ||||
|     } | ||||
|     process.exit(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| const commands = { | ||||
|   ...require('./user').commands, | ||||
|   ...require('./bundle').commands, | ||||
|   ...require('./app').commands, | ||||
|   ...require('./package').commands, | ||||
|   ...require('./versions').commands, | ||||
|   help: printUsage, | ||||
| }; | ||||
|  | ||||
| function run() { | ||||
|   printVersionCommand(); | ||||
|    | ||||
|   const argv = require('cli-arguments').parse(require('../cli.json')); | ||||
|   global.NO_INTERACTIVE = argv.options['no-interactive']; | ||||
|  | ||||
|   loadSession() | ||||
|     .then(()=>commands[argv.command](argv)) | ||||
|     .catch(err=>{ | ||||
|       if (err.status === 401) { | ||||
|         console.log('Not loggined.\nRun `pushy login` at your project directory to login.'); | ||||
|         return; | ||||
|       } | ||||
|       console.error(err.stack); | ||||
|       process.exit(-1); | ||||
|     }); | ||||
| }; | ||||
|  | ||||
| run(); | ||||
							
								
								
									
										87
									
								
								src/package.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/package.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| /** | ||||
|  * Created by tdzl2003 on 4/2/16. | ||||
|  */ | ||||
|  | ||||
| const { get, post, uploadFile } = require('./api'); | ||||
| import { question } from './utils'; | ||||
|  | ||||
| import { checkPlatform, getSelectedApp } from './app'; | ||||
|  | ||||
| import { getApkInfo, getIpaInfo } from './utils'; | ||||
| const Table = require('tty-table'); | ||||
|  | ||||
| export async function listPackage(appId) { | ||||
|   const { data } = await get(`/app/${appId}/package/list?limit=1000`); | ||||
|  | ||||
|   const header = [{ value: 'Package Id' }, { value: 'Version' }]; | ||||
|   const rows = []; | ||||
|   for (const pkg of data) { | ||||
|     const { version } = pkg; | ||||
|     let versionInfo = ''; | ||||
|     if (version) { | ||||
|       versionInfo = ` - ${version.id} ${version.hash.slice(0, 8)} ${version.name}`; | ||||
|     } else { | ||||
|       versionInfo = ' (newest)'; | ||||
|     } | ||||
|  | ||||
|     rows.push([pkg.id, `${pkg.name}(${pkg.status})${versionInfo}`]); | ||||
|   } | ||||
|  | ||||
|   console.log(Table(header, rows).render()); | ||||
|   console.log(`\nTotal ${data.length} package(s).`); | ||||
|   return data; | ||||
| } | ||||
|  | ||||
| export async function choosePackage(appId) { | ||||
|   const list = await listPackage(appId); | ||||
|  | ||||
|   while (true) { | ||||
|     const id = await question('Enter Package Id:'); | ||||
|     const app = list.find(v => v.id === (id | 0)); | ||||
|     if (app) { | ||||
|       return app; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const commands = { | ||||
|   uploadIpa: async function({ args }) { | ||||
|     const fn = args[0]; | ||||
|     if (!fn) { | ||||
|       throw new Error('Usage: pushy uploadIpa <ipaFile>'); | ||||
|     } | ||||
|     const { versionName, buildTime } = await getIpaInfo(fn); | ||||
|     const { appId } = await getSelectedApp('ios'); | ||||
|  | ||||
|     const { hash } = await uploadFile(fn); | ||||
|  | ||||
|     const { id } = await post(`/app/${appId}/package/create`, { | ||||
|       name: versionName, | ||||
|       hash, | ||||
|       buildTime, | ||||
|     }); | ||||
|     console.log(`Ipa uploaded: ${id}`); | ||||
|   }, | ||||
|   uploadApk: async function({ args }) { | ||||
|     const fn = args[0]; | ||||
|     if (!fn) { | ||||
|       throw new Error('Usage: pushy uploadApk <apkFile>'); | ||||
|     } | ||||
|     const { versionName, buildTime } = await getApkInfo(fn); | ||||
|     const { appId } = await getSelectedApp('android'); | ||||
|  | ||||
|     const { hash } = await uploadFile(fn); | ||||
|  | ||||
|     const { id } = await post(`/app/${appId}/package/create`, { | ||||
|       name: versionName, | ||||
|       hash, | ||||
|       buildTime, | ||||
|     }); | ||||
|     console.log(`Apk uploaded: ${id}`); | ||||
|   }, | ||||
|   packages: async function({ options }) { | ||||
|     const platform = checkPlatform(options.platform || (await question('Platform(ios/android):'))); | ||||
|     const { appId } = await getSelectedApp(platform); | ||||
|     await listPackage(appId); | ||||
|   }, | ||||
| }; | ||||
							
								
								
									
										43
									
								
								src/user.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/user.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/13/16. | ||||
|  */ | ||||
|  | ||||
| import {question} from './utils'; | ||||
| const { | ||||
|   post, | ||||
|   get, | ||||
|   replaceSession, | ||||
|   saveSession, | ||||
|   closeSession, | ||||
| } = require('./api'); | ||||
| const crypto = require('crypto'); | ||||
|  | ||||
| function md5(str) { | ||||
|   return crypto.createHash('md5').update(str).digest('hex'); | ||||
| } | ||||
|  | ||||
| exports.commands = { | ||||
|   login: async function ({args}){ | ||||
|     const email = args[0] || await question('email:'); | ||||
|     const pwd = args[1] || await question('password:', true); | ||||
|     const {token, info} = await post('/user/login', { | ||||
|       email, | ||||
|       pwd: md5(pwd), | ||||
|     }); | ||||
|     replaceSession({token}); | ||||
|     await saveSession(); | ||||
|     console.log(`Welcome, ${info.name}.`); | ||||
|   }, | ||||
|   logout: async function (){ | ||||
|     await closeSession(); | ||||
|     console.log('Logged out.'); | ||||
|   }, | ||||
|   me: async function (){ | ||||
|     const me = await get('/user/me'); | ||||
|     for (const k in me) { | ||||
|       if (k !== 'ok') { | ||||
|         console.log(`${k}: ${me[k]}`); | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
| } | ||||
							
								
								
									
										84
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| /** | ||||
|  * Created by tdzl2003 on 2/13/16. | ||||
|  */ | ||||
|  | ||||
| import * as path from 'path'; | ||||
| import * as fs from 'fs-extra'; | ||||
| const AppInfoParser = require('app-info-parser'); | ||||
|  | ||||
| var read = require('read'); | ||||
|  | ||||
| export function question(query, password) { | ||||
|   if (NO_INTERACTIVE) { | ||||
|     return Promise.resolve(''); | ||||
|   } | ||||
|   return new Promise((resolve, reject) => | ||||
|     read( | ||||
|       { | ||||
|         prompt: query, | ||||
|         silent: password, | ||||
|         replace: password ? '*' : undefined, | ||||
|       }, | ||||
|       (err, result) => (err ? reject(err) : resolve(result)), | ||||
|     ), | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export function translateOptions(options) { | ||||
|   const ret = {}; | ||||
|   for (let key in options) { | ||||
|     const v = options[key]; | ||||
|     if (typeof v === 'string') { | ||||
|       ret[key] = v.replace(/\$\{(\w+)\}/g, function(v, n) { | ||||
|         return options[n] || process.env[n] || v; | ||||
|       }); | ||||
|     } else { | ||||
|       ret[key] = v; | ||||
|     } | ||||
|   } | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| export function getRNVersion() { | ||||
|   const version = JSON.parse(fs.readFileSync(path.resolve('node_modules/react-native/package.json'))).version; | ||||
|  | ||||
|   // We only care about major and minor version. | ||||
|   const match = /^(\d+)\.(\d+)\./.exec(version); | ||||
|   return { | ||||
|     version, | ||||
|     major: match[1] | 0, | ||||
|     minor: match[2] | 0, | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export async function getApkInfo(fn) { | ||||
|   const appInfoParser = new AppInfoParser(fn); | ||||
|   const { versionName, application } = await appInfoParser.parse(); | ||||
|   let buildTime = 0; | ||||
|   if (Array.isArray(application.metaData)) { | ||||
|     for (const meta of application.metaData) { | ||||
|       if (meta.name === 'pushy_build_time') { | ||||
|         buildTime = meta.value[0]; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   if (buildTime == 0) { | ||||
|     throw new Error('Can not get build time for this app.'); | ||||
|   } | ||||
|   return { versionName, buildTime }; | ||||
| } | ||||
|  | ||||
| export async function getIpaInfo(fn) { | ||||
|   const appInfoParser = new AppInfoParser(fn); | ||||
|   const { CFBundleShortVersionString: versionName } = await appInfoParser.parse(); | ||||
|   let buildTimeTxtBuffer = await appInfoParser.parser.getEntry(/payload\/.+?\.app\/pushy_build_time.txt/); | ||||
|   if (!buildTimeTxtBuffer) { | ||||
|     // Not in root bundle when use `use_frameworks` | ||||
|     buildTimeTxtBuffer = await appInfoParser.parser.getEntry(/payload\/.+?\.app\/frameworks\/react_native_update.framework\/pushy_build_time.txt/); | ||||
|   } | ||||
|   if (!buildTimeTxtBuffer) { | ||||
|     throw new Error('Can not get build time for this app.'); | ||||
|   } | ||||
|   const buildTime = buildTimeTxtBuffer.toString().replace('\n', ''); | ||||
|   return { versionName, buildTime }; | ||||
| } | ||||
							
								
								
									
										111
									
								
								src/versions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/versions.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| /** | ||||
|  * Created by tdzl2003 on 4/2/16. | ||||
|  */ | ||||
|  | ||||
| const { | ||||
|   get, | ||||
|   post, | ||||
|   put, | ||||
|   uploadFile, | ||||
| } = require('./api'); | ||||
| import { question } from './utils'; | ||||
|  | ||||
| import { checkPlatform, getSelectedApp } from './app'; | ||||
| import { choosePackage } from './package'; | ||||
|  | ||||
| async function showVersion(appId, offset) { | ||||
|   const { data, count } = await get(`/app/${appId}/version/list`); | ||||
|   console.log(`Offset ${offset}`); | ||||
|   for (const version of data) { | ||||
|     let packageInfo = version.packages.slice(0, 3).map(v=>v.name).join(', '); | ||||
|     const count = version.packages.length; | ||||
|     if (count > 3) { | ||||
|       packageInfo += `...and ${count-3} more`; | ||||
|     } | ||||
|     if (count === 0) { | ||||
|       packageInfo = `(no package)`; | ||||
|     } else { | ||||
|       packageInfo = `[${packageInfo}]`; | ||||
|     } | ||||
|     console.log(`${version.id}) ${version.hash.slice(0, 8)} ${version.name} ${packageInfo}`); | ||||
|   } | ||||
|   return data; | ||||
| } | ||||
|  | ||||
| async function listVersions(appId) { | ||||
|   let offset = 0; | ||||
|   while (true) { | ||||
|     await showVersion(appId, offset); | ||||
|     const cmd = await question('page Up/page Down/Begin/Quit(U/D/B/Q)'); | ||||
|     switch (cmd.toLowerCase()) { | ||||
|       case 'u': offset = Math.max(0, offset - 10); break; | ||||
|       case 'd': offset += 10; break; | ||||
|       case 'b': offset = 0; break; | ||||
|       case 'q': return; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function chooseVersion(appId) { | ||||
|   let offset = 0; | ||||
|   while (true) { | ||||
|     const data = await showVersion(appId, offset); | ||||
|     const cmd = await question('Enter versionId or page Up/page Down/Begin(U/D/B)'); | ||||
|     switch (cmd.toLowerCase()) { | ||||
|       case 'U': offset = Math.max(0, offset - 10); break; | ||||
|       case 'D': offset += 10; break; | ||||
|       case 'B': offset = 0; break; | ||||
|       default: | ||||
|       { | ||||
|         const v = data.find(v=>v.id === (cmd | 0)); | ||||
|         if (v) { | ||||
|           return v; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const commands = { | ||||
|   publish: async function({args, options}) { | ||||
|     const fn = args[0]; | ||||
|     const {name, description, metaInfo } = options; | ||||
|  | ||||
|     if (!fn || !fn.endsWith('.ppk')) { | ||||
|       throw new Error('Usage: pushy publish <ppkFile> --platform ios|android'); | ||||
|     } | ||||
|  | ||||
|     const platform = checkPlatform(options.platform || await question('Platform(ios/android):')); | ||||
|     const { appId } = await getSelectedApp(platform); | ||||
|  | ||||
|     const { hash } = await uploadFile(fn); | ||||
|  | ||||
|     const { id } = await post(`/app/${appId}/version/create`, { | ||||
|       name: name || await question('Enter version name:') || '(未命名)', | ||||
|       hash, | ||||
|       description: description || await question('Enter description:'), | ||||
|       metaInfo: metaInfo || await question('Enter meta info:'), | ||||
|     }); | ||||
|     console.log(`Version published: ${id}`); | ||||
|  | ||||
|     const v = await question('Would you like to bind packages to this version?(Y/N)'); | ||||
|     if (v.toLowerCase() === 'y') { | ||||
|       await this.update({args:[], options:{versionId: id, platform}}); | ||||
|     } | ||||
|   }, | ||||
|   versions: async function({options}) { | ||||
|     const platform = checkPlatform(options.platform || await question('Platform(ios/android):')); | ||||
|     const { appId } = await getSelectedApp(platform); | ||||
|     await listVersions(appId); | ||||
|   }, | ||||
|   update: async function({args, options}) { | ||||
|     const platform = checkPlatform(options.platform || await question('Platform(ios/android):')); | ||||
|     const { appId } = await getSelectedApp(platform); | ||||
|     const versionId = options.versionId || (await chooseVersion(appId)).id; | ||||
|     const pkgId = options.packageId || (await choosePackage(appId)).id; | ||||
|     await put(`/app/${appId}/package/${pkgId}`, { | ||||
|       versionId, | ||||
|     }); | ||||
|     console.log('Ok.'); | ||||
|   } | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user
	 sunnylqm
					sunnylqm