@wonseok-han/confluence-sync 1.8.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.
- package/README.md +44 -410
- package/dist/cli/args.js +1 -1
- package/dist/cli/help.js +214 -115
- package/dist/cli/init.js +44 -5
- package/dist/cli/main.js +42 -11
- package/dist/cli/status.js +83 -0
- package/dist/confluence/client.js +121 -57
- package/dist/confluence/config.js +12 -8
- package/dist/confluence/env-file.js +23 -0
- package/dist/confluence/links.js +18 -0
- package/dist/conversion/asset-paths.js +5 -0
- package/dist/conversion/attachments.js +29 -7
- package/dist/conversion/convert.js +10 -22
- package/dist/conversion/footnotes.js +114 -11
- package/dist/conversion/markdown.js +26 -0
- package/dist/conversion/pdf-pages.js +10 -8
- package/dist/documents/anchors.js +9 -0
- package/dist/documents/callout-roundtrip.js +75 -0
- package/dist/documents/callouts.js +74 -0
- package/dist/documents/catalog.js +2 -2
- package/dist/documents/footnote-roundtrip.js +96 -0
- package/dist/documents/format.js +116 -0
- package/dist/documents/html-tables.js +30 -0
- package/dist/documents/html-to-markdown.js +47 -13
- package/dist/documents/markdown.js +46 -4
- package/dist/documents/obsidian.js +6 -3
- package/dist/documents/tables.js +89 -0
- package/dist/runtime/network.js +4 -0
- package/dist/shared/colors.js +2 -2
- package/dist/shared/progress.js +164 -0
- package/dist/shared/version.js +12 -0
- package/dist/site/assets/Monaco-LICENSE.txt +21 -0
- package/dist/site/assets/Monaco-ThirdPartyNotices.txt +448 -0
- package/dist/site/assets/PDFJS-LICENSE.txt +177 -0
- package/dist/site/assets/app.css +1 -0
- package/dist/site/assets/app.js +4 -0
- package/dist/site/assets/chunks/chunk-TEW74QPN.js +35 -0
- package/dist/site/assets/chunks/markdown-S6623E66.js +1 -0
- package/dist/site/assets/chunks/monaco-36ZGUH4Y.js +897 -0
- package/dist/site/assets/chunks/monaco-HFLEBJTI.css +1 -0
- package/dist/site/assets/chunks/pdf-DDN7TXTP.js +55 -0
- package/dist/site/assets/confluence-env-example.txt +32 -0
- package/dist/site/assets/editor.worker.js +1 -0
- package/dist/site/assets/favicon.svg +3 -0
- package/dist/site/assets/guide/01-account-settings.png +0 -0
- package/dist/site/assets/guide/02-atlassian-security.png +0 -0
- package/dist/site/assets/guide/03-api-tokens.png +0 -0
- package/dist/site/assets/guide/04-scope-api-token-step1.png +0 -0
- package/dist/site/assets/guide/05-scope-api-token-step2.png +0 -0
- package/dist/site/assets/guide/06-scope-api-token-step3.png +0 -0
- package/dist/site/assets/guide/07-scope-api-token-step4.png +0 -0
- package/dist/site/assets/guide/web/document-preview.png +0 -0
- package/dist/site/assets/guide/web/remote-diff.png +0 -0
- package/dist/site/assets/guide/web/sync-workspace.png +0 -0
- package/dist/site/assets/pdf/cmaps/78-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/78-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/78-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/78-RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/78-RKSJ-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/78-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Add-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Add-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/B5pc-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/B5pc-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/CNS-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/CNS-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/CNS1-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/CNS1-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/CNS2-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/CNS2-V.bcmap +3 -0
- package/dist/site/assets/pdf/cmaps/ETHK-B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/ETHK-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/ETen-B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/ETen-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/ETenms-B5-H.bcmap +3 -0
- package/dist/site/assets/pdf/cmaps/ETenms-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Ext-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Ext-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GB-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GB-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GB-H.bcmap +4 -0
- package/dist/site/assets/pdf/cmaps/GB-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBK-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBK-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBK2K-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBK2K-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBT-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBT-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBT-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBT-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKdla-B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKdla-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKm314-B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKm314-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKm471-B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKm471-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKscs-B5-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/HKscs-B5-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Hankaku.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Hiragana.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSC-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSC-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSC-Johab-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSC-Johab-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Katakana.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/LICENSE +36 -0
- package/dist/site/assets/pdf/cmaps/NWP-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/NWP-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/RKSJ-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/RKSJ-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/Roman.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/V.bcmap +0 -0
- package/dist/site/assets/pdf/cmaps/WP-Symbol.bcmap +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitDingbats.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitFixed.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitSerif.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/FoxitSymbol.pfb +0 -0
- package/dist/site/assets/pdf/standard_fonts/LICENSE_FOXIT +27 -0
- package/dist/site/assets/pdf/standard_fonts/LICENSE_LIBERATION +363 -0
- package/dist/site/assets/pdf/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/dist/site/assets/pdf/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/dist/site/assets/pdf/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/dist/site/assets/pdf/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/dist/site/assets/pdf/wasm/LICENSE_JBIG2 +196 -0
- package/dist/site/assets/pdf/wasm/LICENSE_OPENJPEG +39 -0
- package/dist/site/assets/pdf/wasm/LICENSE_PDFJS_JBIG2 +13 -0
- package/dist/site/assets/pdf/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
- package/dist/site/assets/pdf/wasm/LICENSE_PDFJS_QCMS +19 -0
- package/dist/site/assets/pdf/wasm/LICENSE_QCMS +21 -0
- package/dist/site/assets/pdf/wasm/jbig2.wasm +0 -0
- package/dist/site/assets/pdf/wasm/jbig2_nowasm_fallback.js +15 -0
- package/dist/site/assets/pdf/wasm/openjpeg.wasm +0 -0
- package/dist/site/assets/pdf/wasm/openjpeg_nowasm_fallback.js +17 -0
- package/dist/site/assets/pdf/wasm/qcms_bg.wasm +0 -0
- package/dist/site/assets/pdf/wasm/quickjs-eval.js +16 -0
- package/dist/site/assets/pdf/wasm/quickjs-eval.wasm +0 -0
- package/dist/site/assets/pdf.worker.min.mjs +29 -0
- package/dist/site/assets/workspace.worker.js +409 -0
- package/dist/site/index.html +253 -0
- package/dist/sync/baseline.js +45 -0
- package/dist/sync/changes.js +51 -0
- package/dist/sync/diff.js +80 -0
- package/dist/sync/fetch.js +287 -0
- package/dist/sync/ignore.js +4 -4
- package/dist/sync/local-state.js +12 -0
- package/dist/sync/mapping.js +31 -8
- package/dist/sync/metadata.js +57 -0
- package/dist/sync/prepare.js +52 -0
- package/dist/sync/pull.js +618 -141
- package/dist/sync/push.js +260 -213
- package/dist/sync/reconcile.js +51 -0
- package/dist/sync/references.js +2 -2
- package/dist/sync/remap.js +17 -5
- package/dist/sync/render.js +3 -2
- package/dist/sync/restore.js +28 -0
- package/dist/sync/scope.js +47 -0
- package/dist/sync/staging.js +193 -0
- package/dist/sync/workspace.js +100 -0
- package/dist/web/assets/PDFJS-LICENSE.txt +177 -0
- package/dist/web/assets/app.css +1 -1
- package/dist/web/assets/app.js +3 -938
- package/dist/web/assets/chunks/chunk-TEW74QPN.js +35 -0
- package/dist/web/assets/chunks/markdown-S6623E66.js +1 -0
- package/dist/web/assets/chunks/monaco-36ZGUH4Y.js +897 -0
- package/dist/web/assets/chunks/monaco-HFLEBJTI.css +1 -0
- package/dist/web/assets/chunks/pdf-DDN7TXTP.js +55 -0
- package/dist/web/assets/confluence-env-example.txt +32 -0
- package/dist/web/assets/editor.worker.js +1 -30
- package/dist/web/assets/favicon.svg +3 -0
- package/dist/web/assets/guide/01-account-settings.png +0 -0
- package/dist/web/assets/guide/02-atlassian-security.png +0 -0
- package/dist/web/assets/guide/03-api-tokens.png +0 -0
- package/dist/web/assets/guide/04-scope-api-token-step1.png +0 -0
- package/dist/web/assets/guide/05-scope-api-token-step2.png +0 -0
- package/dist/web/assets/guide/06-scope-api-token-step3.png +0 -0
- package/dist/web/assets/guide/07-scope-api-token-step4.png +0 -0
- package/dist/web/assets/guide/web/document-preview.png +0 -0
- package/dist/web/assets/guide/web/remote-diff.png +0 -0
- package/dist/web/assets/guide/web/sync-workspace.png +0 -0
- package/dist/web/assets/pdf/cmaps/78-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/78-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/78-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/78-RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/78-RKSJ-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/78-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Add-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Add-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/B5pc-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/B5pc-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/CNS-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/CNS-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/CNS1-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/CNS1-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/CNS2-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/CNS2-V.bcmap +3 -0
- package/dist/web/assets/pdf/cmaps/ETHK-B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/ETHK-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/ETen-B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/ETen-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/ETenms-B5-H.bcmap +3 -0
- package/dist/web/assets/pdf/cmaps/ETenms-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Ext-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Ext-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GB-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GB-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GB-H.bcmap +4 -0
- package/dist/web/assets/pdf/cmaps/GB-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBK-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBK-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBK2K-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBK2K-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBT-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBT-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBT-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBT-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKdla-B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKdla-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKm314-B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKm314-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKm471-B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKm471-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKscs-B5-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/HKscs-B5-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Hankaku.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Hiragana.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSC-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSC-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSC-Johab-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSC-Johab-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Katakana.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/LICENSE +36 -0
- package/dist/web/assets/pdf/cmaps/NWP-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/NWP-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/RKSJ-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/RKSJ-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/Roman.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/V.bcmap +0 -0
- package/dist/web/assets/pdf/cmaps/WP-Symbol.bcmap +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitDingbats.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitFixed.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitSerif.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/FoxitSymbol.pfb +0 -0
- package/dist/web/assets/pdf/standard_fonts/LICENSE_FOXIT +27 -0
- package/dist/web/assets/pdf/standard_fonts/LICENSE_LIBERATION +363 -0
- package/dist/web/assets/pdf/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/dist/web/assets/pdf/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/dist/web/assets/pdf/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/dist/web/assets/pdf/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/dist/web/assets/pdf/wasm/LICENSE_JBIG2 +196 -0
- package/dist/web/assets/pdf/wasm/LICENSE_OPENJPEG +39 -0
- package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_JBIG2 +13 -0
- package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
- package/dist/web/assets/pdf/wasm/LICENSE_PDFJS_QCMS +19 -0
- package/dist/web/assets/pdf/wasm/LICENSE_QCMS +21 -0
- package/dist/web/assets/pdf/wasm/jbig2.wasm +0 -0
- package/dist/web/assets/pdf/wasm/jbig2_nowasm_fallback.js +15 -0
- package/dist/web/assets/pdf/wasm/openjpeg.wasm +0 -0
- package/dist/web/assets/pdf/wasm/openjpeg_nowasm_fallback.js +17 -0
- package/dist/web/assets/pdf/wasm/qcms_bg.wasm +0 -0
- package/dist/web/assets/pdf/wasm/quickjs-eval.js +16 -0
- package/dist/web/assets/pdf/wasm/quickjs-eval.wasm +0 -0
- package/dist/web/assets/pdf.worker.min.mjs +29 -0
- package/dist/web/assets/workspace.worker.js +409 -0
- package/dist/web/document-source.js +14 -0
- package/dist/web/hosted-files.js +189 -0
- package/dist/web/hosted.js +316 -0
- package/dist/web/node-runner.js +26 -0
- package/dist/web/preview.js +74 -0
- package/dist/web/server.js +85 -240
- package/dist/web/sync.js +364 -70
- package/dist/web/table-editor-script.js +99 -0
- package/dist/web/table-format.js +224 -0
- package/dist/web/views/help.js +15 -10
- package/dist/web/views/sync-panel.js +90 -19
- package/dist/web/views/token-guide.js +27 -0
- package/dist/web/views/usage-guide.js +12 -0
- package/dist/web/views/workspace.js +85 -15
- package/dist/web/workspace-api.js +371 -0
- package/docs/user/CLI.md +366 -0
- package/docs/user/CONFIGURATION.md +125 -0
- package/docs/user/OBSIDIAN.md +117 -0
- package/docs/user/WEB.md +90 -0
- package/docs/user/images/web/document-preview.png +0 -0
- package/docs/user/images/web/remote-diff.png +0 -0
- package/docs/user/images/web/sync-workspace.png +0 -0
- 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
|
+
}
|