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

Compare commits

...

103 Commits

Author SHA1 Message Date
sunnylqm
7aaa32a5a2 v1.14.0-beta.0 2024-01-29 19:02:01 +08:00
sunnylqm
ab7920fe38 feat: support symlink 2024-01-29 19:01:11 +08:00
sunnylqm
d912ace4a7 v1.13.0 2023-09-19 18:40:12 +08:00
sunnylqm
8af4d314ce fix appid equal 2023-09-19 18:36:26 +08:00
sunnylqm
f2d5269512 feat: support rn0.71 android hermes 2023-07-20 18:27:14 +08:00
sunnylqm
fe24c4ca36 v1.12.0 2023-07-20 18:25:47 +08:00
sunnylqm
cf61c297a6 update endpoint 2023-01-29 10:35:44 +08:00
sunnylqm
f9adc700ed v1.11.0 2023-01-11 23:36:13 +08:00
sunnylqm
dcff16cbb5 support source-map 2023-01-11 23:35:39 +08:00
sunnylqm
2bb8e83010 v1.10.0 2022-07-01 23:08:16 +08:00
sunnylqm
0cfc6e4f0d support rn 69 hermes 2022-07-01 23:02:20 +08:00
sunnylqm
da7bdbfdd2 v1.9.0 2022-06-29 09:42:05 +08:00
sunnylqm
07ee28ba3b add updateVersionInfo 2022-06-29 09:41:44 +08:00
sunnylqm
dae3e4024f v1.8.1 2021-12-24 17:55:15 +08:00
sunnylqm
d673b5736a Fix selectApp id 2021-12-24 17:53:14 +08:00
sunnylqm
732845faad Cleanup headers 2021-10-28 18:01:12 +08:00
sunnylqm
bcfdd67ea8 v1.8.0 2021-10-18 18:24:15 +08:00
sunnylqm
27ea54c1ec Update package output 2021-10-18 18:22:56 +08:00
sunnylqm
ba0fa836d1 v1.7.2 2021-10-18 18:02:43 +08:00
sunnylqm
bde76094fc v1.7.1 2021-10-18 18:00:21 +08:00
sunnylqm
f1d6c3744e v1.7.0 2021-10-18 17:54:07 +08:00
sunnylqm
768484d7b5 Add packageVersion 2021-10-18 17:53:29 +08:00
sunnylqm
d6632ffcc6 v1.7.0-beta.0 2021-10-18 17:28:01 +08:00
sunnylqm
24f5b316a9 v1.6.0 2021-10-10 12:39:29 +08:00
sunnylqm
03a4108a08 汉化 2021-10-10 12:39:00 +08:00
sunnylqm
32d7ed9b00 v1.5.0 2021-09-01 14:55:09 +08:00
sunnylqm
6f3d45c3f2 Add acc option 2021-09-01 14:54:17 +08:00
sunnylqm
25cb724921 v1.4.2 2021-06-23 10:05:55 +08:00
sunnylqm
a7b79a30e8 Print server error 2021-06-23 10:05:29 +08:00
sunnylqm
11799dd0c1 v1.4.1 2021-04-11 23:29:56 +08:00
sunnylqm
2ab0cad7e5 Detect hermes first 2021-04-11 23:29:44 +08:00
sunnylqm
ec8d6a767b v1.4.0 2021-04-11 23:26:59 +08:00
sunnylqm
fa8290fcbf Re-enable gradle check for hermes 2021-04-11 23:26:43 +08:00
sunnylqm
46d308c7c2 v1.4.0-beta1 2021-04-11 18:11:24 +08:00
sunnylqm
9e721e1858 try require diff separately 2021-04-11 18:11:08 +08:00
sunnylqm
c30454976c v1.4.0-beta0 2021-04-08 16:07:10 +08:00
sunnylqm
3d73b95140 Add hdiff 2021-04-07 22:09:48 +08:00
sunnylqm
32cafd92bf v1.3.2 2021-03-05 12:27:53 +08:00
sunnylqm
ea10cd7828 Show current filesize 2021-03-05 12:27:38 +08:00
sunnylqm
574a52b126 v1.3.1 2021-01-21 17:46:16 +08:00
sunnylqm
c00a7a72fe Disable crunchPngs check 2021-01-21 17:45:15 +08:00
sunnylqm
0e8e3aa420 v1.3.0 2021-01-20 23:30:26 +08:00
sunnylqm
7426e93647 Update gitignore 2021-01-20 23:30:04 +08:00
sunnylqm
b6fb2e7d2a Check does appId/appKey match 2021-01-20 23:29:53 +08:00
sunnylqm
7f1dcbb571 Use table for listApp 2021-01-20 23:29:31 +08:00
sunnylqm
59fad93832 Change local build foler name 2021-01-11 14:06:14 +08:00
sunnylqm
3db4f803e2 v1.2.3 2021-01-05 21:54:04 +08:00
sunnylqm
56d44b8c85 Update defaultEndpoint 2021-01-05 21:53:46 +08:00
sunnylqm
ecd951265f v1.2.2 2020-12-15 13:30:19 +08:00
sunnylqm
62d455ac07 Update app-info-parser 2020-12-15 13:30:04 +08:00
sunnylqm
acfc2dd65b v1.2.1 2020-11-04 14:48:03 +08:00
sunnylqm
7f062f681a v1.2.0 2020-10-21 17:33:01 +08:00
sunnylqm
7f5b9fd7fd Check gradleConfig 2020-10-21 15:50:44 +08:00
sunnylqm
13b21c49c1 Merge branch 'master' of github.com:sunnylqm/react-native-update-cli
# Conflicts:
#	package.json
2020-10-21 15:24:46 +08:00
sunnylqm
85db61704c v1.1.18 2020-10-18 12:16:35 +08:00
sunnylqm
57ddfc5d7d Add token when upload 2020-10-18 12:16:15 +08:00
sunnylqm
6cb38eeec1 v1.1.17 2020-10-13 18:12:35 +08:00
sunnylqm
8a7c21fb50 Do not throw update.json not found error 2020-10-13 18:12:15 +08:00
sunnylqm
7223fa1389 v1.1.16 2020-09-15 15:16:31 +08:00
sunnylqm
2d5c01e990 Improve parse 2020-09-15 15:13:52 +08:00
sunnylqm
16b1cc3ed4 v1.1.15 2020-08-25 11:41:18 +08:00
sunnylqm
5e69793925 Update api endpoint 2020-08-25 11:41:01 +08:00
sunnylqm
b3c8e1f8e3 v1.1.14 2020-08-12 15:28:20 +08:00
sunnylqm
27ed4bd8d1 Add parse ipa and apk 2020-08-12 15:28:07 +08:00
sunnylqm
f0ad8bdcde v1.1.13 2020-06-22 18:44:00 +08:00
sunnylqm
62a37a955b Fix windows bundle command 2020-06-22 18:26:18 +08:00
sunnylqm
233e661f17 v1.1.12 2020-05-30 12:58:47 +08:00
sunnylqm
3a9bf67703 Revert fs-extra 2020-05-30 12:58:34 +08:00
sunnylqm
aa5fd4b4b1 v1.1.11 2020-05-29 13:15:47 +08:00
sunnylqm
6ed6420dd1 rmdir 2020-05-29 13:15:24 +08:00
sunnylqm
92f7d7efc4 v1.1.10 2020-05-29 13:12:16 +08:00
sunnylqm
de68470060 empty dir 2020-05-29 13:11:59 +08:00
sunnylqm
dfee2dd762 v1.1.9 2020-05-29 10:26:14 +08:00
sunnylqm
d726987b2c Update pricing url 2020-05-29 10:26:01 +08:00
sunnylqm
01f11822e1 v1.1.8 2020-05-29 10:24:33 +08:00
sunnylqm
37353222ef Remove fs-extra 2020-05-29 10:24:09 +08:00
sunnylqm
b76a2eb989 v1.1.7 2020-05-07 23:45:52 +08:00
sunnylqm
05b97d1e1e Update packages 2020-05-07 23:45:29 +08:00
sunnylqm
ba4969fe68 Update payment hint 2020-05-07 23:33:49 +08:00
sunnylqm
dad63743c1 v1.1.6 2020-05-07 17:49:44 +08:00
sunnylqm
924c58d254 maxSize 2020-05-07 17:49:20 +08:00
sunnylqm
b950d3dbe8 v1.1.5 2020-05-07 17:17:10 +08:00
sunnylqm
1206cd4461 Pass extension to upload api 2020-05-07 17:16:52 +08:00
sunnylqm
0147582561 v1.1.4 2020-04-15 00:03:05 +08:00
sunnylqm
a343999cb6 Support hermes 0.5 2020-04-15 00:02:35 +08:00
sunnylqm
ef28133cfc v1.1.3 2020-04-02 00:09:21 +08:00
sunnylqm
d16292a7a6 update api endpoint 2020-04-02 00:09:03 +08:00
sunnylqm
2146940d01 v1.1.2 2020-03-27 13:53:33 +08:00
sunnylqm
755485b4cb print version when bundling 2020-03-27 13:53:17 +08:00
sunnylqm
6bc7ee7091 notify global package 2020-03-21 16:55:52 +08:00
sunnylqm
9d00325519 v1.1.1 2020-03-20 15:51:53 +08:00
sunnylqm
84facadf3d 检查bundle文件 2020-03-20 15:51:35 +08:00
sunnylqm
a9dea5c188 v1.1.0 2020-03-18 16:06:20 +08:00
sunnylqm
83fe70f840 Revert "seperate key name"
This reverts commit a6477b4f0f.

# Conflicts:
#	src/package.js
2020-03-18 16:06:03 +08:00
sunnylqm
f1dd31cd56 v1.0.9 2020-03-16 19:10:33 +08:00
sunnylqm
a6477b4f0f seperate key name 2020-03-16 19:10:02 +08:00
sunnylqm
15b82b440e v1.0.8 2020-03-10 09:24:25 +08:00
sunnylqm
cc3f5dc093 Update build time hint 2020-03-10 09:23:47 +08:00
sunnylqm
19c6656dcf v1.0.7 2020-03-08 09:02:46 +08:00
sunnylqm
27f640f305 Tweak ping args 2020-03-08 08:59:42 +08:00
sunnylqm
552db6d2b3 v1.0.6 2020-03-07 17:09:06 +08:00
sunnylqm
443e4c14f6 Downgrade tty-table 2020-03-07 17:08:55 +08:00
sunnylqm
80b45469eb v1.0.5 2020-03-07 16:11:52 +08:00
14 changed files with 3833 additions and 3270 deletions

1
.gitignore vendored
View File

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

29
LICENSE
View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-update-cli",
"version": "1.0.4",
"version": "1.14.0-beta.0",
"description": "Command tools for javaScript updater with `pushy` service for react native apps.",
"main": "index.js",
"bin": {
@@ -32,22 +32,24 @@
},
"homepage": "https://github.com/reactnativecn/react-native-pushy/tree/master/react-native-pushy-cli",
"dependencies": {
"app-info-parser": "^0.3.9",
"app-info-parser": "^1.0.0",
"cli-arguments": "^0.2.1",
"fs-extra": "^8.1.0",
"gradle-to-js": "^2.0.0",
"node-fetch": "^2.6.0",
"filesize-parser": "^1.5.0",
"fs-extra": "8",
"gradle-to-js": "^2.0.1",
"node-fetch": "^2.6.1",
"progress": "^2.0.3",
"properties": "^1.2.1",
"read": "^1.0.7",
"request": "^2.88.2",
"tty-table": "^3.2.0",
"update-notifier": "^4.1.0",
"tcp-ping": "^0.1.1",
"tty-table": "4.1",
"update-notifier": "^4.1.1",
"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",

3117
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,24 @@
/**
* 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');
const defaultEndpoint = 'https://update.reactnative.cn/api';
let host = process.env.PUSHY_REGISTRY || defaultEndpoint;
const fs = require('fs');
import request from 'request';
import ProgressBar from 'progress';
const packageJson = require('../package.json');
const tcpp = require('tcp-ping');
const util = require('util');
const path = require('path');
import filesizeParser from 'filesize-parser';
import { pricingPageUrl } from './utils';
const tcpPing = util.promisify(tcpp.ping);
let session = undefined;
let savedSession = undefined;
const userAgent = `react-native-update-cli/${packageJson.version}`;
exports.loadSession = async function() {
exports.loadSession = async function () {
if (fs.existsSync('.update')) {
try {
exports.replaceSession(JSON.parse(fs.readFileSync('.update', 'utf8')));
@@ -28,15 +32,15 @@ exports.loadSession = async function() {
}
};
exports.getSession = function() {
exports.getSession = function () {
return session;
};
exports.replaceSession = function(newSession) {
exports.replaceSession = function (newSession) {
session = newSession;
};
exports.saveSession = function() {
exports.saveSession = function () {
// Only save on change.
if (session !== savedSession) {
const current = session;
@@ -46,18 +50,25 @@ exports.saveSession = function() {
}
};
exports.closeSession = function() {
exports.closeSession = function () {
if (fs.existsSync('.update')) {
fs.unlinkSync('.update');
savedSession = undefined;
}
session = undefined;
host = process.env.PUSHY_REGISTRY || 'https://update.reactnative.cn';
host = process.env.PUSHY_REGISTRY || defaultEndpoint;
};
async function query(url, options) {
const resp = await fetch(url, options);
const json = await resp.json();
const text = await resp.text();
let json;
try {
json = JSON.parse(text);
} catch (e) {
throw new Error(`Server error: ${text}`);
}
if (resp.status !== 200) {
throw Object.assign(new Error(json.message || json.error), {
status: resp.status,
@@ -67,7 +78,7 @@ async function query(url, options) {
}
function queryWithoutBody(method) {
return function(api) {
return function (api) {
return query(host + api, {
method,
headers: {
@@ -79,7 +90,7 @@ function queryWithoutBody(method) {
}
function queryWithBody(method) {
return function(api, body) {
return function (api, body) {
return query(host + api, {
method,
headers: {
@@ -97,26 +108,51 @@ exports.post = queryWithBody('POST');
exports.put = queryWithBody('PUT');
exports.doDelete = queryWithBody('DELETE');
async function uploadFile(fn) {
const { url, formData } = await exports.post('/upload', {});
async function uploadFile(fn, key) {
const { url, backupUrl, formData, maxSize } = await exports.post('/upload', {
ext: path.extname(fn),
});
let realUrl = url;
if (!/^https?\:\/\//.test(url)) {
realUrl = host + url;
if (backupUrl) {
if (global.USE_ACC_OSS) {
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(' Uploading [:bar] :percent :etas', {
const bar = new ProgressBar(' 上传中 [:bar] :percent :etas', {
complete: '=',
incomplete: ' ',
total: fileSize,
});
const info = await new Promise((resolve, reject) => {
if (key) {
formData.key = key;
}
formData.file = fs.createReadStream(fn);
formData.file.on('data', function(data) {
formData.file.on('data', function (data) {
bar.tick(data.length);
});
request.post(
@@ -133,13 +169,7 @@ async function uploadFile(fn) {
Object.assign(new Error(body), { status: resp.statusCode }),
);
}
resolve(
body
? // qiniu
JSON.parse(body)
: // aliyun oss
{ hash: formData.key },
);
resolve({ hash: formData.key });
},
);
});

View File

@@ -1,15 +1,8 @@
/**
* Created by tdzl2003 on 2/13/16.
*/
import { question } from './utils';
import fs from 'fs';
const Table = require('tty-table');
import {question} from './utils';
import * as fs from 'fs-extra';
const {
post,
get,
doDelete,
} = require('./api');
const { post, get, doDelete } = require('./api');
const validPlatforms = {
ios: 1,
@@ -18,34 +11,48 @@ const validPlatforms = {
export function checkPlatform(platform) {
if (!validPlatforms[platform]) {
throw new Error(`Invalid platform '${platform}'`);
throw new Error(`无法识别的平台 '${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;
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 = [];
for (const app of list) {
console.log(`${app.id}) ${app.name}(${app.platform})`);
rows.push([app.id, app.name, app.platform]);
}
console.log(Table(header, rows).render());
if (platform) {
console.log(`\nTotal ${list.length} ${platform} apps`);
console.log(`\ ${list.length} ${platform} 个应用`);
} else {
console.log(`\nTotal ${list.length} apps`);
console.log(`\ ${list.length} 个应用`);
}
return list;
}
@@ -54,8 +61,8 @@ 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));
const id = await question('输入应用 id:');
const app = list.find((v) => v.id === (id | 0));
if (app) {
return app;
}
@@ -63,48 +70,58 @@ export async function chooseApp(platform) {
}
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}`);
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}`);
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('Canceled');
console.log('已取消');
}
await doDelete(`/app/${id}`);
console.log('Ok.');
console.log('操作成功');
},
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('Platform(ios/android):'));
const id = args[0] || (await chooseApp(platform)).id;
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;
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,40 +1,23 @@
/**
* 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 { question, printVersionCommand } from './utils';
import { checkPlatform } from './app';
const { spawn, spawnSync, execSync } = require('child_process');
const { spawn, spawnSync } = require('child_process');
const g2js = require('gradle-to-js/lib/parser');
const os = require('os');
const properties = require('properties');
var diff;
var bsdiff, hdiff, 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.');
};
}
bsdiff = require('node-bsdiff').diff;
} catch (e) {}
function exec(command) {
const commandResult = spawnSync(command, {
shell: true,
stdio: 'inherit',
});
if (commandResult.error) {
throw commandResult.error;
}
}
try {
hdiff = require('node-hdiffpatch').diff;
} catch (e) {}
async function runReactNativeBundleCommand(
bundleName,
@@ -45,19 +28,30 @@ async function runReactNativeBundleCommand(
sourcemapOutput,
config,
) {
let gradleConfig = {};
if (platform === 'android') {
gradleConfig = await checkGradleConfig();
// if (gradleConfig.crunchPngs !== false) {
// throw new Error('请先禁用android的crunchPngs优化具体请参考 https://pushy.reactnative.cn/docs/getting-started.html#%E7%A6%81%E7%94%A8android%E7%9A%84crunch%E4%BC%98%E5%8C%96')
// }
}
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);
Array.prototype.push.apply(reactNativeBundleArgs, [
path.join("node_modules", "react-native", "local-cli", "cli.js"),
"bundle",
require.resolve('react-native/local-cli/cli.js'), // 'react-native' package may be symlinked
'bundle',
'--assets-dest',
outputFolder,
'--bundle-output',
@@ -80,23 +74,60 @@ 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') {
await compileHermesByteCode(bundleName, outputFolder);
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,
);
}
resolve(null);
}
@@ -110,28 +141,61 @@ function getHermesOSBin() {
if (os.platform() === 'linux') return 'linux64-bin';
}
async function compileHermesByteCode(bundleName, outputFolder) {
async function checkGradleConfig() {
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) {}
if (enableHermes) {
console.log(`Hermes enabled, now compiling to hermes bytecode:\n`);
const hermesPath = fs.existsSync('node_modules/hermes-engine')
? 'node_modules/hermes-engine'
: 'node_modules/hermesvm';
execSync(
`${hermesPath}/${getHermesOSBin()}/hermes -emit-binary -out ${outputFolder}/${bundleName} ${outputFolder}/${bundleName} -O`,
{ stdio: 'ignore' },
);
return {
enableHermes,
crunchPngs,
};
}
async function compileHermesByteCode(
bundleName,
outputFolder,
sourcemapOutput,
) {
console.log(`Hermes enabled, now compiling to hermes bytecode:\n`);
// >= rn 0.69
let hermesCommand = require.resolve(`react-native/sdks/hermesc/${getHermesOSBin()}/hermesc`);
// < rn 0.69
if (!fs.existsSync(hermesCommand)) {
const hermesPackage = fs.existsSync('node_modules/hermes-engine')
? 'node_modules/hermes-engine' // 0.2+
: 'node_modules/hermesvm'; // < 0.2
const hermesPath = `${hermesPackage}/${getHermesOSBin()}`;
hermesCommand = fs.existsSync(`${hermesPath}/hermesc`)
? `${hermesPath}/hermesc` // 0.5+
: `${hermesPath}/hermes`; // < 0.5
}
const args = [
'-emit-binary',
'-out',
path.join(outputFolder, bundleName),
path.join(outputFolder, bundleName),
'-O',
];
if (sourcemapOutput) {
args.push('-output-source-map');
}
spawnSync(path.join.apply(null, hermesCommand.split('/')), args, {
stdio: 'ignore',
});
}
async function pack(dir, output) {
@@ -163,13 +227,15 @@ 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('Bundled saved to: ' + output);
console.log('ppk热更包已生成并保存到: ' + output);
}
function readEntire(entry, zipFile) {
@@ -213,13 +279,15 @@ 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 = {};
@@ -227,12 +295,14 @@ async function diffFromPPK(origin, next, output) {
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 = {};
@@ -261,9 +331,12 @@ 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 {
@@ -288,7 +361,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);
}
@@ -312,12 +385,21 @@ 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 = {};
@@ -339,13 +421,15 @@ async function diffFromPackage(origin, next, output, originBundleName, transform
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 = {};
@@ -353,12 +437,14 @@ async function diffFromPackage(origin, next, output, originBundleName, transform
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) => {
@@ -367,9 +453,12 @@ async function diffFromPackage(origin, next, output, originBundleName, transform
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 {
@@ -385,7 +474,7 @@ async function diffFromPackage(origin, next, output, originBundleName, transform
}
return new Promise((resolve, reject) => {
nextZipfile.openReadStream(entry, function(err, readStream) {
nextZipfile.openReadStream(entry, function (err, readStream) {
if (err) {
return reject(err);
}
@@ -412,7 +501,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());
@@ -425,16 +514,55 @@ 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('Platform(ios/android):')));
bundle: async function ({ options }) {
const platform = checkPlatform(
options.platform || (await question('平台(ios/android):')),
);
let { bundleName, entryFile, intermediaDir, output, dev, verbose } = translateOptions({
...options,
platform,
});
let { bundleName, entryFile, intermediaDir, output, dev, sourcemap } =
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());
@@ -444,13 +572,21 @@ export const commands = {
const { version, major, minor } = getRNVersion();
console.log('Bundling with React Native version: ', version);
console.log('Bundling with react-native: ', version);
printVersionCommand();
await runReactNativeBundleCommand(bundleName, dev, entryFile, intermediaDir, platform);
await runReactNativeBundleCommand(
bundleName,
dev,
entryFile,
intermediaDir,
platform,
sourcemap ? sourcemapOutput : '',
);
await pack(path.resolve(intermediaDir), realOutput);
const v = await question('Would you like to publish it?(Y/N)');
const v = await question('是否现在上传此热更包?(Y/N)');
if (v.toLowerCase() === 'y') {
await this.publish({
args: [realOutput],
@@ -462,47 +598,74 @@ export const commands = {
},
async diff({ args, options }) {
const [origin, next] = args;
const { output } = options;
const { origin, next, realOutput } = diffArgsCheck(args, options, 'diff');
const realOutput = output.replace(/\$\{time\}/g, '' + Date.now());
await diffFromPPK(origin, next, realOutput, 'index.bundlejs');
console.log(`${realOutput} generated.`);
},
if (!origin || !next) {
console.error('pushy diff <origin> <next>');
process.exit(1);
}
async hdiff({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(args, options, 'hdiff');
await diffFromPPK(origin, next, realOutput, 'index.bundlejs');
console.log(`${realOutput} generated.`);
},
async diffFromApk({ args, options }) {
const [origin, next] = args;
const { output } = options;
const { origin, next, realOutput } = diffArgsCheck(
args,
options,
'diffFromApk',
);
const realOutput = output.replace(/\$\{time\}/g, '' + Date.now());
await diffFromPackage(
origin,
next,
realOutput,
'assets/index.android.bundle',
);
console.log(`${realOutput} generated.`);
},
if (!origin || !next) {
console.error('pushy diffFromApk <origin> <next>');
process.exit(1);
}
async hdiffFromApk({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(
args,
options,
'hdiffFromApk',
);
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] = args;
const { output } = options;
const { origin, next, realOutput } = diffArgsCheck(
args,
options,
'diffFromIpa',
);
const realOutput = output.replace(/\$\{time\}/g, '' + Date.now());
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', (v) => {
const m = /^Payload\/[^/]+\/(.+)$/.exec(v);
return m && m[1];
});
if (!origin || !next) {
console.error('pushy diffFromIpa <origin> <next>');
process.exit(1);
}
console.log(`${realOutput} generated.`);
},
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', v => {
async hdiffFromIpa({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(
args,
options,
'hdiffFromIpa',
);
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', (v) => {
const m = /^Payload\/[^/]+\/(.+)$/.exec(v);
return m && m[1];
});

View File

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

View File

@@ -13,22 +13,29 @@ 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 header = [{ value: '原生包 Id' }, { value: '原生版本' }];
const rows = [];
for (const pkg of data) {
const { version } = pkg;
let versionInfo = '';
if (version) {
versionInfo = ` - ${version.id} ${version.hash.slice(0, 8)} ${version.name}`;
versionInfo = `, 已绑定:${version.name} (${version.id})`;
} else {
versionInfo = ' (newest)';
// versionInfo = ' (newest)';
}
rows.push([pkg.id, `${pkg.name}(${pkg.status})${versionInfo}`]);
let output = pkg.name;
if (pkg.status === 'paused') {
output += '(已暂停)';
}
if (pkg.status === 'expired') {
output += '(已过期)';
}
output += versionInfo;
rows.push([pkg.id, output]);
}
console.log(Table(header, rows).render());
console.log(`\nTotal ${data.length} package(s).`);
console.log(`\n ${data.length} 个包`);
return data;
}
@@ -36,8 +43,8 @@ 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));
const id = await question('输入原生包 id:');
const app = list.find((v) => v.id === (id | 0));
if (app) {
return app;
}
@@ -45,13 +52,30 @@ 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('Usage: pushy uploadIpa <ipaFile>');
throw new Error('使用方法: pushy uploadIpa ipa后缀文件');
}
const {
versionName,
buildTime,
appId: appIdInPkg,
appKey: appKeyInPkg,
} = await getIpaInfo(fn);
const { appId, appKey } = 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 { versionName, buildTime } = await getIpaInfo(fn);
const { appId } = await getSelectedApp('ios');
const { hash } = await uploadFile(fn);
@@ -61,15 +85,32 @@ export const commands = {
buildTime,
});
saveToLocal(fn, `${appId}/package/${id}.ipa`);
console.log(`Ipa uploaded: ${id}`);
console.log(`已成功上传ipa原生包id: ${id}`);
},
uploadApk: async function({ args }) {
uploadApk: async function ({ args }) {
const fn = args[0];
if (!fn || !fn.endsWith('.apk')) {
throw new Error('Usage: pushy uploadApk <apkFile>');
throw new Error('使用方法: pushy uploadApk apk后缀文件');
}
const {
versionName,
buildTime,
appId: appIdInPkg,
appKey: appKeyInPkg,
} = await getApkInfo(fn);
const { appId, appKey } = 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 { versionName, buildTime } = await getApkInfo(fn);
const { appId } = await getSelectedApp('android');
const { hash } = await uploadFile(fn);
@@ -79,10 +120,26 @@ export const commands = {
buildTime,
});
saveToLocal(fn, `${appId}/package/${id}.apk`);
console.log(`Apk uploaded: ${id}`);
console.log(`已成功上传apk原生包id: ${id}`);
},
packages: async function({ options }) {
const platform = checkPlatform(options.platform || (await question('Platform(ios/android):')));
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):')),
);
const { appId } = await getSelectedApp(platform);
await listPackage(appId);
},

View File

@@ -26,11 +26,11 @@ exports.commands = {
});
replaceSession({token});
await saveSession();
console.log(`Welcome, ${info.name}.`);
console.log(`欢迎使用 pushy 热更新服务, ${info.name}.`);
},
logout: async function (){
await closeSession();
console.log('Logged out.');
console.log('已退出登录');
},
me: async function (){
const me = await get('/user/me');

View File

@@ -2,9 +2,10 @@
* Created by tdzl2003 on 2/13/16.
*/
import * as fs from 'fs-extra';
import fs from 'fs-extra';
import os from 'os';
import path from 'path';
const pkg = require('../../package.json');
const AppInfoParser = require('app-info-parser');
var read = require('read');
@@ -30,7 +31,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 {
@@ -42,7 +43,7 @@ export function translateOptions(options) {
export function getRNVersion() {
const version = JSON.parse(
fs.readFileSync(path.resolve('node_modules/react-native/package.json')),
fs.readFileSync(require.resolve('react-native/package.json')),
).version;
// We only care about major and minor version.
@@ -56,6 +57,21 @@ 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)) {
@@ -66,13 +82,30 @@ export async function getApkInfo(fn) {
}
}
if (buildTime == 0) {
throw new Error('Can not get build time for this app.');
throw new Error(
'无法获取此包的编译时间戳。请更新react-native-update到最新版本后重新打包上传。',
);
}
return { versionName, buildTime };
return { versionName, buildTime, ...appCredential };
}
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();
@@ -86,10 +119,12 @@ export async function getIpaInfo(fn) {
);
}
if (!buildTimeTxtBuffer) {
throw new Error('Can not get build time for this app.');
throw new Error(
'无法获取此包的编译时间戳。请更新react-native-update到最新版本后重新打包上传。',
);
}
const buildTime = buildTimeTxtBuffer.toString().replace('\n', '');
return { versionName, buildTime };
return { versionName, buildTime, ...appCredential };
}
const localDir = path.resolve(os.homedir(), '.pushy');
@@ -100,3 +135,15 @@ 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');
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,13 +1,4 @@
/**
* Created by tdzl2003 on 4/2/16.
*/
const {
get,
post,
put,
uploadFile,
} = require('./api');
const { get, post, put, uploadFile } = require('./api');
import { question, saveToLocal } from './utils';
import { checkPlatform, getSelectedApp } from './app';
@@ -17,17 +8,24 @@ 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;
}
@@ -38,10 +36,17 @@ 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;
}
}
}
@@ -50,14 +55,21 @@ 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;
}
@@ -67,47 +79,88 @@ 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('Usage: pushy publish <ppkFile> --platform ios|android');
throw new Error(
'使用方法: pushy publish ppk后缀文件 --platform ios|android',
);
}
const platform = checkPlatform(options.platform || await question('Platform(ios/android):'));
const platform = checkPlatform(
options.platform || (await question('平台(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:') || '(未命名)',
name: name || (await question('输入版本名称: ')) || '(未命名)',
hash,
description: description || await question('Enter description:'),
metaInfo: metaInfo || await question('Enter meta info:'),
description: description || (await question('输入版本描述:')),
metaInfo: metaInfo || (await question('输入自定义的 meta info:')),
});
// TODO local diff
saveToLocal(fn, `${appId}/ppk/${id}.ppk`);
console.log(`Version published: ${id}`);
console.log(`已成功上传新热更包id: ${id}`);
const v = await question('Would you like to bind packages to this version?(Y/N)');
const v = await question('是否现在将此热更应用到原生包上?(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('Platform(ios/android):'));
versions: async function ({ options }) {
const platform = checkPlatform(
options.platform || (await question('平台(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):'));
update: 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 pkgId = options.packageId || (await choosePackage(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 参数');
}
await put(`/app/${appId}/package/${pkgId}`, {
versionId,
});
console.log('Ok.');
}
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('操作成功');
},
};

2929
yarn.lock

File diff suppressed because it is too large Load Diff