@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.
- package/README.md +44 -409
- 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 +157 -86
- 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 +83 -12
- 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 +262 -207
- 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 +365 -71
- 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
package/dist/sync/pull.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { progressStep } from '../shared/progress.js';
|
|
2
|
+
import { connectWorkspace } from './scope.js';
|
|
3
|
+
import { storeBaseline } from './baseline.js';
|
|
4
|
+
import { localStateHash } from './local-state.js';
|
|
5
|
+
import { buildTreeRenderer } from './render.js';
|
|
1
6
|
/**
|
|
2
|
-
* `confluence-sync pull <pageId|url> [--out <dir>]
|
|
7
|
+
* `confluence-sync pull <pageId|url> [--out <dir>] (하위 항목 포함)`
|
|
3
8
|
* Confluence 페이지를 읽어 Markdown(.md) 으로 생성한다(역방향).
|
|
4
9
|
* 자식이 있는 페이지는 push 관례를 역으로 적용해 <slug>/README.md 폴더로 펼친다.
|
|
5
10
|
* 첨부는 attachments/<문서명>/ 하위에 내려받고 이미지 링크를 그 경로로 재작성한다.
|
|
@@ -7,19 +12,29 @@
|
|
|
7
12
|
* 함께 받은 페이지끼리의 내부 링크는 마지막에 상대 .md 경로로 이어 붙인다(Obsidian 그래프·백링크가 살아난다).
|
|
8
13
|
* 각 문서 머리에 pageId 를 담은 frontmatter 를 남겨, 매핑 파일 없이도 push 가 원본 페이지를 다시 찾아간다.
|
|
9
14
|
*/
|
|
10
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
15
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, mkdtempSync, rmSync, readdirSync, copyFileSync, lstatSync, realpathSync, rmdirSync, constants } from 'node:fs';
|
|
11
16
|
import { resolve, join, dirname, basename, relative } from 'node:path';
|
|
17
|
+
import { fetchRemote } from './fetch.js';
|
|
18
|
+
import { verifyScope } from './scope.js';
|
|
19
|
+
import { positionalArgs } from '../cli/args.js';
|
|
20
|
+
import { tmpdir } from 'node:os';
|
|
21
|
+
import { createHash } from 'node:crypto';
|
|
22
|
+
import { loadWorkspace, findSyncBase, resolveSyncBase, inScope, inNestedWorkspace, within, assertWorkspaceConnection, requireScope, scopePath } from './workspace.js';
|
|
12
23
|
import { readEnv, requireEnv } from '../confluence/config.js';
|
|
13
|
-
import { createClient } from '../confluence/client.js';
|
|
24
|
+
import { createClient, ConfluenceDownloadError } from '../confluence/client.js';
|
|
14
25
|
import { htmlToMarkdown, codeLanguagesFromStorage } from '../documents/html-to-markdown.js';
|
|
15
|
-
import { buildFrontmatter, splitFrontmatter } from '../documents/obsidian.js';
|
|
26
|
+
import { buildFrontmatter, splitFrontmatter, wikilinkDisplayText } from '../documents/obsidian.js';
|
|
16
27
|
import { collectHeadings, matchConfluenceAnchor } from '../documents/anchors.js';
|
|
17
28
|
import { collectMarkdown } from '../documents/catalog.js';
|
|
18
|
-
import {
|
|
29
|
+
import { preparePushTree } from './prepare.js';
|
|
19
30
|
import { docHash } from '../documents/markdown.js';
|
|
31
|
+
import { rewriteTableLinks } from '../documents/tables.js';
|
|
20
32
|
import { buildIgnorer } from './ignore.js';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
33
|
+
import { mappingPath as defaultMappingPath, metadataFiles, METADATA_DIR } from './metadata.js';
|
|
34
|
+
import { loadMapping, saveMapping } from './mapping.js';
|
|
35
|
+
import { cyan, dim, green } from '../shared/colors.js';
|
|
36
|
+
class PullError extends Error {
|
|
37
|
+
}
|
|
23
38
|
// 첨부 이미지는 항상 <md위치>/attachments/<문서명>/ 하위에 저장한다.
|
|
24
39
|
const ASSETS_DIR = 'attachments';
|
|
25
40
|
/** 마크다운 링크 중 Confluence 페이지를 가리키는 것: [text](https://.../pages/<id>/...) */
|
|
@@ -28,6 +43,15 @@ const PAGE_LINK = /\[([^\]]*)\]\(<?(https?:\/\/[^\s)<>]*?\/pages\/(\d+)[^\s)<>]*
|
|
|
28
43
|
const SELF_ANCHOR = /\[([^\]]*)\]\(#([^)\s]+)\)/g;
|
|
29
44
|
/** 다른 페이지의 섹션: [text](https://.../pages/<id>/제목#앵커) — 앵커만 따로 잡는다 */
|
|
30
45
|
const PAGE_ANCHOR = /(\[[^\]]*\]\(<?https?:\/\/[^\s)<>]*?\/pages\/(\d+)[^\s)<>#]*)#([^\s)<>]+?)(>?\))/g;
|
|
46
|
+
function claim(ctx, path, id, directory = false) {
|
|
47
|
+
const rel = relative(ctx.stageBase, path).split('\\').join('/');
|
|
48
|
+
if (!within(ctx.stageBase, path) || !inScope(rel || '.', ctx.workspace) || inNestedWorkspace(ctx.baseDir, directory ? `${rel}/.boundary` : rel))
|
|
49
|
+
throw new Error(`관리 범위 밖 pull 대상: ${rel}`);
|
|
50
|
+
const prior = ctx.claimed.get(path);
|
|
51
|
+
if (prior && prior !== id)
|
|
52
|
+
throw new Error(`같은 로컬 경로에 여러 원격 항목이 대응합니다: ${rel}`);
|
|
53
|
+
ctx.claimed.set(path, id);
|
|
54
|
+
}
|
|
31
55
|
function optVal(argv, name) {
|
|
32
56
|
const i = argv.indexOf(name);
|
|
33
57
|
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : undefined;
|
|
@@ -42,48 +66,87 @@ function parseContentId(input) {
|
|
|
42
66
|
}
|
|
43
67
|
/** 제목 → 파일/폴더명(파일시스템 금지문자 치환, 한글 유지) */
|
|
44
68
|
function slug(title) {
|
|
45
|
-
|
|
69
|
+
const value = title.replace(/[\/\\:*?"<>|]/g, '-').replace(/\s+/g, ' ').trim();
|
|
70
|
+
return !value || /^\.+$/.test(value) ? 'untitled' : value;
|
|
46
71
|
}
|
|
47
72
|
/** 공백이 있으면 <...> 로 감싼 마크다운 링크 목적지 */
|
|
48
73
|
const dest = (p) => (/\s/.test(p) ? `<${p}>` : p);
|
|
49
74
|
/** 페이지 1건을 .md 로 생성(자식 페이지/폴더가 있으면 <slug>/README.md 폴더로). */
|
|
50
75
|
async function pullPage(ctx, node, destDir) {
|
|
51
|
-
const { client
|
|
76
|
+
const { client } = ctx;
|
|
52
77
|
const { id, title, html, storage } = node;
|
|
53
78
|
// 페이지도 하위 페이지 + 하위 폴더를 모두 가질 수 있다(폴더가 페이지 밑에 올 수 있음).
|
|
54
|
-
const childFolders =
|
|
55
|
-
const childPages =
|
|
79
|
+
const childFolders = ctx.incremental ? [] : await client.getChildFolders(id);
|
|
80
|
+
const childPages = ctx.incremental ? [] : await client.getChildPages(id);
|
|
56
81
|
const hasChildren = childFolders.length + childPages.length > 0;
|
|
57
|
-
const
|
|
58
|
-
const
|
|
82
|
+
const rooted = id === ctx.rootId;
|
|
83
|
+
const known = ctx.knownPaths.get(id);
|
|
84
|
+
const filePath = known ?? (rooted ? join(destDir, 'README.md') : hasChildren ? join(destDir, slug(title), 'README.md') : join(destDir, `${slug(title)}.md`));
|
|
85
|
+
const folder = dirname(filePath);
|
|
86
|
+
claim(ctx, filePath, id);
|
|
87
|
+
if (node.version !== undefined)
|
|
88
|
+
ctx.versions.set(id, node.version);
|
|
59
89
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
60
90
|
// 첨부는 항상 <md위치>/attachments/<문서명>/이미지 에 저장
|
|
61
91
|
const mdBase = basename(filePath).replace(/\.md$/, '');
|
|
62
92
|
const assetPrefix = `${ASSETS_DIR}/${mdBase}`;
|
|
63
93
|
const referenced = new Set();
|
|
64
|
-
|
|
94
|
+
let body = htmlToMarkdown(html, {
|
|
95
|
+
obsidian: ctx.obsidian, pageId: id,
|
|
65
96
|
onImage: (f) => referenced.add(f),
|
|
66
97
|
assetPrefix,
|
|
67
98
|
codeLangs: codeLanguagesFromStorage(storage), // 코드블록 언어는 storage 가 진짜
|
|
99
|
+
storage,
|
|
68
100
|
});
|
|
69
101
|
if (referenced.size) {
|
|
70
102
|
const attachments = await client.listAttachments(id);
|
|
71
103
|
const byName = new Map(attachments.map((a) => [a.filename, a.downloadPath]));
|
|
72
|
-
const saveDir = join(dirname(filePath), assetPrefix);
|
|
104
|
+
const saveDir = join(dirname(filePath), assetPrefix), unavailable = new Set();
|
|
105
|
+
const missing = (name, reason) => {
|
|
106
|
+
unavailable.add(name);
|
|
107
|
+
ctx.missingAttachments++;
|
|
108
|
+
console.log(`[경고] 원격 첨부 누락 #${id}: ${name} (${reason}). 원격 참조를 유지하고 계속합니다.`);
|
|
109
|
+
};
|
|
73
110
|
for (const name of referenced) {
|
|
111
|
+
if (basename(name) !== name || name === '.' || name === '..' || name.includes('\\'))
|
|
112
|
+
throw new Error(`잘못된 첨부 파일명: ${name}`);
|
|
74
113
|
const dp = byName.get(name);
|
|
75
114
|
if (!dp) {
|
|
76
|
-
|
|
115
|
+
missing(name, '이 페이지 첨부 목록에 없음');
|
|
77
116
|
continue;
|
|
78
117
|
}
|
|
118
|
+
let bytes;
|
|
79
119
|
try {
|
|
80
|
-
|
|
81
|
-
writeFileSync(join(saveDir, name), await client.downloadAttachment(dp));
|
|
120
|
+
bytes = await client.downloadAttachment(dp);
|
|
82
121
|
}
|
|
83
|
-
catch (
|
|
84
|
-
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof ConfluenceDownloadError && error.status === 404) {
|
|
124
|
+
missing(name, '다운로드 404');
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
85
128
|
}
|
|
129
|
+
mkdirSync(saveDir, { recursive: true });
|
|
130
|
+
const assetPath = join(saveDir, name);
|
|
131
|
+
ctx.assetOwners.set(assetPath, id);
|
|
132
|
+
writeFileSync(assetPath, bytes);
|
|
86
133
|
}
|
|
134
|
+
if (unavailable.size)
|
|
135
|
+
body = htmlToMarkdown(html, {
|
|
136
|
+
obsidian: ctx.obsidian, pageId: id, assetPrefix, codeLangs: codeLanguagesFromStorage(storage), storage,
|
|
137
|
+
attachmentDestination: (name, source) => {
|
|
138
|
+
if (!unavailable.has(name))
|
|
139
|
+
return `${assetPrefix}/${name}`;
|
|
140
|
+
if (/^https?:\/\//i.test(source))
|
|
141
|
+
return source;
|
|
142
|
+
const base = ctx.remoteBaseUrl.replace(/\/$/, '');
|
|
143
|
+
if (source.startsWith('/wiki/'))
|
|
144
|
+
return base.replace(/\/wiki$/, '') + source;
|
|
145
|
+
if (source.startsWith('/'))
|
|
146
|
+
return base + source;
|
|
147
|
+
return `${base}/download/attachments/${id}/${encodeURIComponent(name)}`;
|
|
148
|
+
},
|
|
149
|
+
});
|
|
87
150
|
}
|
|
88
151
|
// frontmatter 의 pageId 는 push 왕복의 앵커다(매핑 파일이 없어도 원본 페이지를 찾아간다).
|
|
89
152
|
// 취향 옵션이 아니라 중복 페이지 생성을 막는 장치이므로 항상 쓴다. Obsidian 은 이를 속성으로 보여준다.
|
|
@@ -100,7 +163,7 @@ async function pullPage(ctx, node, destDir) {
|
|
|
100
163
|
ctx.written.push(filePath);
|
|
101
164
|
ctx.pathById.set(id, filePath);
|
|
102
165
|
const imgNote = referenced.size ? `, 이미지 ${referenced.size}` : '';
|
|
103
|
-
console.log(` ${
|
|
166
|
+
console.log(` ${cyan('↓ 다운로드 준비')} ${join(ctx.baseDir, relative(ctx.stageBase, filePath))} ${dim(`(#${id}${imgNote}, 검증 후 저장)`)}`);
|
|
104
167
|
const counts = { pages: 1, folders: 0 };
|
|
105
168
|
for (const f of childFolders)
|
|
106
169
|
add(counts, await pullNode(ctx, f.id, folder));
|
|
@@ -109,34 +172,46 @@ async function pullPage(ctx, node, destDir) {
|
|
|
109
172
|
return counts;
|
|
110
173
|
}
|
|
111
174
|
function add(acc, c) { acc.pages += c.pages; acc.folders += c.folders; }
|
|
112
|
-
/** 노드(페이지/폴더)를 재귀적으로 가져온다. 폴더는 디렉토리(본문 없음)로 만든다. 노드
|
|
175
|
+
/** 노드(페이지/폴더)를 재귀적으로 가져온다. 폴더는 디렉토리(본문 없음)로 만든다. 노드 하나라도 실패하면 임시 다운로드 전체를 폐기. */
|
|
113
176
|
async function pullNode(ctx, id, destDir) {
|
|
114
|
-
const { client
|
|
177
|
+
const { client } = ctx;
|
|
178
|
+
if (ctx.visited.has(id))
|
|
179
|
+
return { pages: 0, folders: 0 };
|
|
180
|
+
const known = ctx.knownPaths.get(id);
|
|
181
|
+
if (known && !inScope(relative(ctx.stageBase, known).split('\\').join('/'), ctx.workspace))
|
|
182
|
+
return { pages: 0, folders: 0 };
|
|
183
|
+
ctx.visited.add(id);
|
|
184
|
+
progressStep(`원격 문서·폴더 #${id} 가져오기 · ${ctx.visited.size}번째 항목`);
|
|
115
185
|
try {
|
|
186
|
+
const location = await client.assertInScope(id);
|
|
187
|
+
if (location?.parentId)
|
|
188
|
+
ctx.parents.set(id, location.parentId);
|
|
116
189
|
const node = await client.getNode(id);
|
|
190
|
+
if (ctx.incremental && (String(node.id) !== id || node.type !== 'page'))
|
|
191
|
+
throw new Error(`매핑된 원격 페이지가 아닙니다: #${id}`);
|
|
192
|
+
const expected = ctx.expectedVersions.get(id);
|
|
193
|
+
if (ctx.incremental && (!Number.isInteger(node.version) || !node.version || (expected !== undefined && node.version < expected)))
|
|
194
|
+
throw new Error(`원격 버전을 확인할 수 없거나 Fetch보다 이전 버전입니다: #${id}. 다시 Fetch한 후 Pull하세요.`);
|
|
117
195
|
if (node.type === 'folder') {
|
|
118
|
-
const dir = join(destDir, slug(node.title));
|
|
196
|
+
const dir = ctx.knownPaths.get(id) ?? (id === ctx.rootId ? destDir : join(destDir, slug(node.title)));
|
|
197
|
+
claim(ctx, dir, id, true);
|
|
119
198
|
mkdirSync(dir, { recursive: true });
|
|
120
199
|
ctx.folderIds.set(dir, id);
|
|
121
|
-
console.log(` ${cyan('📁 폴더')} ${dir} ${dim(`(#${id})`)}`);
|
|
200
|
+
console.log(` ${cyan('📁 폴더 준비')} ${join(ctx.baseDir, relative(ctx.stageBase, dir))} ${dim(`(#${id}, 검증 후 저장)`)}`);
|
|
122
201
|
const counts = { pages: 0, folders: 1 };
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
add(counts, await pullNode(ctx, p.id, dir));
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
console.log(dim(' (하위 내용은 --children 으로 가져옵니다)'));
|
|
131
|
-
}
|
|
202
|
+
for (const f of await client.getChildFolders(id))
|
|
203
|
+
add(counts, await pullNode(ctx, f.id, dir));
|
|
204
|
+
for (const p of await client.getChildPages(id))
|
|
205
|
+
add(counts, await pullNode(ctx, p.id, dir));
|
|
132
206
|
return counts;
|
|
133
207
|
}
|
|
134
208
|
// page: getNode 결과(html + storage) 그대로 사용
|
|
135
209
|
return await pullPage(ctx, node, destDir);
|
|
136
210
|
}
|
|
137
211
|
catch (e) {
|
|
138
|
-
|
|
139
|
-
|
|
212
|
+
if (e instanceof PullError)
|
|
213
|
+
throw e;
|
|
214
|
+
throw new PullError(`pull 실패 #${id}: ${e.message}`);
|
|
140
215
|
}
|
|
141
216
|
}
|
|
142
217
|
function readDocInfo(file) {
|
|
@@ -155,8 +230,8 @@ function readDocInfo(file) {
|
|
|
155
230
|
* 아무 데도 가리키지 못한다. 실제 헤딩을 찾아 그 문서의 슬러그로 바꿔 줘야 링크가 산다.
|
|
156
231
|
* relinkPass 보다 **먼저** 돈다 — 아직 링크가 절대 URL 이라 어느 페이지인지 id 로 알 수 있기 때문이다.
|
|
157
232
|
*/
|
|
158
|
-
export function anchorPass(written, pathById) {
|
|
159
|
-
const info = new Map();
|
|
233
|
+
export function anchorPass(written, pathById, references = new Map()) {
|
|
234
|
+
const info = new Map(references);
|
|
160
235
|
for (const f of written)
|
|
161
236
|
info.set(f, readDocInfo(f));
|
|
162
237
|
let count = 0;
|
|
@@ -188,6 +263,17 @@ export function anchorPass(written, pathById) {
|
|
|
188
263
|
count++;
|
|
189
264
|
return `${head}#${slug}${tail}`;
|
|
190
265
|
});
|
|
266
|
+
after = rewriteTableLinks(after, href => {
|
|
267
|
+
const match = href.match(/^(https?:\/\/[^\s]*?\/pages\/(\d+)[^#]*)#(.+)$/);
|
|
268
|
+
const target = match ? pathById.get(match[2]) : undefined;
|
|
269
|
+
const targetInfo = href.startsWith('#') ? self : target ? info.get(target) : undefined;
|
|
270
|
+
const fragment = href.startsWith('#') ? href.slice(1) : match?.[3];
|
|
271
|
+
const slug = targetInfo && fragment ? toSlug(fragment, targetInfo) : null;
|
|
272
|
+
if (!slug)
|
|
273
|
+
return href;
|
|
274
|
+
count++;
|
|
275
|
+
return href.startsWith('#') ? `#${slug}` : `${match[1]}#${slug}`;
|
|
276
|
+
});
|
|
191
277
|
if (after !== before)
|
|
192
278
|
writeFileSync(file, after);
|
|
193
279
|
}
|
|
@@ -218,13 +304,23 @@ export function relinkPass(written, pathById, wikilinks, info) {
|
|
|
218
304
|
if (wikilinks && nameCount.get(name) === 1) {
|
|
219
305
|
// Obsidian 은 슬러그가 아니라 헤딩 텍스트로 섹션을 가리킨다: [[문서#헤딩 텍스트]]
|
|
220
306
|
const heading = slug ? info?.get(target)?.textBySlug.get(slug) : undefined;
|
|
307
|
+
const display = wikilinkDisplayText(label);
|
|
221
308
|
if (!heading)
|
|
222
|
-
return
|
|
223
|
-
return `[[${name}#${heading}|${
|
|
309
|
+
return display === name ? `[[${name}]]` : `[[${name}|${display}]]`;
|
|
310
|
+
return `[[${name}#${heading}|${display}]]`;
|
|
224
311
|
}
|
|
225
312
|
const rel = relative(dirname(file), target).split('\\').join('/');
|
|
226
313
|
return `[${label}](${dest(rel + (slug ? `#${slug}` : ''))})`;
|
|
227
314
|
});
|
|
315
|
+
// HTML cells retain HTML links even with --obsidian (wikilinks do not render there).
|
|
316
|
+
after = rewriteTableLinks(after, href => {
|
|
317
|
+
const match = href.match(/^https?:\/\/[^\s]*?\/pages\/(\d+)[^#]*(#.*)?$/);
|
|
318
|
+
const target = match ? pathById.get(match[1]) : undefined;
|
|
319
|
+
if (!target || target === file)
|
|
320
|
+
return href;
|
|
321
|
+
count++;
|
|
322
|
+
return relative(dirname(file), target).split('\\').join('/') + (match[2] ?? '');
|
|
323
|
+
});
|
|
228
324
|
// 같은 문서 섹션도 vault 표기로: [§4](#슬러그) → [[#헤딩 텍스트|§4]]
|
|
229
325
|
if (wikilinks) {
|
|
230
326
|
after = after.replace(SELF_ANCHOR, (whole, text, frag) => {
|
|
@@ -232,7 +328,7 @@ export function relinkPass(written, pathById, wikilinks, info) {
|
|
|
232
328
|
if (!heading)
|
|
233
329
|
return whole;
|
|
234
330
|
count++;
|
|
235
|
-
const label = text.trim();
|
|
331
|
+
const label = wikilinkDisplayText(text.trim());
|
|
236
332
|
return label && label !== heading ? `[[#${heading}|${label}]]` : `[[#${heading}]]`;
|
|
237
333
|
});
|
|
238
334
|
}
|
|
@@ -241,19 +337,6 @@ export function relinkPass(written, pathById, wikilinks, info) {
|
|
|
241
337
|
}
|
|
242
338
|
return count;
|
|
243
339
|
}
|
|
244
|
-
/**
|
|
245
|
-
* 매핑 파일(.confluence-sync.json)이 있는 가장 가까운 상위 디렉토리 = 동기화 루트.
|
|
246
|
-
* pull 을 base 하위 폴더로 받아도(`--out <base>/어딘가`) 매핑 키가 base 기준으로 남아야
|
|
247
|
-
* 이어지는 push 가 같은 문서를 알아본다.
|
|
248
|
-
*/
|
|
249
|
-
function findSyncBase(from) {
|
|
250
|
-
for (let d = from;; d = dirname(d)) {
|
|
251
|
-
if (existsSync(join(d, MAPPING_FILE)))
|
|
252
|
-
return d;
|
|
253
|
-
if (dirname(d) === d)
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
340
|
/**
|
|
258
341
|
* 받아온 문서를 매핑 파일에 기록한다.
|
|
259
342
|
*
|
|
@@ -263,109 +346,503 @@ function findSyncBase(from) {
|
|
|
263
346
|
*
|
|
264
347
|
* 반환값은 기록한 항목 수.
|
|
265
348
|
*/
|
|
266
|
-
export function mappingPass(baseDir, mappingPath, pathById, folderIds) {
|
|
349
|
+
export async function mappingPass(baseDir, mappingPath, pathById, folderIds, versions = new Map(), parents = new Map(), validate) {
|
|
267
350
|
const posix = (p) => p.split('\\').join('/');
|
|
268
351
|
const ignorer = buildIgnorer(baseDir, []);
|
|
269
352
|
const rels = collectMarkdown(baseDir)
|
|
270
353
|
.map((f) => posix(relative(baseDir, f)))
|
|
271
354
|
.filter((r) => !ignorer.ignores(r));
|
|
272
|
-
|
|
355
|
+
progressStep('가져온 문서의 동기화 기준 계산');
|
|
356
|
+
const before = existsSync(mappingPath) ? readFileSync(mappingPath, 'utf8') : undefined;
|
|
273
357
|
const m = loadMapping(mappingPath);
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
358
|
+
const createdObjects = [];
|
|
359
|
+
const prepared = await preparePushTree(baseDir, rels, [...pathById.values()].map(path => posix(relative(baseDir, path))).filter(rel => rels.includes(rel)));
|
|
360
|
+
try {
|
|
361
|
+
const { docs, render } = prepared;
|
|
362
|
+
const pending = [];
|
|
363
|
+
// 같은 페이지가 다른 키에 남아 있으면(문서가 옮겨졌으면) 중복 항목이 된다 → 옛 키를 걷어낸다
|
|
364
|
+
const ids = new Set([...pathById.keys(), ...folderIds.values()]);
|
|
365
|
+
for (const [k, v] of Object.entries(m))
|
|
366
|
+
if (ids.has(v.pageId))
|
|
367
|
+
delete m[k];
|
|
368
|
+
let n = 0;
|
|
369
|
+
for (const [id, abs] of pathById) {
|
|
370
|
+
const rel = posix(relative(baseDir, abs));
|
|
371
|
+
const doc = rel.startsWith('..') ? undefined : docs[rel];
|
|
372
|
+
if (!doc)
|
|
373
|
+
continue; // base 밖으로 받았거나 제외 규칙에 걸린 문서는 기록하지 않는다
|
|
374
|
+
m[rel] = { pageId: id, title: doc.title, hash: docHash(doc.title, render(rel, doc.body, doc.title)), localHash: localStateHash(baseDir, rel, render(rel, doc.body, doc.title)), ...(versions.has(id) ? { remoteVersion: versions.get(id) } : {}), ...(parents.has(id) ? { remoteParentId: parents.get(id) } : {}) };
|
|
375
|
+
pending.push({ rel, text: readFileSync(abs, 'utf8') });
|
|
376
|
+
n++;
|
|
377
|
+
}
|
|
378
|
+
for (const [dir, id] of folderIds) {
|
|
379
|
+
const rel = posix(relative(baseDir, dir));
|
|
380
|
+
if (!rel || rel.startsWith('..'))
|
|
381
|
+
continue;
|
|
382
|
+
m[`${rel}/`] = { pageId: id, title: basename(dir), type: 'folder' };
|
|
383
|
+
n++;
|
|
384
|
+
}
|
|
385
|
+
validate?.();
|
|
386
|
+
for (const { rel, text } of pending) {
|
|
387
|
+
const id = createHash('sha256').update(text).digest('hex');
|
|
388
|
+
const object = join(baseDir, METADATA_DIR, 'objects', id + '.md');
|
|
389
|
+
if (!existsSync(object))
|
|
390
|
+
createdObjects.push(object);
|
|
391
|
+
m[rel].sourceObject = storeBaseline(baseDir, text);
|
|
392
|
+
}
|
|
393
|
+
validate?.();
|
|
394
|
+
if ((existsSync(mappingPath) ? readFileSync(mappingPath, 'utf8') : undefined) !== before)
|
|
395
|
+
throw new Error('기준 계산 중 매핑이 변경되었습니다. 다시 가져오세요.');
|
|
396
|
+
saveMapping(mappingPath, m);
|
|
397
|
+
return n;
|
|
287
398
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
399
|
+
catch (error) {
|
|
400
|
+
let retained = new Set();
|
|
401
|
+
try {
|
|
402
|
+
retained = new Set(Object.values(loadMapping(mappingPath)).map(entry => entry.sourceObject ?? ''));
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
for (const object of createdObjects)
|
|
408
|
+
if (!retained.has(basename(object, '.md')) && existsSync(object))
|
|
409
|
+
rmSync(object, { force: true });
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
prepared.dispose();
|
|
294
414
|
}
|
|
295
|
-
saveMapping(mappingPath, m);
|
|
296
|
-
return n;
|
|
297
415
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
416
|
+
/** Explicit remote recovery only accepts mapped Markdown files; replacement needs opt-in. */
|
|
417
|
+
export function validateRestorePaths(base, paths, overwrite = false) {
|
|
418
|
+
if (!paths.length)
|
|
419
|
+
throw new Error('복구할 파일을 직접 지정하세요: csync restore README.md');
|
|
420
|
+
requireScope(loadWorkspace(base));
|
|
421
|
+
const mapping = loadMapping(defaultMappingPath(base)), ignore = buildIgnorer(base, []);
|
|
422
|
+
const selected = [...new Set(paths.map(path => scopePath(base, path)))];
|
|
423
|
+
for (const rel of selected) {
|
|
424
|
+
const entry = mapping[rel];
|
|
425
|
+
if (!/\.md$/i.test(rel) || !entry || entry.type === 'folder')
|
|
426
|
+
throw new Error(`매핑된 Markdown 파일만 복구할 수 있습니다: ${rel}`);
|
|
427
|
+
if (ignore.ignores(rel) || inNestedWorkspace(base, rel))
|
|
428
|
+
throw new Error(`제외되었거나 독립 작업공간에 속한 파일입니다: ${rel}`);
|
|
429
|
+
if (entry.pendingMove)
|
|
430
|
+
throw new Error(`이동 대기 중인 파일은 복구할 수 없습니다: ${rel}`);
|
|
431
|
+
try {
|
|
432
|
+
const info = lstatSync(resolve(base, rel));
|
|
433
|
+
if (overwrite && info.isFile() && !info.isSymbolicLink()) {
|
|
434
|
+
const id = splitFrontmatter(readFileSync(resolve(base, rel), 'utf8')).data.pageId;
|
|
435
|
+
if (id && id !== entry.pageId)
|
|
436
|
+
throw new Error(`매핑과 로컬 pageId가 다릅니다: ${rel}`);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
if (error.code === 'ENOENT')
|
|
442
|
+
continue;
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
throw new Error(`이미 존재하는 로컬 파일은 복구로 덮어쓰지 않습니다: ${rel}. 일반 파일의 수정을 버리려면 --overwrite를 지정하세요.`);
|
|
310
446
|
}
|
|
311
|
-
const
|
|
447
|
+
const requestedIds = new Set(selected.map(rel => mapping[rel].pageId));
|
|
448
|
+
for (const file of collectMarkdown(base)) {
|
|
449
|
+
const rel = relative(base, file).split('\\').join('/');
|
|
450
|
+
if (inNestedWorkspace(base, rel))
|
|
451
|
+
continue;
|
|
452
|
+
const id = splitFrontmatter(readFileSync(file, 'utf8')).data.pageId;
|
|
453
|
+
if (id && requestedIds.has(id) && !(overwrite && selected.includes(rel) && mapping[rel].pageId === id))
|
|
454
|
+
throw new Error(`같은 pageId의 로컬 문서가 이미 있습니다: ${rel}. 파일 이동 여부를 확인하세요.`);
|
|
455
|
+
}
|
|
456
|
+
return selected;
|
|
457
|
+
}
|
|
458
|
+
export async function runPull(argv, options = {}) {
|
|
459
|
+
const restoring = options.restorePaths !== undefined;
|
|
460
|
+
const restoreOverwrite = restoring && options.restoreOverwrite === true;
|
|
461
|
+
if (argv.includes('--scope'))
|
|
462
|
+
throw new Error('--scope는 제거되었습니다. Pull은 스페이스 작업공간을 기준으로 실행합니다.');
|
|
463
|
+
if (argv.includes('--no-mapping'))
|
|
464
|
+
throw new Error('Pull은 매핑을 유지해야 합니다.');
|
|
465
|
+
if (argv.includes('--root'))
|
|
466
|
+
throw new Error('--root는 제거되었습니다. 기준 폴더는 스페이스 루트에 연결됩니다.');
|
|
467
|
+
const explicitRemoteOnly = argv.includes('--remote-only');
|
|
468
|
+
const legacyTraversal = !restoring && (argv.includes('--full') || argv.includes('--space') || positionalArgs(argv).length > 1);
|
|
469
|
+
const remoteOnly = !restoring && !legacyTraversal;
|
|
470
|
+
if (explicitRemoteOnly && (argv.includes('--full') || argv.includes('--space') || positionalArgs(argv).length > 1))
|
|
471
|
+
throw new Error('--remote-only는 전체 탐색·개별 페이지 지정과 함께 사용할 수 없습니다.');
|
|
472
|
+
const explicitBase = optVal(argv, '--base');
|
|
473
|
+
const outDir = resolve(optVal(argv, '--out') ?? resolveSyncBase(explicitBase) ?? process.cwd());
|
|
474
|
+
const baseDir = resolve(explicitBase ?? findSyncBase(outDir) ?? outDir);
|
|
475
|
+
if (!within(baseDir, outDir))
|
|
476
|
+
throw new Error(`--out 이 base 밖입니다: ${outDir}`);
|
|
477
|
+
if (baseDir !== outDir)
|
|
478
|
+
throw new Error('Pull은 기준 폴더 안의 원래 경로에 저장합니다. --base와 --out은 같아야 합니다.');
|
|
479
|
+
const mappingPath = resolve(optVal(argv, '--mapping') ?? defaultMappingPath(baseDir));
|
|
480
|
+
const restorePaths = restoring ? validateRestorePaths(baseDir, options.restorePaths, restoreOverwrite) : undefined;
|
|
481
|
+
let workspace = loadWorkspace(baseDir);
|
|
482
|
+
const env = readEnv(baseDir);
|
|
312
483
|
requireEnv(env);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
484
|
+
if (workspace.version !== 2) {
|
|
485
|
+
mkdirSync(baseDir, { recursive: true });
|
|
486
|
+
workspace = await connectWorkspace(baseDir, env);
|
|
487
|
+
}
|
|
488
|
+
const scope = requireScope(workspace);
|
|
489
|
+
const oldMapping = loadMapping(mappingPath);
|
|
490
|
+
const stamp = (file) => existsSync(file) ? createHash('sha256').update(readFileSync(file)).digest('hex') : undefined;
|
|
491
|
+
const metadataBefore = new Map([mappingPath, join(baseDir, '.env'), ...metadataFiles(baseDir)].map(path => [path, stamp(path)]));
|
|
492
|
+
const assertMetadataUnchanged = () => { for (const [path, hash] of metadataBefore)
|
|
493
|
+
if (stamp(path) !== hash)
|
|
494
|
+
throw new Error('pull 도중 설정·매핑이 변경되었습니다.'); };
|
|
495
|
+
assertWorkspaceConnection(workspace, env);
|
|
496
|
+
const client = createClient({ baseUrl: env.baseUrl, email: env.email, token: env.token }, { force: false, verify: false, scope: { id: scope.id, spaceId: workspace.connection.spaceId, spaceOnly: !legacyTraversal } });
|
|
497
|
+
const target = positionalArgs(argv)[1];
|
|
498
|
+
const knownById = new Map(Object.entries(oldMapping).map(([rel, entry]) => [entry.pageId, rel]));
|
|
499
|
+
let ids;
|
|
500
|
+
if (target) {
|
|
501
|
+
const id = parseContentId(target);
|
|
502
|
+
if (!id || !((id === scope.id) || (knownById.has(id) && inScope(knownById.get(id).replace(/\/$/, ''), workspace))))
|
|
503
|
+
throw new Error('가져올 페이지가 작업공간에 매핑되지 않았습니다.');
|
|
504
|
+
ids = [id];
|
|
505
|
+
}
|
|
506
|
+
else
|
|
507
|
+
ids = [scope.id];
|
|
508
|
+
// A complete Fetch observation selects existing pages; it never authorizes a baseline advance.
|
|
509
|
+
let incremental = !legacyTraversal, skipped = 0;
|
|
510
|
+
let discovered;
|
|
511
|
+
const newPaths = new Set();
|
|
512
|
+
const recoveringPaths = new Set();
|
|
513
|
+
const expectedVersions = new Map();
|
|
514
|
+
if (restorePaths) {
|
|
515
|
+
ids = restorePaths.map(rel => oldMapping[rel].pageId);
|
|
516
|
+
for (const rel of restorePaths)
|
|
517
|
+
if (oldMapping[rel].remoteVersion)
|
|
518
|
+
expectedVersions.set(oldMapping[rel].pageId, oldMapping[rel].remoteVersion);
|
|
519
|
+
console.log(`원격에서 ${restoreOverwrite ? '선택 문서 원복(로컬 수정 덮어쓰기)' : '누락 문서 복구'} · ${restorePaths.length}개 · 하위 항목 탐색 없음`);
|
|
520
|
+
}
|
|
521
|
+
else if (legacyTraversal) {
|
|
522
|
+
console.log('기존 전체 탐색 옵션 호환 실행 · 일반 가져오기는 csync pull을 사용하세요.');
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
const { snapshot, cached, requests } = await fetchRemote(baseDir, env, { mappingFile: mappingPath });
|
|
526
|
+
discovered = snapshot;
|
|
527
|
+
const localIds = new Map();
|
|
528
|
+
for (const file of collectMarkdown(baseDir)) {
|
|
529
|
+
const rel = relative(baseDir, file).split('\\').join('/');
|
|
530
|
+
if (inNestedWorkspace(baseDir, rel))
|
|
531
|
+
continue;
|
|
532
|
+
const id = splitFrontmatter(readFileSync(file, 'utf8')).data.pageId;
|
|
533
|
+
if (id)
|
|
534
|
+
localIds.set(id, [...(localIds.get(id) ?? []), rel]);
|
|
342
535
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
536
|
+
ids = snapshot.items.filter(item => {
|
|
537
|
+
if (target && !ids.includes(item.pageId))
|
|
538
|
+
return false;
|
|
539
|
+
if (item.state !== 'changed' && item.state !== 'new') {
|
|
540
|
+
skipped++;
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
if (item.remoteVersion !== undefined)
|
|
544
|
+
expectedVersions.set(item.pageId, item.remoteVersion);
|
|
545
|
+
if (item.state === 'new') {
|
|
546
|
+
if (localIds.get(item.pageId)?.some(path => path !== item.path)) {
|
|
547
|
+
console.log(`같은 pageId의 다른 로컬 문서가 있어 보존: ${item.path}`);
|
|
548
|
+
skipped++;
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
const info = lstatSync(resolve(baseDir, item.path));
|
|
553
|
+
const fm = info.isFile() && !info.isSymbolicLink() ? splitFrontmatter(readFileSync(resolve(baseDir, item.path), 'utf8')).data : {};
|
|
554
|
+
if (fm.pageId === item.pageId && fm.spaceKey === env.spaceKey) {
|
|
555
|
+
recoveringPaths.add(item.path);
|
|
556
|
+
console.log(`받기 미완료 문서 재검증: ${item.path}`);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
console.log(`기존 로컬 경로가 있어 보존: ${item.path}`);
|
|
560
|
+
skipped++;
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
if (error.code !== 'ENOENT')
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
if (inNestedWorkspace(baseDir, item.path) || buildIgnorer(baseDir, []).ignores(item.path))
|
|
569
|
+
return false;
|
|
570
|
+
knownById.set(item.pageId, item.path);
|
|
571
|
+
if (!recoveringPaths.has(item.path))
|
|
572
|
+
newPaths.add(item.path);
|
|
573
|
+
}
|
|
574
|
+
return true;
|
|
575
|
+
}).map(item => item.pageId);
|
|
576
|
+
console.log(`Fetch 기반 Pull · 조회 시각 ${snapshot.checkedAt}${cached ? ' (캐시)' : ''} · 메타데이터 요청 ${requests}회`);
|
|
577
|
+
console.log(`가져올 원격 신규·변경 ${ids.length}건 · 건너뜀 ${skipped}건`);
|
|
578
|
+
const unavailable = snapshot.items.filter(item => item.state === 'unavailable');
|
|
579
|
+
if (unavailable.length)
|
|
580
|
+
console.log(`[경고] 원격 조회 불가 ${unavailable.length}건은 문서와 매핑을 유지합니다.`);
|
|
581
|
+
}
|
|
582
|
+
assertMetadataUnchanged();
|
|
583
|
+
let rootId = ids.includes(scope.id) ? scope.id : undefined;
|
|
584
|
+
if (rootId && knownById.has(rootId) && knownById.get(rootId) !== 'README.md')
|
|
585
|
+
throw new Error('스페이스 루트 매핑은 README.md여야 합니다. 기존 매핑을 확인하세요.');
|
|
586
|
+
if (incremental && !ids.length) {
|
|
587
|
+
console.log('완료 · Fetch 기준 내려받을 원격 변경이 없습니다. 문서·매핑을 유지합니다.');
|
|
346
588
|
return;
|
|
347
589
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
590
|
+
await verifyScope(client, workspace);
|
|
591
|
+
assertMetadataUnchanged();
|
|
592
|
+
// Stage everything first. Any download/conversion/scope failure leaves local sources untouched.
|
|
593
|
+
const originals = new Map();
|
|
594
|
+
const remember = (dir) => {
|
|
595
|
+
if (!existsSync(dir))
|
|
596
|
+
return;
|
|
597
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
598
|
+
const path = join(dir, entry.name);
|
|
599
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules')
|
|
600
|
+
remember(path);
|
|
601
|
+
else if (entry.isFile())
|
|
602
|
+
originals.set(path, stamp(path));
|
|
354
603
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
604
|
+
};
|
|
605
|
+
remember(baseDir);
|
|
606
|
+
for (const path of [mappingPath, join(baseDir, '.env'), ...metadataFiles(baseDir)])
|
|
607
|
+
originals.set(path, stamp(path));
|
|
608
|
+
const ignore = buildIgnorer(baseDir, []);
|
|
609
|
+
const rels = (existsSync(baseDir) ? collectMarkdown(baseDir) : []).map(file => relative(baseDir, file).split('\\').join('/')).filter(rel => !inNestedWorkspace(baseDir, rel) && !ignore.ignores(rel));
|
|
610
|
+
const requestedIds = new Set(ids);
|
|
611
|
+
const scoped = rels.filter(rel => inScope(rel, workspace) && (!incremental || requestedIds.has(oldMapping[rel]?.pageId)));
|
|
612
|
+
progressStep('덮어쓰기 전 로컬 변경·첨부 확인');
|
|
613
|
+
const tree = await preparePushTree(baseDir, rels, scoped);
|
|
614
|
+
const localClean = new Set();
|
|
615
|
+
try {
|
|
616
|
+
// Old releases stored the unprepared renderer hash. Accept only an exact match
|
|
617
|
+
// with that baseline, never a fresh hash inferred from the current file alone.
|
|
618
|
+
const legacy = buildTreeRenderer(baseDir, rels);
|
|
619
|
+
for (const rel of scoped) {
|
|
620
|
+
const doc = tree.docs[rel], rendered = tree.render(rel, doc.body, doc.title), entry = oldMapping[rel];
|
|
621
|
+
const clean = entry?.localHash
|
|
622
|
+
? entry.localHash === localStateHash(baseDir, rel, rendered)
|
|
623
|
+
: !!entry?.hash && (entry.hash === docHash(doc.title, rendered)
|
|
624
|
+
|| entry.hash === docHash(legacy.docs[rel].title, legacy.render(rel, legacy.docs[rel].body, legacy.docs[rel].title))
|
|
625
|
+
|| entry.hash === docHash(doc.title, tree.render(rel, doc.body, doc.title, 'panels'))
|
|
626
|
+
|| entry.hash === docHash(doc.title, tree.render(rel, doc.body, doc.title, true))
|
|
627
|
+
|| entry.hash === docHash(legacy.docs[rel].title, legacy.render(rel, legacy.docs[rel].body, legacy.docs[rel].title, 'panels'))
|
|
628
|
+
|| entry.hash === docHash(legacy.docs[rel].title, legacy.render(rel, legacy.docs[rel].body, legacy.docs[rel].title, true)));
|
|
629
|
+
if (clean)
|
|
630
|
+
localClean.add(rel);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
finally {
|
|
634
|
+
tree.dispose();
|
|
358
635
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
636
|
+
if (remoteOnly && incremental) {
|
|
637
|
+
ids = ids.filter(id => { const rel = knownById.get(id); const clean = !!rel && (newPaths.has(rel) || recoveringPaths.has(rel) || localClean.has(rel)) && !oldMapping[rel]?.pendingMove; if (!clean) {
|
|
638
|
+
skipped++;
|
|
639
|
+
console.log(`로컬 변경·확인 필요로 보존: ${rel ?? id}`);
|
|
640
|
+
} return clean; });
|
|
641
|
+
rootId = ids.includes(scope.id) ? scope.id : undefined;
|
|
642
|
+
if (!ids.length) {
|
|
643
|
+
console.log('완료 · 안전하게 가져올 원격 전용 변경이 없습니다. 로컬 변경은 유지합니다.');
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const stage = mkdtempSync(join(tmpdir(), 'csync-pull-'));
|
|
648
|
+
const stageOut = join(stage, relative(baseDir, outDir));
|
|
649
|
+
const ctx = {
|
|
650
|
+
client, remoteBaseUrl: env.baseUrl, missingAttachments: 0, incremental, expectedVersions,
|
|
651
|
+
obsidian: argv.includes('--obsidian'), spaceKey: env.spaceKey, written: [], pathById: new Map(), folderIds: new Map(),
|
|
652
|
+
versions: new Map(), parents: new Map(), assetOwners: new Map(), knownPaths: new Map([...knownById].map(([id, rel]) => [id, resolve(stage, rel)])),
|
|
653
|
+
stageBase: stage, baseDir, workspace, rootId, claimed: new Map(), visited: new Set(),
|
|
654
|
+
};
|
|
655
|
+
try {
|
|
656
|
+
if (discovered?.folders)
|
|
657
|
+
for (const folder of discovered.folders) {
|
|
658
|
+
if (![...newPaths, ...recoveringPaths].some(path => path.startsWith(folder.path)))
|
|
659
|
+
continue;
|
|
660
|
+
const prior = oldMapping[folder.path];
|
|
661
|
+
if (prior && prior.pageId !== folder.pageId)
|
|
662
|
+
throw new Error(`원격 폴더 경로 충돌: ${folder.path}`);
|
|
663
|
+
const path = resolve(stage, folder.path);
|
|
664
|
+
claim(ctx, path, folder.pageId, true);
|
|
665
|
+
mkdirSync(path, { recursive: true });
|
|
666
|
+
ctx.folderIds.set(path, folder.pageId);
|
|
667
|
+
}
|
|
668
|
+
// Reuse page IDs and local paths even when a remote title has changed.
|
|
669
|
+
if (rootId)
|
|
670
|
+
ctx.knownPaths.delete(rootId);
|
|
671
|
+
const counts = { pages: 0, folders: 0 };
|
|
672
|
+
for (const id of [...new Set(ids)]) {
|
|
673
|
+
if (rootId === id && oldMapping['README.md'] && oldMapping['README.md'].pageId !== id)
|
|
674
|
+
throw new Error('루트 README.md가 다른 페이지에 연결되어 있습니다.');
|
|
675
|
+
add(counts, await pullNode(ctx, id, stageOut));
|
|
676
|
+
}
|
|
677
|
+
// Link changed pages to unchanged local pages without rewriting or re-baselining them.
|
|
678
|
+
const linkPaths = new Map(ctx.pathById), referenceInfo = new Map();
|
|
679
|
+
if (incremental)
|
|
680
|
+
for (const [id, rel] of knownById) {
|
|
681
|
+
if (linkPaths.has(id) || !/\.md$/i.test(rel) || ignore.ignores(rel) || inNestedWorkspace(baseDir, rel))
|
|
682
|
+
continue;
|
|
683
|
+
const original = resolve(baseDir, rel), staged = resolve(stage, rel);
|
|
684
|
+
if (!within(baseDir, original) || !within(stage, staged) || !existsSync(original) || !lstatSync(original).isFile() || lstatSync(original).isSymbolicLink() || !within(realpathSync(baseDir), realpathSync(original)))
|
|
685
|
+
continue;
|
|
686
|
+
linkPaths.set(id, staged);
|
|
687
|
+
referenceInfo.set(staged, readDocInfo(original));
|
|
688
|
+
}
|
|
689
|
+
const { count: anchors, info } = anchorPass(ctx.written, linkPaths, referenceInfo);
|
|
690
|
+
const relinked = relinkPass(ctx.written, linkPaths, ctx.obsidian, info);
|
|
691
|
+
const stagedFiles = [];
|
|
692
|
+
const stagedDirs = [];
|
|
693
|
+
const gather = (dir) => {
|
|
694
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
695
|
+
const path = join(dir, entry.name);
|
|
696
|
+
if (entry.isDirectory()) {
|
|
697
|
+
stagedDirs.push(path);
|
|
698
|
+
gather(path);
|
|
699
|
+
}
|
|
700
|
+
else
|
|
701
|
+
stagedFiles.push(path);
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
gather(stage);
|
|
705
|
+
const stagedDocuments = new Set(ctx.written);
|
|
706
|
+
const checkDestination = (rel, directory) => {
|
|
707
|
+
const dest = resolve(baseDir, rel);
|
|
708
|
+
for (let path = dest; within(baseDir, path); path = dirname(path)) {
|
|
709
|
+
if (existsSync(path)) {
|
|
710
|
+
const info = lstatSync(path);
|
|
711
|
+
if (info.isSymbolicLink() || !within(realpathSync(baseDir), realpathSync(path)))
|
|
712
|
+
throw new Error(`심볼릭 링크 pull 대상은 지원하지 않습니다: ${rel}`);
|
|
713
|
+
if ((path === dest && !directory) ? !info.isFile() : !info.isDirectory())
|
|
714
|
+
throw new Error(`파일·폴더 경로 충돌: ${rel}`);
|
|
715
|
+
}
|
|
716
|
+
if (path === baseDir)
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
for (const dir of stagedDirs)
|
|
721
|
+
checkDestination(relative(stage, dir), true);
|
|
722
|
+
// Validate all destinations before the first replacement, including attachment collisions.
|
|
723
|
+
for (const file of stagedFiles) {
|
|
724
|
+
const rel = relative(stage, file).split('\\').join('/');
|
|
725
|
+
const dest = resolve(baseDir, rel);
|
|
726
|
+
if (ignore.ignores(rel))
|
|
727
|
+
throw new Error(`제외된 pull 대상: ${rel}`);
|
|
728
|
+
checkDestination(rel, false);
|
|
729
|
+
if (stamp(dest) !== originals.get(dest))
|
|
730
|
+
throw new Error(`pull 도중 로컬 파일이 변경되었습니다: ${rel}`);
|
|
731
|
+
if (!existsSync(dest))
|
|
732
|
+
continue;
|
|
733
|
+
if (stagedDocuments.has(file)) {
|
|
734
|
+
const entry = oldMapping[rel];
|
|
735
|
+
if (recoveringPaths.has(rel)) {
|
|
736
|
+
if (!readFileSync(file).equals(readFileSync(dest)))
|
|
737
|
+
throw new Error(`받기 미완료 문서가 원격과 다릅니다: ${rel}. 로컬 수정은 유지합니다. 원격과 비교하고 필요한 변경을 보관한 뒤 다시 시도하세요.`);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (!entry || (!localClean.has(rel) && !(restoreOverwrite && restorePaths?.includes(rel))) || entry.pendingMove)
|
|
741
|
+
throw new Error(`로컬 변경 충돌: ${rel}. 변경을 보관하거나 비교한 뒤 다시 pull하세요.`);
|
|
742
|
+
}
|
|
743
|
+
else if (stamp(file) !== stamp(dest)) {
|
|
744
|
+
const owner = ctx.assetOwners.get(file);
|
|
745
|
+
const ownerRel = owner ? knownById.get(owner) : undefined;
|
|
746
|
+
if (!ownerRel || (!localClean.has(ownerRel) && !(restoreOverwrite && restorePaths?.includes(ownerRel))))
|
|
747
|
+
throw new Error(`첨부 파일 충돌: ${rel}. 별도 --out으로 받아 비교하세요.`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
for (const path of [mappingPath, join(baseDir, '.env'), ...metadataFiles(baseDir)])
|
|
751
|
+
if (stamp(path) !== originals.get(path))
|
|
752
|
+
throw new Error('pull 도중 설정·매핑이 변경되었습니다.');
|
|
753
|
+
if (restorePaths)
|
|
754
|
+
validateRestorePaths(baseDir, restorePaths, restoreOverwrite);
|
|
755
|
+
const backup = mkdtempSync(join(tmpdir(), 'csync-pull-backup-'));
|
|
756
|
+
const applied = [], createdDirs = [];
|
|
757
|
+
let mapped, keepBackup = false;
|
|
758
|
+
const ensureDirectory = (dir) => {
|
|
759
|
+
if (existsSync(dir))
|
|
760
|
+
return;
|
|
761
|
+
const absent = [];
|
|
762
|
+
for (let p = dir; within(baseDir, p) && !existsSync(p); p = dirname(p))
|
|
763
|
+
absent.push(p);
|
|
764
|
+
mkdirSync(dir, { recursive: true });
|
|
765
|
+
createdDirs.push(...absent.reverse());
|
|
766
|
+
};
|
|
767
|
+
try {
|
|
768
|
+
ensureDirectory(baseDir);
|
|
769
|
+
for (const dir of stagedDirs)
|
|
770
|
+
ensureDirectory(join(baseDir, relative(stage, dir)));
|
|
771
|
+
for (const file of stagedFiles) {
|
|
772
|
+
const rel = relative(stage, file).split('\\').join('/'), dest = join(baseDir, relative(stage, file));
|
|
773
|
+
if (recoveringPaths.has(rel) && stagedDocuments.has(file))
|
|
774
|
+
continue;
|
|
775
|
+
if (restorePaths && existsSync(dest) && !stagedDocuments.has(file) && stamp(dest) === stamp(file))
|
|
776
|
+
continue;
|
|
777
|
+
if (stamp(dest) !== originals.get(dest))
|
|
778
|
+
throw new Error(`pull 도중 로컬 파일이 변경되었습니다: ${rel}`);
|
|
779
|
+
ensureDirectory(dirname(dest));
|
|
780
|
+
const record = { dest, hash: stamp(file) };
|
|
781
|
+
if (existsSync(dest)) {
|
|
782
|
+
record.prior = join(backup, String(applied.length));
|
|
783
|
+
copyFileSync(dest, record.prior);
|
|
784
|
+
}
|
|
785
|
+
applied.push(record);
|
|
786
|
+
try {
|
|
787
|
+
copyFileSync(file, dest, (restorePaths && !restoreOverwrite || newPaths.has(rel)) ? constants.COPYFILE_EXCL : 0);
|
|
788
|
+
}
|
|
789
|
+
catch (error) {
|
|
790
|
+
if (error.code === 'EEXIST' && (restorePaths && !restoreOverwrite || newPaths.has(rel))) {
|
|
791
|
+
applied.pop();
|
|
792
|
+
throw error;
|
|
793
|
+
}
|
|
794
|
+
record.hash = stamp(dest);
|
|
795
|
+
throw error;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const paths = new Map([...ctx.pathById].map(([id, path]) => [id, join(baseDir, relative(stage, path))]));
|
|
799
|
+
const folders = new Map([...ctx.folderIds].map(([path, id]) => [join(baseDir, relative(stage, path)), id]));
|
|
800
|
+
mapped = await mappingPass(baseDir, mappingPath, paths, folders, ctx.versions, ctx.parents, () => {
|
|
801
|
+
for (const file of ctx.written)
|
|
802
|
+
if (stamp(join(baseDir, relative(stage, file))) !== stamp(file))
|
|
803
|
+
throw new Error('기준 계산 중 로컬 문서가 변경되었습니다. 추가 수정은 보존합니다.');
|
|
804
|
+
for (const record of applied)
|
|
805
|
+
if (stamp(record.dest) !== record.hash)
|
|
806
|
+
throw new Error('기준 계산 중 로컬 문서·첨부가 변경되었습니다. 추가 수정은 보존합니다.');
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
let preserved = 0;
|
|
811
|
+
for (const record of applied.reverse()) {
|
|
812
|
+
if (stamp(record.dest) !== record.hash) {
|
|
813
|
+
preserved++;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
if (record.prior)
|
|
818
|
+
copyFileSync(record.prior, record.dest);
|
|
819
|
+
else
|
|
820
|
+
rmSync(record.dest, { force: true });
|
|
821
|
+
}
|
|
822
|
+
catch {
|
|
823
|
+
keepBackup = true;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
for (const dir of [...new Set(createdDirs)].reverse())
|
|
827
|
+
try {
|
|
828
|
+
rmdirSync(dir);
|
|
829
|
+
}
|
|
830
|
+
catch { /* Keep non-empty directories and concurrent local additions. */ }
|
|
831
|
+
console.log(preserved ? `[경고] 적용 실패로 저장을 취소했습니다. 도중 추가 수정된 ${preserved}개 파일은 보존했습니다.` : '적용 실패로 문서·첨부 저장을 취소했습니다. 기존 로컬 파일은 복원했습니다.');
|
|
832
|
+
if (keepBackup)
|
|
833
|
+
throw new Error(`${error.message} · 일부 파일 복원 실패. 복구용 백업: ${backup}`);
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
836
|
+
finally {
|
|
837
|
+
if (!keepBackup)
|
|
838
|
+
rmSync(backup, { recursive: true, force: true });
|
|
839
|
+
}
|
|
840
|
+
console.log(`\n${green('완료')} 페이지 ${counts.pages}개, 폴더 ${counts.folders}개${incremental ? `, 건너뜀 ${skipped}개` : ''} 매핑 ${mapped}건 내부링크 ${relinked}개, 앵커 ${anchors}개`);
|
|
841
|
+
console.log(dim(` base: ${baseDir}`));
|
|
842
|
+
if (ctx.missingAttachments)
|
|
843
|
+
console.log(`[경고 요약] 원격 첨부 ${ctx.missingAttachments}개를 로컬로 저장하지 못했습니다. 문서는 저장했으며 원격 참조를 유지했습니다.`);
|
|
363
844
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
console.error(red(`✗ 콘텐츠 ID 를 인식할 수 없습니다: ${target}`) + '\n 숫자 ID 또는 .../pages/<ID>/... · .../folder/<ID> URL 을 주세요.');
|
|
367
|
-
process.exit(1);
|
|
845
|
+
finally {
|
|
846
|
+
rmSync(stage, { recursive: true, force: true });
|
|
368
847
|
}
|
|
369
|
-
console.log(`${dim('pull:')} #${contentId} ${dim('→')} ${cyan(outDir)}${withChildren ? dim(' (+children)') : ''}`);
|
|
370
|
-
done(await pullNode(ctx, contentId, outDir));
|
|
371
848
|
}
|