@storyteller-platform/epub 0.4.0 → 0.4.2

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/index.cjs CHANGED
@@ -5,6 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
9
+ var __typeError = (msg) => {
10
+ throw TypeError(msg);
11
+ };
8
12
  var __export = (target, all) => {
9
13
  for (var name in all)
10
14
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -26,6 +30,47 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
30
  mod
27
31
  ));
28
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+ var __using = (stack, value, async) => {
34
+ if (value != null) {
35
+ if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
36
+ var dispose, inner;
37
+ if (async) dispose = value[__knownSymbol("asyncDispose")];
38
+ if (dispose === void 0) {
39
+ dispose = value[__knownSymbol("dispose")];
40
+ if (async) inner = dispose;
41
+ }
42
+ if (typeof dispose !== "function") __typeError("Object not disposable");
43
+ if (inner) dispose = function() {
44
+ try {
45
+ inner.call(this);
46
+ } catch (e) {
47
+ return Promise.reject(e);
48
+ }
49
+ };
50
+ stack.push([async, dispose, value]);
51
+ } else if (async) {
52
+ stack.push([async]);
53
+ }
54
+ return value;
55
+ };
56
+ var __callDispose = (stack, error, hasError) => {
57
+ var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
58
+ return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
59
+ };
60
+ var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
61
+ var next = (it) => {
62
+ while (it = stack.pop()) {
63
+ try {
64
+ var result = it[1] && it[1].call(it[2]);
65
+ if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
66
+ } catch (e) {
67
+ fail(e);
68
+ }
69
+ }
70
+ if (hasError) throw error;
71
+ };
72
+ return next();
73
+ };
29
74
  var index_exports = {};
30
75
  __export(index_exports, {
31
76
  Epub: () => Epub
@@ -35,17 +80,41 @@ var import_node_crypto = require("node:crypto");
35
80
  var import_node_fs = require("node:fs");
36
81
  var import_promises = require("node:fs/promises");
37
82
  var import_node_os = require("node:os");
38
- var import_fslib = require("@yarnpkg/fslib");
39
- var import_libzip = require("@yarnpkg/libzip");
83
+ var util = __toESM(require("node:util"), 1);
40
84
  var import_async_mutex = require("async-mutex");
41
85
  var import_fast_xml_parser = require("fast-xml-parser");
42
86
  var import_mem = __toESM(require("mem"), 1);
43
87
  var import_mime_types = require("mime-types");
44
88
  var import_nanoid = require("nanoid");
89
+ var import_yauzl = __toESM(require("yauzl"), 1);
90
+ var import_yazl = require("yazl");
91
+ var import_path = require("@storyteller-platform/path");
92
+ function promisify(api) {
93
+ return function(arg, options) {
94
+ return new Promise(function(resolve2, reject) {
95
+ api(arg, options, function(err, response) {
96
+ if (err) {
97
+ reject(err);
98
+ return;
99
+ }
100
+ resolve2(response);
101
+ });
102
+ });
103
+ };
104
+ }
105
+ const unzipFromBuffer = promisify(
106
+ (arg, options, callback) => {
107
+ import_yauzl.default.fromBuffer(arg, options, callback);
108
+ }
109
+ );
110
+ const unzipFromPath = promisify(
111
+ (arg, options, callback) => {
112
+ import_yauzl.default.open(arg, options, callback);
113
+ }
114
+ );
45
115
  class Epub {
46
- constructor(zipFs, zipPath, inputPath) {
47
- this.zipFs = zipFs;
48
- this.zipPath = zipPath;
116
+ constructor(extractPath, inputPath) {
117
+ this.extractPath = extractPath;
49
118
  this.inputPath = inputPath;
50
119
  this.readXhtmlItemContents = (0, import_mem.default)(
51
120
  this.readXhtmlItemContents.bind(this),
@@ -222,11 +291,10 @@ ${JSON.stringify(element, null, 2)}`
222
291
  creators,
223
292
  contributors
224
293
  }, additionalMetadata = []) {
225
- const tmp = import_fslib.npath.join(
294
+ const extractPath = (0, import_path.join)(
226
295
  (0, import_node_os.tmpdir)(),
227
- `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}.epub`
296
+ `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`
228
297
  );
229
- const zipFs = new import_libzip.ZipFS(import_fslib.npath.toPortablePath(tmp), { create: true });
230
298
  const encoder = new TextEncoder();
231
299
  const container = encoder.encode(`<?xml version="1.0"?>
232
300
  <container>
@@ -235,11 +303,8 @@ ${JSON.stringify(element, null, 2)}`
235
303
  </rootfiles>
236
304
  </container>
237
305
  `);
238
- await zipFs.mkdirpPromise(import_fslib.ppath.join(import_fslib.PortablePath.root, "META-INF"));
239
- await zipFs.writeFilePromise(
240
- import_fslib.ppath.join(import_fslib.PortablePath.root, "META-INF", "container.xml"),
241
- container
242
- );
306
+ await (0, import_promises.mkdir)((0, import_path.join)(extractPath, "META-INF"), { recursive: true });
307
+ await (0, import_promises.writeFile)((0, import_path.join)(extractPath, "META-INF", "container.xml"), container);
243
308
  const packageDocument = encoder.encode(`<?xml version="1.0"?>
244
309
  <package unique-identifier="pub-id" dir="${language.textInfo.direction}" xml:lang=${language.toString()} version="3.0">
245
310
  <metadata>
@@ -250,12 +315,9 @@ ${JSON.stringify(element, null, 2)}`
250
315
  </spine>
251
316
  </package>
252
317
  `);
253
- await zipFs.mkdirpPromise(import_fslib.ppath.join(import_fslib.PortablePath.root, "OEBPS"));
254
- await zipFs.writeFilePromise(
255
- import_fslib.ppath.join(import_fslib.PortablePath.root, "OEBPS", "content.opf"),
256
- packageDocument
257
- );
258
- const epub = new this(zipFs, tmp, path);
318
+ await (0, import_promises.mkdir)((0, import_path.join)(extractPath, "OEBPS"));
319
+ await (0, import_promises.writeFile)((0, import_path.join)(extractPath, "OEBPS", "content.opf"), packageDocument);
320
+ const epub = new this(extractPath, path);
259
321
  const metadata = [
260
322
  {
261
323
  id: "pub-id",
@@ -293,35 +355,66 @@ ${JSON.stringify(element, null, 2)}`
293
355
  */
294
356
  // eslint-disable-next-line @typescript-eslint/require-await
295
357
  static async from(pathOrData) {
296
- const tmp = import_fslib.npath.join(
297
- (0, import_node_os.tmpdir)(),
298
- `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}.epub`
299
- );
300
- if (typeof pathOrData !== "string") {
301
- await (0, import_promises.writeFile)(tmp, pathOrData);
302
- } else {
303
- await (0, import_promises.cp)(pathOrData, tmp);
358
+ var _stack = [];
359
+ try {
360
+ const extractPath = (0, import_path.join)(
361
+ (0, import_node_os.tmpdir)(),
362
+ `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}.epub`
363
+ );
364
+ const zipfile = typeof pathOrData === "string" ? await unzipFromPath(pathOrData, { lazyEntries: true }) : await unzipFromBuffer(Buffer.from(pathOrData), { lazyEntries: true });
365
+ const stack = __using(_stack, new DisposableStack());
366
+ stack.defer(() => {
367
+ zipfile.close();
368
+ });
369
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
370
+ zipfile.on("end", () => {
371
+ resolve2();
372
+ });
373
+ const openReadStream = util.promisify(zipfile.openReadStream.bind(zipfile));
374
+ zipfile.readEntry();
375
+ zipfile.on("entry", async (entry) => {
376
+ if (entry.fileName.endsWith("/")) {
377
+ zipfile.readEntry();
378
+ } else {
379
+ const writePath = (0, import_path.join)(extractPath, entry.fileName);
380
+ const readStream = await openReadStream(entry);
381
+ await (0, import_promises.mkdir)((0, import_path.dirname)(writePath), { recursive: true });
382
+ await new Promise((resolvePipe) => {
383
+ const writePath2 = (0, import_path.join)(extractPath, entry.fileName);
384
+ const writeStream = (0, import_node_fs.createWriteStream)(writePath2);
385
+ writeStream.on("finish", () => {
386
+ resolvePipe();
387
+ });
388
+ readStream.pipe(writeStream);
389
+ }).finally(() => {
390
+ zipfile.readEntry();
391
+ });
392
+ }
393
+ });
394
+ await promise;
395
+ return new this(
396
+ extractPath,
397
+ typeof pathOrData === "string" ? pathOrData : void 0
398
+ );
399
+ } catch (_) {
400
+ var _error = _, _hasError = true;
401
+ } finally {
402
+ __callDispose(_stack, _error, _hasError);
304
403
  }
305
- const zipFs = new import_libzip.ZipFS(import_fslib.npath.toPortablePath(tmp));
306
- return new this(
307
- zipFs,
308
- tmp,
309
- typeof pathOrData === "string" ? pathOrData : void 0
310
- );
311
404
  }
312
405
  async removeEntry(href) {
313
406
  const rootfile = await this.getRootfile();
314
407
  const filename = this.resolveHref(rootfile, href);
315
- await this.zipFs.removePromise(filename);
408
+ await (0, import_promises.rm)(filename);
316
409
  }
317
410
  async getFileData(path, encoding) {
318
- return await this.zipFs.readFilePromise(path, encoding);
411
+ return await (0, import_promises.readFile)(path, encoding);
319
412
  }
320
413
  async getRootfile() {
321
414
  var _a;
322
415
  if (this.rootfile !== null) return this.rootfile;
323
416
  const containerString = await this.getFileData(
324
- import_fslib.ppath.join(import_fslib.PortablePath.root, "META-INF", "container.xml"),
417
+ (0, import_path.join)(this.extractPath, "META-INF", "container.xml"),
325
418
  "utf-8"
326
419
  );
327
420
  if (!containerString)
@@ -353,7 +446,7 @@ ${JSON.stringify(element, null, 2)}`
353
446
  throw new Error(
354
447
  "Failed to parse EPUB container.xml: Found no rootfile element"
355
448
  );
356
- this.rootfile = import_fslib.ppath.resolve(import_fslib.PortablePath.root, fullPath);
449
+ this.rootfile = (0, import_path.resolve)(this.extractPath, fullPath);
357
450
  return this.rootfile;
358
451
  }
359
452
  migratePackageDocument(packageDocument) {
@@ -836,7 +929,11 @@ ${JSON.stringify(element, null, 2)}`
836
929
  if (!primaryLanguage) return null;
837
930
  const locale = primaryLanguage.value;
838
931
  if (!locale || locale.toLowerCase() === "und") return null;
839
- return new Intl.Locale(locale);
932
+ try {
933
+ return new Intl.Locale(locale);
934
+ } catch {
935
+ return null;
936
+ }
840
937
  }
841
938
  /**
842
939
  * Update the Epub's language metadata entry.
@@ -1476,8 +1573,8 @@ ${JSON.stringify(element, null, 2)}`
1476
1573
  * Returns a Zip Entry path for an HREF
1477
1574
  */
1478
1575
  resolveHref(from, href) {
1479
- const startPath = import_fslib.ppath.dirname(from);
1480
- return import_fslib.ppath.resolve(import_fslib.PortablePath.root, startPath, href);
1576
+ const startPath = (0, import_path.dirname)(from);
1577
+ return (0, import_path.resolve)(this.extractPath, startPath, href);
1481
1578
  }
1482
1579
  async readItemContents(id, encoding) {
1483
1580
  const rootfile = await this.getRootfile();
@@ -1523,8 +1620,8 @@ ${JSON.stringify(element, null, 2)}`
1523
1620
  return Epub.getXhtmlTextContent(body);
1524
1621
  }
1525
1622
  async writeEntryContents(path, contents, encoding) {
1526
- await this.zipFs.mkdirpPromise(import_fslib.ppath.dirname(path));
1527
- await this.zipFs.writeFilePromise(path, contents, encoding);
1623
+ await (0, import_promises.mkdir)((0, import_path.dirname)(path), { recursive: true });
1624
+ await (0, import_promises.writeFile)(path, contents, encoding);
1528
1625
  }
1529
1626
  async writeItemContents(id, contents, encoding) {
1530
1627
  const rootfile = await this.getRootfile();
@@ -1614,8 +1711,8 @@ ${JSON.stringify(element, null, 2)}`
1614
1711
  contents
1615
1712
  )
1616
1713
  ) : contents;
1617
- await this.zipFs.mkdirpPromise(import_fslib.ppath.dirname(filename));
1618
- await this.zipFs.writeFilePromise(filename, data);
1714
+ await (0, import_promises.mkdir)((0, import_path.dirname)(filename), { recursive: true });
1715
+ await (0, import_promises.writeFile)(filename, data);
1619
1716
  }
1620
1717
  /**
1621
1718
  * Update the manifest entry for an existing item.
@@ -1766,11 +1863,10 @@ ${JSON.stringify(element, null, 2)}`
1766
1863
  });
1767
1864
  }
1768
1865
  discardAndClose() {
1769
- this.zipFs.discardAndClose();
1770
1866
  this.rootfile = null;
1771
1867
  this.manifest = null;
1772
1868
  this.spine = null;
1773
- (0, import_node_fs.rmSync)(this.zipPath, { recursive: true, force: true });
1869
+ (0, import_node_fs.rmSync)(this.extractPath, { recursive: true, force: true });
1774
1870
  }
1775
1871
  /**
1776
1872
  * Write the current contents of the Epub to a new
@@ -1781,32 +1877,62 @@ ${JSON.stringify(element, null, 2)}`
1781
1877
  * timestamp.
1782
1878
  */
1783
1879
  async saveAndClose() {
1784
- if (!this.inputPath) {
1785
- throw new Error("In-memory EPUB files cannot be saved to disk");
1786
- }
1787
- await this.replaceMetadata(
1788
- (entry) => entry.properties["property"] === "dcterms:modified",
1789
- {
1790
- type: "meta",
1791
- properties: { property: "dcterms:modified" },
1792
- // We need UTC with integer seconds, but toISOString gives UTC with ms
1793
- value: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+/, "")
1880
+ var _stack = [];
1881
+ try {
1882
+ if (!this.inputPath) {
1883
+ throw new Error("In-memory EPUB files cannot be saved to disk");
1794
1884
  }
1795
- );
1796
- this.zipFs.level = 0;
1797
- await this.zipFs.writeFilePromise(
1798
- import_fslib.ppath.join(import_fslib.PortablePath.root, "mimetype"),
1799
- "application/epub+zip",
1800
- "utf-8"
1801
- );
1802
- this.zipFs.level = import_libzip.DEFAULT_COMPRESSION_LEVEL;
1803
- this.zipFs.saveAndClose();
1804
- await (0, import_promises.cp)(this.zipPath, this.inputPath, { force: true });
1885
+ await this.replaceMetadata(
1886
+ (entry) => entry.properties["property"] === "dcterms:modified",
1887
+ {
1888
+ type: "meta",
1889
+ properties: { property: "dcterms:modified" },
1890
+ // We need UTC with integer seconds, but toISOString gives UTC with ms
1891
+ value: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+/, "")
1892
+ }
1893
+ );
1894
+ const tmpArchivePath = (0, import_path.join)(
1895
+ (0, import_node_os.tmpdir)(),
1896
+ `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`
1897
+ );
1898
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
1899
+ const zipfile = new import_yazl.ZipFile();
1900
+ const writeStream = (0, import_node_fs.createWriteStream)(tmpArchivePath);
1901
+ writeStream.on("close", () => {
1902
+ resolve2();
1903
+ });
1904
+ const stack = __using(_stack, new AsyncDisposableStack(), true);
1905
+ stack.defer(async () => {
1906
+ writeStream.close();
1907
+ await (0, import_promises.rm)(tmpArchivePath, { force: true });
1908
+ });
1909
+ zipfile.outputStream.pipe(writeStream);
1910
+ zipfile.addBuffer(Buffer.from("application/epub+zip"), "mimetype", {
1911
+ compress: false
1912
+ });
1913
+ const entries = await (0, import_promises.readdir)(this.extractPath, {
1914
+ recursive: true,
1915
+ withFileTypes: true
1916
+ });
1917
+ for (const entry of entries) {
1918
+ if (entry.name === "mimetype" || entry.isDirectory()) continue;
1919
+ zipfile.addFile(
1920
+ (0, import_path.join)(entry.parentPath, entry.name),
1921
+ (0, import_path.join)(entry.parentPath, entry.name).replace(`${this.extractPath}/`, "")
1922
+ );
1923
+ }
1924
+ zipfile.end();
1925
+ await promise;
1926
+ await (0, import_promises.cp)(tmpArchivePath, this.inputPath);
1927
+ } catch (_) {
1928
+ var _error = _, _hasError = true;
1929
+ } finally {
1930
+ var _promise = __callDispose(_stack, _error, _hasError);
1931
+ _promise && await _promise;
1932
+ }
1805
1933
  }
1806
1934
  [Symbol.dispose]() {
1807
- if (this.zipFs.ready) {
1808
- this.discardAndClose();
1809
- }
1935
+ this.discardAndClose();
1810
1936
  }
1811
1937
  }
1812
1938
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -1,5 +1,3 @@
1
- import { NativePath } from '@yarnpkg/fslib';
2
- import { ZipFS } from '@yarnpkg/libzip';
3
1
  import { XMLParser, XMLBuilder } from 'fast-xml-parser';
4
2
 
5
3
  declare global {
@@ -105,8 +103,7 @@ type PackageElement = XmlElement<"package"> | XmlElement<"opf:package">;
105
103
  * @link https://www.w3.org/TR/epub-33/
106
104
  */
107
105
  declare class Epub {
108
- protected zipFs: ZipFS;
109
- protected zipPath: NativePath;
106
+ protected extractPath: string;
110
107
  protected inputPath: string | undefined;
111
108
  static xmlParser: XMLParser;
112
109
  static xhtmlParser: XMLParser;
@@ -167,7 +164,7 @@ declare class Epub {
167
164
  private manifest;
168
165
  private spine;
169
166
  private packageMutex;
170
- protected constructor(zipFs: ZipFS, zipPath: NativePath, inputPath: string | undefined);
167
+ protected constructor(extractPath: string, inputPath: string | undefined);
171
168
  /**
172
169
  * Construct an Epub instance, optionally beginning
173
170
  * with the provided metadata.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { NativePath } from '@yarnpkg/fslib';
2
- import { ZipFS } from '@yarnpkg/libzip';
3
1
  import { XMLParser, XMLBuilder } from 'fast-xml-parser';
4
2
 
5
3
  declare global {
@@ -105,8 +103,7 @@ type PackageElement = XmlElement<"package"> | XmlElement<"opf:package">;
105
103
  * @link https://www.w3.org/TR/epub-33/
106
104
  */
107
105
  declare class Epub {
108
- protected zipFs: ZipFS;
109
- protected zipPath: NativePath;
106
+ protected extractPath: string;
110
107
  protected inputPath: string | undefined;
111
108
  static xmlParser: XMLParser;
112
109
  static xhtmlParser: XMLParser;
@@ -167,7 +164,7 @@ declare class Epub {
167
164
  private manifest;
168
165
  private spine;
169
166
  private packageMutex;
170
- protected constructor(zipFs: ZipFS, zipPath: NativePath, inputPath: string | undefined);
167
+ protected constructor(extractPath: string, inputPath: string | undefined);
171
168
  /**
172
169
  * Construct an Epub instance, optionally beginning
173
170
  * with the provided metadata.