@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
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Layer definitions for CLI commands.
3
3
  */
4
- import * as NodeContext from "@effect/platform-node/NodeContext"
5
4
  import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient"
5
+ import * as NodeServices from "@effect/platform-node/NodeServices"
6
6
  import * as NodeTerminal from "@effect/platform-node/NodeTerminal"
7
7
  import * as Effect from "effect/Effect"
8
8
  import * as Layer from "effect/Layer"
9
+ import { layer as AdfSchemaValidatorLayer } from "../AdfSchemaValidator.js"
10
+ import { layer as AtlaskitTransformersLayer } from "../AtlaskitTransformers.js"
9
11
  import type { PageId } from "../Brand.js"
10
12
  import { ConfluenceAuth, layer as ConfluenceAuthLayer } from "../ConfluenceAuth.js"
11
13
  import { ConfluenceClient, type ConfluenceClientConfig, layer as ConfluenceClientLayer } from "../ConfluenceClient.js"
@@ -21,6 +23,11 @@ import { layer as MarkdownConverterLayer } from "../MarkdownConverter.js"
21
23
  import { layer as SyncEngineLayer, SyncEngine } from "../SyncEngine.js"
22
24
  import { getAuth } from "./shared.js"
23
25
 
26
+ const ConverterPipeline = MarkdownConverterLayer.pipe(
27
+ Layer.provide(AtlaskitTransformersLayer),
28
+ Layer.provide(AdfSchemaValidatorLayer)
29
+ )
30
+
24
31
  // Dummy config layer for help/init
25
32
  const DummyConfigLayer = ConfluenceConfigLayerFromValues({
26
33
  rootPageId: "dummy" as PageId,
@@ -35,16 +42,16 @@ const DummyConfigLayer = ConfluenceConfigLayerFromValues({
35
42
  const DummyConfluenceClientLayer = Layer.succeed(
36
43
  ConfluenceClient,
37
44
  ConfluenceClient.of({
38
- getPage: () => Effect.dieMessage("Not configured"),
39
- getChildren: () => Effect.dieMessage("Not configured"),
40
- getAllChildren: () => Effect.dieMessage("Not configured"),
41
- createPage: () => Effect.dieMessage("Not configured"),
42
- updatePage: () => Effect.dieMessage("Not configured"),
43
- deletePage: () => Effect.dieMessage("Not configured"),
44
- getPageVersions: () => Effect.dieMessage("Not configured"),
45
- getUser: () => Effect.dieMessage("Not configured"),
46
- getSpaceId: () => Effect.dieMessage("Not configured"),
47
- setEditorVersion: () => Effect.dieMessage("Not configured")
45
+ getPage: () => Effect.die("Not configured"),
46
+ getChildren: () => Effect.die("Not configured"),
47
+ getAllChildren: () => Effect.die("Not configured"),
48
+ createPage: () => Effect.die("Not configured"),
49
+ updatePage: () => Effect.die("Not configured"),
50
+ deletePage: () => Effect.die("Not configured"),
51
+ getPageVersions: () => Effect.die("Not configured"),
52
+ getUser: () => Effect.die("Not configured"),
53
+ getSpaceId: () => Effect.die("Not configured"),
54
+ setEditorVersion: () => Effect.die("Not configured")
48
55
  })
49
56
  )
50
57
 
@@ -52,15 +59,15 @@ const DummyConfluenceClientLayer = Layer.succeed(
52
59
  const DummySyncEngineLayer = Layer.succeed(
53
60
  SyncEngine,
54
61
  SyncEngine.of({
55
- pull: () => Effect.dieMessage("Not configured - run 'confluence clone' first"),
62
+ pull: () => Effect.die("Not configured - run 'confluence clone' first"),
56
63
  push: (_options: { dryRun: boolean; message?: string }) =>
57
- Effect.dieMessage("Not configured - run 'confluence clone' first"),
58
- status: () => Effect.dieMessage("Not configured - run 'confluence clone' first")
64
+ Effect.die("Not configured - run 'confluence clone' first"),
65
+ status: () => Effect.die("Not configured - run 'confluence clone' first")
59
66
  })
60
67
  )
61
68
 
62
69
  // Dummy git layer for auth/minimal
63
- const notConfigured = () => Effect.dieMessage("Not configured - run 'confluence clone' first")
70
+ const notConfigured = () => Effect.die("Not configured - run 'confluence clone' first")
64
71
  const DummyGitServiceLayer = Layer.succeed(
65
72
  GitService,
66
73
  GitService.of({
@@ -100,22 +107,22 @@ const DummyGitServiceLayer = Layer.succeed(
100
107
  const DummyConfluenceAuthLayer = Layer.succeed(
101
108
  ConfluenceAuth,
102
109
  ConfluenceAuth.of({
103
- configure: () => Effect.dieMessage("Not configured"),
110
+ configure: () => Effect.die("Not configured"),
104
111
  isConfigured: () => Effect.succeed(false),
105
- login: () => Effect.dieMessage("Not configured"),
106
- logout: () => Effect.dieMessage("Not configured"),
107
- getAccessToken: () => Effect.dieMessage("Not configured"),
108
- getCloudId: () => Effect.dieMessage("Not configured"),
112
+ login: () => Effect.die("Not configured"),
113
+ logout: () => Effect.die("Not configured"),
114
+ getAccessToken: () => Effect.die("Not configured"),
115
+ getCloudId: () => Effect.die("Not configured"),
109
116
  getCurrentUser: () => Effect.succeed(null),
110
117
  isLoggedIn: () => Effect.succeed(false)
111
118
  })
112
119
  )
113
120
 
114
121
  // Auth layer with HTTP client
115
- const AuthLive = ConfluenceAuthLayer.pipe(Layer.provide(NodeHttpClient.layer))
122
+ const AuthLive = ConfluenceAuthLayer.pipe(Layer.provide(NodeHttpClient.layerFetch))
116
123
 
117
124
  // Build client layer dynamically based on auth
118
- const ConfluenceClientLive = Layer.unwrapEffect(
125
+ const ConfluenceClientLive = Layer.unwrap(
119
126
  Effect.gen(function*() {
120
127
  const auth = yield* getAuth()
121
128
  const config = yield* ConfluenceConfig
@@ -136,12 +143,12 @@ export const AppLayer = SyncEngineLayer.pipe(
136
143
  Layer.provideMerge(UserCacheLayer),
137
144
  Layer.provideMerge(GitServiceLayer),
138
145
  Layer.provideMerge(ConfluenceClientLive),
139
- Layer.provideMerge(MarkdownConverterLayer),
146
+ Layer.provideMerge(ConverterPipeline),
140
147
  Layer.provideMerge(LocalFileSystemLayer),
141
148
  Layer.provideMerge(ConfluenceConfigLayer()),
142
149
  Layer.provideMerge(AuthLive),
143
- Layer.provideMerge(NodeHttpClient.layer),
144
- Layer.provideMerge(NodeContext.layer)
150
+ Layer.provideMerge(NodeHttpClient.layerFetch),
151
+ Layer.provideMerge(NodeServices.layer)
145
152
  )
146
153
 
147
154
  /**
@@ -152,11 +159,11 @@ export const AuthOnlyLayer = DummySyncEngineLayer.pipe(
152
159
  Layer.provideMerge(DummyGitServiceLayer),
153
160
  Layer.provideMerge(DummyConfluenceClientLayer),
154
161
  Layer.provideMerge(AuthLive),
155
- Layer.provideMerge(MarkdownConverterLayer),
162
+ Layer.provideMerge(ConverterPipeline),
156
163
  Layer.provideMerge(LocalFileSystemLayer),
157
164
  Layer.provideMerge(DummyConfigLayer),
158
- Layer.provideMerge(NodeHttpClient.layer),
159
- Layer.provideMerge(NodeContext.layer)
165
+ Layer.provideMerge(NodeHttpClient.layerFetch),
166
+ Layer.provideMerge(NodeServices.layer)
160
167
  )
161
168
 
162
169
  /**
@@ -167,11 +174,11 @@ export const MinimalLayer = DummySyncEngineLayer.pipe(
167
174
  Layer.provideMerge(GitServiceLayer),
168
175
  Layer.provideMerge(DummyConfluenceClientLayer),
169
176
  Layer.provideMerge(DummyConfluenceAuthLayer),
170
- Layer.provideMerge(MarkdownConverterLayer),
177
+ Layer.provideMerge(ConverterPipeline),
171
178
  Layer.provideMerge(LocalFileSystemLayer),
172
179
  Layer.provideMerge(DummyConfigLayer),
173
180
  Layer.provideMerge(NodeTerminal.layer),
174
- Layer.provideMerge(NodeContext.layer)
181
+ Layer.provideMerge(NodeServices.layer)
175
182
  )
176
183
 
177
184
  /**
@@ -182,19 +189,35 @@ export const CloneLayer = DummySyncEngineLayer.pipe(
182
189
  Layer.provideMerge(GitServiceLayer),
183
190
  Layer.provideMerge(DummyConfluenceClientLayer),
184
191
  Layer.provideMerge(AuthLive),
185
- Layer.provideMerge(MarkdownConverterLayer),
192
+ Layer.provideMerge(ConverterPipeline),
193
+ Layer.provideMerge(LocalFileSystemLayer),
194
+ Layer.provideMerge(DummyConfigLayer),
195
+ Layer.provideMerge(NodeHttpClient.layerFetch),
196
+ Layer.provideMerge(NodeTerminal.layer),
197
+ Layer.provideMerge(NodeServices.layer)
198
+ )
199
+
200
+ /**
201
+ * Fetch layer - needs auth + converter but no config, sync engine, or git workspace.
202
+ */
203
+ export const FetchLayer = DummySyncEngineLayer.pipe(
204
+ Layer.provideMerge(UserCacheLayer),
205
+ Layer.provideMerge(DummyGitServiceLayer),
206
+ Layer.provideMerge(DummyConfluenceClientLayer),
207
+ Layer.provideMerge(AuthLive),
208
+ Layer.provideMerge(ConverterPipeline),
186
209
  Layer.provideMerge(LocalFileSystemLayer),
187
210
  Layer.provideMerge(DummyConfigLayer),
188
- Layer.provideMerge(NodeHttpClient.layer),
211
+ Layer.provideMerge(NodeHttpClient.layerFetch),
189
212
  Layer.provideMerge(NodeTerminal.layer),
190
- Layer.provideMerge(NodeContext.layer)
213
+ Layer.provideMerge(NodeServices.layer)
191
214
  )
192
215
 
193
216
  /**
194
217
  * Determine which layer to use based on command.
195
218
  */
196
- export const getLayerType = (): "full" | "auth" | "clone" | "minimal" => {
197
- const cmd = process.argv[2]
219
+ export const getLayerType = (argv: ReadonlyArray<string>): "full" | "auth" | "clone" | "fetch" | "minimal" => {
220
+ const cmd = argv[0]
198
221
  // auth commands need auth layer only
199
222
  if (cmd === "auth") {
200
223
  return "auth"
@@ -203,8 +226,12 @@ export const getLayerType = (): "full" | "auth" | "clone" | "minimal" => {
203
226
  if (cmd === "clone") {
204
227
  return "clone"
205
228
  }
206
- // --help, -h, --version don't need config
207
- if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "--version") {
229
+ // fetch needs auth + converter but no existing config
230
+ if (cmd === "fetch") {
231
+ return "fetch"
232
+ }
233
+ // skills/help/version don't need config
234
+ if (!cmd || cmd === "skills" || cmd === "--help" || cmd === "-h" || cmd === "--version") {
208
235
  return "minimal"
209
236
  }
210
237
  return "full"
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * New page command for Confluence CLI.
3
3
  */
4
- import { Command, Prompt } from "@effect/cli"
5
- import * as Path from "@effect/platform/Path"
6
4
  import * as Console from "effect/Console"
7
5
  import * as Effect from "effect/Effect"
6
+ import * as Path from "effect/Path"
7
+ import { Command, Prompt } from "effect/unstable/cli"
8
8
  import { ConfluenceConfig } from "../ConfluenceConfig.js"
9
9
  import { LocalFileSystem } from "../LocalFileSystem.js"
10
10
  import { flattenPageTree } from "./pageTree.js"
@@ -24,7 +24,8 @@ export const newCommand = Command.make("new", {}, () =>
24
24
  const config = yield* ConfluenceConfig
25
25
  const pathService = yield* Path.Path
26
26
 
27
- const docsPath = pathService.join(process.cwd(), config.docsPath)
27
+ const cwd = pathService.resolve(".")
28
+ const docsPath = pathService.join(cwd, config.docsPath)
28
29
 
29
30
  // Build page tree
30
31
  yield* Console.log("Scanning page structure...")
@@ -89,7 +90,7 @@ export const newCommand = Command.make("new", {}, () =>
89
90
  "\n<!-- Write your page content here -->\n"
90
91
  )
91
92
 
92
- const relativePath = pathService.relative(process.cwd(), filePath)
93
+ const relativePath = pathService.relative(cwd, filePath)
93
94
  yield* Console.log(`Created new page: ${relativePath}`)
94
95
  yield* Console.log("")
95
96
  yield* Console.log("Next steps:")
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Shared page input parsing for commands that accept Confluence page IDs.
3
+ */
4
+ import * as Effect from "effect/Effect"
5
+ import { ConfigError } from "../ConfluenceError.js"
6
+
7
+ export interface PageInput {
8
+ readonly url?: string | undefined
9
+ readonly pageId?: string | undefined
10
+ readonly baseUrl?: string | undefined
11
+ }
12
+
13
+ export interface ResolvedPageInput {
14
+ readonly pageId: string
15
+ readonly baseUrl: string
16
+ }
17
+
18
+ const isSupportedHost = (host: string): boolean => /^[a-z0-9-]+\.atlassian\.(?:net|com)$/.test(host)
19
+
20
+ const isNumericPageId = (segment: string): boolean => /^[0-9]+$/.test(segment)
21
+
22
+ export const validatePageId = (input: string): Effect.Effect<string, ConfigError> => {
23
+ const pageId = input.trim()
24
+ return pageId.length > 0 && isNumericPageId(pageId)
25
+ ? Effect.succeed(pageId)
26
+ : Effect.fail(new ConfigError({ message: `Invalid Confluence page ID: ${input}` }))
27
+ }
28
+
29
+ export const validateBaseUrl = (input: string): Effect.Effect<string, ConfigError> =>
30
+ Effect.gen(function*() {
31
+ const url = yield* Effect.try({
32
+ try: () => new URL(input.trim()),
33
+ catch: () => new ConfigError({ message: `Invalid Confluence URL: ${input}` })
34
+ })
35
+ if (url.protocol !== "https:" || url.pathname !== "/" || !isSupportedHost(url.host)) {
36
+ return yield* Effect.fail(
37
+ new ConfigError({
38
+ message: `Invalid Confluence URL: ${input}. Expected format: https://yoursite.atlassian.net`
39
+ })
40
+ )
41
+ }
42
+ return `${url.protocol}//${url.host}`
43
+ })
44
+
45
+ export const parseConfluencePageUrl = (input: string): Effect.Effect<ResolvedPageInput, ConfigError> =>
46
+ Effect.gen(function*() {
47
+ const url = yield* Effect.try({
48
+ try: () => new URL(input.trim()),
49
+ catch: () => new ConfigError({ message: `Invalid Confluence page URL: ${input}` })
50
+ })
51
+
52
+ if (url.protocol !== "https:" || !isSupportedHost(url.host)) {
53
+ return yield* Effect.fail(
54
+ new ConfigError({
55
+ message: `Unsupported Confluence page URL: ${input}. Expected an https Atlassian Cloud URL.`
56
+ })
57
+ )
58
+ }
59
+
60
+ const segments = url.pathname.split("/").filter((segment) => segment.length > 0)
61
+ const pagesIndex = segments.indexOf("pages")
62
+ const pageIdFromPages = pagesIndex >= 0 ? segments[pagesIndex + 1] : undefined
63
+ const pageId = pagesIndex >= 0
64
+ ? pageIdFromPages && isNumericPageId(pageIdFromPages) ? pageIdFromPages : undefined
65
+ : segments.find(isNumericPageId)
66
+
67
+ if (!pageId) {
68
+ return yield* Effect.fail(new ConfigError({ message: `Could not find a page ID in URL: ${input}` }))
69
+ }
70
+
71
+ return {
72
+ pageId,
73
+ baseUrl: `${url.protocol}//${url.host}`
74
+ }
75
+ })
76
+
77
+ export const resolvePageInput = (input: PageInput): Effect.Effect<ResolvedPageInput, ConfigError> =>
78
+ Effect.gen(function*() {
79
+ const url = input.url?.trim()
80
+ const pageId = input.pageId?.trim()
81
+ const baseUrl = input.baseUrl?.trim()
82
+
83
+ if (url && (pageId || baseUrl)) {
84
+ return yield* Effect.fail(
85
+ new ConfigError({ message: "Use either --url or --page-id/--base-url, not both." })
86
+ )
87
+ }
88
+
89
+ if (url) {
90
+ return yield* parseConfluencePageUrl(url)
91
+ }
92
+
93
+ if (!pageId || !baseUrl) {
94
+ return yield* Effect.fail(
95
+ new ConfigError({ message: "Both --page-id and --base-url are required when --url is not provided." })
96
+ )
97
+ }
98
+
99
+ return {
100
+ pageId: yield* validatePageId(pageId),
101
+ baseUrl: yield* validateBaseUrl(baseUrl)
102
+ }
103
+ })
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Root CLI command composition.
3
+ */
4
+ import { makeInstallCommand } from "@knpkv/agent-skills"
5
+ import * as Console from "effect/Console"
6
+ import { Command } from "effect/unstable/cli"
7
+ import {
8
+ authCommand,
9
+ cloneCommand,
10
+ commitCommand,
11
+ deleteCommand,
12
+ diffCommand,
13
+ fetchCommand,
14
+ logCommand,
15
+ newCommand,
16
+ pullCommand,
17
+ pushCommand,
18
+ statusCommand
19
+ } from "./index.js"
20
+
21
+ export interface ConfluenceCommandOptions {
22
+ readonly fetch?: typeof fetchCommand
23
+ }
24
+
25
+ const skillsInstall = makeInstallCommand({
26
+ description: "Install the Confluence agent skill",
27
+ name: "install",
28
+ skills: ["confluence"]
29
+ })
30
+
31
+ const skillsCommand = Command.make(
32
+ "skills",
33
+ {},
34
+ () => Console.log("Usage: confluence skills install")
35
+ ).pipe(
36
+ Command.withDescription("Agent skill commands"),
37
+ Command.withSubcommands([skillsInstall])
38
+ )
39
+
40
+ export const makeConfluenceCommand = (options: ConfluenceCommandOptions = {}) =>
41
+ Command.make("confluence").pipe(
42
+ Command.withDescription("Sync Confluence pages to local markdown"),
43
+ Command.withSubcommands([
44
+ cloneCommand,
45
+ authCommand,
46
+ pullCommand,
47
+ pushCommand,
48
+ statusCommand,
49
+ commitCommand,
50
+ logCommand,
51
+ diffCommand,
52
+ options.fetch ?? fetchCommand,
53
+ newCommand,
54
+ deleteCommand,
55
+ skillsCommand
56
+ ])
57
+ )
58
+
59
+ export const confluenceCommand = makeConfluenceCommand()
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Sync commands (pull, push, status) for Confluence CLI.
3
3
  */
4
- import { Command, Options } from "@effect/cli"
5
4
  import * as Console from "effect/Console"
6
5
  import * as Effect from "effect/Effect"
6
+ import { Command, Flag as Options } from "effect/unstable/cli"
7
7
  import { GitService } from "../GitService.js"
8
+ import { writeStdout } from "../internal/stdio.js"
9
+ import type { ProgressCallback } from "../SyncEngine.js"
8
10
  import { SyncEngine } from "../SyncEngine.js"
9
11
 
10
12
  // === Pull command ===
@@ -24,16 +26,15 @@ export const pullCommand = Command.make(
24
26
  Effect.gen(function*() {
25
27
  const engine = yield* SyncEngine
26
28
  yield* Console.log("Pulling pages from Confluence...")
27
- const onProgress = (current: number, total: number, message: string) => {
28
- process.stdout.write(`\r Replaying history: ${current}/${total} - ${message}`)
29
- }
29
+ const onProgress: ProgressCallback = (current, total, message) =>
30
+ writeStdout(`\r Replaying history: ${current}/${total} - ${message}`)
30
31
  const result = yield* engine.pull({
31
32
  force,
32
33
  replayHistory,
33
34
  ...(replayHistory ? { onProgress } : {})
34
35
  })
35
36
  if (replayHistory) {
36
- process.stdout.write("\r" + " ".repeat(80) + "\r")
37
+ yield* writeStdout("\r" + " ".repeat(80) + "\r")
37
38
  }
38
39
  yield* Console.log(`Pulled ${result.pulled} pages`)
39
40
  if (result.commits > 0) {
@@ -91,7 +92,7 @@ export const statusCommand = Command.make("status", {}, () =>
91
92
  const gitStatus = yield* git.status()
92
93
  const commitCount = yield* git.log({ n: 1 }).pipe(
93
94
  Effect.map((commits) => commits.length > 0 ? "has commits" : "no commits"),
94
- Effect.catchAll(() => Effect.succeed("unknown"))
95
+ Effect.catchIf(() => true, () => Effect.succeed("unknown"))
95
96
  )
96
97
  yield* Console.log(`Git: initialized (${commitCount})`)
97
98
  if (gitStatus.hasChanges) {
package/src/index.ts CHANGED
@@ -6,6 +6,9 @@
6
6
  * @packageDocumentation
7
7
  */
8
8
 
9
+ export { AdfSchemaValidator, layer as AdfSchemaValidatorLayer } from "./AdfSchemaValidator.js"
10
+ export { type WalkerWarning, type WalkResult } from "./AdfWalker.js"
11
+ export { AtlaskitTransformers, layer as AtlaskitTransformersLayer, type Transformers } from "./AtlaskitTransformers.js"
9
12
  export * from "./Brand.js"
10
13
  export { ConfluenceAuth, type ConfluenceAuthService, layer as ConfluenceAuthLayer } from "./ConfluenceAuth.js"
11
14
  export {
@@ -35,21 +38,3 @@ export {
35
38
  SyncEngine,
36
39
  type SyncStatus
37
40
  } from "./SyncEngine.js"
38
-
39
- // AST types
40
- export * from "./ast/index.js"
41
-
42
- // Parsers
43
- export { parseConfluenceHtml } from "./parsers/ConfluenceParser.js"
44
- export { parseMarkdown } from "./parsers/MarkdownParser.js"
45
-
46
- // Serializers
47
- export { serializeToConfluence } from "./serializers/ConfluenceSerializer.js"
48
- export { serializeToMarkdown } from "./serializers/MarkdownSerializer.js"
49
-
50
- // Bi-directional schemas
51
- export { DocumentFromConfluence } from "./schemas/ConfluenceSchema.js"
52
- export { DocumentFromMarkdown } from "./schemas/MarkdownSchema.js"
53
-
54
- // Schema converter errors
55
- export { MigrationError, ParseError, SerializeError } from "./SchemaConverterError.js"
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Node.js-specific layer implementations.
3
3
  *
4
- * This is the ONLY file that should import directly from node:* modules.
5
- * All other code should use Effect platform abstractions.
4
+ * This file wires package-specific Node runtime layers.
6
5
  *
7
6
  * @module
8
7
  * @internal
@@ -0,0 +1,145 @@
1
+ /**
2
+ * External sidecar storage for ADF placeholder metadata.
3
+ *
4
+ * Markdown stays readable by replacing large `attrs={...}` / `node={...}`
5
+ * blobs with `ref=./page.adf.json#id`; the sidecar stores decoded JSON.
6
+ *
7
+ * @module
8
+ */
9
+ import * as Schema from "effect/Schema"
10
+
11
+ export type AdfMetadataKind = "attrs" | "marks" | "node"
12
+
13
+ export const AdfMetadataEntrySchema = Schema.Struct({
14
+ kind: Schema.Literals(["attrs", "marks", "node"]),
15
+ value: Schema.Unknown
16
+ })
17
+
18
+ export const AdfMetadataSidecarSchema = Schema.Struct({
19
+ version: Schema.Literal(1),
20
+ entries: Schema.Record(Schema.String, AdfMetadataEntrySchema)
21
+ })
22
+
23
+ export type AdfMetadataEntry = typeof AdfMetadataEntrySchema.Type
24
+ export type AdfMetadataSidecar = typeof AdfMetadataSidecarSchema.Type
25
+
26
+ const stableStringify = (v: unknown): string => {
27
+ if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`
28
+ if (v !== null && typeof v === "object") {
29
+ const entries = Object.entries(v as Record<string, unknown>)
30
+ .filter(([, value]) => value !== undefined)
31
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
32
+ .map(([k, value]) => `${JSON.stringify(k)}:${stableStringify(value)}`)
33
+ return `{${entries.join(",")}}`
34
+ }
35
+ return JSON.stringify(v) ?? "null"
36
+ }
37
+
38
+ const markerKinds: ReadonlyArray<AdfMetadataKind> = ["node", "attrs", "marks"]
39
+
40
+ const markerType = (line: string): string => {
41
+ const match = /<!--\s*adf:([A-Za-z][A-Za-z0-9]*)/.exec(line)
42
+ return match?.[1] ?? "metadata"
43
+ }
44
+
45
+ const fromBase64 = (b64: string): string => {
46
+ const bin = atob(b64)
47
+ return new TextDecoder().decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)))
48
+ }
49
+
50
+ const parseMetadataValue = (raw: string): unknown | null => {
51
+ const trimmed = raw.trim()
52
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
53
+ try {
54
+ return JSON.parse(trimmed) as unknown
55
+ } catch {
56
+ return null
57
+ }
58
+ }
59
+
60
+ try {
61
+ const decoded = fromBase64(trimmed)
62
+ if (!decoded.startsWith("{") && !decoded.startsWith("[")) return null
63
+ return JSON.parse(decoded) as unknown
64
+ } catch {
65
+ return null
66
+ }
67
+ }
68
+
69
+ const externalizeLine = (
70
+ line: string,
71
+ sidecarHref: string,
72
+ nextId: (type: string) => string,
73
+ entries: Record<string, AdfMetadataEntry>
74
+ ): string => {
75
+ if (!line.includes("<!-- adf:") || line.includes("<!-- adf:/")) return line
76
+ const end = line.lastIndexOf("-->")
77
+ if (end === -1) return line
78
+
79
+ for (const kind of markerKinds) {
80
+ const needle = ` ${kind}=`
81
+ const keyStart = line.indexOf(needle)
82
+ if (keyStart === -1 || keyStart > end) continue
83
+
84
+ const valueStart = keyStart + needle.length
85
+ const raw = line.slice(valueStart, end).trim()
86
+ const value = parseMetadataValue(raw)
87
+ if (value === null) return line
88
+
89
+ const id = nextId(markerType(line))
90
+ entries[id] = { kind, value }
91
+ return `${line.slice(0, keyStart)} ref=${sidecarHref}#${id} ${line.slice(end)}`
92
+ }
93
+
94
+ return line
95
+ }
96
+
97
+ export const externalizeAdfMetadata = (
98
+ markdown: string,
99
+ sidecarHref: string
100
+ ): { readonly markdown: string; readonly sidecar: AdfMetadataSidecar | null } => {
101
+ const entries: Record<string, AdfMetadataEntry> = {}
102
+ let counter = 0
103
+ const nextId = (type: string): string => `${type}-${++counter}`
104
+ const lines = markdown.split("\n").map((line) => externalizeLine(line, sidecarHref, nextId, entries))
105
+ return {
106
+ markdown: lines.join("\n"),
107
+ sidecar: Object.keys(entries).length > 0 ? { version: 1, entries } : null
108
+ }
109
+ }
110
+
111
+ export const collectAdfMetadataHrefs = (markdown: string): ReadonlySet<string> => {
112
+ const hrefs = new Set<string>()
113
+ for (const line of markdown.split("\n")) {
114
+ if (!line.includes("<!-- adf:") || !line.includes(" ref=")) continue
115
+ const end = line.lastIndexOf("-->")
116
+ const refStart = line.indexOf(" ref=")
117
+ if (end === -1 || refStart === -1 || refStart > end) continue
118
+ const rawRef = line.slice(refStart + " ref=".length, end).trim()
119
+ const href = rawRef.includes("#") ? rawRef.slice(0, rawRef.lastIndexOf("#")) : rawRef
120
+ if (href.length > 0) hrefs.add(href)
121
+ }
122
+ return hrefs
123
+ }
124
+
125
+ const hydrateLine = (line: string, sidecars: ReadonlyMap<string, AdfMetadataSidecar>): string => {
126
+ if (!line.includes("<!-- adf:") || !line.includes(" ref=")) return line
127
+ const end = line.lastIndexOf("-->")
128
+ const refStart = line.indexOf(" ref=")
129
+ if (end === -1 || refStart === -1 || refStart > end) return line
130
+
131
+ const rawRef = line.slice(refStart + " ref=".length, end).trim()
132
+ const href = rawRef.includes("#") ? rawRef.slice(0, rawRef.lastIndexOf("#")) : rawRef
133
+ const id = rawRef.includes("#") ? rawRef.slice(rawRef.lastIndexOf("#") + 1) : rawRef
134
+ const sidecar = sidecars.get(href)
135
+ if (!sidecar) return line
136
+ const entry = sidecar.entries[id]
137
+ if (!entry) return line
138
+
139
+ return `${line.slice(0, refStart)} ${entry.kind}=${stableStringify(entry.value)} ${line.slice(end)}`
140
+ }
141
+
142
+ export const hydrateAdfMetadata = (
143
+ markdown: string,
144
+ sidecars: ReadonlyMap<string, AdfMetadataSidecar>
145
+ ): string => markdown.split("\n").map((line) => hydrateLine(line, sidecars)).join("\n")
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Remove Confluence round-trip metadata from markdown intended for reading.
3
+ */
4
+
5
+ const ADF_COMMENT_PATTERN = /<!--\s*adf:[\s\S]*?-->/g
6
+
7
+ export const cleanMarkdown = (markdown: string): string => {
8
+ const cleaned = markdown
9
+ .replace(ADF_COMMENT_PATTERN, "")
10
+ .replace(/[ \t]+\n/g, "\n")
11
+ .replace(/\n{3,}/g, "\n\n")
12
+ .trim()
13
+
14
+ return cleaned.length > 0 ? `${cleaned}\n` : ""
15
+ }