From d0037f1217037a1e724be32100dc756ec91f433e Mon Sep 17 00:00:00 2001 From: tdzl2003 Date: Sat, 2 Apr 2016 23:39:09 +0800 Subject: [PATCH] pushy uploadIpa --- local-cli/cli.json | 41 ++++++++++++------ local-cli/src/api.js | 46 +++++++++++++++++++- local-cli/src/app.js | 91 ++++++++++++++++++++++++++++++++++++++++ local-cli/src/index.js | 13 ++++-- local-cli/src/package.js | 31 ++++++++++++++ local-cli/src/user.js | 24 +++++------ local-cli/src/utils.js | 2 +- package.json | 2 + 8 files changed, 218 insertions(+), 32 deletions(-) create mode 100644 local-cli/src/app.js create mode 100644 local-cli/src/package.js diff --git a/local-cli/cli.json b/local-cli/cli.json index 865905f..000f327 100644 --- a/local-cli/cli.json +++ b/local-cli/cli.json @@ -2,25 +2,42 @@ "useCommand": true, "defaultCommand": "help", "commands": { - "login":{ - "options": { - "username": { + "help": { + }, + "login":{ + }, + "logout": { + }, + "me": { + }, + + "createApp": { + "options": { + "name": { + "hasValue": true + }, + "platform": { + "hasValue": true } } }, - "logout": { + "apps": { + }, + "deleteApp": { + }, + "selectApp": { + "options": { + "platform": { + "hasValue": true + } + } + }, + + "uploadIpa": { }, - "me": { - }, - "help": { - - }, - "use": { - "description": "Select app created on web and create token for future release." - }, "build": { "description": "Bundle javascript and copy assets." }, diff --git a/local-cli/src/api.js b/local-cli/src/api.js index 659f959..efa5fe9 100644 --- a/local-cli/src/api.js +++ b/local-cli/src/api.js @@ -5,6 +5,9 @@ const fetch = require('isomorphic-fetch'); let host = process.env.PUSHY_REGISTRY || 'http://pushy.reactnative.cn'; const fs = require('fs-promise'); +import * as fsOrigin from 'fs'; +import request from 'request'; +import ProgressBar from 'progress'; let session = undefined; let savedSession = undefined; @@ -45,7 +48,7 @@ exports.closeSession = async function(){ savedSession = undefined; } session = undefined; - host = process.env.PUSHY_REGISTRY || 'http://pushy.reactnative.cn'; + host = process.env.PUSHY_REGISTRY || 'http://update.reactnative.cn'; } async function query(url, options) { @@ -84,3 +87,44 @@ function queryWithBody(method) { 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)) { + url = host + url; + } + + const fileSize = (await fs.stat(fn)).size; + + const bar = new ProgressBar(' Uploading [:bar] :percent :etas', { + complete: '=', + incomplete: ' ', + total: fileSize, + }); + + const info = await new Promise((resolve, reject) => { + formData.file = fsOrigin.createReadStream(fn); + + formData.file.on('data', function(data) { + bar.tick(data.length); + }) + request.post({ + url, + 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; \ No newline at end of file diff --git a/local-cli/src/app.js b/local-cli/src/app.js new file mode 100644 index 0000000..8e9f15d --- /dev/null +++ b/local-cli/src/app.js @@ -0,0 +1,91 @@ +/** + * Created by tdzl2003 on 2/13/16. + */ + +import {question} from './utils'; +import * as fs from 'fs-promise'; + +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 async function getSelectedApp(platform) { + checkPlatform(platform); + + if (!await fs.exists('update.json')){ + throw new Error(`App not selected. run 'pushy selectApp --platform ${platform}' first!`); + } + const updateInfo = JSON.parse(await fs.readFile('update.json', 'utf8')); + if (!updateInfo[platform]) { + throw new Error(`App not selected. run 'pushy selectApp --platform ${platform}' first!`); + } + return updateInfo[platform]; +} + +export const commands = { + createApp: async function ({options}) { + const name = options.name || await question('App Name:'); + 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}, + }); + }, + deleteApp: async function ({args}) { + const id = args[0] || ((await this.apps()), (await question('Choose App to delete:'))); + if (!id) { + console.log('Canceled'); + } + await doDelete(`/app/${id}`); + console.log('Ok.'); + }, + apps: async function (_, 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`); + } + }, + selectApp: async function({args, options}) { + const {platform} = options; + checkPlatform(platform); + const id = args[0] || ((await this.apps(null, platform)), (await question('Choose App used for ${platform}:'))); + + let updateInfo = {}; + if (await fs.exists('update.json')) { + try { + updateInfo = JSON.parse(await fs.readFile('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, + }; + await fs.writeFile('update.json', JSON.stringify(updateInfo, null, 4), 'utf8'); + }, +} diff --git a/local-cli/src/index.js b/local-cli/src/index.js index 664aeb3..b57c157 100644 --- a/local-cli/src/index.js +++ b/local-cli/src/index.js @@ -3,8 +3,6 @@ */ const {loadSession} = require('./api'); -const userCommands = require('./user').commands; -import {commands as bundleCommands} from './bundle'; function printUsage({args}) { // const commandName = args[0]; @@ -16,8 +14,10 @@ function printUsage({args}) { } const commands = { - ...userCommands, - ...bundleCommands, + ...require('./user').commands, + ...require('./bundle').commands, + ...require('./app').commands, + ...require('./package').commands, help: printUsage, }; @@ -27,6 +27,11 @@ exports.run = function () { 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.log(err.message); setTimeout(()=>{ throw err; }); diff --git a/local-cli/src/package.js b/local-cli/src/package.js new file mode 100644 index 0000000..1703a11 --- /dev/null +++ b/local-cli/src/package.js @@ -0,0 +1,31 @@ +/** + * Created by tdzl2003 on 4/2/16. + */ + +const { + get, + post, + uploadFile, +} = require('./api'); + +import { checkPlatform, getSelectedApp } from './app'; + +import {getIPAVersion, getApkVersion} from './utils'; + +export const commands = { + uploadIpa: async function({args}) { + const fn = args[0]; + if (!fn) { + throw new Error('Usage: pushy uploadIpa '); + } + const name = await getIPAVersion(fn); + const {appId} = await getSelectedApp('ios'); + + const {hash} = await uploadFile(fn); + + await post(`/app/${appId}/package/create`, { + name, + hash, + }); + } +}; diff --git a/local-cli/src/user.js b/local-cli/src/user.js index b2733c3..73ba844 100644 --- a/local-cli/src/user.js +++ b/local-cli/src/user.js @@ -13,35 +13,31 @@ const { const crypto = require('crypto'); function md5(str) { - return crypto.createHash('md5').update(str).digest('base64'); + return crypto.createHash('md5').update(str).digest('hex'); } exports.commands = { login: async function ({args}){ - const login = args[0] || await question('user:'); + const email = args[0] || await question('email:'); const pwd = args[1] || await question('password:', true); - const {token} = await post('/user/login', { - login, + const {token, info} = await post('/user/login', { + email, pwd: md5(pwd), }); replaceSession({token}); await saveSession(); - console.log('OK.'); + console.log(`Welcome, ${info.name}.`); }, logout: async function (){ await closeSession(); console.log('Logged out.'); }, me: async function (){ - try { - const me = await get('/user/me'); - console.log(me); - } catch (e) { - if (e.status === 401) { - console.log('Not loggined.\nRun `pushy login` at your project directory to login.'); - } else { - throw e; + const me = await get('/user/me'); + for (const k in me) { + if (k !== 'ok') { + console.log(`${k}: ${me[k]}`); } } - } + }, } diff --git a/local-cli/src/utils.js b/local-cli/src/utils.js index dc43bcf..1e5233a 100644 --- a/local-cli/src/utils.js +++ b/local-cli/src/utils.js @@ -53,7 +53,7 @@ export function getApkVersion(fn) { export function getIPAVersion(fn) { return new Promise((resolve, reject) => { ipaMetadata(fn, (err, data) => { - err ? reject(err) : resolve(data); + err ? reject(err) : resolve(data.metadata.CFBundleShortVersionString); }); }); } diff --git a/package.json b/package.json index b20e3be..05fcb23 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ "mkdir-recursive": "^0.2.1", "node-apk-parser": "^0.2.3", "node-bsdiff": "^0.1.2", + "progress": "^1.1.8", "read": "^1.0.7", + "request": "^2.69.0", "yauzl": "^2.4.1", "yazl": "^2.3.0" },