@willwade/aac-processors 0.1.4 → 0.1.6

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 (47) hide show
  1. package/README.md +14 -0
  2. package/dist/browser/index.browser.js +15 -1
  3. package/dist/browser/processors/gridset/password.js +11 -0
  4. package/dist/browser/processors/gridset/symbols.js +3 -2
  5. package/dist/browser/processors/gridsetProcessor.js +42 -46
  6. package/dist/browser/processors/obfProcessor.js +47 -63
  7. package/dist/browser/processors/snapProcessor.js +1031 -0
  8. package/dist/browser/processors/touchchatProcessor.js +1004 -0
  9. package/dist/browser/utils/io.js +21 -1
  10. package/dist/browser/utils/sqlite.js +109 -0
  11. package/dist/browser/utils/zip.js +54 -0
  12. package/dist/browser/validation/gridsetValidator.js +21 -2
  13. package/dist/browser/validation/obfValidator.js +4 -5
  14. package/dist/browser/validation/snapValidator.js +200 -0
  15. package/dist/browser/validation/touchChatValidator.js +202 -0
  16. package/dist/index.browser.d.ts +7 -0
  17. package/dist/index.browser.js +19 -2
  18. package/dist/processors/gridset/helpers.js +3 -4
  19. package/dist/processors/gridset/index.d.ts +1 -1
  20. package/dist/processors/gridset/index.js +3 -2
  21. package/dist/processors/gridset/password.d.ts +3 -2
  22. package/dist/processors/gridset/password.js +12 -0
  23. package/dist/processors/gridset/symbols.js +2 -1
  24. package/dist/processors/gridset/wordlistHelpers.js +107 -51
  25. package/dist/processors/gridsetProcessor.js +40 -44
  26. package/dist/processors/obfProcessor.js +46 -62
  27. package/dist/processors/snapProcessor.js +60 -54
  28. package/dist/processors/touchchatProcessor.js +38 -36
  29. package/dist/utils/io.d.ts +5 -0
  30. package/dist/utils/io.js +23 -0
  31. package/dist/utils/sqlite.d.ts +21 -0
  32. package/dist/utils/sqlite.js +137 -0
  33. package/dist/utils/zip.d.ts +7 -0
  34. package/dist/utils/zip.js +80 -0
  35. package/dist/validation/gridsetValidator.js +20 -24
  36. package/dist/validation/obfValidator.js +4 -28
  37. package/docs/BROWSER_USAGE.md +2 -10
  38. package/examples/README.md +3 -75
  39. package/examples/vitedemo/README.md +17 -10
  40. package/examples/vitedemo/index.html +2 -2
  41. package/examples/vitedemo/package-lock.json +531 -1
  42. package/examples/vitedemo/package.json +7 -1
  43. package/examples/vitedemo/src/main.ts +48 -2
  44. package/examples/vitedemo/src/vite-env.d.ts +1 -0
  45. package/package.json +3 -1
  46. package/examples/browser-test-server.js +0 -81
  47. package/examples/vitedemo/QUICKSTART.md +0 -75
package/README.md CHANGED
@@ -30,6 +30,20 @@ const texts = await snap.extractTexts('board.sps');
30
30
  Browser-safe entry that avoids Node-only dependencies. It expects `Buffer`,
31
31
  `Uint8Array`, or `ArrayBuffer` inputs rather than file paths.
32
32
 
33
+ SQLite-backed formats (Snap `.sps/.spb` and TouchChat `.ce`) require a WASM
34
+ SQLite engine. Configure `sql.js` in your bundler before loading those formats:
35
+
36
+ ```ts
37
+ import { configureSqlJs, SnapProcessor } from '@willwade/aac-processors/browser';
38
+
39
+ configureSqlJs({
40
+ locateFile: (file) => new URL(`./${file}`, import.meta.url).toString(),
41
+ });
42
+
43
+ const snap = new SnapProcessor();
44
+ const tree = await snap.loadIntoTree(snapUint8Array);
45
+ ```
46
+
33
47
  ```ts
34
48
  import { GridsetProcessor } from '@willwade/aac-processors/browser';
35
49
 
@@ -6,6 +6,10 @@
6
6
  * **NOTE: Gridset .gridsetx files**
7
7
  * GridsetProcessor supports regular `.gridset` files in browser.
8
8
  * Encrypted `.gridsetx` files require Node.js for crypto operations and are not supported in browser.
9
+ *
10
+ * **NOTE: SQLite-backed formats**
11
+ * Snap (.sps/.spb) and TouchChat (.ce) require a WASM-backed SQLite engine.
12
+ * Configure `sql.js` in browser builds via `configureSqlJs()` before loading these formats.
9
13
  */
10
14
  // ===================================================================
11
15
  // CORE TYPES
@@ -20,14 +24,19 @@ export { DotProcessor } from './processors/dotProcessor';
20
24
  export { OpmlProcessor } from './processors/opmlProcessor';
21
25
  export { ObfProcessor } from './processors/obfProcessor';
22
26
  export { GridsetProcessor } from './processors/gridsetProcessor';
27
+ export { SnapProcessor } from './processors/snapProcessor';
28
+ export { TouchChatProcessor } from './processors/touchchatProcessor';
23
29
  export { ApplePanelsProcessor } from './processors/applePanelsProcessor';
24
30
  export { AstericsGridProcessor } from './processors/astericsGridProcessor';
25
31
  import { DotProcessor } from './processors/dotProcessor';
26
32
  import { OpmlProcessor } from './processors/opmlProcessor';
27
33
  import { ObfProcessor } from './processors/obfProcessor';
28
34
  import { GridsetProcessor } from './processors/gridsetProcessor';
35
+ import { SnapProcessor } from './processors/snapProcessor';
36
+ import { TouchChatProcessor } from './processors/touchchatProcessor';
29
37
  import { ApplePanelsProcessor } from './processors/applePanelsProcessor';
30
38
  import { AstericsGridProcessor } from './processors/astericsGridProcessor';
39
+ export { configureSqlJs } from './utils/sqlite';
31
40
  /**
32
41
  * Factory function to get the appropriate processor for a file extension
33
42
  * @param filePathOrExtension - File path or extension (e.g., '.dot', '/path/to/file.obf')
@@ -48,6 +57,11 @@ export function getProcessor(filePathOrExtension) {
48
57
  return new ObfProcessor();
49
58
  case '.gridset':
50
59
  return new GridsetProcessor();
60
+ case '.spb':
61
+ case '.sps':
62
+ return new SnapProcessor();
63
+ case '.ce':
64
+ return new TouchChatProcessor();
51
65
  case '.plist':
52
66
  return new ApplePanelsProcessor();
53
67
  case '.grd':
@@ -61,7 +75,7 @@ export function getProcessor(filePathOrExtension) {
61
75
  * @returns Array of supported file extensions
62
76
  */
63
77
  export function getSupportedExtensions() {
64
- return ['.dot', '.opml', '.obf', '.obz', '.gridset', '.plist', '.grd'];
78
+ return ['.dot', '.opml', '.obf', '.obz', '.gridset', '.spb', '.sps', '.ce', '.plist', '.grd'];
65
79
  }
66
80
  /**
67
81
  * Check if a file extension is supported
@@ -47,3 +47,14 @@ export function getZipEntriesWithPassword(zip, password) {
47
47
  });
48
48
  return entries;
49
49
  }
50
+ export function getZipEntriesFromAdapter(zip, password) {
51
+ if (password) {
52
+ console.warn('Zip password support is handled at the archive level for .gridsetx files.');
53
+ }
54
+ return zip.listFiles().map((entryName) => ({
55
+ name: entryName,
56
+ entryName,
57
+ dir: false,
58
+ getData: () => zip.readFile(entryName),
59
+ }));
60
+ }
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * This module provides symbol resolution and metadata extraction.
14
14
  */
15
- import { getFs, getPath } from '../../utils/io';
15
+ import { getFs, getNodeRequire, getPath } from '../../utils/io';
16
16
  /**
17
17
  * Default Grid 3 installation paths by platform
18
18
  */
@@ -76,8 +76,9 @@ function getAdmZip() {
76
76
  if (cachedAdmZip)
77
77
  return cachedAdmZip;
78
78
  try {
79
+ const nodeRequire = getNodeRequire();
79
80
  // eslint-disable-next-line @typescript-eslint/no-var-requires
80
- const module = require('adm-zip');
81
+ const module = nodeRequire('adm-zip');
81
82
  const resolved = module.default || module;
82
83
  cachedAdmZip = resolved;
83
84
  return resolved;
@@ -3,7 +3,7 @@ import { AACTree, AACPage, AACButton, AACSemanticCategory, AACSemanticIntent, }
3
3
  import { XMLParser, XMLBuilder } from 'fast-xml-parser';
4
4
  import { resolveGrid3CellImage } from './gridset/resolver';
5
5
  import { extractAllButtonsForTranslation, validateTranslationResults, } from '../utilities/translation/translationProcessor';
6
- import { getZipEntriesWithPassword, resolveGridsetPassword } from './gridset/password';
6
+ import { getZipEntriesFromAdapter, resolveGridsetPassword, } from './gridset/password';
7
7
  import { decryptGridsetEntry } from './gridset/crypto';
8
8
  import { GridsetValidator } from '../validation/gridsetValidator';
9
9
  // New imports for enhanced Grid 3 support
@@ -13,32 +13,8 @@ import { parseSymbolReference } from './gridset/symbols';
13
13
  import { isSymbolLibraryReference } from './gridset/resolver';
14
14
  import { generateCloneId } from '../utilities/analytics/utils/idGenerator';
15
15
  import { translateWithSymbols, extractSymbolsFromButton } from './gridset/symbolAlignment';
16
- import { readBinaryFromInput, decodeText, writeBinaryToPath } from '../utils/io';
17
- let JSZipModule;
18
- async function getJSZip() {
19
- if (!JSZipModule) {
20
- try {
21
- // Try ES module import first (browser/Vite)
22
- const module = await import('jszip');
23
- JSZipModule = module.default || module;
24
- }
25
- catch (error) {
26
- // Fall back to CommonJS require (Node.js)
27
- try {
28
- // eslint-disable-next-line @typescript-eslint/no-var-requires
29
- const module = require('jszip');
30
- JSZipModule = module.default || module;
31
- }
32
- catch (err2) {
33
- throw new Error('Zip handling requires JSZip in this environment.');
34
- }
35
- }
36
- }
37
- if (!JSZipModule) {
38
- throw new Error('Zip handling requires JSZip in this environment.');
39
- }
40
- return JSZipModule;
41
- }
16
+ import { readBinaryFromInput, decodeText, writeBinaryToPath, getNodeRequire, isNodeRuntime, } from '../utils/io';
17
+ import { openZipFromInput } from '../utils/zip';
42
18
  class GridsetProcessor extends BaseProcessor {
43
19
  constructor(options) {
44
20
  super(options);
@@ -411,17 +387,15 @@ class GridsetProcessor extends BaseProcessor {
411
387
  }
412
388
  async loadIntoTree(filePathOrBuffer) {
413
389
  const tree = new AACTree();
414
- let zip;
390
+ let zipResult;
415
391
  try {
416
- const JSZip = await getJSZip();
417
- const zipInput = readBinaryFromInput(filePathOrBuffer);
418
- zip = await JSZip.loadAsync(zipInput);
392
+ zipResult = await openZipFromInput(readBinaryFromInput(filePathOrBuffer));
419
393
  }
420
394
  catch (error) {
421
395
  throw new Error(`Invalid ZIP file format: ${error.message}`);
422
396
  }
423
397
  const password = this.getGridsetPassword(filePathOrBuffer);
424
- const entries = getZipEntriesWithPassword(zip, password);
398
+ const entries = getZipEntriesFromAdapter(zipResult.zip, password);
425
399
  const parser = new XMLParser({ ignoreAttributes: false });
426
400
  const isEncryptedArchive = typeof filePathOrBuffer === 'string' && filePathOrBuffer.toLowerCase().endsWith('.gridsetx');
427
401
  const encryptedContentPassword = this.getGridsetPassword(filePathOrBuffer);
@@ -432,14 +406,11 @@ class GridsetProcessor extends BaseProcessor {
432
406
  passwordProtected: !!password,
433
407
  };
434
408
  const readEntryBuffer = async (entry) => {
435
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument
436
409
  const raw = await entry.getData();
437
410
  if (!isEncryptedArchive) {
438
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
439
411
  return raw;
440
412
  }
441
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return
442
- return decryptGridsetEntry(raw, encryptedContentPassword);
413
+ return decryptGridsetEntry(Buffer.from(raw), encryptedContentPassword);
443
414
  };
444
415
  // Parse FileMap.xml if present to index dynamic files per grid
445
416
  const fileMapIndex = new Map();
@@ -759,7 +730,7 @@ class GridsetProcessor extends BaseProcessor {
759
730
  const gridEntryPath = entry.entryName.replace(/\\/g, '/');
760
731
  const baseDir = gridEntryPath.replace(/\/grid\.xml$/, '/');
761
732
  const dynamicFiles = fileMapIndex.get(gridEntryPath) || [];
762
- const resolvedImageEntry = resolveGrid3CellImage(zip, {
733
+ const resolvedImageEntry = resolveGrid3CellImage(null, {
763
734
  baseDir,
764
735
  imageName: declaredImageName,
765
736
  x: cellX + 1,
@@ -1610,11 +1581,36 @@ class GridsetProcessor extends BaseProcessor {
1610
1581
  return readBinaryFromInput(outputPath);
1611
1582
  }
1612
1583
  async saveFromTree(tree, outputPath) {
1613
- const JSZip = await getJSZip();
1614
- const zip = new JSZip();
1584
+ const useNodeZip = isNodeRuntime();
1585
+ let addText;
1586
+ let addBinary;
1587
+ let finalizeZip;
1588
+ if (useNodeZip) {
1589
+ const AdmZip = getNodeRequire()('adm-zip');
1590
+ const zip = new AdmZip();
1591
+ addText = (entryPath, content) => {
1592
+ zip.addFile(entryPath, Buffer.from(content, 'utf8'));
1593
+ };
1594
+ addBinary = (entryPath, content) => {
1595
+ zip.addFile(entryPath, Buffer.from(content));
1596
+ };
1597
+ finalizeZip = () => Promise.resolve(zip.toBuffer());
1598
+ }
1599
+ else {
1600
+ const module = await import('jszip');
1601
+ const JSZip = module.default || module;
1602
+ const zip = new JSZip();
1603
+ addText = (entryPath, content) => {
1604
+ zip.file(entryPath, content, { binary: false });
1605
+ };
1606
+ addBinary = (entryPath, content) => {
1607
+ zip.file(entryPath, content);
1608
+ };
1609
+ finalizeZip = async () => zip.generateAsync({ type: 'uint8array' });
1610
+ }
1615
1611
  if (Object.keys(tree.pages).length === 0) {
1616
1612
  // Create empty zip for empty tree
1617
- const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
1613
+ const zipBuffer = await finalizeZip();
1618
1614
  writeBinaryToPath(outputPath, zipBuffer);
1619
1615
  return;
1620
1616
  }
@@ -1687,7 +1683,7 @@ class GridsetProcessor extends BaseProcessor {
1687
1683
  suppressEmptyNode: true,
1688
1684
  });
1689
1685
  const settingsXmlContent = settingsBuilder.build(settingsData);
1690
- zip.file('Settings0/settings.xml', settingsXmlContent, { binary: false });
1686
+ addText('Settings0/settings.xml', settingsXmlContent);
1691
1687
  // Create Settings0/Styles/style.xml if there are styles
1692
1688
  if (uniqueStyles.size > 0) {
1693
1689
  const stylesArray = Array.from(uniqueStyles.values()).map(({ id, style }) => {
@@ -1720,7 +1716,7 @@ class GridsetProcessor extends BaseProcessor {
1720
1716
  indentBy: ' ',
1721
1717
  });
1722
1718
  const styleXmlContent = styleBuilder.build(styleData);
1723
- zip.file('Settings0/Styles/styles.xml', styleXmlContent, { binary: false });
1719
+ addText('Settings0/Styles/styles.xml', styleXmlContent);
1724
1720
  }
1725
1721
  // Collect grid file paths for FileMap.xml
1726
1722
  const gridFilePaths = [];
@@ -1861,14 +1857,14 @@ class GridsetProcessor extends BaseProcessor {
1861
1857
  // Add to zip in Grids folder with proper Grid3 naming
1862
1858
  const gridPath = `Grids/${page.name || page.id}/grid.xml`;
1863
1859
  gridFilePaths.push(gridPath);
1864
- zip.file(gridPath, xmlContent, { binary: false });
1860
+ addText(gridPath, xmlContent);
1865
1861
  });
1866
1862
  // Write image files to ZIP
1867
1863
  buttonImages.forEach((imgData) => {
1868
1864
  if (imgData.imageData && imgData.imageData.length > 0) {
1869
1865
  // Create image path in the grid's directory
1870
1866
  const imagePath = `Grids/${imgData.pageName}/${imgData.x}-${imgData.y}-0-text-0.${imgData.ext}`;
1871
- zip.file(imagePath, imgData.imageData);
1867
+ addBinary(imagePath, imgData.imageData);
1872
1868
  }
1873
1869
  });
1874
1870
  // Create FileMap.xml to map all grid files with their dynamic image files
@@ -1907,9 +1903,9 @@ class GridsetProcessor extends BaseProcessor {
1907
1903
  indentBy: ' ',
1908
1904
  });
1909
1905
  const fileMapXmlContent = fileMapBuilder.build(fileMapData);
1910
- zip.file('FileMap.xml', fileMapXmlContent, { binary: false });
1906
+ addText('FileMap.xml', fileMapXmlContent);
1911
1907
  // Write the zip file
1912
- const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
1908
+ const zipBuffer = await finalizeZip();
1913
1909
  writeBinaryToPath(outputPath, zipBuffer);
1914
1910
  }
1915
1911
  // Helper method to calculate column definitions based on page layout
@@ -2,32 +2,8 @@ import { BaseProcessor, } from '../core/baseProcessor';
2
2
  import { AACTree, AACPage, AACButton, AACSemanticCategory, AACSemanticIntent, } from '../core/treeStructure';
3
3
  import { generateCloneId } from '../utilities/analytics/utils/idGenerator';
4
4
  import { extractAllButtonsForTranslation, validateTranslationResults, } from '../utilities/translation/translationProcessor';
5
- import { readBinaryFromInput, readTextFromInput, writeTextToPath, encodeBase64, } from '../utils/io';
6
- let JSZipModuleObf;
7
- async function getJSZipObf() {
8
- if (!JSZipModuleObf) {
9
- try {
10
- // Try ES module import first (browser/Vite)
11
- const module = await import('jszip');
12
- JSZipModuleObf = module.default || module;
13
- }
14
- catch (error) {
15
- // Fall back to CommonJS require (Node.js)
16
- try {
17
- // eslint-disable-next-line @typescript-eslint/no-var-requires
18
- const module = require('jszip');
19
- JSZipModuleObf = module.default || module;
20
- }
21
- catch (err2) {
22
- throw new Error('Zip handling requires JSZip in this environment.');
23
- }
24
- }
25
- }
26
- if (!JSZipModuleObf) {
27
- throw new Error('Zip handling requires JSZip in this environment.');
28
- }
29
- return JSZipModuleObf;
30
- }
5
+ import { readBinaryFromInput, readTextFromInput, writeTextToPath, encodeBase64, decodeText, getNodeRequire, isNodeRuntime, } from '../utils/io';
6
+ import { openZipFromInput } from '../utils/zip';
31
7
  const OBF_FORMAT_VERSION = 'open-board-0.1';
32
8
  /**
33
9
  * Map OBF hidden value to AAC standard visibility
@@ -65,10 +41,12 @@ class ObfProcessor extends BaseProcessor {
65
41
  ].filter(Boolean);
66
42
  for (const imagePath of possiblePaths) {
67
43
  try {
68
- const file = this.zipFile.file(imagePath);
69
- if (file) {
70
- const buffer = await file.async('nodebuffer');
71
- return buffer;
44
+ const buffer = await this.zipFile.readFile(imagePath);
45
+ if (buffer) {
46
+ if (typeof Buffer !== 'undefined') {
47
+ return Buffer.from(buffer);
48
+ }
49
+ return null;
72
50
  }
73
51
  }
74
52
  catch (err) {
@@ -102,9 +80,8 @@ class ObfProcessor extends BaseProcessor {
102
80
  ].filter(Boolean);
103
81
  for (const imagePath of possiblePaths) {
104
82
  try {
105
- const file = this.zipFile.file(imagePath);
106
- if (file) {
107
- const buffer = await file.async('uint8array');
83
+ const buffer = await this.zipFile.readFile(imagePath);
84
+ if (buffer) {
108
85
  const contentType = imageData.content_type ||
109
86
  this.getMimeTypeFromFilename(imagePath);
110
87
  const dataUrl = `data:${contentType};base64,${encodeBase64(buffer)}`;
@@ -407,36 +384,28 @@ class ObfProcessor extends BaseProcessor {
407
384
  if (!isLikelyZip(filePathOrBuffer)) {
408
385
  throw new Error('Invalid OBF content: not JSON and not ZIP');
409
386
  }
410
- const JSZip = await getJSZipObf();
411
- let zip;
412
387
  try {
413
- const zipInput = readBinaryFromInput(filePathOrBuffer);
414
- zip = await JSZip.loadAsync(zipInput);
388
+ const zipResult = await openZipFromInput(filePathOrBuffer);
389
+ this.zipFile = zipResult.zip;
415
390
  }
416
391
  catch (err) {
417
- console.error('[OBF] Error loading ZIP with JSZip:', err);
392
+ console.error('[OBF] Error loading ZIP:', err);
418
393
  throw err;
419
394
  }
420
395
  // Store the ZIP file reference for image extraction
421
- this.zipFile = zip;
422
396
  this.imageCache.clear(); // Clear cache for new file
423
397
  console.log('[OBF] Detected zip archive, extracting .obf files');
424
398
  // Collect all .obf entries
425
- const obfEntries = [];
426
- zip.forEach((relativePath, file) => {
427
- if (file.dir)
428
- return;
429
- if (relativePath.toLowerCase().endsWith('.obf')) {
430
- obfEntries.push({ name: relativePath, file });
431
- }
432
- });
399
+ const obfEntries = this.zipFile
400
+ .listFiles()
401
+ .filter((name) => name.toLowerCase().endsWith('.obf'));
433
402
  // Process each .obf entry
434
- for (const entry of obfEntries) {
403
+ for (const entryName of obfEntries) {
435
404
  try {
436
- const content = await entry.file.async('string');
437
- const boardData = tryParseObfJson(content);
405
+ const content = await this.zipFile.readFile(entryName);
406
+ const boardData = tryParseObfJson(decodeText(content));
438
407
  if (boardData) {
439
- const page = await this.processBoard(boardData, entry.name);
408
+ const page = await this.processBoard(boardData, entryName);
440
409
  tree.addPage(page);
441
410
  // Set metadata if not already set (use first board as reference)
442
411
  if (!tree.metadata.format) {
@@ -453,11 +422,11 @@ class ObfProcessor extends BaseProcessor {
453
422
  }
454
423
  }
455
424
  else {
456
- console.warn('[OBF] Skipped entry (not valid OBF JSON):', entry.name);
425
+ console.warn('[OBF] Skipped entry (not valid OBF JSON):', entryName);
457
426
  }
458
427
  }
459
428
  catch (err) {
460
- console.warn('[OBF] Error processing entry:', entry.name, err);
429
+ console.warn('[OBF] Error processing entry:', entryName, err);
461
430
  }
462
431
  }
463
432
  return tree;
@@ -583,16 +552,31 @@ class ObfProcessor extends BaseProcessor {
583
552
  }
584
553
  else {
585
554
  // Save as OBZ (zip with multiple OBF files)
586
- const JSZip = await getJSZipObf();
587
- const zip = new JSZip();
588
- Object.values(tree.pages).forEach((page) => {
589
- const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
590
- const obfContent = JSON.stringify(obfBoard, null, 2);
591
- zip.file(`${page.id}.obf`, obfContent);
592
- });
593
- const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
594
- const { writeBinaryToPath } = await import('../utils/io');
595
- writeBinaryToPath(outputPath, zipBuffer);
555
+ if (isNodeRuntime()) {
556
+ const AdmZip = getNodeRequire()('adm-zip');
557
+ const zip = new AdmZip();
558
+ Object.values(tree.pages).forEach((page) => {
559
+ const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
560
+ const obfContent = JSON.stringify(obfBoard, null, 2);
561
+ zip.addFile(`${page.id}.obf`, Buffer.from(obfContent, 'utf8'));
562
+ });
563
+ const zipBuffer = zip.toBuffer();
564
+ const { writeBinaryToPath } = await import('../utils/io');
565
+ writeBinaryToPath(outputPath, zipBuffer);
566
+ }
567
+ else {
568
+ const module = await import('jszip');
569
+ const JSZip = module.default || module;
570
+ const zip = new JSZip();
571
+ Object.values(tree.pages).forEach((page) => {
572
+ const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
573
+ const obfContent = JSON.stringify(obfBoard, null, 2);
574
+ zip.file(`${page.id}.obf`, obfContent);
575
+ });
576
+ const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
577
+ const { writeBinaryToPath } = await import('../utils/io');
578
+ writeBinaryToPath(outputPath, zipBuffer);
579
+ }
596
580
  }
597
581
  }
598
582
  /**