erpush-cli 0.0.1-prepayload
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of erpush-cli might be problematic. Click here for more details.
- package/cli.js +2 -0
- package/index.js +9 -0
- package/package.json +27 -0
- package/src/api.js +298 -0
- package/src/commands.js +243 -0
- package/src/pack.js +324 -0
- package/src/patch.js +288 -0
- package/src/shell.js +250 -0
- package/src/utils.js +625 -0
package/src/utils.js
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const yazl = require('yazl');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const wcwidth = require('wcwidth');
|
|
7
|
+
const tough = require("tough-cookie");
|
|
8
|
+
const minimist = require('minimist');
|
|
9
|
+
const FormData = require('form-data');
|
|
10
|
+
const nodeFetch = require("node-fetch");
|
|
11
|
+
const {spawn} = require('child_process');
|
|
12
|
+
const {open:openZipFile} = require('yauzl');
|
|
13
|
+
|
|
14
|
+
const runtimeCache = {};
|
|
15
|
+
const supportPlatforms = ['android', 'ios'];
|
|
16
|
+
const MakeDiff = (() => {
|
|
17
|
+
try {
|
|
18
|
+
return require('erpush').diff;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return e;
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
|
|
24
|
+
// 解析 process 参数
|
|
25
|
+
function parseProcess(p){
|
|
26
|
+
const _ = p.env._||null;
|
|
27
|
+
const npx = _ && _.endsWith('/npx');
|
|
28
|
+
const options = minimist(p.argv.slice(2));
|
|
29
|
+
const args = options._;
|
|
30
|
+
const name = args.shift();
|
|
31
|
+
delete options._;
|
|
32
|
+
return {npx, name, args, options};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 消息相关
|
|
36
|
+
const CInfo = color('Info:', 36, true) + ' ';
|
|
37
|
+
const CWarning = color('Warning:', 35, true) + ' ';
|
|
38
|
+
const CError = color('Error:', 31, true) + ' ';
|
|
39
|
+
|
|
40
|
+
function color(str, code, bold){
|
|
41
|
+
return (bold ? '\033[1m' : '')
|
|
42
|
+
+ (code ? "\x1b["+code+"m" : '')
|
|
43
|
+
+ str
|
|
44
|
+
+ (code ? "\x1b" : '')
|
|
45
|
+
+ (bold ? "\033" : '')
|
|
46
|
+
+ "[0m";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function rmColor(str) {
|
|
50
|
+
return typeof str === 'string' ? str.replace(
|
|
51
|
+
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
52
|
+
''
|
|
53
|
+
) : str;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function errMsg(e){
|
|
57
|
+
if (typeof e === 'object' && 'message' in e) {
|
|
58
|
+
return e.message
|
|
59
|
+
}
|
|
60
|
+
return e;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 转 Array 为 String 表格, 自动对齐
|
|
64
|
+
function makeTable(data) {
|
|
65
|
+
const rows = [];
|
|
66
|
+
const rowWidth = [];
|
|
67
|
+
data.forEach(item => {
|
|
68
|
+
if (!Array.isArray(item)) {
|
|
69
|
+
rows.push(null)
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const row = [];
|
|
73
|
+
item.forEach((str, index) => {
|
|
74
|
+
const width = wcwidth(String(rmColor(str)));
|
|
75
|
+
row.push(width);
|
|
76
|
+
if (!rowWidth[index] || rowWidth[index] < width) {
|
|
77
|
+
rowWidth[index] = width;
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
rows.push(row)
|
|
81
|
+
})
|
|
82
|
+
const txts = [];
|
|
83
|
+
const split = '-'.repeat(rowWidth.reduce((a, b) => a + b) + rowWidth.length * 2);
|
|
84
|
+
data.forEach((item, n) => {
|
|
85
|
+
if (!Array.isArray(item)) {
|
|
86
|
+
txts.push(split)
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let line = '';
|
|
90
|
+
const widths = rows[n];
|
|
91
|
+
item.forEach((str, index) => {
|
|
92
|
+
line += String(str) + ' '.repeat(rowWidth[index] - widths[index] + 2)
|
|
93
|
+
});
|
|
94
|
+
txts.push(line);
|
|
95
|
+
})
|
|
96
|
+
return txts.join("\n")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 文件操作
|
|
100
|
+
function getCacheDir() {
|
|
101
|
+
const dir = path.join(os.homedir(), '.easypush');
|
|
102
|
+
fs.ensureDirSync(dir);
|
|
103
|
+
return dir;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function dirExist(path){
|
|
107
|
+
return fileExist(path, true)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function fileExist(path, dir){
|
|
111
|
+
try {
|
|
112
|
+
const f = fs.lstatSync(path)
|
|
113
|
+
return dir ? f.isDirectory() : f.isFile()
|
|
114
|
+
} catch(e) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function fileMd5(filename) {
|
|
120
|
+
let fd;
|
|
121
|
+
try {
|
|
122
|
+
fd = fs.openSync(filename, 'r')
|
|
123
|
+
} catch (e) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const BUFFER_SIZE = 8192;
|
|
127
|
+
const hash = crypto.createHash('md5')
|
|
128
|
+
const buffer = Buffer.alloc(BUFFER_SIZE)
|
|
129
|
+
try {
|
|
130
|
+
let bytesRead
|
|
131
|
+
do {
|
|
132
|
+
bytesRead = fs.readSync(fd, buffer, 0, BUFFER_SIZE)
|
|
133
|
+
hash.update(buffer.subarray(0, bytesRead))
|
|
134
|
+
} while (bytesRead === BUFFER_SIZE)
|
|
135
|
+
} finally {
|
|
136
|
+
fs.closeSync(fd)
|
|
137
|
+
}
|
|
138
|
+
return hash.digest('hex')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 获取 oldBuf, newBuf 的 diff buffer
|
|
142
|
+
function getDiff(oldBuf, newBuf) {
|
|
143
|
+
if (typeof MakeDiff !== 'function') {
|
|
144
|
+
const message = 'Load "easypush" module failed.';
|
|
145
|
+
if (MakeDiff instanceof Error) {
|
|
146
|
+
MakeDiff.message = message + "\n" + MakeDiff.message;
|
|
147
|
+
throw MakeDiff;
|
|
148
|
+
}
|
|
149
|
+
throw new Error(message);
|
|
150
|
+
}
|
|
151
|
+
return MakeDiff(oldBuf, newBuf);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 打包 xcode 编译的 .app 为 .ipa 文件
|
|
155
|
+
function packIpa(source, dest){
|
|
156
|
+
return packDirToZip(source, dest, true);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 打包 dir 为 zip 文件, 保存到 save 路径
|
|
160
|
+
function packZip(dir, save) {
|
|
161
|
+
return packDirToZip(dir, save);
|
|
162
|
+
}
|
|
163
|
+
function packDirToZip(dir, save, ipa){
|
|
164
|
+
return new Promise(function (resolve, reject) {
|
|
165
|
+
const zip = new yazl.ZipFile();
|
|
166
|
+
let rel = '';
|
|
167
|
+
if (ipa) {
|
|
168
|
+
const appName = path.basename(dir);
|
|
169
|
+
rel = 'Payload/' + appName;
|
|
170
|
+
}
|
|
171
|
+
addRecursive(zip, dir, rel);
|
|
172
|
+
zip.end();
|
|
173
|
+
zip.on('error', function (err) {
|
|
174
|
+
fs.removeSync(save)
|
|
175
|
+
reject(err);
|
|
176
|
+
});
|
|
177
|
+
zip.outputStream.pipe(fs.createWriteStream(save)).on('close', function () {
|
|
178
|
+
resolve();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function addRecursive(zip, root, rel) {
|
|
183
|
+
if (rel) {
|
|
184
|
+
rel += '/';
|
|
185
|
+
zip.addEmptyDirectory(rel);
|
|
186
|
+
}
|
|
187
|
+
const childs = fs.readdirSync(root);
|
|
188
|
+
for (const name of childs) {
|
|
189
|
+
if (name === '.' || name === '..') {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const fullPath = path.join(root, name);
|
|
193
|
+
const stat = fs.statSync(fullPath);
|
|
194
|
+
if (stat.isFile()) {
|
|
195
|
+
zip.addFile(fullPath, rel + name);
|
|
196
|
+
} else if (stat.isDirectory()) {
|
|
197
|
+
addRecursive(zip, fullPath, rel + name);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 枚举 Zip 内所有文件, callback(entry, zipfile), 若不指定 basic 为 true
|
|
203
|
+
// 文件属性 entry 会新增 isDirectory/hash 两个字段, 原 entry 内有一个 crc32 的 hash 值
|
|
204
|
+
// 但考虑到 crc32 的碰撞概率略大, 所以此处额外计算一个新的 hash 值用于校验
|
|
205
|
+
function enumZipEntries(zipFn, callback, basic) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
openZipFile(zipFn, {lazyEntries: true}, (err, zipfile) => {
|
|
208
|
+
if (err) {
|
|
209
|
+
reject(err);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
zipfile.on('end', resolve);
|
|
213
|
+
zipfile.on('error', reject);
|
|
214
|
+
zipfile.on('entry', entry => {
|
|
215
|
+
getZipEntryHash(zipfile, entry, basic).then(entryPlus => {
|
|
216
|
+
return Promise.resolve(callback(entryPlus, zipfile))
|
|
217
|
+
}).then(() => zipfile.readEntry())
|
|
218
|
+
});
|
|
219
|
+
zipfile.readEntry();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function getZipEntryHash(zipfile, entry, basic) {
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
if (basic) {
|
|
226
|
+
resolve(entry);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
entry.isDirectory = /\/$/.test(entry.fileName);
|
|
230
|
+
if (entry.isDirectory) {
|
|
231
|
+
entry.hash = null;
|
|
232
|
+
resolve(entry);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
zipfile.openReadStream(entry, function(err, readStream) {
|
|
236
|
+
if (err) {
|
|
237
|
+
reject(err);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const hash = crypto.createHash('md5').setEncoding('hex');
|
|
241
|
+
readStream.on("end", function() {
|
|
242
|
+
hash.end();
|
|
243
|
+
entry.hash = hash.read();
|
|
244
|
+
resolve(entry);
|
|
245
|
+
});
|
|
246
|
+
readStream.pipe(hash);
|
|
247
|
+
});
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 获取 enumZipEntries 枚举的单个文件 buffer
|
|
252
|
+
function readZipEntireBuffer(entry, zipfile) {
|
|
253
|
+
const buffers = [];
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
zipfile.openReadStream(entry, (err, stream) => {
|
|
256
|
+
if (err) {
|
|
257
|
+
reject(err);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
stream.pipe({
|
|
261
|
+
write(chunk) {
|
|
262
|
+
buffers.push(chunk);
|
|
263
|
+
},
|
|
264
|
+
end() {
|
|
265
|
+
resolve(Buffer.concat(buffers));
|
|
266
|
+
},
|
|
267
|
+
prependListener() {},
|
|
268
|
+
on() {},
|
|
269
|
+
once() {},
|
|
270
|
+
emit() {},
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 保存 ZipFile 对象为文件
|
|
277
|
+
function saveZipFile(zipfile, output) {
|
|
278
|
+
fs.ensureDirSync(path.dirname(output));
|
|
279
|
+
return new Promise(function (resolve, reject) {
|
|
280
|
+
zipfile.on('error', err => {
|
|
281
|
+
fs.removeSync(output)
|
|
282
|
+
reject(err);
|
|
283
|
+
});
|
|
284
|
+
zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', function() {
|
|
285
|
+
resolve();
|
|
286
|
+
});
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 获取字符串共同前缀
|
|
291
|
+
// https://www.geeksforgeeks.org/longest-common-prefix-using-binary-search/
|
|
292
|
+
function getCommonPrefix(arr) {
|
|
293
|
+
let low = 0, high = 0;
|
|
294
|
+
arr.forEach(s => {
|
|
295
|
+
if (!high || s.length < high) {
|
|
296
|
+
high = s.length
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
let prefix = '';
|
|
300
|
+
const first = arr[0];
|
|
301
|
+
while (low <= high) {
|
|
302
|
+
const mid = Math.floor(low + (high - low) / 2);
|
|
303
|
+
const interrupt = arr.some(r => {
|
|
304
|
+
for (let i = low; i <= mid; i++) {
|
|
305
|
+
if (r[i] !== first[i]) {
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
if (interrupt) {
|
|
311
|
+
high = mid - 1;
|
|
312
|
+
} else {
|
|
313
|
+
prefix += first.substr(low, mid-low+1);
|
|
314
|
+
low = mid + 1;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return prefix;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 在 rootDir 目录查找有共同前缀 prefix 的文件(夹)
|
|
321
|
+
function getCommonPath(rootDir, prefix) {
|
|
322
|
+
const dash = prefix.lastIndexOf('/');
|
|
323
|
+
const curPad = prefix.substr(dash + 1);
|
|
324
|
+
const curDir = dash !== -1 ? prefix.substring(0, dash + 1) : '';
|
|
325
|
+
let completions;
|
|
326
|
+
try {
|
|
327
|
+
completions = fs.readdirSync(
|
|
328
|
+
path.join(rootDir, curDir),
|
|
329
|
+
{withFileTypes:true}
|
|
330
|
+
).map(r =>
|
|
331
|
+
(curPad ? '' : prefix) + r.name + (r.isDirectory() ? '/' : '')
|
|
332
|
+
);
|
|
333
|
+
} catch(e) {
|
|
334
|
+
completions = [];
|
|
335
|
+
}
|
|
336
|
+
// 若 prefix 为全路径, 如 /foo/, 直接返回该目录下所有列表即可
|
|
337
|
+
if (!curPad) {
|
|
338
|
+
return [completions, prefix]
|
|
339
|
+
}
|
|
340
|
+
// 若 prefix 为 /foo/ba, 获取 /foo/ 目录下 ba 开头的文件列表
|
|
341
|
+
let hits = [];
|
|
342
|
+
completions.forEach(r => {
|
|
343
|
+
if (r.startsWith(curPad)) {
|
|
344
|
+
hits.push(r);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
// 获取 ba 开头文件的共同前缀, 如 hits 为 [bara, barb], 得到 bar
|
|
348
|
+
if (hits.length > 1) {
|
|
349
|
+
const prefix = getCommonPrefix(hits);
|
|
350
|
+
if (prefix !== curPad) {
|
|
351
|
+
hits = [prefix];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// 给列表文件重新加上 /foo/ 前缀
|
|
355
|
+
if (curDir != '') {
|
|
356
|
+
hits = hits.map(v => curDir + v);
|
|
357
|
+
}
|
|
358
|
+
return [hits, prefix];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 获取 RN 版本
|
|
362
|
+
function getRNVersion(projectDir) {
|
|
363
|
+
if (!runtimeCache.rnVersion) {
|
|
364
|
+
const version = JSON.parse(fs.readFileSync(path.resolve(projectDir||'', 'node_modules/react-native/package.json'))).version;
|
|
365
|
+
const match = /^(\d+)\.(\d+)(\.(\d+))?/.exec(version);
|
|
366
|
+
runtimeCache.rnVersion = {
|
|
367
|
+
version,
|
|
368
|
+
major: match[1] | 0,
|
|
369
|
+
minor: match[2] | 0,
|
|
370
|
+
patch: match[4] | 0
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return runtimeCache.rnVersion;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 获取 EasyPush 版本
|
|
377
|
+
function getEasyVersion() {
|
|
378
|
+
if (!runtimeCache.eyVersion) {
|
|
379
|
+
runtimeCache.eyVersion = JSON.parse(fs.readFileSync(
|
|
380
|
+
path.resolve(__dirname, './../package.json')
|
|
381
|
+
)).version;
|
|
382
|
+
}
|
|
383
|
+
return runtimeCache.eyVersion;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 设置 easypush 配置信息
|
|
387
|
+
function setConfig(projectDir, config){
|
|
388
|
+
const file = path.join(projectDir, 'easypush.json');
|
|
389
|
+
const now = fs.readJsonSync(file, { throws: false })||{};
|
|
390
|
+
config = {...now, ...config};
|
|
391
|
+
fs.writeJsonSync(file, config, {spaces:2});
|
|
392
|
+
return file;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 获取 easypush 配置信息
|
|
396
|
+
function getConfig(projectDir){
|
|
397
|
+
return fs.readJsonSync(
|
|
398
|
+
path.join(projectDir, 'easypush.json'),
|
|
399
|
+
{ throws: false }
|
|
400
|
+
)||{};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 获取项目的 App id
|
|
404
|
+
function getAppId(projectDir, platform, fallbackId){
|
|
405
|
+
if (supportPlatforms.indexOf(platform) == -1) {
|
|
406
|
+
return {code:-1, message:'platform not support'}
|
|
407
|
+
}
|
|
408
|
+
if (fallbackId) {
|
|
409
|
+
return {code:0, message:fallbackId}
|
|
410
|
+
}
|
|
411
|
+
const config = getConfig(projectDir);
|
|
412
|
+
if (!(platform in config)) {
|
|
413
|
+
return {code:-3, message: "Unbound app, please run `easypush app bind` first"}
|
|
414
|
+
}
|
|
415
|
+
return {code:0, message:config[platform]}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 发送 API 请求: 以 cookie 做为凭证, 服务端可以此来鉴权, 返回 json
|
|
419
|
+
async function requestAPI(projectDir, uri, payload, asForm) {
|
|
420
|
+
let {baseUrl} = getConfig(projectDir);
|
|
421
|
+
if (uri && !/^[a-zA-Z]+:\/\//.test(uri)) {
|
|
422
|
+
if (!baseUrl) {
|
|
423
|
+
uri = null;
|
|
424
|
+
} else {
|
|
425
|
+
// trim baseUrl right /
|
|
426
|
+
while(baseUrl.charAt(baseUrl.length-1) === '/') {
|
|
427
|
+
baseUrl = baseUrl.substring(0, baseUrl.length-1);
|
|
428
|
+
}
|
|
429
|
+
// trim uri left /
|
|
430
|
+
while(uri.charAt(0) === '/') {
|
|
431
|
+
uri = uri.substring(1);
|
|
432
|
+
}
|
|
433
|
+
uri = baseUrl + '/' + uri;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (!uri || !/^https?:\/\//i.test(uri)) {
|
|
437
|
+
return {
|
|
438
|
+
code:-2,
|
|
439
|
+
message: "request url incorrect"
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const options = {
|
|
443
|
+
headers:{
|
|
444
|
+
'User-Agent': "easypush-client/" + getEasyVersion(),
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
if (payload) {
|
|
448
|
+
options.method = 'POST';
|
|
449
|
+
if (asForm) {
|
|
450
|
+
const form = new FormData();
|
|
451
|
+
for (let key in payload) {
|
|
452
|
+
form.append(key, payload[key]);
|
|
453
|
+
}
|
|
454
|
+
options.body = form;
|
|
455
|
+
} else {
|
|
456
|
+
options.body = JSON.stringify(payload);
|
|
457
|
+
}
|
|
458
|
+
} else {
|
|
459
|
+
options.method = 'GET';
|
|
460
|
+
}
|
|
461
|
+
// 在进程结束时保存 cookie 为文件
|
|
462
|
+
if (!runtimeCache.jar) {
|
|
463
|
+
runtimeCache.store = new tough.MemoryCookieStore();
|
|
464
|
+
runtimeCache.jarFile = path.join(getCacheDir(), '.cookiejar');
|
|
465
|
+
try {
|
|
466
|
+
if (!fileExist(runtimeCache.jarFile)) {
|
|
467
|
+
throw '';
|
|
468
|
+
}
|
|
469
|
+
runtimeCache.jar = tough.CookieJar.deserializeSync(
|
|
470
|
+
fs.readFileSync(runtimeCache.jarFile).toString(),
|
|
471
|
+
runtimeCache.store
|
|
472
|
+
);
|
|
473
|
+
}catch(e){
|
|
474
|
+
runtimeCache.jar = new tough.CookieJar(runtimeCache.store);
|
|
475
|
+
}
|
|
476
|
+
process.on('exit', () => {
|
|
477
|
+
if (!runtimeCache.changed) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
// 仅保存持久化的, 未设置过期时间的仅在当前进程有效
|
|
481
|
+
const cookieLists = [];
|
|
482
|
+
const Store = runtimeCache.store;
|
|
483
|
+
Store.getAllCookies((err, cookies) => {
|
|
484
|
+
if (err) {
|
|
485
|
+
throw err;
|
|
486
|
+
}
|
|
487
|
+
cookies.forEach(cookie => {
|
|
488
|
+
if (cookie.isPersistent()) {
|
|
489
|
+
cookie = cookie instanceof tough.Cookie ? cookie.toJSON() : cookie;
|
|
490
|
+
delete cookie.creationIndex;
|
|
491
|
+
cookieLists.push(cookie)
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
const serialized = {
|
|
496
|
+
rejectPublicSuffixes: !!runtimeCache.jar.rejectPublicSuffixes,
|
|
497
|
+
enableLooseMode: !!runtimeCache.jar.enableLooseMode,
|
|
498
|
+
allowSpecialUseDomain: !!runtimeCache.jar.allowSpecialUseDomain,
|
|
499
|
+
prefixSecurity: runtimeCache.jar.prefixSecurity,
|
|
500
|
+
cookies: cookieLists
|
|
501
|
+
};
|
|
502
|
+
fs.writeFileSync(runtimeCache.jarFile, JSON.stringify(serialized));
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
// 设置请求 cookie
|
|
506
|
+
const Jar = runtimeCache.jar;
|
|
507
|
+
const cookies = await Jar.getCookieString(uri);
|
|
508
|
+
if (cookies) {
|
|
509
|
+
options.headers['cookie'] = cookies;
|
|
510
|
+
}
|
|
511
|
+
const res = await nodeFetch(uri, options);
|
|
512
|
+
const resCookies = res.headers.raw()['set-cookie'];
|
|
513
|
+
if (resCookies) {
|
|
514
|
+
if (!runtimeCache.changed) {
|
|
515
|
+
runtimeCache.changed = true;
|
|
516
|
+
}
|
|
517
|
+
(Array.isArray(resCookies) ? resCookies : [resCookies]).forEach(cookie => {
|
|
518
|
+
Jar.setCookieSync(cookie, uri)
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
return res;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* 下载 url 指定的文件, 可指定 md5 进行校验
|
|
526
|
+
* download(url, md5).then(rs => {
|
|
527
|
+
* rs: {code:Int, message:String, file:String}
|
|
528
|
+
* })
|
|
529
|
+
*/
|
|
530
|
+
async function download(url, md5) {
|
|
531
|
+
return new Promise(resolve => {
|
|
532
|
+
if (!url) {
|
|
533
|
+
resolve({code:1, message:'download url unavailable'})
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
let localFile;
|
|
537
|
+
if (md5) {
|
|
538
|
+
localFile = path.join(getCacheDir(), md5);
|
|
539
|
+
}
|
|
540
|
+
const tmpFile = localFile
|
|
541
|
+
? localFile + "_tmp"
|
|
542
|
+
: path.join(getCacheDir(), crypto.randomBytes(8).toString("hex"));
|
|
543
|
+
const stream = fs.createWriteStream(tmpFile);
|
|
544
|
+
nodeFetch(url, {
|
|
545
|
+
headers: {
|
|
546
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
|
547
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
548
|
+
'Accept-Language': 'en-US,en;q=0.9,fr;q=0.8,ro;q=0.7,ru;q=0.6,la;q=0.5,pt;q=0.4,de;q=0.3',
|
|
549
|
+
'Cache-Control': 'max-age=0',
|
|
550
|
+
'Connection': 'keep-alive',
|
|
551
|
+
'Upgrade-Insecure-Requests': '1',
|
|
552
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
|
|
553
|
+
}
|
|
554
|
+
}).then(res => {
|
|
555
|
+
res.body.pipe(stream);
|
|
556
|
+
res.body.on("error", (error) => {
|
|
557
|
+
resolve({code:1, message: errMsg(error)})
|
|
558
|
+
});
|
|
559
|
+
stream.on("finish", () => {
|
|
560
|
+
const checkMd5 = fileMd5(tmpFile);
|
|
561
|
+
if (md5 && checkMd5 !== md5) {
|
|
562
|
+
fs.removeSync(tmpFile);
|
|
563
|
+
resolve({code:1, message:'check download file md5 failed'})
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (!localFile) {
|
|
567
|
+
localFile = path.join(getCacheDir(), checkMd5);
|
|
568
|
+
}
|
|
569
|
+
fs.moveSync(tmpFile, localFile, {overwrite:true})
|
|
570
|
+
resolve({code:0, file:localFile})
|
|
571
|
+
});
|
|
572
|
+
}).catch(error => {
|
|
573
|
+
resolve({code:1, message: errMsg(error)})
|
|
574
|
+
})
|
|
575
|
+
})
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// 执行命令
|
|
579
|
+
function execCommand(command, args, options){
|
|
580
|
+
return new Promise(function (resolve, reject) {
|
|
581
|
+
const child = spawn(command, args, options);
|
|
582
|
+
child.on('close', function (code) {
|
|
583
|
+
if (code) {
|
|
584
|
+
reject(`"react-native bundle" command exited with code ${code}.`);
|
|
585
|
+
} else {
|
|
586
|
+
resolve();
|
|
587
|
+
}
|
|
588
|
+
})
|
|
589
|
+
})
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
module.exports = {
|
|
593
|
+
supportPlatforms,
|
|
594
|
+
parseProcess,
|
|
595
|
+
CInfo,
|
|
596
|
+
CWarning,
|
|
597
|
+
CError,
|
|
598
|
+
color,
|
|
599
|
+
rmColor,
|
|
600
|
+
errMsg,
|
|
601
|
+
wcwidth,
|
|
602
|
+
makeTable,
|
|
603
|
+
|
|
604
|
+
getCacheDir,
|
|
605
|
+
dirExist,
|
|
606
|
+
fileExist,
|
|
607
|
+
fileMd5,
|
|
608
|
+
getDiff,
|
|
609
|
+
packIpa,
|
|
610
|
+
packZip,
|
|
611
|
+
enumZipEntries,
|
|
612
|
+
readZipEntireBuffer,
|
|
613
|
+
saveZipFile,
|
|
614
|
+
getCommonPrefix,
|
|
615
|
+
getCommonPath,
|
|
616
|
+
|
|
617
|
+
getRNVersion,
|
|
618
|
+
getEasyVersion,
|
|
619
|
+
setConfig,
|
|
620
|
+
getConfig,
|
|
621
|
+
getAppId,
|
|
622
|
+
download,
|
|
623
|
+
requestAPI,
|
|
624
|
+
execCommand,
|
|
625
|
+
};
|