rn-update-cli 1.0.0

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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/README.zh-CN.md +167 -0
  4. package/cli.json +525 -0
  5. package/lib/api.d.ts +56 -0
  6. package/lib/api.js +671 -0
  7. package/lib/app.d.ts +107 -0
  8. package/lib/app.js +329 -0
  9. package/lib/auto-update.d.ts +35 -0
  10. package/lib/auto-update.js +507 -0
  11. package/lib/bin.d.ts +2 -0
  12. package/lib/bin.js +133 -0
  13. package/lib/bundle-pack.d.ts +6 -0
  14. package/lib/bundle-pack.js +91 -0
  15. package/lib/bundle-runner.d.ts +168 -0
  16. package/lib/bundle-runner.js +1305 -0
  17. package/lib/bundle.d.ts +45 -0
  18. package/lib/bundle.js +226 -0
  19. package/lib/cache.d.ts +10 -0
  20. package/lib/cache.js +25 -0
  21. package/lib/commands.d.ts +11 -0
  22. package/lib/commands.js +72 -0
  23. package/lib/diff.d.ts +18 -0
  24. package/lib/diff.js +690 -0
  25. package/lib/exports.d.ts +11 -0
  26. package/lib/exports.js +20 -0
  27. package/lib/install.d.ts +9 -0
  28. package/lib/install.js +51 -0
  29. package/lib/locales/en.d.ts +187 -0
  30. package/lib/locales/en.js +194 -0
  31. package/lib/locales/zh.d.ts +187 -0
  32. package/lib/locales/zh.js +194 -0
  33. package/lib/native-package.d.ts +21 -0
  34. package/lib/native-package.js +298 -0
  35. package/lib/package.d.ts +68 -0
  36. package/lib/package.js +487 -0
  37. package/lib/provider.d.ts +24 -0
  38. package/lib/provider.js +244 -0
  39. package/lib/standalone-publish.d.ts +17 -0
  40. package/lib/standalone-publish.js +433 -0
  41. package/lib/symbolicate.d.ts +35 -0
  42. package/lib/symbolicate.js +130 -0
  43. package/lib/types.d.ts +152 -0
  44. package/lib/types.js +2 -0
  45. package/lib/user.d.ts +8 -0
  46. package/lib/user.js +50 -0
  47. package/lib/utils/add-gitignore.d.ts +6 -0
  48. package/lib/utils/add-gitignore.js +44 -0
  49. package/lib/utils/app-info-parser/aab.d.ts +27 -0
  50. package/lib/utils/app-info-parser/aab.js +256 -0
  51. package/lib/utils/app-info-parser/apk.d.ts +19 -0
  52. package/lib/utils/app-info-parser/apk.js +95 -0
  53. package/lib/utils/app-info-parser/app.d.ts +9 -0
  54. package/lib/utils/app-info-parser/app.js +15 -0
  55. package/lib/utils/app-info-parser/index.d.ts +21 -0
  56. package/lib/utils/app-info-parser/index.js +51 -0
  57. package/lib/utils/app-info-parser/ipa.d.ts +19 -0
  58. package/lib/utils/app-info-parser/ipa.js +101 -0
  59. package/lib/utils/app-info-parser/resource-finder.d.ts +49 -0
  60. package/lib/utils/app-info-parser/resource-finder.js +429 -0
  61. package/lib/utils/app-info-parser/utils.d.ts +36 -0
  62. package/lib/utils/app-info-parser/utils.js +140 -0
  63. package/lib/utils/app-info-parser/xml-parser/binary.d.ts +61 -0
  64. package/lib/utils/app-info-parser/xml-parser/binary.js +577 -0
  65. package/lib/utils/app-info-parser/xml-parser/manifest.d.ts +15 -0
  66. package/lib/utils/app-info-parser/xml-parser/manifest.js +207 -0
  67. package/lib/utils/app-info-parser/zip.d.ts +29 -0
  68. package/lib/utils/app-info-parser/zip.js +97 -0
  69. package/lib/utils/check-lockfile.d.ts +6 -0
  70. package/lib/utils/check-lockfile.js +93 -0
  71. package/lib/utils/check-plugin.d.ts +12 -0
  72. package/lib/utils/check-plugin.js +36 -0
  73. package/lib/utils/command-result.d.ts +8 -0
  74. package/lib/utils/command-result.js +27 -0
  75. package/lib/utils/constants.d.ts +10 -0
  76. package/lib/utils/constants.js +39 -0
  77. package/lib/utils/dep-versions.d.ts +20 -0
  78. package/lib/utils/dep-versions.js +129 -0
  79. package/lib/utils/git.d.ts +14 -0
  80. package/lib/utils/git.js +64 -0
  81. package/lib/utils/hbcTransform.d.ts +111 -0
  82. package/lib/utils/hbcTransform.js +561 -0
  83. package/lib/utils/hermes-base.d.ts +227 -0
  84. package/lib/utils/hermes-base.js +1287 -0
  85. package/lib/utils/hermes-literals.d.ts +64 -0
  86. package/lib/utils/hermes-literals.js +260 -0
  87. package/lib/utils/http-helper.d.ts +10 -0
  88. package/lib/utils/http-helper.js +150 -0
  89. package/lib/utils/i18n.d.ts +22 -0
  90. package/lib/utils/i18n.js +46 -0
  91. package/lib/utils/index.d.ts +67 -0
  92. package/lib/utils/index.js +545 -0
  93. package/lib/utils/latest-version/index.d.ts +154 -0
  94. package/lib/utils/latest-version/index.js +331 -0
  95. package/lib/utils/options.d.ts +10 -0
  96. package/lib/utils/options.js +68 -0
  97. package/lib/utils/plugin-config.d.ts +14 -0
  98. package/lib/utils/plugin-config.js +66 -0
  99. package/lib/utils/runtime.d.ts +54 -0
  100. package/lib/utils/runtime.js +263 -0
  101. package/lib/utils/slim-sourcemap.d.ts +28 -0
  102. package/lib/utils/slim-sourcemap.js +106 -0
  103. package/lib/utils/zip-entries.d.ts +10 -0
  104. package/lib/utils/zip-entries.js +211 -0
  105. package/lib/utils/zip-options.d.ts +17 -0
  106. package/lib/utils/zip-options.js +190 -0
  107. package/lib/utils/zip-range.d.ts +148 -0
  108. package/lib/utils/zip-range.js +581 -0
  109. package/lib/utils/zip-raw-writer.d.ts +52 -0
  110. package/lib/utils/zip-raw-writer.js +209 -0
  111. package/lib/version-operations.d.ts +21 -0
  112. package/lib/version-operations.js +320 -0
  113. package/lib/versions.d.ts +66 -0
  114. package/lib/versions.js +402 -0
  115. package/package.json +113 -0
  116. package/proto/Configuration.proto +183 -0
  117. package/proto/Resources.proto +569 -0
@@ -0,0 +1,581 @@
1
+ "use strict";
2
+ /**
3
+ * [INPUT]: 依赖 HTTP Range/If-Range 响应、ZIP 中央目录、流与 ZIP 条目读取器
4
+ * [OUTPUT]: 对外提供远程 ZIP 定位、范围读取、缓存读取器和协议错误类型
5
+ * [POS]: CLI 远程制品按需读取底座,减少 Hermes base 与 bundle 获取的网络/磁盘开销
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.HttpRangeReader = exports.RangeUnsupportedError = exports.ZIP_DEFLATED = exports.ZIP_STORED = void 0;
12
+ exports.locateZipEntry = locateZipEntry;
13
+ exports.readZipEntryWithLocation = readZipEntryWithLocation;
14
+ exports.bundleLocationFields = bundleLocationFields;
15
+ exports.parseContentRange = parseContentRange;
16
+ exports.rangeValidator = rangeValidator;
17
+ exports.fetchZipEntryData = fetchZipEntryData;
18
+ exports.parseEndOfCentralDirectory = parseEndOfCentralDirectory;
19
+ exports.openRemoteZip = openRemoteZip;
20
+ exports.readRemoteZipEntry = readRemoteZipEntry;
21
+ // Byte-range access to zip archives.
22
+ //
23
+ // Two halves of the same trick: at publish time `locateZipEntry` records where
24
+ // the bundle's compressed bytes sit inside the ppk (so the server can hand
25
+ // that location to the next build), and at build time `fetchZipEntryData` /
26
+ // `openRemoteZip` read just the bundle of a remote archive through HTTP Range
27
+ // requests instead of downloading the whole ppk / apk / ipa. Every helper
28
+ // throws on anything unexpected; callers fall back to a full download.
29
+ const fs_extra_1 = __importDefault(require("fs-extra"));
30
+ const stream_1 = require("stream");
31
+ const yauzl_1 = require("yauzl");
32
+ const zlib_1 = require("zlib");
33
+ const runtime_1 = require("./runtime");
34
+ const zip_entries_1 = require("./zip-entries");
35
+ exports.ZIP_STORED = 0;
36
+ exports.ZIP_DEFLATED = 8;
37
+ const LOCAL_HEADER_SIGNATURE = 0x04034b50;
38
+ const LOCAL_HEADER_SIZE = 30;
39
+ /**
40
+ * Exactly what yauzl reads to find the EOCD (22 + 65535 comment + 20 zip64
41
+ * locator), so its first read is a cache hit; small archives fit entirely.
42
+ */
43
+ const TAIL_BYTES = 22 + 0xffff + 20;
44
+ /** minimum span fetched for header reads, so header + directory share a request */
45
+ const CHUNK_BYTES = 64 * 1024;
46
+ /** extra bytes past a hinted entry, covering a longer local extra field */
47
+ const HINT_SLACK_BYTES = 4 * 1024;
48
+ /** Data location of the first entry matching `matches` in a local zip. */
49
+ async function locateZipEntry(zipPath, matches) {
50
+ const found = await openFirstEntry(zipPath, matches);
51
+ if (!found)
52
+ return null;
53
+ found.zipFile.close();
54
+ return locationOfEntry(zipPath, found.entry);
55
+ }
56
+ /**
57
+ * Content and data location of the first matching entry in one pass over the
58
+ * archive (publish needs both: the bundle to hash and cache, its location to
59
+ * report to the server).
60
+ */
61
+ async function readZipEntryWithLocation(zipPath, matches) {
62
+ const found = await openFirstEntry(zipPath, matches);
63
+ if (!found)
64
+ return null;
65
+ let data;
66
+ try {
67
+ data = await (0, zip_entries_1.readEntry)(found.entry, found.zipFile);
68
+ }
69
+ finally {
70
+ found.zipFile.close();
71
+ }
72
+ return { data, location: await locationOfEntry(zipPath, found.entry) };
73
+ }
74
+ async function locationOfEntry(zipPath, entry) {
75
+ const header = Buffer.alloc(LOCAL_HEADER_SIZE);
76
+ const fd = await fs_extra_1.default.open(zipPath, 'r');
77
+ try {
78
+ const { bytesRead } = await fs_extra_1.default.read(fd, header, 0, LOCAL_HEADER_SIZE, entry.relativeOffsetOfLocalHeader);
79
+ if (bytesRead !== LOCAL_HEADER_SIZE) {
80
+ throw new Error('truncated local file header');
81
+ }
82
+ }
83
+ finally {
84
+ await fs_extra_1.default.close(fd);
85
+ }
86
+ if (header.readUInt32LE(0) !== LOCAL_HEADER_SIGNATURE) {
87
+ throw new Error('invalid local file header signature');
88
+ }
89
+ const fileNameLength = header.readUInt16LE(26);
90
+ const extraFieldLength = header.readUInt16LE(28);
91
+ return {
92
+ fileName: entry.fileName,
93
+ dataOffset: entry.relativeOffsetOfLocalHeader +
94
+ LOCAL_HEADER_SIZE +
95
+ fileNameLength +
96
+ extraFieldLength,
97
+ compressedSize: entry.compressedSize,
98
+ uncompressedSize: entry.uncompressedSize,
99
+ compressionMethod: entry.compressionMethod,
100
+ };
101
+ }
102
+ /** Open a local zip and stop at the first matching entry (caller closes). */
103
+ function openFirstEntry(zipPath, matches) {
104
+ return new Promise((resolve, reject) => {
105
+ (0, yauzl_1.open)(zipPath, { lazyEntries: true, autoClose: false }, (err, zipFile) => {
106
+ if (err)
107
+ return reject(err);
108
+ let settled = false;
109
+ zipFile.on('error', (error) => {
110
+ if (!settled) {
111
+ settled = true;
112
+ reject(error);
113
+ }
114
+ });
115
+ zipFile.on('end', () => {
116
+ if (!settled) {
117
+ settled = true;
118
+ zipFile.close();
119
+ resolve(null);
120
+ }
121
+ });
122
+ zipFile.on('entry', (entry) => {
123
+ if (settled)
124
+ return;
125
+ if (matches(entry.fileName)) {
126
+ settled = true;
127
+ resolve({ zipFile, entry });
128
+ return;
129
+ }
130
+ zipFile.readEntry();
131
+ });
132
+ zipFile.readEntry();
133
+ });
134
+ });
135
+ }
136
+ /**
137
+ * The `bundleOffset` / `bundleCompressedSize` / `bundleCompression` fields for
138
+ * version/create and package/create, or nothing when the entry cannot be
139
+ * fetched by a single Range request (unknown, empty or exotic compression).
140
+ */
141
+ function bundleLocationFields(location) {
142
+ if (!location ||
143
+ location.compressedSize <= 0 ||
144
+ (location.compressionMethod !== exports.ZIP_STORED &&
145
+ location.compressionMethod !== exports.ZIP_DEFLATED)) {
146
+ return {};
147
+ }
148
+ return {
149
+ bundleOffset: location.dataOffset,
150
+ bundleCompressedSize: location.compressedSize,
151
+ bundleCompression: location.compressionMethod,
152
+ };
153
+ }
154
+ const DEFAULT_HEADER_TIMEOUT_MS = 20_000;
155
+ const DEFAULT_DATA_TIMEOUT_MS = 30_000;
156
+ const DEFAULT_DATA_TIMEOUT_PER_MB_MS = 1_000;
157
+ /** deadline for a request of `bytes`: flat for header-sized reads, scaled otherwise */
158
+ function timeoutFor(bytes, options) {
159
+ if (bytes <= CHUNK_BYTES) {
160
+ return options.headerTimeoutMs ?? DEFAULT_HEADER_TIMEOUT_MS;
161
+ }
162
+ return ((options.dataTimeoutMs ?? DEFAULT_DATA_TIMEOUT_MS) +
163
+ ((options.dataTimeoutPerMbMs ?? DEFAULT_DATA_TIMEOUT_PER_MB_MS) * bytes) /
164
+ (1024 * 1024));
165
+ }
166
+ /** The server answered a Range request with the whole body (HTTP 200). */
167
+ class RangeUnsupportedError extends Error {
168
+ response;
169
+ constructor(response) {
170
+ super(`HTTP ${response.status} without Content-Range`);
171
+ this.response = response;
172
+ this.name = 'RangeUnsupportedError';
173
+ }
174
+ }
175
+ exports.RangeUnsupportedError = RangeUnsupportedError;
176
+ function parseContentRange(header) {
177
+ const match = /^bytes\s+(\d+)-(\d+)\/(\d+)$/i.exec(header?.trim() ?? '');
178
+ if (!match)
179
+ return null;
180
+ const [start, end, total] = match.slice(1).map(Number);
181
+ if (end < start || end >= total)
182
+ return null;
183
+ return { start, end, total };
184
+ }
185
+ /**
186
+ * The validator to send as `If-Range` on later requests for the same object,
187
+ * so a replaced archive answers 200 (handled like a server without Range)
188
+ * instead of mixing bytes of two versions. Weak ETags are not allowed there.
189
+ */
190
+ function rangeValidator(headers) {
191
+ const etag = headers.get('etag')?.trim();
192
+ if (etag && !/^W\//i.test(etag))
193
+ return etag;
194
+ return headers.get('last-modified')?.trim() || undefined;
195
+ }
196
+ /** An aborted fetch as a plain Error (never mistaken for a 200 answer). */
197
+ function describeAbort(error, what, timeoutMs) {
198
+ const name = error?.name;
199
+ if (name === 'TimeoutError' || name === 'AbortError') {
200
+ return new Error(`timed out after ${timeoutMs} ms fetching ${what}`);
201
+ }
202
+ return error instanceof Error ? error : new Error(String(error));
203
+ }
204
+ /** One Range request; `end` is exclusive. Resolves with a 206 response. */
205
+ async function fetchRange(url, start, end, options) {
206
+ const what = `bytes=${start}-${end - 1}`;
207
+ const headers = {
208
+ Range: what,
209
+ 'Accept-Encoding': 'identity',
210
+ };
211
+ if (options.ifRange)
212
+ headers['If-Range'] = options.ifRange;
213
+ let response;
214
+ try {
215
+ response = await (0, runtime_1.webFetch)(url, {
216
+ headers,
217
+ signal: AbortSignal.timeout(options.timeoutMs),
218
+ });
219
+ }
220
+ catch (error) {
221
+ throw describeAbort(error, what, options.timeoutMs);
222
+ }
223
+ if (response.status === 200)
224
+ throw new RangeUnsupportedError(response);
225
+ if (response.status !== 206 || !response.body) {
226
+ await response.body?.cancel().catch(() => { });
227
+ throw new Error(`HTTP ${response.status} for ${what}`);
228
+ }
229
+ const range = parseContentRange(response.headers.get('content-range'));
230
+ if (!range || range.start !== start || range.end !== end - 1) {
231
+ await response.body.cancel().catch(() => { });
232
+ throw new Error(`unexpected Content-Range ${response.headers.get('content-range')}`);
233
+ }
234
+ if (options.expectTotal !== undefined &&
235
+ range.total !== options.expectTotal) {
236
+ await response.body.cancel().catch(() => { });
237
+ throw new Error(`archive size changed: ${range.total} bytes, expected ${options.expectTotal}`);
238
+ }
239
+ return Object.assign(response, { range });
240
+ }
241
+ async function fetchRangeBuffer(url, start, end, options) {
242
+ const response = await fetchRange(url, start, end, options);
243
+ let data;
244
+ try {
245
+ // Buffer.from(ArrayBuffer) is a view: the body is held exactly once
246
+ data = Buffer.from(await response.arrayBuffer());
247
+ }
248
+ catch (error) {
249
+ throw describeAbort(error, `bytes=${start}-${end - 1}`, options.timeoutMs);
250
+ }
251
+ if (data.length !== end - start) {
252
+ throw new Error(`range returned ${data.length} bytes, expected ${end - start}`);
253
+ }
254
+ return { data, total: response.range.total };
255
+ }
256
+ /**
257
+ * Fetch one entry whose compressed bytes are already located (the ppk's own
258
+ * publish-time report) and decompress it. Returns the raw entry content.
259
+ */
260
+ async function fetchZipEntryData(url, location, options = {}) {
261
+ const { dataOffset, compressedSize, compressionMethod } = location;
262
+ if (compressionMethod !== exports.ZIP_STORED && compressionMethod !== exports.ZIP_DEFLATED) {
263
+ throw new Error(`unsupported compression method ${compressionMethod}`);
264
+ }
265
+ if (!Number.isInteger(dataOffset) ||
266
+ !Number.isInteger(compressedSize) ||
267
+ dataOffset < 0 ||
268
+ compressedSize <= 0) {
269
+ throw new Error('invalid bundle location');
270
+ }
271
+ const { data, total } = await fetchRangeBuffer(url, dataOffset, dataOffset + compressedSize, { timeoutMs: timeoutFor(compressedSize, options) });
272
+ return {
273
+ data: compressionMethod === exports.ZIP_DEFLATED ? (0, zlib_1.inflateRawSync)(data) : data,
274
+ fetchedBytes: compressedSize,
275
+ totalBytes: total,
276
+ };
277
+ }
278
+ /**
279
+ * yauzl reader over HTTP Range. Header reads (EOCD, central directory, local
280
+ * file headers) are widened to CHUNK_BYTES and cached. Chunks are kept as
281
+ * fetched — only overlapping ones are merged, touching ones are read across —
282
+ * so an entry's bytes are held once, and a read reuses whatever prefix the
283
+ * cache holds (typically the bytes following the local file header) before
284
+ * requesting the rest. Every request after the first carries If-Range and
285
+ * must report the same archive size.
286
+ */
287
+ class HttpRangeReader extends yauzl_1.RandomAccessReader {
288
+ url;
289
+ totalSize;
290
+ options;
291
+ /** sorted by `start`, never overlapping */
292
+ chunks = [];
293
+ /** span a header read inside it should widen to (see `hintEntry`) */
294
+ hint = null;
295
+ requests = 0;
296
+ fetchedBytes = 0;
297
+ constructor(url, totalSize, tail, options = {}) {
298
+ super();
299
+ this.url = url;
300
+ this.totalSize = totalSize;
301
+ this.options = options;
302
+ if (tail)
303
+ this.addChunk(tail.start, tail.data);
304
+ }
305
+ /**
306
+ * The next entry to be read: yauzl first reads its local file header, and
307
+ * widening that read to the entry's data makes the data stream a cache hit
308
+ * — one request for header + bundle instead of two. The central directory
309
+ * may understate the local header's extra field, hence the slack.
310
+ */
311
+ hintEntry(entry) {
312
+ const start = entry.relativeOffsetOfLocalHeader;
313
+ const end = start +
314
+ LOCAL_HEADER_SIZE +
315
+ entry.fileNameLength +
316
+ entry.extraFieldLength +
317
+ entry.compressedSize +
318
+ HINT_SLACK_BYTES;
319
+ this.hint = { start, end: Math.min(end, this.totalSize) };
320
+ }
321
+ /** Cache `data` at `start`, merging with any chunk it overlaps. */
322
+ addChunk(start, data) {
323
+ if (data.length === 0)
324
+ return;
325
+ let lo = start;
326
+ let hi = start + data.length;
327
+ const pieces = [{ start, data }];
328
+ const rest = [];
329
+ for (const chunk of this.chunks) {
330
+ const chunkEnd = chunk.start + chunk.data.length;
331
+ if (chunk.start < hi && chunkEnd > lo) {
332
+ pieces.push(chunk);
333
+ lo = Math.min(lo, chunk.start);
334
+ hi = Math.max(hi, chunkEnd);
335
+ }
336
+ else {
337
+ rest.push(chunk);
338
+ }
339
+ }
340
+ if (pieces.length === 1) {
341
+ rest.push(pieces[0]);
342
+ }
343
+ else {
344
+ const merged = Buffer.alloc(hi - lo);
345
+ // the new data is written last so it wins over stale overlaps
346
+ for (const piece of pieces.reverse()) {
347
+ piece.data.copy(merged, piece.start - lo);
348
+ }
349
+ rest.push({ start: lo, data: merged });
350
+ }
351
+ rest.sort((a, b) => a.start - b.start);
352
+ this.chunks = rest;
353
+ }
354
+ /** Fetch `[start, end)` into the cache in one request. */
355
+ async prefetch(start, end) {
356
+ this.addChunk(start, await this.fetchBuffer(start, end));
357
+ }
358
+ requestOptions(bytes) {
359
+ return {
360
+ timeoutMs: timeoutFor(bytes, this.options),
361
+ ifRange: this.options.ifRange,
362
+ expectTotal: this.totalSize,
363
+ };
364
+ }
365
+ async fetchBuffer(start, end) {
366
+ this.requests++;
367
+ const { data } = await fetchRangeBuffer(this.url, start, end, this.requestOptions(end - start));
368
+ this.fetchedBytes += data.length;
369
+ return data;
370
+ }
371
+ /**
372
+ * Contiguous cached pieces starting exactly at `start`, up to `end`: views
373
+ * into one chunk or into a run of touching chunks, possibly ending short.
374
+ */
375
+ cachedPieces(start, end) {
376
+ const pieces = [];
377
+ let at = start;
378
+ for (const chunk of this.chunks) {
379
+ const chunkEnd = chunk.start + chunk.data.length;
380
+ if (chunkEnd <= at)
381
+ continue;
382
+ if (chunk.start > at)
383
+ break;
384
+ const stop = Math.min(end, chunkEnd);
385
+ pieces.push(chunk.data.subarray(at - chunk.start, stop - chunk.start));
386
+ at = stop;
387
+ if (at >= end)
388
+ break;
389
+ }
390
+ return pieces;
391
+ }
392
+ /** start of the cached run (touching chunks) holding byte `end - 1` */
393
+ cachedSuffixStart(end) {
394
+ let i = this.chunks.findIndex((c) => end - 1 >= c.start && end - 1 < c.start + c.data.length);
395
+ if (i < 0)
396
+ return null;
397
+ while (i > 0 &&
398
+ this.chunks[i - 1].start + this.chunks[i - 1].data.length ===
399
+ this.chunks[i].start) {
400
+ i--;
401
+ }
402
+ return this.chunks[i].start;
403
+ }
404
+ /** start of the first cached chunk at or after `position` */
405
+ nextCachedStart(position) {
406
+ const chunk = this.chunks.find((c) => c.start >= position);
407
+ return chunk ? chunk.start : null;
408
+ }
409
+ _readStreamForRange(start, end) {
410
+ const pieces = this.cachedPieces(start, end);
411
+ const have = pieces.reduce((sum, piece) => sum + piece.length, 0);
412
+ if (have === end - start)
413
+ return stream_1.Readable.from(pieces);
414
+ const out = new stream_1.PassThrough();
415
+ for (const piece of pieces)
416
+ out.write(piece);
417
+ const from = start + have;
418
+ const options = this.requestOptions(end - from);
419
+ this.requests++;
420
+ fetchRange(this.url, from, end, options)
421
+ .then((response) => {
422
+ this.fetchedBytes += end - from;
423
+ const body = stream_1.Readable.fromWeb(response.body);
424
+ body.on('error', (error) => out.destroy(describeAbort(error, `bytes=${from}-${end - 1}`, options.timeoutMs)));
425
+ body.pipe(out);
426
+ })
427
+ .catch((error) => out.destroy(error));
428
+ return out;
429
+ }
430
+ read(buffer, offset, length, position, callback) {
431
+ const requestEnd = position + length;
432
+ const copyOut = (pieces) => {
433
+ let at = offset;
434
+ for (const piece of pieces) {
435
+ piece.copy(buffer, at);
436
+ at += piece.length;
437
+ }
438
+ return at - offset === length;
439
+ };
440
+ const cached = this.cachedPieces(position, requestEnd);
441
+ if (copyOut(cached)) {
442
+ setImmediate(() => callback(null, length));
443
+ return;
444
+ }
445
+ // fetch only what the cache lacks: skip the cached prefix, stop at a
446
+ // cached suffix (yauzl's EOCD read may just exceed the tail), and
447
+ // otherwise widen so the next header read is free — to exactly the hinted
448
+ // entry when the hint covers this read, else by CHUNK_BYTES — but never
449
+ // into bytes already cached beyond the request
450
+ const from = position + cached.reduce((sum, piece) => sum + piece.length, 0);
451
+ const suffixStart = this.cachedSuffixStart(requestEnd);
452
+ let end;
453
+ if (suffixStart !== null && suffixStart > from) {
454
+ end = suffixStart;
455
+ }
456
+ else {
457
+ const hinted = this.hint !== null &&
458
+ position >= this.hint.start &&
459
+ requestEnd <= this.hint.end;
460
+ end = hinted
461
+ ? this.hint.end
462
+ : Math.max(requestEnd, from + CHUNK_BYTES);
463
+ end = Math.min(end, this.totalSize);
464
+ const next = this.nextCachedStart(requestEnd);
465
+ if (next !== null && next < end)
466
+ end = next;
467
+ }
468
+ this.fetchBuffer(from, end).then((data) => {
469
+ this.addChunk(from, data);
470
+ if (!copyOut(this.cachedPieces(position, requestEnd))) {
471
+ callback(new Error('range read did not cover the requested bytes'));
472
+ return;
473
+ }
474
+ callback(null, length);
475
+ }, (error) => callback(error));
476
+ }
477
+ }
478
+ exports.HttpRangeReader = HttpRangeReader;
479
+ /** End-of-central-directory record: where the central directory lives. */
480
+ const EOCD_SIGNATURE = 0x06054b50;
481
+ const EOCD_SIZE = 22;
482
+ function parseEndOfCentralDirectory(tail) {
483
+ for (let i = tail.length - EOCD_SIZE; i >= 0; i--) {
484
+ if (tail.readUInt32LE(i) !== EOCD_SIGNATURE)
485
+ continue;
486
+ const commentLength = tail.readUInt16LE(i + 20);
487
+ if (i + EOCD_SIZE + commentLength !== tail.length)
488
+ continue;
489
+ const cdSize = tail.readUInt32LE(i + 12);
490
+ const cdOffset = tail.readUInt32LE(i + 16);
491
+ // zip64 markers: let yauzl resolve the real values itself
492
+ if (cdSize === 0xffffffff || cdOffset === 0xffffffff)
493
+ return null;
494
+ return { cdOffset, cdSize };
495
+ }
496
+ return null;
497
+ }
498
+ /**
499
+ * Open a remote zip by fetching its tail (EOCD + central directory for
500
+ * typical archives) and letting yauzl read the rest on demand.
501
+ */
502
+ async function openRemoteZip(url, options = {}) {
503
+ const timeoutMs = timeoutFor(TAIL_BYTES, options);
504
+ let response;
505
+ try {
506
+ response = await (0, runtime_1.webFetch)(url, {
507
+ headers: { Range: `bytes=-${TAIL_BYTES}`, 'Accept-Encoding': 'identity' },
508
+ signal: AbortSignal.timeout(timeoutMs),
509
+ });
510
+ }
511
+ catch (error) {
512
+ throw describeAbort(error, `bytes=-${TAIL_BYTES}`, timeoutMs);
513
+ }
514
+ if (!response.ok || !response.body) {
515
+ await response.body?.cancel().catch(() => { });
516
+ throw new Error(`HTTP ${response.status}`);
517
+ }
518
+ if (response.status !== 206)
519
+ return { kind: 'full', response };
520
+ const range = parseContentRange(response.headers.get('content-range'));
521
+ if (!range) {
522
+ await response.body.cancel().catch(() => { });
523
+ throw new Error(`unexpected Content-Range ${response.headers.get('content-range')}`);
524
+ }
525
+ let data;
526
+ try {
527
+ data = Buffer.from(await response.arrayBuffer());
528
+ }
529
+ catch (error) {
530
+ throw describeAbort(error, `bytes=-${TAIL_BYTES}`, timeoutMs);
531
+ }
532
+ if (data.length !== range.end - range.start + 1) {
533
+ throw new Error(`tail returned ${data.length} bytes, expected ${range.end - range.start + 1}`);
534
+ }
535
+ const reader = new HttpRangeReader(url, range.total, { start: range.start, data }, { ...options, ifRange: rangeValidator(response.headers) });
536
+ reader.requests = 1;
537
+ reader.fetchedBytes = data.length;
538
+ // Large archives (apk / ipa with thousands of files) keep their central
539
+ // directory well before the tail; yauzl would otherwise page it in with one
540
+ // sequential request per CHUNK_BYTES. Fetch the missing part at once — one
541
+ // buffer of exactly its size — as long as the EOCD describes a directory
542
+ // that ends inside the tail (a corrupt record could claim the whole file).
543
+ const eocd = parseEndOfCentralDirectory(data);
544
+ if (eocd &&
545
+ eocd.cdOffset < range.start &&
546
+ eocd.cdOffset + eocd.cdSize >= range.start &&
547
+ eocd.cdOffset + eocd.cdSize <= range.total - EOCD_SIZE) {
548
+ await reader.prefetch(eocd.cdOffset, range.start);
549
+ }
550
+ const zipFile = await new Promise((resolve, reject) => {
551
+ (0, yauzl_1.fromRandomAccessReader)(reader, range.total, { lazyEntries: true, autoClose: false }, (err, file) => (err ? reject(err) : resolve(file)));
552
+ });
553
+ return { kind: 'zip', zipFile, reader };
554
+ }
555
+ /**
556
+ * Read the first matching entry of an opened remote zip, then close it. With
557
+ * `reader` given, the entry's header and data are fetched in one request.
558
+ */
559
+ function readRemoteZipEntry(zipFile, matches, reader) {
560
+ return new Promise((resolve, reject) => {
561
+ let settled = false;
562
+ const finish = (error, result = null) => {
563
+ if (settled)
564
+ return;
565
+ settled = true;
566
+ zipFile.close();
567
+ error ? reject(error) : resolve(result);
568
+ };
569
+ zipFile.on('error', (error) => finish(error));
570
+ zipFile.on('end', () => finish(null, null));
571
+ zipFile.on('entry', (entry) => {
572
+ if (!matches(entry.fileName)) {
573
+ zipFile.readEntry();
574
+ return;
575
+ }
576
+ reader?.hintEntry(entry);
577
+ (0, zip_entries_1.readEntry)(entry, zipFile).then((data) => finish(null, data), (error) => finish(error));
578
+ });
579
+ zipFile.readEntry();
580
+ });
581
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * [INPUT]: 依赖源 ZIP 中央目录、可读流、CRC/尺寸元数据与文件系统
3
+ * [OUTPUT]: 对外提供 RawZipEntry、fitsRawZip、crc32Update 与 RawZipWriter
4
+ * [POS]: CLI 归档快速重打包底座,原样复制已压缩条目,避免资源二次压缩
5
+ */
6
+ import type { Readable } from 'stream';
7
+ import type { Entry } from 'yauzl';
8
+ export interface RawZipEntry {
9
+ fileName: string;
10
+ compressionMethod: number;
11
+ crc32: number;
12
+ compressedSize: number;
13
+ uncompressedSize: number;
14
+ lastModFileTime: number;
15
+ lastModFileDate: number;
16
+ generalPurposeBitFlag?: number;
17
+ versionMadeBy?: number;
18
+ versionNeededToExtract?: number;
19
+ internalFileAttributes?: number;
20
+ externalFileAttributes?: number;
21
+ }
22
+ /**
23
+ * Whether an archive of `sourceSize` bytes with `entryCount` entries can be
24
+ * (partially) copied without zip64 structures: an output that only keeps a
25
+ * subset of the entries is never larger than its source.
26
+ */
27
+ export declare function fitsRawZip(sourceSize: number, entryCount: number): boolean;
28
+ /** CRC-32 (IEEE) continued from `seed` (the running value, not inverted) */
29
+ export declare function crc32Update(seed: number, chunk: Buffer): number;
30
+ export declare class RawZipWriter {
31
+ readonly output: string;
32
+ private readonly out;
33
+ private readonly central;
34
+ private offset;
35
+ private count;
36
+ private failure;
37
+ private readonly closed;
38
+ constructor(output: string);
39
+ /** Copy one entry with its file data exactly as stored in the source. */
40
+ addRawEntry(meta: RawZipEntry, data: Readable): Promise<void>;
41
+ /**
42
+ * Add a local file uncompressed (its CRC is computed in a first pass).
43
+ * `like` lends its timestamp and attributes, e.g. the archive entry this
44
+ * file replaces.
45
+ */
46
+ addStoredFile(filePath: string, fileName: string, like?: Pick<Entry, 'lastModFileTime' | 'lastModFileDate' | 'externalFileAttributes' | 'versionMadeBy'>): Promise<void>;
47
+ /** Write the central directory and close the file. */
48
+ end(): Promise<void>;
49
+ /** Stop writing and remove the partial output. */
50
+ abort(): Promise<void>;
51
+ private write;
52
+ }