epubcheck-standalone-cli 5.4.0-build1 → 5.4.0-build3
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/dist/archive.d.ts +5 -2
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +2 -340
- package/dist/chunks/archive-DmawhYP4.js +250 -0
- package/dist/chunks/archive-DmawhYP4.js.map +1 -0
- package/dist/chunks/help-text-CvNP4Zt6.js +19 -0
- package/dist/chunks/help-text-CvNP4Zt6.js.map +1 -0
- package/dist/chunks/list-checks-data-BbnWFkVn.js +6 -0
- package/dist/chunks/list-checks-data-BbnWFkVn.js.map +1 -0
- package/dist/chunks/list-checks-locale-data-CBAyIO4k.js +17 -0
- package/dist/chunks/list-checks-locale-data-CBAyIO4k.js.map +1 -0
- package/dist/chunks/locale-messages-C7JC1hxi.js +351 -0
- package/dist/chunks/locale-messages-C7JC1hxi.js.map +1 -0
- package/dist/chunks/messages-6yMjM2Sn.js +130 -0
- package/dist/chunks/messages-6yMjM2Sn.js.map +1 -0
- package/dist/chunks/messages-Bc_tdDVv.d.ts +96 -0
- package/dist/chunks/messages-Bc_tdDVv.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +591 -884
- package/dist/cli.js.map +1 -0
- package/dist/help-text.d.ts +3 -0
- package/dist/help-text.d.ts.map +1 -0
- package/dist/help-text.js +2 -25
- package/dist/list-checks-data.d.ts +4 -1
- package/dist/list-checks-data.d.ts.map +1 -0
- package/dist/list-checks-data.js +2 -11
- package/dist/list-checks-locale-data.d.ts +3 -0
- package/dist/list-checks-locale-data.d.ts.map +1 -0
- package/dist/list-checks-locale-data.js +2 -22
- package/dist/locale-messages.d.ts +4 -1
- package/dist/locale-messages.d.ts.map +1 -0
- package/dist/locale-messages.js +2 -346
- package/dist/messages.d.ts +2 -95
- package/dist/messages.js +2 -138
- package/package.json +9 -6
- package/src/archive.ts +392 -0
- package/src/cli.ts +1006 -0
- package/src/help-text.ts +27 -0
- package/src/list-checks-data.ts +11 -0
- package/src/list-checks-locale-data.ts +22 -0
- package/src/locale-messages.ts +360 -0
- package/src/messages.ts +159 -0
package/dist/archive.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//#region src/archive.d.ts
|
|
1
2
|
/**
|
|
2
3
|
* Thresholds at which ZIP64 kicks in. They default to the real 32-bit/16-bit
|
|
3
4
|
* limits (so production output is unchanged), but are overridable so the ZIP64
|
|
@@ -8,8 +9,8 @@
|
|
|
8
9
|
* matching commons-compress's `>= ZIP64_MAGIC` / `>= ZIP64_MAGIC_SHORT`.
|
|
9
10
|
*/
|
|
10
11
|
export interface Zip64Thresholds {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
size?: number;
|
|
13
|
+
entries?: number;
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Package `baseDir` (an existing directory) into `<parent>/<name>.epub` beside
|
|
@@ -23,3 +24,5 @@ export interface Zip64Thresholds {
|
|
|
23
24
|
* output byte-identical to the pre-ZIP64 writer below 4 GB.
|
|
24
25
|
*/
|
|
25
26
|
export declare function createArchive(baseDir: string, thresholds?: Zip64Thresholds): Promise<string>;
|
|
27
|
+
//#endregion
|
|
28
|
+
//# sourceMappingURL=archive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.d.ts","names":[],"sources":["../src/archive.ts"],"mappings":";;;;;;;;;;iBAgGiB;EACf;EACA;;;;;;;;;;;;;wBA4IoB,cACpB,iBACA,aAAY,kBACX"}
|
package/dist/archive.js
CHANGED
|
@@ -1,340 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// `<canonical-parent>/<directory-name>.epub`, laid out the way the jar's
|
|
4
|
-
// commons-compress ZipArchiveOutputStream writes it.
|
|
5
|
-
//
|
|
6
|
-
// Faithful to the jar's observed output (verified against the real jar):
|
|
7
|
-
// - Files are collected in RAW readdir order (Java's File.listFiles order).
|
|
8
|
-
// Node's fs.readdir would NOT match: libuv sorts scandir results, so this
|
|
9
|
-
// walks with the promises fs.opendir (uv_fs_opendir/readdir, NOT scandir),
|
|
10
|
-
// which preserves the on-disk order the jar sees -- the same raw order the old
|
|
11
|
-
// opendirSync gave, since async opendir uses the identical libuv directory API.
|
|
12
|
-
// - `.DS_Store`, `._DS_Store`, `Thumbs.db`, `ehthumbs.db` files and `.svn`/
|
|
13
|
-
// `.git` directories are skipped (Archive.collectFiles, epubcheck issue 256).
|
|
14
|
-
// - A root-level `mimetype` is moved to the front and STORED (method 0) with a
|
|
15
|
-
// precomputed CRC and NO extra fields; every other entry is DEFLATED.
|
|
16
|
-
// - Entry names are UTF-8 with the language-encoding flag (0x0800) set, no
|
|
17
|
-
// unicode-path extra fields (setCreateUnicodeExtraFields(NEVER)).
|
|
18
|
-
// - Deflated entries carry the same timestamp extra fields commons-compress
|
|
19
|
-
// emits from the file's real attributes: extended-timestamp 0x5455 (local:
|
|
20
|
-
// mtime/atime/birthtime seconds; central: mtime only) and NTFS 0x000a
|
|
21
|
-
// (mtime/atime/birthtime as 100 ns FILETIME values).
|
|
22
|
-
// - Symlinks are followed (Java's File.isFile/isDirectory semantics); an entry
|
|
23
|
-
// that is neither a regular file nor a directory is skipped.
|
|
24
|
-
//
|
|
25
|
-
// One deliberate divergence, impossible to close from Node: the DEFLATED
|
|
26
|
-
// streams differ byte-for-byte from the jar's because Node's bundled zlib and
|
|
27
|
-
// the JDK's zlib produce different (both valid) output for the same input.
|
|
28
|
-
// Entry order, names, methods, CRCs, sizes, and timestamps all match.
|
|
29
|
-
//
|
|
30
|
-
// ZIP64 (APPNOTE 6.3.x), emitted the way commons-compress's default
|
|
31
|
-
// Zip64Mode.AsNeeded does on seekable output: classic 32-bit headers are used
|
|
32
|
-
// until a field would actually overflow, and only then are ZIP64 structures
|
|
33
|
-
// added -- per entry and at the end -- so a packaged form that needs more than
|
|
34
|
-
// 4 GB of offset, a 4 GB+ entry, or 65535+ files produces a VALID archive
|
|
35
|
-
// instead of throwing. The trigger crosses over when a size/offset reaches
|
|
36
|
-
// 0xFFFFFFFF or the entry count reaches 0xFFFF (both overridable for tests via
|
|
37
|
-
// the `createArchive` options, which default to the real limits):
|
|
38
|
-
// - Per-entry ZIP64 extended-information extra field (id 0x0001), added only
|
|
39
|
-
// for the entry that needs it, placed FIRST among its extra fields
|
|
40
|
-
// (commons-compress's addAsFirstExtraField). The local header carries it when
|
|
41
|
-
// the entry's own size overflows and always writes BOTH 8-byte sizes
|
|
42
|
-
// (uncompressed then compressed); the central header carries only the values
|
|
43
|
-
// that overflow, in APPNOTE order (uncompressed size, compressed size,
|
|
44
|
-
// local-header offset), with the overflowing 32-bit fields set to the
|
|
45
|
-
// 0xFFFFFFFF sentinel and version-needed bumped to 45.
|
|
46
|
-
// - A ZIP64 end-of-central-directory record (0x06064b50, 56 bytes) + locator
|
|
47
|
-
// (0x07064b50, 20 bytes) before the classic EOCD once any entry used ZIP64 or
|
|
48
|
-
// the central-directory offset/size/entry-count would overflow; the classic
|
|
49
|
-
// EOCD then carries 0xFFFF / 0xFFFFFFFF sentinels in the overflowing fields.
|
|
50
|
-
//
|
|
51
|
-
// Standard for the ZIP64 path: STRUCTURE / field placement mirrors
|
|
52
|
-
// commons-compress AsNeeded, NOT byte-identical output. Byte parity above 4 GB
|
|
53
|
-
// was never achievable anyway -- our DEFLATE stream already differs from the
|
|
54
|
-
// JDK's (see above) -- so the goal is a valid archive whose ZIP64 layout
|
|
55
|
-
// matches the jar's, verified by round-tripping through an independent
|
|
56
|
-
// ZIP64-aware reader. Below the threshold NO ZIP64 structures appear, so
|
|
57
|
-
// sub-4 GB packaging stays byte-identical to the pre-ZIP64 output.
|
|
58
|
-
import { opendir, readFile, realpath, stat, open } from "node:fs/promises";
|
|
59
|
-
import { basename, dirname, join } from "node:path";
|
|
60
|
-
import { crc32, deflateRaw } from "node:zlib";
|
|
61
|
-
import { promisify } from "node:util";
|
|
62
|
-
// zlib exposes only a callback deflateRaw; promisify it so packaging runs on the
|
|
63
|
-
// libuv thread pool without blocking the event loop (async law). Its output is
|
|
64
|
-
// byte-identical to the old deflateRawSync -- same zlib, same default options, a
|
|
65
|
-
// deterministic deflate stream -- which the save-parity structural test confirms.
|
|
66
|
-
const deflateRawAsync = promisify(deflateRaw);
|
|
67
|
-
const IGNORED_FILES = new Set([".DS_Store", "._DS_Store", "Thumbs.db", "ehthumbs.db"]);
|
|
68
|
-
const IGNORED_DIRS = new Set([".svn", ".git"]);
|
|
69
|
-
// Sentinel values written into the classic 32-bit / 16-bit fields to say "the
|
|
70
|
-
// real value lives in a ZIP64 structure" (APPNOTE 4.3.9.2, 4.4.1.4). These are
|
|
71
|
-
// ALSO the true overflow limits commons-compress uses as its AsNeeded trigger.
|
|
72
|
-
const ZIP64_MAGIC = 0xffffffff;
|
|
73
|
-
const ZIP64_MAGIC_SHORT = 0xffff;
|
|
74
|
-
const ZIP64_VERSION = 45; // version-needed-to-extract for a ZIP64 entry/record
|
|
75
|
-
/**
|
|
76
|
-
* ZIP64 local extended-information extra field (id 0x0001): commons-compress
|
|
77
|
-
* always writes BOTH the 8-byte uncompressed and 8-byte compressed size in the
|
|
78
|
-
* local extra when it adds one.
|
|
79
|
-
*/
|
|
80
|
-
function zip64LocalExtra(uncompressedSize, compressedSize) {
|
|
81
|
-
const b = Buffer.alloc(4 + 16);
|
|
82
|
-
b.writeUInt16LE(0x0001, 0);
|
|
83
|
-
b.writeUInt16LE(16, 2);
|
|
84
|
-
b.writeBigUInt64LE(BigInt(uncompressedSize), 4);
|
|
85
|
-
b.writeBigUInt64LE(BigInt(compressedSize), 12);
|
|
86
|
-
return b;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* ZIP64 central extended-information extra field (id 0x0001): carries ONLY the
|
|
90
|
-
* values that overflow, in the fixed APPNOTE order (uncompressed size,
|
|
91
|
-
* compressed size, local-header offset), matching commons-compress's
|
|
92
|
-
* getCentralDirectoryData(). The two sizes are emitted together (both or
|
|
93
|
-
* neither) when either overflows; the offset is emitted when it overflows. The
|
|
94
|
-
* disk-start-number never overflows here (single disk), so it is never present.
|
|
95
|
-
*/
|
|
96
|
-
function zip64CentralExtra(needSizes, uncompressedSize, compressedSize, needOffset, offset) {
|
|
97
|
-
const dataLen = (needSizes ? 16 : 0) + (needOffset ? 8 : 0);
|
|
98
|
-
const b = Buffer.alloc(4 + dataLen);
|
|
99
|
-
b.writeUInt16LE(0x0001, 0);
|
|
100
|
-
b.writeUInt16LE(dataLen, 2);
|
|
101
|
-
let o = 4;
|
|
102
|
-
if (needSizes) {
|
|
103
|
-
b.writeBigUInt64LE(BigInt(uncompressedSize), o);
|
|
104
|
-
b.writeBigUInt64LE(BigInt(compressedSize), o + 8);
|
|
105
|
-
o += 16;
|
|
106
|
-
}
|
|
107
|
-
if (needOffset) {
|
|
108
|
-
b.writeBigUInt64LE(BigInt(offset), o);
|
|
109
|
-
}
|
|
110
|
-
return b;
|
|
111
|
-
}
|
|
112
|
-
/** Archive.collectFiles: depth-first, raw readdir order, junk entries skipped. */
|
|
113
|
-
async function collectFiles(dir, dirName, out) {
|
|
114
|
-
// Drain the whole directory first (for-await auto-closes the Dir on
|
|
115
|
-
// completion), so only one directory handle is open at a time down the
|
|
116
|
-
// recursion. `for await` reads entries via the same libuv opendir/readdir the
|
|
117
|
-
// sync path used, preserving the raw (unsorted) order the jar's File.listFiles
|
|
118
|
-
// sees -- the byte-order the saved zip depends on.
|
|
119
|
-
const names = [];
|
|
120
|
-
const d = await opendir(dir);
|
|
121
|
-
for await (const ent of d)
|
|
122
|
-
names.push(ent.name);
|
|
123
|
-
for (const name of names) {
|
|
124
|
-
const full = join(dir, name);
|
|
125
|
-
let st;
|
|
126
|
-
try {
|
|
127
|
-
st = await stat(full); // follows symlinks, like java.io.File
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
continue; // dangling symlink: neither isFile nor isDirectory to Java
|
|
131
|
-
}
|
|
132
|
-
if (st.isFile()) {
|
|
133
|
-
if (!IGNORED_FILES.has(name))
|
|
134
|
-
out.push({ name: dirName + name, path: full });
|
|
135
|
-
}
|
|
136
|
-
else if (st.isDirectory()) {
|
|
137
|
-
if (!IGNORED_DIRS.has(name))
|
|
138
|
-
await collectFiles(full, dirName + name + "/", out);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/** MS-DOS date+time (local time, 2 s resolution) as (date << 16) | time. */
|
|
143
|
-
function dosDateTime(ms) {
|
|
144
|
-
const d = new Date(ms);
|
|
145
|
-
if (d.getFullYear() < 1980)
|
|
146
|
-
return 0x0021 << 16; // 1980-01-01 00:00:00
|
|
147
|
-
const date = ((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate();
|
|
148
|
-
const time = (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() >> 1);
|
|
149
|
-
return ((date << 16) | time) >>> 0;
|
|
150
|
-
}
|
|
151
|
-
/** Windows FILETIME (100 ns intervals since 1601-01-01) from unix nanoseconds. */
|
|
152
|
-
function toFiletime(ns) {
|
|
153
|
-
return ns / 100n + 116444736000000000n;
|
|
154
|
-
}
|
|
155
|
-
/** Unix seconds as the unsigned 32-bit value the 0x5455 field stores. */
|
|
156
|
-
function toUnixSec(ns) {
|
|
157
|
-
return Number(BigInt.asUintN(32, ns / 1000000000n));
|
|
158
|
-
}
|
|
159
|
-
/** NTFS extra field 0x000a (36 bytes): mtime, atime, creation as FILETIME. */
|
|
160
|
-
function writeNtfsExtra(b, off, st) {
|
|
161
|
-
b.writeUInt16LE(0x000a, off);
|
|
162
|
-
b.writeUInt16LE(32, off + 2);
|
|
163
|
-
b.writeUInt32LE(0, off + 4); // reserved
|
|
164
|
-
b.writeUInt16LE(0x0001, off + 8); // tag: times
|
|
165
|
-
b.writeUInt16LE(24, off + 10);
|
|
166
|
-
b.writeBigUInt64LE(toFiletime(st.mtimeNs), off + 12);
|
|
167
|
-
b.writeBigUInt64LE(toFiletime(st.atimeNs), off + 20);
|
|
168
|
-
b.writeBigUInt64LE(toFiletime(st.birthtimeNs), off + 28);
|
|
169
|
-
}
|
|
170
|
-
/** Local-header extra: 0x5455 (flags 7: mtime/atime/creation) + NTFS. */
|
|
171
|
-
function localExtra(st) {
|
|
172
|
-
const b = Buffer.alloc(17 + 36);
|
|
173
|
-
b.writeUInt16LE(0x5455, 0);
|
|
174
|
-
b.writeUInt16LE(13, 2);
|
|
175
|
-
b.writeUInt8(0x07, 4);
|
|
176
|
-
b.writeUInt32LE(toUnixSec(st.mtimeNs), 5);
|
|
177
|
-
b.writeUInt32LE(toUnixSec(st.atimeNs), 9);
|
|
178
|
-
b.writeUInt32LE(toUnixSec(st.birthtimeNs), 13);
|
|
179
|
-
writeNtfsExtra(b, 17, st);
|
|
180
|
-
return b;
|
|
181
|
-
}
|
|
182
|
-
/** Central-directory extra: 0x5455 (flags 7, mtime only) + NTFS. */
|
|
183
|
-
function centralExtra(st) {
|
|
184
|
-
const b = Buffer.alloc(9 + 36);
|
|
185
|
-
b.writeUInt16LE(0x5455, 0);
|
|
186
|
-
b.writeUInt16LE(5, 2);
|
|
187
|
-
b.writeUInt8(0x07, 4);
|
|
188
|
-
b.writeUInt32LE(toUnixSec(st.mtimeNs), 5);
|
|
189
|
-
writeNtfsExtra(b, 9, st);
|
|
190
|
-
return b;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Package `baseDir` (an existing directory) into `<parent>/<name>.epub` beside
|
|
194
|
-
* it, overwriting any existing file there, and return the written path. The
|
|
195
|
-
* base directory is canonicalized first (Archive's makeCanonical), so a
|
|
196
|
-
* symlinked input saves next to the real directory. Throws on any failure; the
|
|
197
|
-
* caller maps that onto the jar's packaging-failure behavior.
|
|
198
|
-
*
|
|
199
|
-
* `thresholds` lets tests lower the ZIP64 trigger so the ZIP64 path runs on
|
|
200
|
-
* tiny inputs; it defaults to the real 32-bit/16-bit limits, keeping production
|
|
201
|
-
* output byte-identical to the pre-ZIP64 writer below 4 GB.
|
|
202
|
-
*/
|
|
203
|
-
export async function createArchive(baseDir, thresholds = {}) {
|
|
204
|
-
const sizeThreshold = thresholds.size ?? ZIP64_MAGIC;
|
|
205
|
-
const entryThreshold = thresholds.entries ?? ZIP64_MAGIC_SHORT;
|
|
206
|
-
const canonical = await realpath(baseDir);
|
|
207
|
-
const epubFile = join(dirname(canonical), basename(canonical) + ".epub");
|
|
208
|
-
const entries = [];
|
|
209
|
-
await collectFiles(canonical, "", entries);
|
|
210
|
-
// Make a root-level mimetype the first entry; only then is one STORED.
|
|
211
|
-
const mimetypeIndex = entries.findIndex((e) => e.name === "mimetype");
|
|
212
|
-
if (mimetypeIndex > -1) {
|
|
213
|
-
const [m] = entries.splice(mimetypeIndex, 1);
|
|
214
|
-
entries.unshift(m);
|
|
215
|
-
}
|
|
216
|
-
const fd = await open(epubFile, "w");
|
|
217
|
-
try {
|
|
218
|
-
let offset = 0;
|
|
219
|
-
const central = [];
|
|
220
|
-
// Set once any entry emits a ZIP64 extra: it forces the ZIP64 EOCD, exactly
|
|
221
|
-
// like commons-compress's hasUsedZip64 flag (independent of whether the
|
|
222
|
-
// central-directory offset/size/count themselves overflow).
|
|
223
|
-
let usedZip64 = false;
|
|
224
|
-
for (let i = 0; i < entries.length; i++) {
|
|
225
|
-
const e = entries[i];
|
|
226
|
-
const st = await stat(e.path, { bigint: true }); // attrs BEFORE the read, like the jar
|
|
227
|
-
const data = await readFile(e.path);
|
|
228
|
-
const stored = i === 0 && mimetypeIndex > -1;
|
|
229
|
-
const comp = stored ? data : await deflateRawAsync(data);
|
|
230
|
-
// crc32 is a synchronous pure-CPU checksum with no async equivalent
|
|
231
|
-
// (async law's pure-CPU exemption); it runs on the already-in-memory buffer.
|
|
232
|
-
const crc = crc32(data);
|
|
233
|
-
const name = Buffer.from(e.name, "utf8");
|
|
234
|
-
const dos = dosDateTime(Number(st.mtimeNs / 1000000n));
|
|
235
|
-
const method = stored ? 0 : 8;
|
|
236
|
-
// AsNeeded ZIP64 decision, per field. The local header carries a ZIP64
|
|
237
|
-
// extra (both sizes) when the entry's own size overflows; the central
|
|
238
|
-
// header additionally carries the local-header offset when THAT overflows.
|
|
239
|
-
// The local header has no offset field, so an offset-only overflow leaves
|
|
240
|
-
// the local header classic (version 20, real 32-bit sizes) -- exactly what
|
|
241
|
-
// commons-compress does, since it only learns the offset overflow while
|
|
242
|
-
// building the central header.
|
|
243
|
-
const needSizes = data.length >= sizeThreshold || comp.length >= sizeThreshold;
|
|
244
|
-
const needOffset = offset >= sizeThreshold;
|
|
245
|
-
if (needSizes || needOffset)
|
|
246
|
-
usedZip64 = true;
|
|
247
|
-
const baseExtraL = stored ? Buffer.alloc(0) : localExtra(st);
|
|
248
|
-
const baseExtraC = stored ? Buffer.alloc(0) : centralExtra(st);
|
|
249
|
-
const extraL = needSizes
|
|
250
|
-
? Buffer.concat([zip64LocalExtra(data.length, comp.length), baseExtraL])
|
|
251
|
-
: baseExtraL;
|
|
252
|
-
const extraC = needSizes || needOffset
|
|
253
|
-
? Buffer.concat([
|
|
254
|
-
zip64CentralExtra(needSizes, data.length, comp.length, needOffset, offset),
|
|
255
|
-
baseExtraC,
|
|
256
|
-
])
|
|
257
|
-
: baseExtraC;
|
|
258
|
-
const classicVersion = stored ? 10 : 20;
|
|
259
|
-
const localVersion = needSizes ? ZIP64_VERSION : classicVersion;
|
|
260
|
-
const centralVersion = needSizes || needOffset ? ZIP64_VERSION : classicVersion;
|
|
261
|
-
const lfhComp = needSizes ? ZIP64_MAGIC : comp.length;
|
|
262
|
-
const lfhUncomp = needSizes ? ZIP64_MAGIC : data.length;
|
|
263
|
-
const lfh = Buffer.alloc(30);
|
|
264
|
-
lfh.writeUInt32LE(0x04034b50, 0);
|
|
265
|
-
lfh.writeUInt16LE(localVersion, 4);
|
|
266
|
-
lfh.writeUInt16LE(0x0800, 6); // UTF-8 names
|
|
267
|
-
lfh.writeUInt16LE(method, 8);
|
|
268
|
-
lfh.writeUInt32LE(dos, 10);
|
|
269
|
-
lfh.writeUInt32LE(crc, 14);
|
|
270
|
-
lfh.writeUInt32LE(lfhComp, 18);
|
|
271
|
-
lfh.writeUInt32LE(lfhUncomp, 22);
|
|
272
|
-
lfh.writeUInt16LE(name.length, 26);
|
|
273
|
-
lfh.writeUInt16LE(extraL.length, 28);
|
|
274
|
-
const cen = Buffer.alloc(46);
|
|
275
|
-
cen.writeUInt32LE(0x02014b50, 0);
|
|
276
|
-
cen.writeUInt16LE(20, 4); // version made by: 2.0, FAT
|
|
277
|
-
cen.writeUInt16LE(centralVersion, 6);
|
|
278
|
-
cen.writeUInt16LE(0x0800, 8);
|
|
279
|
-
cen.writeUInt16LE(method, 10);
|
|
280
|
-
cen.writeUInt32LE(dos, 12);
|
|
281
|
-
cen.writeUInt32LE(crc, 16);
|
|
282
|
-
cen.writeUInt32LE(needSizes ? ZIP64_MAGIC : comp.length, 20);
|
|
283
|
-
cen.writeUInt32LE(needSizes ? ZIP64_MAGIC : data.length, 24);
|
|
284
|
-
cen.writeUInt16LE(name.length, 28);
|
|
285
|
-
cen.writeUInt16LE(extraC.length, 30);
|
|
286
|
-
// comment length, disk start, internal and external attributes: all 0
|
|
287
|
-
cen.writeUInt32LE(needOffset ? ZIP64_MAGIC : offset, 42);
|
|
288
|
-
central.push(cen, name, extraC);
|
|
289
|
-
await fd.write(lfh);
|
|
290
|
-
await fd.write(name);
|
|
291
|
-
await fd.write(extraL);
|
|
292
|
-
await fd.write(comp);
|
|
293
|
-
offset += lfh.length + name.length + extraL.length + comp.length;
|
|
294
|
-
}
|
|
295
|
-
const cdOffset = offset;
|
|
296
|
-
let cdSize = 0;
|
|
297
|
-
for (const b of central) {
|
|
298
|
-
await fd.write(b);
|
|
299
|
-
cdSize += b.length;
|
|
300
|
-
}
|
|
301
|
-
// ZIP64 EOCD record + locator (commons-compress writeZip64CentralDirectory):
|
|
302
|
-
// written when any entry used ZIP64 or the central-directory offset/size or
|
|
303
|
-
// entry count would overflow the classic 32-bit/16-bit EOCD fields.
|
|
304
|
-
const eocdOffsetOverflow = cdOffset >= sizeThreshold;
|
|
305
|
-
const eocdSizeOverflow = cdSize >= sizeThreshold;
|
|
306
|
-
const eocdCountOverflow = entries.length >= entryThreshold;
|
|
307
|
-
if (usedZip64 || eocdOffsetOverflow || eocdSizeOverflow || eocdCountOverflow) {
|
|
308
|
-
const z64eocd = Buffer.alloc(56);
|
|
309
|
-
z64eocd.writeUInt32LE(0x06064b50, 0);
|
|
310
|
-
z64eocd.writeBigUInt64LE(44n, 4); // size of the remainder of this record (56 - 12)
|
|
311
|
-
z64eocd.writeUInt16LE(ZIP64_VERSION, 12); // version made by
|
|
312
|
-
z64eocd.writeUInt16LE(ZIP64_VERSION, 14); // version needed to extract
|
|
313
|
-
z64eocd.writeUInt32LE(0, 16); // number of this disk
|
|
314
|
-
z64eocd.writeUInt32LE(0, 20); // disk with the start of the central directory
|
|
315
|
-
z64eocd.writeBigUInt64LE(BigInt(entries.length), 24); // entries on this disk
|
|
316
|
-
z64eocd.writeBigUInt64LE(BigInt(entries.length), 32); // total entries
|
|
317
|
-
z64eocd.writeBigUInt64LE(BigInt(cdSize), 40); // size of the central directory
|
|
318
|
-
z64eocd.writeBigUInt64LE(BigInt(cdOffset), 48); // offset of the central directory
|
|
319
|
-
await fd.write(z64eocd);
|
|
320
|
-
const z64loc = Buffer.alloc(20);
|
|
321
|
-
z64loc.writeUInt32LE(0x07064b50, 0);
|
|
322
|
-
z64loc.writeUInt32LE(0, 4); // disk with the ZIP64 EOCD record
|
|
323
|
-
z64loc.writeBigUInt64LE(BigInt(cdOffset + cdSize), 8); // offset of the ZIP64 EOCD record
|
|
324
|
-
z64loc.writeUInt32LE(1, 16); // total number of disks
|
|
325
|
-
await fd.write(z64loc);
|
|
326
|
-
}
|
|
327
|
-
const eocdCount = eocdCountOverflow ? ZIP64_MAGIC_SHORT : entries.length;
|
|
328
|
-
const eocd = Buffer.alloc(22);
|
|
329
|
-
eocd.writeUInt32LE(0x06054b50, 0);
|
|
330
|
-
eocd.writeUInt16LE(eocdCount, 8);
|
|
331
|
-
eocd.writeUInt16LE(eocdCount, 10);
|
|
332
|
-
eocd.writeUInt32LE(eocdSizeOverflow ? ZIP64_MAGIC : cdSize, 12);
|
|
333
|
-
eocd.writeUInt32LE(eocdOffsetOverflow ? ZIP64_MAGIC : cdOffset, 16);
|
|
334
|
-
await fd.write(eocd);
|
|
335
|
-
}
|
|
336
|
-
finally {
|
|
337
|
-
await fd.close();
|
|
338
|
-
}
|
|
339
|
-
return epubFile;
|
|
340
|
-
}
|
|
1
|
+
import { t as createArchive } from "./chunks/archive-DmawhYP4.js";
|
|
2
|
+
export { createArchive };
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { open, opendir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { crc32, deflateRaw } from "node:zlib";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
//#region src/archive.ts
|
|
6
|
+
const deflateRawAsync = promisify(deflateRaw);
|
|
7
|
+
const IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
8
|
+
".DS_Store",
|
|
9
|
+
"._DS_Store",
|
|
10
|
+
"Thumbs.db",
|
|
11
|
+
"ehthumbs.db"
|
|
12
|
+
]);
|
|
13
|
+
const IGNORED_DIRS = /* @__PURE__ */ new Set([".svn", ".git"]);
|
|
14
|
+
const ZIP64_MAGIC = 4294967295;
|
|
15
|
+
const ZIP64_MAGIC_SHORT = 65535;
|
|
16
|
+
const ZIP64_VERSION = 45;
|
|
17
|
+
/**
|
|
18
|
+
* ZIP64 local extended-information extra field (id 0x0001): commons-compress
|
|
19
|
+
* always writes BOTH the 8-byte uncompressed and 8-byte compressed size in the
|
|
20
|
+
* local extra when it adds one.
|
|
21
|
+
*/
|
|
22
|
+
function zip64LocalExtra(uncompressedSize, compressedSize) {
|
|
23
|
+
const b = Buffer.alloc(20);
|
|
24
|
+
b.writeUInt16LE(1, 0);
|
|
25
|
+
b.writeUInt16LE(16, 2);
|
|
26
|
+
b.writeBigUInt64LE(BigInt(uncompressedSize), 4);
|
|
27
|
+
b.writeBigUInt64LE(BigInt(compressedSize), 12);
|
|
28
|
+
return b;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* ZIP64 central extended-information extra field (id 0x0001): carries ONLY the
|
|
32
|
+
* values that overflow, in the fixed APPNOTE order (uncompressed size,
|
|
33
|
+
* compressed size, local-header offset), matching commons-compress's
|
|
34
|
+
* getCentralDirectoryData(). The two sizes are emitted together (both or
|
|
35
|
+
* neither) when either overflows; the offset is emitted when it overflows. The
|
|
36
|
+
* disk-start-number never overflows here (single disk), so it is never present.
|
|
37
|
+
*/
|
|
38
|
+
function zip64CentralExtra(needSizes, uncompressedSize, compressedSize, needOffset, offset) {
|
|
39
|
+
const dataLen = (needSizes ? 16 : 0) + (needOffset ? 8 : 0);
|
|
40
|
+
const b = Buffer.alloc(4 + dataLen);
|
|
41
|
+
b.writeUInt16LE(1, 0);
|
|
42
|
+
b.writeUInt16LE(dataLen, 2);
|
|
43
|
+
let o = 4;
|
|
44
|
+
if (needSizes) {
|
|
45
|
+
b.writeBigUInt64LE(BigInt(uncompressedSize), o);
|
|
46
|
+
b.writeBigUInt64LE(BigInt(compressedSize), o + 8);
|
|
47
|
+
o += 16;
|
|
48
|
+
}
|
|
49
|
+
if (needOffset) b.writeBigUInt64LE(BigInt(offset), o);
|
|
50
|
+
return b;
|
|
51
|
+
}
|
|
52
|
+
/** Archive.collectFiles: depth-first, raw readdir order, junk entries skipped. */
|
|
53
|
+
async function collectFiles(dir, dirName, out) {
|
|
54
|
+
const names = [];
|
|
55
|
+
const d = await opendir(dir);
|
|
56
|
+
for await (const ent of d) names.push(ent.name);
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
const full = join(dir, name);
|
|
59
|
+
let st;
|
|
60
|
+
try {
|
|
61
|
+
st = await stat(full);
|
|
62
|
+
} catch {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (st.isFile()) {
|
|
66
|
+
if (!IGNORED_FILES.has(name)) out.push({
|
|
67
|
+
name: dirName + name,
|
|
68
|
+
path: full
|
|
69
|
+
});
|
|
70
|
+
} else if (st.isDirectory()) {
|
|
71
|
+
if (!IGNORED_DIRS.has(name)) await collectFiles(full, dirName + name + "/", out);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** MS-DOS date+time (local time, 2 s resolution) as (date << 16) | time. */
|
|
76
|
+
function dosDateTime(ms) {
|
|
77
|
+
const d = new Date(ms);
|
|
78
|
+
if (d.getFullYear() < 1980) return 33 << 16;
|
|
79
|
+
const date = d.getFullYear() - 1980 << 9 | d.getMonth() + 1 << 5 | d.getDate();
|
|
80
|
+
const time = d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() >> 1;
|
|
81
|
+
return (date << 16 | time) >>> 0;
|
|
82
|
+
}
|
|
83
|
+
/** Windows FILETIME (100 ns intervals since 1601-01-01) from unix nanoseconds. */
|
|
84
|
+
function toFiletime(ns) {
|
|
85
|
+
return ns / 100n + 116444736000000000n;
|
|
86
|
+
}
|
|
87
|
+
/** Unix seconds as the unsigned 32-bit value the 0x5455 field stores. */
|
|
88
|
+
function toUnixSec(ns) {
|
|
89
|
+
return Number(BigInt.asUintN(32, ns / 1000000000n));
|
|
90
|
+
}
|
|
91
|
+
/** NTFS extra field 0x000a (36 bytes): mtime, atime, creation as FILETIME. */
|
|
92
|
+
function writeNtfsExtra(b, off, st) {
|
|
93
|
+
b.writeUInt16LE(10, off);
|
|
94
|
+
b.writeUInt16LE(32, off + 2);
|
|
95
|
+
b.writeUInt32LE(0, off + 4);
|
|
96
|
+
b.writeUInt16LE(1, off + 8);
|
|
97
|
+
b.writeUInt16LE(24, off + 10);
|
|
98
|
+
b.writeBigUInt64LE(toFiletime(st.mtimeNs), off + 12);
|
|
99
|
+
b.writeBigUInt64LE(toFiletime(st.atimeNs), off + 20);
|
|
100
|
+
b.writeBigUInt64LE(toFiletime(st.birthtimeNs), off + 28);
|
|
101
|
+
}
|
|
102
|
+
/** Local-header extra: 0x5455 (flags 7: mtime/atime/creation) + NTFS. */
|
|
103
|
+
function localExtra(st) {
|
|
104
|
+
const b = Buffer.alloc(53);
|
|
105
|
+
b.writeUInt16LE(21589, 0);
|
|
106
|
+
b.writeUInt16LE(13, 2);
|
|
107
|
+
b.writeUInt8(7, 4);
|
|
108
|
+
b.writeUInt32LE(toUnixSec(st.mtimeNs), 5);
|
|
109
|
+
b.writeUInt32LE(toUnixSec(st.atimeNs), 9);
|
|
110
|
+
b.writeUInt32LE(toUnixSec(st.birthtimeNs), 13);
|
|
111
|
+
writeNtfsExtra(b, 17, st);
|
|
112
|
+
return b;
|
|
113
|
+
}
|
|
114
|
+
/** Central-directory extra: 0x5455 (flags 7, mtime only) + NTFS. */
|
|
115
|
+
function centralExtra(st) {
|
|
116
|
+
const b = Buffer.alloc(45);
|
|
117
|
+
b.writeUInt16LE(21589, 0);
|
|
118
|
+
b.writeUInt16LE(5, 2);
|
|
119
|
+
b.writeUInt8(7, 4);
|
|
120
|
+
b.writeUInt32LE(toUnixSec(st.mtimeNs), 5);
|
|
121
|
+
writeNtfsExtra(b, 9, st);
|
|
122
|
+
return b;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Package `baseDir` (an existing directory) into `<parent>/<name>.epub` beside
|
|
126
|
+
* it, overwriting any existing file there, and return the written path. The
|
|
127
|
+
* base directory is canonicalized first (Archive's makeCanonical), so a
|
|
128
|
+
* symlinked input saves next to the real directory. Throws on any failure; the
|
|
129
|
+
* caller maps that onto the jar's packaging-failure behavior.
|
|
130
|
+
*
|
|
131
|
+
* `thresholds` lets tests lower the ZIP64 trigger so the ZIP64 path runs on
|
|
132
|
+
* tiny inputs; it defaults to the real 32-bit/16-bit limits, keeping production
|
|
133
|
+
* output byte-identical to the pre-ZIP64 writer below 4 GB.
|
|
134
|
+
*/
|
|
135
|
+
async function createArchive(baseDir, thresholds = {}) {
|
|
136
|
+
const sizeThreshold = thresholds.size ?? ZIP64_MAGIC;
|
|
137
|
+
const entryThreshold = thresholds.entries ?? ZIP64_MAGIC_SHORT;
|
|
138
|
+
const canonical = await realpath(baseDir);
|
|
139
|
+
const epubFile = join(dirname(canonical), basename(canonical) + ".epub");
|
|
140
|
+
const entries = [];
|
|
141
|
+
await collectFiles(canonical, "", entries);
|
|
142
|
+
const mimetypeIndex = entries.findIndex((e) => e.name === "mimetype");
|
|
143
|
+
if (mimetypeIndex > -1) {
|
|
144
|
+
const [m] = entries.splice(mimetypeIndex, 1);
|
|
145
|
+
entries.unshift(m);
|
|
146
|
+
}
|
|
147
|
+
const fd = await open(epubFile, "w");
|
|
148
|
+
try {
|
|
149
|
+
let offset = 0;
|
|
150
|
+
const central = [];
|
|
151
|
+
let usedZip64 = false;
|
|
152
|
+
for (let i = 0; i < entries.length; i++) {
|
|
153
|
+
const e = entries[i];
|
|
154
|
+
const st = await stat(e.path, { bigint: true });
|
|
155
|
+
const data = await readFile(e.path);
|
|
156
|
+
const stored = i === 0 && mimetypeIndex > -1;
|
|
157
|
+
const comp = stored ? data : await deflateRawAsync(data);
|
|
158
|
+
const crc = crc32(data);
|
|
159
|
+
const name = Buffer.from(e.name, "utf8");
|
|
160
|
+
const dos = dosDateTime(Number(st.mtimeNs / 1000000n));
|
|
161
|
+
const method = stored ? 0 : 8;
|
|
162
|
+
const needSizes = data.length >= sizeThreshold || comp.length >= sizeThreshold;
|
|
163
|
+
const needOffset = offset >= sizeThreshold;
|
|
164
|
+
if (needSizes || needOffset) usedZip64 = true;
|
|
165
|
+
const baseExtraL = stored ? Buffer.alloc(0) : localExtra(st);
|
|
166
|
+
const baseExtraC = stored ? Buffer.alloc(0) : centralExtra(st);
|
|
167
|
+
const extraL = needSizes ? Buffer.concat([zip64LocalExtra(data.length, comp.length), baseExtraL]) : baseExtraL;
|
|
168
|
+
const extraC = needSizes || needOffset ? Buffer.concat([zip64CentralExtra(needSizes, data.length, comp.length, needOffset, offset), baseExtraC]) : baseExtraC;
|
|
169
|
+
const classicVersion = stored ? 10 : 20;
|
|
170
|
+
const localVersion = needSizes ? ZIP64_VERSION : classicVersion;
|
|
171
|
+
const centralVersion = needSizes || needOffset ? ZIP64_VERSION : classicVersion;
|
|
172
|
+
const lfhComp = needSizes ? ZIP64_MAGIC : comp.length;
|
|
173
|
+
const lfhUncomp = needSizes ? ZIP64_MAGIC : data.length;
|
|
174
|
+
const lfh = Buffer.alloc(30);
|
|
175
|
+
lfh.writeUInt32LE(67324752, 0);
|
|
176
|
+
lfh.writeUInt16LE(localVersion, 4);
|
|
177
|
+
lfh.writeUInt16LE(2048, 6);
|
|
178
|
+
lfh.writeUInt16LE(method, 8);
|
|
179
|
+
lfh.writeUInt32LE(dos, 10);
|
|
180
|
+
lfh.writeUInt32LE(crc, 14);
|
|
181
|
+
lfh.writeUInt32LE(lfhComp, 18);
|
|
182
|
+
lfh.writeUInt32LE(lfhUncomp, 22);
|
|
183
|
+
lfh.writeUInt16LE(name.length, 26);
|
|
184
|
+
lfh.writeUInt16LE(extraL.length, 28);
|
|
185
|
+
const cen = Buffer.alloc(46);
|
|
186
|
+
cen.writeUInt32LE(33639248, 0);
|
|
187
|
+
cen.writeUInt16LE(20, 4);
|
|
188
|
+
cen.writeUInt16LE(centralVersion, 6);
|
|
189
|
+
cen.writeUInt16LE(2048, 8);
|
|
190
|
+
cen.writeUInt16LE(method, 10);
|
|
191
|
+
cen.writeUInt32LE(dos, 12);
|
|
192
|
+
cen.writeUInt32LE(crc, 16);
|
|
193
|
+
cen.writeUInt32LE(needSizes ? ZIP64_MAGIC : comp.length, 20);
|
|
194
|
+
cen.writeUInt32LE(needSizes ? ZIP64_MAGIC : data.length, 24);
|
|
195
|
+
cen.writeUInt16LE(name.length, 28);
|
|
196
|
+
cen.writeUInt16LE(extraC.length, 30);
|
|
197
|
+
cen.writeUInt32LE(needOffset ? ZIP64_MAGIC : offset, 42);
|
|
198
|
+
central.push(cen, name, extraC);
|
|
199
|
+
await fd.write(lfh);
|
|
200
|
+
await fd.write(name);
|
|
201
|
+
await fd.write(extraL);
|
|
202
|
+
await fd.write(comp);
|
|
203
|
+
offset += lfh.length + name.length + extraL.length + comp.length;
|
|
204
|
+
}
|
|
205
|
+
const cdOffset = offset;
|
|
206
|
+
let cdSize = 0;
|
|
207
|
+
for (const b of central) {
|
|
208
|
+
await fd.write(b);
|
|
209
|
+
cdSize += b.length;
|
|
210
|
+
}
|
|
211
|
+
const eocdOffsetOverflow = cdOffset >= sizeThreshold;
|
|
212
|
+
const eocdSizeOverflow = cdSize >= sizeThreshold;
|
|
213
|
+
const eocdCountOverflow = entries.length >= entryThreshold;
|
|
214
|
+
if (usedZip64 || eocdOffsetOverflow || eocdSizeOverflow || eocdCountOverflow) {
|
|
215
|
+
const z64eocd = Buffer.alloc(56);
|
|
216
|
+
z64eocd.writeUInt32LE(101075792, 0);
|
|
217
|
+
z64eocd.writeBigUInt64LE(44n, 4);
|
|
218
|
+
z64eocd.writeUInt16LE(ZIP64_VERSION, 12);
|
|
219
|
+
z64eocd.writeUInt16LE(ZIP64_VERSION, 14);
|
|
220
|
+
z64eocd.writeUInt32LE(0, 16);
|
|
221
|
+
z64eocd.writeUInt32LE(0, 20);
|
|
222
|
+
z64eocd.writeBigUInt64LE(BigInt(entries.length), 24);
|
|
223
|
+
z64eocd.writeBigUInt64LE(BigInt(entries.length), 32);
|
|
224
|
+
z64eocd.writeBigUInt64LE(BigInt(cdSize), 40);
|
|
225
|
+
z64eocd.writeBigUInt64LE(BigInt(cdOffset), 48);
|
|
226
|
+
await fd.write(z64eocd);
|
|
227
|
+
const z64loc = Buffer.alloc(20);
|
|
228
|
+
z64loc.writeUInt32LE(117853008, 0);
|
|
229
|
+
z64loc.writeUInt32LE(0, 4);
|
|
230
|
+
z64loc.writeBigUInt64LE(BigInt(cdOffset + cdSize), 8);
|
|
231
|
+
z64loc.writeUInt32LE(1, 16);
|
|
232
|
+
await fd.write(z64loc);
|
|
233
|
+
}
|
|
234
|
+
const eocdCount = eocdCountOverflow ? ZIP64_MAGIC_SHORT : entries.length;
|
|
235
|
+
const eocd = Buffer.alloc(22);
|
|
236
|
+
eocd.writeUInt32LE(101010256, 0);
|
|
237
|
+
eocd.writeUInt16LE(eocdCount, 8);
|
|
238
|
+
eocd.writeUInt16LE(eocdCount, 10);
|
|
239
|
+
eocd.writeUInt32LE(eocdSizeOverflow ? ZIP64_MAGIC : cdSize, 12);
|
|
240
|
+
eocd.writeUInt32LE(eocdOffsetOverflow ? ZIP64_MAGIC : cdOffset, 16);
|
|
241
|
+
await fd.write(eocd);
|
|
242
|
+
} finally {
|
|
243
|
+
await fd.close();
|
|
244
|
+
}
|
|
245
|
+
return epubFile;
|
|
246
|
+
}
|
|
247
|
+
//#endregion
|
|
248
|
+
export { createArchive as t };
|
|
249
|
+
|
|
250
|
+
//# sourceMappingURL=archive-DmawhYP4.js.map
|