docxmlater 10.1.3 → 10.1.5

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 (371) 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 +50 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +483 -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.map +1 -1
  146. package/dist/formatting/NumberingManager.js +9 -9
  147. package/dist/formatting/NumberingManager.js.map +1 -1
  148. package/dist/formatting/Style.d.ts +11 -11
  149. package/dist/formatting/Style.d.ts.map +1 -1
  150. package/dist/formatting/Style.js +219 -247
  151. package/dist/formatting/Style.js.map +1 -1
  152. package/dist/formatting/StylesManager.d.ts +2 -2
  153. package/dist/formatting/StylesManager.d.ts.map +1 -1
  154. package/dist/formatting/StylesManager.js +96 -102
  155. package/dist/formatting/StylesManager.js.map +1 -1
  156. package/dist/helpers/CleanupHelper.d.ts +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  158. package/dist/helpers/CleanupHelper.js +6 -6
  159. package/dist/helpers/CleanupHelper.js.map +1 -1
  160. package/dist/images/ImageOptimizer.js +7 -7
  161. package/dist/images/ImageOptimizer.js.map +1 -1
  162. package/dist/index.d.ts +9 -9
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js.map +1 -1
  165. package/dist/managers/DrawingManager.js.map +1 -1
  166. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.js +23 -7
  168. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  169. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  170. package/dist/tracking/TrackingContext.js.map +1 -1
  171. package/dist/types/compatibility-types.js.map +1 -1
  172. package/dist/types/formatting.js.map +1 -1
  173. package/dist/types/list-types.d.ts +6 -6
  174. package/dist/types/list-types.js.map +1 -1
  175. package/dist/types/settings-types.js.map +1 -1
  176. package/dist/types/styleConfig.d.ts +2 -2
  177. package/dist/types/styleConfig.js.map +1 -1
  178. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  179. package/dist/utils/ChangelogGenerator.js +97 -101
  180. package/dist/utils/ChangelogGenerator.js.map +1 -1
  181. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.js +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  184. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  186. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  187. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  188. package/dist/utils/MoveOperationHelper.js +1 -1
  189. package/dist/utils/MoveOperationHelper.js.map +1 -1
  190. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.js +2 -4
  192. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  193. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  194. package/dist/utils/RevisionWalker.js +4 -12
  195. package/dist/utils/RevisionWalker.js.map +1 -1
  196. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  198. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  199. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  200. package/dist/utils/ShadingResolver.js +1 -1
  201. package/dist/utils/ShadingResolver.js.map +1 -1
  202. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  203. package/dist/utils/acceptRevisions.js +23 -12
  204. package/dist/utils/acceptRevisions.js.map +1 -1
  205. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  207. package/dist/utils/cnfStyleDecoder.js +40 -40
  208. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  209. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  210. package/dist/utils/corruptionDetection.js.map +1 -1
  211. package/dist/utils/dateFormatting.js.map +1 -1
  212. package/dist/utils/deepClone.js +1 -1
  213. package/dist/utils/deepClone.js.map +1 -1
  214. package/dist/utils/diagnostics.d.ts.map +1 -1
  215. package/dist/utils/diagnostics.js +1 -1
  216. package/dist/utils/diagnostics.js.map +1 -1
  217. package/dist/utils/errorHandling.js.map +1 -1
  218. package/dist/utils/formatting.d.ts.map +1 -1
  219. package/dist/utils/formatting.js +10 -2
  220. package/dist/utils/formatting.js.map +1 -1
  221. package/dist/utils/list-detection.d.ts +2 -2
  222. package/dist/utils/list-detection.d.ts.map +1 -1
  223. package/dist/utils/list-detection.js +21 -23
  224. package/dist/utils/list-detection.js.map +1 -1
  225. package/dist/utils/logger.d.ts.map +1 -1
  226. package/dist/utils/logger.js +12 -7
  227. package/dist/utils/logger.js.map +1 -1
  228. package/dist/utils/parsingHelpers.js.map +1 -1
  229. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  230. package/dist/utils/stripTrackedChanges.js +3 -3
  231. package/dist/utils/stripTrackedChanges.js.map +1 -1
  232. package/dist/utils/textDiff.d.ts +1 -1
  233. package/dist/utils/textDiff.js +8 -8
  234. package/dist/utils/textDiff.js.map +1 -1
  235. package/dist/utils/units.js.map +1 -1
  236. package/dist/utils/validation.d.ts.map +1 -1
  237. package/dist/utils/validation.js +24 -7
  238. package/dist/utils/validation.js.map +1 -1
  239. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  240. package/dist/utils/xmlSanitization.js +3 -3
  241. package/dist/utils/xmlSanitization.js.map +1 -1
  242. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.js +5 -5
  244. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  245. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  246. package/dist/validation/RevisionValidator.js +7 -9
  247. package/dist/validation/RevisionValidator.js.map +1 -1
  248. package/dist/validation/ValidationRules.js +3 -3
  249. package/dist/validation/ValidationRules.js.map +1 -1
  250. package/dist/validation/index.js.map +1 -1
  251. package/dist/xml/XMLBuilder.d.ts +1 -1
  252. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  253. package/dist/xml/XMLBuilder.js +98 -100
  254. package/dist/xml/XMLBuilder.js.map +1 -1
  255. package/dist/xml/XMLParser.d.ts.map +1 -1
  256. package/dist/xml/XMLParser.js +61 -66
  257. package/dist/xml/XMLParser.js.map +1 -1
  258. package/dist/zip/ZipHandler.d.ts.map +1 -1
  259. package/dist/zip/ZipHandler.js.map +1 -1
  260. package/dist/zip/ZipReader.d.ts.map +1 -1
  261. package/dist/zip/ZipReader.js +1 -3
  262. package/dist/zip/ZipReader.js.map +1 -1
  263. package/dist/zip/ZipWriter.d.ts +1 -1
  264. package/dist/zip/ZipWriter.d.ts.map +1 -1
  265. package/dist/zip/ZipWriter.js +28 -36
  266. package/dist/zip/ZipWriter.js.map +1 -1
  267. package/dist/zip/types.js +1 -1
  268. package/dist/zip/types.js.map +1 -1
  269. package/package.json +92 -92
  270. package/src/__tests__/helper-methods.test.ts +512 -512
  271. package/src/constants/legacyCompatFlags.ts +138 -138
  272. package/src/constants/limits.ts +50 -50
  273. package/src/core/Document.ts +985 -1145
  274. package/src/core/DocumentContent.ts +461 -467
  275. package/src/core/DocumentGenerator.ts +1133 -1104
  276. package/src/core/DocumentIdManager.ts +158 -158
  277. package/src/core/DocumentParser.ts +2347 -2716
  278. package/src/core/DocumentValidator.ts +363 -372
  279. package/src/core/Relationship.ts +367 -367
  280. package/src/core/RelationshipManager.ts +429 -428
  281. package/src/elements/AlternateContent.ts +42 -42
  282. package/src/elements/Bookmark.ts +212 -210
  283. package/src/elements/BookmarkManager.ts +247 -250
  284. package/src/elements/Comment.ts +356 -359
  285. package/src/elements/CommentManager.ts +499 -502
  286. package/src/elements/CommonTypes.ts +524 -549
  287. package/src/elements/CustomXml.ts +36 -36
  288. package/src/elements/Endnote.ts +221 -217
  289. package/src/elements/EndnoteManager.ts +246 -249
  290. package/src/elements/Field.ts +1292 -1233
  291. package/src/elements/FieldHelpers.ts +329 -333
  292. package/src/elements/FontManager.ts +336 -339
  293. package/src/elements/Footer.ts +269 -269
  294. package/src/elements/Footnote.ts +221 -217
  295. package/src/elements/FootnoteManager.ts +246 -249
  296. package/src/elements/Header.ts +269 -269
  297. package/src/elements/HeaderFooterManager.ts +219 -219
  298. package/src/elements/Hyperlink.ts +1288 -1193
  299. package/src/elements/Image.ts +1982 -1756
  300. package/src/elements/ImageManager.ts +437 -432
  301. package/src/elements/ImageRun.ts +59 -59
  302. package/src/elements/MathElement.ts +65 -65
  303. package/src/elements/Paragraph.ts +4347 -4287
  304. package/src/elements/PreservedElement.ts +53 -53
  305. package/src/elements/PropertyChangeTypes.ts +458 -442
  306. package/src/elements/RangeMarker.ts +382 -400
  307. package/src/elements/Revision.ts +1198 -1217
  308. package/src/elements/RevisionContent.ts +73 -73
  309. package/src/elements/RevisionManager.ts +1070 -1070
  310. package/src/elements/Run.ts +3103 -3073
  311. package/src/elements/Section.ts +1521 -1421
  312. package/src/elements/Shape.ts +884 -873
  313. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  314. package/src/elements/Table.ts +2468 -2524
  315. package/src/elements/TableCell.ts +1617 -1621
  316. package/src/elements/TableGridChange.ts +149 -151
  317. package/src/elements/TableOfContents.ts +701 -691
  318. package/src/elements/TableOfContentsElement.ts +89 -89
  319. package/src/elements/TableRow.ts +960 -929
  320. package/src/elements/TextBox.ts +766 -768
  321. package/src/formatting/AbstractNumbering.ts +580 -579
  322. package/src/formatting/NumberingInstance.ts +295 -299
  323. package/src/formatting/NumberingLevel.ts +981 -1040
  324. package/src/formatting/NumberingManager.ts +833 -827
  325. package/src/formatting/Style.ts +1785 -1879
  326. package/src/formatting/StylesManager.ts +1090 -1130
  327. package/src/helpers/CleanupHelper.ts +524 -524
  328. package/src/images/ImageOptimizer.ts +274 -274
  329. package/src/index.ts +559 -554
  330. package/src/managers/DrawingManager.ts +319 -319
  331. package/src/tracking/DocumentTrackingContext.ts +687 -674
  332. package/src/tracking/TrackingContext.ts +175 -173
  333. package/src/types/compatibility-types.ts +49 -49
  334. package/src/types/formatting.ts +210 -210
  335. package/src/types/list-types.ts +14 -14
  336. package/src/types/settings-types.ts +59 -59
  337. package/src/types/styleConfig.ts +189 -189
  338. package/src/utils/ChangelogGenerator.ts +1583 -1581
  339. package/src/utils/CompatibilityUpgrader.ts +235 -237
  340. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  341. package/src/utils/MoveOperationHelper.ts +233 -238
  342. package/src/utils/RevisionAwareProcessor.ts +518 -526
  343. package/src/utils/RevisionWalker.ts +427 -457
  344. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  345. package/src/utils/ShadingResolver.ts +105 -107
  346. package/src/utils/acceptRevisions.ts +723 -714
  347. package/src/utils/cnfStyleDecoder.ts +212 -217
  348. package/src/utils/corruptionDetection.ts +346 -345
  349. package/src/utils/dateFormatting.ts +20 -20
  350. package/src/utils/deepClone.ts +77 -78
  351. package/src/utils/diagnostics.ts +125 -129
  352. package/src/utils/errorHandling.ts +80 -80
  353. package/src/utils/formatting.ts +220 -213
  354. package/src/utils/list-detection.ts +32 -42
  355. package/src/utils/logger.ts +412 -404
  356. package/src/utils/parsingHelpers.ts +190 -190
  357. package/src/utils/stripTrackedChanges.ts +356 -353
  358. package/src/utils/textDiff.ts +100 -100
  359. package/src/utils/units.ts +421 -421
  360. package/src/utils/validation.ts +553 -542
  361. package/src/utils/xmlSanitization.ts +179 -182
  362. package/src/validation/RevisionAutoFixer.ts +541 -542
  363. package/src/validation/RevisionValidator.ts +470 -460
  364. package/src/validation/ValidationRules.ts +338 -338
  365. package/src/validation/index.ts +30 -30
  366. package/src/xml/XMLBuilder.ts +857 -871
  367. package/src/xml/XMLParser.ts +877 -919
  368. package/src/zip/ZipHandler.ts +629 -637
  369. package/src/zip/ZipReader.ts +295 -299
  370. package/src/zip/ZipWriter.ts +374 -390
  371. 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
+ }