@knpkv/confluence-to-markdown 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +58 -14
  3. package/dist/AdfPlaceholders.d.ts +42 -0
  4. package/dist/AdfPlaceholders.d.ts.map +1 -0
  5. package/dist/AdfPlaceholders.js +547 -0
  6. package/dist/AdfPlaceholders.js.map +1 -0
  7. package/dist/AdfSchemaValidator.d.ts +37 -0
  8. package/dist/AdfSchemaValidator.d.ts.map +1 -0
  9. package/dist/AdfSchemaValidator.js +37 -0
  10. package/dist/AdfSchemaValidator.js.map +1 -0
  11. package/dist/AdfWalker.d.ts +39 -0
  12. package/dist/AdfWalker.d.ts.map +1 -0
  13. package/dist/AdfWalker.js +527 -0
  14. package/dist/AdfWalker.js.map +1 -0
  15. package/dist/AtlaskitTransformers.d.ts +35 -0
  16. package/dist/AtlaskitTransformers.d.ts.map +1 -0
  17. package/dist/AtlaskitTransformers.js +48 -0
  18. package/dist/AtlaskitTransformers.js.map +1 -0
  19. package/dist/Brand.d.ts +6 -6
  20. package/dist/Brand.d.ts.map +1 -1
  21. package/dist/Brand.js +8 -6
  22. package/dist/Brand.js.map +1 -1
  23. package/dist/ConfluenceAuth.d.ts +4 -4
  24. package/dist/ConfluenceAuth.d.ts.map +1 -1
  25. package/dist/ConfluenceAuth.js +15 -27
  26. package/dist/ConfluenceAuth.js.map +1 -1
  27. package/dist/ConfluenceClient.d.ts +4 -4
  28. package/dist/ConfluenceClient.d.ts.map +1 -1
  29. package/dist/ConfluenceClient.js +21 -14
  30. package/dist/ConfluenceClient.js.map +1 -1
  31. package/dist/ConfluenceConfig.d.ts +3 -3
  32. package/dist/ConfluenceConfig.d.ts.map +1 -1
  33. package/dist/ConfluenceConfig.js +13 -11
  34. package/dist/ConfluenceConfig.js.map +1 -1
  35. package/dist/ConfluenceError.d.ts +56 -4
  36. package/dist/ConfluenceError.d.ts.map +1 -1
  37. package/dist/ConfluenceError.js +30 -1
  38. package/dist/ConfluenceError.js.map +1 -1
  39. package/dist/GitService.d.ts +11 -3
  40. package/dist/GitService.d.ts.map +1 -1
  41. package/dist/GitService.js +19 -27
  42. package/dist/GitService.js.map +1 -1
  43. package/dist/LocalFileSystem.d.ts +3 -3
  44. package/dist/LocalFileSystem.d.ts.map +1 -1
  45. package/dist/LocalFileSystem.js +6 -6
  46. package/dist/LocalFileSystem.js.map +1 -1
  47. package/dist/MarkdownConverter.d.ts +16 -65
  48. package/dist/MarkdownConverter.d.ts.map +1 -1
  49. package/dist/MarkdownConverter.js +64 -85
  50. package/dist/MarkdownConverter.js.map +1 -1
  51. package/dist/Schemas.d.ts +128 -141
  52. package/dist/Schemas.d.ts.map +1 -1
  53. package/dist/Schemas.js +21 -23
  54. package/dist/Schemas.js.map +1 -1
  55. package/dist/SyncEngine.d.ts +8 -5
  56. package/dist/SyncEngine.d.ts.map +1 -1
  57. package/dist/SyncEngine.js +189 -113
  58. package/dist/SyncEngine.js.map +1 -1
  59. package/dist/bin.js +23 -35
  60. package/dist/bin.js.map +1 -1
  61. package/dist/commands/auth.d.ts +2 -14
  62. package/dist/commands/auth.d.ts.map +1 -1
  63. package/dist/commands/auth.js +11 -16
  64. package/dist/commands/auth.js.map +1 -1
  65. package/dist/commands/clone.d.ts +4 -6
  66. package/dist/commands/clone.d.ts.map +1 -1
  67. package/dist/commands/clone.js +34 -32
  68. package/dist/commands/clone.js.map +1 -1
  69. package/dist/commands/delete.d.ts +2 -10
  70. package/dist/commands/delete.d.ts.map +1 -1
  71. package/dist/commands/delete.js +5 -4
  72. package/dist/commands/delete.js.map +1 -1
  73. package/dist/commands/errorHandler.d.ts +2 -1
  74. package/dist/commands/errorHandler.d.ts.map +1 -1
  75. package/dist/commands/errorHandler.js +22 -15
  76. package/dist/commands/errorHandler.js.map +1 -1
  77. package/dist/commands/fetch.d.ts +27 -0
  78. package/dist/commands/fetch.d.ts.map +1 -0
  79. package/dist/commands/fetch.js +48 -0
  80. package/dist/commands/fetch.js.map +1 -0
  81. package/dist/commands/git.d.ts +7 -10
  82. package/dist/commands/git.d.ts.map +1 -1
  83. package/dist/commands/git.js +6 -6
  84. package/dist/commands/git.js.map +1 -1
  85. package/dist/commands/index.d.ts +1 -0
  86. package/dist/commands/index.d.ts.map +1 -1
  87. package/dist/commands/index.js +1 -0
  88. package/dist/commands/index.js.map +1 -1
  89. package/dist/commands/layers.d.ts +10 -9
  90. package/dist/commands/layers.d.ts.map +1 -1
  91. package/dist/commands/layers.js +41 -30
  92. package/dist/commands/layers.js.map +1 -1
  93. package/dist/commands/new.d.ts +2 -6
  94. package/dist/commands/new.d.ts.map +1 -1
  95. package/dist/commands/new.js +5 -4
  96. package/dist/commands/new.js.map +1 -1
  97. package/dist/commands/pageInput.d.ts +19 -0
  98. package/dist/commands/pageInput.d.ts.map +1 -0
  99. package/dist/commands/pageInput.js +68 -0
  100. package/dist/commands/pageInput.js.map +1 -0
  101. package/dist/commands/root.d.ts +8 -0
  102. package/dist/commands/root.d.ts.map +1 -0
  103. package/dist/commands/root.js +29 -0
  104. package/dist/commands/root.js.map +1 -0
  105. package/dist/commands/sync.d.ts +6 -9
  106. package/dist/commands/sync.d.ts.map +1 -1
  107. package/dist/commands/sync.js +5 -6
  108. package/dist/commands/sync.js.map +1 -1
  109. package/dist/index.d.ts +3 -8
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +2 -13
  112. package/dist/index.js.map +1 -1
  113. package/dist/internal/NodeLayers.d.ts.map +1 -1
  114. package/dist/internal/NodeLayers.js +1 -2
  115. package/dist/internal/NodeLayers.js.map +1 -1
  116. package/dist/internal/adfMetadata.d.ts +30 -0
  117. package/dist/internal/adfMetadata.d.ts.map +1 -0
  118. package/dist/internal/adfMetadata.js +126 -0
  119. package/dist/internal/adfMetadata.js.map +1 -0
  120. package/dist/internal/cleanMarkdown.d.ts +5 -0
  121. package/dist/internal/cleanMarkdown.d.ts.map +1 -0
  122. package/dist/internal/cleanMarkdown.js +13 -0
  123. package/dist/internal/cleanMarkdown.js.map +1 -0
  124. package/dist/internal/frontmatter.d.ts.map +1 -1
  125. package/dist/internal/frontmatter.js +41 -8
  126. package/dist/internal/frontmatter.js.map +1 -1
  127. package/dist/internal/gitCommands.d.ts +9 -3
  128. package/dist/internal/gitCommands.d.ts.map +1 -1
  129. package/dist/internal/gitCommands.js +18 -9
  130. package/dist/internal/gitCommands.js.map +1 -1
  131. package/dist/internal/hashUtils.d.ts +1 -1
  132. package/dist/internal/hashUtils.d.ts.map +1 -1
  133. package/dist/internal/hashUtils.js +1 -1
  134. package/dist/internal/hashUtils.js.map +1 -1
  135. package/dist/internal/oauthServer.d.ts +10 -5
  136. package/dist/internal/oauthServer.d.ts.map +1 -1
  137. package/dist/internal/oauthServer.js +19 -40
  138. package/dist/internal/oauthServer.js.map +1 -1
  139. package/dist/internal/pathUtils.d.ts +1 -1
  140. package/dist/internal/pathUtils.d.ts.map +1 -1
  141. package/dist/internal/pathUtils.js +1 -1
  142. package/dist/internal/pathUtils.js.map +1 -1
  143. package/dist/internal/process.d.ts +15 -0
  144. package/dist/internal/process.d.ts.map +1 -0
  145. package/dist/internal/process.js +10 -0
  146. package/dist/internal/process.js.map +1 -0
  147. package/dist/internal/stdio.d.ts +6 -0
  148. package/dist/internal/stdio.d.ts.map +1 -0
  149. package/dist/internal/stdio.js +15 -0
  150. package/dist/internal/stdio.js.map +1 -0
  151. package/dist/internal/tokenStorage.d.ts +3 -13
  152. package/dist/internal/tokenStorage.d.ts.map +1 -1
  153. package/dist/internal/tokenStorage.js +26 -24
  154. package/dist/internal/tokenStorage.js.map +1 -1
  155. package/dist/internal/userCache.d.ts +1 -1
  156. package/dist/internal/userCache.d.ts.map +1 -1
  157. package/dist/internal/userCache.js +1 -1
  158. package/dist/internal/userCache.js.map +1 -1
  159. package/package.json +29 -20
  160. package/skills/confluence/SKILL.md +143 -0
  161. package/skills/confluence/agents/openai.yaml +4 -0
  162. package/src/AdfPlaceholders.ts +563 -0
  163. package/src/AdfSchemaValidator.ts +65 -0
  164. package/src/AdfWalker.ts +591 -0
  165. package/src/AtlaskitTransformers.ts +70 -0
  166. package/src/Brand.ts +11 -16
  167. package/src/ConfluenceAuth.ts +22 -30
  168. package/src/ConfluenceClient.ts +28 -24
  169. package/src/ConfluenceConfig.ts +14 -14
  170. package/src/ConfluenceError.ts +65 -3
  171. package/src/GitService.ts +39 -49
  172. package/src/LocalFileSystem.ts +7 -9
  173. package/src/MarkdownConverter.ts +108 -143
  174. package/src/Schemas.ts +17 -16
  175. package/src/SyncEngine.ts +272 -127
  176. package/src/atlaskit-adf-schema.d.ts +3 -0
  177. package/src/bin.ts +30 -56
  178. package/src/commands/auth.ts +21 -18
  179. package/src/commands/clone.ts +46 -38
  180. package/src/commands/delete.ts +5 -4
  181. package/src/commands/errorHandler.ts +25 -18
  182. package/src/commands/fetch.ts +90 -0
  183. package/src/commands/git.ts +6 -6
  184. package/src/commands/index.ts +1 -0
  185. package/src/commands/layers.ts +64 -37
  186. package/src/commands/new.ts +5 -4
  187. package/src/commands/pageInput.ts +103 -0
  188. package/src/commands/root.ts +59 -0
  189. package/src/commands/sync.ts +7 -6
  190. package/src/index.ts +3 -18
  191. package/src/internal/NodeLayers.ts +1 -2
  192. package/src/internal/adfMetadata.ts +145 -0
  193. package/src/internal/cleanMarkdown.ts +15 -0
  194. package/src/internal/frontmatter.ts +45 -8
  195. package/src/internal/gitCommands.ts +23 -17
  196. package/src/internal/hashUtils.ts +2 -2
  197. package/src/internal/oauthServer.ts +84 -105
  198. package/src/internal/pathUtils.ts +1 -1
  199. package/src/internal/process.ts +15 -0
  200. package/src/internal/stdio.ts +22 -0
  201. package/src/internal/tokenStorage.ts +39 -29
  202. package/src/internal/userCache.ts +2 -2
  203. package/test/AdfPlaceholders.test.ts +508 -0
  204. package/test/AdfSchemaValidator.test.ts +34 -0
  205. package/test/AdfWalker.test.ts +676 -0
  206. package/test/AtlaskitTransformers.test.ts +25 -0
  207. package/test/Brand.test.ts +11 -11
  208. package/test/GitService.test.ts +6 -2
  209. package/test/MarkdownConverter.test.ts +121 -105
  210. package/test/RoundTrip.test.ts +521 -0
  211. package/test/Schemas.test.ts +40 -40
  212. package/test/adfMetadata.test.ts +110 -0
  213. package/test/cleanMarkdown.test.ts +36 -0
  214. package/test/commandHarness.test.ts +79 -0
  215. package/test/commandHarness.ts +147 -0
  216. package/test/fetch.test.ts +61 -0
  217. package/test/frontmatter.test.ts +41 -0
  218. package/test/integration.test.ts +569 -156
  219. package/test/layers.test.ts +12 -0
  220. package/test/oauthServer.test.ts +4 -5
  221. package/test/pageInput.test.ts +83 -0
  222. package/test/tokenStorage.test.ts +17 -17
  223. package/dist/SchemaConverterError.d.ts +0 -108
  224. package/dist/SchemaConverterError.d.ts.map +0 -1
  225. package/dist/SchemaConverterError.js +0 -84
  226. package/dist/SchemaConverterError.js.map +0 -1
  227. package/dist/ast/BlockNode.d.ts +0 -453
  228. package/dist/ast/BlockNode.d.ts.map +0 -1
  229. package/dist/ast/BlockNode.js +0 -310
  230. package/dist/ast/BlockNode.js.map +0 -1
  231. package/dist/ast/Document.d.ts +0 -216
  232. package/dist/ast/Document.d.ts.map +0 -1
  233. package/dist/ast/Document.js +0 -69
  234. package/dist/ast/Document.js.map +0 -1
  235. package/dist/ast/InlineNode.d.ts +0 -477
  236. package/dist/ast/InlineNode.d.ts.map +0 -1
  237. package/dist/ast/InlineNode.js +0 -263
  238. package/dist/ast/InlineNode.js.map +0 -1
  239. package/dist/ast/MacroNode.d.ts +0 -267
  240. package/dist/ast/MacroNode.d.ts.map +0 -1
  241. package/dist/ast/MacroNode.js +0 -164
  242. package/dist/ast/MacroNode.js.map +0 -1
  243. package/dist/ast/index.d.ts +0 -10
  244. package/dist/ast/index.d.ts.map +0 -1
  245. package/dist/ast/index.js +0 -14
  246. package/dist/ast/index.js.map +0 -1
  247. package/dist/parsers/ConfluenceParser.d.ts +0 -26
  248. package/dist/parsers/ConfluenceParser.d.ts.map +0 -1
  249. package/dist/parsers/ConfluenceParser.js +0 -797
  250. package/dist/parsers/ConfluenceParser.js.map +0 -1
  251. package/dist/parsers/MarkdownParser.d.ts +0 -26
  252. package/dist/parsers/MarkdownParser.d.ts.map +0 -1
  253. package/dist/parsers/MarkdownParser.js +0 -982
  254. package/dist/parsers/MarkdownParser.js.map +0 -1
  255. package/dist/parsers/index.d.ts +0 -8
  256. package/dist/parsers/index.d.ts.map +0 -1
  257. package/dist/parsers/index.js +0 -8
  258. package/dist/parsers/index.js.map +0 -1
  259. package/dist/schemas/ConfluenceSchema.d.ts +0 -21
  260. package/dist/schemas/ConfluenceSchema.d.ts.map +0 -1
  261. package/dist/schemas/ConfluenceSchema.js +0 -38
  262. package/dist/schemas/ConfluenceSchema.js.map +0 -1
  263. package/dist/schemas/ConversionSchema.d.ts +0 -35
  264. package/dist/schemas/ConversionSchema.d.ts.map +0 -1
  265. package/dist/schemas/ConversionSchema.js +0 -208
  266. package/dist/schemas/ConversionSchema.js.map +0 -1
  267. package/dist/schemas/MarkdownSchema.d.ts +0 -21
  268. package/dist/schemas/MarkdownSchema.d.ts.map +0 -1
  269. package/dist/schemas/MarkdownSchema.js +0 -38
  270. package/dist/schemas/MarkdownSchema.js.map +0 -1
  271. package/dist/schemas/hast/HastFromHtml.d.ts +0 -27
  272. package/dist/schemas/hast/HastFromHtml.d.ts.map +0 -1
  273. package/dist/schemas/hast/HastFromHtml.js +0 -107
  274. package/dist/schemas/hast/HastFromHtml.js.map +0 -1
  275. package/dist/schemas/hast/HastSchema.d.ts +0 -195
  276. package/dist/schemas/hast/HastSchema.d.ts.map +0 -1
  277. package/dist/schemas/hast/HastSchema.js +0 -183
  278. package/dist/schemas/hast/HastSchema.js.map +0 -1
  279. package/dist/schemas/hast/index.d.ts +0 -9
  280. package/dist/schemas/hast/index.d.ts.map +0 -1
  281. package/dist/schemas/hast/index.js +0 -3
  282. package/dist/schemas/hast/index.js.map +0 -1
  283. package/dist/schemas/index.d.ts +0 -14
  284. package/dist/schemas/index.d.ts.map +0 -1
  285. package/dist/schemas/index.js +0 -16
  286. package/dist/schemas/index.js.map +0 -1
  287. package/dist/schemas/mdast/MdastFromMarkdown.d.ts +0 -30
  288. package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +0 -1
  289. package/dist/schemas/mdast/MdastFromMarkdown.js +0 -79
  290. package/dist/schemas/mdast/MdastFromMarkdown.js.map +0 -1
  291. package/dist/schemas/mdast/MdastSchema.d.ts +0 -385
  292. package/dist/schemas/mdast/MdastSchema.d.ts.map +0 -1
  293. package/dist/schemas/mdast/MdastSchema.js +0 -266
  294. package/dist/schemas/mdast/MdastSchema.js.map +0 -1
  295. package/dist/schemas/mdast/index.d.ts +0 -10
  296. package/dist/schemas/mdast/index.d.ts.map +0 -1
  297. package/dist/schemas/mdast/index.js +0 -4
  298. package/dist/schemas/mdast/index.js.map +0 -1
  299. package/dist/schemas/mdast/mdastToString.d.ts +0 -13
  300. package/dist/schemas/mdast/mdastToString.d.ts.map +0 -1
  301. package/dist/schemas/mdast/mdastToString.js +0 -85
  302. package/dist/schemas/mdast/mdastToString.js.map +0 -1
  303. package/dist/schemas/nodes/block/BlockSchema.d.ts +0 -43
  304. package/dist/schemas/nodes/block/BlockSchema.d.ts.map +0 -1
  305. package/dist/schemas/nodes/block/BlockSchema.js +0 -634
  306. package/dist/schemas/nodes/block/BlockSchema.js.map +0 -1
  307. package/dist/schemas/nodes/block/index.d.ts +0 -7
  308. package/dist/schemas/nodes/block/index.d.ts.map +0 -1
  309. package/dist/schemas/nodes/block/index.js +0 -7
  310. package/dist/schemas/nodes/block/index.js.map +0 -1
  311. package/dist/schemas/nodes/index.d.ts +0 -9
  312. package/dist/schemas/nodes/index.d.ts.map +0 -1
  313. package/dist/schemas/nodes/index.js +0 -12
  314. package/dist/schemas/nodes/index.js.map +0 -1
  315. package/dist/schemas/nodes/inline/InlineSchema.d.ts +0 -48
  316. package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +0 -1
  317. package/dist/schemas/nodes/inline/InlineSchema.js +0 -436
  318. package/dist/schemas/nodes/inline/InlineSchema.js.map +0 -1
  319. package/dist/schemas/nodes/inline/index.d.ts +0 -7
  320. package/dist/schemas/nodes/inline/index.d.ts.map +0 -1
  321. package/dist/schemas/nodes/inline/index.js +0 -7
  322. package/dist/schemas/nodes/inline/index.js.map +0 -1
  323. package/dist/schemas/nodes/macro/MacroSchema.d.ts +0 -27
  324. package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +0 -1
  325. package/dist/schemas/nodes/macro/MacroSchema.js +0 -162
  326. package/dist/schemas/nodes/macro/MacroSchema.js.map +0 -1
  327. package/dist/schemas/nodes/macro/index.d.ts +0 -7
  328. package/dist/schemas/nodes/macro/index.d.ts.map +0 -1
  329. package/dist/schemas/nodes/macro/index.js +0 -7
  330. package/dist/schemas/nodes/macro/index.js.map +0 -1
  331. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +0 -24
  332. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +0 -1
  333. package/dist/schemas/preprocessing/ConfluencePreprocessor.js +0 -359
  334. package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +0 -1
  335. package/dist/schemas/preprocessing/index.d.ts +0 -8
  336. package/dist/schemas/preprocessing/index.d.ts.map +0 -1
  337. package/dist/schemas/preprocessing/index.js +0 -2
  338. package/dist/schemas/preprocessing/index.js.map +0 -1
  339. package/dist/serializers/ConfluenceSerializer.d.ts +0 -30
  340. package/dist/serializers/ConfluenceSerializer.d.ts.map +0 -1
  341. package/dist/serializers/ConfluenceSerializer.js +0 -560
  342. package/dist/serializers/ConfluenceSerializer.js.map +0 -1
  343. package/dist/serializers/MarkdownSerializer.d.ts +0 -34
  344. package/dist/serializers/MarkdownSerializer.d.ts.map +0 -1
  345. package/dist/serializers/MarkdownSerializer.js +0 -395
  346. package/dist/serializers/MarkdownSerializer.js.map +0 -1
  347. package/dist/serializers/index.d.ts +0 -8
  348. package/dist/serializers/index.d.ts.map +0 -1
  349. package/dist/serializers/index.js +0 -8
  350. package/dist/serializers/index.js.map +0 -1
  351. package/src/SchemaConverterError.ts +0 -108
  352. package/src/ast/BlockNode.ts +0 -469
  353. package/src/ast/Document.ts +0 -90
  354. package/src/ast/InlineNode.ts +0 -323
  355. package/src/ast/MacroNode.ts +0 -245
  356. package/src/ast/index.ts +0 -83
  357. package/src/parsers/ConfluenceParser.ts +0 -956
  358. package/src/parsers/MarkdownParser.ts +0 -1338
  359. package/src/parsers/index.ts +0 -8
  360. package/src/schemas/ConfluenceSchema.ts +0 -56
  361. package/src/schemas/ConversionSchema.ts +0 -318
  362. package/src/schemas/MarkdownSchema.ts +0 -56
  363. package/src/schemas/hast/HastFromHtml.ts +0 -153
  364. package/src/schemas/hast/HastSchema.ts +0 -274
  365. package/src/schemas/hast/index.ts +0 -35
  366. package/src/schemas/index.ts +0 -20
  367. package/src/schemas/mdast/MdastFromMarkdown.ts +0 -118
  368. package/src/schemas/mdast/MdastSchema.ts +0 -566
  369. package/src/schemas/mdast/index.ts +0 -59
  370. package/src/schemas/mdast/mdastToString.ts +0 -102
  371. package/src/schemas/nodes/block/BlockSchema.ts +0 -773
  372. package/src/schemas/nodes/block/index.ts +0 -13
  373. package/src/schemas/nodes/index.ts +0 -20
  374. package/src/schemas/nodes/inline/InlineSchema.ts +0 -523
  375. package/src/schemas/nodes/inline/index.ts +0 -14
  376. package/src/schemas/nodes/macro/MacroSchema.ts +0 -226
  377. package/src/schemas/nodes/macro/index.ts +0 -6
  378. package/src/schemas/preprocessing/ConfluencePreprocessor.ts +0 -455
  379. package/src/schemas/preprocessing/index.ts +0 -8
  380. package/src/serializers/ConfluenceSerializer.ts +0 -737
  381. package/src/serializers/MarkdownSerializer.ts +0 -543
  382. package/src/serializers/index.ts +0 -8
  383. package/test/ast/BlockNode.test.ts +0 -265
  384. package/test/ast/Document.test.ts +0 -126
  385. package/test/ast/InlineNode.test.ts +0 -161
  386. package/test/fixtures/integration-test.html.fixture +0 -103
  387. package/test/fixtures/integration-test.md.expected +0 -257
  388. package/test/parsers/ConfluenceParser.test.ts +0 -452
  389. package/test/schemas/ConfluencePreprocessor.test.ts +0 -180
  390. package/test/schemas/ConversionSchema.test.ts +0 -159
  391. package/test/schemas/HastSchema.test.ts +0 -138
  392. package/test/schemas/MdastSchema.test.ts +0 -145
  393. package/test/schemas/nodes/block/BlockSchema.test.ts +0 -173
  394. package/test/schemas/nodes/inline/InlineSchema.test.ts +0 -198
  395. package/test/schemas/nodes/macro/MacroSchema.test.ts +0 -142
package/src/SyncEngine.ts CHANGED
@@ -3,10 +3,15 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import * as Path from "@effect/platform/Path"
7
6
  import * as Context from "effect/Context"
8
7
  import * as Effect from "effect/Effect"
8
+ import * as FileSystem from "effect/FileSystem"
9
9
  import * as Layer from "effect/Layer"
10
+ import * as Path from "effect/Path"
11
+ import type * as PlatformError from "effect/PlatformError"
12
+ import * as Result from "effect/Result"
13
+ import * as Schema from "effect/Schema"
14
+ import type * as Terminal from "effect/Terminal"
10
15
  import { PageId } from "./Brand.js"
11
16
  import { ConfluenceClient } from "./ConfluenceClient.js"
12
17
  import { ConfluenceConfig } from "./ConfluenceConfig.js"
@@ -14,6 +19,14 @@ import type { ApiError, ConversionError, FrontMatterError, RateLimitError } from
14
19
  import { FileSystemError, StructureError } from "./ConfluenceError.js"
15
20
  import type { GitServiceError } from "./GitService.js"
16
21
  import { GitService } from "./GitService.js"
22
+ import type { AdfMetadataSidecar } from "./internal/adfMetadata.js"
23
+ import {
24
+ AdfMetadataSidecarSchema,
25
+ collectAdfMetadataHrefs,
26
+ externalizeAdfMetadata,
27
+ hydrateAdfMetadata
28
+ } from "./internal/adfMetadata.js"
29
+ import { parseMarkdown } from "./internal/frontmatter.js"
17
30
  import { computeHash, HashServiceLive } from "./internal/hashUtils.js"
18
31
  import { UserCache } from "./internal/userCache.js"
19
32
  import { LocalFileSystem } from "./LocalFileSystem.js"
@@ -40,7 +53,11 @@ export type SyncStatus =
40
53
  /**
41
54
  * Progress callback for version replay.
42
55
  */
43
- export type ProgressCallback = (current: number, total: number, message: string) => void
56
+ export type ProgressCallback = (
57
+ current: number,
58
+ total: number,
59
+ message: string
60
+ ) => Effect.Effect<void, PlatformError.PlatformError, Terminal.Terminal>
44
61
 
45
62
  /**
46
63
  * Options for pull operation.
@@ -101,6 +118,26 @@ type SyncError =
101
118
  | GitServiceError
102
119
  | StructureError
103
120
 
121
+ /**
122
+ * Pretty-print an ADF JSON wire string for the `.source.json` companion file.
123
+ * Falls back to the raw string if it can't be parsed as JSON.
124
+ */
125
+ const prettyAdf = (raw: string): string => {
126
+ try {
127
+ return JSON.stringify(JSON.parse(raw), null, 2)
128
+ } catch {
129
+ return raw
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Hash a markdown body in the form `readMarkdownFile` will later return it.
135
+ * `parseMarkdown` trims the body when reading, so the hash we store on
136
+ * frontmatter must hash the trimmed body too — otherwise every subsequent
137
+ * read sees a "changed" file and `push` keeps pushing a no-op update.
138
+ */
139
+ const hashBody = (markdown: string) => computeHash(markdown.trim()).pipe(Effect.provide(HashServiceLive))
140
+
104
141
  /**
105
142
  * Sync engine service for Confluence <-> Markdown operations.
106
143
  *
@@ -118,15 +155,15 @@ type SyncError =
118
155
  *
119
156
  * @category Sync
120
157
  */
121
- export class SyncEngine extends Context.Tag(
122
- "@knpkv/confluence-to-markdown/SyncEngine"
123
- )<
158
+ export class SyncEngine extends Context.Service<
124
159
  SyncEngine,
125
160
  {
126
161
  /**
127
162
  * Pull pages from Confluence to local markdown.
128
163
  */
129
- readonly pull: (options: PullOptions) => Effect.Effect<PullResult, SyncError>
164
+ readonly pull: (
165
+ options: PullOptions
166
+ ) => Effect.Effect<PullResult, SyncError | PlatformError.PlatformError, Terminal.Terminal>
130
167
 
131
168
  /**
132
169
  * Push local markdown changes to Confluence.
@@ -138,7 +175,7 @@ export class SyncEngine extends Context.Tag(
138
175
  */
139
176
  readonly status: () => Effect.Effect<StatusResult, SyncError>
140
177
  }
141
- >() {}
178
+ >()("@knpkv/confluence-to-markdown/SyncEngine") {}
142
179
 
143
180
  /**
144
181
  * Layer that provides SyncEngine.
@@ -148,7 +185,14 @@ export class SyncEngine extends Context.Tag(
148
185
  export const layer: Layer.Layer<
149
186
  SyncEngine,
150
187
  never,
151
- ConfluenceClient | ConfluenceConfig | MarkdownConverter | LocalFileSystem | Path.Path | GitService | UserCache
188
+ | ConfluenceClient
189
+ | ConfluenceConfig
190
+ | MarkdownConverter
191
+ | LocalFileSystem
192
+ | Path.Path
193
+ | GitService
194
+ | UserCache
195
+ | FileSystem.FileSystem
152
196
  > = Layer.effect(
153
197
  SyncEngine,
154
198
  Effect.gen(function*() {
@@ -157,10 +201,69 @@ export const layer: Layer.Layer<
157
201
  const converter = yield* MarkdownConverter
158
202
  const localFs = yield* LocalFileSystem
159
203
  const pathService = yield* Path.Path
204
+ const fs = yield* FileSystem.FileSystem
160
205
  const git = yield* GitService
161
206
  const userCache = yield* UserCache
162
207
 
163
- const docsPath = pathService.join(process.cwd(), config.docsPath)
208
+ const cwd = pathService.resolve(".")
209
+ const docsPath = pathService.join(cwd, config.docsPath)
210
+
211
+ const adfMetadataPath = (filePath: string, pageId?: string): string =>
212
+ pageId
213
+ ? pathService.join(pathService.dirname(filePath), `${pageId}.adf.json`)
214
+ : filePath.replace(/\.md$/, ".adf.json")
215
+
216
+ const adfMetadataHref = (filePath: string, pageId?: string): string =>
217
+ `./${pathService.basename(adfMetadataPath(filePath, pageId))}`
218
+
219
+ const prepareMarkdownForFile = (filePath: string, markdown: string, pageId?: string) =>
220
+ externalizeAdfMetadata(markdown, adfMetadataHref(filePath, pageId))
221
+
222
+ const writePreparedMarkdownWithAdfMetadata = (
223
+ filePath: string,
224
+ frontMatter: PageFrontMatter,
225
+ prepared: ReturnType<typeof externalizeAdfMetadata>
226
+ ): Effect.Effect<void, FileSystemError> =>
227
+ Effect.gen(function*() {
228
+ yield* localFs.writeMarkdownFile(filePath, frontMatter, prepared.markdown)
229
+ const sidecarPath = adfMetadataPath(filePath, frontMatter.pageId)
230
+ if (prepared.sidecar !== null) {
231
+ yield* localFs.writeFile(
232
+ sidecarPath,
233
+ JSON.stringify(prepared.sidecar, null, 2) + "\n"
234
+ )
235
+ } else if (yield* localFs.exists(sidecarPath)) {
236
+ yield* localFs.deleteFile(sidecarPath)
237
+ }
238
+ })
239
+
240
+ const readAdfMetadataSidecar = (sidecarPath: string): Effect.Effect<AdfMetadataSidecar | null, FileSystemError> =>
241
+ Effect.gen(function*() {
242
+ const exists = yield* localFs.exists(sidecarPath)
243
+ if (!exists) return null
244
+ const raw = yield* fs.readFileString(sidecarPath).pipe(
245
+ Effect.mapError((cause) => new FileSystemError({ operation: "read", path: sidecarPath, cause }))
246
+ )
247
+ const parsed = yield* Effect.try({
248
+ try: () => JSON.parse(raw) as unknown,
249
+ catch: (cause) => new FileSystemError({ operation: "read", path: sidecarPath, cause })
250
+ })
251
+ return yield* Schema.decodeUnknownEffect(AdfMetadataSidecarSchema)(parsed).pipe(
252
+ Effect.mapError((cause) => new FileSystemError({ operation: "read", path: sidecarPath, cause }))
253
+ )
254
+ })
255
+
256
+ const hydrateMarkdownForFile = (filePath: string, markdown: string): Effect.Effect<string, FileSystemError> =>
257
+ Effect.gen(function*() {
258
+ const sidecars = new Map<string, AdfMetadataSidecar>()
259
+ const dir = pathService.dirname(filePath)
260
+ for (const href of collectAdfMetadataHrefs(markdown)) {
261
+ const sidecarPath = pathService.resolve(dir, href)
262
+ const sidecar = yield* readAdfMetadataSidecar(sidecarPath)
263
+ if (sidecar !== null) sidecars.set(href, sidecar)
264
+ }
265
+ return hydrateAdfMetadata(markdown, sidecars)
266
+ })
164
267
 
165
268
  /**
166
269
  * Build a map of relative path (without .md) to pageId for resolving parents.
@@ -320,25 +423,30 @@ export const layer: Layer.Layer<
320
423
  */
321
424
  const getUser = (accountId: string): Effect.Effect<AtlassianUser | undefined, ApiError | RateLimitError> =>
322
425
  userCache.getOrFetch(accountId, client.getUser).pipe(
323
- Effect.catchAll(() => Effect.succeed(undefined))
426
+ Effect.catchCause(() => Effect.succeed(undefined))
324
427
  )
325
428
 
326
429
  /**
327
430
  * Convert version content to markdown and front-matter.
328
431
  */
329
432
  const versionToMarkdown = (
433
+ filePath: string,
330
434
  pageId: PageId,
331
435
  version: PageVersionContent,
332
436
  title: string,
333
437
  parentId?: string,
334
438
  position?: number
335
- ): Effect.Effect<{ markdown: string; frontMatter: PageFrontMatter }, SyncError> =>
439
+ ): Effect.Effect<{
440
+ markdown: string
441
+ frontMatter: PageFrontMatter
442
+ prepared: ReturnType<typeof externalizeAdfMetadata>
443
+ }, SyncError> =>
336
444
  Effect.gen(function*() {
337
- const htmlContent = version.body?.storage?.value ?? ""
338
- const markdown = yield* converter.htmlToMarkdown(htmlContent, {
339
- includeRawSource: config.saveSource
340
- })
341
- const contentHash = yield* computeHash(markdown).pipe(Effect.provide(HashServiceLive))
445
+ const adfJson = version.body?.atlas_doc_format?.value ?? ""
446
+ const rawMarkdown = yield* converter.adfToMarkdown(adfJson)
447
+ const prepared = prepareMarkdownForFile(filePath, rawMarkdown, pageId)
448
+ const { markdown } = prepared
449
+ const contentHash = yield* hashBody(markdown)
342
450
 
343
451
  // Get author info
344
452
  const author = version.authorId ? yield* getUser(version.authorId) : undefined
@@ -356,7 +464,7 @@ export const layer: Layer.Layer<
356
464
  ...(author?.email ? { authorEmail: author.email } : {})
357
465
  }
358
466
 
359
- return { markdown, frontMatter }
467
+ return { markdown, frontMatter, prepared }
360
468
  })
361
469
 
362
470
  /**
@@ -369,7 +477,11 @@ export const layer: Layer.Layer<
369
477
  options: PullOptions,
370
478
  gitInitialized: boolean,
371
479
  knownParentId?: string
372
- ): Effect.Effect<{ pulled: number; commits: number }, SyncError> =>
480
+ ): Effect.Effect<
481
+ { pulled: number; commits: number },
482
+ SyncError | PlatformError.PlatformError,
483
+ Terminal.Terminal
484
+ > =>
373
485
  Effect.gen(function*() {
374
486
  const pageId = page.id as PageId
375
487
  // Get children to determine if this is a folder
@@ -430,11 +542,11 @@ export const layer: Layer.Layer<
430
542
  versionIdx++
431
543
  // Report progress
432
544
  if (options.onProgress) {
433
- options.onProgress(versionIdx, totalVersions, `${fullPage.title} v${versionInfo.number}`)
545
+ yield* options.onProgress(versionIdx, totalVersions, `${fullPage.title} v${versionInfo.number}`)
434
546
  }
435
547
 
436
548
  // Check if body content is available from the versions list
437
- const bodyContent = versionInfo.page?.body?.storage?.value
549
+ const bodyContent = versionInfo.page?.body?.atlas_doc_format?.value
438
550
  if (!bodyContent) {
439
551
  // No body content available - history replay not supported
440
552
  historyReplayFailed = true
@@ -448,13 +560,14 @@ export const layer: Layer.Layer<
448
560
  createdAt: versionInfo.createdAt,
449
561
  message: versionInfo.message,
450
562
  body: {
451
- storage: {
563
+ atlas_doc_format: {
452
564
  value: bodyContent,
453
- representation: "storage" as const
565
+ representation: "atlas_doc_format" as const
454
566
  }
455
567
  }
456
568
  }
457
- const { frontMatter, markdown } = yield* versionToMarkdown(
569
+ const { frontMatter, prepared } = yield* versionToMarkdown(
570
+ filePath,
458
571
  pageId,
459
572
  versionContent,
460
573
  versionInfo.page?.title ?? fullPage.title,
@@ -463,12 +576,12 @@ export const layer: Layer.Layer<
463
576
  )
464
577
 
465
578
  // Write file
466
- yield* localFs.writeMarkdownFile(filePath, frontMatter, markdown)
579
+ yield* writePreparedMarkdownWithAdfMetadata(filePath, frontMatter, prepared)
467
580
 
468
- // Save source HTML if configured
469
- if (config.saveSource && versionContent.body?.storage?.value) {
470
- const sourceFilePath = filePath.replace(/\.md$/, ".html")
471
- yield* localFs.writeFile(sourceFilePath, versionContent.body.storage.value)
581
+ // Save source ADF JSON if configured
582
+ if (config.saveSource && versionContent.body?.atlas_doc_format?.value) {
583
+ const sourceFilePath = filePath.replace(/\.md$/, ".source.json")
584
+ yield* localFs.writeFile(sourceFilePath, prettyAdf(versionContent.body.atlas_doc_format.value))
472
585
  }
473
586
 
474
587
  // Commit this version
@@ -501,10 +614,8 @@ export const layer: Layer.Layer<
501
614
  }
502
615
 
503
616
  // Simple pull without history replay
504
- const htmlContent = fullPage.body?.storage?.value ?? ""
505
- let markdown = yield* converter.htmlToMarkdown(htmlContent, {
506
- includeRawSource: config.saveSource
507
- })
617
+ const adfJson = fullPage.body?.atlas_doc_format?.value ?? ""
618
+ let rawMarkdown = yield* converter.adfToMarkdown(adfJson)
508
619
 
509
620
  // Add child page links for index pages
510
621
  if (hasChildren && config.spaceKey) {
@@ -514,10 +625,12 @@ export const layer: Layer.Layer<
514
625
  return `- [${child.title}](${pageUrl})`
515
626
  })
516
627
  .join("\n")
517
- markdown = markdown.trim() + "\n\n## Child Pages\n\n" + childLinks + "\n"
628
+ rawMarkdown = rawMarkdown.trim() + "\n\n## Child Pages\n\n" + childLinks + "\n"
518
629
  }
519
630
 
520
- const contentHash = yield* computeHash(markdown).pipe(Effect.provide(HashServiceLive))
631
+ const prepared = prepareMarkdownForFile(filePath, rawMarkdown, pageId)
632
+ const { markdown } = prepared
633
+ const contentHash = yield* hashBody(markdown)
521
634
 
522
635
  // Get author info
523
636
  const author = fullPage.version.authorId ? yield* getUser(fullPage.version.authorId) : undefined
@@ -536,12 +649,12 @@ export const layer: Layer.Layer<
536
649
  ...(author?.email ? { authorEmail: author.email } : {})
537
650
  }
538
651
 
539
- yield* localFs.writeMarkdownFile(filePath, frontMatter, markdown)
652
+ yield* writePreparedMarkdownWithAdfMetadata(filePath, frontMatter, prepared)
540
653
 
541
- // Save source HTML if configured
542
- if (config.saveSource && htmlContent) {
543
- const sourceFilePath = filePath.replace(/\.md$/, ".html")
544
- yield* localFs.writeFile(sourceFilePath, htmlContent)
654
+ // Save source ADF JSON if configured
655
+ if (config.saveSource && adfJson) {
656
+ const sourceFilePath = filePath.replace(/\.md$/, ".source.json")
657
+ yield* localFs.writeFile(sourceFilePath, prettyAdf(adfJson))
545
658
  }
546
659
  }
547
660
 
@@ -558,7 +671,9 @@ export const layer: Layer.Layer<
558
671
  return { pulled: 1 + childPulled, commits: totalCommits + childCommits }
559
672
  })
560
673
 
561
- const pull = (options: PullOptions): Effect.Effect<PullResult, SyncError> =>
674
+ const pull = (
675
+ options: PullOptions
676
+ ): Effect.Effect<PullResult, SyncError | PlatformError.PlatformError, Terminal.Terminal> =>
562
677
  Effect.gen(function*() {
563
678
  yield* localFs.ensureDir(docsPath)
564
679
 
@@ -576,31 +691,55 @@ export const layer: Layer.Layer<
576
691
  yield* git.checkout("origin/confluence")
577
692
  }
578
693
 
579
- const rootPage = yield* client.getPage(config.rootPageId)
580
- const result = yield* pullPage(rootPage, docsPath, options, gitInitialized)
694
+ const pullAndMerge = Effect.gen(function*() {
695
+ const rootPage = yield* client.getPage(config.rootPageId)
696
+ const result = yield* pullPage(rootPage, docsPath, options, gitInitialized)
581
697
 
582
- // If git is initialized and we have changes but didn't replay history, auto-commit
583
- if (gitInitialized && !options.replayHistory && result.pulled > 0) {
584
- yield* git.addAll()
585
- yield* git.commit({
586
- message: `Pull from Confluence (${result.pulled} page${result.pulled !== 1 ? "s" : ""})`
587
- }).pipe(Effect.catchTag("GitNoChangesError", () => Effect.void))
588
- }
698
+ // If git is initialized and we have changes but didn't replay history, auto-commit
699
+ if (gitInitialized && !options.replayHistory && result.pulled > 0) {
700
+ yield* git.addAll()
701
+ yield* git.commit({
702
+ message: `Pull from Confluence (${result.pulled} page${result.pulled !== 1 ? "s" : ""})`
703
+ }).pipe(Effect.catchTag("GitNoChangesError", () => Effect.void))
704
+ }
589
705
 
590
- // Two-branch model: merge origin/confluence into current branch
591
- if (hasRemoteBranch && originalBranch && originalBranch !== "origin/confluence") {
592
- yield* git.checkout(originalBranch)
593
- yield* git.merge("origin/confluence", {
594
- message: `Merge remote changes from Confluence`
595
- }).pipe(Effect.catchAll(() => Effect.void)) // May fail if no changes
596
- }
706
+ // Two-branch model: merge origin/confluence into current branch
707
+ if (hasRemoteBranch && originalBranch && originalBranch !== "origin/confluence") {
708
+ yield* git.checkout(originalBranch)
709
+ yield* git.merge("origin/confluence", {
710
+ message: `Merge remote changes from Confluence`
711
+ }).pipe(Effect.catchIf(() => true, () => Effect.void)) // May fail if no changes
712
+ }
597
713
 
598
- return {
599
- pulled: result.pulled,
600
- skipped: 0,
601
- commits: result.commits,
602
- errors: [] as ReadonlyArray<string>
603
- }
714
+ return {
715
+ pulled: result.pulled,
716
+ skipped: 0,
717
+ commits: result.commits,
718
+ errors: [] as ReadonlyArray<string>
719
+ }
720
+ })
721
+
722
+ // Any failure after the origin/confluence checkout (API, conversion,
723
+ // git) must not strand the repo on a detached HEAD — same protection
724
+ // as findUnpushedCommits. On success this re-checkout is a no-op.
725
+ // The restore itself can fail (a half-written pull leaves dirty files
726
+ // that differ between branches); that must be loud, not swallowed.
727
+ return yield* pullAndMerge.pipe(
728
+ Effect.ensuring(
729
+ hasRemoteBranch && originalBranch
730
+ ? git.checkout(originalBranch).pipe(
731
+ Effect.catchCause((cause) =>
732
+ Effect.logWarning(
733
+ `pull: could not restore branch '${originalBranch}' — the repo may still be on origin/confluence. ` +
734
+ `Restore manually with \`git checkout ${originalBranch}\` (stash local changes first if needed). Cause: ${
735
+ String(cause)
736
+ }`
737
+ )
738
+ )
739
+ )
740
+ : Effect.void
741
+ )
742
+ )
604
743
  })
605
744
 
606
745
  /**
@@ -627,22 +766,18 @@ export const layer: Layer.Layer<
627
766
 
628
767
  // For new pages, re-parse front-matter to get title
629
768
  // The localFile only has the content (body), not the original front-matter
630
- const title = yield* Effect.tryPromise({
631
- try: async () => {
632
- const fs = await import("node:fs/promises")
633
- const matter = await import("gray-matter")
634
- const rawFile = await fs.readFile(filePath, "utf-8")
635
- const parsed = matter.default(rawFile)
636
- return (parsed.data as { title?: string }).title ?? baseName
637
- },
638
- catch: (cause) => new FileSystemError({ operation: "read", path: filePath, cause })
639
- })
769
+ const rawFile = yield* fs.readFileString(filePath).pipe(
770
+ Effect.mapError((cause) => new FileSystemError({ operation: "read", path: filePath, cause }))
771
+ )
772
+ const parsed = yield* parseMarkdown(filePath, rawFile)
773
+ const title = parsed.frontMatter?.title ?? baseName
640
774
 
641
775
  // Resolve parent from directory structure
642
776
  const parentId = yield* resolveParent(filePath, pageIdMap)
643
777
 
644
- // Convert markdown to HTML
645
- const html = yield* converter.markdownToHtml(localFile.content)
778
+ // Convert markdown to ADF
779
+ const hydratedContent = yield* hydrateMarkdownForFile(filePath, localFile.content)
780
+ const adfValue = yield* converter.markdownToAdf(hydratedContent)
646
781
 
647
782
  // Create the page
648
783
  const createdPage = yield* client.createPage({
@@ -650,14 +785,14 @@ export const layer: Layer.Layer<
650
785
  parentId,
651
786
  title,
652
787
  body: {
653
- representation: "storage",
654
- value: html
788
+ representation: "atlas_doc_format",
789
+ value: adfValue
655
790
  }
656
791
  })
657
792
 
658
793
  // Set editor version to v2 (new editor)
659
794
  yield* client.setEditorVersion(createdPage.id as PageId, "v2").pipe(
660
- Effect.catchAll((error) => {
795
+ Effect.catchIf(() => true, (error) => {
661
796
  // Log warning but don't fail the push
662
797
  return Effect.logWarning(`Failed to set editor v2 for page ${createdPage.id}: ${error.message}`)
663
798
  })
@@ -665,11 +800,11 @@ export const layer: Layer.Layer<
665
800
 
666
801
  // Fetch canonical content back from Confluence
667
802
  const canonicalPage = yield* client.getPage(createdPage.id as PageId)
668
- const canonicalHtml = canonicalPage.body?.storage?.value ?? ""
669
- const canonicalMarkdown = yield* converter.htmlToMarkdown(canonicalHtml, {
670
- includeRawSource: config.saveSource
671
- })
672
- const canonicalHash = yield* computeHash(canonicalMarkdown).pipe(Effect.provide(HashServiceLive))
803
+ const canonicalAdf = canonicalPage.body?.atlas_doc_format?.value ?? ""
804
+ const rawCanonicalMarkdown = yield* converter.adfToMarkdown(canonicalAdf)
805
+ const preparedCanonicalMarkdown = prepareMarkdownForFile(filePath, rawCanonicalMarkdown, createdPage.id)
806
+ const { markdown: canonicalMarkdown } = preparedCanonicalMarkdown
807
+ const canonicalHash = yield* hashBody(canonicalMarkdown)
673
808
 
674
809
  // Write canonical content with full front-matter
675
810
  const newFrontMatter: PageFrontMatter = {
@@ -680,7 +815,7 @@ export const layer: Layer.Layer<
680
815
  parentId: parentId as PageId,
681
816
  contentHash: canonicalHash
682
817
  }
683
- yield* localFs.writeMarkdownFile(filePath, newFrontMatter, canonicalMarkdown)
818
+ yield* writePreparedMarkdownWithAdfMetadata(filePath, newFrontMatter, preparedCanonicalMarkdown)
684
819
 
685
820
  // Update pageIdMap with new page
686
821
  const key = relativePath.replace(/\.md$/, "")
@@ -698,7 +833,8 @@ export const layer: Layer.Layer<
698
833
 
699
834
  // Fetch current version to avoid conflicts
700
835
  const remotePage = yield* client.getPage(fm.pageId)
701
- const html = yield* converter.markdownToHtml(localFile.content)
836
+ const hydratedContent = yield* hydrateMarkdownForFile(filePath, localFile.content)
837
+ const adfValue = yield* converter.markdownToAdf(hydratedContent)
702
838
  const updatedPage = yield* client.updatePage({
703
839
  id: fm.pageId,
704
840
  title: fm.title,
@@ -708,18 +844,18 @@ export const layer: Layer.Layer<
708
844
  message: revisionMessage
709
845
  },
710
846
  body: {
711
- representation: "storage",
712
- value: html
847
+ representation: "atlas_doc_format",
848
+ value: adfValue
713
849
  }
714
850
  })
715
851
 
716
852
  // Fetch canonical content back from Confluence
717
853
  const canonicalPage = yield* client.getPage(fm.pageId)
718
- const canonicalHtml = canonicalPage.body?.storage?.value ?? ""
719
- const canonicalMarkdown = yield* converter.htmlToMarkdown(canonicalHtml, {
720
- includeRawSource: config.saveSource
721
- })
722
- const canonicalHash = yield* computeHash(canonicalMarkdown).pipe(Effect.provide(HashServiceLive))
854
+ const canonicalAdf = canonicalPage.body?.atlas_doc_format?.value ?? ""
855
+ const rawCanonicalMarkdown = yield* converter.adfToMarkdown(canonicalAdf)
856
+ const preparedCanonicalMarkdown = prepareMarkdownForFile(filePath, rawCanonicalMarkdown, fm.pageId)
857
+ const { markdown: canonicalMarkdown } = preparedCanonicalMarkdown
858
+ const canonicalHash = yield* hashBody(canonicalMarkdown)
723
859
 
724
860
  // Write canonical content with updated front-matter
725
861
  const newFrontMatter: PageFrontMatter = {
@@ -728,7 +864,7 @@ export const layer: Layer.Layer<
728
864
  updated: new Date(canonicalPage.version.createdAt ?? new Date().toISOString()),
729
865
  contentHash: canonicalHash
730
866
  }
731
- yield* localFs.writeMarkdownFile(filePath, newFrontMatter, canonicalMarkdown)
867
+ yield* writePreparedMarkdownWithAdfMetadata(filePath, newFrontMatter, preparedCanonicalMarkdown)
732
868
 
733
869
  return { pushed: true, created: false }
734
870
  })
@@ -759,34 +895,45 @@ export const layer: Layer.Layer<
759
895
  const allCommits = yield* git.log({ n: 100 })
760
896
  if (allCommits.length === 0) return []
761
897
 
762
- const unpushed: Array<{ hash: string; message: string }> = []
763
-
764
- for (const commit of allCommits) {
765
- yield* git.checkout(commit.hash)
766
-
767
- let hasChanges = false
768
- for (const filePath of files) {
769
- const exists = yield* localFs.exists(filePath)
770
- if (!exists) continue
898
+ // We checkout each commit to compare content; restore HEAD before returning
899
+ // so subsequent pushFile() calls see the working-tree content the user staged,
900
+ // not whatever commit we last inspected.
901
+ const originalBranch = yield* git.getCurrentBranch()
902
+
903
+ const collect = Effect.gen(function*() {
904
+ const unpushed: Array<{ hash: string; message: string }> = []
905
+ for (const commit of allCommits) {
906
+ yield* git.checkout(commit.hash)
907
+
908
+ let hasChanges = false
909
+ for (const filePath of files) {
910
+ const exists = yield* localFs.exists(filePath)
911
+ if (!exists) continue
912
+
913
+ const localFile = yield* localFs.readMarkdownFile(filePath)
914
+ if (!localFile.frontMatter) {
915
+ hasChanges = true
916
+ break
917
+ }
771
918
 
772
- const localFile = yield* localFs.readMarkdownFile(filePath)
773
- if (!localFile.frontMatter) {
774
- hasChanges = true
775
- break
919
+ const currentHash = yield* computeHash(localFile.content).pipe(Effect.provide(HashServiceLive))
920
+ if (currentHash !== localFile.frontMatter.contentHash) {
921
+ hasChanges = true
922
+ break
923
+ }
776
924
  }
777
925
 
778
- const currentHash = yield* computeHash(localFile.content).pipe(Effect.provide(HashServiceLive))
779
- if (currentHash !== localFile.frontMatter.contentHash) {
780
- hasChanges = true
781
- break
782
- }
926
+ if (!hasChanges) break
927
+ unpushed.push({ hash: commit.hash, message: commit.message })
783
928
  }
929
+ return unpushed.reverse()
930
+ })
784
931
 
785
- if (!hasChanges) break
786
- unpushed.push({ hash: commit.hash, message: commit.message })
787
- }
788
-
789
- return unpushed.reverse()
932
+ return yield* collect.pipe(
933
+ Effect.ensuring(
934
+ originalBranch ? git.checkout(originalBranch).pipe(Effect.ignore) : Effect.void
935
+ )
936
+ )
790
937
  })
791
938
 
792
939
  const push = (options: { dryRun: boolean; message?: string }): Effect.Effect<PushResult, SyncError> =>
@@ -827,13 +974,12 @@ export const layer: Layer.Layer<
827
974
  spaceId,
828
975
  pageIdMap
829
976
  ).pipe(
830
- Effect.catchAll((error) =>
977
+ Effect.catchIf(() => true, (error) =>
831
978
  Effect.succeed({
832
979
  pushed: false,
833
980
  created: false,
834
981
  error: `Failed: ${error._tag}`
835
- })
836
- )
982
+ }))
837
983
  )
838
984
  if (result.error) errors.push(result.error)
839
985
  if (result.pushed) pushed++
@@ -890,13 +1036,13 @@ export const layer: Layer.Layer<
890
1036
  const match = content.match(/pageId:\s*['"]?(\d+)['"]?/)
891
1037
  return match ? match[1] : null
892
1038
  }),
893
- Effect.catchAll(() => Effect.succeed(null))
1039
+ Effect.catchIf(() => true, () => Effect.succeed(null))
894
1040
  )
895
1041
 
896
1042
  if (pageIdFromOrigin) {
897
1043
  yield* client.deletePage(PageId(pageIdFromOrigin)).pipe(
898
1044
  Effect.tap(() => Effect.sync(() => deleted++)),
899
- Effect.catchAll((error) => {
1045
+ Effect.catchIf(() => true, (error) => {
900
1046
  errors.push(`Failed to delete page ${pageIdFromOrigin}: ${error.message}`)
901
1047
  return Effect.void
902
1048
  })
@@ -907,13 +1053,12 @@ export const layer: Layer.Layer<
907
1053
 
908
1054
  for (const filePath of sortedFiles) {
909
1055
  const result = yield* pushFile(filePath, revisionMessage, spaceId, pageIdMap).pipe(
910
- Effect.catchAll((error) =>
1056
+ Effect.catchIf(() => true, (error) =>
911
1057
  Effect.succeed({
912
1058
  pushed: false,
913
1059
  created: false,
914
1060
  error: `Failed to push ${filePath}: ${error._tag}`
915
- })
916
- )
1061
+ }))
917
1062
  )
918
1063
  if (result.error) errors.push(result.error)
919
1064
  if (result.pushed) pushed++
@@ -923,7 +1068,7 @@ export const layer: Layer.Layer<
923
1068
  // Amend the last commit with canonical content
924
1069
  yield* git.addAll()
925
1070
  yield* git.amend({ noEdit: true }).pipe(
926
- Effect.catchAll(() => Effect.void)
1071
+ Effect.catchIf(() => true, () => Effect.void)
927
1072
  )
928
1073
 
929
1074
  // Two-branch model: update origin/confluence to match HEAD
@@ -959,15 +1104,15 @@ export const layer: Layer.Layer<
959
1104
  const currentHash = yield* computeHash(localFile.content).pipe(Effect.provide(HashServiceLive))
960
1105
 
961
1106
  // Fetch remote page
962
- const remotePage = yield* Effect.either(client.getPage(fm.pageId))
1107
+ const remotePage = yield* Effect.result(client.getPage(fm.pageId))
963
1108
 
964
- if (remotePage._tag === "Left") {
1109
+ if (Result.isFailure(remotePage)) {
965
1110
  statuses.push({ _tag: "LocalOnly", path: filePath, title: fm.title })
966
1111
  localOnly++
967
1112
  continue
968
1113
  }
969
1114
 
970
- const page = remotePage.right
1115
+ const page = remotePage.success
971
1116
  const localChanged = currentHash !== fm.contentHash
972
1117
  const remoteChanged = page.version.number > fm.version
973
1118