@wonseok-han/confluence-sync 1.7.0 → 2.0.0

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 (518) hide show
  1. package/README.md +44 -409
  2. package/dist/cli/args.js +1 -1
  3. package/dist/cli/help.js +214 -115
  4. package/dist/cli/init.js +44 -5
  5. package/dist/cli/main.js +42 -11
  6. package/dist/cli/status.js +83 -0
  7. package/dist/confluence/client.js +157 -86
  8. package/dist/confluence/config.js +12 -8
  9. package/dist/confluence/env-file.js +23 -0
  10. package/dist/confluence/links.js +18 -0
  11. package/dist/conversion/asset-paths.js +5 -0
  12. package/dist/conversion/attachments.js +29 -7
  13. package/dist/conversion/convert.js +10 -22
  14. package/dist/conversion/footnotes.js +114 -11
  15. package/dist/conversion/markdown.js +26 -0
  16. package/dist/conversion/pdf-pages.js +10 -8
  17. package/dist/documents/anchors.js +9 -0
  18. package/dist/documents/callout-roundtrip.js +75 -0
  19. package/dist/documents/callouts.js +74 -0
  20. package/dist/documents/catalog.js +2 -2
  21. package/dist/documents/footnote-roundtrip.js +96 -0
  22. package/dist/documents/format.js +116 -0
  23. package/dist/documents/html-tables.js +30 -0
  24. package/dist/documents/html-to-markdown.js +47 -13
  25. package/dist/documents/markdown.js +83 -12
  26. package/dist/documents/obsidian.js +6 -3
  27. package/dist/documents/tables.js +89 -0
  28. package/dist/runtime/network.js +4 -0
  29. package/dist/shared/colors.js +2 -2
  30. package/dist/shared/progress.js +164 -0
  31. package/dist/shared/version.js +12 -0
  32. package/dist/site/assets/Monaco-LICENSE.txt +21 -0
  33. package/dist/site/assets/Monaco-ThirdPartyNotices.txt +448 -0
  34. package/dist/site/assets/PDFJS-LICENSE.txt +177 -0
  35. package/dist/site/assets/app.css +1 -0
  36. package/dist/site/assets/app.js +4 -0
  37. package/dist/site/assets/chunks/chunk-TEW74QPN.js +35 -0
  38. package/dist/site/assets/chunks/markdown-S6623E66.js +1 -0
  39. package/dist/site/assets/chunks/monaco-36ZGUH4Y.js +897 -0
  40. package/dist/site/assets/chunks/monaco-HFLEBJTI.css +1 -0
  41. package/dist/site/assets/chunks/pdf-DDN7TXTP.js +55 -0
  42. package/dist/site/assets/confluence-env-example.txt +32 -0
  43. package/dist/site/assets/editor.worker.js +1 -0
  44. package/dist/site/assets/favicon.svg +3 -0
  45. package/dist/site/assets/guide/01-account-settings.png +0 -0
  46. package/dist/site/assets/guide/02-atlassian-security.png +0 -0
  47. package/dist/site/assets/guide/03-api-tokens.png +0 -0
  48. package/dist/site/assets/guide/04-scope-api-token-step1.png +0 -0
  49. package/dist/site/assets/guide/05-scope-api-token-step2.png +0 -0
  50. package/dist/site/assets/guide/06-scope-api-token-step3.png +0 -0
  51. package/dist/site/assets/guide/07-scope-api-token-step4.png +0 -0
  52. package/dist/site/assets/guide/web/document-preview.png +0 -0
  53. package/dist/site/assets/guide/web/remote-diff.png +0 -0
  54. package/dist/site/assets/guide/web/sync-workspace.png +0 -0
  55. package/dist/site/assets/pdf/cmaps/78-EUC-H.bcmap +0 -0
  56. package/dist/site/assets/pdf/cmaps/78-EUC-V.bcmap +0 -0
  57. package/dist/site/assets/pdf/cmaps/78-H.bcmap +0 -0
  58. package/dist/site/assets/pdf/cmaps/78-RKSJ-H.bcmap +0 -0
  59. package/dist/site/assets/pdf/cmaps/78-RKSJ-V.bcmap +0 -0
  60. package/dist/site/assets/pdf/cmaps/78-V.bcmap +0 -0
  61. package/dist/site/assets/pdf/cmaps/78ms-RKSJ-H.bcmap +0 -0
  62. package/dist/site/assets/pdf/cmaps/78ms-RKSJ-V.bcmap +0 -0
  63. package/dist/site/assets/pdf/cmaps/83pv-RKSJ-H.bcmap +0 -0
  64. package/dist/site/assets/pdf/cmaps/90ms-RKSJ-H.bcmap +0 -0
  65. package/dist/site/assets/pdf/cmaps/90ms-RKSJ-V.bcmap +0 -0
  66. package/dist/site/assets/pdf/cmaps/90msp-RKSJ-H.bcmap +0 -0
  67. package/dist/site/assets/pdf/cmaps/90msp-RKSJ-V.bcmap +0 -0
  68. package/dist/site/assets/pdf/cmaps/90pv-RKSJ-H.bcmap +0 -0
  69. package/dist/site/assets/pdf/cmaps/90pv-RKSJ-V.bcmap +0 -0
  70. package/dist/site/assets/pdf/cmaps/Add-H.bcmap +0 -0
  71. package/dist/site/assets/pdf/cmaps/Add-RKSJ-H.bcmap +0 -0
  72. package/dist/site/assets/pdf/cmaps/Add-RKSJ-V.bcmap +0 -0
  73. package/dist/site/assets/pdf/cmaps/Add-V.bcmap +0 -0
  74. package/dist/site/assets/pdf/cmaps/Adobe-CNS1-0.bcmap +0 -0
  75. package/dist/site/assets/pdf/cmaps/Adobe-CNS1-1.bcmap +0 -0
  76. package/dist/site/assets/pdf/cmaps/Adobe-CNS1-2.bcmap +0 -0
  77. package/dist/site/assets/pdf/cmaps/Adobe-CNS1-3.bcmap +0 -0
  78. package/dist/site/assets/pdf/cmaps/Adobe-CNS1-4.bcmap +0 -0
  79. package/dist/site/assets/pdf/cmaps/Adobe-CNS1-5.bcmap +0 -0
  80. package/dist/site/assets/pdf/cmaps/Adobe-CNS1-6.bcmap +0 -0
  81. package/dist/site/assets/pdf/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  82. package/dist/site/assets/pdf/cmaps/Adobe-GB1-0.bcmap +0 -0
  83. package/dist/site/assets/pdf/cmaps/Adobe-GB1-1.bcmap +0 -0
  84. package/dist/site/assets/pdf/cmaps/Adobe-GB1-2.bcmap +0 -0
  85. package/dist/site/assets/pdf/cmaps/Adobe-GB1-3.bcmap +0 -0
  86. package/dist/site/assets/pdf/cmaps/Adobe-GB1-4.bcmap +0 -0
  87. package/dist/site/assets/pdf/cmaps/Adobe-GB1-5.bcmap +0 -0
  88. package/dist/site/assets/pdf/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  89. package/dist/site/assets/pdf/cmaps/Adobe-Japan1-0.bcmap +0 -0
  90. package/dist/site/assets/pdf/cmaps/Adobe-Japan1-1.bcmap +0 -0
  91. package/dist/site/assets/pdf/cmaps/Adobe-Japan1-2.bcmap +0 -0
  92. package/dist/site/assets/pdf/cmaps/Adobe-Japan1-3.bcmap +0 -0
  93. package/dist/site/assets/pdf/cmaps/Adobe-Japan1-4.bcmap +0 -0
  94. package/dist/site/assets/pdf/cmaps/Adobe-Japan1-5.bcmap +0 -0
  95. package/dist/site/assets/pdf/cmaps/Adobe-Japan1-6.bcmap +0 -0
  96. package/dist/site/assets/pdf/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  97. package/dist/site/assets/pdf/cmaps/Adobe-Korea1-0.bcmap +0 -0
  98. package/dist/site/assets/pdf/cmaps/Adobe-Korea1-1.bcmap +0 -0
  99. package/dist/site/assets/pdf/cmaps/Adobe-Korea1-2.bcmap +0 -0
  100. package/dist/site/assets/pdf/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  101. package/dist/site/assets/pdf/cmaps/B5-H.bcmap +0 -0
  102. package/dist/site/assets/pdf/cmaps/B5-V.bcmap +0 -0
  103. package/dist/site/assets/pdf/cmaps/B5pc-H.bcmap +0 -0
  104. package/dist/site/assets/pdf/cmaps/B5pc-V.bcmap +0 -0
  105. package/dist/site/assets/pdf/cmaps/CNS-EUC-H.bcmap +0 -0
  106. package/dist/site/assets/pdf/cmaps/CNS-EUC-V.bcmap +0 -0
  107. package/dist/site/assets/pdf/cmaps/CNS1-H.bcmap +0 -0
  108. package/dist/site/assets/pdf/cmaps/CNS1-V.bcmap +0 -0
  109. package/dist/site/assets/pdf/cmaps/CNS2-H.bcmap +0 -0
  110. package/dist/site/assets/pdf/cmaps/CNS2-V.bcmap +3 -0
  111. package/dist/site/assets/pdf/cmaps/ETHK-B5-H.bcmap +0 -0
  112. package/dist/site/assets/pdf/cmaps/ETHK-B5-V.bcmap +0 -0
  113. package/dist/site/assets/pdf/cmaps/ETen-B5-H.bcmap +0 -0
  114. package/dist/site/assets/pdf/cmaps/ETen-B5-V.bcmap +0 -0
  115. package/dist/site/assets/pdf/cmaps/ETenms-B5-H.bcmap +3 -0
  116. package/dist/site/assets/pdf/cmaps/ETenms-B5-V.bcmap +0 -0
  117. package/dist/site/assets/pdf/cmaps/EUC-H.bcmap +0 -0
  118. package/dist/site/assets/pdf/cmaps/EUC-V.bcmap +0 -0
  119. package/dist/site/assets/pdf/cmaps/Ext-H.bcmap +0 -0
  120. package/dist/site/assets/pdf/cmaps/Ext-RKSJ-H.bcmap +0 -0
  121. package/dist/site/assets/pdf/cmaps/Ext-RKSJ-V.bcmap +0 -0
  122. package/dist/site/assets/pdf/cmaps/Ext-V.bcmap +0 -0
  123. package/dist/site/assets/pdf/cmaps/GB-EUC-H.bcmap +0 -0
  124. package/dist/site/assets/pdf/cmaps/GB-EUC-V.bcmap +0 -0
  125. package/dist/site/assets/pdf/cmaps/GB-H.bcmap +4 -0
  126. package/dist/site/assets/pdf/cmaps/GB-V.bcmap +0 -0
  127. package/dist/site/assets/pdf/cmaps/GBK-EUC-H.bcmap +0 -0
  128. package/dist/site/assets/pdf/cmaps/GBK-EUC-V.bcmap +0 -0
  129. package/dist/site/assets/pdf/cmaps/GBK2K-H.bcmap +0 -0
  130. package/dist/site/assets/pdf/cmaps/GBK2K-V.bcmap +0 -0
  131. package/dist/site/assets/pdf/cmaps/GBKp-EUC-H.bcmap +0 -0
  132. package/dist/site/assets/pdf/cmaps/GBKp-EUC-V.bcmap +0 -0
  133. package/dist/site/assets/pdf/cmaps/GBT-EUC-H.bcmap +0 -0
  134. package/dist/site/assets/pdf/cmaps/GBT-EUC-V.bcmap +0 -0
  135. package/dist/site/assets/pdf/cmaps/GBT-H.bcmap +0 -0
  136. package/dist/site/assets/pdf/cmaps/GBT-V.bcmap +0 -0
  137. package/dist/site/assets/pdf/cmaps/GBTpc-EUC-H.bcmap +0 -0
  138. package/dist/site/assets/pdf/cmaps/GBTpc-EUC-V.bcmap +0 -0
  139. package/dist/site/assets/pdf/cmaps/GBpc-EUC-H.bcmap +0 -0
  140. package/dist/site/assets/pdf/cmaps/GBpc-EUC-V.bcmap +0 -0
  141. package/dist/site/assets/pdf/cmaps/H.bcmap +0 -0
  142. package/dist/site/assets/pdf/cmaps/HKdla-B5-H.bcmap +0 -0
  143. package/dist/site/assets/pdf/cmaps/HKdla-B5-V.bcmap +0 -0
  144. package/dist/site/assets/pdf/cmaps/HKdlb-B5-H.bcmap +0 -0
  145. package/dist/site/assets/pdf/cmaps/HKdlb-B5-V.bcmap +0 -0
  146. package/dist/site/assets/pdf/cmaps/HKgccs-B5-H.bcmap +0 -0
  147. package/dist/site/assets/pdf/cmaps/HKgccs-B5-V.bcmap +0 -0
  148. package/dist/site/assets/pdf/cmaps/HKm314-B5-H.bcmap +0 -0
  149. package/dist/site/assets/pdf/cmaps/HKm314-B5-V.bcmap +0 -0
  150. package/dist/site/assets/pdf/cmaps/HKm471-B5-H.bcmap +0 -0
  151. package/dist/site/assets/pdf/cmaps/HKm471-B5-V.bcmap +0 -0
  152. package/dist/site/assets/pdf/cmaps/HKscs-B5-H.bcmap +0 -0
  153. package/dist/site/assets/pdf/cmaps/HKscs-B5-V.bcmap +0 -0
  154. package/dist/site/assets/pdf/cmaps/Hankaku.bcmap +0 -0
  155. package/dist/site/assets/pdf/cmaps/Hiragana.bcmap +0 -0
  156. package/dist/site/assets/pdf/cmaps/KSC-EUC-H.bcmap +0 -0
  157. package/dist/site/assets/pdf/cmaps/KSC-EUC-V.bcmap +0 -0
  158. package/dist/site/assets/pdf/cmaps/KSC-H.bcmap +0 -0
  159. package/dist/site/assets/pdf/cmaps/KSC-Johab-H.bcmap +0 -0
  160. package/dist/site/assets/pdf/cmaps/KSC-Johab-V.bcmap +0 -0
  161. package/dist/site/assets/pdf/cmaps/KSC-V.bcmap +0 -0
  162. package/dist/site/assets/pdf/cmaps/KSCms-UHC-H.bcmap +0 -0
  163. package/dist/site/assets/pdf/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  164. package/dist/site/assets/pdf/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  165. package/dist/site/assets/pdf/cmaps/KSCms-UHC-V.bcmap +0 -0
  166. package/dist/site/assets/pdf/cmaps/KSCpc-EUC-H.bcmap +0 -0
  167. package/dist/site/assets/pdf/cmaps/KSCpc-EUC-V.bcmap +0 -0
  168. package/dist/site/assets/pdf/cmaps/Katakana.bcmap +0 -0
  169. package/dist/site/assets/pdf/cmaps/LICENSE +36 -0
  170. package/dist/site/assets/pdf/cmaps/NWP-H.bcmap +0 -0
  171. package/dist/site/assets/pdf/cmaps/NWP-V.bcmap +0 -0
  172. package/dist/site/assets/pdf/cmaps/RKSJ-H.bcmap +0 -0
  173. package/dist/site/assets/pdf/cmaps/RKSJ-V.bcmap +0 -0
  174. package/dist/site/assets/pdf/cmaps/Roman.bcmap +0 -0
  175. package/dist/site/assets/pdf/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  176. package/dist/site/assets/pdf/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  177. package/dist/site/assets/pdf/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  178. package/dist/site/assets/pdf/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  179. package/dist/site/assets/pdf/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  180. package/dist/site/assets/pdf/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  181. package/dist/site/assets/pdf/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  182. package/dist/site/assets/pdf/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  183. package/dist/site/assets/pdf/cmaps/UniGB-UCS2-H.bcmap +0 -0
  184. package/dist/site/assets/pdf/cmaps/UniGB-UCS2-V.bcmap +0 -0
  185. package/dist/site/assets/pdf/cmaps/UniGB-UTF16-H.bcmap +0 -0
  186. package/dist/site/assets/pdf/cmaps/UniGB-UTF16-V.bcmap +0 -0
  187. package/dist/site/assets/pdf/cmaps/UniGB-UTF32-H.bcmap +0 -0
  188. package/dist/site/assets/pdf/cmaps/UniGB-UTF32-V.bcmap +0 -0
  189. package/dist/site/assets/pdf/cmaps/UniGB-UTF8-H.bcmap +0 -0
  190. package/dist/site/assets/pdf/cmaps/UniGB-UTF8-V.bcmap +0 -0
  191. package/dist/site/assets/pdf/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  192. package/dist/site/assets/pdf/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  193. package/dist/site/assets/pdf/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  194. package/dist/site/assets/pdf/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  195. package/dist/site/assets/pdf/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  196. package/dist/site/assets/pdf/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  197. package/dist/site/assets/pdf/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  198. package/dist/site/assets/pdf/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  199. package/dist/site/assets/pdf/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  200. package/dist/site/assets/pdf/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  201. package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  202. package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  203. package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  204. package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  205. package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  206. package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  207. package/dist/site/assets/pdf/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  208. package/dist/site/assets/pdf/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  209. package/dist/site/assets/pdf/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  210. package/dist/site/assets/pdf/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  211. package/dist/site/assets/pdf/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  212. package/dist/site/assets/pdf/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  213. package/dist/site/assets/pdf/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  214. package/dist/site/assets/pdf/cmaps/UniKS-UCS2-H.bcmap +0 -0
  215. package/dist/site/assets/pdf/cmaps/UniKS-UCS2-V.bcmap +0 -0
  216. package/dist/site/assets/pdf/cmaps/UniKS-UTF16-H.bcmap +0 -0
  217. package/dist/site/assets/pdf/cmaps/UniKS-UTF16-V.bcmap +0 -0
  218. package/dist/site/assets/pdf/cmaps/UniKS-UTF32-H.bcmap +0 -0
  219. package/dist/site/assets/pdf/cmaps/UniKS-UTF32-V.bcmap +0 -0
  220. package/dist/site/assets/pdf/cmaps/UniKS-UTF8-H.bcmap +0 -0
  221. package/dist/site/assets/pdf/cmaps/UniKS-UTF8-V.bcmap +0 -0
  222. package/dist/site/assets/pdf/cmaps/V.bcmap +0 -0
  223. package/dist/site/assets/pdf/cmaps/WP-Symbol.bcmap +0 -0
  224. package/dist/site/assets/pdf/standard_fonts/FoxitDingbats.pfb +0 -0
  225. package/dist/site/assets/pdf/standard_fonts/FoxitFixed.pfb +0 -0
  226. package/dist/site/assets/pdf/standard_fonts/FoxitFixedBold.pfb +0 -0
  227. package/dist/site/assets/pdf/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  228. package/dist/site/assets/pdf/standard_fonts/FoxitFixedItalic.pfb +0 -0
  229. package/dist/site/assets/pdf/standard_fonts/FoxitSerif.pfb +0 -0
  230. package/dist/site/assets/pdf/standard_fonts/FoxitSerifBold.pfb +0 -0
  231. package/dist/site/assets/pdf/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  232. package/dist/site/assets/pdf/standard_fonts/FoxitSerifItalic.pfb +0 -0
  233. package/dist/site/assets/pdf/standard_fonts/FoxitSymbol.pfb +0 -0
  234. package/dist/site/assets/pdf/standard_fonts/LICENSE_FOXIT +27 -0
  235. package/dist/site/assets/pdf/standard_fonts/LICENSE_LIBERATION +363 -0
  236. package/dist/site/assets/pdf/standard_fonts/LiberationSans-Bold.ttf +0 -0
  237. package/dist/site/assets/pdf/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  238. package/dist/site/assets/pdf/standard_fonts/LiberationSans-Italic.ttf +0 -0
  239. package/dist/site/assets/pdf/standard_fonts/LiberationSans-Regular.ttf +0 -0
  240. package/dist/site/assets/pdf/wasm/LICENSE_JBIG2 +196 -0
  241. package/dist/site/assets/pdf/wasm/LICENSE_OPENJPEG +39 -0
  242. package/dist/site/assets/pdf/wasm/LICENSE_PDFJS_JBIG2 +13 -0
  243. package/dist/site/assets/pdf/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
  244. package/dist/site/assets/pdf/wasm/LICENSE_PDFJS_QCMS +19 -0
  245. package/dist/site/assets/pdf/wasm/LICENSE_QCMS +21 -0
  246. package/dist/site/assets/pdf/wasm/jbig2.wasm +0 -0
  247. package/dist/site/assets/pdf/wasm/jbig2_nowasm_fallback.js +15 -0
  248. package/dist/site/assets/pdf/wasm/openjpeg.wasm +0 -0
  249. package/dist/site/assets/pdf/wasm/openjpeg_nowasm_fallback.js +17 -0
  250. package/dist/site/assets/pdf/wasm/qcms_bg.wasm +0 -0
  251. package/dist/site/assets/pdf/wasm/quickjs-eval.js +16 -0
  252. package/dist/site/assets/pdf/wasm/quickjs-eval.wasm +0 -0
  253. package/dist/site/assets/pdf.worker.min.mjs +29 -0
  254. package/dist/site/assets/workspace.worker.js +409 -0
  255. package/dist/site/index.html +253 -0
  256. package/dist/sync/baseline.js +45 -0
  257. package/dist/sync/changes.js +51 -0
  258. package/dist/sync/diff.js +80 -0
  259. package/dist/sync/fetch.js +287 -0
  260. package/dist/sync/ignore.js +4 -4
  261. package/dist/sync/local-state.js +12 -0
  262. package/dist/sync/mapping.js +31 -8
  263. package/dist/sync/metadata.js +57 -0
  264. package/dist/sync/prepare.js +52 -0
  265. package/dist/sync/pull.js +618 -141
  266. package/dist/sync/push.js +262 -207
  267. package/dist/sync/reconcile.js +51 -0
  268. package/dist/sync/references.js +2 -2
  269. package/dist/sync/remap.js +17 -5
  270. package/dist/sync/render.js +3 -2
  271. package/dist/sync/restore.js +28 -0
  272. package/dist/sync/scope.js +47 -0
  273. package/dist/sync/staging.js +193 -0
  274. package/dist/sync/workspace.js +100 -0
  275. package/dist/web/assets/PDFJS-LICENSE.txt +177 -0
  276. package/dist/web/assets/app.css +1 -1
  277. package/dist/web/assets/app.js +3 -938
  278. package/dist/web/assets/chunks/chunk-TEW74QPN.js +35 -0
  279. package/dist/web/assets/chunks/markdown-S6623E66.js +1 -0
  280. package/dist/web/assets/chunks/monaco-36ZGUH4Y.js +897 -0
  281. package/dist/web/assets/chunks/monaco-HFLEBJTI.css +1 -0
  282. package/dist/web/assets/chunks/pdf-DDN7TXTP.js +55 -0
  283. package/dist/web/assets/confluence-env-example.txt +32 -0
  284. package/dist/web/assets/editor.worker.js +1 -30
  285. package/dist/web/assets/favicon.svg +3 -0
  286. package/dist/web/assets/guide/01-account-settings.png +0 -0
  287. package/dist/web/assets/guide/02-atlassian-security.png +0 -0
  288. package/dist/web/assets/guide/03-api-tokens.png +0 -0
  289. package/dist/web/assets/guide/04-scope-api-token-step1.png +0 -0
  290. package/dist/web/assets/guide/05-scope-api-token-step2.png +0 -0
  291. package/dist/web/assets/guide/06-scope-api-token-step3.png +0 -0
  292. package/dist/web/assets/guide/07-scope-api-token-step4.png +0 -0
  293. package/dist/web/assets/guide/web/document-preview.png +0 -0
  294. package/dist/web/assets/guide/web/remote-diff.png +0 -0
  295. package/dist/web/assets/guide/web/sync-workspace.png +0 -0
  296. package/dist/web/assets/pdf/cmaps/78-EUC-H.bcmap +0 -0
  297. package/dist/web/assets/pdf/cmaps/78-EUC-V.bcmap +0 -0
  298. package/dist/web/assets/pdf/cmaps/78-H.bcmap +0 -0
  299. package/dist/web/assets/pdf/cmaps/78-RKSJ-H.bcmap +0 -0
  300. package/dist/web/assets/pdf/cmaps/78-RKSJ-V.bcmap +0 -0
  301. package/dist/web/assets/pdf/cmaps/78-V.bcmap +0 -0
  302. package/dist/web/assets/pdf/cmaps/78ms-RKSJ-H.bcmap +0 -0
  303. package/dist/web/assets/pdf/cmaps/78ms-RKSJ-V.bcmap +0 -0
  304. package/dist/web/assets/pdf/cmaps/83pv-RKSJ-H.bcmap +0 -0
  305. package/dist/web/assets/pdf/cmaps/90ms-RKSJ-H.bcmap +0 -0
  306. package/dist/web/assets/pdf/cmaps/90ms-RKSJ-V.bcmap +0 -0
  307. package/dist/web/assets/pdf/cmaps/90msp-RKSJ-H.bcmap +0 -0
  308. package/dist/web/assets/pdf/cmaps/90msp-RKSJ-V.bcmap +0 -0
  309. package/dist/web/assets/pdf/cmaps/90pv-RKSJ-H.bcmap +0 -0
  310. package/dist/web/assets/pdf/cmaps/90pv-RKSJ-V.bcmap +0 -0
  311. package/dist/web/assets/pdf/cmaps/Add-H.bcmap +0 -0
  312. package/dist/web/assets/pdf/cmaps/Add-RKSJ-H.bcmap +0 -0
  313. package/dist/web/assets/pdf/cmaps/Add-RKSJ-V.bcmap +0 -0
  314. package/dist/web/assets/pdf/cmaps/Add-V.bcmap +0 -0
  315. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-0.bcmap +0 -0
  316. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-1.bcmap +0 -0
  317. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-2.bcmap +0 -0
  318. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-3.bcmap +0 -0
  319. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-4.bcmap +0 -0
  320. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-5.bcmap +0 -0
  321. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-6.bcmap +0 -0
  322. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  323. package/dist/web/assets/pdf/cmaps/Adobe-GB1-0.bcmap +0 -0
  324. package/dist/web/assets/pdf/cmaps/Adobe-GB1-1.bcmap +0 -0
  325. package/dist/web/assets/pdf/cmaps/Adobe-GB1-2.bcmap +0 -0
  326. package/dist/web/assets/pdf/cmaps/Adobe-GB1-3.bcmap +0 -0
  327. package/dist/web/assets/pdf/cmaps/Adobe-GB1-4.bcmap +0 -0
  328. package/dist/web/assets/pdf/cmaps/Adobe-GB1-5.bcmap +0 -0
  329. package/dist/web/assets/pdf/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  330. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-0.bcmap +0 -0
  331. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-1.bcmap +0 -0
  332. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-2.bcmap +0 -0
  333. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-3.bcmap +0 -0
  334. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-4.bcmap +0 -0
  335. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-5.bcmap +0 -0
  336. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-6.bcmap +0 -0
  337. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  338. package/dist/web/assets/pdf/cmaps/Adobe-Korea1-0.bcmap +0 -0
  339. package/dist/web/assets/pdf/cmaps/Adobe-Korea1-1.bcmap +0 -0
  340. package/dist/web/assets/pdf/cmaps/Adobe-Korea1-2.bcmap +0 -0
  341. package/dist/web/assets/pdf/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  342. package/dist/web/assets/pdf/cmaps/B5-H.bcmap +0 -0
  343. package/dist/web/assets/pdf/cmaps/B5-V.bcmap +0 -0
  344. package/dist/web/assets/pdf/cmaps/B5pc-H.bcmap +0 -0
  345. package/dist/web/assets/pdf/cmaps/B5pc-V.bcmap +0 -0
  346. package/dist/web/assets/pdf/cmaps/CNS-EUC-H.bcmap +0 -0
  347. package/dist/web/assets/pdf/cmaps/CNS-EUC-V.bcmap +0 -0
  348. package/dist/web/assets/pdf/cmaps/CNS1-H.bcmap +0 -0
  349. package/dist/web/assets/pdf/cmaps/CNS1-V.bcmap +0 -0
  350. package/dist/web/assets/pdf/cmaps/CNS2-H.bcmap +0 -0
  351. package/dist/web/assets/pdf/cmaps/CNS2-V.bcmap +3 -0
  352. package/dist/web/assets/pdf/cmaps/ETHK-B5-H.bcmap +0 -0
  353. package/dist/web/assets/pdf/cmaps/ETHK-B5-V.bcmap +0 -0
  354. package/dist/web/assets/pdf/cmaps/ETen-B5-H.bcmap +0 -0
  355. package/dist/web/assets/pdf/cmaps/ETen-B5-V.bcmap +0 -0
  356. package/dist/web/assets/pdf/cmaps/ETenms-B5-H.bcmap +3 -0
  357. package/dist/web/assets/pdf/cmaps/ETenms-B5-V.bcmap +0 -0
  358. package/dist/web/assets/pdf/cmaps/EUC-H.bcmap +0 -0
  359. package/dist/web/assets/pdf/cmaps/EUC-V.bcmap +0 -0
  360. package/dist/web/assets/pdf/cmaps/Ext-H.bcmap +0 -0
  361. package/dist/web/assets/pdf/cmaps/Ext-RKSJ-H.bcmap +0 -0
  362. package/dist/web/assets/pdf/cmaps/Ext-RKSJ-V.bcmap +0 -0
  363. package/dist/web/assets/pdf/cmaps/Ext-V.bcmap +0 -0
  364. package/dist/web/assets/pdf/cmaps/GB-EUC-H.bcmap +0 -0
  365. package/dist/web/assets/pdf/cmaps/GB-EUC-V.bcmap +0 -0
  366. package/dist/web/assets/pdf/cmaps/GB-H.bcmap +4 -0
  367. package/dist/web/assets/pdf/cmaps/GB-V.bcmap +0 -0
  368. package/dist/web/assets/pdf/cmaps/GBK-EUC-H.bcmap +0 -0
  369. package/dist/web/assets/pdf/cmaps/GBK-EUC-V.bcmap +0 -0
  370. package/dist/web/assets/pdf/cmaps/GBK2K-H.bcmap +0 -0
  371. package/dist/web/assets/pdf/cmaps/GBK2K-V.bcmap +0 -0
  372. package/dist/web/assets/pdf/cmaps/GBKp-EUC-H.bcmap +0 -0
  373. package/dist/web/assets/pdf/cmaps/GBKp-EUC-V.bcmap +0 -0
  374. package/dist/web/assets/pdf/cmaps/GBT-EUC-H.bcmap +0 -0
  375. package/dist/web/assets/pdf/cmaps/GBT-EUC-V.bcmap +0 -0
  376. package/dist/web/assets/pdf/cmaps/GBT-H.bcmap +0 -0
  377. package/dist/web/assets/pdf/cmaps/GBT-V.bcmap +0 -0
  378. package/dist/web/assets/pdf/cmaps/GBTpc-EUC-H.bcmap +0 -0
  379. package/dist/web/assets/pdf/cmaps/GBTpc-EUC-V.bcmap +0 -0
  380. package/dist/web/assets/pdf/cmaps/GBpc-EUC-H.bcmap +0 -0
  381. package/dist/web/assets/pdf/cmaps/GBpc-EUC-V.bcmap +0 -0
  382. package/dist/web/assets/pdf/cmaps/H.bcmap +0 -0
  383. package/dist/web/assets/pdf/cmaps/HKdla-B5-H.bcmap +0 -0
  384. package/dist/web/assets/pdf/cmaps/HKdla-B5-V.bcmap +0 -0
  385. package/dist/web/assets/pdf/cmaps/HKdlb-B5-H.bcmap +0 -0
  386. package/dist/web/assets/pdf/cmaps/HKdlb-B5-V.bcmap +0 -0
  387. package/dist/web/assets/pdf/cmaps/HKgccs-B5-H.bcmap +0 -0
  388. package/dist/web/assets/pdf/cmaps/HKgccs-B5-V.bcmap +0 -0
  389. package/dist/web/assets/pdf/cmaps/HKm314-B5-H.bcmap +0 -0
  390. package/dist/web/assets/pdf/cmaps/HKm314-B5-V.bcmap +0 -0
  391. package/dist/web/assets/pdf/cmaps/HKm471-B5-H.bcmap +0 -0
  392. package/dist/web/assets/pdf/cmaps/HKm471-B5-V.bcmap +0 -0
  393. package/dist/web/assets/pdf/cmaps/HKscs-B5-H.bcmap +0 -0
  394. package/dist/web/assets/pdf/cmaps/HKscs-B5-V.bcmap +0 -0
  395. package/dist/web/assets/pdf/cmaps/Hankaku.bcmap +0 -0
  396. package/dist/web/assets/pdf/cmaps/Hiragana.bcmap +0 -0
  397. package/dist/web/assets/pdf/cmaps/KSC-EUC-H.bcmap +0 -0
  398. package/dist/web/assets/pdf/cmaps/KSC-EUC-V.bcmap +0 -0
  399. package/dist/web/assets/pdf/cmaps/KSC-H.bcmap +0 -0
  400. package/dist/web/assets/pdf/cmaps/KSC-Johab-H.bcmap +0 -0
  401. package/dist/web/assets/pdf/cmaps/KSC-Johab-V.bcmap +0 -0
  402. package/dist/web/assets/pdf/cmaps/KSC-V.bcmap +0 -0
  403. package/dist/web/assets/pdf/cmaps/KSCms-UHC-H.bcmap +0 -0
  404. package/dist/web/assets/pdf/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  405. package/dist/web/assets/pdf/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  406. package/dist/web/assets/pdf/cmaps/KSCms-UHC-V.bcmap +0 -0
  407. package/dist/web/assets/pdf/cmaps/KSCpc-EUC-H.bcmap +0 -0
  408. package/dist/web/assets/pdf/cmaps/KSCpc-EUC-V.bcmap +0 -0
  409. package/dist/web/assets/pdf/cmaps/Katakana.bcmap +0 -0
  410. package/dist/web/assets/pdf/cmaps/LICENSE +36 -0
  411. package/dist/web/assets/pdf/cmaps/NWP-H.bcmap +0 -0
  412. package/dist/web/assets/pdf/cmaps/NWP-V.bcmap +0 -0
  413. package/dist/web/assets/pdf/cmaps/RKSJ-H.bcmap +0 -0
  414. package/dist/web/assets/pdf/cmaps/RKSJ-V.bcmap +0 -0
  415. package/dist/web/assets/pdf/cmaps/Roman.bcmap +0 -0
  416. package/dist/web/assets/pdf/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  417. package/dist/web/assets/pdf/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  418. package/dist/web/assets/pdf/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  419. package/dist/web/assets/pdf/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  420. package/dist/web/assets/pdf/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  421. package/dist/web/assets/pdf/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  422. package/dist/web/assets/pdf/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  423. package/dist/web/assets/pdf/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  424. package/dist/web/assets/pdf/cmaps/UniGB-UCS2-H.bcmap +0 -0
  425. package/dist/web/assets/pdf/cmaps/UniGB-UCS2-V.bcmap +0 -0
  426. package/dist/web/assets/pdf/cmaps/UniGB-UTF16-H.bcmap +0 -0
  427. package/dist/web/assets/pdf/cmaps/UniGB-UTF16-V.bcmap +0 -0
  428. package/dist/web/assets/pdf/cmaps/UniGB-UTF32-H.bcmap +0 -0
  429. package/dist/web/assets/pdf/cmaps/UniGB-UTF32-V.bcmap +0 -0
  430. package/dist/web/assets/pdf/cmaps/UniGB-UTF8-H.bcmap +0 -0
  431. package/dist/web/assets/pdf/cmaps/UniGB-UTF8-V.bcmap +0 -0
  432. package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  433. package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  434. package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  435. package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  436. package/dist/web/assets/pdf/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  437. package/dist/web/assets/pdf/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  438. package/dist/web/assets/pdf/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  439. package/dist/web/assets/pdf/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  440. package/dist/web/assets/pdf/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  441. package/dist/web/assets/pdf/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  442. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  443. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  444. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  445. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  446. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  447. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  448. package/dist/web/assets/pdf/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  449. package/dist/web/assets/pdf/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  450. package/dist/web/assets/pdf/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  451. package/dist/web/assets/pdf/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  452. package/dist/web/assets/pdf/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  453. package/dist/web/assets/pdf/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  454. package/dist/web/assets/pdf/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  455. package/dist/web/assets/pdf/cmaps/UniKS-UCS2-H.bcmap +0 -0
  456. package/dist/web/assets/pdf/cmaps/UniKS-UCS2-V.bcmap +0 -0
  457. package/dist/web/assets/pdf/cmaps/UniKS-UTF16-H.bcmap +0 -0
  458. package/dist/web/assets/pdf/cmaps/UniKS-UTF16-V.bcmap +0 -0
  459. package/dist/web/assets/pdf/cmaps/UniKS-UTF32-H.bcmap +0 -0
  460. package/dist/web/assets/pdf/cmaps/UniKS-UTF32-V.bcmap +0 -0
  461. package/dist/web/assets/pdf/cmaps/UniKS-UTF8-H.bcmap +0 -0
  462. package/dist/web/assets/pdf/cmaps/UniKS-UTF8-V.bcmap +0 -0
  463. package/dist/web/assets/pdf/cmaps/V.bcmap +0 -0
  464. package/dist/web/assets/pdf/cmaps/WP-Symbol.bcmap +0 -0
  465. package/dist/web/assets/pdf/standard_fonts/FoxitDingbats.pfb +0 -0
  466. package/dist/web/assets/pdf/standard_fonts/FoxitFixed.pfb +0 -0
  467. package/dist/web/assets/pdf/standard_fonts/FoxitFixedBold.pfb +0 -0
  468. package/dist/web/assets/pdf/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  469. package/dist/web/assets/pdf/standard_fonts/FoxitFixedItalic.pfb +0 -0
  470. package/dist/web/assets/pdf/standard_fonts/FoxitSerif.pfb +0 -0
  471. package/dist/web/assets/pdf/standard_fonts/FoxitSerifBold.pfb +0 -0
  472. package/dist/web/assets/pdf/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  473. package/dist/web/assets/pdf/standard_fonts/FoxitSerifItalic.pfb +0 -0
  474. package/dist/web/assets/pdf/standard_fonts/FoxitSymbol.pfb +0 -0
  475. package/dist/web/assets/pdf/standard_fonts/LICENSE_FOXIT +27 -0
  476. package/dist/web/assets/pdf/standard_fonts/LICENSE_LIBERATION +363 -0
  477. package/dist/web/assets/pdf/standard_fonts/LiberationSans-Bold.ttf +0 -0
  478. package/dist/web/assets/pdf/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  479. package/dist/web/assets/pdf/standard_fonts/LiberationSans-Italic.ttf +0 -0
  480. package/dist/web/assets/pdf/standard_fonts/LiberationSans-Regular.ttf +0 -0
  481. package/dist/web/assets/pdf/wasm/LICENSE_JBIG2 +196 -0
  482. package/dist/web/assets/pdf/wasm/LICENSE_OPENJPEG +39 -0
  483. package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_JBIG2 +13 -0
  484. package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
  485. package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_QCMS +19 -0
  486. package/dist/web/assets/pdf/wasm/LICENSE_QCMS +21 -0
  487. package/dist/web/assets/pdf/wasm/jbig2.wasm +0 -0
  488. package/dist/web/assets/pdf/wasm/jbig2_nowasm_fallback.js +15 -0
  489. package/dist/web/assets/pdf/wasm/openjpeg.wasm +0 -0
  490. package/dist/web/assets/pdf/wasm/openjpeg_nowasm_fallback.js +17 -0
  491. package/dist/web/assets/pdf/wasm/qcms_bg.wasm +0 -0
  492. package/dist/web/assets/pdf/wasm/quickjs-eval.js +16 -0
  493. package/dist/web/assets/pdf/wasm/quickjs-eval.wasm +0 -0
  494. package/dist/web/assets/pdf.worker.min.mjs +29 -0
  495. package/dist/web/assets/workspace.worker.js +409 -0
  496. package/dist/web/document-source.js +14 -0
  497. package/dist/web/hosted-files.js +189 -0
  498. package/dist/web/hosted.js +316 -0
  499. package/dist/web/node-runner.js +26 -0
  500. package/dist/web/preview.js +74 -0
  501. package/dist/web/server.js +85 -240
  502. package/dist/web/sync.js +365 -71
  503. package/dist/web/table-editor-script.js +99 -0
  504. package/dist/web/table-format.js +224 -0
  505. package/dist/web/views/help.js +15 -10
  506. package/dist/web/views/sync-panel.js +90 -19
  507. package/dist/web/views/token-guide.js +27 -0
  508. package/dist/web/views/usage-guide.js +12 -0
  509. package/dist/web/views/workspace.js +85 -15
  510. package/dist/web/workspace-api.js +371 -0
  511. package/docs/user/CLI.md +366 -0
  512. package/docs/user/CONFIGURATION.md +125 -0
  513. package/docs/user/OBSIDIAN.md +117 -0
  514. package/docs/user/WEB.md +90 -0
  515. package/docs/user/images/web/document-preview.png +0 -0
  516. package/docs/user/images/web/remote-diff.png +0 -0
  517. package/docs/user/images/web/sync-workspace.png +0 -0
  518. package/package.json +23 -6
@@ -4,7 +4,8 @@
4
4
  */
5
5
  import { readFileSync, existsSync } from 'node:fs';
6
6
  import { relative } from 'node:path';
7
- import { red, yellow, dim } from '../shared/colors.js';
7
+ import { confluenceFetch as fetch } from '../runtime/network.js';
8
+ import { red, yellow } from '../shared/colors.js';
8
9
  class ConfluenceApiError extends Error {
9
10
  status;
10
11
  responseBody;
@@ -14,26 +15,39 @@ class ConfluenceApiError extends Error {
14
15
  this.responseBody = responseBody;
15
16
  }
16
17
  }
18
+ /** Distinguishes missing binary content from authorization and transport failures. */
19
+ export class ConfluenceDownloadError extends Error {
20
+ status;
21
+ constructor(status, statusText, path) {
22
+ super(`download ${status} ${statusText}\n${path}`);
23
+ this.status = status;
24
+ }
25
+ }
17
26
  export function createClient(cfg, opts) {
18
27
  const authHeader = () => 'Basic ' + Buffer.from(`${cfg.email}:${cfg.token}`).toString('base64');
19
- async function api(path, init) {
20
- const res = await fetch(`${cfg.baseUrl}${path}`, {
21
- ...init,
22
- headers: {
23
- Authorization: authHeader(),
24
- 'Content-Type': 'application/json',
25
- Accept: 'application/json',
26
- ...(init?.headers ?? {}),
27
- },
28
- });
29
- if (!res.ok) {
30
- throw new ConfluenceApiError(res.status, path, await res.text(), res.statusText);
28
+ async function api(path, init, onHeaders) {
29
+ const signal = opts.requestTimeoutMs ? AbortSignal.timeout(opts.requestTimeoutMs) : undefined;
30
+ try {
31
+ const res = await fetch(`${cfg.baseUrl}${path}`, {
32
+ ...init, ...(signal ? { signal } : {}),
33
+ headers: {
34
+ Authorization: authHeader(), 'Content-Type': 'application/json', Accept: 'application/json', ...(init?.headers ?? {}),
35
+ },
36
+ });
37
+ if (!res.ok)
38
+ throw new ConfluenceApiError(res.status, path, await res.text(), res.statusText);
39
+ onHeaders?.(res.headers);
40
+ return res.status === 204 ? null : await res.json();
41
+ }
42
+ catch (error) {
43
+ if (signal?.aborted)
44
+ throw new Error(`Confluence 메타데이터 응답 대기 시간이 초과되었습니다 (${opts.requestTimeoutMs / 1000}초). 잠시 후 다시 시도하세요. 요청: ${path}`);
45
+ throw error;
31
46
  }
32
- return res.status === 204 ? null : res.json();
33
47
  }
34
48
  /** 페이지 조회. 삭제(휴지통)·부재면 404 → null 반환(그 외 오류는 throw) */
35
- async function getPageOrNull(pageId) {
36
- const res = await fetch(`${cfg.baseUrl}/api/v2/pages/${pageId}`, {
49
+ async function getPageOrNull(pageId, withBody = false) {
50
+ const res = await fetch(`${cfg.baseUrl}/api/v2/pages/${pageId}${withBody ? '?body-format=storage' : ''}`, {
37
51
  headers: { Authorization: authHeader(), Accept: 'application/json' },
38
52
  });
39
53
  if (res.status === 404)
@@ -51,84 +65,122 @@ export function createClient(cfg, opts) {
51
65
  const space = data.results?.[0];
52
66
  if (!space)
53
67
  throw new Error(`Space key '${key}' 를 찾을 수 없습니다.`);
54
- return { id: space.id, homepageId: space.homepageId };
55
- }
56
- /** 매핑에 기록된 페이지를 모두 삭제(휴지통). 매핑 파일 영속화는 호출자 책임. */
57
- async function deleteAll(mapping) {
58
- // 페이지(자식) 먼저, 폴더(컨테이너) 나중에 삭제
59
- const entries = Object.entries(mapping).sort(([, a], [, b]) => (a.type === 'folder' ? 1 : 0) - (b.type === 'folder' ? 1 : 0));
60
- console.log(yellow(`--rebuild: 기존 ${entries.length}건 삭제`));
61
- for (const [key, { pageId, type }] of entries) {
62
- try {
63
- await api(type === 'folder' ? `/api/v2/folders/${pageId}` : `/api/v2/pages/${pageId}`, { method: 'DELETE' });
64
- console.log(` ${yellow('🗑 삭제')} ${key} ${dim(`(#${pageId})`)}`);
65
- }
66
- catch (e) {
67
- console.error(red(` ✗ 삭제 실패 ${key}`) + `\n${e.message}`);
68
- }
69
- }
68
+ return { id: String(space.id), homepageId: space.homepageId ? String(space.homepageId) : undefined };
70
69
  }
71
70
  /** 페이지의 기존 첨부 중 같은 파일명의 attachmentId 를 찾는다(없으면 null). */
72
71
  async function findAttachmentId(pageId, filename) {
73
- try {
74
- const data = await api(`/rest/api/content/${pageId}/child/attachment?filename=${encodeURIComponent(filename)}`);
75
- return data?.results?.[0]?.id ?? null;
76
- }
77
- catch {
78
- return null;
79
- }
72
+ const data = await api(`/rest/api/content/${pageId}/child/attachment?filename=${encodeURIComponent(filename)}`);
73
+ return data?.results?.[0]?.id ?? null;
80
74
  }
81
75
  /**
82
- * 로컬 이미지를 페이지 첨부로 업로드(v1, multipart).
76
+ * 로컬 파일을 페이지 첨부로 업로드(v1, multipart).
83
77
  * 같은 파일명 첨부가 이미 있으면 데이터만 새 버전으로 갱신(중복 생성 400 방지),
84
78
  * 없으면 신규 생성한다.
85
79
  */
86
80
  async function uploadAttachment(pageId, filename, abs) {
87
81
  const buf = readFileSync(abs);
88
- const form = new FormData();
89
- form.append('file', new Blob([buf]), filename);
90
- form.append('minorEdit', 'true');
91
- const attId = await findAttachmentId(pageId, filename);
92
- const path = attId
93
- ? `/rest/api/content/${pageId}/child/attachment/${attId}/data` // 기존 첨부 데이터 갱신
94
- : `/rest/api/content/${pageId}/child/attachment`; // 신규 첨부 생성
95
- const res = await fetch(`${cfg.baseUrl}${path}`, {
96
- method: 'POST',
97
- headers: { Authorization: authHeader(), 'X-Atlassian-Token': 'nocheck' },
98
- body: form,
99
- });
100
- if (!res.ok)
101
- throw new Error(`attachment ${res.status} ${res.statusText}\n${await res.text()}`);
82
+ // 롤백 오류에만 재시도한다. 매번 첨부를 재조회해 생성/갱신 경로를 다시 결정한다.
83
+ for (let attempt = 0; attempt < 3; attempt++) {
84
+ const attId = await findAttachmentId(pageId, filename);
85
+ await assertInScope(pageId);
86
+ const path = attId
87
+ ? `/rest/api/content/${pageId}/child/attachment/${attId}/data`
88
+ : `/rest/api/content/${pageId}/child/attachment`;
89
+ const form = new FormData();
90
+ form.append('file', new Blob([buf]), filename);
91
+ form.append('minorEdit', 'true');
92
+ const res = await fetch(`${cfg.baseUrl}${path}`, {
93
+ method: 'POST',
94
+ headers: { Authorization: authHeader(), 'X-Atlassian-Token': 'nocheck' },
95
+ body: form,
96
+ });
97
+ if (res.ok)
98
+ return;
99
+ const detail = await res.text();
100
+ if (res.status === 500 && /UnexpectedRollbackException|rollback-only/i.test(detail) && attempt < 2) {
101
+ console.error(yellow(` ↻ 첨부 트랜잭션 롤백, 재시도 ${attempt + 1}/2: ${filename}`));
102
+ await new Promise(resolve => setTimeout(resolve, 1000 * 2 ** attempt));
103
+ continue;
104
+ }
105
+ const traceId = res.headers.get('atl-traceid') ?? res.headers.get('x-arequestid');
106
+ throw new Error(`attachment ${res.status} ${res.statusText}\nPOST ${path}${traceId ? `\ntraceId: ${traceId}` : ''}\n${detail}`);
107
+ }
102
108
  }
103
- /** 페이지의 로컬 이미지들을 업로드. 성공 개수 반환. baseDir 은 로그 표시용. */
104
- async function uploadImages(pageId, images, baseDir) {
109
+ /** 페이지의 로컬 첨부를 업로드. 성공 개수 반환. */
110
+ async function uploadAttachments(pageId, attachments, baseDir) {
105
111
  let ok = 0;
106
- for (const img of images) {
107
- if (!existsSync(img.abs)) {
108
- console.error(yellow(` ⚠ 이미지 없음: ${relative(baseDir, img.abs)}`));
112
+ for (const file of attachments) {
113
+ if (!existsSync(file.abs)) {
114
+ console.error(yellow(` ⚠ 첨부 없음: ${relative(baseDir, file.abs)}`));
109
115
  continue;
110
116
  }
111
117
  try {
112
- await uploadAttachment(pageId, img.filename, img.abs);
118
+ await uploadAttachment(pageId, file.filename, file.abs);
113
119
  ok++;
114
120
  }
115
121
  catch (e) {
116
- console.error(red(` ✗ 이미지 업로드 실패 ${img.filename}`) + `\n${e.message}`);
122
+ console.error(red(` ✗ 첨부 업로드 실패 ${file.filename}`) + `\n${e.message}`);
117
123
  }
118
124
  }
119
125
  return ok;
120
126
  }
121
- /** 페이지 생성/갱신. 삭제 감지 시 재생성. 변경 없으면(opts.verify 아니면) 호출 없이 skip. */
127
+ const uploadImages = uploadAttachments;
128
+ /** Expanded metadata only: scope selection never downloads page bodies. */
129
+ async function getContentInfo(id) {
130
+ const data = await getV1(`/rest/api/content/${encodeURIComponent(id)}?expand=ancestors,space,version`);
131
+ if (String(data.id) !== id || !['page', 'folder'].includes(data.type) || !data.space?.id || !Array.isArray(data.ancestors))
132
+ throw new Error(`콘텐츠 위치를 확인할 수 없습니다: ${id}`);
133
+ const parentId = data.parentId ?? data.ancestors.at(-1)?.id;
134
+ return { id: String(data.id), title: data.title, type: data.type, spaceId: String(data.space.id), ...(parentId ? { parentId: String(parentId) } : {}), ancestors: data.ancestors.map((a) => ({ id: String(a.id), title: a.title })) };
135
+ }
136
+ async function assertInScope(id) {
137
+ if (!opts.scope)
138
+ return;
139
+ const info = await getContentInfo(id);
140
+ if (info.spaceId !== opts.scope.spaceId || (!opts.scope.spaceOnly && info.id !== opts.scope.id && !info.ancestors.some(a => a.id === opts.scope.id)))
141
+ throw new Error(`선택한 원격 스코프 밖입니다: #${id}`);
142
+ return info;
143
+ }
144
+ /** Read-only preflight. An unknown baseline must never authorize overwriting an existing page. */
145
+ async function checkPage(mapping, spaceId, key, hash, forceUpdate = false) {
146
+ const entry = mapping[key];
147
+ if (!entry?.pageId)
148
+ return;
149
+ const changed = entry.hash !== hash || opts.force || forceUpdate;
150
+ if (!changed && !opts.verify && !entry.pendingMove && !opts.scope)
151
+ return;
152
+ await assertInScope(entry.pageId);
153
+ const current = await getPageOrNull(entry.pageId);
154
+ assertPageBaseline(entry, current, spaceId, key, changed || !!entry.pendingMove);
155
+ }
156
+ function assertPageBaseline(entry, current, spaceId, key, writes) {
157
+ if (!current)
158
+ throw new Error(`원격 페이지 #${entry.pageId}가 없습니다: ${key}. 자동 재생성하지 않습니다.`);
159
+ if (current.spaceId !== undefined && String(current.spaceId) !== spaceId)
160
+ throw new Error(`페이지 스페이스가 다릅니다: ${key}`);
161
+ if (writes && entry.remoteParentId && typeof current.parentId === 'string' && current.parentId !== entry.remoteParentId)
162
+ throw new Error(`원격 위치 충돌: ${key} (기준 부모 #${entry.remoteParentId}, 현재 #${current.parentId}) — pull로 원격 위치를 확인하세요.`);
163
+ if (entry.remoteVersion === undefined) {
164
+ if (writes)
165
+ throw new Error(`원격 버전 기준이 없습니다: ${key}. 로컬 변경을 보관한 뒤 pull로 기준을 확보하세요.`);
166
+ return;
167
+ }
168
+ if (current.version?.number !== entry.remoteVersion)
169
+ throw new Error(`원격 변경 충돌: ${key} (기준 v${entry.remoteVersion}, 현재 v${current.version?.number ?? '?'}) — pull로 원격 내용을 확인하세요.`);
170
+ }
171
+ /** Incremental push with remote-version protection; --force does not bypass conflicts. */
122
172
  async function upsertPage(mapping, spaceId, key, title, storage, hash, parentId, forceUpdate = false) {
123
173
  const existing = mapping[key];
124
174
  if (existing?.pageId) {
125
175
  const changed = existing.hash !== hash || opts.force || forceUpdate;
126
176
  // 변경 없고 검증(--verify)도 안 하면 호출 없이 스킵
127
- if (!changed && !opts.verify && !existing.pendingMove)
177
+ if (!changed && !opts.verify && !existing.pendingMove && !opts.scope)
128
178
  return 'skipped';
129
- let cur = await getPageOrNull(existing.pageId);
130
- if (!cur && existing.pendingMove)
131
- throw new Error(`이동 대상 페이지 #${existing.pageId}가 없습니다. 자동 재생성하지 않습니다.`);
179
+ await assertInScope(existing.pageId);
180
+ if (existing.pendingMove && existing.pageId === opts.scope?.id)
181
+ throw new Error('스코프 루트는 이동할 수 없습니다.');
182
+ let cur = await getPageOrNull(existing.pageId, !!existing.pendingMove);
183
+ assertPageBaseline(existing, cur, spaceId, key, changed || !!existing.pendingMove);
132
184
  if (cur) {
133
185
  let moved = false;
134
186
  if (existing.pendingMove) {
@@ -142,7 +194,7 @@ export function createClient(cfg, opts) {
142
194
  const space = await api(`/api/v2/spaces/${spaceId}`);
143
195
  targetId = space.homepageId;
144
196
  if (!targetId)
145
- throw new Error('최상위 이동 기준 홈페이지가 없습니다. CONFLUENCE_PARENT_ID를 지정하세요.');
197
+ throw new Error('최상위 이동 기준 홈페이지가 없습니다. 원격 스코프를 다시 설정하세요.');
146
198
  const target = await getPageOrNull(targetId);
147
199
  if (!target || target.parentId || String(target.spaceId) !== spaceId)
148
200
  throw new Error('최상위 이동 기준 홈페이지를 확인할 수 없습니다.');
@@ -150,8 +202,16 @@ export function createClient(cfg, opts) {
150
202
  }
151
203
  if (targetId === existing.pageId)
152
204
  throw new Error('페이지를 자기 자신 아래로 이동할 수 없습니다.');
205
+ const priorBody = cur.body?.storage?.value;
206
+ const priorTitle = cur.title;
207
+ if (typeof priorBody !== 'string')
208
+ throw new Error('이동 전 원격 본문을 확인할 수 없습니다.');
209
+ if (parentId)
210
+ await assertInScope(parentId);
153
211
  await api(`/rest/api/content/${existing.pageId}/move/${position}/${targetId}`, { method: 'PUT' });
154
- cur = await getPageOrNull(existing.pageId);
212
+ cur = await getPageOrNull(existing.pageId, true);
213
+ if (cur?.body?.storage?.value !== priorBody || cur?.title !== priorTitle)
214
+ throw new Error('이동 도중 원격 본문·제목이 변경되었습니다. 본문을 덮어쓰지 않습니다.');
155
215
  if (!cur || (cur.parentId ?? undefined) !== parentId)
156
216
  throw new Error('이동 후 부모 위치 검증에 실패했습니다. 다시 sync하세요.');
157
217
  moved = true;
@@ -159,8 +219,13 @@ export function createClient(cfg, opts) {
159
219
  }
160
220
  if (!changed) {
161
221
  delete existing.pendingMove;
222
+ if (typeof cur.parentId === 'string')
223
+ existing.remoteParentId = cur.parentId;
224
+ if (moved && cur.version?.number)
225
+ existing.remoteVersion = cur.version.number;
162
226
  return moved ? 'moved' : 'skipped';
163
227
  }
228
+ await assertInScope(existing.pageId);
164
229
  const nextVersion = (cur.version?.number ?? 1) + 1;
165
230
  await api(`/api/v2/pages/${existing.pageId}`, {
166
231
  method: 'PUT',
@@ -172,11 +237,15 @@ export function createClient(cfg, opts) {
172
237
  version: { number: nextVersion, message: `sync from git: ${key}` },
173
238
  }),
174
239
  });
175
- mapping[key] = { pageId: existing.pageId, title, hash };
240
+ const remoteParentId = typeof cur.parentId === 'string' ? cur.parentId : existing.remoteParentId;
241
+ mapping[key] = { pageId: existing.pageId, title, hash, remoteVersion: nextVersion, ...(remoteParentId ? { remoteParentId } : {}) };
176
242
  return 'updated';
177
243
  }
178
- // cur === null: Confluence 에서 삭제됨 → 아래에서 신규 생성으로 복구
179
244
  }
245
+ if (opts.scope && !parentId)
246
+ throw new Error('스코프 안의 부모가 필요합니다.');
247
+ if (parentId)
248
+ await assertInScope(parentId);
180
249
  const body = {
181
250
  spaceId, status: 'current', title,
182
251
  body: { representation: 'storage', value: storage },
@@ -184,18 +253,13 @@ export function createClient(cfg, opts) {
184
253
  if (parentId)
185
254
  body.parentId = parentId;
186
255
  const created = await api(`/api/v2/pages`, { method: 'POST', body: JSON.stringify(body) });
187
- mapping[key] = { pageId: created.id, title, hash };
188
- return existing?.pageId ? 'recreated' : 'created';
256
+ mapping[key] = { pageId: created.id, title, hash, remoteVersion: created.version?.number ?? 1, ...(parentId ? { remoteParentId: parentId } : {}) };
257
+ return 'created';
189
258
  }
190
259
  // ---- pull(역방향) 전용 ----
191
260
  /** v1 GET. api() 와 달리 /rest/api 계열 + 임의 expand 응답을 그대로 돌려준다. */
192
261
  async function getV1(path) {
193
- const res = await fetch(`${cfg.baseUrl}${path}`, {
194
- headers: { Authorization: authHeader(), Accept: 'application/json' },
195
- });
196
- if (!res.ok)
197
- throw new ConfluenceApiError(res.status, path, await res.text(), res.statusText);
198
- return res.json();
262
+ return api(path);
199
263
  }
200
264
  /**
201
265
  * 콘텐츠 노드 조회. type + 제목 + export_view(HTML) + storage(원본 XHTML) + 출처 메타.
@@ -214,6 +278,7 @@ export function createClient(cfg, opts) {
214
278
  storage: data.body?.storage?.value ?? '',
215
279
  url: base && webui ? `${base}${webui}` : undefined,
216
280
  updated: data.version?.when,
281
+ ...(data.version?.number !== undefined ? { version: data.version.number } : {}),
217
282
  };
218
283
  }
219
284
  /** 자식 노드 목록(kind = page|folder, 전체 페이지네이션). */
@@ -223,7 +288,7 @@ export function createClient(cfg, opts) {
223
288
  while (path) {
224
289
  const data = await getV1(path);
225
290
  for (const c of data.results ?? [])
226
- out.push({ id: c.id, title: c.title });
291
+ out.push({ id: String(c.id), title: c.title });
227
292
  const next = data._links?.next;
228
293
  path = next ? (next.startsWith('/') ? next : `/${next}`) : null;
229
294
  }
@@ -252,7 +317,7 @@ export function createClient(cfg, opts) {
252
317
  const url = downloadPath.startsWith('http') ? downloadPath : `${cfg.baseUrl}${downloadPath}`;
253
318
  const res = await fetch(url, { headers: { Authorization: authHeader() } });
254
319
  if (!res.ok)
255
- throw new Error(`download ${res.status} ${res.statusText}\n${downloadPath}`);
320
+ throw new ConfluenceDownloadError(res.status, res.statusText, downloadPath);
256
321
  return Buffer.from(await res.arrayBuffer());
257
322
  }
258
323
  // ---- 폴더(README 없는 디렉토리 컨테이너) ----
@@ -310,6 +375,10 @@ export function createClient(cfg, opts) {
310
375
  }
311
376
  /** 폴더 생성(write:folder 필요). parentId 없으면 공간 최상위. 생성된 folder id 반환. */
312
377
  async function createFolder(spaceId, title, parentId) {
378
+ if (opts.scope && !parentId)
379
+ throw new Error('스코프 안의 부모가 필요합니다.');
380
+ if (parentId)
381
+ await assertInScope(parentId);
313
382
  const body = { spaceId, title };
314
383
  if (parentId)
315
384
  body.parentId = parentId;
@@ -319,6 +388,11 @@ export function createClient(cfg, opts) {
319
388
  /** 조회→재사용→생성. 동시 생성/검색 지연으로 충돌하면 한 번 재조회한다. */
320
389
  async function upsertFolder(spaceId, spaceKey, title, parentId, mappedId) {
321
390
  parentId = parentId?.trim() || undefined;
391
+ if (parentId)
392
+ await assertInScope(parentId);
393
+ if (mappedId) {
394
+ await assertInScope(mappedId);
395
+ }
322
396
  if (mappedId) {
323
397
  let folder;
324
398
  try {
@@ -339,6 +413,7 @@ export function createClient(cfg, opts) {
339
413
  }
340
414
  return { id: mappedId, created: false };
341
415
  }
416
+ throw new Error(`매핑 폴더 #${mappedId}가 없습니다. 자동 재생성하지 않습니다.`);
342
417
  }
343
418
  const found = await findFolder(spaceId, spaceKey, title, parentId);
344
419
  if (found)
@@ -357,13 +432,9 @@ export function createClient(cfg, opts) {
357
432
  throw error;
358
433
  }
359
434
  }
360
- /** 폴더 삭제(--rebuild). */
361
- async function deleteFolder(id) {
362
- await api(`/api/v2/folders/${id}`, { method: 'DELETE' });
363
- }
364
435
  return {
365
- api, getPageOrNull, getSpaceId, getSpaceInfo, deleteAll, uploadImages, upsertPage,
436
+ api, getContentInfo, assertInScope, getPageOrNull, getSpaceId, getSpaceInfo, uploadImages, uploadAttachments, upsertPage, checkPage,
366
437
  getNode, getChildPages, getChildFolders, listAttachments, downloadAttachment,
367
- getContentOrNull, findFolder, createFolder, upsertFolder, deleteFolder,
438
+ getContentOrNull, findFolder, createFolder, upsertFolder,
368
439
  };
369
440
  }
@@ -1,16 +1,20 @@
1
1
  /**
2
2
  * 환경변수(.env / 셸) 로딩과 필수값 검증.
3
- * dotenv 로딩은 진입점(sync.ts)에서 `import 'dotenv/config'` 로 먼저 수행된다.
3
+ * 작업공간 .env를 읽고 셸 환경변수를 우선 적용한다.
4
4
  */
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { parse } from 'dotenv';
5
8
  import { red, cyan } from '../shared/colors.js';
6
- export function readEnv() {
9
+ export function readEnv(base) {
10
+ const read = (dir) => existsSync(join(dir, '.env')) ? parse(readFileSync(join(dir, '.env'))) : {};
11
+ const dir = base && existsSync(join(base, '.env')) ? base : process.cwd();
12
+ const values = { ...read(dir), ...process.env };
7
13
  return {
8
- baseUrl: process.env.CONFLUENCE_BASE_URL,
9
- email: process.env.CONFLUENCE_EMAIL,
10
- token: process.env.CONFLUENCE_API_TOKEN,
11
- spaceKey: process.env.CONFLUENCE_SPACE_KEY,
12
- // 신규 이름 우선, 없으면 구 이름(CONFLUENCE_PARENT_PAGE_ID) 하위호환
13
- parentId: (process.env.CONFLUENCE_PARENT_ID ?? process.env.CONFLUENCE_PARENT_PAGE_ID)?.trim() || undefined,
14
+ baseUrl: values.CONFLUENCE_BASE_URL,
15
+ email: values.CONFLUENCE_EMAIL,
16
+ token: values.CONFLUENCE_API_TOKEN,
17
+ spaceKey: values.CONFLUENCE_SPACE_KEY,
14
18
  };
15
19
  }
16
20
  /** 동기화(생성/갱신)에 필요한 필수 환경변수 검증. 누락 시 종료. */
@@ -0,0 +1,23 @@
1
+ /** Shared dotenv import/update rules. Only connection keys are imported. */
2
+ import { parse } from 'dotenv';
3
+ export const connectionKeys = ['CONFLUENCE_BASE_URL', 'CONFLUENCE_EMAIL', 'CONFLUENCE_API_TOKEN', 'CONFLUENCE_SPACE_KEY'];
4
+ // Match dotenv assignments, including quoted multiline values, without rewriting unrelated content.
5
+ const assignments = () => /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
6
+ export function importConnection(text) {
7
+ if (typeof text !== 'string' || Buffer.byteLength(text) > 12_000 || text.includes('\0'))
8
+ throw new Error('.env 파일은 12KB 이하 텍스트여야 합니다.');
9
+ const values = parse(text);
10
+ if (!connectionKeys.some(key => Object.hasOwn(values, key)))
11
+ throw new Error('Confluence 연결 환경변수가 없는 파일입니다.');
12
+ const env = {}, raw = {};
13
+ for (const match of text.matchAll(assignments()))
14
+ raw[match[1]] = match[2] ?? '';
15
+ for (const key of connectionKeys)
16
+ env[key] = values[key]?.trim() ?? '';
17
+ const content = connectionKeys.map(key => `${key}=${raw[key] ?? ''}`).join('\n') + '\n';
18
+ return { env, content };
19
+ }
20
+ export function mergeConnection(existing, content) {
21
+ const remaining = existing.replace(assignments(), (match, key) => connectionKeys.includes(key) ? '' : match);
22
+ return `${remaining.trimEnd()}${remaining.trim() ? '\n\n' : ''}${content}`;
23
+ }
@@ -0,0 +1,18 @@
1
+ /** Navigation links emitted by Confluence exports are web paths, not disk paths. */
2
+ export function isConfluenceWebPath(value) {
3
+ let path = value.split(/[?#]/)[0];
4
+ try {
5
+ for (let i = 0; i < 4; i++) {
6
+ const decoded = decodeURIComponent(path);
7
+ if (decoded === path)
8
+ break;
9
+ path = decoded;
10
+ }
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ if (/[\\\x00-\x1f\x7f]/.test(path) || path.split('/').some(part => part === '.' || part === '..'))
16
+ return false;
17
+ return path === '/wiki' || path.startsWith('/wiki/');
18
+ }
@@ -0,0 +1,5 @@
1
+ import { basename, dirname, join, relative } from 'node:path';
2
+ /** Pull and converted documents keep attachments beside their owning Markdown file. */
3
+ export function documentAttachmentDir(file, base) {
4
+ return join(dirname(relative(base, file)), 'attachments', basename(file).replace(/\.md$/i, '')).split('\\').join('/');
5
+ }
@@ -1,13 +1,16 @@
1
+ import { createDocument } from '@mixmark-io/domino';
1
2
  import { createHash } from 'node:crypto';
2
3
  import { existsSync, statSync, readFileSync } from 'node:fs';
3
- import { resolve, relative, dirname, basename, isAbsolute } from 'node:path';
4
+ import { resolve, relative, dirname, basename, extname, isAbsolute } from 'node:path';
4
5
  import { fileURLToPath } from 'node:url';
6
+ import { documentAttachmentDir } from './asset-paths.js';
7
+ import { isConfluenceWebPath } from '../confluence/links.js';
5
8
  import { mapMarkdownText } from './pdf-pages.js';
6
9
  /** 출력 트리의 경로와 실제 입력 파일을 연결한다. dry-run에서도 파일 복사 없이 사용한다. */
7
10
  export function attachmentMapper(file, base) {
8
11
  const copies = new Map(); // base 상대 출력 경로 → 입력 절대경로
9
12
  function localPath(raw) {
10
- if (!raw || raw.startsWith('#') || raw.startsWith('//'))
13
+ if (!raw || raw.startsWith('#') || raw.startsWith('//') || isConfluenceWebPath(raw))
11
14
  return null;
12
15
  if (/^file:/i.test(raw))
13
16
  return fileURLToPath(raw);
@@ -30,15 +33,33 @@ export function attachmentMapper(file, base) {
30
33
  // 일반 외부 URL 및 없는 링크는 그대로 남긴다.
31
34
  if (!existsSync(abs) || !statSync(abs).isFile())
32
35
  return null;
33
- let rel = relative(base, abs).split('\\').join('/');
34
- if (!rel.startsWith('attachments/')) {
36
+ const folder = documentAttachmentDir(file, base), name = basename(abs);
37
+ let rel = `${folder}/${name}`;
38
+ const previous = copies.get(rel) ?? resolve(base, rel);
39
+ if (previous !== abs && existsSync(previous) && statSync(previous).isFile() && !readFileSync(previous).equals(readFileSync(abs))) {
35
40
  const hash = createHash('sha256').update(readFileSync(abs)).digest('hex').slice(0, 24);
36
- rel = `attachments/files/${hash}/${basename(abs)}`;
41
+ const extension = extname(name);
42
+ rel = `${folder}/${basename(name, extension)}-${hash}${extension}`;
37
43
  }
38
44
  copies.set(rel, abs);
39
45
  const target = wiki ? rel : relative(dirname(file), resolve(base, rel)).split('\\').join('/');
40
46
  return target + fragment;
41
47
  }
48
+ function rewriteHtml(part) {
49
+ return part.replace(/<!--[\s\S]*?-->|<(script|style|pre|code)\b[^>]*>[\s\S]*?<\/\1\s*>|<(img|a)\b(?:[^"'<>]|"[^"]*"|'[^']*')*>/gi, (tag, excluded, type) => {
50
+ if (!type)
51
+ return tag;
52
+ const attribute = type.toLowerCase() === 'img' ? 'src' : 'href';
53
+ const raw = createDocument(tag).querySelector(type)?.getAttribute(attribute);
54
+ if (!raw)
55
+ return tag;
56
+ const target = relocate(raw);
57
+ if (target === null)
58
+ return tag;
59
+ const value = target.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
60
+ return tag.replace(new RegExp(`(\\s${attribute}\\s*=\\s*)("[^"]*"|'[^']*'|[^\\s>]+)`, 'i'), (_all, prefix) => `${prefix}"${value}"`);
61
+ });
62
+ }
42
63
  function rewrite(text) {
43
64
  return mapMarkdownText(text, part => {
44
65
  const md = part.replace(/(?<!!)(!?)\[([^\]\n]*)\]\((<[^>\n]+>|[^()\s]+)\)/g, (all, bang, label, raw, offset) => {
@@ -47,13 +68,14 @@ export function attachmentMapper(file, base) {
47
68
  const dest = relocate(raw.replace(/^<(.*)>$/, '$1'));
48
69
  return dest === null ? all : `${bang}[${label}](<${dest.replace(/</g, '%3C').replace(/>/g, '%3E')}>)`;
49
70
  });
50
- return md.replace(/(!?)\[\[([^\]|\n]+)(?:\|([^\]\n]*))?\]\]/g, (all, bang, target, label, offset) => {
71
+ const wiki = md.replace(/(!?)\[\[([^\]|\n]+)(?:\|([^\]\n]*))?\]\]/g, (all, bang, target, label, offset) => {
51
72
  if ((md.slice(0, offset).match(/\\+$/)?.[0].length ?? 0) % 2)
52
73
  return all;
53
74
  const dest = relocate(target, true);
54
75
  return dest === null ? all : `${bang}[[${dest}${label === undefined ? '' : '|' + label}]]`;
55
76
  });
56
- });
77
+ return rewriteHtml(wiki);
78
+ }, rewriteHtml);
57
79
  }
58
80
  return { copies, rewrite, source: (abs) => copies.get(relative(base, abs).split('\\').join('/')) ?? abs };
59
81
  }
@@ -9,14 +9,15 @@
9
9
  * 스페이스를 통째로 다시 받지 않고도 최신 변환 품질을 얻기 위한 것이다.
10
10
  * 링크는 대상이 트리 안에 실제로 있을 때만 바꾼다(외부 URL·이미지·깨진 링크는 손대지 않는다).
11
11
  */
12
+ import { prepareMarkdown } from './markdown.js';
12
13
  import { attachmentMapper } from './attachments.js';
13
14
  import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync, copyFileSync, realpathSync } from 'node:fs';
14
15
  import { resolve, relative, dirname, basename, join, isAbsolute } from 'node:path';
15
- import { collectMarkdown, collectAssets, buildVault, vaultResolver } from '../documents/catalog.js';
16
- import { linksToWikilinks, resolveWikilinks, splitFrontmatter } from '../documents/obsidian.js';
16
+ import { collectMarkdown, collectAssets, buildVault } from '../documents/catalog.js';
17
+ import { linksToWikilinks, splitFrontmatter } from '../documents/obsidian.js';
17
18
  import { buildAnchorIndex } from '../documents/anchors.js';
18
- import { footnotesToLinks, linksToFootnotes } from './footnotes.js';
19
- import { extractPdfPages, restorePdfReferences, mapMarkdownText } from './pdf-pages.js';
19
+ import { linksToFootnotes } from './footnotes.js';
20
+ import { restorePdfReferences } from './pdf-pages.js';
20
21
  import { repairMarkdown, totalFixes } from './repair.js';
21
22
  import { bold, cyan, dim, gray, green, red, yellow } from '../shared/colors.js';
22
23
  function optVal(argv, name) {
@@ -248,29 +249,16 @@ export async function runConvert(argv) {
248
249
  text = toObsidian(text, abs, baseDir, nameCount, headingOf, (s) => selfIdx.get(s.toLowerCase()), attachments.source);
249
250
  }
250
251
  else if (to === 'markdown') {
251
- const lookup = vaultResolver(rel, vault);
252
- // 명시된 첨부 경로를 먼저 출력 트리로 옮긴 뒤 wiki 이름을 해석한다.
253
- text = attachments.rewrite(text);
254
- text = resolveWikilinks(text, (target, embed) => {
255
- const imported = resolve(baseDir, target);
256
- if (attachments.copies.has(posix(relative(baseDir, imported)))) {
257
- return posix(relative(dirname(abs), imported));
258
- }
259
- return lookup(target, embed);
260
- });
261
- text = footnotesToLinks(attachments.rewrite(text));
262
- mapMarkdownText(text, part => {
263
- if (/\[\[[^\]\n]*\.pdf#page=[^\]\n]*\]\]/i.test(part)) {
264
- throw new Error(`PDF 위키링크를 해석하지 못했습니다 (${rel}). --base에 PDF가 포함되는지 확인하세요.`);
265
- }
266
- return part;
267
- });
268
- const pdf = await extractPdfPages(text, abs, baseDir, outDir ?? baseDir, dryRun, attachments.source);
252
+ const pdf = await prepareMarkdown(text, abs, baseDir, vault, outDir ?? baseDir, dryRun, attachments);
269
253
  pdf.assets.forEach(a => sourceAssets.add(a));
270
254
  text = pdf.text;
271
255
  if (pdf.pages)
272
256
  notes.push(dim(`PDF ${pdf.pages}쪽 이미지`));
273
257
  }
258
+ else if (outDir) {
259
+ // Repair-only exports use the same attachment layout without changing document link syntax.
260
+ text = attachments.rewrite(text);
261
+ }
274
262
  // 제자리 변환이면 바뀐 것만 쓰지만, --out 이면 대상 전부를 내보낸다(온전한 트리가 나와야 하므로)
275
263
  if (!outDir && text === before)
276
264
  continue;