react-native-update-cli 2.9.3 → 2.9.5
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.
- package/lib/api.js +23 -9
- package/lib/app.d.ts +5 -1
- package/lib/app.js +23 -13
- package/lib/bundle-pack.js +2 -1
- package/lib/diff.js +98 -17
- package/lib/module-manager.js +0 -3
- package/lib/package.js +7 -3
- package/lib/utils/http-helper.js +1 -1
- package/lib/utils/zip-entries.d.ts +1 -0
- package/lib/utils/zip-entries.js +61 -0
- package/lib/utils/zip-options.d.ts +9 -0
- package/lib/utils/zip-options.js +272 -0
- package/lib/versions.js +12 -12
- package/package.json +1 -1
- package/src/api.ts +32 -9
- package/src/app.ts +24 -11
- package/src/bundle-pack.ts +6 -1
- package/src/diff.ts +209 -15
- package/src/module-manager.ts +0 -4
- package/src/package.ts +5 -4
- package/src/utils/http-helper.ts +1 -1
- package/src/utils/zip-entries.ts +69 -0
- package/src/utils/zip-options.ts +173 -0
- package/src/versions.ts +12 -12
package/src/diff.ts
CHANGED
|
@@ -6,9 +6,54 @@ import type { CommandContext } from './types';
|
|
|
6
6
|
import { translateOptions } from './utils';
|
|
7
7
|
import { isPPKBundleFileName, scriptName, tempDir } from './utils/constants';
|
|
8
8
|
import { t } from './utils/i18n';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
enumZipEntries,
|
|
11
|
+
readEntry,
|
|
12
|
+
readEntryPrefix,
|
|
13
|
+
} from './utils/zip-entries';
|
|
14
|
+
import {
|
|
15
|
+
ZIP_ENTRY_SNIFF_BYTES,
|
|
16
|
+
zipOptionsForManifestEntry,
|
|
17
|
+
zipOptionsForPatchEntry,
|
|
18
|
+
zipOptionsForPayloadEntry,
|
|
19
|
+
} from './utils/zip-options';
|
|
10
20
|
|
|
11
21
|
type Diff = (oldSource?: Buffer, newSource?: Buffer) => Buffer;
|
|
22
|
+
type HpatchCover = {
|
|
23
|
+
oldPos: number | string | bigint;
|
|
24
|
+
newPos: number | string | bigint;
|
|
25
|
+
len: number | string | bigint;
|
|
26
|
+
};
|
|
27
|
+
type HpatchCompatiblePlan = {
|
|
28
|
+
covers?: HpatchCover[];
|
|
29
|
+
};
|
|
30
|
+
type HdiffWithCoversOptions = {
|
|
31
|
+
mode?: 'replace' | 'merge' | 'native-coalesce';
|
|
32
|
+
};
|
|
33
|
+
type HdiffModule = {
|
|
34
|
+
diff?: Diff;
|
|
35
|
+
diffWithCovers?: (
|
|
36
|
+
oldSource: Buffer,
|
|
37
|
+
newSource: Buffer,
|
|
38
|
+
covers: HpatchCover[],
|
|
39
|
+
options?: HdiffWithCoversOptions,
|
|
40
|
+
) => { diff?: Buffer };
|
|
41
|
+
};
|
|
42
|
+
type BsdiffModule = {
|
|
43
|
+
diff?: Diff;
|
|
44
|
+
};
|
|
45
|
+
type ChiffModule = {
|
|
46
|
+
hpatchCompatiblePlanResult?: (
|
|
47
|
+
oldSource: Buffer,
|
|
48
|
+
newSource: Buffer,
|
|
49
|
+
) => HpatchCompatiblePlan;
|
|
50
|
+
hpatchApproximatePlanResult?: (
|
|
51
|
+
oldSource: Buffer,
|
|
52
|
+
newSource: Buffer,
|
|
53
|
+
) => HpatchCompatiblePlan;
|
|
54
|
+
};
|
|
55
|
+
type ChiffHpatchPolicy = 'off' | 'costed';
|
|
56
|
+
type ChiffHpatchExactPolicy = 'off' | 'on';
|
|
12
57
|
type EntryMap = Record<string, { crc32: number; fileName: string }>;
|
|
13
58
|
type CrcMap = Record<number, string>;
|
|
14
59
|
type CopyMap = Record<string, string>;
|
|
@@ -28,22 +73,134 @@ type DiffCommandConfig = {
|
|
|
28
73
|
|
|
29
74
|
export { enumZipEntries, readEntry };
|
|
30
75
|
|
|
31
|
-
const
|
|
76
|
+
const loadModule = <T>(pkgName: string): T | undefined => {
|
|
32
77
|
const resolvePaths = ['.', npm.packages, yarn.packages];
|
|
33
78
|
|
|
34
79
|
try {
|
|
35
80
|
const resolved = require.resolve(pkgName, { paths: resolvePaths });
|
|
36
|
-
|
|
37
|
-
if (mod?.diff) {
|
|
38
|
-
return mod.diff as Diff;
|
|
39
|
-
}
|
|
81
|
+
return require(resolved) as T;
|
|
40
82
|
} catch {}
|
|
41
83
|
|
|
42
84
|
return undefined;
|
|
43
85
|
};
|
|
44
86
|
|
|
45
|
-
const hdiff =
|
|
46
|
-
const bsdiff =
|
|
87
|
+
const hdiff = loadModule<HdiffModule>('node-hdiffpatch');
|
|
88
|
+
const bsdiff = loadModule<BsdiffModule>('node-bsdiff');
|
|
89
|
+
const chiff = loadModule<ChiffModule>('@chiff/node');
|
|
90
|
+
|
|
91
|
+
// Structured covers are experimental and can be expensive on real Hermes input.
|
|
92
|
+
// Keep native hdiff as the default unless the server explicitly opts in.
|
|
93
|
+
function resolveChiffHpatchPolicy(policy?: unknown): ChiffHpatchPolicy {
|
|
94
|
+
const value = String(
|
|
95
|
+
policy ?? process.env.RNU_CHIFF_HPATCH_POLICY ?? 'off',
|
|
96
|
+
).toLowerCase();
|
|
97
|
+
if (
|
|
98
|
+
value === 'costed' ||
|
|
99
|
+
value === 'on' ||
|
|
100
|
+
value === 'true' ||
|
|
101
|
+
value === '1'
|
|
102
|
+
) {
|
|
103
|
+
return 'costed';
|
|
104
|
+
}
|
|
105
|
+
return 'off';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveChiffHpatchMinNativeBytes(value?: unknown): number {
|
|
109
|
+
const raw = value ?? process.env.RNU_CHIFF_HPATCH_MIN_NATIVE_BYTES ?? 4096;
|
|
110
|
+
const parsed = Number(raw);
|
|
111
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
112
|
+
return 4096;
|
|
113
|
+
}
|
|
114
|
+
return Math.floor(parsed);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveChiffHpatchExactPolicy(policy?: unknown): ChiffHpatchExactPolicy {
|
|
118
|
+
const value = String(
|
|
119
|
+
policy ?? process.env.RNU_CHIFF_HPATCH_EXACT_COVERS ?? 'off',
|
|
120
|
+
).toLowerCase();
|
|
121
|
+
if (value === 'on' || value === 'true' || value === '1') {
|
|
122
|
+
return 'on';
|
|
123
|
+
}
|
|
124
|
+
return 'off';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function createChiffAwareHdiff(
|
|
128
|
+
hdiffModule: HdiffModule,
|
|
129
|
+
chiffModule: ChiffModule | undefined,
|
|
130
|
+
policy: ChiffHpatchPolicy,
|
|
131
|
+
minNativeBytes: number,
|
|
132
|
+
exactPolicy: ChiffHpatchExactPolicy,
|
|
133
|
+
): Diff {
|
|
134
|
+
const baseDiff = hdiffModule.diff;
|
|
135
|
+
if (!baseDiff) {
|
|
136
|
+
throw new Error(t('nodeHdiffpatchRequired', { scriptName }));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (policy === 'off') {
|
|
140
|
+
return baseDiff;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return (oldSource?: Buffer, newSource?: Buffer) => {
|
|
144
|
+
const nativeDiff = baseDiff(oldSource, newSource);
|
|
145
|
+
if (!oldSource || !newSource || !hdiffModule.diffWithCovers) {
|
|
146
|
+
return nativeDiff;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let bestDiff = nativeDiff;
|
|
150
|
+
const tryDiffWithCovers = (
|
|
151
|
+
covers: HpatchCover[],
|
|
152
|
+
mode: 'replace' | 'merge' | 'native-coalesce',
|
|
153
|
+
) => {
|
|
154
|
+
try {
|
|
155
|
+
const result = hdiffModule.diffWithCovers?.(
|
|
156
|
+
oldSource,
|
|
157
|
+
newSource,
|
|
158
|
+
covers,
|
|
159
|
+
{ mode },
|
|
160
|
+
);
|
|
161
|
+
if (
|
|
162
|
+
Buffer.isBuffer(result?.diff) &&
|
|
163
|
+
result.diff.length < bestDiff.length
|
|
164
|
+
) {
|
|
165
|
+
bestDiff = result.diff;
|
|
166
|
+
}
|
|
167
|
+
} catch {}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
tryDiffWithCovers([], 'native-coalesce');
|
|
171
|
+
|
|
172
|
+
if (nativeDiff.length < minNativeBytes) {
|
|
173
|
+
return bestDiff;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const approximatePlan = chiffModule?.hpatchApproximatePlanResult?.(
|
|
178
|
+
oldSource,
|
|
179
|
+
newSource,
|
|
180
|
+
);
|
|
181
|
+
if (Array.isArray(approximatePlan?.covers)) {
|
|
182
|
+
tryDiffWithCovers(approximatePlan.covers, 'merge');
|
|
183
|
+
}
|
|
184
|
+
} catch {}
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
exactPolicy === 'off' ||
|
|
188
|
+
!chiffModule?.hpatchCompatiblePlanResult
|
|
189
|
+
) {
|
|
190
|
+
return bestDiff;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const plan = chiffModule.hpatchCompatiblePlanResult(oldSource, newSource);
|
|
195
|
+
if (Array.isArray(plan.covers)) {
|
|
196
|
+
tryDiffWithCovers(plan.covers, 'replace');
|
|
197
|
+
tryDiffWithCovers(plan.covers, 'merge');
|
|
198
|
+
}
|
|
199
|
+
} catch {}
|
|
200
|
+
|
|
201
|
+
return bestDiff;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
47
204
|
|
|
48
205
|
function basename(fn: string): string | undefined {
|
|
49
206
|
const m = /^(.+\/)[^\/]+\/?$/.exec(fn);
|
|
@@ -123,6 +280,7 @@ async function diffFromPPK(
|
|
|
123
280
|
zipfile.addBuffer(
|
|
124
281
|
diffFn(originSource, newSource),
|
|
125
282
|
`${entry.fileName}.patch`,
|
|
283
|
+
zipOptionsForPatchEntry(),
|
|
126
284
|
);
|
|
127
285
|
//console.log('End diff');
|
|
128
286
|
} else {
|
|
@@ -150,6 +308,11 @@ async function diffFromPPK(
|
|
|
150
308
|
addEntry(basePath);
|
|
151
309
|
}
|
|
152
310
|
|
|
311
|
+
const entryPrefix = await readEntryPrefix(
|
|
312
|
+
entry,
|
|
313
|
+
nextZipfile,
|
|
314
|
+
ZIP_ENTRY_SNIFF_BYTES,
|
|
315
|
+
);
|
|
153
316
|
await new Promise<void>((resolve, reject) => {
|
|
154
317
|
nextZipfile.openReadStream(entry, (err, readStream) => {
|
|
155
318
|
if (err) {
|
|
@@ -160,7 +323,11 @@ async function diffFromPPK(
|
|
|
160
323
|
new Error(`Unable to read zip entry: ${entry.fileName}`),
|
|
161
324
|
);
|
|
162
325
|
}
|
|
163
|
-
zipfile.addReadStream(
|
|
326
|
+
zipfile.addReadStream(
|
|
327
|
+
readStream,
|
|
328
|
+
entry.fileName,
|
|
329
|
+
zipOptionsForPayloadEntry(entry.fileName, entryPrefix),
|
|
330
|
+
);
|
|
164
331
|
readStream.on('end', () => {
|
|
165
332
|
//console.log('add finished');
|
|
166
333
|
resolve(void 0);
|
|
@@ -183,6 +350,7 @@ async function diffFromPPK(
|
|
|
183
350
|
zipfile.addBuffer(
|
|
184
351
|
Buffer.from(JSON.stringify({ copies, deletes })),
|
|
185
352
|
'__diff.json',
|
|
353
|
+
zipOptionsForManifestEntry(),
|
|
186
354
|
);
|
|
187
355
|
zipfile.end();
|
|
188
356
|
await writePromise;
|
|
@@ -248,6 +416,7 @@ async function diffFromPackage(
|
|
|
248
416
|
zipfile.addBuffer(
|
|
249
417
|
diffFn(originSource, newSource),
|
|
250
418
|
`${entry.fileName}.patch`,
|
|
419
|
+
zipOptionsForPatchEntry(),
|
|
251
420
|
);
|
|
252
421
|
//console.log('End diff');
|
|
253
422
|
} else {
|
|
@@ -263,6 +432,11 @@ async function diffFromPackage(
|
|
|
263
432
|
return;
|
|
264
433
|
}
|
|
265
434
|
|
|
435
|
+
const entryPrefix = await readEntryPrefix(
|
|
436
|
+
entry,
|
|
437
|
+
nextZipfile,
|
|
438
|
+
ZIP_ENTRY_SNIFF_BYTES,
|
|
439
|
+
);
|
|
266
440
|
await new Promise<void>((resolve, reject) => {
|
|
267
441
|
nextZipfile.openReadStream(entry, (err, readStream) => {
|
|
268
442
|
if (err) {
|
|
@@ -273,7 +447,11 @@ async function diffFromPackage(
|
|
|
273
447
|
new Error(`Unable to read zip entry: ${entry.fileName}`),
|
|
274
448
|
);
|
|
275
449
|
}
|
|
276
|
-
zipfile.addReadStream(
|
|
450
|
+
zipfile.addReadStream(
|
|
451
|
+
readStream,
|
|
452
|
+
entry.fileName,
|
|
453
|
+
zipOptionsForPayloadEntry(entry.fileName, entryPrefix),
|
|
454
|
+
);
|
|
277
455
|
readStream.on('end', () => {
|
|
278
456
|
//console.log('add finished');
|
|
279
457
|
resolve(void 0);
|
|
@@ -283,13 +461,22 @@ async function diffFromPackage(
|
|
|
283
461
|
}
|
|
284
462
|
});
|
|
285
463
|
|
|
286
|
-
zipfile.addBuffer(
|
|
464
|
+
zipfile.addBuffer(
|
|
465
|
+
Buffer.from(JSON.stringify({ copies })),
|
|
466
|
+
'__diff.json',
|
|
467
|
+
zipOptionsForManifestEntry(),
|
|
468
|
+
);
|
|
287
469
|
zipfile.end();
|
|
288
470
|
await writePromise;
|
|
289
471
|
}
|
|
290
472
|
|
|
291
473
|
type DiffCommandOptions = {
|
|
292
474
|
customDiff?: Diff;
|
|
475
|
+
customHdiffModule?: HdiffModule;
|
|
476
|
+
customChiffModule?: ChiffModule;
|
|
477
|
+
chiffHpatchPolicy?: ChiffHpatchPolicy;
|
|
478
|
+
chiffHpatchMinNativeBytes?: number | string;
|
|
479
|
+
chiffHpatchExactCovers?: ChiffHpatchExactPolicy | boolean | string | number;
|
|
293
480
|
[key: string]: any;
|
|
294
481
|
};
|
|
295
482
|
|
|
@@ -302,16 +489,23 @@ function resolveDiffImplementation(
|
|
|
302
489
|
}
|
|
303
490
|
|
|
304
491
|
if (useHdiff) {
|
|
305
|
-
|
|
492
|
+
const hdiffModule = options.customHdiffModule ?? hdiff;
|
|
493
|
+
if (!hdiffModule?.diff) {
|
|
306
494
|
throw new Error(t('nodeHdiffpatchRequired', { scriptName }));
|
|
307
495
|
}
|
|
308
|
-
return
|
|
496
|
+
return createChiffAwareHdiff(
|
|
497
|
+
hdiffModule,
|
|
498
|
+
options.customChiffModule ?? chiff,
|
|
499
|
+
resolveChiffHpatchPolicy(options.chiffHpatchPolicy),
|
|
500
|
+
resolveChiffHpatchMinNativeBytes(options.chiffHpatchMinNativeBytes),
|
|
501
|
+
resolveChiffHpatchExactPolicy(options.chiffHpatchExactCovers),
|
|
502
|
+
);
|
|
309
503
|
}
|
|
310
504
|
|
|
311
|
-
if (!bsdiff) {
|
|
505
|
+
if (!bsdiff?.diff) {
|
|
312
506
|
throw new Error(t('nodeBsdiffRequired', { scriptName }));
|
|
313
507
|
}
|
|
314
|
-
return bsdiff;
|
|
508
|
+
return bsdiff.diff;
|
|
315
509
|
}
|
|
316
510
|
|
|
317
511
|
function diffArgsCheck(
|
package/src/module-manager.ts
CHANGED
package/src/package.ts
CHANGED
|
@@ -99,7 +99,7 @@ async function uploadNativePackage(
|
|
|
99
99
|
const { appId: appIdInPkg, appKey: appKeyInPkg } = info;
|
|
100
100
|
const { appId, appKey } = await getSelectedApp(config.platform);
|
|
101
101
|
|
|
102
|
-
if (appIdInPkg && appIdInPkg
|
|
102
|
+
if (appIdInPkg && String(appIdInPkg) !== appId) {
|
|
103
103
|
throw new Error(t(config.appIdMismatchKey, { appIdInPkg, appId }));
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -175,10 +175,11 @@ export async function listPackage(appId: string) {
|
|
|
175
175
|
|
|
176
176
|
export async function choosePackage(appId: string) {
|
|
177
177
|
const list = await listPackage(appId);
|
|
178
|
+
const packageMap = new Map(list?.map((v) => [v.id.toString(), v]));
|
|
178
179
|
|
|
179
180
|
while (true) {
|
|
180
181
|
const id = await question(t('enterNativePackageId'));
|
|
181
|
-
const app =
|
|
182
|
+
const app = packageMap.get(id);
|
|
182
183
|
if (app) {
|
|
183
184
|
return app;
|
|
184
185
|
}
|
|
@@ -329,7 +330,7 @@ export const packageCommands = {
|
|
|
329
330
|
packages: async ({ options }: { options: { platform: Platform } }) => {
|
|
330
331
|
const platform = await getPlatform(options.platform);
|
|
331
332
|
const { appId } = await getSelectedApp(platform);
|
|
332
|
-
await listPackage(appId);
|
|
333
|
+
await listPackage(String(appId));
|
|
333
334
|
},
|
|
334
335
|
deletePackage: async ({
|
|
335
336
|
args,
|
|
@@ -347,7 +348,7 @@ export const packageCommands = {
|
|
|
347
348
|
|
|
348
349
|
if (!appId) {
|
|
349
350
|
const platform = await getPlatform(options.platform);
|
|
350
|
-
appId = (await getSelectedApp(platform)).appId
|
|
351
|
+
appId = (await getSelectedApp(platform)).appId;
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
// If no packageId provided as argument, let user choose from list
|
package/src/utils/http-helper.ts
CHANGED
package/src/utils/zip-entries.ts
CHANGED
|
@@ -28,6 +28,75 @@ export function readEntry(
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export function readEntryPrefix(
|
|
32
|
+
entry: Entry,
|
|
33
|
+
zipFile: YauzlZipFile,
|
|
34
|
+
maxBytes: number,
|
|
35
|
+
): Promise<Buffer> {
|
|
36
|
+
if (maxBytes <= 0) {
|
|
37
|
+
return Promise.resolve(Buffer.alloc(0));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const buffers: Buffer[] = [];
|
|
41
|
+
let length = 0;
|
|
42
|
+
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
zipFile.openReadStream(entry, (err, stream) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
return reject(err);
|
|
47
|
+
}
|
|
48
|
+
if (!stream) {
|
|
49
|
+
return reject(new Error(`Unable to read zip entry: ${entry.fileName}`));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let settled = false;
|
|
53
|
+
const cleanup = () => {
|
|
54
|
+
stream.off('data', onData);
|
|
55
|
+
stream.off('end', onEnd);
|
|
56
|
+
stream.off('error', onError);
|
|
57
|
+
};
|
|
58
|
+
const finish = () => {
|
|
59
|
+
if (settled) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
settled = true;
|
|
63
|
+
cleanup();
|
|
64
|
+
resolve(Buffer.concat(buffers, length));
|
|
65
|
+
};
|
|
66
|
+
const onData = (chunk: Buffer) => {
|
|
67
|
+
const remaining = maxBytes - length;
|
|
68
|
+
if (remaining <= 0) {
|
|
69
|
+
finish();
|
|
70
|
+
stream.destroy();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const slice =
|
|
75
|
+
chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
|
|
76
|
+
buffers.push(slice);
|
|
77
|
+
length += slice.length;
|
|
78
|
+
if (length >= maxBytes) {
|
|
79
|
+
finish();
|
|
80
|
+
stream.destroy();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const onEnd = () => finish();
|
|
84
|
+
const onError = (error: Error) => {
|
|
85
|
+
if (settled) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
settled = true;
|
|
89
|
+
cleanup();
|
|
90
|
+
reject(error);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
stream.on('data', onData);
|
|
94
|
+
stream.once('end', onEnd);
|
|
95
|
+
stream.once('error', onError);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
31
100
|
export async function enumZipEntries(
|
|
32
101
|
zipFn: string,
|
|
33
102
|
callback: (
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
|
|
4
|
+
export type ZipEntryOptions = {
|
|
5
|
+
compress?: boolean;
|
|
6
|
+
compressionLevel?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const ZIP_ENTRY_SNIFF_BYTES = 64;
|
|
10
|
+
|
|
11
|
+
const alreadyCompressedExtensions = new Set([
|
|
12
|
+
'.7z',
|
|
13
|
+
'.aab',
|
|
14
|
+
'.apk',
|
|
15
|
+
'.br',
|
|
16
|
+
'.bz2',
|
|
17
|
+
'.gif',
|
|
18
|
+
'.gz',
|
|
19
|
+
'.heic',
|
|
20
|
+
'.jpeg',
|
|
21
|
+
'.jpg',
|
|
22
|
+
'.lzma',
|
|
23
|
+
'.mp3',
|
|
24
|
+
'.mp4',
|
|
25
|
+
'.ogg',
|
|
26
|
+
'.png',
|
|
27
|
+
'.webm',
|
|
28
|
+
'.webp',
|
|
29
|
+
'.woff',
|
|
30
|
+
'.woff2',
|
|
31
|
+
'.xz',
|
|
32
|
+
'.zip',
|
|
33
|
+
'.zst',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const HERMES_MAGIC = Buffer.from([
|
|
37
|
+
0xc6, 0x1f, 0xbc, 0x03, 0xc1, 0x03, 0x19, 0x1f,
|
|
38
|
+
]);
|
|
39
|
+
const HERMES_DELTA_MAGIC = Buffer.from([
|
|
40
|
+
0x39, 0xe0, 0x43, 0xfc, 0x3e, 0xfc, 0xe6, 0xe0,
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
function startsWith(bytes: Buffer, signature: Buffer): boolean {
|
|
44
|
+
return (
|
|
45
|
+
bytes.length >= signature.length &&
|
|
46
|
+
bytes.subarray(0, signature.length).equals(signature)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasHermesBytecodeMagic(bytes: Buffer): boolean {
|
|
51
|
+
return (
|
|
52
|
+
startsWith(bytes, HERMES_MAGIC) || startsWith(bytes, HERMES_DELTA_MAGIC)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function hasAlreadyCompressedMagic(bytes: Buffer): boolean {
|
|
57
|
+
if (bytes.length < 2) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (startsWith(bytes, Buffer.from([0x89, 0x50, 0x4e, 0x47]))) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (startsWith(bytes, Buffer.from([0xff, 0xd8, 0xff]))) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
startsWith(bytes, Buffer.from('GIF87a', 'ascii')) ||
|
|
69
|
+
startsWith(bytes, Buffer.from('GIF89a', 'ascii'))
|
|
70
|
+
) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
bytes.length >= 12 &&
|
|
75
|
+
bytes.subarray(0, 4).equals(Buffer.from('RIFF', 'ascii')) &&
|
|
76
|
+
bytes.subarray(8, 12).equals(Buffer.from('WEBP', 'ascii'))
|
|
77
|
+
) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
startsWith(bytes, Buffer.from([0x50, 0x4b, 0x03, 0x04])) ||
|
|
82
|
+
startsWith(bytes, Buffer.from([0x50, 0x4b, 0x05, 0x06])) ||
|
|
83
|
+
startsWith(bytes, Buffer.from([0x50, 0x4b, 0x07, 0x08]))
|
|
84
|
+
) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (startsWith(bytes, Buffer.from([0x1f, 0x8b]))) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (startsWith(bytes, Buffer.from('BZh', 'ascii'))) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (startsWith(bytes, Buffer.from([0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00]))) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (startsWith(bytes, Buffer.from([0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c]))) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (startsWith(bytes, Buffer.from([0x28, 0xb5, 0x2f, 0xfd]))) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (bytes.length >= 12 && bytes.subarray(4, 8).equals(Buffer.from('ftyp'))) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (startsWith(bytes, Buffer.from('OggS', 'ascii'))) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (startsWith(bytes, Buffer.from('ID3', 'ascii'))) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
bytes[0] === 0xff &&
|
|
113
|
+
bytes.length >= 2 &&
|
|
114
|
+
(bytes[1] & 0xe0) === 0xe0
|
|
115
|
+
) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
startsWith(bytes, Buffer.from('wOFF', 'ascii')) ||
|
|
120
|
+
startsWith(bytes, Buffer.from('wOF2', 'ascii'))
|
|
121
|
+
) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readFilePrefix(filePath: string): Buffer {
|
|
129
|
+
const buffer = Buffer.alloc(ZIP_ENTRY_SNIFF_BYTES);
|
|
130
|
+
const fd = fs.openSync(filePath, 'r');
|
|
131
|
+
try {
|
|
132
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
133
|
+
return buffer.subarray(0, bytesRead);
|
|
134
|
+
} finally {
|
|
135
|
+
fs.closeSync(fd);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function zipOptionsForPatchEntry(): ZipEntryOptions {
|
|
140
|
+
return { compress: false };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function zipOptionsForManifestEntry(): ZipEntryOptions {
|
|
144
|
+
return { compress: false };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function zipOptionsForPayloadEntry(
|
|
148
|
+
fileName: string,
|
|
149
|
+
prefix?: Buffer,
|
|
150
|
+
): ZipEntryOptions {
|
|
151
|
+
if (prefix && prefix.length > 0) {
|
|
152
|
+
if (hasHermesBytecodeMagic(prefix)) {
|
|
153
|
+
// Hermes bytecode is binary, but still benefits significantly from zip deflate.
|
|
154
|
+
return { compress: true, compressionLevel: 9 };
|
|
155
|
+
}
|
|
156
|
+
if (hasAlreadyCompressedMagic(prefix)) {
|
|
157
|
+
return { compress: false };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const extension = path.extname(fileName).toLowerCase();
|
|
162
|
+
if (alreadyCompressedExtensions.has(extension)) {
|
|
163
|
+
return { compress: false };
|
|
164
|
+
}
|
|
165
|
+
return { compress: true, compressionLevel: 9 };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function zipOptionsForPayloadFile(
|
|
169
|
+
filePath: string,
|
|
170
|
+
entryName = filePath,
|
|
171
|
+
): ZipEntryOptions {
|
|
172
|
+
return zipOptionsForPayloadEntry(entryName, readFilePrefix(filePath));
|
|
173
|
+
}
|