1
0
mirror of https://gitcode.com/github-mirrors/react-native-update-cli.git synced 2025-09-17 18:06:10 +08:00
Code Issues Packages Projects Releases Wiki Activity GitHub Gitee

Compare commits

..

1 Commits

Author SHA1 Message Date
sunnylqm
38fd6ae7b0 v1.0.9 2020-03-16 19:10:18 +08:00
15 changed files with 3334 additions and 4061 deletions

1
.gitignore vendored
View File

@@ -104,4 +104,3 @@ dist
.tern-port
lib/
.DS_Store

29
LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, reactnativecn
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -2,11 +2,15 @@
"useCommand": true,
"defaultCommand": "help",
"commands": {
"help": {},
"help": {
},
"login": {},
"logout": {},
"me": {},
"login":{
},
"logout": {
},
"me": {
},
"createApp": {
"options": {
@@ -25,7 +29,8 @@
}
}
},
"deleteApp": {},
"deleteApp": {
},
"selectApp": {
"options": {
"platform": {
@@ -34,10 +39,10 @@
}
},
"uploadIpa": {},
"uploadApk": {},
"parseIpa": {},
"parseApk": {},
"uploadIpa": {
},
"uploadApk": {
},
"packages": {
"options": {
"platform": {
@@ -80,33 +85,6 @@
},
"packageId": {
"hasValue": true
},
"packageVersion": {
"hasValue": true
}
}
},
"updateVersionInfo": {
"options": {
"platform": {
"hasValue": true
},
"versionId": {
"hasValue": true
},
"name": {
"default": false,
"hasValue": true
},
"description": {
"default": false,
"hasValue": true
},
"metaInfo": {
"default": false,
"hasValue": true
}
}
},
@@ -124,7 +102,7 @@
"platform": {
"hasValue": true
},
"bundleName": {
"bundleName":{
"default": "index.bundlejs",
"hasValue": true
},
@@ -133,15 +111,15 @@
"hasValue": true
},
"intermediaDir": {
"default": ".pushy/intermedia/${platform}",
"default": "build/intermedia/${platform}",
"hasValue": true
},
"output": {
"default": ".pushy/output/${platform}.${time}.ppk",
"default": "build/output/${platform}.${time}.ppk",
"hasValue": true
},
"sourcemap": {
"default": false
"verbose": {
}
}
},
@@ -152,7 +130,7 @@
"description": "Create diff patch",
"options": {
"output": {
"default": ".pushy/output/diff",
"default": "build/output/diff",
"hasValue": true
}
}
@@ -161,7 +139,7 @@
"description": "Create diff patch from a Android package(.apk)",
"options": {
"output": {
"default": ".pushy/output/diff-${time}.apk-patch",
"default": "build/output/diff-${time}.apk-patch",
"hasValue": true
}
}
@@ -170,40 +148,13 @@
"description": "Create diff patch from a iOS package(.ipa)",
"options": {
"output": {
"default": ".pushy/output/diff-${time}.ipa-patch",
"hasValue": true
}
}
},
"hdiff": {
"description": "Create hdiff patch",
"options": {
"output": {
"default": ".pushy/output/hdiff",
"hasValue": true
}
}
},
"hdiffFromApk": {
"description": "Create hdiff patch from a Android package(.apk)",
"options": {
"output": {
"default": ".pushy/output/hdiff-${time}.apk-patch",
"hasValue": true
}
}
},
"hdiffFromIpa": {
"description": "Create hdiff patch from a iOS package(.ipa)",
"options": {
"output": {
"default": ".pushy/output/hdiff-${time}.ipa-patch",
"default": "build/output/diff-${time}.ipa-patch",
"hasValue": true
}
}
}
},
"globalOptions": {
"globalOptions":{
"no-interactive": {
"default": false
}

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-update-cli",
"version": "1.20.1",
"version": "1.0.9",
"description": "Command tools for javaScript updater with `pushy` service for react native apps.",
"main": "index.js",
"bin": {
@@ -12,6 +12,7 @@
"cli.json"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepublish": "babel src --out-dir lib"
},
"repository": {
@@ -31,30 +32,30 @@
},
"homepage": "https://github.com/reactnativecn/react-native-pushy/tree/master/react-native-pushy-cli",
"dependencies": {
"app-info-parser": "github:sunnylqm/app-info-parser#fix/support-ascii-versionname",
"app-info-parser": "^0.3.9",
"cli-arguments": "^0.2.1",
"filesize-parser": "^1.5.0",
"fs-extra": "8",
"gradle-to-js": "^2.0.1",
"node-fetch": "^2.6.1",
"fs-extra": "^8.1.0",
"gradle-to-js": "^2.0.0",
"node-fetch": "^2.6.0",
"progress": "^2.0.3",
"properties": "^1.2.1",
"read": "^1.0.7",
"request": "^2.88.2",
"tcp-ping": "^0.1.1",
"tty-table": "4.2",
"update-notifier": "^5.1.0",
"tty-table": "2.8",
"uid-safe": "^2.1.5",
"update-notifier": "^4.1.0",
"yauzl": "^2.10.0",
"yazl": "2.5.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "^10.1.0",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"babel-plugin-transform-es2015-spread": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0"
},
"engines": {
"node": ">= 10"
"node": ">= 8"
}
}

View File

@@ -1,17 +0,0 @@
diff --git a/src/xml-parser/binary.js b/src/xml-parser/binary.js
index e20cc96e8131078ea81afeb31af46142a52da58e..f0f788cc555046f0dab47f8854f26344e0d5d714 100644
--- a/src/xml-parser/binary.js
+++ b/src/xml-parser/binary.js
@@ -547,8 +547,10 @@ var BinaryXmlParser = /*#__PURE__*/function () {
attr.nodeName = attr.name = this.strings[nameRef];
if (valueRef > 0) {
// some apk have versionName with special characters
- if (attr.name === 'versionName') {
- this.strings[valueRef] = this.strings[valueRef].replace(/[^\d\w-.]/g, '');
+ if (attr.name === 'versionName') {
+ // only keep printable characters
+ // https://www.ascii-code.com/characters/printable-characters
+ this.strings[valueRef] = this.strings[valueRef].replace(/[^\x21-\x7E]/g, '')
}
attr.value = this.strings[valueRef];
}

3257
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,15 @@
import fetch from 'node-fetch';
const defaultEndpoint = 'https://update.reactnative.cn/api';
let host = process.env.PUSHY_REGISTRY || defaultEndpoint;
import fs from 'fs';
/**
* Created by tdzl2003 on 2/13/16.
*/
const fetch = require('node-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';
import packageJson from '../package.json';
import tcpp from 'tcp-ping';
import util from 'util';
import path from 'path';
import filesizeParser from 'filesize-parser';
import { pricingPageUrl } from './utils';
const packageJson = require('../package.json');
const tcpp = require('tcp-ping');
const util = require('util');
const tcpPing = util.promisify(tcpp.ping);
@@ -18,18 +18,10 @@ let savedSession = undefined;
const userAgent = `react-native-update-cli/${packageJson.version}`;
export const getSession = function () {
return session;
};
export const replaceSession = function (newSession) {
session = newSession;
};
export const loadSession = async function () {
exports.loadSession = async function() {
if (fs.existsSync('.update')) {
try {
replaceSession(JSON.parse(fs.readFileSync('.update', 'utf8')));
exports.replaceSession(JSON.parse(fs.readFileSync('.update', 'utf8')));
savedSession = session;
} catch (e) {
console.error(
@@ -40,7 +32,15 @@ export const loadSession = async function () {
}
};
export const saveSession = function () {
exports.getSession = function() {
return session;
};
exports.replaceSession = function(newSession) {
session = newSession;
};
exports.saveSession = function() {
// Only save on change.
if (session !== savedSession) {
const current = session;
@@ -50,25 +50,18 @@ export const saveSession = function () {
}
};
export const closeSession = function () {
exports.closeSession = function() {
if (fs.existsSync('.update')) {
fs.unlinkSync('.update');
savedSession = undefined;
}
session = undefined;
host = process.env.PUSHY_REGISTRY || defaultEndpoint;
host = process.env.PUSHY_REGISTRY || 'https://update.reactnative.cn';
};
async function query(url, options) {
const resp = await fetch(url, options);
const text = await resp.text();
let json;
try {
json = JSON.parse(text);
} catch (e) {
throw new Error(`Server error: ${text}`);
}
const json = await resp.json();
if (resp.status !== 200) {
throw Object.assign(new Error(json.message || json.error), {
status: resp.status,
@@ -78,7 +71,7 @@ async function query(url, options) {
}
function queryWithoutBody(method) {
return function (api) {
return function(api) {
return query(host + api, {
method,
headers: {
@@ -90,7 +83,7 @@ function queryWithoutBody(method) {
}
function queryWithBody(method) {
return function (api, body) {
return function(api, body) {
return query(host + api, {
method,
headers: {
@@ -103,44 +96,31 @@ function queryWithBody(method) {
};
}
export const get = queryWithoutBody('GET');
export const post = queryWithBody('POST');
export const put = queryWithBody('PUT');
export const doDelete = queryWithBody('DELETE');
exports.get = queryWithoutBody('GET');
exports.post = queryWithBody('POST');
exports.put = queryWithBody('PUT');
exports.doDelete = queryWithBody('DELETE');
export async function uploadFile(fn, key) {
const { url, backupUrl, formData, maxSize } = await post('/upload', {
ext: path.extname(fn),
});
async function uploadFile(fn, key) {
const { url, backupUrl, formData } = await exports.post('/upload', {});
let realUrl = url;
if (backupUrl) {
if (global.USE_ACC_OSS) {
const pingResult = await tcpPing({
address: url.replace('https://', ''),
attempts: 4,
timeout: 1000,
});
// console.log({pingResult});
if (isNaN(pingResult.avg) || pingResult.avg > 150) {
realUrl = backupUrl;
} else {
const pingResult = await tcpPing({
address: url.replace('https://', ''),
attempts: 4,
timeout: 1000,
});
// console.log({pingResult});
if (isNaN(pingResult.avg) || pingResult.avg > 150) {
realUrl = backupUrl;
}
}
// console.log({realUrl});
}
const fileSize = fs.statSync(fn).size;
if (maxSize && fileSize > filesizeParser(maxSize)) {
throw new Error(
`此文件大小${(fileSize / 1048576).toFixed(
1,
)}m, 超出当前额度${maxSize}。您可以考虑升级付费业务以提升此额度。详情请访问: ${pricingPageUrl}`,
);
}
const bar = new ProgressBar(' 上传中 [:bar] :percent :etas', {
const bar = new ProgressBar(' Uploading [:bar] :percent :etas', {
complete: '=',
incomplete: ' ',
total: fileSize,
@@ -152,7 +132,7 @@ export async function uploadFile(fn, key) {
}
formData.file = fs.createReadStream(fn);
formData.file.on('data', function (data) {
formData.file.on('data', function(data) {
bar.tick(data.length);
});
request.post(
@@ -169,9 +149,17 @@ export async function uploadFile(fn, key) {
Object.assign(new Error(body), { status: resp.statusCode }),
);
}
resolve({ hash: formData.key });
resolve(
body
? // qiniu
JSON.parse(body)
: // aliyun oss
{ hash: formData.key },
);
},
);
});
return info;
}
exports.uploadFile = uploadFile;

View File

@@ -1,8 +1,15 @@
import { question } from './utils';
import fs from 'fs';
import Table from 'tty-table';
/**
* Created by tdzl2003 on 2/13/16.
*/
import { post, get, doDelete } from './api';
import {question} from './utils';
import * as fs from 'fs-extra';
const {
post,
get,
doDelete,
} = require('./api');
const validPlatforms = {
ios: 1,
@@ -11,48 +18,34 @@ const validPlatforms = {
export function checkPlatform(platform) {
if (!validPlatforms[platform]) {
throw new Error(`无法识别的平台 '${platform}'`);
throw new Error(`Invalid platform '${platform}'`);
}
return 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!`,
);
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!`,
);
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;
const header = [
{ value: '应用 id' },
{ value: '应用名称' },
{ value: '平台' },
];
const rows = [];
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) {
rows.push([app.id, app.name, app.platform]);
console.log(`${app.id}) ${app.name}(${app.platform})`);
}
console.log(Table(header, rows).render());
if (platform) {
console.log(`\ ${list.length} ${platform} 个应用`);
console.log(`\nTotal ${list.length} ${platform} apps`);
} else {
console.log(`\ ${list.length} 个应用`);
console.log(`\nTotal ${list.length} apps`);
}
return list;
}
@@ -61,8 +54,8 @@ export async function chooseApp(platform) {
const list = await listApp(platform);
while (true) {
const id = await question('输入应用 id:');
const app = list.find((v) => v.id === (id | 0));
const id = await question('Enter appId:');
const app = list.find(v=>v.id === (id|0));
if (app) {
return app;
}
@@ -70,58 +63,48 @@ export async function chooseApp(platform) {
}
export const commands = {
createApp: async function ({ options }) {
const name = options.name || (await question('应用名称:'));
const { downloadUrl } = options;
const platform = checkPlatform(
options.platform || (await question('平台(ios/android):')),
);
const { id } = await post('/app/create', { name, platform });
console.log(`已成功创建应用id: ${id}`);
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 },
options: {platform, downloadUrl},
});
},
deleteApp: async function ({ args, options }) {
const { platform } = options;
deleteApp: async function ({args, options}) {
const {platform} = options;
const id = args[0] || chooseApp(platform);
if (!id) {
console.log('已取消');
console.log('Canceled');
}
await doDelete(`/app/${id}`);
console.log('操作成功');
console.log('Ok.');
},
apps: async function ({ options }) {
const { platform } = options;
apps: async function ({options}){
const {platform} = options;
listApp(platform);
},
selectApp: async function ({ args, options }) {
const platform = checkPlatform(
options.platform || (await question('平台(ios/android):')),
);
const id = args[0] ? parseInt(args[0]) : (await chooseApp(platform)).id;
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.',
);
console.error('Failed to parse file `update.json`. Try to remove it manually.');
throw e;
}
}
const { appKey } = await get(`/app/${id}`);
const {appKey} = await get(`/app/${id}`);
updateInfo[platform] = {
appId: id,
appKey,
};
fs.writeFileSync(
'update.json',
JSON.stringify(updateInfo, null, 4),
'utf8',
);
fs.writeFileSync('update.json', JSON.stringify(updateInfo, null, 4), 'utf8');
},
};
}

View File

@@ -1,23 +1,40 @@
import path from 'path';
/**
* 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, printVersionCommand } from './utils';
import { question } from './utils';
import { checkPlatform } from './app';
import { spawn, spawnSync } from 'child_process';
const { spawn, spawnSync, execSync } = require('child_process');
const g2js = require('gradle-to-js/lib/parser');
import os from 'os';
const properties = require('properties');
const os = require('os');
let bsdiff, hdiff, diff;
var diff;
try {
bsdiff = require('node-bsdiff').diff;
} catch (e) {}
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.');
};
}
try {
hdiff = require('node-hdiffpatch').diff;
} catch (e) {}
function exec(command) {
const commandResult = spawnSync(command, {
shell: true,
stdio: 'inherit',
});
if (commandResult.error) {
throw commandResult.error;
}
}
async function runReactNativeBundleCommand(
bundleName,
@@ -28,50 +45,19 @@ async function runReactNativeBundleCommand(
sourcemapOutput,
config,
) {
let gradleConfig = {};
if (platform === 'android') {
gradleConfig = await checkGradleConfig();
if (gradleConfig.crunchPngs !== false) {
console.warn(
'android的crunchPngs选项似乎尚未禁用如已禁用则请忽略此提示这可能导致热更包体积异常增大具体请参考 https://pushy.reactnative.cn/docs/getting-started.html#%E7%A6%81%E7%94%A8-android-%E7%9A%84-crunch-%E4%BC%98%E5%8C%96 \n',
);
}
}
let reactNativeBundleArgs = [];
let envArgs = process.env.PUSHY_ENV_ARGS;
if (envArgs) {
Array.prototype.push.apply(
reactNativeBundleArgs,
envArgs.trim().split(/\s+/),
);
Array.prototype.push.apply(reactNativeBundleArgs, envArgs.trim().split(/\s+/));
}
fs.emptyDirSync(outputFolder);
let cliPath = require.resolve('react-native/local-cli/cli.js', {
paths: [process.cwd()],
});
try {
require.resolve('expo-router', {
paths: [process.cwd()],
});
console.log(`expo-router detected, will use @expo/cli to bundle.\n`);
// if using expo-router, use expo-cli
cliPath = require.resolve('@expo/cli', {
paths: [process.cwd()],
});
} catch (e) {}
const bundleCommand = cliPath.includes('@expo/cli')
? 'export:embed'
: 'bundle';
Array.prototype.push.apply(reactNativeBundleArgs, [
cliPath,
bundleCommand,
path.join("node_modules", "react-native", "local-cli", "cli.js"),
"bundle",
'--assets-dest',
outputFolder,
'--bundle-output',
@@ -94,60 +80,23 @@ async function runReactNativeBundleCommand(
}
const reactNativeBundleProcess = spawn('node', reactNativeBundleArgs);
console.log(
`Running bundle command: node ${reactNativeBundleArgs.join(' ')}`,
);
console.log(`Running bundle command: node ${reactNativeBundleArgs.join(' ')}`);
return new Promise((resolve, reject) => {
reactNativeBundleProcess.stdout.on('data', (data) => {
reactNativeBundleProcess.stdout.on('data', data => {
console.log(data.toString().trim());
});
reactNativeBundleProcess.stderr.on('data', (data) => {
reactNativeBundleProcess.stderr.on('data', data => {
console.error(data.toString().trim());
});
reactNativeBundleProcess.on('close', async (exitCode) => {
reactNativeBundleProcess.on('close', async exitCode => {
if (exitCode) {
reject(
new Error(
`"react-native bundle" command exited with code ${exitCode}.`,
),
);
reject(new Error(`"react-native bundle" command exited with code ${exitCode}.`));
} else {
let hermesEnabled = false;
if (platform === 'android') {
const gradlePropeties = await new Promise((resolve) => {
properties.parse(
'./android/gradle.properties',
{ path: true },
function (error, props) {
if (error) {
console.error(error);
resolve(null);
}
resolve(props);
},
);
});
hermesEnabled = gradlePropeties.hermesEnabled;
if (typeof hermesEnabled !== 'boolean')
hermesEnabled = gradleConfig.enableHermes;
} else if (
platform === 'ios' &&
fs.existsSync('ios/Pods/hermes-engine')
) {
hermesEnabled = true;
}
if (hermesEnabled) {
await compileHermesByteCode(
bundleName,
outputFolder,
sourcemapOutput,
);
await compileHermesByteCode(bundleName, outputFolder);
}
resolve(null);
}
@@ -161,73 +110,35 @@ function getHermesOSBin() {
if (os.platform() === 'linux') return 'linux64-bin';
}
async function checkGradleConfig() {
async function compileHermesByteCode(bundleName, outputFolder) {
let enableHermes = false;
let crunchPngs;
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')
) {
if (packagerConfig.includes('enableHermes') && packagerConfig.includes('true')) {
enableHermes = true;
break;
}
}
crunchPngs = gradleConfig.android.buildTypes.release.crunchPngs;
} catch (e) {}
return {
enableHermes,
crunchPngs,
};
}
async function compileHermesByteCode(
bundleName,
outputFolder,
sourcemapOutput,
) {
console.log(`Hermes enabled, now compiling to hermes bytecode:\n`);
// >= rn 0.69
const rnDir = path.dirname(
require.resolve('react-native', {
paths: [process.cwd()],
}),
);
let hermesPath = path.join(rnDir, `/sdks/hermesc/${getHermesOSBin()}`);
// < rn 0.69
if (!fs.existsSync(hermesPath)) {
hermesPath = `node_modules/hermes-engine/${getHermesOSBin()}`;
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' },
);
}
const hermesCommand = `${hermesPath}/hermesc`;
const args = [
'-emit-binary',
'-out',
path.join(outputFolder, bundleName),
path.join(outputFolder, bundleName),
'-O',
];
if (sourcemapOutput) {
args.push('-output-source-map');
}
console.log(
'Running hermesc: ' + hermesCommand + ' ' + args.join(' ') + '\n',
);
spawnSync(hermesCommand, args, {
stdio: 'ignore',
});
}
async function pack(dir, output) {
console.log('Packing');
fs.ensureDirSync(path.dirname(output));
await new Promise((resolve, reject) => {
const zipfile = new ZipFile();
var zipfile = new ZipFile();
function addDirectory(root, rel) {
if (rel) {
@@ -252,15 +163,13 @@ async function pack(dir, output) {
addDirectory(dir, '');
zipfile.outputStream.on('error', (err) => reject(err));
zipfile.outputStream
.pipe(fs.createWriteStream(output))
.on('close', function () {
resolve();
});
zipfile.outputStream.on('error', err => reject(err));
zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function() {
resolve();
});
zipfile.end();
});
console.log('ppk热更包已生成并保存到: ' + output);
console.log('Bundled saved to: ' + output);
}
function readEntire(entry, zipFile) {
@@ -304,30 +213,26 @@ async function diffFromPPK(origin, next, output) {
if (entry.fileName === 'index.bundlejs') {
// This is source.
return readEntire(entry, zipFile).then((v) => (originSource = v));
return readEntire(entry, zipFile).then(v => (originSource = v));
}
}
});
if (!originSource) {
throw new Error(
`Bundle file not found! Please use default bundle file name and path.`,
);
throw new Error(`Bundle file not found! Please use default bundle file name and path.`);
}
const copies = {};
const zipfile = new ZipFile();
var zipfile = new ZipFile();
const writePromise = new Promise((resolve, reject) => {
zipfile.outputStream.on('error', (err) => {
zipfile.outputStream.on('error', err => {
throw err;
});
zipfile.outputStream
.pipe(fs.createWriteStream(output))
.on('close', function () {
resolve();
});
zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function() {
resolve();
});
});
const addedEntry = {};
@@ -356,12 +261,9 @@ async function diffFromPPK(origin, next, output) {
}
} else if (entry.fileName === 'index.bundlejs') {
//console.log('Found bundle');
return readEntire(entry, nextZipfile).then((newSource) => {
return readEntire(entry, nextZipfile).then(newSource => {
//console.log('Begin diff');
zipfile.addBuffer(
diff(originSource, newSource),
'index.bundlejs.patch',
);
zipfile.addBuffer(diff(originSource, newSource), 'index.bundlejs.patch');
//console.log('End diff');
});
} else {
@@ -386,7 +288,7 @@ async function diffFromPPK(origin, next, output) {
addEntry(basename(entry.fileName));
return new Promise((resolve, reject) => {
nextZipfile.openReadStream(entry, function (err, readStream) {
nextZipfile.openReadStream(entry, function(err, readStream) {
if (err) {
return reject(err);
}
@@ -402,7 +304,7 @@ async function diffFromPPK(origin, next, output) {
const deletes = {};
for (let k in originEntries) {
for (var k in originEntries) {
if (!newEntries[k]) {
console.log('Delete ' + k);
deletes[k] = 1;
@@ -410,21 +312,12 @@ async function diffFromPPK(origin, next, output) {
}
//console.log({copies, deletes});
zipfile.addBuffer(
Buffer.from(JSON.stringify({ copies, deletes })),
'__diff.json',
);
zipfile.addBuffer(Buffer.from(JSON.stringify({ copies, deletes })), '__diff.json');
zipfile.end();
await writePromise;
}
async function diffFromPackage(
origin,
next,
output,
originBundleName,
transformPackagePath = (v) => v,
) {
async function diffFromPackage(origin, next, output, originBundleName, transformPackagePath = v => v) {
fs.ensureDirSync(path.dirname(output));
const originEntries = {};
@@ -446,30 +339,26 @@ async function diffFromPackage(
if (fn === originBundleName) {
// This is source.
return readEntire(entry, zipFile).then((v) => (originSource = v));
return readEntire(entry, zipFile).then(v => (originSource = v));
}
}
});
if (!originSource) {
throw new Error(
`Bundle file not found! Please use default bundle file name and path.`,
);
throw new Error(`Bundle file not found! Please use default bundle file name and path.`);
}
const copies = {};
const zipfile = new ZipFile();
var zipfile = new ZipFile();
const writePromise = new Promise((resolve, reject) => {
zipfile.outputStream.on('error', (err) => {
zipfile.outputStream.on('error', err => {
throw err;
});
zipfile.outputStream
.pipe(fs.createWriteStream(output))
.on('close', function () {
resolve();
});
zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function() {
resolve();
});
});
await enumZipEntries(next, (entry, nextZipfile) => {
@@ -478,12 +367,9 @@ async function diffFromPackage(
zipfile.addEmptyDirectory(entry.fileName);
} else if (entry.fileName === 'index.bundlejs') {
//console.log('Found bundle');
return readEntire(entry, nextZipfile).then((newSource) => {
return readEntire(entry, nextZipfile).then(newSource => {
//console.log('Begin diff');
zipfile.addBuffer(
diff(originSource, newSource),
'index.bundlejs.patch',
);
zipfile.addBuffer(diff(originSource, newSource), 'index.bundlejs.patch');
//console.log('End diff');
});
} else {
@@ -499,7 +385,7 @@ async function diffFromPackage(
}
return new Promise((resolve, reject) => {
nextZipfile.openReadStream(entry, function (err, readStream) {
nextZipfile.openReadStream(entry, function(err, readStream) {
if (err) {
return reject(err);
}
@@ -526,7 +412,7 @@ function enumZipEntries(zipFn, callback) {
}
zipfile.on('end', resolve);
zipfile.on('error', reject);
zipfile.on('entry', (entry) => {
zipfile.on('entry', entry => {
const result = callback(entry, zipfile);
if (result && typeof result.then === 'function') {
result.then(() => zipfile.readEntry());
@@ -539,55 +425,16 @@ function enumZipEntries(zipFn, callback) {
});
}
function diffArgsCheck(args, options, diffFn) {
const [origin, next] = args;
if (!origin || !next) {
console.error(`Usage: pushy ${diffFn} <origin> <next>`);
process.exit(1);
}
if (diffFn.startsWith('hdiff')) {
if (!hdiff) {
console.error(
`This function needs "node-hdiffpatch".
Please run "npm i node-hdiffpatch" to install`,
);
process.exit(1);
}
diff = hdiff;
} else {
if (!bsdiff) {
console.error(
`This function needs "node-bsdiff".
Please run "npm i node-bsdiff" to install`,
);
process.exit(1);
}
diff = bsdiff;
}
const { output } = options;
return {
origin,
next,
realOutput: output.replace(/\$\{time\}/g, '' + Date.now()),
};
}
export const commands = {
bundle: async function ({ options }) {
const platform = checkPlatform(
options.platform || (await question('平台(ios/android):')),
);
bundle: async function({ options }) {
const platform = checkPlatform(options.platform || (await question('Platform(ios/android):')));
let { bundleName, entryFile, intermediaDir, output, dev, sourcemap } =
translateOptions({
...options,
platform,
});
let { bundleName, entryFile, intermediaDir, output, dev, verbose } = translateOptions({
...options,
platform,
});
const sourcemapOutput = path.join(intermediaDir, bundleName + '.map');
// const sourcemapOutput = path.join(intermediaDir, bundleName + ".map");
const realOutput = output.replace(/\$\{time\}/g, '' + Date.now());
@@ -597,21 +444,13 @@ export const commands = {
const { version, major, minor } = getRNVersion();
console.log('Bundling with react-native: ', version);
printVersionCommand();
console.log('Bundling with React Native version: ', version);
await runReactNativeBundleCommand(
bundleName,
dev,
entryFile,
intermediaDir,
platform,
sourcemap ? sourcemapOutput : '',
);
await runReactNativeBundleCommand(bundleName, dev, entryFile, intermediaDir, platform);
await pack(path.resolve(intermediaDir), realOutput);
const v = await question('是否现在上传此热更包?(Y/N)');
const v = await question('Would you like to publish it?(Y/N)');
if (v.toLowerCase() === 'y') {
await this.publish({
args: [realOutput],
@@ -623,74 +462,47 @@ export const commands = {
},
async diff({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(args, options, 'diff');
const [origin, next] = args;
const { output } = options;
await diffFromPPK(origin, next, realOutput, 'index.bundlejs');
console.log(`${realOutput} generated.`);
},
const realOutput = output.replace(/\$\{time\}/g, '' + Date.now());
async hdiff({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(args, options, 'hdiff');
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, realOutput } = diffArgsCheck(
args,
options,
'diffFromApk',
);
const [origin, next] = args;
const { output } = options;
await diffFromPackage(
origin,
next,
realOutput,
'assets/index.android.bundle',
);
console.log(`${realOutput} generated.`);
},
const realOutput = output.replace(/\$\{time\}/g, '' + Date.now());
async hdiffFromApk({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(
args,
options,
'hdiffFromApk',
);
if (!origin || !next) {
console.error('pushy diffFromApk <origin> <next>');
process.exit(1);
}
await diffFromPackage(
origin,
next,
realOutput,
'assets/index.android.bundle',
);
await diffFromPackage(origin, next, realOutput, 'assets/index.android.bundle');
console.log(`${realOutput} generated.`);
},
async diffFromIpa({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(
args,
options,
'diffFromIpa',
);
const [origin, next] = args;
const { output } = options;
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', (v) => {
const m = /^Payload\/[^/]+\/(.+)$/.exec(v);
return m && m[1];
});
const realOutput = output.replace(/\$\{time\}/g, '' + Date.now());
console.log(`${realOutput} generated.`);
},
if (!origin || !next) {
console.error('pushy diffFromIpa <origin> <next>');
process.exit(1);
}
async hdiffFromIpa({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(
args,
options,
'hdiffFromIpa',
);
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', (v) => {
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', v => {
const m = /^Payload\/[^/]+\/(.+)$/.exec(v);
return m && m[1];
});

View File

@@ -1,23 +1,42 @@
#!/usr/bin/env node
/**
* Created by tdzl2003 on 2/13/16.
*/
import { loadSession } from './api';
import updateNotifier from 'update-notifier';
import { printVersionCommand } from './utils/index.js';
import pkg from '../package.json';
const {loadSession} = require('./api');
const updateNotifier = require('update-notifier');
const pkg = require('../package.json');
updateNotifier({ pkg }).notify({ isGlobal: true });
updateNotifier({pkg}).notify();
function printUsage({ args }) {
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.',
);
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,
@@ -28,25 +47,21 @@ const commands = {
};
function run() {
if (process.argv.indexOf('-v') >= 0 || process.argv[2] === 'version') {
printVersionCommand();
process.exit();
}
printVersionCommand();
const argv = require('cli-arguments').parse(require('../cli.json'));
global.NO_INTERACTIVE = argv.options['no-interactive'];
global.USE_ACC_OSS = argv.options['acc'];
loadSession()
.then(() => commands[argv.command](argv))
.catch((err) => {
.then(()=>commands[argv.command](argv))
.catch(err=>{
if (err.status === 401) {
console.log('尚未登录。\n请在项目目录中运行`pushy login`命令来登录');
console.log('Not loggined.\nRun `pushy login` at your project directory to login.');
return;
}
console.error(err.stack);
process.exit(-1);
});
}
};
run();
run();

View File

@@ -1,37 +1,38 @@
import { get, post, uploadFile } from './api';
/**
* Created by tdzl2003 on 4/2/16.
*/
const { get, post, uploadFile } = require('./api');
import { question, saveToLocal } from './utils';
import { checkPlatform, getSelectedApp } from './app';
import { getApkInfo, getIpaInfo } from './utils';
import Table from 'tty-table';
import uid from 'uid-safe';
const uidSync = uid.sync;
const Table = require('tty-table');
export async function listPackage(appId) {
const { data } = await get(`/app/${appId}/package/list?limit=1000`);
const header = [{ value: '原生包 Id' }, { value: '原生版本' }];
const header = [{ value: 'Package Id' }, { value: 'Version' }];
const rows = [];
for (const pkg of data) {
const { version } = pkg;
let versionInfo = '';
if (version) {
versionInfo = `, 已绑定:${version.name} (${version.id})`;
versionInfo = ` - ${version.id} ${version.hash.slice(0, 8)} ${version.name}`;
} else {
// versionInfo = ' (newest)';
versionInfo = ' (newest)';
}
let output = pkg.name;
if (pkg.status === 'paused') {
output += '(已暂停)';
}
if (pkg.status === 'expired') {
output += '(已过期)';
}
output += versionInfo;
rows.push([pkg.id, output]);
rows.push([pkg.id, `${pkg.name}(${pkg.status})${versionInfo}`]);
}
console.log(Table(header, rows).render());
console.log(`\n ${data.length} 个包`);
console.log(`\nTotal ${data.length} package(s).`);
return data;
}
@@ -39,8 +40,8 @@ export async function choosePackage(appId) {
const list = await listPackage(appId);
while (true) {
const id = await question('输入原生包 id:');
const app = list.find((v) => v.id === (id | 0));
const id = await question('Enter Package Id:');
const app = list.find(v => v.id === (id | 0));
if (app) {
return app;
}
@@ -48,32 +49,15 @@ export async function choosePackage(appId) {
}
export const commands = {
uploadIpa: async function ({ args }) {
uploadIpa: async function({ args }) {
const fn = args[0];
if (!fn || !fn.endsWith('.ipa')) {
throw new Error('使用方法: pushy uploadIpa ipa后缀文件');
throw new Error('Usage: pushy uploadIpa <ipaFile>');
}
const {
versionName,
buildTime,
appId: appIdInPkg,
appKey: appKeyInPkg,
} = await getIpaInfo(fn);
const { appId, appKey } = await getSelectedApp('ios');
const { versionName, buildTime } = await getIpaInfo(fn);
const { appId } = await getSelectedApp('ios');
if (appIdInPkg && appIdInPkg != appId) {
throw new Error(
`appId不匹配当前ipa: ${appIdInPkg}, 当前update.json: ${appId}`,
);
}
if (appKeyInPkg && appKeyInPkg !== appKey) {
throw new Error(
`appKey不匹配当前ipa: ${appKeyInPkg}, 当前update.json: ${appKey}`,
);
}
const { hash } = await uploadFile(fn);
const { hash } = await uploadFile(fn, `i/${uidSync(19)}`);
const { id } = await post(`/app/${appId}/package/create`, {
name: versionName,
@@ -81,36 +65,17 @@ export const commands = {
buildTime,
});
saveToLocal(fn, `${appId}/package/${id}.ipa`);
console.log(
`已成功上传ipa原生包id: ${id}, version: ${versionName}, buildTime: ${buildTime}`,
);
console.log(`Ipa uploaded: ${id}`);
},
uploadApk: async function ({ args }) {
uploadApk: async function({ args }) {
const fn = args[0];
if (!fn || !fn.endsWith('.apk')) {
throw new Error('使用方法: pushy uploadApk apk后缀文件');
throw new Error('Usage: pushy uploadApk <apkFile>');
}
const {
versionName,
buildTime,
appId: appIdInPkg,
appKey: appKeyInPkg,
} = await getApkInfo(fn);
const { appId, appKey } = await getSelectedApp('android');
const { versionName, buildTime } = await getApkInfo(fn);
const { appId } = await getSelectedApp('android');
if (appIdInPkg && appIdInPkg != appId) {
throw new Error(
`appId不匹配当前apk: ${appIdInPkg}, 当前update.json: ${appId}`,
);
}
if (appKeyInPkg && appKeyInPkg !== appKey) {
throw new Error(
`appKey不匹配当前apk: ${appKeyInPkg}, 当前update.json: ${appKey}`,
);
}
const { hash } = await uploadFile(fn);
const { hash } = await uploadFile(fn, `a/${uidSync(19)}`);
const { id } = await post(`/app/${appId}/package/create`, {
name: versionName,
@@ -118,28 +83,10 @@ export const commands = {
buildTime,
});
saveToLocal(fn, `${appId}/package/${id}.apk`);
console.log(
`已成功上传apk原生包id: ${id}, version: ${versionName}, buildTime: ${buildTime}`,
);
console.log(`Apk uploaded: ${id}`);
},
parseIpa: async function ({ args }) {
const fn = args[0];
if (!fn || !fn.endsWith('.ipa')) {
throw new Error('使用方法: pushy parseIpa ipa后缀文件');
}
console.log(await getIpaInfo(fn));
},
parseApk: async function ({ args }) {
const fn = args[0];
if (!fn || !fn.endsWith('.apk')) {
throw new Error('使用方法: pushy parseApk apk后缀文件');
}
console.log(await getApkInfo(fn));
},
packages: async function ({ options }) {
const platform = checkPlatform(
options.platform || (await question('平台(ios/android):')),
);
packages: async function({ options }) {
const platform = checkPlatform(options.platform || (await question('Platform(ios/android):')));
const { appId } = await getSelectedApp(platform);
await listPackage(appId);
},

View File

@@ -1,28 +1,38 @@
import { question } from './utils';
import { post, get, replaceSession, saveSession, closeSession } from './api';
import crypto from 'crypto';
/**
* 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');
}
export const 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', {
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 });
replaceSession({token});
await saveSession();
console.log(`欢迎使用 pushy 热更新服务, ${info.name}.`);
console.log(`Welcome, ${info.name}.`);
},
logout: async function () {
logout: async function (){
await closeSession();
console.log('已退出登录');
console.log('Logged out.');
},
me: async function () {
me: async function (){
const me = await get('/user/me');
for (const k in me) {
if (k !== 'ok') {
@@ -30,4 +40,4 @@ export const commands = {
}
}
},
};
}

View File

@@ -1,10 +1,13 @@
import fs from 'fs-extra';
/**
* Created by tdzl2003 on 2/13/16.
*/
import * as fs from 'fs-extra';
import os from 'os';
import path from 'path';
import pkg from '../../package.json';
import AppInfoParser from 'app-info-parser';
const AppInfoParser = require('app-info-parser');
import read from 'read';
var read = require('read');
export function question(query, password) {
if (NO_INTERACTIVE) {
@@ -27,7 +30,7 @@ export function translateOptions(options) {
for (let key in options) {
const v = options[key];
if (typeof v === 'string') {
ret[key] = v.replace(/\$\{(\w+)\}/g, function (v, n) {
ret[key] = v.replace(/\$\{(\w+)\}/g, function(v, n) {
return options[n] || process.env[n] || v;
});
} else {
@@ -39,11 +42,7 @@ export function translateOptions(options) {
export function getRNVersion() {
const version = JSON.parse(
fs.readFileSync(
require.resolve('react-native/package.json', {
paths: [process.cwd()],
}),
),
fs.readFileSync(path.resolve('node_modules/react-native/package.json')),
).version;
// We only care about major and minor version.
@@ -57,21 +56,6 @@ export function getRNVersion() {
export async function getApkInfo(fn) {
const appInfoParser = new AppInfoParser(fn);
const bundleFile = await appInfoParser.parser.getEntry(
/assets\/index.android.bundle/,
);
if (!bundleFile) {
throw new Error(
'找不到bundle文件。请确保此apk为release版本且bundle文件名为默认的index.android.bundle',
);
}
const updateJsonFile = await appInfoParser.parser.getEntry(
/res\/raw\/update.json/,
);
let appCredential = {};
if (updateJsonFile) {
appCredential = JSON.parse(updateJsonFile.toString()).android;
}
const { versionName, application } = await appInfoParser.parse();
let buildTime = 0;
if (Array.isArray(application.metaData)) {
@@ -82,32 +66,16 @@ export async function getApkInfo(fn) {
}
}
if (buildTime == 0) {
throw new Error(
'无法获取此包的编译时间戳。请更新react-native-update到最新版本后重新打包上传。',
);
throw new Error('无法获取此包的编译时间戳。请更新react-native-update到最新版本后重新打包上传。');
}
return { versionName, buildTime, ...appCredential };
return { versionName, buildTime };
}
export async function getIpaInfo(fn) {
const appInfoParser = new AppInfoParser(fn);
const bundleFile = await appInfoParser.parser.getEntry(
/payload\/.+?\.app\/main.jsbundle/,
);
if (!bundleFile) {
throw new Error(
'找不到bundle文件。请确保此ipa为release版本且bundle文件名为默认的main.jsbundle',
);
}
const updateJsonFile = await appInfoParser.parser.getEntry(
/payload\/.+?\.app\/assets\/update.json/,
);
let appCredential = {};
if (updateJsonFile) {
appCredential = JSON.parse(updateJsonFile.toString()).ios;
}
const { CFBundleShortVersionString: versionName } =
await appInfoParser.parse();
const {
CFBundleShortVersionString: versionName,
} = await appInfoParser.parse();
let buildTimeTxtBuffer = await appInfoParser.parser.getEntry(
/payload\/.+?\.app\/pushy_build_time.txt/,
);
@@ -118,12 +86,10 @@ export async function getIpaInfo(fn) {
);
}
if (!buildTimeTxtBuffer) {
throw new Error(
'无法获取此包的编译时间戳。请更新react-native-update到最新版本后重新打包上传。',
);
throw new Error('无法获取此包的编译时间戳。请更新react-native-update到最新版本后重新打包上传。');
}
const buildTime = buildTimeTxtBuffer.toString().replace('\n', '');
return { versionName, buildTime, ...appCredential };
return { versionName, buildTime };
}
const localDir = path.resolve(os.homedir(), '.pushy');
@@ -134,20 +100,3 @@ export function saveToLocal(originPath, destName) {
// fs.ensureDirSync(path.dirname(destPath));
// fs.copyFileSync(originPath, destPath);
}
export function printVersionCommand() {
console.log('react-native-update-cli: ' + pkg.version);
try {
const PACKAGE_JSON_PATH = require.resolve(
'react-native-update/package.json',
{
paths: [process.cwd()],
},
);
console.log('react-native-update: ' + require(PACKAGE_JSON_PATH).version);
} catch (e) {
console.log('react-native-update: 无法获取版本号,请在项目目录中运行命令');
}
}
export const pricingPageUrl = 'https://pushy.reactnative.cn/pricing.html';

View File

@@ -1,31 +1,35 @@
import { get, post, put, uploadFile } from './api';
/**
* Created by tdzl2003 on 4/2/16.
*/
const {
get,
post,
put,
uploadFile,
} = require('./api');
import { question, saveToLocal } from './utils';
import { checkPlatform, getSelectedApp } from './app';
import { choosePackage } from './package';
import uid from 'uid-safe';
const uidSync = uid.sync;
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(', ');
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`;
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}`,
);
console.log(`${version.id}) ${version.hash.slice(0, 8)} ${version.name} ${packageInfo}`);
}
return data;
}
@@ -36,17 +40,10 @@ async function listVersions(appId) {
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;
case 'u': offset = Math.max(0, offset - 10); break;
case 'd': offset += 10; break;
case 'b': offset = 0; break;
case 'q': return;
}
}
}
@@ -55,21 +52,14 @@ 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)',
);
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));
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;
}
@@ -79,88 +69,47 @@ async function chooseVersion(appId) {
}
export const commands = {
publish: async function ({ args, options }) {
publish: async function({args, options}) {
const fn = args[0];
const { name, description, metaInfo } = options;
const {name, description, metaInfo } = options;
if (!fn || !fn.endsWith('.ppk')) {
throw new Error(
'使用方法: pushy publish ppk后缀文件 --platform ios|android',
);
throw new Error('Usage: pushy publish <ppkFile> --platform ios|android');
}
const platform = checkPlatform(
options.platform || (await question('平台(ios/android):')),
);
const platform = checkPlatform(options.platform || await question('Platform(ios/android):'));
const { appId } = await getSelectedApp(platform);
const { hash } = await uploadFile(fn);
const { hash } = await uploadFile(fn, `v/${uidSync(19)}`);
const { id } = await post(`/app/${appId}/version/create`, {
name: name || (await question('输入版本名称: ')) || '(未命名)',
name: name || await question('Enter version name:') || '(未命名)',
hash,
description: description || (await question('输入版本描述:')),
metaInfo: metaInfo || (await question('输入自定义的 meta info:')),
description: description || await question('Enter description:'),
metaInfo: metaInfo || await question('Enter meta info:'),
});
// TODO local diff
saveToLocal(fn, `${appId}/ppk/${id}.ppk`);
console.log(`已成功上传新热更包id: ${id}`);
console.log(`Version published: ${id}`);
const v = await question('是否现在将此热更应用到原生包上?(Y/N)');
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 } });
await this.update({args:[], options:{versionId: id, platform}});
}
},
versions: async function ({ options }) {
const platform = checkPlatform(
options.platform || (await question('平台(ios/android):')),
);
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('平台(ios/android):')),
);
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;
let pkgId;
let pkgVersion = options.packageVersion;
if (pkgVersion) {
pkgVersion = pkgVersion.trim();
const { data } = await get(`/app/${appId}/package/list?limit=1000`);
const pkg = data.find((d) => d.name === pkgVersion);
if (pkg) {
pkgId = pkg.id;
} else {
throw new Error(`未查询到匹配原生版本:${pkgVersion}`);
}
}
if (!pkgId) {
pkgId = options.packageId || (await choosePackage(appId)).id;
}
if (!pkgId) {
throw new Error('请提供 packageId 或 packageVersion 参数');
}
const pkgId = options.packageId || (await choosePackage(appId)).id;
await put(`/app/${appId}/package/${pkgId}`, {
versionId,
});
console.log('操作成功');
},
updateVersionInfo: async function ({ args, options }) {
const platform = checkPlatform(
options.platform || (await question('平台(ios/android):')),
);
const { appId } = await getSelectedApp(platform);
const versionId = options.versionId || (await chooseVersion(appId)).id;
const updateParams = {};
options.name && (updateParams.name = options.name);
options.description && (updateParams.description = options.description);
options.metaInfo && (updateParams.metaInfo = options.metaInfo);
await put(`/app/${appId}/version/${versionId}`, updateParams);
console.log('操作成功');
},
console.log('Ok.');
}
};

2914
yarn.lock Normal file

File diff suppressed because it is too large Load Diff