@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
@@ -0,0 +1,14 @@
1
+ import { readFile, realpath, stat } from 'node:fs/promises';
2
+ import { relative } from 'node:path';
3
+ import { within } from './output.js';
4
+ export const hiddenDocumentPath = (base, path) => relative(base, path).split(/[\\/]/).some(p => p.startsWith('.') || p === 'node_modules');
5
+ export async function readPreviewSource(basePath, filePath) {
6
+ const base = await realpath(basePath);
7
+ const path = await realpath(filePath);
8
+ if (!(await stat(base)).isDirectory() || !within(base, path) || hiddenDocumentPath(base, path)
9
+ || !/\.md$/i.test(path) || !(await stat(path)).isFile())
10
+ throw new Error('선택한 폴더 안의 Markdown 파일을 선택해 주세요.');
11
+ if ((await stat(path)).size > 1_000_000)
12
+ throw new Error('문서 미리보기는 1MB 이하 파일을 지원합니다.');
13
+ return { source: await readFile(path, 'utf8'), base, path };
14
+ }
@@ -0,0 +1,189 @@
1
+ /** Private workspace storage for browser sessions. Paths are virtual at the HTTP boundary. */
2
+ import { readdir, lstat, readFile, mkdir, rename, rm } from 'node:fs/promises';
3
+ import { resolve, join, dirname, relative } from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+ import MarkdownIt from 'markdown-it';
6
+ import { createDocument } from '@mixmark-io/domino';
7
+ import { parse } from 'dotenv';
8
+ import { mapMarkdownText } from '../conversion/pdf-pages.js';
9
+ import { WIKILINK_RE, parseWikilink } from '../documents/anchors.js';
10
+ import { within } from '../sync/workspace.js';
11
+ import { isConfluenceWebPath } from '../confluence/links.js';
12
+ export const MAX_FILE = 100 * 1024 * 1024, MAX_TOTAL = 512 * 1024 * 1024, MAX_FILES = 20000;
13
+ export const digest = (data) => createHash('sha256').update(data).digest('hex');
14
+ export function safeRelative(path) {
15
+ if (!path || path.includes('\\') || /[\x00-\x1f:]/.test(path) || path.startsWith('/') || path.split('/').some(p => !p || p === '.' || p === '..'))
16
+ throw Error('잘못된 파일 경로입니다.');
17
+ return path;
18
+ }
19
+ export function permitted(path) {
20
+ const parts = path.split('/');
21
+ return !parts.some((part, i) => part === 'node_modules' || part === '.git' || (part.startsWith('.') && !(i === 0 && ['.csync', '.env', '.confluence-sync.json', '.confluence-workspace.json', '.confluence-syncignore'].includes(part))));
22
+ }
23
+ export function pathIn(mounts, path) {
24
+ const mount = mounts.find(m => path === m.virtual || path.startsWith(m.virtual + '/'));
25
+ if (!mount)
26
+ throw Error('선택한 브라우저 폴더 안의 경로만 사용할 수 있습니다.');
27
+ const rel = path.slice(mount.virtual.length).replace(/^\//, '');
28
+ if (rel)
29
+ safeRelative(rel);
30
+ return join(mount.root, rel);
31
+ }
32
+ export function virtualPath(mounts, path) {
33
+ for (const mount of mounts) {
34
+ const rel = relative(mount.root, path);
35
+ if (!rel)
36
+ return mount.virtual;
37
+ if (within(mount.root, path))
38
+ return mount.virtual + '/' + rel.split('\\').join('/');
39
+ }
40
+ throw Error('작업공간 밖의 경로입니다.');
41
+ }
42
+ export async function snapshot(mounts, cache = new Map()) {
43
+ const files = [];
44
+ let total = 0;
45
+ async function walk(mount, dir) {
46
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
47
+ const file = join(dir, entry.name), rel = relative(mount.root, file).split('\\').join('/');
48
+ if (!permitted(rel))
49
+ continue;
50
+ safeRelative(rel);
51
+ const stat = await lstat(file);
52
+ if (stat.isSymbolicLink())
53
+ throw Error('작업공간의 심볼릭 링크는 지원하지 않습니다.');
54
+ if (stat.isDirectory()) {
55
+ await walk(mount, file);
56
+ continue;
57
+ }
58
+ if (!stat.isFile())
59
+ continue;
60
+ total += stat.size;
61
+ if (files.length >= MAX_FILES || stat.size > MAX_FILE || total > MAX_TOTAL)
62
+ throw Error('작업공간 한도를 초과했습니다(파일 100MiB, 전체 512MiB, 20,000개).');
63
+ const stamp = `${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`, old = cache.get(file), hash = old?.stamp === stamp ? old.hash : digest(await readFile(file));
64
+ cache.set(file, { stamp, hash });
65
+ files.push({ path: virtualPath(mounts, file), size: stat.size, hash });
66
+ }
67
+ }
68
+ for (const mount of mounts)
69
+ await walk(mount, mount.root);
70
+ return files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
71
+ }
72
+ export function validateEnv(text) { const env = parse(text); const base = env.CONFLUENCE_BASE_URL; if (!base)
73
+ return; const url = new URL(base); if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || !(url.hostname === 'api.atlassian.com' || url.hostname.endsWith('.atlassian.net')))
74
+ throw Error('배포 웹은 Atlassian Cloud의 HTTPS 주소만 지원합니다.'); }
75
+ /** Reject local link destinations that would make shared CLI code read server files.
76
+ * Metadata-only Fetch does not follow document links; keep env/mapping checks.
77
+ */
78
+ export async function validateWorkspace(mounts, checkEnv = true, checkDocuments = true) {
79
+ const parser = new MarkdownIt({ html: true });
80
+ const entries = await snapshot(mounts);
81
+ const allowed = (file, target, webLink = false) => {
82
+ try {
83
+ target = target.replace(/^<(.*)>$/, '$1');
84
+ for (let i = 0; i < 4; i++) {
85
+ const decoded = decodeURIComponent(target);
86
+ if (decoded === target)
87
+ break;
88
+ target = decoded;
89
+ }
90
+ }
91
+ catch {
92
+ throw Error('해석할 수 없는 링크 경로입니다.');
93
+ }
94
+ if (!target || target.startsWith('#') || /^(?:https?:|mailto:|data:)/i.test(target))
95
+ return;
96
+ if (webLink && isConfluenceWebPath(target))
97
+ return;
98
+ if (/^[a-z][a-z\d+.-]*:|^\/\//i.test(target) || target.startsWith('/') || target.includes('\\'))
99
+ throw Error(`${virtualPath(mounts, file)}: 서버의 절대 경로를 참조하는 문서는 처리할 수 없습니다.`);
100
+ const path = resolve(dirname(file), target.split(/[?#]/)[0]);
101
+ if (!mounts.some(m => within(m.root, path)))
102
+ throw Error('선택한 폴더 밖의 첨부·문서를 참조합니다. 참조 폴더를 추가하거나 링크를 수정하세요.');
103
+ };
104
+ for (const entry of entries) {
105
+ const file = pathIn(mounts, entry.path), rel = relative(mounts.find(m => entry.path.startsWith(m.virtual + '/')).root, file).split('\\').join('/');
106
+ if (rel === '.env') {
107
+ if (checkEnv)
108
+ validateEnv(await readFile(file, 'utf8'));
109
+ continue;
110
+ }
111
+ if (rel === '.csync/mapping.json' || rel === '.confluence-sync.json' || rel === '.csync/index.json') {
112
+ const value = JSON.parse(await readFile(file, 'utf8'));
113
+ for (const key of Object.keys(rel.endsWith('index.json') ? value.entries ?? {} : value))
114
+ safeRelative(key.replace(/\/$/, ''));
115
+ }
116
+ if (!checkDocuments || !/\.md$/i.test(file) || rel.startsWith('.csync/'))
117
+ continue;
118
+ const text = await readFile(file, 'utf8');
119
+ const scan = (part) => {
120
+ for (const match of part.matchAll(/(?<!!)(!?)\[([^\]\n]*)\]\((<[^>\n]+>|[^()\s]+)\)/g)) {
121
+ if ((part.slice(0, match.index).match(/\\+$/)?.[0].length ?? 0) % 2)
122
+ continue;
123
+ allowed(file, match[3], !match[1]);
124
+ }
125
+ for (const match of part.matchAll(WIKILINK_RE)) {
126
+ if ((part.slice(0, match.index).match(/\\+$/)?.[0].length ?? 0) % 2)
127
+ continue;
128
+ const link = parseWikilink(match[1], match[2], match[3], match[4]);
129
+ if (link?.target)
130
+ allowed(file, link.target, !link.embed);
131
+ }
132
+ return part;
133
+ };
134
+ mapMarkdownText(text, scan, html => scan(html.replace(/<!--[\s\S]*?-->|<(script|style|pre|code)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, '')));
135
+ const inspect = (tokens) => {
136
+ for (const token of tokens) {
137
+ if (token.type === 'image')
138
+ allowed(file, token.attrGet('src') ?? '');
139
+ if (token.type === 'link_open')
140
+ allowed(file, token.attrGet('href') ?? '', true);
141
+ if (token.children)
142
+ inspect(token.children);
143
+ if (token.type === 'html_block' || token.type === 'html_inline')
144
+ for (const node of Array.from(createDocument(token.content).querySelectorAll('[href], [src]'))) {
145
+ if (node.hasAttribute('href'))
146
+ allowed(file, node.getAttribute('href') ?? '', node.localName === 'a');
147
+ if (node.hasAttribute('src'))
148
+ allowed(file, node.getAttribute('src') ?? '');
149
+ }
150
+ }
151
+ };
152
+ inspect(parser.parse(text, {}));
153
+ }
154
+ }
155
+ /** Apply a complete upload transaction; rollback only files this transaction moved. */
156
+ export async function commitMirror(mounts, stage, uploads, removed) {
157
+ const applied = [], backups = [];
158
+ await mkdir(join(stage, 'backup'), { recursive: true });
159
+ try {
160
+ for (const path of [...uploads.map(f => f.path), ...removed]) {
161
+ const file = pathIn(mounts, path);
162
+ try {
163
+ const stat = await lstat(file);
164
+ if (!stat.isFile() || stat.isSymbolicLink())
165
+ throw Error('파일 충돌을 확인하세요.');
166
+ const backup = join(stage, 'backup', String(backups.length));
167
+ await rename(file, backup);
168
+ backups.push({ file, backup });
169
+ }
170
+ catch (e) {
171
+ if (e.code !== 'ENOENT')
172
+ throw e;
173
+ }
174
+ }
175
+ for (let i = 0; i < uploads.length; i++) {
176
+ const file = pathIn(mounts, uploads[i].path);
177
+ await mkdir(dirname(file), { recursive: true });
178
+ await rename(join(stage, String(i)), file);
179
+ applied.push(file);
180
+ }
181
+ }
182
+ catch (error) {
183
+ for (const file of applied.reverse())
184
+ await rm(file, { force: true });
185
+ for (const item of backups.reverse())
186
+ await rename(item.backup, item.file);
187
+ throw error;
188
+ }
189
+ }
@@ -0,0 +1,316 @@
1
+ /** Browser-only deployment: same CLI core inside isolated, short-lived file workspaces. */
2
+ import { createServer } from 'node:http';
3
+ import { randomBytes, randomUUID, createHash } from 'node:crypto';
4
+ import { mkdtemp, mkdir, rm, readFile, writeFile, realpath } from 'node:fs/promises';
5
+ import { join, resolve, extname, isAbsolute } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { startWeb, deployedUi } from './server.js';
9
+ import { webPage } from './views/workspace.js';
10
+ import { tableEditorBridge } from './table-editor-script.js';
11
+ import { readPkgVersion } from '../shared/version.js';
12
+ import { within } from '../sync/workspace.js';
13
+ import { snapshot, pathIn, virtualPath, permitted, safeRelative, digest, validateWorkspace, validateEnv, commitMirror, MAX_FILE, MAX_TOTAL, MAX_FILES } from './hosted-files.js';
14
+ function publicValue(session, value) {
15
+ if (typeof value === 'string') {
16
+ if (isAbsolute(value) && session.mounts.some(m => within(m.root, value)))
17
+ return virtualPath(session.mounts, value);
18
+ return session.mounts.reduce((text, m) => text.split(m.root).join(m.virtual), value).split(session.root).join('(임시 작업공간)');
19
+ }
20
+ if (Array.isArray(value))
21
+ return value.map(item => publicValue(session, item));
22
+ if (value && typeof value === 'object')
23
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, publicValue(session, item)]));
24
+ return value;
25
+ }
26
+ const index = (files) => new Map(files.map(f => [f.path, f]));
27
+ async function body(req, max = 16 * 1024 * 1024) { let size = 0; const chunks = []; for await (const chunk of req) {
28
+ size += chunk.length;
29
+ if (size > max)
30
+ throw Error('요청 크기 한도를 초과했습니다.');
31
+ chunks.push(Buffer.from(chunk));
32
+ } return Buffer.concat(chunks); }
33
+ const jsonBody = async (req) => { if (!req.headers['content-type']?.startsWith('application/json'))
34
+ throw Error('JSON 요청이 필요합니다.'); return JSON.parse((await body(req)).toString('utf8')); };
35
+ export async function startHostedWeb(options = {}) {
36
+ const sessions = new Map();
37
+ let creating = 0;
38
+ const ttl = options.ttlMs ?? 30 * 60_000;
39
+ const publicOrigin = options.publicUrl ? deployedUi(options.publicUrl).origin : undefined;
40
+ const assets = resolve(fileURLToPath(new URL('../../dist/web/assets/', import.meta.url)));
41
+ const bridgeHash = createHash('sha256').update(`(${tableEditorBridge.toString()})()`).digest('base64');
42
+ const dispose = async (s) => { sessions.delete(s.id); s.core.server.closeAllConnections(); await new Promise(r => s.core.server.close(() => r())); await rm(s.root, { recursive: true, force: true }); };
43
+ const changes = async (s) => { const files = await snapshot(s.mounts, s.cache), old = index(s.baseline), now = index(files); return { files: files.filter(f => old.get(f.path)?.hash !== f.hash), removed: s.baseline.filter(f => !now.has(f.path)).map(f => f.path), snapshot: files }; };
44
+ const server = createServer(async (req, res) => {
45
+ const send = (code, value) => { res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(JSON.stringify(value)); };
46
+ res.setHeader('Cache-Control', 'no-store');
47
+ res.setHeader('X-Content-Type-Options', 'nosniff');
48
+ res.setHeader('Referrer-Policy', 'no-referrer');
49
+ res.setHeader('Content-Security-Policy', `default-src 'self'; script-src 'self' 'sha256-${bridgeHash}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-src 'self'; worker-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'`);
50
+ let session, locked = false;
51
+ try {
52
+ const url = new URL(req.url ?? '/', 'http://localhost');
53
+ if (req.headers.origin && req.headers.origin !== (publicOrigin ?? `http://${req.headers.host}`)) {
54
+ send(403, { error: '같은 웹 화면에서만 요청할 수 있습니다.' });
55
+ return;
56
+ }
57
+ if (req.method === 'GET' && url.pathname === '/') {
58
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
59
+ res.end(webPage('browser', 'browser'));
60
+ return;
61
+ }
62
+ if (req.method === 'GET' && url.pathname === '/healthz') {
63
+ send(200, { ok: true });
64
+ return;
65
+ }
66
+ if (req.method === 'GET' && url.pathname.startsWith('/assets/')) {
67
+ const file = resolve(assets, decodeURIComponent(url.pathname.slice(8)));
68
+ if (file === assets || !within(assets, file))
69
+ throw Error('잘못된 파일 경로입니다.');
70
+ const types = { '.js': 'text/javascript', '.css': 'text/css', '.ttf': 'font/ttf', '.png': 'image/png', '.svg': 'image/svg+xml', '.txt': 'text/plain; charset=utf-8' };
71
+ if (!types[extname(file)]) {
72
+ send(404, {});
73
+ return;
74
+ }
75
+ res.setHeader('Content-Type', types[extname(file)]);
76
+ res.end(await readFile(file));
77
+ return;
78
+ }
79
+ if (url.pathname === '/api/browser/session' && req.method === 'POST') {
80
+ const input = await jsonBody(req);
81
+ if (sessions.size + creating >= (options.maxSessions ?? 8)) {
82
+ send(503, { error: '현재 작업공간이 가득 찼습니다. 잠시 후 다시 시도하세요.' });
83
+ return;
84
+ }
85
+ const name = String(input.name ?? '문서');
86
+ if (!name || name.length > 255)
87
+ throw Error('폴더 이름을 확인하세요.');
88
+ safeRelative(name);
89
+ if (name.includes('/'))
90
+ throw Error('폴더 이름을 확인하세요.');
91
+ creating++;
92
+ let root;
93
+ try {
94
+ root = await realpath(await mkdtemp(join(tmpdir(), 'csync-browser-')));
95
+ await mkdir(join(root, 'workspace'));
96
+ await mkdir(join(root, 'output'));
97
+ const core = await startWeb({ start: join(root, 'workspace'), port: 0, syncRunner: options.syncRunner });
98
+ const s = { id: randomUUID(), token: randomBytes(32).toString('hex'), root, core, mounts: [{ virtual: '/workspace/' + name, root: join(root, 'workspace') }, { virtual: '/output', root: join(root, 'output') }], baseline: [], cache: new Map(), busy: false, touched: Date.now() };
99
+ sessions.set(s.id, s);
100
+ send(201, { id: s.id, token: s.token, base: s.mounts[0].virtual, output: '/output', version: readPkgVersion() });
101
+ }
102
+ catch (e) {
103
+ if (root)
104
+ await rm(root, { recursive: true, force: true });
105
+ throw e;
106
+ }
107
+ finally {
108
+ creating--;
109
+ }
110
+ return;
111
+ }
112
+ const id = String(req.headers['x-csync-session'] ?? '');
113
+ session = sessions.get(id);
114
+ if (!session || req.headers['x-csync-token'] !== session.token) {
115
+ send(401, { error: '브라우저 작업공간을 다시 연결하세요.' });
116
+ return;
117
+ }
118
+ const s = session;
119
+ if (Date.now() - s.touched > ttl && !s.core.isRunning()) {
120
+ await dispose(s);
121
+ send(410, { error: '임시 작업공간이 만료되었습니다. 폴더를 다시 여세요.' });
122
+ return;
123
+ }
124
+ s.touched = Date.now();
125
+ if (s.busy) {
126
+ send(409, { error: '다른 브라우저 요청이 진행 중입니다.' });
127
+ return;
128
+ }
129
+ s.busy = true;
130
+ locked = true;
131
+ if (url.pathname === '/api/browser/session' && req.method === 'DELETE') {
132
+ if (s.core.isRunning())
133
+ throw Error('작업 완료 후 연결을 해제하세요.');
134
+ await dispose(s);
135
+ send(200, { ok: true });
136
+ return;
137
+ }
138
+ if (url.pathname === '/api/browser/mount' && req.method === 'POST') {
139
+ if (s.core.isRunning())
140
+ throw Error('작업 완료 후 폴더를 추가하세요.');
141
+ const input = await jsonBody(req);
142
+ if (input.kind !== 'reference')
143
+ throw Error('폴더 종류를 확인하세요.');
144
+ const n = s.mounts.filter(m => m.readonly).length;
145
+ if (n >= 8)
146
+ throw Error('참조 폴더는 8개까지 추가할 수 있습니다.');
147
+ const mount = { virtual: '/references/' + randomUUID(), root: join(s.root, 'reference-' + n), readonly: true };
148
+ await mkdir(mount.root);
149
+ s.mounts.push(mount);
150
+ send(201, { path: mount.virtual });
151
+ return;
152
+ }
153
+ if (url.pathname === '/api/browser/mirror' && req.method === 'POST') {
154
+ if (s.core.isRunning())
155
+ throw Error('작업 중에는 파일을 다시 전송할 수 없습니다.');
156
+ const pending = await changes(s);
157
+ if (pending.files.length || pending.removed.length) {
158
+ send(409, { error: '서버 결과를 로컬에 먼저 반영하세요.', code: 'APPLY_PENDING' });
159
+ return;
160
+ }
161
+ const { files } = await jsonBody(req);
162
+ if (!Array.isArray(files) || files.length > MAX_FILES)
163
+ throw Error('파일 목록 한도를 확인하세요.');
164
+ const seen = new Set();
165
+ let total = 0;
166
+ for (const f of files) {
167
+ if (typeof f.path !== 'string' || !Number.isSafeInteger(f.size) || f.size < 0 || f.size > MAX_FILE || typeof f.hash !== 'string' || !/^[a-f0-9]{64}$/.test(f.hash) || seen.has(f.path))
168
+ throw Error('파일 목록을 확인하세요.');
169
+ const file = pathIn(s.mounts, f.path);
170
+ const mount = s.mounts.find(m => file !== m.root && within(m.root, file));
171
+ if (!mount || !permitted(f.path.slice(mount.virtual.length + 1)))
172
+ throw Error('지원하지 않는 파일 경로입니다.');
173
+ seen.add(f.path);
174
+ total += f.size;
175
+ }
176
+ if (total > MAX_TOTAL)
177
+ throw Error('작업공간이 512MiB를 초과했습니다.');
178
+ if (s.transaction)
179
+ await rm(s.transaction.stage, { recursive: true, force: true });
180
+ const before = index(s.baseline), uploads = files.filter(f => before.get(f.path)?.hash !== f.hash);
181
+ s.transaction = { stage: await mkdtemp(join(s.root, 'mirror-')), manifest: files, uploads, received: new Set() };
182
+ send(200, { uploads });
183
+ return;
184
+ }
185
+ if (url.pathname === '/api/browser/file' && req.method === 'PUT') {
186
+ const tx = s.transaction, path = url.searchParams.get('path') ?? '', at = tx?.uploads.findIndex(f => f.path === path) ?? -1;
187
+ if (!tx || at < 0)
188
+ throw Error('파일 전송을 다시 시작하세요.');
189
+ const bytes = await body(req, MAX_FILE), file = tx.uploads[at];
190
+ if (bytes.length !== file.size || digest(bytes) !== file.hash)
191
+ throw Error('파일 전송 중 내용이 바뀌었습니다. 다시 시도하세요.');
192
+ await writeFile(join(tx.stage, String(at)), bytes);
193
+ tx.received.add(path);
194
+ send(200, { ok: true });
195
+ return;
196
+ }
197
+ if (url.pathname === '/api/browser/commit' && req.method === 'POST') {
198
+ const tx = s.transaction;
199
+ if (!tx || tx.received.size !== tx.uploads.length)
200
+ throw Error('파일 전송을 먼저 완료하세요.');
201
+ const remaining = new Set(tx.manifest.map(f => f.path));
202
+ await commitMirror(s.mounts, tx.stage, tx.uploads, s.baseline.filter(f => !remaining.has(f.path)).map(f => f.path));
203
+ s.baseline = await snapshot(s.mounts, s.cache);
204
+ await rm(tx.stage, { recursive: true, force: true });
205
+ s.transaction = undefined;
206
+ send(200, { ok: true });
207
+ return;
208
+ }
209
+ if (url.pathname === '/api/browser/changes' && req.method === 'GET') {
210
+ if (s.core.isRunning())
211
+ throw Error('작업 완료 후 결과를 반영하세요.');
212
+ const value = await changes(s);
213
+ send(200, value);
214
+ return;
215
+ }
216
+ if (url.pathname === '/api/browser/file' && req.method === 'GET') {
217
+ const file = pathIn(s.mounts, url.searchParams.get('path') ?? '');
218
+ const bytes = await readFile(file);
219
+ if (bytes.length > MAX_FILE)
220
+ throw Error('파일 크기를 확인하세요.');
221
+ res.setHeader('Content-Type', 'application/octet-stream');
222
+ res.end(bytes);
223
+ return;
224
+ }
225
+ if (url.pathname === '/api/browser/ack' && req.method === 'POST') {
226
+ const { files } = await jsonBody(req), now = await snapshot(s.mounts, s.cache);
227
+ if (JSON.stringify(files) !== JSON.stringify(now))
228
+ throw Error('반영 중 서버 파일이 바뀌었습니다. 다시 확인하세요.');
229
+ s.baseline = now;
230
+ send(200, { ok: true });
231
+ return;
232
+ }
233
+ if (!url.pathname.startsWith('/api/') || !['GET', 'POST'].includes(req.method ?? '')) {
234
+ send(404, {});
235
+ return;
236
+ }
237
+ if (s.transaction)
238
+ throw Error('파일 전송을 먼저 완료하세요.');
239
+ if (url.pathname !== '/api/sync/job' && !s.core.isRunning()) {
240
+ const pending = await changes(s);
241
+ if (pending.files.length || pending.removed.length) {
242
+ send(409, { error: '서버 결과를 로컬에 먼저 반영하세요.', code: 'APPLY_PENDING' });
243
+ return;
244
+ }
245
+ }
246
+ if (['/api/pick-folder', '/api/open'].includes(url.pathname))
247
+ throw Error('브라우저에서 폴더를 선택하세요.');
248
+ if (req.method === 'POST') {
249
+ const input = await jsonBody(req);
250
+ if (url.pathname === '/api/sync/env/import')
251
+ validateEnv(String(input.text ?? ''));
252
+ for (const key of ['base', 'file', 'out', 'start'])
253
+ if (typeof input[key] === 'string') {
254
+ if (!input[key] && ['base', 'file'].includes(key))
255
+ throw Error('브라우저 폴더를 먼저 선택하세요.');
256
+ if (input[key])
257
+ input[key] = pathIn(s.mounts, input[key]);
258
+ }
259
+ if (typeof input.path === 'string' && input.path.startsWith('/'))
260
+ input.path = pathIn(s.mounts, input.path);
261
+ if (input.base && !within(s.mounts[0].root, input.base))
262
+ throw Error('문서 폴더를 기준으로 작업하세요.');
263
+ if (input.referenceRoots)
264
+ input.referenceRoots = input.referenceRoots.map((path) => pathIn(s.mounts, path));
265
+ if (url.pathname === '/api/convert' && !input.out) {
266
+ input.out = s.mounts[1].root;
267
+ }
268
+ if (!['/api/sync/config'].includes(url.pathname))
269
+ await validateWorkspace(s.mounts, url.pathname.startsWith('/api/sync/') && !input.envId && !url.pathname.endsWith('/env/import'), url.pathname !== '/api/sync/fetch' && !(url.pathname === '/api/sync/staging' && input?.remoteOnly === true));
270
+ const response = await fetch(s.core.url + url.pathname, { method: 'POST', headers: { 'X-Csync-Token': s.core.token, 'Content-Type': 'application/json' }, body: JSON.stringify(input) });
271
+ const data = await response.json();
272
+ send(response.status, publicValue(s, data));
273
+ return;
274
+ }
275
+ if (url.pathname === '/api/browse' || url.pathname === '/api/search')
276
+ url.searchParams.set('path', pathIn(s.mounts, url.searchParams.get('path') || s.mounts[0].virtual));
277
+ const response = await fetch(s.core.url + url.pathname + url.search, { headers: { 'X-Csync-Token': s.core.token } });
278
+ const data = await response.json();
279
+ if (url.pathname === '/api/browse') {
280
+ data.parent = data.path === s.mounts[0].root ? s.mounts[0].root : data.parent;
281
+ data.home = s.mounts[0].root;
282
+ }
283
+ send(response.status, publicValue(s, data));
284
+ }
285
+ catch (error) {
286
+ if (!res.writableEnded) {
287
+ const value = String(error.message);
288
+ send(400, { error: session ? value.split(session.root).join('(임시 작업공간)') : value });
289
+ }
290
+ }
291
+ finally {
292
+ if (locked && session)
293
+ session.busy = false;
294
+ }
295
+ });
296
+ const timer = setInterval(() => { for (const s of sessions.values())
297
+ if (!s.busy && !s.core.isRunning() && Date.now() - s.touched > ttl)
298
+ void dispose(s); }, Math.min(ttl, 60_000));
299
+ timer.unref();
300
+ await new Promise((done, reject) => { server.once('error', reject); server.listen(options.port ?? 8080, options.host ?? '0.0.0.0', done); });
301
+ return { server, url: `http://127.0.0.1:${server.address().port}`, close: async () => { clearInterval(timer); server.closeAllConnections(); await new Promise(r => server.close(() => r())); for (const s of [...sessions.values()])
302
+ await dispose(s); } };
303
+ }
304
+ export async function runHostedWeb(argv) {
305
+ const at = argv.indexOf('--port'), port = Number(at < 0 ? 8080 : argv[at + 1]);
306
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
307
+ throw Error('포트를 확인하세요.');
308
+ const app = await startHostedWeb({ port, publicUrl: process.env.CSYNC_PUBLIC_URL || undefined });
309
+ console.log(`브라우저 작업공간 웹: 0.0.0.0:${port} (로컬 실행기 불필요)`);
310
+ const stop = () => void app.close();
311
+ process.once('SIGTERM', stop);
312
+ process.once('SIGINT', stop);
313
+ await new Promise(done => app.server.once('close', done));
314
+ process.removeListener('SIGTERM', stop);
315
+ process.removeListener('SIGINT', stop);
316
+ }
@@ -0,0 +1,26 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
3
+ export const runSyncCLI = (args, settings, log) => new Promise((resolveJob, reject) => {
4
+ const source = import.meta.url.endsWith('.ts');
5
+ const worker = fileURLToPath(new URL(source ? '../sync.ts' : '../sync.js', import.meta.url));
6
+ const child = spawn(process.execPath, [...(source ? process.execArgv : []), worker, ...args], {
7
+ env: { ...process.env, ...settings, NO_COLOR: '1' }, stdio: ['ignore', 'pipe', 'pipe'],
8
+ });
9
+ // Emit complete lines so secret masking cannot be bypassed by split stream chunks.
10
+ for (const stream of [child.stdout, child.stderr]) {
11
+ let pending = '';
12
+ stream.setEncoding('utf8');
13
+ stream.on('data', (chunk) => {
14
+ pending += chunk;
15
+ const end = pending.lastIndexOf('\n');
16
+ if (end >= 0) {
17
+ log(pending.slice(0, end + 1));
18
+ pending = pending.slice(end + 1);
19
+ }
20
+ });
21
+ stream.on('end', () => { if (pending)
22
+ log(pending); });
23
+ }
24
+ child.once('error', reject);
25
+ child.once('close', code => resolveJob(code ?? 1));
26
+ });
@@ -0,0 +1,74 @@
1
+ import { readFile, realpath, stat } from 'node:fs/promises';
2
+ import { dirname, extname, resolve } from 'node:path';
3
+ import { createDocument } from '@mixmark-io/domino';
4
+ import { htmlTableMarkdown } from '../documents/html-tables.js';
5
+ import { splitFrontmatter } from '../documents/obsidian.js';
6
+ import { within } from './output.js';
7
+ import { readPreviewSource, hiddenDocumentPath as hidden } from './document-source.js';
8
+ import { editableTables, sourceHash } from './table-format.js';
9
+ import { tableEditorBridge } from './table-editor-script.js';
10
+ import { randomBytes } from 'node:crypto';
11
+ const elements = new Set('h1 h2 h3 h4 h5 h6 p a img ul ol li dl dt dd blockquote pre code em strong s del b i u sub sup br hr table caption colgroup col thead tbody tfoot tr th td span div details summary'.split(' '));
12
+ const discard = new Set('SCRIPT STYLE IFRAME OBJECT EMBED FORM INPUT BUTTON TEXTAREA SELECT META LINK BASE SVG MATH TEMPLATE'.split(' '));
13
+ const attributes = new Set(['alt', 'title', 'colspan', 'rowspan', 'span', 'start', 'scope', 'id']);
14
+ const imageTypes = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.avif': 'image/avif' };
15
+ const allowedStyle = /^(?:width|max-width|height|text-align|vertical-align|table-layout|color|background-color|white-space|font-weight|font-style|text-decoration)$/i;
16
+ /** Return a self-contained, inert document. No file writes or external requests. */
17
+ export async function previewDocument(basePath, filePath, nonce) {
18
+ const { source: original, base, path: file } = await readPreviewSource(basePath, filePath);
19
+ const { body } = splitFrontmatter(original);
20
+ const doc = createDocument(htmlTableMarkdown().render(body));
21
+ for (const element of Array.from(doc.body.querySelectorAll('*'))) {
22
+ if (discard.has(element.tagName)) {
23
+ element.remove();
24
+ continue;
25
+ }
26
+ if (!elements.has(element.tagName.toLowerCase())) {
27
+ while (element.firstChild)
28
+ element.parentNode?.insertBefore(element.firstChild, element);
29
+ element.remove();
30
+ continue;
31
+ }
32
+ const source = element.tagName === 'IMG' ? element.getAttribute('src') : null;
33
+ for (const attr of Array.from(element.attributes)) {
34
+ if (attr.name === 'style') {
35
+ const safe = attr.value.split(';').filter(declaration => {
36
+ const colon = declaration.indexOf(':');
37
+ return colon > 0 && allowedStyle.test(declaration.slice(0, colon).trim())
38
+ && !/url\s*\(|expression|@|\\|[<>]/i.test(declaration.slice(colon + 1));
39
+ }).join(';');
40
+ element.setAttribute('style', safe);
41
+ }
42
+ else if (!(attributes.has(attr.name) || (attr.name === 'href' && attr.value.startsWith('#'))))
43
+ element.removeAttribute(attr.name);
44
+ }
45
+ if (source) {
46
+ try {
47
+ if (/^[a-z][a-z\d+.-]*:|^[/\\]/i.test(source))
48
+ continue;
49
+ const image = await realpath(resolve(dirname(file), decodeURIComponent(source.split(/[?#]/)[0])));
50
+ const type = imageTypes[extname(image).toLowerCase()];
51
+ if (!type || !within(base, image) || hidden(base, image))
52
+ continue;
53
+ const info = await stat(image);
54
+ if (!info.isFile() || info.size > 5_000_000)
55
+ continue;
56
+ element.setAttribute('src', `data:${type};base64,${(await readFile(image)).toString('base64')}`);
57
+ }
58
+ catch { /* Missing/out-of-root images remain as their alt text. */ }
59
+ }
60
+ }
61
+ const tables = editableTables(original);
62
+ const rendered = Array.from(doc.querySelectorAll('table'));
63
+ if (rendered.length !== tables.length)
64
+ tables.forEach(t => { t.editable = false; });
65
+ tables.forEach((t, i) => { if (t.editable)
66
+ rendered[i]?.setAttribute('data-csync-table', String(t.index)); });
67
+ const channel = randomBytes(24).toString('hex');
68
+ const bridge = typeof __CSYNC_TABLE_BRIDGE__ === 'string' ? __CSYNC_TABLE_BRIDGE__ : tableEditorBridge.toString();
69
+ const script = nonce ? `<script nonce="${nonce}" data-channel="${channel}">(${bridge})()</script>` : '';
70
+ return { hash: sourceHash(original), channel, tables, html: `<!doctype html><html lang="ko"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src ${nonce ? `'nonce-${nonce}'` : "'none'"}; img-src data:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'"><meta name="viewport" content="width=device-width,initial-scale=1"><style>
71
+ *{box-sizing:border-box}body{margin:0;padding:24px;color:#172b4d;background:white;font:15px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}main{max-width:760px;margin:auto}h1,h2,h3,h4{line-height:1.4}h1{font-size:26px}h2{font-size:22px}h3{font-size:18px}table{border-collapse:collapse;margin:16px 0}th,td{border:1px solid #c7cfdb;padding:8px;vertical-align:top;overflow-wrap:anywhere}th{background:#f0f3f8}td p,th p{margin:0}img{max-width:100%;height:auto}pre{padding:14px;background:#f4f6f9;overflow:auto}code{font-size:.88em;overflow-wrap:anywhere}blockquote{border-left:3px solid #c7cfdb;margin-left:0;padding-left:16px;color:#526078}a{color:#315fd8}hr{border:0;border-top:1px solid #dde3ec}
72
+ .csync-editing table[data-csync-table]{cursor:pointer}.csync-editing table[data-csync-table]:hover{outline:2px dashed #8192e3}.csync-selected{outline:3px solid #5466d8!important}.csync-resize{position:absolute;width:10px;cursor:col-resize;z-index:10;touch-action:none;border-left:2px solid #5466d8;background:#5466d82a}
73
+ </style></head><body><main>${doc.body.innerHTML}</main>${script}</body></html>` };
74
+ }