@wonseok-han/confluence-sync 1.8.0 → 2.0.1

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 (519) hide show
  1. package/README.md +44 -410
  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 +121 -57
  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 +46 -4
  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 +5 -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 +338 -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 +668 -140
  266. package/dist/sync/push.js +260 -213
  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/remote-folder-moves.js +66 -0
  271. package/dist/sync/render.js +3 -2
  272. package/dist/sync/restore.js +28 -0
  273. package/dist/sync/scope.js +47 -0
  274. package/dist/sync/staging.js +193 -0
  275. package/dist/sync/workspace.js +100 -0
  276. package/dist/web/assets/PDFJS-LICENSE.txt +177 -0
  277. package/dist/web/assets/app.css +1 -1
  278. package/dist/web/assets/app.js +4 -938
  279. package/dist/web/assets/chunks/chunk-TEW74QPN.js +35 -0
  280. package/dist/web/assets/chunks/markdown-S6623E66.js +1 -0
  281. package/dist/web/assets/chunks/monaco-36ZGUH4Y.js +897 -0
  282. package/dist/web/assets/chunks/monaco-HFLEBJTI.css +1 -0
  283. package/dist/web/assets/chunks/pdf-DDN7TXTP.js +55 -0
  284. package/dist/web/assets/confluence-env-example.txt +32 -0
  285. package/dist/web/assets/editor.worker.js +1 -30
  286. package/dist/web/assets/favicon.svg +3 -0
  287. package/dist/web/assets/guide/01-account-settings.png +0 -0
  288. package/dist/web/assets/guide/02-atlassian-security.png +0 -0
  289. package/dist/web/assets/guide/03-api-tokens.png +0 -0
  290. package/dist/web/assets/guide/04-scope-api-token-step1.png +0 -0
  291. package/dist/web/assets/guide/05-scope-api-token-step2.png +0 -0
  292. package/dist/web/assets/guide/06-scope-api-token-step3.png +0 -0
  293. package/dist/web/assets/guide/07-scope-api-token-step4.png +0 -0
  294. package/dist/web/assets/guide/web/document-preview.png +0 -0
  295. package/dist/web/assets/guide/web/remote-diff.png +0 -0
  296. package/dist/web/assets/guide/web/sync-workspace.png +0 -0
  297. package/dist/web/assets/pdf/cmaps/78-EUC-H.bcmap +0 -0
  298. package/dist/web/assets/pdf/cmaps/78-EUC-V.bcmap +0 -0
  299. package/dist/web/assets/pdf/cmaps/78-H.bcmap +0 -0
  300. package/dist/web/assets/pdf/cmaps/78-RKSJ-H.bcmap +0 -0
  301. package/dist/web/assets/pdf/cmaps/78-RKSJ-V.bcmap +0 -0
  302. package/dist/web/assets/pdf/cmaps/78-V.bcmap +0 -0
  303. package/dist/web/assets/pdf/cmaps/78ms-RKSJ-H.bcmap +0 -0
  304. package/dist/web/assets/pdf/cmaps/78ms-RKSJ-V.bcmap +0 -0
  305. package/dist/web/assets/pdf/cmaps/83pv-RKSJ-H.bcmap +0 -0
  306. package/dist/web/assets/pdf/cmaps/90ms-RKSJ-H.bcmap +0 -0
  307. package/dist/web/assets/pdf/cmaps/90ms-RKSJ-V.bcmap +0 -0
  308. package/dist/web/assets/pdf/cmaps/90msp-RKSJ-H.bcmap +0 -0
  309. package/dist/web/assets/pdf/cmaps/90msp-RKSJ-V.bcmap +0 -0
  310. package/dist/web/assets/pdf/cmaps/90pv-RKSJ-H.bcmap +0 -0
  311. package/dist/web/assets/pdf/cmaps/90pv-RKSJ-V.bcmap +0 -0
  312. package/dist/web/assets/pdf/cmaps/Add-H.bcmap +0 -0
  313. package/dist/web/assets/pdf/cmaps/Add-RKSJ-H.bcmap +0 -0
  314. package/dist/web/assets/pdf/cmaps/Add-RKSJ-V.bcmap +0 -0
  315. package/dist/web/assets/pdf/cmaps/Add-V.bcmap +0 -0
  316. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-0.bcmap +0 -0
  317. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-1.bcmap +0 -0
  318. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-2.bcmap +0 -0
  319. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-3.bcmap +0 -0
  320. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-4.bcmap +0 -0
  321. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-5.bcmap +0 -0
  322. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-6.bcmap +0 -0
  323. package/dist/web/assets/pdf/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  324. package/dist/web/assets/pdf/cmaps/Adobe-GB1-0.bcmap +0 -0
  325. package/dist/web/assets/pdf/cmaps/Adobe-GB1-1.bcmap +0 -0
  326. package/dist/web/assets/pdf/cmaps/Adobe-GB1-2.bcmap +0 -0
  327. package/dist/web/assets/pdf/cmaps/Adobe-GB1-3.bcmap +0 -0
  328. package/dist/web/assets/pdf/cmaps/Adobe-GB1-4.bcmap +0 -0
  329. package/dist/web/assets/pdf/cmaps/Adobe-GB1-5.bcmap +0 -0
  330. package/dist/web/assets/pdf/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  331. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-0.bcmap +0 -0
  332. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-1.bcmap +0 -0
  333. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-2.bcmap +0 -0
  334. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-3.bcmap +0 -0
  335. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-4.bcmap +0 -0
  336. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-5.bcmap +0 -0
  337. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-6.bcmap +0 -0
  338. package/dist/web/assets/pdf/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  339. package/dist/web/assets/pdf/cmaps/Adobe-Korea1-0.bcmap +0 -0
  340. package/dist/web/assets/pdf/cmaps/Adobe-Korea1-1.bcmap +0 -0
  341. package/dist/web/assets/pdf/cmaps/Adobe-Korea1-2.bcmap +0 -0
  342. package/dist/web/assets/pdf/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  343. package/dist/web/assets/pdf/cmaps/B5-H.bcmap +0 -0
  344. package/dist/web/assets/pdf/cmaps/B5-V.bcmap +0 -0
  345. package/dist/web/assets/pdf/cmaps/B5pc-H.bcmap +0 -0
  346. package/dist/web/assets/pdf/cmaps/B5pc-V.bcmap +0 -0
  347. package/dist/web/assets/pdf/cmaps/CNS-EUC-H.bcmap +0 -0
  348. package/dist/web/assets/pdf/cmaps/CNS-EUC-V.bcmap +0 -0
  349. package/dist/web/assets/pdf/cmaps/CNS1-H.bcmap +0 -0
  350. package/dist/web/assets/pdf/cmaps/CNS1-V.bcmap +0 -0
  351. package/dist/web/assets/pdf/cmaps/CNS2-H.bcmap +0 -0
  352. package/dist/web/assets/pdf/cmaps/CNS2-V.bcmap +3 -0
  353. package/dist/web/assets/pdf/cmaps/ETHK-B5-H.bcmap +0 -0
  354. package/dist/web/assets/pdf/cmaps/ETHK-B5-V.bcmap +0 -0
  355. package/dist/web/assets/pdf/cmaps/ETen-B5-H.bcmap +0 -0
  356. package/dist/web/assets/pdf/cmaps/ETen-B5-V.bcmap +0 -0
  357. package/dist/web/assets/pdf/cmaps/ETenms-B5-H.bcmap +3 -0
  358. package/dist/web/assets/pdf/cmaps/ETenms-B5-V.bcmap +0 -0
  359. package/dist/web/assets/pdf/cmaps/EUC-H.bcmap +0 -0
  360. package/dist/web/assets/pdf/cmaps/EUC-V.bcmap +0 -0
  361. package/dist/web/assets/pdf/cmaps/Ext-H.bcmap +0 -0
  362. package/dist/web/assets/pdf/cmaps/Ext-RKSJ-H.bcmap +0 -0
  363. package/dist/web/assets/pdf/cmaps/Ext-RKSJ-V.bcmap +0 -0
  364. package/dist/web/assets/pdf/cmaps/Ext-V.bcmap +0 -0
  365. package/dist/web/assets/pdf/cmaps/GB-EUC-H.bcmap +0 -0
  366. package/dist/web/assets/pdf/cmaps/GB-EUC-V.bcmap +0 -0
  367. package/dist/web/assets/pdf/cmaps/GB-H.bcmap +4 -0
  368. package/dist/web/assets/pdf/cmaps/GB-V.bcmap +0 -0
  369. package/dist/web/assets/pdf/cmaps/GBK-EUC-H.bcmap +0 -0
  370. package/dist/web/assets/pdf/cmaps/GBK-EUC-V.bcmap +0 -0
  371. package/dist/web/assets/pdf/cmaps/GBK2K-H.bcmap +0 -0
  372. package/dist/web/assets/pdf/cmaps/GBK2K-V.bcmap +0 -0
  373. package/dist/web/assets/pdf/cmaps/GBKp-EUC-H.bcmap +0 -0
  374. package/dist/web/assets/pdf/cmaps/GBKp-EUC-V.bcmap +0 -0
  375. package/dist/web/assets/pdf/cmaps/GBT-EUC-H.bcmap +0 -0
  376. package/dist/web/assets/pdf/cmaps/GBT-EUC-V.bcmap +0 -0
  377. package/dist/web/assets/pdf/cmaps/GBT-H.bcmap +0 -0
  378. package/dist/web/assets/pdf/cmaps/GBT-V.bcmap +0 -0
  379. package/dist/web/assets/pdf/cmaps/GBTpc-EUC-H.bcmap +0 -0
  380. package/dist/web/assets/pdf/cmaps/GBTpc-EUC-V.bcmap +0 -0
  381. package/dist/web/assets/pdf/cmaps/GBpc-EUC-H.bcmap +0 -0
  382. package/dist/web/assets/pdf/cmaps/GBpc-EUC-V.bcmap +0 -0
  383. package/dist/web/assets/pdf/cmaps/H.bcmap +0 -0
  384. package/dist/web/assets/pdf/cmaps/HKdla-B5-H.bcmap +0 -0
  385. package/dist/web/assets/pdf/cmaps/HKdla-B5-V.bcmap +0 -0
  386. package/dist/web/assets/pdf/cmaps/HKdlb-B5-H.bcmap +0 -0
  387. package/dist/web/assets/pdf/cmaps/HKdlb-B5-V.bcmap +0 -0
  388. package/dist/web/assets/pdf/cmaps/HKgccs-B5-H.bcmap +0 -0
  389. package/dist/web/assets/pdf/cmaps/HKgccs-B5-V.bcmap +0 -0
  390. package/dist/web/assets/pdf/cmaps/HKm314-B5-H.bcmap +0 -0
  391. package/dist/web/assets/pdf/cmaps/HKm314-B5-V.bcmap +0 -0
  392. package/dist/web/assets/pdf/cmaps/HKm471-B5-H.bcmap +0 -0
  393. package/dist/web/assets/pdf/cmaps/HKm471-B5-V.bcmap +0 -0
  394. package/dist/web/assets/pdf/cmaps/HKscs-B5-H.bcmap +0 -0
  395. package/dist/web/assets/pdf/cmaps/HKscs-B5-V.bcmap +0 -0
  396. package/dist/web/assets/pdf/cmaps/Hankaku.bcmap +0 -0
  397. package/dist/web/assets/pdf/cmaps/Hiragana.bcmap +0 -0
  398. package/dist/web/assets/pdf/cmaps/KSC-EUC-H.bcmap +0 -0
  399. package/dist/web/assets/pdf/cmaps/KSC-EUC-V.bcmap +0 -0
  400. package/dist/web/assets/pdf/cmaps/KSC-H.bcmap +0 -0
  401. package/dist/web/assets/pdf/cmaps/KSC-Johab-H.bcmap +0 -0
  402. package/dist/web/assets/pdf/cmaps/KSC-Johab-V.bcmap +0 -0
  403. package/dist/web/assets/pdf/cmaps/KSC-V.bcmap +0 -0
  404. package/dist/web/assets/pdf/cmaps/KSCms-UHC-H.bcmap +0 -0
  405. package/dist/web/assets/pdf/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  406. package/dist/web/assets/pdf/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  407. package/dist/web/assets/pdf/cmaps/KSCms-UHC-V.bcmap +0 -0
  408. package/dist/web/assets/pdf/cmaps/KSCpc-EUC-H.bcmap +0 -0
  409. package/dist/web/assets/pdf/cmaps/KSCpc-EUC-V.bcmap +0 -0
  410. package/dist/web/assets/pdf/cmaps/Katakana.bcmap +0 -0
  411. package/dist/web/assets/pdf/cmaps/LICENSE +36 -0
  412. package/dist/web/assets/pdf/cmaps/NWP-H.bcmap +0 -0
  413. package/dist/web/assets/pdf/cmaps/NWP-V.bcmap +0 -0
  414. package/dist/web/assets/pdf/cmaps/RKSJ-H.bcmap +0 -0
  415. package/dist/web/assets/pdf/cmaps/RKSJ-V.bcmap +0 -0
  416. package/dist/web/assets/pdf/cmaps/Roman.bcmap +0 -0
  417. package/dist/web/assets/pdf/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  418. package/dist/web/assets/pdf/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  419. package/dist/web/assets/pdf/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  420. package/dist/web/assets/pdf/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  421. package/dist/web/assets/pdf/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  422. package/dist/web/assets/pdf/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  423. package/dist/web/assets/pdf/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  424. package/dist/web/assets/pdf/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  425. package/dist/web/assets/pdf/cmaps/UniGB-UCS2-H.bcmap +0 -0
  426. package/dist/web/assets/pdf/cmaps/UniGB-UCS2-V.bcmap +0 -0
  427. package/dist/web/assets/pdf/cmaps/UniGB-UTF16-H.bcmap +0 -0
  428. package/dist/web/assets/pdf/cmaps/UniGB-UTF16-V.bcmap +0 -0
  429. package/dist/web/assets/pdf/cmaps/UniGB-UTF32-H.bcmap +0 -0
  430. package/dist/web/assets/pdf/cmaps/UniGB-UTF32-V.bcmap +0 -0
  431. package/dist/web/assets/pdf/cmaps/UniGB-UTF8-H.bcmap +0 -0
  432. package/dist/web/assets/pdf/cmaps/UniGB-UTF8-V.bcmap +0 -0
  433. package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  434. package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  435. package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  436. package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  437. package/dist/web/assets/pdf/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  438. package/dist/web/assets/pdf/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  439. package/dist/web/assets/pdf/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  440. package/dist/web/assets/pdf/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  441. package/dist/web/assets/pdf/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  442. package/dist/web/assets/pdf/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  443. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  444. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  445. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  446. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  447. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  448. package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  449. package/dist/web/assets/pdf/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  450. package/dist/web/assets/pdf/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  451. package/dist/web/assets/pdf/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  452. package/dist/web/assets/pdf/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  453. package/dist/web/assets/pdf/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  454. package/dist/web/assets/pdf/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  455. package/dist/web/assets/pdf/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  456. package/dist/web/assets/pdf/cmaps/UniKS-UCS2-H.bcmap +0 -0
  457. package/dist/web/assets/pdf/cmaps/UniKS-UCS2-V.bcmap +0 -0
  458. package/dist/web/assets/pdf/cmaps/UniKS-UTF16-H.bcmap +0 -0
  459. package/dist/web/assets/pdf/cmaps/UniKS-UTF16-V.bcmap +0 -0
  460. package/dist/web/assets/pdf/cmaps/UniKS-UTF32-H.bcmap +0 -0
  461. package/dist/web/assets/pdf/cmaps/UniKS-UTF32-V.bcmap +0 -0
  462. package/dist/web/assets/pdf/cmaps/UniKS-UTF8-H.bcmap +0 -0
  463. package/dist/web/assets/pdf/cmaps/UniKS-UTF8-V.bcmap +0 -0
  464. package/dist/web/assets/pdf/cmaps/V.bcmap +0 -0
  465. package/dist/web/assets/pdf/cmaps/WP-Symbol.bcmap +0 -0
  466. package/dist/web/assets/pdf/standard_fonts/FoxitDingbats.pfb +0 -0
  467. package/dist/web/assets/pdf/standard_fonts/FoxitFixed.pfb +0 -0
  468. package/dist/web/assets/pdf/standard_fonts/FoxitFixedBold.pfb +0 -0
  469. package/dist/web/assets/pdf/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  470. package/dist/web/assets/pdf/standard_fonts/FoxitFixedItalic.pfb +0 -0
  471. package/dist/web/assets/pdf/standard_fonts/FoxitSerif.pfb +0 -0
  472. package/dist/web/assets/pdf/standard_fonts/FoxitSerifBold.pfb +0 -0
  473. package/dist/web/assets/pdf/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  474. package/dist/web/assets/pdf/standard_fonts/FoxitSerifItalic.pfb +0 -0
  475. package/dist/web/assets/pdf/standard_fonts/FoxitSymbol.pfb +0 -0
  476. package/dist/web/assets/pdf/standard_fonts/LICENSE_FOXIT +27 -0
  477. package/dist/web/assets/pdf/standard_fonts/LICENSE_LIBERATION +363 -0
  478. package/dist/web/assets/pdf/standard_fonts/LiberationSans-Bold.ttf +0 -0
  479. package/dist/web/assets/pdf/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  480. package/dist/web/assets/pdf/standard_fonts/LiberationSans-Italic.ttf +0 -0
  481. package/dist/web/assets/pdf/standard_fonts/LiberationSans-Regular.ttf +0 -0
  482. package/dist/web/assets/pdf/wasm/LICENSE_JBIG2 +196 -0
  483. package/dist/web/assets/pdf/wasm/LICENSE_OPENJPEG +39 -0
  484. package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_JBIG2 +13 -0
  485. package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
  486. package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_QCMS +19 -0
  487. package/dist/web/assets/pdf/wasm/LICENSE_QCMS +21 -0
  488. package/dist/web/assets/pdf/wasm/jbig2.wasm +0 -0
  489. package/dist/web/assets/pdf/wasm/jbig2_nowasm_fallback.js +15 -0
  490. package/dist/web/assets/pdf/wasm/openjpeg.wasm +0 -0
  491. package/dist/web/assets/pdf/wasm/openjpeg_nowasm_fallback.js +17 -0
  492. package/dist/web/assets/pdf/wasm/qcms_bg.wasm +0 -0
  493. package/dist/web/assets/pdf/wasm/quickjs-eval.js +16 -0
  494. package/dist/web/assets/pdf/wasm/quickjs-eval.wasm +0 -0
  495. package/dist/web/assets/pdf.worker.min.mjs +29 -0
  496. package/dist/web/assets/workspace.worker.js +409 -0
  497. package/dist/web/document-source.js +14 -0
  498. package/dist/web/hosted-files.js +190 -0
  499. package/dist/web/hosted.js +316 -0
  500. package/dist/web/node-runner.js +26 -0
  501. package/dist/web/preview.js +74 -0
  502. package/dist/web/server.js +85 -240
  503. package/dist/web/sync.js +366 -70
  504. package/dist/web/table-editor-script.js +99 -0
  505. package/dist/web/table-format.js +224 -0
  506. package/dist/web/views/help.js +15 -10
  507. package/dist/web/views/sync-panel.js +90 -19
  508. package/dist/web/views/token-guide.js +27 -0
  509. package/dist/web/views/usage-guide.js +12 -0
  510. package/dist/web/views/workspace.js +85 -15
  511. package/dist/web/workspace-api.js +371 -0
  512. package/docs/user/CLI.md +368 -0
  513. package/docs/user/CONFIGURATION.md +125 -0
  514. package/docs/user/OBSIDIAN.md +117 -0
  515. package/docs/user/WEB.md +90 -0
  516. package/docs/user/images/web/document-preview.png +0 -0
  517. package/docs/user/images/web/remote-diff.png +0 -0
  518. package/docs/user/images/web/sync-workspace.png +0 -0
  519. package/package.json +23 -6
@@ -0,0 +1,338 @@
1
+ import { progressStep } from '../shared/progress.js';
2
+ /** Read-only, batched remote observation. Never advances the synchronization baseline. */
3
+ import { bold, cyan, green, yellow, red, dim } from '../shared/colors.js';
4
+ import { createHash, randomUUID } from 'node:crypto';
5
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { basename, dirname, join, resolve } from 'node:path';
7
+ import { readEnv } from '../confluence/config.js';
8
+ import { buildFolderIndex, parentKeyOf } from '../documents/catalog.js';
9
+ import { buildIgnorer } from './ignore.js';
10
+ import { loadMapping } from './mapping.js';
11
+ import { mappingPath, METADATA_DIR } from './metadata.js';
12
+ import { scopeClient } from './scope.js';
13
+ import { assertWorkspaceConnection, inNestedWorkspace, inScope, loadWorkspace, requireScope, resolveSyncBase, within } from './workspace.js';
14
+ export const FETCH_TTL_MS = 120_000;
15
+ const BATCH_SIZE = 100, MAX_REQUESTS = 20;
16
+ const folderUnavailableReason = '원격 상위 폴더를 조회할 수 없습니다. 삭제·권한 변경 여부를 확인하세요.';
17
+ const folderMoveReason = (folder) => `원격 상위 폴더 위치 변경${basename(folder.previousPath.replace(/\/$/, '')) !== basename(folder.path.replace(/\/$/, '')) ? ' (폴더 이름 변경 포함)' : ''}: ${folder.previousPath} → ${folder.path}`;
18
+ const states = new Set(['unchanged', 'changed', 'unavailable', 'unknown', 'new']);
19
+ const digest = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
20
+ function cachePath(base) {
21
+ mappingPath(base); // Validates the metadata directory, including symlinks.
22
+ const path = join(base, METADATA_DIR, 'remote.json');
23
+ if (existsSync(path) && (!lstatSync(path).isFile() || lstatSync(path).isSymbolicLink()))
24
+ throw new Error('원격 조회 캐시는 일반 파일이어야 합니다.');
25
+ return path;
26
+ }
27
+ function fetchContext(baseInput, env, options = {}) {
28
+ const base = realpathSync.native?.(baseInput) ?? realpathSync(baseInput), workspace = loadWorkspace(base), root = requireScope(workspace);
29
+ assertWorkspaceConnection(workspace, env);
30
+ const mapping = loadMapping(options.mappingFile ?? mappingPath(base)), ignore = buildIgnorer(base, []);
31
+ const scoped = Object.entries(mapping).filter(([path]) => inScope(path.replace(/\/$/, ''), workspace) && !ignore.ignores(path) && !inNestedWorkspace(base, path));
32
+ const pages = scoped.filter(([path, entry]) => entry.type !== 'folder' && /\.md$/i.test(path)).sort(([a], [b]) => a.localeCompare(b));
33
+ if (pages.some(([, entry]) => !/^\d+$/.test(entry.pageId)))
34
+ throw new Error('매핑의 페이지 ID가 숫자가 아닙니다. 매핑을 확인하세요.');
35
+ const folderIndex = buildFolderIndex(Object.keys(mapping).filter(path => /\.md$/i.test(path)));
36
+ const expectedParents = new Map(pages.map(([path]) => { const parent = parentKeyOf(path, folderIndex); return [path, path === 'README.md' && mapping[path].pageId === root.id ? undefined : mapping[path].remoteParentId ?? (parent ? mapping[parent]?.pageId : root.id)]; }));
37
+ const key = digest(['discovery-v3', base, workspace, env.baseUrl, env.spaceKey, digest([env.email, env.token]), pages, [...expectedParents], scoped.filter(([, e]) => e.type === 'folder')]);
38
+ const binding = digest([base, workspace, env.baseUrl, env.spaceKey, digest([env.email, env.token])]);
39
+ const path = cachePath(base), now = Date.now();
40
+ return { base, workspace, root, mapping, pages, scoped, expectedParents, key, path, now, binding };
41
+ }
42
+ function cachedSnapshot(context, allowExpired = false) {
43
+ try {
44
+ const value = JSON.parse(readFileSync(context.path, 'utf8'));
45
+ const age = context.now - Date.parse(value?.checkedAt);
46
+ if (value?.version !== 1 || value.key !== context.key || age < 0 || !Number.isFinite(age) || !allowExpired && age >= FETCH_TTL_MS || !Array.isArray(value.items))
47
+ return null;
48
+ if (value.folders !== undefined && (!Array.isArray(value.folders) || value.folders.some(folder => !folder || typeof folder.path !== 'string' || !folder.path.endsWith('/') || !/^\d+$/.test(folder.pageId) || typeof folder.title !== 'string' || folder.previousPath !== undefined && (typeof folder.previousPath !== 'string' || !folder.previousPath.endsWith('/') || !within(context.base, resolve(context.base, folder.previousPath))) || !within(context.base, resolve(context.base, folder.path)))))
49
+ return null;
50
+ const ids = new Set();
51
+ for (const item of value.items) {
52
+ if (!item || typeof item.path !== 'string' || !within(context.base, resolve(context.base, item.path)) || !/\.md$/i.test(item.path) || !/^\d+$/.test(item.pageId) || ids.has(item.pageId) || !states.has(item.state) || !Array.isArray(item.reasons) || item.reasons.some(r => typeof r !== 'string'))
53
+ return null;
54
+ ids.add(item.pageId);
55
+ }
56
+ if (context.pages.some(([, entry]) => !ids.has(entry.pageId)))
57
+ return null;
58
+ return value;
59
+ }
60
+ catch (error) {
61
+ if (error.code === 'ENOENT' || error instanceof SyntaxError)
62
+ return null;
63
+ throw error;
64
+ }
65
+ }
66
+ /** Reclassify saved metadata against current baselines without API calls. */
67
+ export function readFetchSnapshot(base, env) {
68
+ const context = fetchContext(base, env), strict = cachedSnapshot(context, true);
69
+ if (strict)
70
+ return strict;
71
+ let prior;
72
+ try {
73
+ prior = JSON.parse(readFileSync(context.path, 'utf8'));
74
+ }
75
+ catch (error) {
76
+ if (error.code === 'ENOENT' || error instanceof SyntaxError)
77
+ return null;
78
+ throw error;
79
+ }
80
+ if (prior?.binding !== context.binding || !cachedSnapshot({ ...context, key: prior.key }, true))
81
+ return null;
82
+ const byId = new Map(context.pages.map(([path, entry]) => [entry.pageId, { path, entry }]));
83
+ return { ...prior, key: context.key, items: prior.items.map(item => {
84
+ const mapped = byId.get(item.pageId);
85
+ if (!mapped)
86
+ return item;
87
+ const { path, entry } = mapped, reasons = [];
88
+ if (item.state === 'unavailable' || item.state === 'unknown' && item.reasons.includes(folderUnavailableReason))
89
+ return { ...item, path, baselineVersion: entry.remoteVersion };
90
+ if (!entry.remoteVersion || !item.remoteVersion || item.remoteVersion < entry.remoteVersion)
91
+ return { ...item, path, baselineVersion: entry.remoteVersion, state: 'unknown', reasons: ['원격 버전 기준을 확인할 수 없습니다. Fetch로 갱신하세요.'] };
92
+ if (item.remoteVersion !== entry.remoteVersion)
93
+ reasons.push(`버전 v${entry.remoteVersion} → v${item.remoteVersion}`);
94
+ if (entry.title && item.title && entry.title !== item.title)
95
+ reasons.push('제목 변경');
96
+ const parent = context.expectedParents.get(path);
97
+ if (parent && item.parentId && parent !== item.parentId)
98
+ reasons.push('원격 부모 위치 변경');
99
+ for (const folder of prior.folders ?? [])
100
+ if (folder.previousPath && path.startsWith(folder.previousPath) && context.mapping[folder.previousPath]?.pageId === folder.pageId)
101
+ reasons.push(folderMoveReason(folder));
102
+ return { ...item, path, baselineVersion: entry.remoteVersion, state: reasons.length ? 'changed' : 'unchanged', reasons };
103
+ }) };
104
+ }
105
+ export async function fetchRemote(baseInput, env, options = {}) {
106
+ const context = fetchContext(baseInput, env, options);
107
+ const { base, workspace, pages, scoped, key, path, now, expectedParents, binding } = context;
108
+ const cached = options.refresh ? null : cachedSnapshot(context);
109
+ if (cached)
110
+ return { snapshot: cached, cached: true, requests: 0 };
111
+ const client = scopeClient({ ...env, baseUrl: env.baseUrl?.replace(/\/$/, '') });
112
+ const remote = new Map();
113
+ let requests = 0;
114
+ const query = new URLSearchParams({ 'space-id': workspace.connection.spaceId, status: 'current', limit: String(BATCH_SIZE) });
115
+ let cursor = '', seen = new Set();
116
+ do {
117
+ if (requests >= MAX_REQUESTS)
118
+ throw new Error('원격 조회가 최대 20회에 도달했습니다. 기존 캐시는 유지합니다.');
119
+ progressStep(`스페이스 원격 메타데이터 확인 · ${remote.size}개 발견`);
120
+ const params = new URLSearchParams(query);
121
+ if (cursor)
122
+ params.set('cursor', cursor);
123
+ let link = '';
124
+ requests++;
125
+ const data = await client.api('/api/v2/pages?' + params, undefined, headers => { link = headers.get('link') ?? ''; });
126
+ if (!data || !Array.isArray(data.results))
127
+ throw new Error('원격 페이지 목록 응답이 올바르지 않습니다.');
128
+ for (const page of data.results) {
129
+ if (!page || !/^\d+$/.test(String(page.id)) || page.parentId != null && !/^\d+$/.test(String(page.parentId)) || String(page.spaceId) !== workspace.connection.spaceId || remote.has(String(page.id)))
130
+ throw new Error('원격 페이지 목록의 ID·스페이스 또는 중복 응답을 확인하세요.');
131
+ remote.set(String(page.id), { ...page, id: String(page.id), parentId: page.parentId == null ? undefined : String(page.parentId) });
132
+ }
133
+ const next = data._links?.next || link.split(',').find(part => /rel\s*=\s*"?next"?/i.test(part))?.match(/<([^>]+)>/)?.[1];
134
+ cursor = '';
135
+ if (next) {
136
+ const url = new URL(next, env.baseUrl.replace(/\/$/, '') + '/api/v2/pages');
137
+ if (url.origin !== new URL(env.baseUrl).origin || !/\/api\/v2\/pages$/.test(url.pathname) || !url.searchParams.get('cursor'))
138
+ throw new Error('원격 페이지 목록의 다음 페이지 주소가 올바르지 않습니다.');
139
+ cursor = url.searchParams.get('cursor');
140
+ if (seen.has(cursor))
141
+ throw new Error('원격 페이지 목록의 페이지네이션이 반복됩니다.');
142
+ seen.add(cursor);
143
+ }
144
+ } while (cursor);
145
+ const items = pages.map(([path, entry]) => {
146
+ const page = remote.get(entry.pageId), item = { path, pageId: entry.pageId, baselineVersion: entry.remoteVersion, state: 'unknown', reasons: [] };
147
+ if (!page)
148
+ return { ...item, state: 'unavailable', reasons: ['조회 결과에 없음 — 삭제·권한 변경·보관 여부는 확인되지 않았습니다.'] };
149
+ if (typeof page.title === 'string')
150
+ item.title = page.title;
151
+ if (typeof page.parentId === 'string')
152
+ item.parentId = page.parentId;
153
+ if (Number.isInteger(page.version?.number) && page.version.number > 0)
154
+ item.remoteVersion = page.version.number;
155
+ if (!entry.remoteVersion || !item.remoteVersion || item.remoteVersion < entry.remoteVersion)
156
+ item.reasons.push('비교할 기준 버전 또는 원격 버전을 확인할 수 없습니다.');
157
+ else {
158
+ item.state = 'unchanged';
159
+ if (item.remoteVersion !== entry.remoteVersion)
160
+ item.reasons.push(`버전 v${entry.remoteVersion} → v${item.remoteVersion}`);
161
+ if (entry.title && item.title && entry.title !== item.title)
162
+ item.reasons.push('제목 변경');
163
+ const parent = expectedParents.get(path);
164
+ if (parent && item.parentId && parent !== item.parentId)
165
+ item.reasons.push('원격 부모 위치 변경');
166
+ if (item.reasons.length)
167
+ item.state = 'changed';
168
+ }
169
+ return item;
170
+ });
171
+ // Batch only unresolved parents of unmapped pages; expanded ancestry is shared.
172
+ const mappingById = new Map(Object.entries(context.mapping).map(([path, entry]) => [entry.pageId, { path, entry }]));
173
+ const folders = new Map();
174
+ const slug = (title) => { const value = title.replace(/[\/\\:*?"<>|]/g, '-').replace(/\s+/g, ' ').trim(); return !value || /^\.+$/.test(value) ? 'untitled' : value.startsWith('.') || ['attachments', 'node_modules'].includes(value) ? '_' + value : value; };
175
+ const nodes = new Map(remote);
176
+ const unavailableFolders = new Set();
177
+ const trackedFolders = context.scoped.filter(([path, entry]) => entry.type === 'folder' && pages.some(([page]) => page.startsWith(path))).map(([, entry]) => entry.pageId);
178
+ const missingParents = [...new Set([...trackedFolders, ...[...remote].filter(([id]) => !mappingById.has(id)).map(([, page]) => String(page.parentId ?? '')).filter(id => id && id !== context.root.id && !nodes.has(id) && !mappingById.has(id))])];
179
+ for (let offset = 0; offset < missingParents.length; offset += BATCH_SIZE) {
180
+ const ids = missingParents.slice(offset, offset + BATCH_SIZE), remaining = new Set(ids), seenStarts = new Set();
181
+ let start = '';
182
+ do {
183
+ if (requests >= MAX_REQUESTS)
184
+ throw new Error('원격 조회가 최대 20회에 도달했습니다. 기존 캐시는 유지합니다.');
185
+ const params = new URLSearchParams({ cql: `id in (${ids.join(',')})`, expand: 'ancestors,space', limit: String(BATCH_SIZE) });
186
+ if (start)
187
+ params.set('start', start);
188
+ requests++;
189
+ progressStep(`원격 폴더 메타데이터 조회 · ${offset + 1}–${offset + ids.length}`);
190
+ const data = await client.api('/rest/api/content/search?' + params);
191
+ if (!Array.isArray(data?.results))
192
+ throw new Error('원격 부모 폴더 목록을 확인할 수 없습니다.');
193
+ for (const info of data.results) {
194
+ const id = String(info?.id);
195
+ if (!remaining.has(id) || info.type !== 'folder' || String(info.space?.id) !== workspace.connection.spaceId || !Array.isArray(info.ancestors))
196
+ throw new Error('원격 부모 폴더의 ID·스페이스·유형을 확인하세요.');
197
+ remaining.delete(id);
198
+ for (let i = 0; i < info.ancestors.length; i++) {
199
+ const a = info.ancestors[i];
200
+ if (!/^\d+$/.test(String(a.id)) || typeof a.title !== 'string')
201
+ throw new Error('원격 상위 폴더 정보를 확인할 수 없습니다.');
202
+ if (!nodes.has(String(a.id)))
203
+ nodes.set(String(a.id), { id: String(a.id), title: a.title, type: 'folder', parentId: info.ancestors[i - 1]?.id });
204
+ }
205
+ nodes.set(id, { id, title: info.title, type: 'folder', parentId: info.ancestors.at(-1)?.id });
206
+ }
207
+ start = '';
208
+ if (data._links?.next) {
209
+ const url = new URL(data._links.next, env.baseUrl);
210
+ if (url.origin !== new URL(env.baseUrl).origin || !/\/rest\/api\/content\/search$/.test(url.pathname) || !/^\d+$/.test(url.searchParams.get('start') ?? ''))
211
+ throw new Error('부모 폴더 목록의 다음 페이지 주소가 올바르지 않습니다.');
212
+ start = url.searchParams.get('start');
213
+ if (seenStarts.has(start))
214
+ throw new Error('부모 폴더 페이지네이션이 반복됩니다.');
215
+ seenStarts.add(start);
216
+ }
217
+ } while (start);
218
+ if ([...remaining].some(id => !trackedFolders.includes(id)))
219
+ throw new Error('신규 원격 문서의 부모 폴더를 찾을 수 없습니다. 기존 캐시는 유지합니다.');
220
+ for (const id of remaining)
221
+ unavailableFolders.add(id);
222
+ }
223
+ async function container(id, visiting = new Set()) {
224
+ if (id === context.root.id)
225
+ return '';
226
+ if (visiting.has(id))
227
+ throw new Error('원격 부모 관계가 순환합니다.');
228
+ visiting.add(id);
229
+ const mapped = mappingById.get(id);
230
+ if (mapped) {
231
+ if (mapped.entry.type !== 'folder')
232
+ return dirname(mapped.path) === '.' ? '' : dirname(mapped.path);
233
+ const prior = mapped.path.replace(/\/$/, ''), node = nodes.get(id);
234
+ if (!node)
235
+ return prior;
236
+ if (typeof node.title !== 'string' || !node.title.trim())
237
+ throw new Error(`원격 폴더 제목을 확인할 수 없습니다: #${id}`);
238
+ const parent = node.parentId ? await container(String(node.parentId), visiting) : '';
239
+ const renamed = slug(node.title) !== slug(mapped.entry.title ?? basename(prior));
240
+ const path = [parent, renamed ? slug(node.title) : basename(prior)].filter(Boolean).join('/');
241
+ if (path !== prior)
242
+ folders.set(id, { path: path + '/', previousPath: prior + '/', pageId: id, title: node.title, parentId: node.parentId ? String(node.parentId) : undefined });
243
+ return path;
244
+ }
245
+ const node = nodes.get(id);
246
+ if (!node)
247
+ throw new Error(`원격 부모 정보를 확인할 수 없습니다: #${id}`);
248
+ if (typeof node.title !== 'string' || !node.title.trim())
249
+ throw new Error(`원격 제목을 확인할 수 없습니다: #${id}`);
250
+ const parent = node.parentId ? await container(String(node.parentId), visiting) : '';
251
+ const path = [parent, slug(node.title)].filter(Boolean).join('/');
252
+ if (!remote.has(id))
253
+ folders.set(id, { path: path + '/', pageId: id, title: node.title });
254
+ return path;
255
+ }
256
+ for (const id of trackedFolders)
257
+ await container(id);
258
+ for (const item of items) {
259
+ if (item.state === 'unavailable' || item.state === 'unknown')
260
+ continue;
261
+ // A deleted historical local folder is not necessarily a current ancestor:
262
+ // its documents may already have moved elsewhere without changing local paths.
263
+ let parent = item.parentId, missingAncestor = false;
264
+ const seenParents = new Set();
265
+ while (parent && parent !== context.root.id) {
266
+ if (unavailableFolders.has(parent) || seenParents.has(parent)) {
267
+ missingAncestor = true;
268
+ break;
269
+ }
270
+ seenParents.add(parent);
271
+ const node = nodes.get(parent);
272
+ parent = node?.parentId ? String(node.parentId) : undefined;
273
+ }
274
+ if (missingAncestor) {
275
+ item.state = 'unknown';
276
+ item.reasons.push(folderUnavailableReason);
277
+ continue;
278
+ }
279
+ for (const folder of folders.values())
280
+ if (folder.previousPath && item.path.startsWith(folder.previousPath)) {
281
+ item.state = 'changed';
282
+ item.reasons.push(folderMoveReason(folder));
283
+ }
284
+ }
285
+ const mappedIds = new Set(Object.values(context.mapping).map(entry => entry.pageId));
286
+ for (const [id, page] of remote)
287
+ if (!mappedIds.has(id) && page.parentId)
288
+ await container(String(page.parentId));
289
+ for (const [id, page] of remote) {
290
+ if (mappedIds.has(id))
291
+ continue;
292
+ if (typeof page.title !== 'string' || !Number.isInteger(page.version?.number) || page.version.number < 1)
293
+ throw new Error(`신규 원격 문서의 제목·버전을 확인할 수 없습니다: #${id}`);
294
+ const parent = page.parentId ? await container(String(page.parentId)) : '';
295
+ const hasChildren = [...nodes.values()].some(node => String(node.parentId) === id);
296
+ const path = id === context.root.id ? 'README.md' : [parent, slug(page.title) + (hasChildren ? '/README.md' : '.md')].filter(Boolean).join('/');
297
+ if (!inScope(path, workspace) || buildIgnorer(base, []).ignores(path) || inNestedWorkspace(base, path))
298
+ continue;
299
+ items.push({ path, pageId: id, title: page.title, parentId: page.parentId, remoteVersion: page.version.number, state: 'new', reasons: ['아직 로컬에 받지 않은 원격 문서'] });
300
+ }
301
+ const snapshot = { version: 1, key, binding, checkedAt: new Date(now).toISOString(), scope: '.', requests, items, folders: [...folders.values()], foldersSkipped: scoped.filter(([, e]) => e.type === 'folder').length };
302
+ // Publish only a complete observation. Never replace a good cache with partial results.
303
+ cachePath(base);
304
+ mkdirSync(dirname(path), { recursive: true });
305
+ const temp = join(dirname(path), `.remote-${randomUUID()}.tmp`);
306
+ try {
307
+ writeFileSync(temp, JSON.stringify(snapshot, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
308
+ renameSync(temp, path);
309
+ }
310
+ finally {
311
+ rmSync(temp, { force: true });
312
+ }
313
+ return { snapshot, cached: false, requests };
314
+ }
315
+ export async function runFetch(argv) {
316
+ for (let i = 0; i < argv.length; i++) {
317
+ if (argv[i] === '--base') {
318
+ if (!argv[i + 1] || argv[i + 1].startsWith('-'))
319
+ throw new Error('--base에 기준 폴더를 지정하세요.');
320
+ i++;
321
+ }
322
+ else if (!['fetch', '--no-color', '--refresh'].includes(argv[i]))
323
+ throw new Error('fetch는 연결된 작업공간을 사용합니다. 옵션은 --base와 --refresh만 지정하세요.');
324
+ }
325
+ const i = argv.indexOf('--base'), base = resolveSyncBase(i >= 0 ? argv[i + 1] : undefined);
326
+ if (!base)
327
+ throw new Error('작업공간이 없습니다. --base로 기준 폴더를 지정하세요.');
328
+ const { snapshot, cached, requests } = await fetchRemote(base, readEnv(base), { refresh: argv.includes('--refresh') });
329
+ const counts = Object.fromEntries([...states].map(state => [state, snapshot.items.filter(item => item.state === state).length]));
330
+ console.log(`${bold(cyan('원격 변경 확인 · 스페이스 작업공간'))}\n${dim(`조회 시각: ${snapshot.checkedAt}${cached ? ' · 2분 이내 캐시 사용' : ''}`)}\n이번 API 요청: ${cyan(`${requests}회`)} · 대상 ${snapshot.items.length}건`);
331
+ const tones = { new: cyan, changed: cyan, unavailable: red, unknown: yellow, unchanged: green };
332
+ for (const item of snapshot.items.filter(item => item.state !== 'unchanged'))
333
+ console.log(` ${tones[item.state](`[${({ new: '원격 신규', changed: '원격 변경', unavailable: '조회 불가', unknown: '확인 필요', unchanged: '버전 동일' })[item.state]}]`)} ${item.path} ${dim(`(#${item.pageId})`)} · ${item.reasons.join(', ')}`);
334
+ console.log(`${green(`버전 동일 ${counts.unchanged}`)} · ${cyan(`원격 신규 ${counts.new}`)} · ${cyan(`원격 변경 ${counts.changed}`)} · ${counts.unavailable ? red(`조회 불가 ${counts.unavailable}`) : dim('조회 불가 0')} · ${counts.unknown ? yellow(`확인 필요 ${counts.unknown}`) : dim('확인 필요 0')}`);
335
+ if (!snapshot.items.length)
336
+ console.log(dim('스페이스에서 가져올 문서가 없습니다.'));
337
+ console.log(dim('문서·동기화 기준은 유지합니다. 스페이스의 새 문서와 버전·위치 변경을 확인했습니다. 가져오기: csync pull'));
338
+ }
@@ -1,15 +1,15 @@
1
1
  /**
2
- * 동기화 제외 규칙. base 루트의 .confluence-syncignore(.gitignore 방식) + --exclude 옵션.
2
+ * 동기화 제외 규칙. base 루트의 .csync/ignore(구 .confluence-syncignore)(.gitignore 방식) + --exclude 옵션.
3
3
  * 매칭은 npm `ignore`(gitignore 스펙) 사용. 경로는 base 기준 상대(POSIX).
4
4
  */
5
5
  import { readFileSync, existsSync } from 'node:fs';
6
- import { join } from 'node:path';
6
+ import { ignorePath } from './metadata.js';
7
7
  import ignore from 'ignore';
8
- export const IGNORE_FILE = '.confluence-syncignore';
8
+ export { IGNORE_FILE } from './metadata.js';
9
9
  export function buildIgnorer(baseDir, excludes) {
10
10
  const ig = ignore();
11
11
  let active = false;
12
- const file = join(baseDir, IGNORE_FILE);
12
+ const file = ignorePath(baseDir);
13
13
  if (existsSync(file)) {
14
14
  ig.add(readFileSync(file, 'utf8'));
15
15
  active = true;
@@ -0,0 +1,12 @@
1
+ /** Local edit detection is independent of rendered storage and incoming anchor links. */
2
+ import { createHash } from 'node:crypto';
3
+ import { readFileSync } from 'node:fs';
4
+ import { resolve } from 'node:path';
5
+ export function localStateHash(base, rel, rendered) {
6
+ const hash = createHash('sha256').update(readFileSync(resolve(base, rel)));
7
+ for (const asset of [...rendered.attachments].sort((a, b) => a.filename.localeCompare(b.filename))) {
8
+ hash.update('\0' + asset.filename + '\0');
9
+ hash.update(createHash('sha256').update(readFileSync(asset.abs)).digest());
10
+ }
11
+ return hash.digest('hex');
12
+ }
@@ -1,18 +1,41 @@
1
1
  /**
2
2
  * 매핑(파일 경로 ↔ pageId ↔ hash) 영속화.
3
- * 기본 위치는 <base>/.confluence-sync.json 이며 문서셋과 함께 버전관리된다.
3
+ * 기본 위치는 <base>/.csync/mapping.json 이며 문서셋과 함께 버전관리된다.
4
4
  */
5
- import { readFileSync, writeFileSync } from 'node:fs';
6
- /** base 루트에 놓이는 매핑 파일 이름. 이 파일이 있는 곳이 곧 동기화 루트다. */
7
- export const MAPPING_FILE = '.confluence-sync.json';
5
+ import { readFileSync, writeFileSync, mkdirSync, renameSync, rmSync } from 'node:fs';
6
+ import { randomUUID } from 'node:crypto';
7
+ import { dirname } from 'node:path';
8
+ export { MAPPING_FILE } from './metadata.js';
8
9
  export function loadMapping(path) {
10
+ let data;
9
11
  try {
10
- return JSON.parse(readFileSync(path, 'utf8'));
12
+ data = JSON.parse(readFileSync(path, 'utf8'));
11
13
  }
12
- catch {
13
- return {};
14
+ catch (error) {
15
+ if (error.code === 'ENOENT')
16
+ return {};
17
+ throw new Error(`매핑 파일을 읽을 수 없습니다: ${path}`);
14
18
  }
19
+ if (!data || typeof data !== 'object' || Array.isArray(data) || Object.values(data).some(entry => !entry || typeof entry.pageId !== 'string' || !entry.pageId ||
20
+ (entry.sourceObject !== undefined && (typeof entry.sourceObject !== 'string' || !/^[a-f0-9]{64}$/.test(entry.sourceObject))) ||
21
+ (entry.localHash !== undefined && (typeof entry.localHash !== 'string' || !/^[a-f0-9]{64}$/.test(entry.localHash))) ||
22
+ (entry.remoteParentId !== undefined && (typeof entry.remoteParentId !== 'string' || !entry.remoteParentId)) ||
23
+ (entry.remoteVersion !== undefined && (!Number.isInteger(entry.remoteVersion) || entry.remoteVersion < 1)))) {
24
+ throw new Error(`잘못된 매핑 파일: ${path}`);
25
+ }
26
+ const ids = Object.values(data).map(entry => entry.pageId);
27
+ if (new Set(ids).size !== ids.length)
28
+ throw new Error(`매핑 pageId 중복: ${path}`);
29
+ return data;
15
30
  }
16
31
  export function saveMapping(path, m) {
17
- writeFileSync(path, JSON.stringify(m, null, 2) + '\n');
32
+ mkdirSync(dirname(path), { recursive: true });
33
+ const temporary = `${path}.${randomUUID()}.tmp`;
34
+ try {
35
+ writeFileSync(temporary, JSON.stringify(m, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
36
+ renameSync(temporary, path);
37
+ }
38
+ finally {
39
+ rmSync(temporary, { force: true });
40
+ }
18
41
  }
@@ -0,0 +1,57 @@
1
+ /** Metadata locations shared by CLI, references and the web adapter. */
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, lstatSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ export const METADATA_DIR = '.csync';
5
+ export const MAPPING_FILE = '.csync/mapping.json';
6
+ export const WORKSPACE_FILE = '.csync/workspace.json';
7
+ export const IGNORE_FILE = '.csync/ignore';
8
+ const files = {
9
+ mapping: [MAPPING_FILE, '.confluence-sync.json'],
10
+ workspace: [WORKSPACE_FILE, '.confluence-workspace.json'],
11
+ ignore: [IGNORE_FILE, '.confluence-syncignore'],
12
+ };
13
+ export function metadataPath(base, kind) {
14
+ const directory = join(base, METADATA_DIR);
15
+ if (existsSync(directory) && (!lstatSync(directory).isDirectory() || lstatSync(directory).isSymbolicLink()))
16
+ throw new Error(`메타데이터 폴더가 올바르지 않습니다: ${directory}`);
17
+ const [current, legacy] = files[kind].map(name => join(base, name));
18
+ for (const path of [current, legacy])
19
+ if (existsSync(path) && (!lstatSync(path).isFile() || lstatSync(path).isSymbolicLink()))
20
+ throw new Error(`메타데이터 파일이 올바르지 않습니다: ${path}`);
21
+ if (existsSync(current) && existsSync(legacy) && !readFileSync(current).equals(readFileSync(legacy))) {
22
+ throw new Error(`기존·새 메타데이터가 충돌합니다: ${legacy}, ${current}. 사용할 기준을 먼저 정리하세요.`);
23
+ }
24
+ return existsSync(current) || !existsSync(legacy) ? current : legacy;
25
+ }
26
+ export const mappingPath = (base) => metadataPath(base, 'mapping');
27
+ export const workspacePath = (base) => metadataPath(base, 'workspace');
28
+ export const ignorePath = (base) => metadataPath(base, 'ignore');
29
+ export function hasMetadata(base) {
30
+ return existsSync(join(base, METADATA_DIR)) || ['mapping', 'workspace'].some(kind => existsSync(join(base, files[kind][1])));
31
+ }
32
+ /** Include absent locations too, so creating a competing legacy file invalidates a plan. */
33
+ export function metadataFiles(base) {
34
+ return Object.values(files).flatMap(names => names.map(name => join(base, name)));
35
+ }
36
+ /** Explicit, offline migration. Copy and verify every destination before removing any source. */
37
+ export function migrateMetadata(base, dryRun = false) {
38
+ const plan = Object.keys(files).map(kind => {
39
+ metadataPath(base, kind); // Validate every pair before any mutation.
40
+ const [next, prior] = files[kind];
41
+ return { source: join(base, prior), target: join(base, next) };
42
+ }).filter(item => existsSync(item.source)).map(item => ({ ...item, bytes: readFileSync(item.source) }));
43
+ for (const item of plan)
44
+ console.log(`${item.source} → ${item.target}`);
45
+ if (!dryRun && plan.length) {
46
+ mkdirSync(join(base, METADATA_DIR), { recursive: true });
47
+ for (const item of plan)
48
+ if (!existsSync(item.target))
49
+ writeFileSync(item.target, item.bytes, { flag: 'wx' });
50
+ for (const item of plan)
51
+ if (!readFileSync(item.target).equals(item.bytes) || !readFileSync(item.source).equals(item.bytes))
52
+ throw new Error('이전 중 메타데이터가 변경되었습니다. 원본은 제거하지 않았습니다.');
53
+ for (const item of plan)
54
+ unlinkSync(item.source);
55
+ }
56
+ return plan.length;
57
+ }
@@ -0,0 +1,52 @@
1
+ /** Prepare publication content without writing to the source workspace. */
2
+ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join, relative, resolve } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { buildVault, collectAssets } from '../documents/catalog.js';
6
+ import { prepareMarkdown } from '../conversion/markdown.js';
7
+ import { within } from './workspace.js';
8
+ import { buildTreeRenderer } from './render.js';
9
+ export async function preparePushTree(base, rels, selected, options) {
10
+ const stage = mkdtempSync(join(tmpdir(), 'csync-push-'));
11
+ const dispose = () => rmSync(stage, { recursive: true, force: true });
12
+ try {
13
+ const chosen = new Set(selected);
14
+ const sourceRenderer = buildTreeRenderer(base, rels);
15
+ const vault = buildVault(rels, collectAssets(base).map(file => relative(base, file).split('\\').join('/')));
16
+ for (const rel of rels) {
17
+ const original = resolve(base, rel), destination = resolve(stage, rel);
18
+ let text = readFileSync(original, 'utf8');
19
+ if (chosen.has(rel)) {
20
+ // Preview renders actual temporary PNGs too, so attachment hashes match Push.
21
+ const converted = await prepareMarkdown(text, original, base, vault, stage, false, undefined, false);
22
+ text = converted.text;
23
+ for (const [asset, source] of converted.copies) {
24
+ const target = resolve(stage, asset);
25
+ mkdirSync(dirname(target), { recursive: true });
26
+ copyFileSync(source, target);
27
+ }
28
+ }
29
+ // Use the publication renderer as the final asset authority: conversion may
30
+ // miss balanced parentheses in Markdown paths or assets inside raw HTML.
31
+ if (chosen.has(rel)) {
32
+ const doc = sourceRenderer.docs[rel];
33
+ for (const asset of sourceRenderer.render(rel, doc.body, doc.title).attachments) {
34
+ if (!within(base, asset.abs) || !existsSync(asset.abs))
35
+ continue;
36
+ const target = resolve(stage, relative(base, asset.abs));
37
+ if (existsSync(target))
38
+ continue;
39
+ mkdirSync(dirname(target), { recursive: true });
40
+ copyFileSync(asset.abs, target);
41
+ }
42
+ }
43
+ mkdirSync(dirname(destination), { recursive: true });
44
+ writeFileSync(destination, text);
45
+ }
46
+ return { ...buildTreeRenderer(stage, rels, options ? { ...options, referenceBase: base } : undefined), dispose };
47
+ }
48
+ catch (error) {
49
+ dispose();
50
+ throw error;
51
+ }
52
+ }