@storyteller-platform/epub 0.6.3 → 0.7.1

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.
@@ -13,38 +13,23 @@ import {
13
13
  stat,
14
14
  writeFile
15
15
  } from "node:fs/promises";
16
- import { tmpdir } from "node:os";
17
16
  import { pipeline } from "node:stream/promises";
18
- import { fromBuffer, open } from "yauzl-promise";
19
17
  import { ZipFile } from "yazl";
18
+ import { scratchPath } from "@storyteller-platform/fs";
19
+ import { MediaType } from "@storyteller-platform/media-types";
20
20
  import { dirname, join, sep } from "@storyteller-platform/path";
21
- const MP3_FILE_EXTENSIONS = [".mp3"];
22
- const MPEG4_FILE_EXTENSIONS = [".mp4", ".m4a", ".m4b"];
23
- const AAC_FILE_EXTENSIONS = [".aac"];
24
- const OGG_FILE_EXTENSIONS = [".ogg", ".oga", ".mogg"];
25
- const OPUS_FILE_EXTENSIONS = [".opus"];
26
- const WAVE_FILE_EXTENSIONS = [".wav"];
27
- const AIFF_FILE_EXTENSIONS = [".aiff"];
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
- return AUDIO_FILE_EXTENSIONS.some((ext) => filenameOrExt.endsWith(ext));
45
- }
21
+ import { openZip } from "../zip.js";
22
+ const PRECOMPRESSED_KINDS = /* @__PURE__ */ new Set([
23
+ "audio",
24
+ "image",
25
+ "archive",
26
+ "package"
27
+ ]);
46
28
  function mintRootPath() {
47
- return join(tmpdir(), `storyteller-platform-epub-${randomUUID()}`);
29
+ return scratchPath(`storyteller-platform-epub-${randomUUID()}`);
30
+ }
31
+ function isStorableEntryName(entry) {
32
+ return !entry.split("/").some((segment) => segment === "" || segment === "." || segment === "..");
48
33
  }
49
34
  class TmpFsAdapter {
50
35
  constructor(rootPath) {
@@ -57,7 +42,7 @@ class TmpFsAdapter {
57
42
  try {
58
43
  var _stack = [];
59
44
  try {
60
- const zipfile = typeof source === "string" ? await open(source) : await fromBuffer(Buffer.from(source));
45
+ const zipfile = await openZip(source);
61
46
  const stack = __using(_stack, new AsyncDisposableStack(), true);
62
47
  stack.defer(async () => {
63
48
  await zipfile.close();
@@ -66,7 +51,8 @@ class TmpFsAdapter {
66
51
  if (entry.filename.endsWith("/")) {
67
52
  continue;
68
53
  }
69
- const writePath = join(rootPath, entry.filename);
54
+ if (!isStorableEntryName(entry.filename)) continue;
55
+ const writePath = join(rootPath, ...entry.filename.split("/"));
70
56
  const readStream = await entry.openReadStream();
71
57
  await mkdir(dirname(writePath), { recursive: true });
72
58
  const writeStream = createWriteStream(writePath);
@@ -89,19 +75,36 @@ class TmpFsAdapter {
89
75
  await mkdir(rootPath, { recursive: true });
90
76
  return new TmpFsAdapter(rootPath);
91
77
  }
92
- async read(path, encoding) {
93
- return await readFile(path, encoding);
78
+ diskPath(entry) {
79
+ if (!isStorableEntryName(entry)) {
80
+ const err = new Error(`ENOENT: no such entry in EPUB archive: ${entry}`);
81
+ err.code = "ENOENT";
82
+ throw err;
83
+ }
84
+ return join(this.rootPath, ...entry.split("/"));
85
+ }
86
+ async read(entry, encoding) {
87
+ return await readFile(this.diskPath(entry), encoding);
94
88
  }
95
- async write(path, data, encoding) {
89
+ async write(entry, data, encoding) {
90
+ const path = this.diskPath(entry);
96
91
  await mkdir(dirname(path), { recursive: true });
97
92
  await writeFile(path, data, encoding);
98
93
  }
99
- async remove(path) {
100
- await rm(path);
94
+ async remove(entry) {
95
+ await rm(this.diskPath(entry));
96
+ }
97
+ async exists(entry) {
98
+ try {
99
+ await stat(this.diskPath(entry));
100
+ return true;
101
+ } catch {
102
+ return false;
103
+ }
101
104
  }
102
- async archiveLength(path) {
105
+ async archiveLength(entry) {
103
106
  try {
104
- const st = await stat(path);
107
+ const st = await stat(this.diskPath(entry));
105
108
  return st.size;
106
109
  } catch {
107
110
  return 0;
@@ -117,7 +120,7 @@ class TmpFsAdapter {
117
120
  const absolutePath = join(entry.parentPath, entry.name);
118
121
  yield {
119
122
  absolutePath,
120
- relativePath: absolutePath.replace(`${this.rootPath}${sep}`, "")
123
+ relativePath: absolutePath.slice(this.rootPath.length + 1).split(sep).join("/")
121
124
  };
122
125
  }
123
126
  }
@@ -132,31 +135,33 @@ class TmpFsAdapter {
132
135
  return new TmpFsAdapter(newRoot);
133
136
  }
134
137
  async serialize(targetPath) {
138
+ var _a;
135
139
  var _stack = [];
136
140
  try {
137
- const tmpArchivePath = join(
138
- tmpdir(),
139
- `storyteller-platform-epub-${randomUUID()}`
140
- );
141
- const { promise, resolve } = Promise.withResolvers();
141
+ const tmpArchivePath = mintRootPath();
142
+ const { promise, resolve, reject } = Promise.withResolvers();
142
143
  const zipfile = new ZipFile();
143
144
  const writeStream = createWriteStream(tmpArchivePath);
144
145
  writeStream.on("close", () => {
145
146
  resolve();
146
147
  });
148
+ writeStream.on("error", reject);
149
+ zipfile.outputStream.on("error", reject);
147
150
  const stack = __using(_stack, new AsyncDisposableStack(), true);
148
151
  stack.defer(async () => {
149
152
  writeStream.close();
150
153
  await rm(tmpArchivePath, { force: true });
151
154
  });
152
155
  zipfile.outputStream.pipe(writeStream);
153
- zipfile.addBuffer(Buffer.from("application/epub+zip"), "mimetype", {
156
+ zipfile.addBuffer(Buffer.from(MediaType.EPUB.mime), "mimetype", {
154
157
  compress: false
155
158
  });
156
159
  for await (const entry of this.list()) {
157
160
  if (entry.relativePath === "mimetype") continue;
158
161
  zipfile.addFile(entry.absolutePath, entry.relativePath, {
159
- compress: !isAudioFile(entry.absolutePath)
162
+ compress: !PRECOMPRESSED_KINDS.has(
163
+ (_a = MediaType.fromPath(entry.absolutePath)) == null ? void 0 : _a.kind
164
+ )
160
165
  });
161
166
  }
162
167
  zipfile.end();
@@ -24,15 +24,15 @@ module.exports = __toCommonJS(epubcheck_exports);
24
24
  var import_node_child_process = require("node:child_process");
25
25
  var import_node_crypto = require("node:crypto");
26
26
  var import_promises = require("node:fs/promises");
27
- var import_node_os = require("node:os");
28
27
  var import_node_path = require("node:path");
29
28
  var import_node_url = require("node:url");
30
29
  var import_node_util = require("node:util");
30
+ var import_fs = require("@storyteller-platform/fs");
31
31
  const import_meta = {};
32
32
  const here = (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
33
33
  const JAR = (0, import_node_path.join)(here, "vendors", "epubcheck-5.3.0", "epubcheck.jar");
34
34
  async function epubcheck(epubFile) {
35
- const output = (0, import_node_path.join)((0, import_node_os.tmpdir)(), `epubcheck-${(0, import_node_crypto.randomUUID)()}.json`);
35
+ const output = (0, import_fs.scratchPath)(`epubcheck-${(0, import_node_crypto.randomUUID)()}.json`);
36
36
  const execFilePromise = (0, import_node_util.promisify)(import_node_child_process.execFile);
37
37
  try {
38
38
  await execFilePromise("java", [
package/dist/epubcheck.js CHANGED
@@ -2,14 +2,14 @@ import "./chunk-BIEQXUOY.js";
2
2
  import { execFile } from "node:child_process";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { readFile, unlink } from "node:fs/promises";
5
- import { tmpdir } from "node:os";
6
5
  import { dirname, join } from "node:path";
7
6
  import { fileURLToPath } from "node:url";
8
7
  import { promisify } from "node:util";
8
+ import { scratchPath } from "@storyteller-platform/fs";
9
9
  const here = dirname(fileURLToPath(import.meta.url));
10
10
  const JAR = join(here, "vendors", "epubcheck-5.3.0", "epubcheck.jar");
11
11
  async function epubcheck(epubFile) {
12
- const output = join(tmpdir(), `epubcheck-${randomUUID()}.json`);
12
+ const output = scratchPath(`epubcheck-${randomUUID()}.json`);
13
13
  const execFilePromise = promisify(execFile);
14
14
  try {
15
15
  await execFilePromise("java", [