@storyteller-platform/epub 0.7.0 → 0.8.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.
- package/README.md +762 -1054
- package/dist/adapters/interface.d.ts +31 -27
- package/dist/adapters/interface.js +1 -0
- package/dist/adapters/memory.d.ts +7 -11
- package/dist/adapters/memory.js +70 -78
- package/dist/adapters/tmpfs.d.ts +11 -12
- package/dist/adapters/tmpfs.js +238 -165
- package/dist/epubcheck.d.ts +4 -6
- package/dist/epubcheck.js +25 -27
- package/dist/index.d.ts +1161 -5
- package/dist/index.js +2595 -2661
- package/dist/upgrade.d.ts +23 -1169
- package/dist/upgrade.js +411 -463
- package/dist/zip.d.ts +2 -0
- package/dist/zip.js +137 -0
- package/package.json +18 -32
- package/dist/adapters/interface.cjs +0 -16
- package/dist/adapters/interface.d.cts +0 -69
- package/dist/adapters/memory.cjs +0 -103
- package/dist/adapters/memory.d.cts +0 -37
- package/dist/adapters/tmpfs.cjs +0 -236
- package/dist/adapters/tmpfs.d.cts +0 -29
- package/dist/chunk-BIEQXUOY.js +0 -50
- package/dist/epubcheck.cjs +0 -60
- package/dist/epubcheck.d.cts +0 -23
- package/dist/index.cjs +0 -2749
- package/dist/index.d.cts +0 -5
- package/dist/upgrade.cjs +0 -555
- package/dist/upgrade.d.cts +0 -1182
package/dist/adapters/tmpfs.js
CHANGED
|
@@ -1,179 +1,252 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
5
53
|
import { randomUUID } from "node:crypto";
|
|
6
54
|
import { createWriteStream, rmSync } from "node:fs";
|
|
7
|
-
import {
|
|
8
|
-
cp,
|
|
9
|
-
mkdir,
|
|
10
|
-
readFile,
|
|
11
|
-
readdir,
|
|
12
|
-
rm,
|
|
13
|
-
stat,
|
|
14
|
-
writeFile
|
|
15
|
-
} from "node:fs/promises";
|
|
16
|
-
import { tmpdir } from "node:os";
|
|
55
|
+
import { cp, mkdir, readFile, readdir, rm, stat, writeFile, } from "node:fs/promises";
|
|
17
56
|
import { pipeline } from "node:stream/promises";
|
|
18
|
-
import { fromBuffer, open } from "yauzl-promise";
|
|
19
57
|
import { ZipFile } from "yazl";
|
|
58
|
+
import { scratchPath } from "@storyteller-platform/fs";
|
|
59
|
+
import { MediaType } from "@storyteller-platform/media-types";
|
|
20
60
|
import { dirname, join, sep } from "@storyteller-platform/path";
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const FLAC_FILE_EXTENSIONS = [".flac"];
|
|
29
|
-
const ALAC_FILE_EXTENSIONS = [".alac"];
|
|
30
|
-
const WEBM_FILE_EXTENSIONS = [".weba"];
|
|
31
|
-
const AUDIO_FILE_EXTENSIONS = [
|
|
32
|
-
...MP3_FILE_EXTENSIONS,
|
|
33
|
-
...AAC_FILE_EXTENSIONS,
|
|
34
|
-
...MPEG4_FILE_EXTENSIONS,
|
|
35
|
-
...OPUS_FILE_EXTENSIONS,
|
|
36
|
-
...OGG_FILE_EXTENSIONS,
|
|
37
|
-
...WAVE_FILE_EXTENSIONS,
|
|
38
|
-
...AIFF_FILE_EXTENSIONS,
|
|
39
|
-
...FLAC_FILE_EXTENSIONS,
|
|
40
|
-
...ALAC_FILE_EXTENSIONS,
|
|
41
|
-
...WEBM_FILE_EXTENSIONS
|
|
42
|
-
];
|
|
43
|
-
function isAudioFile(filenameOrExt) {
|
|
44
|
-
const lowered = filenameOrExt.toLowerCase();
|
|
45
|
-
return AUDIO_FILE_EXTENSIONS.some((ext) => lowered.endsWith(ext));
|
|
46
|
-
}
|
|
61
|
+
import { openZip } from "../zip.js";
|
|
62
|
+
const PRECOMPRESSED_KINDS = new Set([
|
|
63
|
+
"audio",
|
|
64
|
+
"image",
|
|
65
|
+
"archive",
|
|
66
|
+
"package",
|
|
67
|
+
]);
|
|
47
68
|
function mintRootPath() {
|
|
48
|
-
|
|
69
|
+
return scratchPath(`storyteller-platform-epub-${randomUUID()}`);
|
|
49
70
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Whether an entry name can be laid down inside the extract root.
|
|
73
|
+
*
|
|
74
|
+
* A `.` or `..` segment cannot name a real file, and joining one onto the
|
|
75
|
+
* root would walk out of it, so such an entry is unreachable here whether it
|
|
76
|
+
* came from a hostile archive or from an href that spelled it `%2E%2E`.
|
|
77
|
+
*/
|
|
78
|
+
function isStorableEntryName(entry) {
|
|
79
|
+
return !entry
|
|
80
|
+
.split("/")
|
|
81
|
+
.some((segment) => segment === "" || segment === "." || segment === "..");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Extracts an EPUB archive to a temp directory and serves all I/O off
|
|
85
|
+
* the real filesystem.
|
|
86
|
+
* `Epub.using(TmpFsAdapter).from(...)` returns a writable {@link Epub}.
|
|
87
|
+
*/
|
|
88
|
+
export class TmpFsAdapter {
|
|
89
|
+
rootPath;
|
|
90
|
+
static kind = "extracted-dir";
|
|
91
|
+
static capabilities = { writable: true };
|
|
92
|
+
static async init(source) {
|
|
93
|
+
const rootPath = mintRootPath();
|
|
94
|
+
try {
|
|
95
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
96
|
+
try {
|
|
97
|
+
const zipfile = await openZip(source);
|
|
98
|
+
const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
99
|
+
stack.defer(async () => {
|
|
100
|
+
await zipfile.close();
|
|
101
|
+
});
|
|
102
|
+
for await (const entry of zipfile) {
|
|
103
|
+
if (entry.filename.endsWith("/")) {
|
|
104
|
+
// directory entries are skipped; parent dirs are created implicitly
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!isStorableEntryName(entry.filename))
|
|
108
|
+
continue;
|
|
109
|
+
const writePath = join(rootPath, ...entry.filename.split("/"));
|
|
110
|
+
const readStream = await entry.openReadStream();
|
|
111
|
+
await mkdir(dirname(writePath), { recursive: true });
|
|
112
|
+
const writeStream = createWriteStream(writePath);
|
|
113
|
+
await pipeline(readStream, writeStream);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (e_1) {
|
|
117
|
+
env_1.error = e_1;
|
|
118
|
+
env_1.hasError = true;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
const result_1 = __disposeResources(env_1);
|
|
122
|
+
if (result_1)
|
|
123
|
+
await result_1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
rmSync(rootPath, { force: true, recursive: true });
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
return new TmpFsAdapter(rootPath);
|
|
131
|
+
}
|
|
132
|
+
static async initEmpty() {
|
|
133
|
+
const rootPath = mintRootPath();
|
|
134
|
+
await mkdir(rootPath, { recursive: true });
|
|
135
|
+
return new TmpFsAdapter(rootPath);
|
|
136
|
+
}
|
|
137
|
+
constructor(rootPath) {
|
|
138
|
+
this.rootPath = rootPath;
|
|
139
|
+
}
|
|
140
|
+
diskPath(entry) {
|
|
141
|
+
if (!isStorableEntryName(entry)) {
|
|
142
|
+
const err = new Error(`ENOENT: no such entry in EPUB archive: ${entry}`);
|
|
143
|
+
err.code = "ENOENT";
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
return join(this.rootPath, ...entry.split("/"));
|
|
85
147
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
static async initEmpty() {
|
|
89
|
-
const rootPath = mintRootPath();
|
|
90
|
-
await mkdir(rootPath, { recursive: true });
|
|
91
|
-
return new TmpFsAdapter(rootPath);
|
|
92
|
-
}
|
|
93
|
-
async read(path, encoding) {
|
|
94
|
-
return await readFile(path, encoding);
|
|
95
|
-
}
|
|
96
|
-
async write(path, data, encoding) {
|
|
97
|
-
await mkdir(dirname(path), { recursive: true });
|
|
98
|
-
await writeFile(path, data, encoding);
|
|
99
|
-
}
|
|
100
|
-
async remove(path) {
|
|
101
|
-
await rm(path);
|
|
102
|
-
}
|
|
103
|
-
async archiveLength(path) {
|
|
104
|
-
try {
|
|
105
|
-
const st = await stat(path);
|
|
106
|
-
return st.size;
|
|
107
|
-
} catch {
|
|
108
|
-
return 0;
|
|
148
|
+
async read(entry, encoding) {
|
|
149
|
+
return await readFile(this.diskPath(entry), encoding);
|
|
109
150
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
withFileTypes: true
|
|
115
|
-
});
|
|
116
|
-
for (const entry of entries) {
|
|
117
|
-
if (entry.isDirectory()) continue;
|
|
118
|
-
const absolutePath = join(entry.parentPath, entry.name);
|
|
119
|
-
yield {
|
|
120
|
-
absolutePath,
|
|
121
|
-
relativePath: absolutePath.replace(`${this.rootPath}${sep}`, "")
|
|
122
|
-
};
|
|
151
|
+
async write(entry, data, encoding) {
|
|
152
|
+
const path = this.diskPath(entry);
|
|
153
|
+
await mkdir(dirname(path), { recursive: true });
|
|
154
|
+
await writeFile(path, data, encoding);
|
|
123
155
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const newRoot = mintRootPath();
|
|
127
|
-
try {
|
|
128
|
-
await cp(this.rootPath, newRoot, { recursive: true });
|
|
129
|
-
} catch (error) {
|
|
130
|
-
rmSync(newRoot, { force: true, recursive: true });
|
|
131
|
-
throw error;
|
|
156
|
+
async remove(entry) {
|
|
157
|
+
await rm(this.diskPath(entry));
|
|
132
158
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
compress: false
|
|
156
|
-
});
|
|
157
|
-
for await (const entry of this.list()) {
|
|
158
|
-
if (entry.relativePath === "mimetype") continue;
|
|
159
|
-
zipfile.addFile(entry.absolutePath, entry.relativePath, {
|
|
160
|
-
compress: !isAudioFile(entry.absolutePath)
|
|
159
|
+
async exists(entry) {
|
|
160
|
+
try {
|
|
161
|
+
await stat(this.diskPath(entry));
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async archiveLength(entry) {
|
|
169
|
+
try {
|
|
170
|
+
const st = await stat(this.diskPath(entry));
|
|
171
|
+
return st.size;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async *list() {
|
|
178
|
+
const entries = await readdir(this.rootPath, {
|
|
179
|
+
recursive: true,
|
|
180
|
+
withFileTypes: true,
|
|
161
181
|
});
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
if (entry.isDirectory())
|
|
184
|
+
continue;
|
|
185
|
+
const absolutePath = join(entry.parentPath, entry.name);
|
|
186
|
+
yield {
|
|
187
|
+
absolutePath,
|
|
188
|
+
relativePath: absolutePath
|
|
189
|
+
.slice(this.rootPath.length + 1)
|
|
190
|
+
.split(sep)
|
|
191
|
+
.join("/"),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async duplicate() {
|
|
196
|
+
const newRoot = mintRootPath();
|
|
197
|
+
try {
|
|
198
|
+
await cp(this.rootPath, newRoot, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
rmSync(newRoot, { force: true, recursive: true });
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
return new TmpFsAdapter(newRoot);
|
|
205
|
+
}
|
|
206
|
+
async serialize(targetPath) {
|
|
207
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
208
|
+
try {
|
|
209
|
+
const tmpArchivePath = mintRootPath();
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
|
211
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
212
|
+
const zipfile = new ZipFile();
|
|
213
|
+
const writeStream = createWriteStream(tmpArchivePath);
|
|
214
|
+
writeStream.on("close", () => {
|
|
215
|
+
resolve();
|
|
216
|
+
});
|
|
217
|
+
writeStream.on("error", reject);
|
|
218
|
+
zipfile.outputStream.on("error", reject);
|
|
219
|
+
const stack = __addDisposableResource(env_2, new AsyncDisposableStack(), true);
|
|
220
|
+
stack.defer(async () => {
|
|
221
|
+
writeStream.close();
|
|
222
|
+
await rm(tmpArchivePath, { force: true });
|
|
223
|
+
});
|
|
224
|
+
zipfile.outputStream.pipe(writeStream);
|
|
225
|
+
zipfile.addBuffer(Buffer.from(MediaType.EPUB.mime), "mimetype", {
|
|
226
|
+
compress: false,
|
|
227
|
+
});
|
|
228
|
+
for await (const entry of this.list()) {
|
|
229
|
+
if (entry.relativePath === "mimetype")
|
|
230
|
+
continue;
|
|
231
|
+
zipfile.addFile(entry.absolutePath, entry.relativePath, {
|
|
232
|
+
compress: !PRECOMPRESSED_KINDS.has(MediaType.fromPath(entry.absolutePath)?.kind),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
zipfile.end();
|
|
236
|
+
await promise;
|
|
237
|
+
await cp(tmpArchivePath, targetPath);
|
|
238
|
+
}
|
|
239
|
+
catch (e_2) {
|
|
240
|
+
env_2.error = e_2;
|
|
241
|
+
env_2.hasError = true;
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
const result_2 = __disposeResources(env_2);
|
|
245
|
+
if (result_2)
|
|
246
|
+
await result_2;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
dispose() {
|
|
250
|
+
rmSync(this.rootPath, { recursive: true, force: true });
|
|
171
251
|
}
|
|
172
|
-
}
|
|
173
|
-
dispose() {
|
|
174
|
-
rmSync(this.rootPath, { recursive: true, force: true });
|
|
175
|
-
}
|
|
176
252
|
}
|
|
177
|
-
export {
|
|
178
|
-
TmpFsAdapter
|
|
179
|
-
};
|
package/dist/epubcheck.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
interface Location {
|
|
1
|
+
export interface Location {
|
|
2
2
|
url: {
|
|
3
3
|
hierachical: boolean;
|
|
4
4
|
opaque: boolean;
|
|
@@ -8,16 +8,14 @@ interface Location {
|
|
|
8
8
|
column: number;
|
|
9
9
|
context: string | null;
|
|
10
10
|
}
|
|
11
|
-
interface Message {
|
|
11
|
+
export interface Message {
|
|
12
12
|
ID: string;
|
|
13
13
|
severity: "FATAL" | "ERROR" | "WARNING" | "INFO" | "USAGE";
|
|
14
14
|
message: string;
|
|
15
15
|
additionalLocations: number;
|
|
16
16
|
locations: Location[];
|
|
17
17
|
}
|
|
18
|
-
interface Report {
|
|
18
|
+
export interface Report {
|
|
19
19
|
messages: Message[];
|
|
20
20
|
}
|
|
21
|
-
declare function epubcheck(epubFile: string): Promise<Report>;
|
|
22
|
-
|
|
23
|
-
export { type Location, type Message, type Report, epubcheck };
|
|
21
|
+
export declare function epubcheck(epubFile: string): Promise<Report>;
|
package/dist/epubcheck.js
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
|
-
import "./chunk-BIEQXUOY.js";
|
|
2
1
|
import { execFile } from "node:child_process";
|
|
3
2
|
import { randomUUID } from "node:crypto";
|
|
4
3
|
import { readFile, unlink } from "node:fs/promises";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
4
|
import { dirname, join } from "node:path";
|
|
7
5
|
import { fileURLToPath } from "node:url";
|
|
8
6
|
import { promisify } from "node:util";
|
|
7
|
+
import { scratchPath } from "@storyteller-platform/fs";
|
|
9
8
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
10
9
|
const JAR = join(here, "vendors", "epubcheck-5.3.0", "epubcheck.jar");
|
|
11
|
-
async function epubcheck(epubFile) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
10
|
+
export async function epubcheck(epubFile) {
|
|
11
|
+
const output = scratchPath(`epubcheck-${randomUUID()}.json`);
|
|
12
|
+
const execFilePromise = promisify(execFile);
|
|
13
|
+
try {
|
|
14
|
+
await execFilePromise("java", [
|
|
15
|
+
"-jar",
|
|
16
|
+
JAR,
|
|
17
|
+
"--json",
|
|
18
|
+
output,
|
|
19
|
+
epubFile,
|
|
20
|
+
]).catch((error) => {
|
|
21
|
+
if (error instanceof Error &&
|
|
22
|
+
error.message.includes("Check finished with errors")) {
|
|
23
|
+
// dont throw, just let the caller handle it
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
});
|
|
28
|
+
const json = await readFile(output, "utf8");
|
|
29
|
+
return JSON.parse(json);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
await unlink(output).catch(() => { });
|
|
33
|
+
}
|
|
33
34
|
}
|
|
34
|
-
export {
|
|
35
|
-
epubcheck
|
|
36
|
-
};
|