docxmlater 10.1.4 → 10.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 (372) hide show
  1. package/README.md +759 -754
  2. package/dist/constants/legacyCompatFlags.js +1 -1
  3. package/dist/constants/legacyCompatFlags.js.map +1 -1
  4. package/dist/constants/limits.js.map +1 -1
  5. package/dist/core/Document.d.ts +51 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +486 -471
  8. package/dist/core/Document.js.map +1 -1
  9. package/dist/core/DocumentContent.d.ts +9 -9
  10. package/dist/core/DocumentContent.d.ts.map +1 -1
  11. package/dist/core/DocumentContent.js +1 -1
  12. package/dist/core/DocumentContent.js.map +1 -1
  13. package/dist/core/DocumentGenerator.d.ts +11 -11
  14. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  15. package/dist/core/DocumentGenerator.js +251 -251
  16. package/dist/core/DocumentGenerator.js.map +1 -1
  17. package/dist/core/DocumentIdManager.js.map +1 -1
  18. package/dist/core/DocumentParser.d.ts +15 -15
  19. package/dist/core/DocumentParser.d.ts.map +1 -1
  20. package/dist/core/DocumentParser.js +2123 -2155
  21. package/dist/core/DocumentParser.js.map +1 -1
  22. package/dist/core/DocumentValidator.d.ts.map +1 -1
  23. package/dist/core/DocumentValidator.js +2 -5
  24. package/dist/core/DocumentValidator.js.map +1 -1
  25. package/dist/core/Relationship.js.map +1 -1
  26. package/dist/core/RelationshipManager.d.ts.map +1 -1
  27. package/dist/core/RelationshipManager.js +3 -3
  28. package/dist/core/RelationshipManager.js.map +1 -1
  29. package/dist/elements/AlternateContent.js.map +1 -1
  30. package/dist/elements/Bookmark.d.ts.map +1 -1
  31. package/dist/elements/Bookmark.js +3 -1
  32. package/dist/elements/Bookmark.js.map +1 -1
  33. package/dist/elements/BookmarkManager.d.ts.map +1 -1
  34. package/dist/elements/BookmarkManager.js.map +1 -1
  35. package/dist/elements/Comment.d.ts.map +1 -1
  36. package/dist/elements/Comment.js +9 -6
  37. package/dist/elements/Comment.js.map +1 -1
  38. package/dist/elements/CommentManager.d.ts.map +1 -1
  39. package/dist/elements/CommentManager.js +18 -17
  40. package/dist/elements/CommentManager.js.map +1 -1
  41. package/dist/elements/CommonTypes.d.ts +21 -21
  42. package/dist/elements/CommonTypes.d.ts.map +1 -1
  43. package/dist/elements/CommonTypes.js +56 -56
  44. package/dist/elements/CommonTypes.js.map +1 -1
  45. package/dist/elements/CustomXml.js.map +1 -1
  46. package/dist/elements/Endnote.d.ts.map +1 -1
  47. package/dist/elements/Endnote.js +6 -6
  48. package/dist/elements/Endnote.js.map +1 -1
  49. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  50. package/dist/elements/EndnoteManager.js +6 -7
  51. package/dist/elements/EndnoteManager.js.map +1 -1
  52. package/dist/elements/Field.d.ts.map +1 -1
  53. package/dist/elements/Field.js +82 -25
  54. package/dist/elements/Field.js.map +1 -1
  55. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  56. package/dist/elements/FieldHelpers.js.map +1 -1
  57. package/dist/elements/FontManager.d.ts.map +1 -1
  58. package/dist/elements/FontManager.js +1 -1
  59. package/dist/elements/FontManager.js.map +1 -1
  60. package/dist/elements/Footer.js +2 -2
  61. package/dist/elements/Footer.js.map +1 -1
  62. package/dist/elements/Footnote.d.ts.map +1 -1
  63. package/dist/elements/Footnote.js +6 -6
  64. package/dist/elements/Footnote.js.map +1 -1
  65. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  66. package/dist/elements/FootnoteManager.js +6 -7
  67. package/dist/elements/FootnoteManager.js.map +1 -1
  68. package/dist/elements/Header.js +2 -2
  69. package/dist/elements/Header.js.map +1 -1
  70. package/dist/elements/HeaderFooterManager.js.map +1 -1
  71. package/dist/elements/Hyperlink.d.ts +5 -3
  72. package/dist/elements/Hyperlink.d.ts.map +1 -1
  73. package/dist/elements/Hyperlink.js +134 -76
  74. package/dist/elements/Hyperlink.js.map +1 -1
  75. package/dist/elements/Image.d.ts.map +1 -1
  76. package/dist/elements/Image.js +238 -106
  77. package/dist/elements/Image.js.map +1 -1
  78. package/dist/elements/ImageManager.d.ts.map +1 -1
  79. package/dist/elements/ImageManager.js +1 -1
  80. package/dist/elements/ImageManager.js.map +1 -1
  81. package/dist/elements/ImageRun.js +1 -1
  82. package/dist/elements/ImageRun.js.map +1 -1
  83. package/dist/elements/MathElement.js.map +1 -1
  84. package/dist/elements/Paragraph.d.ts +24 -24
  85. package/dist/elements/Paragraph.d.ts.map +1 -1
  86. package/dist/elements/Paragraph.js +181 -188
  87. package/dist/elements/Paragraph.js.map +1 -1
  88. package/dist/elements/PreservedElement.js.map +1 -1
  89. package/dist/elements/PropertyChangeTypes.d.ts.map +1 -1
  90. package/dist/elements/PropertyChangeTypes.js +6 -6
  91. package/dist/elements/PropertyChangeTypes.js.map +1 -1
  92. package/dist/elements/RangeMarker.d.ts.map +1 -1
  93. package/dist/elements/RangeMarker.js.map +1 -1
  94. package/dist/elements/Revision.d.ts.map +1 -1
  95. package/dist/elements/Revision.js +4 -5
  96. package/dist/elements/Revision.js.map +1 -1
  97. package/dist/elements/RevisionContent.js.map +1 -1
  98. package/dist/elements/RevisionManager.d.ts.map +1 -1
  99. package/dist/elements/RevisionManager.js +40 -48
  100. package/dist/elements/RevisionManager.js.map +1 -1
  101. package/dist/elements/Run.d.ts +16 -16
  102. package/dist/elements/Run.d.ts.map +1 -1
  103. package/dist/elements/Run.js +256 -238
  104. package/dist/elements/Run.js.map +1 -1
  105. package/dist/elements/Section.d.ts.map +1 -1
  106. package/dist/elements/Section.js +36 -11
  107. package/dist/elements/Section.js.map +1 -1
  108. package/dist/elements/Shape.d.ts.map +1 -1
  109. package/dist/elements/Shape.js.map +1 -1
  110. package/dist/elements/StructuredDocumentTag.d.ts +6 -6
  111. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  112. package/dist/elements/StructuredDocumentTag.js +99 -104
  113. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  114. package/dist/elements/Table.d.ts +11 -11
  115. package/dist/elements/Table.d.ts.map +1 -1
  116. package/dist/elements/Table.js +102 -107
  117. package/dist/elements/Table.js.map +1 -1
  118. package/dist/elements/TableCell.d.ts +10 -10
  119. package/dist/elements/TableCell.d.ts.map +1 -1
  120. package/dist/elements/TableCell.js +105 -106
  121. package/dist/elements/TableCell.js.map +1 -1
  122. package/dist/elements/TableGridChange.d.ts.map +1 -1
  123. package/dist/elements/TableGridChange.js.map +1 -1
  124. package/dist/elements/TableOfContents.d.ts.map +1 -1
  125. package/dist/elements/TableOfContents.js +4 -4
  126. package/dist/elements/TableOfContents.js.map +1 -1
  127. package/dist/elements/TableOfContentsElement.js.map +1 -1
  128. package/dist/elements/TableRow.d.ts.map +1 -1
  129. package/dist/elements/TableRow.js +13 -6
  130. package/dist/elements/TableRow.js.map +1 -1
  131. package/dist/elements/TextBox.d.ts.map +1 -1
  132. package/dist/elements/TextBox.js +3 -5
  133. package/dist/elements/TextBox.js.map +1 -1
  134. package/dist/formatting/AbstractNumbering.d.ts +4 -4
  135. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  136. package/dist/formatting/AbstractNumbering.js +54 -49
  137. package/dist/formatting/AbstractNumbering.js.map +1 -1
  138. package/dist/formatting/NumberingInstance.d.ts.map +1 -1
  139. package/dist/formatting/NumberingInstance.js +1 -3
  140. package/dist/formatting/NumberingInstance.js.map +1 -1
  141. package/dist/formatting/NumberingLevel.d.ts +5 -5
  142. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  143. package/dist/formatting/NumberingLevel.js +119 -125
  144. package/dist/formatting/NumberingLevel.js.map +1 -1
  145. package/dist/formatting/NumberingManager.d.ts +1 -0
  146. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  147. package/dist/formatting/NumberingManager.js +27 -9
  148. package/dist/formatting/NumberingManager.js.map +1 -1
  149. package/dist/formatting/Style.d.ts +11 -11
  150. package/dist/formatting/Style.d.ts.map +1 -1
  151. package/dist/formatting/Style.js +219 -247
  152. package/dist/formatting/Style.js.map +1 -1
  153. package/dist/formatting/StylesManager.d.ts +2 -2
  154. package/dist/formatting/StylesManager.d.ts.map +1 -1
  155. package/dist/formatting/StylesManager.js +96 -102
  156. package/dist/formatting/StylesManager.js.map +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts +1 -1
  158. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  159. package/dist/helpers/CleanupHelper.js +6 -6
  160. package/dist/helpers/CleanupHelper.js.map +1 -1
  161. package/dist/images/ImageOptimizer.js +7 -7
  162. package/dist/images/ImageOptimizer.js.map +1 -1
  163. package/dist/index.d.ts +9 -9
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js.map +1 -1
  166. package/dist/managers/DrawingManager.js.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  168. package/dist/tracking/DocumentTrackingContext.js +23 -7
  169. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  170. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  171. package/dist/tracking/TrackingContext.js.map +1 -1
  172. package/dist/types/compatibility-types.js.map +1 -1
  173. package/dist/types/formatting.js.map +1 -1
  174. package/dist/types/list-types.d.ts +6 -6
  175. package/dist/types/list-types.js.map +1 -1
  176. package/dist/types/settings-types.js.map +1 -1
  177. package/dist/types/styleConfig.d.ts +2 -2
  178. package/dist/types/styleConfig.js.map +1 -1
  179. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  180. package/dist/utils/ChangelogGenerator.js +97 -101
  181. package/dist/utils/ChangelogGenerator.js.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js +1 -1
  184. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  186. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  187. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  188. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  189. package/dist/utils/MoveOperationHelper.js +1 -1
  190. package/dist/utils/MoveOperationHelper.js.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  192. package/dist/utils/RevisionAwareProcessor.js +2 -4
  193. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  194. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  195. package/dist/utils/RevisionWalker.js +4 -12
  196. package/dist/utils/RevisionWalker.js.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  198. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  199. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  200. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  201. package/dist/utils/ShadingResolver.js +1 -1
  202. package/dist/utils/ShadingResolver.js.map +1 -1
  203. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  204. package/dist/utils/acceptRevisions.js +23 -12
  205. package/dist/utils/acceptRevisions.js.map +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  207. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  208. package/dist/utils/cnfStyleDecoder.js +40 -40
  209. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  210. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  211. package/dist/utils/corruptionDetection.js.map +1 -1
  212. package/dist/utils/dateFormatting.js.map +1 -1
  213. package/dist/utils/deepClone.js +1 -1
  214. package/dist/utils/deepClone.js.map +1 -1
  215. package/dist/utils/diagnostics.d.ts.map +1 -1
  216. package/dist/utils/diagnostics.js +1 -1
  217. package/dist/utils/diagnostics.js.map +1 -1
  218. package/dist/utils/errorHandling.js.map +1 -1
  219. package/dist/utils/formatting.d.ts.map +1 -1
  220. package/dist/utils/formatting.js +10 -2
  221. package/dist/utils/formatting.js.map +1 -1
  222. package/dist/utils/list-detection.d.ts +2 -2
  223. package/dist/utils/list-detection.d.ts.map +1 -1
  224. package/dist/utils/list-detection.js +21 -23
  225. package/dist/utils/list-detection.js.map +1 -1
  226. package/dist/utils/logger.d.ts.map +1 -1
  227. package/dist/utils/logger.js +12 -7
  228. package/dist/utils/logger.js.map +1 -1
  229. package/dist/utils/parsingHelpers.js.map +1 -1
  230. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  231. package/dist/utils/stripTrackedChanges.js +3 -3
  232. package/dist/utils/stripTrackedChanges.js.map +1 -1
  233. package/dist/utils/textDiff.d.ts +1 -1
  234. package/dist/utils/textDiff.js +8 -8
  235. package/dist/utils/textDiff.js.map +1 -1
  236. package/dist/utils/units.js.map +1 -1
  237. package/dist/utils/validation.d.ts.map +1 -1
  238. package/dist/utils/validation.js +24 -7
  239. package/dist/utils/validation.js.map +1 -1
  240. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  241. package/dist/utils/xmlSanitization.js +3 -3
  242. package/dist/utils/xmlSanitization.js.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  244. package/dist/validation/RevisionAutoFixer.js +5 -5
  245. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  246. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  247. package/dist/validation/RevisionValidator.js +7 -9
  248. package/dist/validation/RevisionValidator.js.map +1 -1
  249. package/dist/validation/ValidationRules.js +3 -3
  250. package/dist/validation/ValidationRules.js.map +1 -1
  251. package/dist/validation/index.js.map +1 -1
  252. package/dist/xml/XMLBuilder.d.ts +1 -1
  253. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  254. package/dist/xml/XMLBuilder.js +98 -100
  255. package/dist/xml/XMLBuilder.js.map +1 -1
  256. package/dist/xml/XMLParser.d.ts.map +1 -1
  257. package/dist/xml/XMLParser.js +61 -66
  258. package/dist/xml/XMLParser.js.map +1 -1
  259. package/dist/zip/ZipHandler.d.ts.map +1 -1
  260. package/dist/zip/ZipHandler.js.map +1 -1
  261. package/dist/zip/ZipReader.d.ts.map +1 -1
  262. package/dist/zip/ZipReader.js +1 -3
  263. package/dist/zip/ZipReader.js.map +1 -1
  264. package/dist/zip/ZipWriter.d.ts +1 -1
  265. package/dist/zip/ZipWriter.d.ts.map +1 -1
  266. package/dist/zip/ZipWriter.js +28 -36
  267. package/dist/zip/ZipWriter.js.map +1 -1
  268. package/dist/zip/types.js +1 -1
  269. package/dist/zip/types.js.map +1 -1
  270. package/package.json +92 -92
  271. package/src/__tests__/helper-methods.test.ts +512 -512
  272. package/src/constants/legacyCompatFlags.ts +138 -138
  273. package/src/constants/limits.ts +50 -50
  274. package/src/core/Document.ts +1010 -1145
  275. package/src/core/DocumentContent.ts +461 -467
  276. package/src/core/DocumentGenerator.ts +1133 -1104
  277. package/src/core/DocumentIdManager.ts +158 -158
  278. package/src/core/DocumentParser.ts +2347 -2716
  279. package/src/core/DocumentValidator.ts +363 -372
  280. package/src/core/Relationship.ts +367 -367
  281. package/src/core/RelationshipManager.ts +429 -428
  282. package/src/elements/AlternateContent.ts +42 -42
  283. package/src/elements/Bookmark.ts +212 -210
  284. package/src/elements/BookmarkManager.ts +247 -250
  285. package/src/elements/Comment.ts +356 -359
  286. package/src/elements/CommentManager.ts +499 -502
  287. package/src/elements/CommonTypes.ts +524 -549
  288. package/src/elements/CustomXml.ts +36 -36
  289. package/src/elements/Endnote.ts +221 -217
  290. package/src/elements/EndnoteManager.ts +246 -249
  291. package/src/elements/Field.ts +1292 -1233
  292. package/src/elements/FieldHelpers.ts +329 -333
  293. package/src/elements/FontManager.ts +336 -339
  294. package/src/elements/Footer.ts +269 -269
  295. package/src/elements/Footnote.ts +221 -217
  296. package/src/elements/FootnoteManager.ts +246 -249
  297. package/src/elements/Header.ts +269 -269
  298. package/src/elements/HeaderFooterManager.ts +219 -219
  299. package/src/elements/Hyperlink.ts +1288 -1193
  300. package/src/elements/Image.ts +1982 -1756
  301. package/src/elements/ImageManager.ts +437 -432
  302. package/src/elements/ImageRun.ts +59 -59
  303. package/src/elements/MathElement.ts +65 -65
  304. package/src/elements/Paragraph.ts +4347 -4287
  305. package/src/elements/PreservedElement.ts +53 -53
  306. package/src/elements/PropertyChangeTypes.ts +458 -442
  307. package/src/elements/RangeMarker.ts +382 -400
  308. package/src/elements/Revision.ts +1198 -1217
  309. package/src/elements/RevisionContent.ts +73 -73
  310. package/src/elements/RevisionManager.ts +1070 -1070
  311. package/src/elements/Run.ts +3103 -3073
  312. package/src/elements/Section.ts +1521 -1421
  313. package/src/elements/Shape.ts +884 -873
  314. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  315. package/src/elements/Table.ts +2468 -2524
  316. package/src/elements/TableCell.ts +1617 -1621
  317. package/src/elements/TableGridChange.ts +149 -151
  318. package/src/elements/TableOfContents.ts +701 -691
  319. package/src/elements/TableOfContentsElement.ts +89 -89
  320. package/src/elements/TableRow.ts +960 -929
  321. package/src/elements/TextBox.ts +766 -768
  322. package/src/formatting/AbstractNumbering.ts +580 -579
  323. package/src/formatting/NumberingInstance.ts +295 -299
  324. package/src/formatting/NumberingLevel.ts +981 -1040
  325. package/src/formatting/NumberingManager.ts +875 -827
  326. package/src/formatting/Style.ts +1785 -1879
  327. package/src/formatting/StylesManager.ts +1090 -1130
  328. package/src/helpers/CleanupHelper.ts +524 -524
  329. package/src/images/ImageOptimizer.ts +274 -274
  330. package/src/index.ts +559 -554
  331. package/src/managers/DrawingManager.ts +319 -319
  332. package/src/tracking/DocumentTrackingContext.ts +687 -674
  333. package/src/tracking/TrackingContext.ts +175 -173
  334. package/src/types/compatibility-types.ts +49 -49
  335. package/src/types/formatting.ts +210 -210
  336. package/src/types/list-types.ts +14 -14
  337. package/src/types/settings-types.ts +59 -59
  338. package/src/types/styleConfig.ts +189 -189
  339. package/src/utils/ChangelogGenerator.ts +1583 -1581
  340. package/src/utils/CompatibilityUpgrader.ts +235 -237
  341. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  342. package/src/utils/MoveOperationHelper.ts +233 -238
  343. package/src/utils/RevisionAwareProcessor.ts +518 -526
  344. package/src/utils/RevisionWalker.ts +427 -457
  345. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  346. package/src/utils/ShadingResolver.ts +105 -107
  347. package/src/utils/acceptRevisions.ts +723 -714
  348. package/src/utils/cnfStyleDecoder.ts +212 -217
  349. package/src/utils/corruptionDetection.ts +346 -345
  350. package/src/utils/dateFormatting.ts +20 -20
  351. package/src/utils/deepClone.ts +77 -78
  352. package/src/utils/diagnostics.ts +125 -129
  353. package/src/utils/errorHandling.ts +80 -80
  354. package/src/utils/formatting.ts +220 -213
  355. package/src/utils/list-detection.ts +32 -42
  356. package/src/utils/logger.ts +412 -404
  357. package/src/utils/parsingHelpers.ts +190 -190
  358. package/src/utils/stripTrackedChanges.ts +356 -353
  359. package/src/utils/textDiff.ts +100 -100
  360. package/src/utils/units.ts +421 -421
  361. package/src/utils/validation.ts +553 -542
  362. package/src/utils/xmlSanitization.ts +179 -182
  363. package/src/validation/RevisionAutoFixer.ts +541 -542
  364. package/src/validation/RevisionValidator.ts +470 -460
  365. package/src/validation/ValidationRules.ts +338 -338
  366. package/src/validation/index.ts +30 -30
  367. package/src/xml/XMLBuilder.ts +857 -871
  368. package/src/xml/XMLParser.ts +877 -919
  369. package/src/zip/ZipHandler.ts +629 -637
  370. package/src/zip/ZipReader.ts +295 -299
  371. package/src/zip/ZipWriter.ts +374 -390
  372. package/src/zip/types.ts +116 -116
@@ -1,390 +1,374 @@
1
- /**
2
- * ZipWriter - Handles writing ZIP archives (DOCX files)
3
- *
4
- * DOCX Compliance Notes:
5
- * - [Content_Types].xml MUST be the first entry in the ZIP
6
- * - [Content_Types].xml MUST use STORE compression (uncompressed)
7
- * - File order matters for Microsoft Word compatibility
8
- */
9
-
10
- import JSZip from "jszip";
11
- import { promises as fs } from "fs";
12
- import { ZipFile, FileMap, SaveOptions, AddFileOptions } from "./types";
13
- import { FileOperationError } from "./errors";
14
- import { validateDocxStructure, normalizePath } from "../utils/validation";
15
-
16
- /**
17
- * Handles writing operations on ZIP archives with DOCX-specific compliance
18
- */
19
- export class ZipWriter {
20
- private zip: JSZip;
21
- private files: FileMap = new Map();
22
-
23
- constructor() {
24
- this.zip = new JSZip();
25
- }
26
-
27
- /**
28
- * Adds a file to the archive
29
- * @param filePath - Path where the file will be stored in the archive
30
- * @param content - File content (string or Buffer)
31
- * @param options - Options for adding the file
32
- *
33
- * **Encoding Note:**
34
- * - String content is always encoded as UTF-8 per DOCX/OpenXML standard
35
- * - Buffer content is stored as-is (should already be UTF-8 encoded for text)
36
- * - XML files must use UTF-8 encoding as specified in their XML declaration
37
- *
38
- * **DOCX Compliance:**
39
- * - [Content_Types].xml is automatically set to STORE compression
40
- */
41
- /**
42
- * Validates a file path for security issues
43
- * @param path - Normalized file path
44
- * @throws {Error} If path contains path traversal or other security issues
45
- * @private
46
- */
47
- private validatePathSecurity(path: string): void {
48
- // Check for path traversal attacks
49
- if (path.includes("..")) {
50
- throw new Error(
51
- `Security error: Path "${path}" contains path traversal sequence "..". ` +
52
- `This could be an attempt to write files outside the archive.`
53
- );
54
- }
55
-
56
- // Check for absolute paths (even after normalization)
57
- if (path.startsWith("/") || /^[a-zA-Z]:/.test(path)) {
58
- throw new Error(
59
- `Security error: Path "${path}" is an absolute path. ` +
60
- `Only relative paths are allowed in ZIP archives.`
61
- );
62
- }
63
-
64
- // Check for null bytes (can be used to truncate paths)
65
- if (path.includes("\0")) {
66
- throw new Error(
67
- `Security error: Path "${path}" contains null byte. ` +
68
- `This could be an attempt to exploit path handling.`
69
- );
70
- }
71
-
72
- // Check for excessively long paths (potential DoS)
73
- const MAX_PATH_LENGTH = 260; // Windows MAX_PATH
74
- if (path.length > MAX_PATH_LENGTH) {
75
- throw new Error(
76
- `Security error: Path "${path}" exceeds maximum length of ${MAX_PATH_LENGTH} characters.`
77
- );
78
- }
79
- }
80
-
81
- addFile(
82
- filePath: string,
83
- content: string | Buffer,
84
- options: AddFileOptions = {}
85
- ): void {
86
- const {
87
- binary = Buffer.isBuffer(content),
88
- compression = 6,
89
- date = new Date(),
90
- } = options;
91
-
92
- const normalizedPath = normalizePath(filePath);
93
-
94
- // Security: Validate path for traversal and other attacks
95
- this.validatePathSecurity(normalizedPath);
96
-
97
- // Convert string content to UTF-8 Buffer if not already binary
98
- // This ensures consistent UTF-8 encoding regardless of system locale
99
- let processedContent = content;
100
- if (typeof content === "string") {
101
- // Explicitly encode string as UTF-8 Buffer
102
- processedContent = Buffer.from(content, "utf8");
103
- }
104
-
105
- // DOCX REQUIREMENT: [Content_Types].xml MUST be uncompressed (STORE)
106
- const isContentTypes = normalizedPath === "[Content_Types].xml";
107
- const useCompression = isContentTypes
108
- ? "STORE"
109
- : compression > 0
110
- ? "DEFLATE"
111
- : "STORE";
112
- const compressionLevel = isContentTypes ? 0 : compression;
113
-
114
- // For text content (XML), this ensures UTF-8 encoding is preserved
115
- this.zip.file(normalizedPath, processedContent, {
116
- binary: true, // Always treat as binary since we're using Buffers
117
- compression: useCompression,
118
- compressionOptions: {
119
- level: compressionLevel,
120
- },
121
- date,
122
- });
123
-
124
- // Store in our file map
125
- // IMPORTANT: Store the PROCESSED content (Buffer), not the original
126
- // This ensures consistency with what was added to this.zip and prevents
127
- // double UTF-8 conversion in toBuffer() method (Issue #1)
128
- this.files.set(normalizedPath, {
129
- path: normalizedPath,
130
- content: processedContent, // Store Buffer, not original string
131
- isBinary: binary,
132
- size: processedContent.length, // Buffer.length is always correct (Issue #3)
133
- date,
134
- });
135
- }
136
-
137
- /**
138
- * Adds multiple files to the archive
139
- * @param files - Map of file paths to contents
140
- * @param options - Options for adding files
141
- */
142
- addFiles(files: FileMap, options: AddFileOptions = {}): void {
143
- for (const [path, file] of files) {
144
- this.addFile(path, file.content, {
145
- ...options,
146
- binary: file.isBinary,
147
- date: file.date,
148
- });
149
- }
150
- }
151
-
152
- /**
153
- * Removes a file from the archive
154
- * @param filePath - Path to the file to remove
155
- * @returns True if the file was removed, false if it didn't exist
156
- */
157
- removeFile(filePath: string): boolean {
158
- const normalizedPath = normalizePath(filePath);
159
-
160
- // Remove from JSZip
161
- const zipFile = this.zip.file(normalizedPath);
162
- if (zipFile) {
163
- this.zip.remove(normalizedPath);
164
- this.files.delete(normalizedPath);
165
- return true;
166
- }
167
-
168
- return false;
169
- }
170
-
171
- /**
172
- * Checks if a file exists in the archive
173
- * @param filePath - Path to check
174
- * @returns True if the file exists
175
- */
176
- hasFile(filePath: string): boolean {
177
- const normalizedPath = normalizePath(filePath);
178
- return this.files.has(normalizedPath);
179
- }
180
-
181
- /**
182
- * Gets a file from the archive
183
- * @param filePath - Path to the file
184
- * @returns The file data, or undefined if not found
185
- */
186
- getFile(filePath: string): ZipFile | undefined {
187
- const normalizedPath = normalizePath(filePath);
188
- return this.files.get(normalizedPath);
189
- }
190
-
191
- /**
192
- * Gets all files in the archive
193
- * @returns Map of file paths to file data
194
- */
195
- getAllFiles(): FileMap {
196
- return new Map(this.files);
197
- }
198
-
199
- /**
200
- * Gets a list of all file paths in the archive
201
- * @returns Array of file paths
202
- */
203
- getFilePaths(): string[] {
204
- return Array.from(this.files.keys());
205
- }
206
-
207
- /**
208
- * Validates the DOCX structure before saving
209
- * @throws {MissingRequiredFileError} If required files are missing
210
- */
211
- validate(): void {
212
- const filePaths = this.getFilePaths();
213
- validateDocxStructure(filePaths);
214
- }
215
-
216
- /**
217
- * Sorts files according to DOCX best practices
218
- * Microsoft Word expects files in a specific order for optimal compatibility
219
- *
220
- * @returns Sorted array of file paths
221
- *
222
- * **DOCX File Order (CRITICAL):**
223
- * 1. [Content_Types].xml - MUST be first
224
- * 2. _rels/.rels - Root relationships
225
- * 3. docProps/* - Document properties
226
- * 4. word/_rels/document.xml.rels - Document relationships
227
- * 5. word/document.xml - Main document
228
- * 6. word/* - Other word files (styles, numbering, etc.)
229
- * 7. Everything else - Media, custom XML, etc.
230
- */
231
- private getSortedFilePaths(): string[] {
232
- const paths = Array.from(this.files.keys());
233
-
234
- return paths.sort((a, b) => {
235
- // Priority 1: [Content_Types].xml MUST be first (CRITICAL for MS Word)
236
- if (a === "[Content_Types].xml") return -1;
237
- if (b === "[Content_Types].xml") return 1;
238
-
239
- // Priority 2: Root relationships
240
- if (a === "_rels/.rels") return -1;
241
- if (b === "_rels/.rels") return 1;
242
-
243
- // Priority 3: Document properties
244
- const aIsDocProps = a.startsWith("docProps/");
245
- const bIsDocProps = b.startsWith("docProps/");
246
- if (aIsDocProps && !bIsDocProps) return -1;
247
- if (!aIsDocProps && bIsDocProps) return 1;
248
-
249
- // Priority 4: word/_rels/document.xml.rels
250
- if (a === "word/_rels/document.xml.rels") return -1;
251
- if (b === "word/_rels/document.xml.rels") return 1;
252
-
253
- // Priority 5: word/document.xml
254
- if (a === "word/document.xml") return -1;
255
- if (b === "word/document.xml") return 1;
256
-
257
- // Priority 6: Other word/ folder files (before relationships)
258
- const aIsWordRels = a.startsWith("word/_rels/");
259
- const bIsWordRels = b.startsWith("word/_rels/");
260
- const aIsWord = a.startsWith("word/") && !aIsWordRels;
261
- const bIsWord = b.startsWith("word/") && !bIsWordRels;
262
-
263
- if (aIsWord && !bIsWord && !bIsWordRels) return -1;
264
- if (!aIsWord && bIsWord && !aIsWordRels) return 1;
265
-
266
- // Priority 7: word/_rels/ files
267
- if (aIsWordRels && !bIsWordRels) return -1;
268
- if (!aIsWordRels && bIsWordRels) return 1;
269
-
270
- // Alphabetical for same priority
271
- return a.localeCompare(b);
272
- });
273
- }
274
-
275
- /**
276
- * Generates the ZIP archive as a buffer
277
- * @param options - Save options
278
- * @returns Buffer containing the ZIP archive
279
- *
280
- * **Encoding Note:**
281
- * The generated buffer contains UTF-8 encoded XML and text files.
282
- * All string content within files has been explicitly UTF-8 encoded
283
- * before being added to the archive to ensure consistency.
284
- *
285
- * **DOCX Compliance:**
286
- * - Files are ordered with [Content_Types].xml first (REQUIRED)
287
- * - [Content_Types].xml uses STORE compression (uncompressed)
288
- * - All other files use DEFLATE compression by default
289
- */
290
- async toBuffer(options: SaveOptions = {}): Promise<Buffer> {
291
- const { compression = 6, validate = true } = options;
292
-
293
- // Validate structure if requested
294
- if (validate) {
295
- this.validate();
296
- }
297
-
298
- try {
299
- // Create a new JSZip instance with proper file ordering
300
- const orderedZip = new JSZip();
301
- const sortedPaths = this.getSortedFilePaths();
302
-
303
- // Add files in the correct order
304
- for (const path of sortedPaths) {
305
- const file = this.files.get(path);
306
- if (!file) continue;
307
-
308
- // Content is already a Buffer from addFile() - no re-conversion needed (Issue #2)
309
- const processedContent = file.content as Buffer;
310
-
311
- // DOCX REQUIREMENT: [Content_Types].xml MUST be uncompressed
312
- const isContentTypes = path === "[Content_Types].xml";
313
- const useCompression = isContentTypes
314
- ? "STORE"
315
- : compression > 0
316
- ? "DEFLATE"
317
- : "STORE";
318
- const compressionLevel = isContentTypes ? 0 : compression;
319
-
320
- orderedZip.file(path, processedContent, {
321
- binary: true,
322
- compression: useCompression,
323
- compressionOptions: {
324
- level: compressionLevel,
325
- },
326
- date: file.date,
327
- });
328
- }
329
-
330
- // Generate ZIP with the ordered files
331
- const buffer = await orderedZip.generateAsync({
332
- type: "nodebuffer",
333
- compression: compression > 0 ? "DEFLATE" : "STORE",
334
- compressionOptions: {
335
- level: compression,
336
- },
337
- streamFiles: true,
338
- });
339
-
340
- return buffer;
341
- } catch (error: unknown) {
342
- const err = error instanceof Error ? error : new Error(String(error));
343
- throw new FileOperationError("generate", err.message);
344
- }
345
- }
346
-
347
- /**
348
- * Saves the archive to a file
349
- * @param filePath - Path where the file will be saved
350
- * @param options - Save options
351
- */
352
- async saveToFile(filePath: string, options: SaveOptions = {}): Promise<void> {
353
- try {
354
- const buffer = await this.toBuffer(options);
355
- await fs.writeFile(filePath, buffer);
356
- } catch (error: unknown) {
357
- if (error instanceof FileOperationError) {
358
- throw error;
359
- }
360
- const err = error instanceof Error ? error : new Error(String(error));
361
- throw new FileOperationError("save", err.message);
362
- }
363
- }
364
-
365
- /**
366
- * Creates a new empty archive
367
- */
368
- clear(): void {
369
- this.zip = new JSZip();
370
- this.files.clear();
371
- }
372
-
373
- /**
374
- * Gets the number of files in the archive
375
- * @returns Number of files
376
- */
377
- getFileCount(): number {
378
- return this.files.size;
379
- }
380
-
381
- /**
382
- * Creates a clone of this writer with all its files
383
- * @returns A new ZipWriter instance with the same files
384
- */
385
- clone(): ZipWriter {
386
- const newWriter = new ZipWriter();
387
- newWriter.addFiles(this.files);
388
- return newWriter;
389
- }
390
- }
1
+ /**
2
+ * ZipWriter - Handles writing ZIP archives (DOCX files)
3
+ *
4
+ * DOCX Compliance Notes:
5
+ * - [Content_Types].xml MUST be the first entry in the ZIP
6
+ * - [Content_Types].xml MUST use STORE compression (uncompressed)
7
+ * - File order matters for Microsoft Word compatibility
8
+ */
9
+
10
+ import JSZip from 'jszip';
11
+ import { promises as fs } from 'fs';
12
+ import { ZipFile, FileMap, SaveOptions, AddFileOptions } from './types';
13
+ import { FileOperationError } from './errors';
14
+ import { validateDocxStructure, normalizePath } from '../utils/validation';
15
+
16
+ /**
17
+ * Handles writing operations on ZIP archives with DOCX-specific compliance
18
+ */
19
+ export class ZipWriter {
20
+ private zip: JSZip;
21
+ private files: FileMap = new Map();
22
+
23
+ constructor() {
24
+ this.zip = new JSZip();
25
+ }
26
+
27
+ /**
28
+ * Adds a file to the archive
29
+ * @param filePath - Path where the file will be stored in the archive
30
+ * @param content - File content (string or Buffer)
31
+ * @param options - Options for adding the file
32
+ *
33
+ * **Encoding Note:**
34
+ * - String content is always encoded as UTF-8 per DOCX/OpenXML standard
35
+ * - Buffer content is stored as-is (should already be UTF-8 encoded for text)
36
+ * - XML files must use UTF-8 encoding as specified in their XML declaration
37
+ *
38
+ * **DOCX Compliance:**
39
+ * - [Content_Types].xml is automatically set to STORE compression
40
+ */
41
+ /**
42
+ * Validates a file path for security issues
43
+ * @param path - Normalized file path
44
+ * @throws {Error} If path contains path traversal or other security issues
45
+ * @private
46
+ */
47
+ private validatePathSecurity(path: string): void {
48
+ // Check for path traversal attacks
49
+ if (path.includes('..')) {
50
+ throw new Error(
51
+ `Security error: Path "${path}" contains path traversal sequence "..". ` +
52
+ `This could be an attempt to write files outside the archive.`
53
+ );
54
+ }
55
+
56
+ // Check for absolute paths (even after normalization)
57
+ if (path.startsWith('/') || /^[a-zA-Z]:/.test(path)) {
58
+ throw new Error(
59
+ `Security error: Path "${path}" is an absolute path. ` +
60
+ `Only relative paths are allowed in ZIP archives.`
61
+ );
62
+ }
63
+
64
+ // Check for null bytes (can be used to truncate paths)
65
+ if (path.includes('\0')) {
66
+ throw new Error(
67
+ `Security error: Path "${path}" contains null byte. ` +
68
+ `This could be an attempt to exploit path handling.`
69
+ );
70
+ }
71
+
72
+ // Check for excessively long paths (potential DoS)
73
+ const MAX_PATH_LENGTH = 260; // Windows MAX_PATH
74
+ if (path.length > MAX_PATH_LENGTH) {
75
+ throw new Error(
76
+ `Security error: Path "${path}" exceeds maximum length of ${MAX_PATH_LENGTH} characters.`
77
+ );
78
+ }
79
+ }
80
+
81
+ addFile(filePath: string, content: string | Buffer, options: AddFileOptions = {}): void {
82
+ const { binary = Buffer.isBuffer(content), compression = 6, date = new Date() } = options;
83
+
84
+ const normalizedPath = normalizePath(filePath);
85
+
86
+ // Security: Validate path for traversal and other attacks
87
+ this.validatePathSecurity(normalizedPath);
88
+
89
+ // Convert string content to UTF-8 Buffer if not already binary
90
+ // This ensures consistent UTF-8 encoding regardless of system locale
91
+ let processedContent = content;
92
+ if (typeof content === 'string') {
93
+ // Explicitly encode string as UTF-8 Buffer
94
+ processedContent = Buffer.from(content, 'utf8');
95
+ }
96
+
97
+ // DOCX REQUIREMENT: [Content_Types].xml MUST be uncompressed (STORE)
98
+ const isContentTypes = normalizedPath === '[Content_Types].xml';
99
+ const useCompression = isContentTypes ? 'STORE' : compression > 0 ? 'DEFLATE' : 'STORE';
100
+ const compressionLevel = isContentTypes ? 0 : compression;
101
+
102
+ // For text content (XML), this ensures UTF-8 encoding is preserved
103
+ this.zip.file(normalizedPath, processedContent, {
104
+ binary: true, // Always treat as binary since we're using Buffers
105
+ compression: useCompression,
106
+ compressionOptions: {
107
+ level: compressionLevel,
108
+ },
109
+ date,
110
+ });
111
+
112
+ // Store in our file map
113
+ // IMPORTANT: Store the PROCESSED content (Buffer), not the original
114
+ // This ensures consistency with what was added to this.zip and prevents
115
+ // double UTF-8 conversion in toBuffer() method (Issue #1)
116
+ this.files.set(normalizedPath, {
117
+ path: normalizedPath,
118
+ content: processedContent, // Store Buffer, not original string
119
+ isBinary: binary,
120
+ size: processedContent.length, // Buffer.length is always correct (Issue #3)
121
+ date,
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Adds multiple files to the archive
127
+ * @param files - Map of file paths to contents
128
+ * @param options - Options for adding files
129
+ */
130
+ addFiles(files: FileMap, options: AddFileOptions = {}): void {
131
+ for (const [path, file] of files) {
132
+ this.addFile(path, file.content, {
133
+ ...options,
134
+ binary: file.isBinary,
135
+ date: file.date,
136
+ });
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Removes a file from the archive
142
+ * @param filePath - Path to the file to remove
143
+ * @returns True if the file was removed, false if it didn't exist
144
+ */
145
+ removeFile(filePath: string): boolean {
146
+ const normalizedPath = normalizePath(filePath);
147
+
148
+ // Remove from JSZip
149
+ const zipFile = this.zip.file(normalizedPath);
150
+ if (zipFile) {
151
+ this.zip.remove(normalizedPath);
152
+ this.files.delete(normalizedPath);
153
+ return true;
154
+ }
155
+
156
+ return false;
157
+ }
158
+
159
+ /**
160
+ * Checks if a file exists in the archive
161
+ * @param filePath - Path to check
162
+ * @returns True if the file exists
163
+ */
164
+ hasFile(filePath: string): boolean {
165
+ const normalizedPath = normalizePath(filePath);
166
+ return this.files.has(normalizedPath);
167
+ }
168
+
169
+ /**
170
+ * Gets a file from the archive
171
+ * @param filePath - Path to the file
172
+ * @returns The file data, or undefined if not found
173
+ */
174
+ getFile(filePath: string): ZipFile | undefined {
175
+ const normalizedPath = normalizePath(filePath);
176
+ return this.files.get(normalizedPath);
177
+ }
178
+
179
+ /**
180
+ * Gets all files in the archive
181
+ * @returns Map of file paths to file data
182
+ */
183
+ getAllFiles(): FileMap {
184
+ return new Map(this.files);
185
+ }
186
+
187
+ /**
188
+ * Gets a list of all file paths in the archive
189
+ * @returns Array of file paths
190
+ */
191
+ getFilePaths(): string[] {
192
+ return Array.from(this.files.keys());
193
+ }
194
+
195
+ /**
196
+ * Validates the DOCX structure before saving
197
+ * @throws {MissingRequiredFileError} If required files are missing
198
+ */
199
+ validate(): void {
200
+ const filePaths = this.getFilePaths();
201
+ validateDocxStructure(filePaths);
202
+ }
203
+
204
+ /**
205
+ * Sorts files according to DOCX best practices
206
+ * Microsoft Word expects files in a specific order for optimal compatibility
207
+ *
208
+ * @returns Sorted array of file paths
209
+ *
210
+ * **DOCX File Order (CRITICAL):**
211
+ * 1. [Content_Types].xml - MUST be first
212
+ * 2. _rels/.rels - Root relationships
213
+ * 3. docProps/* - Document properties
214
+ * 4. word/_rels/document.xml.rels - Document relationships
215
+ * 5. word/document.xml - Main document
216
+ * 6. word/* - Other word files (styles, numbering, etc.)
217
+ * 7. Everything else - Media, custom XML, etc.
218
+ */
219
+ private getSortedFilePaths(): string[] {
220
+ const paths = Array.from(this.files.keys());
221
+
222
+ return paths.sort((a, b) => {
223
+ // Priority 1: [Content_Types].xml MUST be first (CRITICAL for MS Word)
224
+ if (a === '[Content_Types].xml') return -1;
225
+ if (b === '[Content_Types].xml') return 1;
226
+
227
+ // Priority 2: Root relationships
228
+ if (a === '_rels/.rels') return -1;
229
+ if (b === '_rels/.rels') return 1;
230
+
231
+ // Priority 3: Document properties
232
+ const aIsDocProps = a.startsWith('docProps/');
233
+ const bIsDocProps = b.startsWith('docProps/');
234
+ if (aIsDocProps && !bIsDocProps) return -1;
235
+ if (!aIsDocProps && bIsDocProps) return 1;
236
+
237
+ // Priority 4: word/_rels/document.xml.rels
238
+ if (a === 'word/_rels/document.xml.rels') return -1;
239
+ if (b === 'word/_rels/document.xml.rels') return 1;
240
+
241
+ // Priority 5: word/document.xml
242
+ if (a === 'word/document.xml') return -1;
243
+ if (b === 'word/document.xml') return 1;
244
+
245
+ // Priority 6: Other word/ folder files (before relationships)
246
+ const aIsWordRels = a.startsWith('word/_rels/');
247
+ const bIsWordRels = b.startsWith('word/_rels/');
248
+ const aIsWord = a.startsWith('word/') && !aIsWordRels;
249
+ const bIsWord = b.startsWith('word/') && !bIsWordRels;
250
+
251
+ if (aIsWord && !bIsWord && !bIsWordRels) return -1;
252
+ if (!aIsWord && bIsWord && !aIsWordRels) return 1;
253
+
254
+ // Priority 7: word/_rels/ files
255
+ if (aIsWordRels && !bIsWordRels) return -1;
256
+ if (!aIsWordRels && bIsWordRels) return 1;
257
+
258
+ // Alphabetical for same priority
259
+ return a.localeCompare(b);
260
+ });
261
+ }
262
+
263
+ /**
264
+ * Generates the ZIP archive as a buffer
265
+ * @param options - Save options
266
+ * @returns Buffer containing the ZIP archive
267
+ *
268
+ * **Encoding Note:**
269
+ * The generated buffer contains UTF-8 encoded XML and text files.
270
+ * All string content within files has been explicitly UTF-8 encoded
271
+ * before being added to the archive to ensure consistency.
272
+ *
273
+ * **DOCX Compliance:**
274
+ * - Files are ordered with [Content_Types].xml first (REQUIRED)
275
+ * - [Content_Types].xml uses STORE compression (uncompressed)
276
+ * - All other files use DEFLATE compression by default
277
+ */
278
+ async toBuffer(options: SaveOptions = {}): Promise<Buffer> {
279
+ const { compression = 6, validate = true } = options;
280
+
281
+ // Validate structure if requested
282
+ if (validate) {
283
+ this.validate();
284
+ }
285
+
286
+ try {
287
+ // Create a new JSZip instance with proper file ordering
288
+ const orderedZip = new JSZip();
289
+ const sortedPaths = this.getSortedFilePaths();
290
+
291
+ // Add files in the correct order
292
+ for (const path of sortedPaths) {
293
+ const file = this.files.get(path);
294
+ if (!file) continue;
295
+
296
+ // Content is already a Buffer from addFile() - no re-conversion needed (Issue #2)
297
+ const processedContent = file.content as Buffer;
298
+
299
+ // DOCX REQUIREMENT: [Content_Types].xml MUST be uncompressed
300
+ const isContentTypes = path === '[Content_Types].xml';
301
+ const useCompression = isContentTypes ? 'STORE' : compression > 0 ? 'DEFLATE' : 'STORE';
302
+ const compressionLevel = isContentTypes ? 0 : compression;
303
+
304
+ orderedZip.file(path, processedContent, {
305
+ binary: true,
306
+ compression: useCompression,
307
+ compressionOptions: {
308
+ level: compressionLevel,
309
+ },
310
+ date: file.date,
311
+ });
312
+ }
313
+
314
+ // Generate ZIP with the ordered files
315
+ const buffer = await orderedZip.generateAsync({
316
+ type: 'nodebuffer',
317
+ compression: compression > 0 ? 'DEFLATE' : 'STORE',
318
+ compressionOptions: {
319
+ level: compression,
320
+ },
321
+ streamFiles: true,
322
+ });
323
+
324
+ return buffer;
325
+ } catch (error: unknown) {
326
+ const err = error instanceof Error ? error : new Error(String(error));
327
+ throw new FileOperationError('generate', err.message);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Saves the archive to a file
333
+ * @param filePath - Path where the file will be saved
334
+ * @param options - Save options
335
+ */
336
+ async saveToFile(filePath: string, options: SaveOptions = {}): Promise<void> {
337
+ try {
338
+ const buffer = await this.toBuffer(options);
339
+ await fs.writeFile(filePath, buffer);
340
+ } catch (error: unknown) {
341
+ if (error instanceof FileOperationError) {
342
+ throw error;
343
+ }
344
+ const err = error instanceof Error ? error : new Error(String(error));
345
+ throw new FileOperationError('save', err.message);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Creates a new empty archive
351
+ */
352
+ clear(): void {
353
+ this.zip = new JSZip();
354
+ this.files.clear();
355
+ }
356
+
357
+ /**
358
+ * Gets the number of files in the archive
359
+ * @returns Number of files
360
+ */
361
+ getFileCount(): number {
362
+ return this.files.size;
363
+ }
364
+
365
+ /**
366
+ * Creates a clone of this writer with all its files
367
+ * @returns A new ZipWriter instance with the same files
368
+ */
369
+ clone(): ZipWriter {
370
+ const newWriter = new ZipWriter();
371
+ newWriter.addFiles(this.files);
372
+ return newWriter;
373
+ }
374
+ }