diff --git a/.gitignore b/.gitignore index ccb2996..25dd9de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ /.idea /node_modules -/local-cli/lib -/react-native-pushy-cli/node_modules -/react-native-pushy-cli/lib /android/build /android/obj *.iml diff --git a/.npmignore b/.npmignore index 944d9cf..fa984fe 100644 --- a/.npmignore +++ b/.npmignore @@ -4,8 +4,6 @@ /.eslintrc /.nvmrc /.travis.yml -/local-cli/src -/react-native-pushy-cli /Example /android/build diff --git a/local-cli/.babelrc b/local-cli/.babelrc deleted file mode 100644 index 88d637f..0000000 --- a/local-cli/.babelrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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" - ] -} diff --git a/local-cli/cli.json b/local-cli/cli.json deleted file mode 100644 index 76414c5..0000000 --- a/local-cli/cli.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "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 - } - } -} diff --git a/local-cli/index.js b/local-cli/index.js deleted file mode 100644 index 0f38a3d..0000000 --- a/local-cli/index.js +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Created by tdzl2003 on 2/22/16. - */ -module.exports = require('./lib'); \ No newline at end of file diff --git a/local-cli/src/api.js b/local-cli/src/api.js deleted file mode 100644 index 8204a3e..0000000 --- a/local-cli/src/api.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * 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; diff --git a/local-cli/src/app.js b/local-cli/src/app.js deleted file mode 100644 index 6d805e4..0000000 --- a/local-cli/src/app.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * 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'); - }, -} diff --git a/local-cli/src/bundle.js b/local-cli/src/bundle.js deleted file mode 100644 index 26e69c2..0000000 --- a/local-cli/src/bundle.js +++ /dev/null @@ -1,508 +0,0 @@ -/** - * 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 '); - 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 '); - 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 '); - process.exit(1); - } - - await diffFromPackage(origin, next, realOutput, 'main.jsbundle', v => { - const m = /^Payload\/[^/]+\/(.+)$/.exec(v); - return m && m[1]; - }); - - console.log(`${realOutput} generated.`); - }, -}; diff --git a/local-cli/src/index.js b/local-cli/src/index.js deleted file mode 100644 index 9d613f9..0000000 --- a/local-cli/src/index.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Created by tdzl2003 on 2/13/16. - */ - -const {loadSession} = require('./api'); - -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); -} - -const commands = { - ...require('./user').commands, - ...require('./bundle').commands, - ...require('./app').commands, - ...require('./package').commands, - ...require('./versions').commands, - help: printUsage, -}; - -exports.run = function () { - 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); - }); -}; diff --git a/local-cli/src/package.js b/local-cli/src/package.js deleted file mode 100644 index 8dfc80a..0000000 --- a/local-cli/src/package.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * 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 '); - } - 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 '); - } - 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); - }, -}; diff --git a/local-cli/src/user.js b/local-cli/src/user.js deleted file mode 100644 index 73ba844..0000000 --- a/local-cli/src/user.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 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]}`); - } - } - }, -} diff --git a/local-cli/src/utils/index.js b/local-cli/src/utils/index.js deleted file mode 100644 index 48e6dba..0000000 --- a/local-cli/src/utils/index.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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 }; -} diff --git a/local-cli/src/versions.js b/local-cli/src/versions.js deleted file mode 100644 index 036dcd7..0000000 --- a/local-cli/src/versions.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * 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 --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.'); - } -}; diff --git a/package.json b/package.json index 371404d..616ceca 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,7 @@ "main": "lib/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build-lib": "$ANDROID_HOME/ndk-bundle/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_LIBS_OUT=android/lib", - "prepare": "node_modules/.bin/babel local-cli/src --out-dir local-cli/lib" + "build-lib": "$ANDROID_HOME/ndk-bundle/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_LIBS_OUT=android/lib" }, "repository": { "type": "git", @@ -27,31 +26,5 @@ "react-native": ">=0.27.0" }, "homepage": "https://github.com/reactnativecn/react-native-pushy#readme", - "dependencies": { - "app-info-parser": "^0.3.5", - "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", - "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" - } + "dependencies": {} } diff --git a/react-native-pushy-cli/.babelrc b/react-native-pushy-cli/.babelrc deleted file mode 100644 index c4755eb..0000000 --- a/react-native-pushy-cli/.babelrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "plugins": [ - "transform-es2015-modules-commonjs", - "transform-strict-mode" - ] -} diff --git a/react-native-pushy-cli/.npmignore b/react-native-pushy-cli/.npmignore deleted file mode 100644 index b8121b2..0000000 --- a/react-native-pushy-cli/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -/.idea -/src -/.babelrc -/.npmignore -/.eslintrc -/.nvmrc -/.travis.yml diff --git a/react-native-pushy-cli/package.json b/react-native-pushy-cli/package.json deleted file mode 100644 index 81a915d..0000000 --- a/react-native-pushy-cli/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "react-native-update-cli", - "version": "0.1.0", - "description": "Command tools for javaScript updater with `pushy` service for react native apps.", - "main": "index.js", - "bin": { - "pushy": "lib/cli.js" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "prepublish": "node_modules/.bin/babel src --out-dir lib" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/reactnativecn/react-native-pushy.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", - "devDependencies": { - "babel-cli": "^6.5.1", - "babel-plugin-transform-es2015-modules-commonjs": "^6.5.2", - "babel-plugin-transform-strict-mode": "^6.5.2" - } -} diff --git a/react-native-pushy-cli/src/cli.js b/react-native-pushy-cli/src/cli.js deleted file mode 100755 index 51d214b..0000000 --- a/react-native-pushy-cli/src/cli.js +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env node -/** - * Created by tdzl2003 on 2/13/16. - */ - -import * as path from 'path'; -import * as fs from 'fs-extra'; - -const CLI_MODULE_PATH = function() { - return path.resolve( - process.cwd(), - 'node_modules', - 'react-native-update', - 'local-cli' - ); -}; - -const PACKAGE_JSON_PATH = function() { - return path.resolve( - process.cwd(), - 'node_modules', - 'react-native-update', - 'package.json' - ); -}; - -checkForVersionCommand(); - -let cli; -const cliPath = CLI_MODULE_PATH(); -if (fs.existsSync(cliPath)) { - cli = require(cliPath); -} - -if (cli) { - cli.run(); -} else { - console.error('Are you at home directory of a react-native project?'); - console.error('`pushy install` is under development, please run `npm install react-native-update` to install pushy manually.'); - process.exit(1); -} - -function checkForVersionCommand() { - if (process.argv.indexOf('-v') >= 0 || process.argv[2] === 'version') { - console.log('react-native-update-cli: ' + require('../package.json').version); - try { - 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(); - } -}