@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
@@ -0,0 +1,563 @@
1
+ /**
2
+ * Reverse the AdfWalker's placeholder syntax back into proper ADF nodes
3
+ * after the @atlaskit markdown transformer has run.
4
+ *
5
+ * The walker emits Confluence-only nodes (status, extension, inlineExtension)
6
+ * as HTML/comment placeholders so they survive a pull. The markdown transformer
7
+ * has no concept of these nodes, so on push it parses the placeholders as
8
+ * plain text — Confluence then renders the literal HTML/comment. This module
9
+ * walks the produced ADF and rewrites those text patterns into the structured
10
+ * nodes the editor expects.
11
+ *
12
+ * Patterns recognized (must match the AdfWalker emission exactly):
13
+ * - `<span class="adf-status" data-color="COLOR">TEXT</span>`
14
+ * - `<!-- adf:extension key=KEY type=TYPE attrs=BASE64 -->` (block, when
15
+ * the whole paragraph is just this comment; `attrs` is base64 JSON of
16
+ * the node's full attrs — parameters, localId, layout — and wins over
17
+ * the readable key/type parts; key/type-only is the legacy form)
18
+ * - `<!-- adf:paragraph marks=BASE64 --> BODY <!-- adf:/paragraph -->`
19
+ * (the body paragraph regains its paragraph-level marks)
20
+ * - `<!-- adf:bodiedExtension … --> BODY <!-- adf:/bodiedExtension -->`
21
+ * (the sibling blocks between the markers become the extension's body)
22
+ * - `<!-- adf:inlineCard attrs=BASE64 -->` (inline)
23
+ * - `<!-- adf:inlineExtension key=KEY type=TYPE attrs=BASE64 -->` (inline)
24
+ * - `<!-- adf:date node=BASE64 -->` and `<!-- adf:emoji node=BASE64 -->`
25
+ * (inline)
26
+ * - `<!-- adf:panel type=TYPE attrs=BASE64 --> BODY <!-- adf:/panel -->`
27
+ * (the sibling blocks between the markers become the panel's body)
28
+ * - `<!-- adf:TYPE node=BASE64 --> BODY <!-- adf:/TYPE -->` for selected
29
+ * native block nodes such as task/decision lists, expands, layouts,
30
+ * cards, and tables
31
+ * - `<u>TEXT</u>`, `<sub>TEXT</sub>`, `<sup>TEXT</sup>`, and exact styled
32
+ * spans emitted for Confluence-only inline marks
33
+ * - `[@Name](confluence-mention://ACCOUNT_ID)` (link mark with a
34
+ * custom scheme — the only way to round-trip mention accountIds)
35
+ * - `[[toc]]`, `[[toc:min=1,max=3]]` (block-level native syntax for the
36
+ * Confluence Table of Contents macro)
37
+ *
38
+ * @module
39
+ */
40
+
41
+ import * as Option from "effect/Option"
42
+ import * as Schema from "effect/Schema"
43
+
44
+ interface AdfNode {
45
+ readonly type: string
46
+ readonly attrs?: Record<string, unknown>
47
+ readonly content?: ReadonlyArray<AdfNode>
48
+ readonly text?: string
49
+ readonly marks?: ReadonlyArray<AdfNode>
50
+ }
51
+
52
+ const STATUS_RE = /<span class="adf-status"\s+data-color="([^"]+)">([^<]*)<\/span>/g
53
+ const INLINE_NODE_RE = /<!--\s*adf:(date|emoji)(?:\s+node=([\s\S]*?))?\s*-->/g
54
+ const INLINE_CARD_RE = /<!--\s*adf:inlineCard(?:\s+attrs=([\s\S]*?))?\s*-->/g
55
+ const INLINE_EXTENSION_RE =
56
+ /<!--\s*adf:inlineExtension(?:\s+key=(\S+?))?(?:\s+type=(\S+?))?(?:\s+attrs=([\s\S]*?))?\s*-->/g
57
+ const UNDERLINE_RE = /<u>([^<]*)<\/u>/g
58
+ const SUBSCRIPT_RE = /<sub>([^<]*)<\/sub>/g
59
+ const SUPERSCRIPT_RE = /<sup>([^<]*)<\/sup>/g
60
+ const TEXT_COLOR_RE = /<span style="color:([^"<>]+)">([^<]*)<\/span>/g
61
+ const BACKGROUND_COLOR_RE = /<span style="background-color:([^"<>]+)">([^<]*)<\/span>/g
62
+ const COMBINED_INLINE_RE = new RegExp(
63
+ [
64
+ INLINE_NODE_RE.source,
65
+ STATUS_RE.source,
66
+ INLINE_CARD_RE.source,
67
+ INLINE_EXTENSION_RE.source,
68
+ UNDERLINE_RE.source,
69
+ SUBSCRIPT_RE.source,
70
+ SUPERSCRIPT_RE.source,
71
+ TEXT_COLOR_RE.source,
72
+ BACKGROUND_COLOR_RE.source
73
+ ].join("|"),
74
+ "g"
75
+ )
76
+
77
+ const BLOCK_EXTENSION_RE =
78
+ /^\s*<!--\s*adf:(extension|bodiedExtension)(?:\s+key=(\S+?))?(?:\s+type=(\S+?))?(?:\s+attrs=([\s\S]*?))?\s*-->\s*$/
79
+ const BODIED_EXTENSION_END_RE = /^\s*<!--\s*adf:\/bodiedExtension\s*-->\s*$/
80
+ const PANEL_RE = /^\s*<!--\s*adf:panel(?:\s+type=(\S+?))?(?:\s+attrs=([\s\S]*?))?\s*-->\s*$/
81
+ const PANEL_END_RE = /^\s*<!--\s*adf:\/panel\s*-->\s*$/
82
+ const ENCODED_BLOCK_NODE_RE =
83
+ /^\s*<!--\s*adf:(taskList|decisionList|expand|nestedExpand|table|layoutSection|blockCard|embedCard)(?:\s+node=([\s\S]*?))?\s*-->\s*$/
84
+ const ENCODED_BLOCK_NODE_END_RE =
85
+ /^\s*<!--\s*adf:\/(taskList|decisionList|expand|nestedExpand|table|layoutSection|blockCard|embedCard)\s*-->\s*$/
86
+ const PARAGRAPH_MARKS_RE = /^\s*<!--\s*adf:paragraph(?:\s+marks=([\s\S]*?))?\s*-->\s*$/
87
+ const PARAGRAPH_MARKS_END_RE = /^\s*<!--\s*adf:\/paragraph\s*-->\s*$/
88
+ const TOC_RE = /^\s*\[\[toc(?::([^\]]+))?\]\]\s*$/
89
+ const CONFLUENCE_CORE_MACRO_TYPE = "com.atlassian.confluence.macro.core"
90
+
91
+ const textNode = (text: string, marks: ReadonlyArray<AdfNode> | undefined): AdfNode =>
92
+ marks && marks.length > 0 ? { type: "text", text, marks } : { type: "text", text }
93
+
94
+ const addMark = (marks: ReadonlyArray<AdfNode> | undefined, mark: AdfNode): ReadonlyArray<AdfNode> =>
95
+ marks && marks.length > 0 ? [...marks, mark] : [mark]
96
+
97
+ // Code-marked text is a *quotation* of placeholder syntax, not a placeholder
98
+ // (the walker never emits placeholders with a code mark) — expanding it would
99
+ // corrupt documentation that demonstrates the syntax.
100
+ const hasCodeMark = (n: AdfNode): boolean => (n.marks ?? []).some((m) => m.type === "code")
101
+
102
+ // Parents whose content model permits bodiedExtension per @atlaskit/adf-schema
103
+ // (blockquote/listItem/tableCell allow plain extension but NOT bodied — emitting
104
+ // one there fails outgoing validation and the whole push errors out).
105
+ const BODIED_EXTENSION_PARENTS = new Set(["doc", "layoutColumn"])
106
+
107
+ // Web APIs only (atob/TextDecoder) — this module is a standalone subpath
108
+ // export and must not assume Node, mirroring the walker's encoder.
109
+ const fromBase64 = (b64: string): string => {
110
+ const bin = atob(b64)
111
+ return new TextDecoder().decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)))
112
+ }
113
+
114
+ // JSON string → free-form attrs record; rejects null/arrays/primitives.
115
+ const AttrsBlob = Schema.Record(Schema.String, Schema.Unknown)
116
+ const decodeAttrsBlob = Schema.decodeUnknownOption(AttrsBlob)
117
+
118
+ const decodeAttrs = (b64: string | undefined): Record<string, unknown> | null => {
119
+ if (!b64) return null
120
+ try {
121
+ const raw = b64.trim()
122
+ const parsed = JSON.parse(raw.startsWith("{") ? raw : fromBase64(raw)) as unknown
123
+ const decoded = decodeAttrsBlob(parsed)
124
+ return Option.isSome(decoded) ? decoded.value : null
125
+ } catch {
126
+ // Invalid JSON/base64 (hand-edited file?) — fall back to the readable key/type.
127
+ return null
128
+ }
129
+ }
130
+
131
+ const buildExtensionAttrs = (
132
+ key: string | undefined,
133
+ type: string | undefined,
134
+ attrsB64: string | undefined
135
+ ): Record<string, unknown> => {
136
+ const decoded = decodeAttrs(attrsB64)
137
+ if (decoded) return decoded
138
+ const attrs: Record<string, unknown> = {}
139
+ if (key) attrs.extensionKey = key
140
+ if (type) attrs.extensionType = type
141
+ return attrs
142
+ }
143
+
144
+ const buildPanelAttrs = (type: string | undefined, attrsB64: string | undefined): Record<string, unknown> => {
145
+ const decoded = decodeAttrs(attrsB64)
146
+ if (decoded) return decoded
147
+ return type ? { panelType: type } : {}
148
+ }
149
+
150
+ const buildInlineCardAttrs = (attrsB64: string | undefined): Record<string, unknown> => decodeAttrs(attrsB64) ?? {}
151
+
152
+ const decodeMarks = (b64: string | undefined): ReadonlyArray<AdfNode> => {
153
+ if (!b64) return []
154
+ try {
155
+ const raw = b64.trim()
156
+ const parsed = JSON.parse(raw.startsWith("[") ? raw : fromBase64(raw)) as unknown
157
+ return Array.isArray(parsed)
158
+ ? parsed.filter((mark): mark is AdfNode =>
159
+ mark !== null && typeof mark === "object" && typeof (mark as Record<string, unknown>)["type"] === "string"
160
+ )
161
+ : []
162
+ } catch {
163
+ return []
164
+ }
165
+ }
166
+
167
+ const decodeNode = (b64: string | undefined): AdfNode | null => {
168
+ if (!b64) return null
169
+ try {
170
+ const raw = b64.trim()
171
+ const parsed = JSON.parse(raw.startsWith("{") ? raw : fromBase64(raw)) as unknown
172
+ return parsed !== null && typeof parsed === "object" &&
173
+ typeof (parsed as Record<string, unknown>)["type"] === "string"
174
+ ? parsed as AdfNode
175
+ : null
176
+ } catch {
177
+ return null
178
+ }
179
+ }
180
+
181
+ /** Split a text node into a sequence of text + status + inlineExtension nodes. */
182
+ const expandInlineText = (
183
+ text: string,
184
+ marks: ReadonlyArray<AdfNode> | undefined
185
+ ): ReadonlyArray<AdfNode> => {
186
+ // Reset lastIndex; sticky regexes are stateful across calls.
187
+ const re = new RegExp(COMBINED_INLINE_RE.source, "g")
188
+ const out: Array<AdfNode> = []
189
+ let lastIndex = 0
190
+ let match: RegExpExecArray | null
191
+
192
+ while ((match = re.exec(text)) !== null) {
193
+ if (match.index > lastIndex) {
194
+ out.push(textNode(text.slice(lastIndex, match.index), marks))
195
+ }
196
+ // Capture groups follow COMBINED_INLINE_RE order.
197
+ // InlineNode: 1=type, 2=node. Status: 3=color, 4=text.
198
+ // InlineCard: 5=attrs. InlineExtension: 6=key, 7=type, 8=attrs.
199
+ // Underline: 9=text. Sub: 10=text. Sup: 11=text.
200
+ // Text color: 12=color, 13=text. Background color: 14=color, 15=text.
201
+ if (match[1] !== undefined) {
202
+ const decoded = decodeNode(match[2])
203
+ if (decoded && decoded.type === match[1]) out.push(decoded)
204
+ } else if (match[3] !== undefined) {
205
+ out.push({ type: "status", attrs: { text: match[4] ?? "", color: match[3] } })
206
+ } else if (match[5] !== undefined) {
207
+ out.push({ type: "inlineCard", attrs: buildInlineCardAttrs(match[5]) })
208
+ } else if (match[9] !== undefined) {
209
+ out.push(textNode(match[9], addMark(marks, { type: "underline" })))
210
+ } else if (match[10] !== undefined) {
211
+ out.push(textNode(match[10], addMark(marks, { type: "subsup", attrs: { type: "sub" } })))
212
+ } else if (match[11] !== undefined) {
213
+ out.push(textNode(match[11], addMark(marks, { type: "subsup", attrs: { type: "sup" } })))
214
+ } else if (match[12] !== undefined) {
215
+ out.push(textNode(match[13] ?? "", addMark(marks, { type: "textColor", attrs: { color: match[12] } })))
216
+ } else if (match[14] !== undefined) {
217
+ out.push(textNode(match[15] ?? "", addMark(marks, { type: "backgroundColor", attrs: { color: match[14] } })))
218
+ } else {
219
+ out.push({ type: "inlineExtension", attrs: buildExtensionAttrs(match[6], match[7], match[8]) })
220
+ }
221
+ lastIndex = match.index + match[0].length
222
+ }
223
+
224
+ if (lastIndex === 0) return [textNode(text, marks)]
225
+ if (lastIndex < text.length) out.push(textNode(text.slice(lastIndex), marks))
226
+ return out
227
+ }
228
+
229
+ const MENTION_SCHEME = "confluence-mention://"
230
+
231
+ /**
232
+ * If the text node carries a `confluence-mention://` link mark, return the
233
+ * corresponding mention node — otherwise null.
234
+ */
235
+ const tryParseMentionTextNode = (n: AdfNode): AdfNode | null => {
236
+ if (!n.text || !n.marks) return null
237
+ const link = n.marks.find((m) => m.type === "link")
238
+ if (!link) return null
239
+ const href = link.attrs?.["href"]
240
+ if (typeof href !== "string" || !href.startsWith(MENTION_SCHEME)) return null
241
+ let id: string
242
+ try {
243
+ id = decodeURIComponent(href.slice(MENTION_SCHEME.length))
244
+ } catch {
245
+ // Malformed encoding — leave the link alone rather than crashing the push.
246
+ return null
247
+ }
248
+ return { type: "mention", attrs: { id, text: n.text } }
249
+ }
250
+
251
+ interface BlockExtensionMarker {
252
+ readonly kind: "extension" | "bodiedExtension"
253
+ readonly attrs: Record<string, unknown>
254
+ }
255
+
256
+ /**
257
+ * If the paragraph's only content is a single text node holding a block-extension
258
+ * comment, return the marker's kind and reconstructed attrs — otherwise null.
259
+ */
260
+ const soleTextChild = (node: AdfNode): AdfNode | null => {
261
+ if (node.type !== "paragraph") return null
262
+ const content = node.content ?? []
263
+ if (content.length !== 1) return null
264
+ const child = content[0]
265
+ if (!child || child.type !== "text" || !child.text || hasCodeMark(child)) return null
266
+ return child
267
+ }
268
+
269
+ const parseBlockExtensionParagraph = (node: AdfNode): BlockExtensionMarker | null => {
270
+ const child = soleTextChild(node)
271
+ if (!child || !child.text) return null
272
+ const match = BLOCK_EXTENSION_RE.exec(child.text)
273
+ if (!match) return null
274
+ const [, kind, key, type, attrsB64] = match
275
+ return {
276
+ kind: kind === "bodiedExtension" ? "bodiedExtension" : "extension",
277
+ attrs: buildExtensionAttrs(key, type, attrsB64)
278
+ }
279
+ }
280
+
281
+ const parseTocParagraph = (node: AdfNode): AdfNode | null => {
282
+ const child = soleTextChild(node)
283
+ if (!child || !child.text) return null
284
+ const match = TOC_RE.exec(child.text)
285
+ if (!match) return null
286
+
287
+ let minLevel: string | undefined
288
+ let maxLevel: string | undefined
289
+ const params = match[1]?.trim()
290
+
291
+ if (params && params.length > 0) {
292
+ const seen = new Set<string>()
293
+ for (const part of params.split(",")) {
294
+ const [rawKey, rawValue, ...rest] = part.split("=")
295
+ if (rest.length > 0) return null
296
+ const key = rawKey?.trim()
297
+ const value = rawValue?.trim()
298
+ if ((key !== "min" && key !== "max") || !value || !/^[1-6]$/.test(value) || seen.has(key)) return null
299
+ seen.add(key)
300
+ if (key === "min") minLevel = value
301
+ else maxLevel = value
302
+ }
303
+ } else if (params !== undefined) {
304
+ return null
305
+ }
306
+
307
+ const macroParams: Record<string, { readonly value: string }> = {}
308
+ if (minLevel) macroParams.minLevel = { value: minLevel }
309
+ if (maxLevel) macroParams.maxLevel = { value: maxLevel }
310
+
311
+ const attrs: Record<string, unknown> = {
312
+ extensionKey: "toc",
313
+ extensionType: CONFLUENCE_CORE_MACRO_TYPE
314
+ }
315
+ if (Object.keys(macroParams).length > 0) {
316
+ attrs.parameters = { macroParams }
317
+ }
318
+
319
+ return { type: "extension", attrs }
320
+ }
321
+
322
+ const isBodiedExtensionEnd = (node: AdfNode): boolean => {
323
+ const child = soleTextChild(node)
324
+ return child !== null && typeof child.text === "string" && BODIED_EXTENSION_END_RE.test(child.text)
325
+ }
326
+
327
+ const parsePanelParagraph = (node: AdfNode): Record<string, unknown> | null => {
328
+ const child = soleTextChild(node)
329
+ if (!child || !child.text) return null
330
+ const match = PANEL_RE.exec(child.text)
331
+ if (!match) return null
332
+ const [, type, attrsB64] = match
333
+ return buildPanelAttrs(type, attrsB64)
334
+ }
335
+
336
+ const isPanelEnd = (node: AdfNode): boolean => {
337
+ const child = soleTextChild(node)
338
+ return child !== null && typeof child.text === "string" && PANEL_END_RE.test(child.text)
339
+ }
340
+
341
+ const parseEncodedBlockNodeParagraph = (node: AdfNode): { readonly type: string; readonly node: AdfNode } | null => {
342
+ const child = soleTextChild(node)
343
+ if (!child || !child.text) return null
344
+ const match = ENCODED_BLOCK_NODE_RE.exec(child.text)
345
+ if (!match) return null
346
+ const decoded = decodeNode(match[2])
347
+ if (!decoded || decoded.type !== match[1]) return null
348
+ return { type: match[1]!, node: decoded }
349
+ }
350
+
351
+ const isEncodedBlockNodeEnd = (node: AdfNode, type: string): boolean => {
352
+ const child = soleTextChild(node)
353
+ if (child === null || typeof child.text !== "string") return false
354
+ const match = ENCODED_BLOCK_NODE_END_RE.exec(child.text)
355
+ return match?.[1] === type
356
+ }
357
+
358
+ const parseParagraphMarksParagraph = (node: AdfNode): ReadonlyArray<AdfNode> | null => {
359
+ const child = soleTextChild(node)
360
+ if (!child || !child.text) return null
361
+ const match = PARAGRAPH_MARKS_RE.exec(child.text)
362
+ if (!match) return null
363
+ return decodeMarks(match[1])
364
+ }
365
+
366
+ const isParagraphMarksEnd = (node: AdfNode): boolean => {
367
+ const child = soleTextChild(node)
368
+ return child !== null && typeof child.text === "string" && PARAGRAPH_MARKS_END_RE.test(child.text)
369
+ }
370
+
371
+ /**
372
+ * Replace block-extension marker paragraphs among `children`. A bare
373
+ * `extension` marker becomes an extension node; a `bodiedExtension` marker
374
+ * swallows every sibling up to its `adf:/bodiedExtension` end marker as the
375
+ * extension's body.
376
+ *
377
+ * Pairing rules, in order of defence:
378
+ * - the forward scan stops at the next bodied *open* marker, so an unpaired
379
+ * legacy/hand-edited open cannot steal a later macro's end marker and
380
+ * swallow unrelated content in between;
381
+ * - an open with no end marker is downgraded to a plain extension (macro
382
+ * identity and configuration kept, body left in place as siblings);
383
+ * - an open/end pair with nothing in between keeps its bodied kind via a
384
+ * stub empty paragraph (the schema requires non-empty content);
385
+ * - parents whose content model forbids bodiedExtension (blockquote,
386
+ * listItem, …) get the downgrade too, or outgoing validation would fail;
387
+ * - stray end markers are dropped — they are this module's own syntax,
388
+ * never user content.
389
+ */
390
+ const groupBlockExtensions = (children: ReadonlyArray<AdfNode>, parentType: string): ReadonlyArray<AdfNode> => {
391
+ const allowBodied = BODIED_EXTENSION_PARENTS.has(parentType)
392
+ const out: Array<AdfNode> = []
393
+ for (let i = 0; i < children.length; i++) {
394
+ const child = children[i]
395
+ if (!child) continue
396
+ const marker = parseBlockExtensionParagraph(child)
397
+ if (!marker) {
398
+ if (!isBodiedExtensionEnd(child)) out.push(child)
399
+ continue
400
+ }
401
+ if (marker.kind === "extension") {
402
+ out.push({ type: "extension", attrs: marker.attrs })
403
+ continue
404
+ }
405
+ let end = -1
406
+ for (let j = i + 1; j < children.length; j++) {
407
+ if (isBodiedExtensionEnd(children[j]!)) {
408
+ end = j
409
+ break
410
+ }
411
+ if (parseBlockExtensionParagraph(children[j]!)?.kind === "bodiedExtension") break
412
+ }
413
+ if (end === -1 || !allowBodied) {
414
+ out.push({ type: "extension", attrs: marker.attrs })
415
+ continue
416
+ }
417
+ // Group recursively so an extension marker *inside* the body (a macro
418
+ // nested in a bodied macro) is also reverted, not left as literal text.
419
+ const body = groupBlockExtensions(children.slice(i + 1, end), "bodiedExtension")
420
+ out.push({
421
+ type: "bodiedExtension",
422
+ attrs: marker.attrs,
423
+ content: body.length > 0 ? body : [{ type: "paragraph", content: [] }]
424
+ })
425
+ i = end
426
+ }
427
+ return out
428
+ }
429
+
430
+ const groupPanels = (children: ReadonlyArray<AdfNode>): ReadonlyArray<AdfNode> => {
431
+ const out: Array<AdfNode> = []
432
+ for (let i = 0; i < children.length; i++) {
433
+ const child = children[i]
434
+ if (!child) continue
435
+ const attrs = parsePanelParagraph(child)
436
+ if (!attrs) {
437
+ if (!isPanelEnd(child)) out.push(child)
438
+ continue
439
+ }
440
+
441
+ let end = -1
442
+ for (let j = i + 1; j < children.length; j++) {
443
+ if (isPanelEnd(children[j]!)) {
444
+ end = j
445
+ break
446
+ }
447
+ if (parsePanelParagraph(children[j]!)) break
448
+ }
449
+
450
+ if (end === -1) {
451
+ out.push({ type: "panel", attrs, content: [{ type: "paragraph", content: [] }] })
452
+ continue
453
+ }
454
+
455
+ const body = groupPanels(children.slice(i + 1, end))
456
+ out.push({
457
+ type: "panel",
458
+ attrs,
459
+ content: body.length > 0 ? body : [{ type: "paragraph", content: [] }]
460
+ })
461
+ i = end
462
+ }
463
+ return out
464
+ }
465
+
466
+ const groupEncodedBlockNodes = (children: ReadonlyArray<AdfNode>): ReadonlyArray<AdfNode> => {
467
+ const out: Array<AdfNode> = []
468
+ for (let i = 0; i < children.length; i++) {
469
+ const child = children[i]
470
+ if (!child) continue
471
+ const marker = parseEncodedBlockNodeParagraph(child)
472
+ if (!marker) {
473
+ out.push(child)
474
+ continue
475
+ }
476
+
477
+ let end = -1
478
+ for (let j = i + 1; j < children.length; j++) {
479
+ if (isEncodedBlockNodeEnd(children[j]!, marker.type)) {
480
+ end = j
481
+ break
482
+ }
483
+ if (parseEncodedBlockNodeParagraph(children[j]!) !== null) break
484
+ }
485
+
486
+ out.push(marker.node)
487
+ if (end !== -1) i = end
488
+ }
489
+ return out
490
+ }
491
+
492
+ const groupMarkedParagraphs = (children: ReadonlyArray<AdfNode>): ReadonlyArray<AdfNode> => {
493
+ const out: Array<AdfNode> = []
494
+ for (let i = 0; i < children.length; i++) {
495
+ const child = children[i]
496
+ if (!child) continue
497
+ const marks = parseParagraphMarksParagraph(child)
498
+ if (!marks) {
499
+ if (!isParagraphMarksEnd(child)) out.push(child)
500
+ continue
501
+ }
502
+
503
+ let end = -1
504
+ for (let j = i + 1; j < children.length; j++) {
505
+ if (isParagraphMarksEnd(children[j]!)) {
506
+ end = j
507
+ break
508
+ }
509
+ if (parseParagraphMarksParagraph(children[j]!) !== null) break
510
+ }
511
+
512
+ if (end === -1) {
513
+ out.push({ type: "paragraph", marks, content: [] })
514
+ continue
515
+ }
516
+
517
+ const body = children.slice(i + 1, end)
518
+ const first = body[0]
519
+ if (first?.type === "paragraph") {
520
+ out.push(marks.length > 0 ? { ...first, marks } : first)
521
+ for (const rest of body.slice(1)) out.push(rest)
522
+ } else {
523
+ out.push({ type: "paragraph", marks, content: [] })
524
+ for (const rest of body) out.push(rest)
525
+ }
526
+ i = end
527
+ }
528
+ return out
529
+ }
530
+
531
+ const groupNativeMacros = (children: ReadonlyArray<AdfNode>): ReadonlyArray<AdfNode> =>
532
+ children.map((child) => parseTocParagraph(child) ?? child)
533
+
534
+ const transform = (node: AdfNode): AdfNode => {
535
+ // ADF codeBlock permits only plain text children — expanding placeholder-
536
+ // looking text inside one would inject schema-invalid nodes and corrupt
537
+ // code samples that merely *quote* the placeholder syntax.
538
+ if (node.type === "codeBlock") return node
539
+ if (!node.content) return node
540
+
541
+ const newContent: Array<AdfNode> = []
542
+ for (const child of node.content) {
543
+ if (child.type === "text" && child.text && !hasCodeMark(child)) {
544
+ const mention = tryParseMentionTextNode(child)
545
+ if (mention) {
546
+ newContent.push(mention)
547
+ } else {
548
+ for (const piece of expandInlineText(child.text, child.marks)) {
549
+ newContent.push(piece)
550
+ }
551
+ }
552
+ } else {
553
+ newContent.push(transform(child))
554
+ }
555
+ }
556
+ const nativeMacrosRestored = groupNativeMacros(newContent)
557
+ const paragraphsRestored = groupMarkedParagraphs(nativeMacrosRestored)
558
+ const encodedBlocksRestored = groupEncodedBlockNodes(paragraphsRestored)
559
+ return { ...node, content: groupPanels(groupBlockExtensions(encodedBlocksRestored, node.type)) }
560
+ }
561
+
562
+ /** Walk the document tree and rewrite placeholder text into proper ADF nodes. */
563
+ export const revertPlaceholders = (doc: unknown): unknown => transform(doc as AdfNode)
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Runtime JSON-Schema validator for ADF documents.
3
+ *
4
+ * Wraps Ajv compiled against the canonical schema bundled in
5
+ * `@atlaskit/adf-schema` (`json-schema/v1/full.json`). Used on both
6
+ * directions of the conversion: incoming (after `JSON.parse`, before walking)
7
+ * and outgoing (after the @atlaskit transformer produces JSON, before
8
+ * `JSON.stringify`). The single project-wide cast `as DocNode` lives here,
9
+ * bridging Ajv's runtime predicate to the TypeScript types.
10
+ *
11
+ * @module
12
+ */
13
+ import type { DocNode } from "@atlaskit/adf-schema"
14
+ import adfJsonSchema from "@atlaskit/adf-schema/json-schema/v1/full.json" with { type: "json" }
15
+ import Ajv from "ajv-draft-04"
16
+ import * as Context from "effect/Context"
17
+ import * as Effect from "effect/Effect"
18
+ import * as Layer from "effect/Layer"
19
+ import { AdfSchemaError, type AdfSchemaIssue } from "./ConfluenceError.js"
20
+
21
+ const ajv = new Ajv({ strict: false, allErrors: true })
22
+ const validate = ajv.compile(adfJsonSchema as object)
23
+
24
+ /**
25
+ * Effect service that runtime-validates ADF documents against the canonical
26
+ * @atlaskit/adf-schema JSON Schema and narrows the success type to `DocNode`.
27
+ *
28
+ * @category Service
29
+ */
30
+ export class AdfSchemaValidator extends Context.Service<
31
+ AdfSchemaValidator,
32
+ {
33
+ readonly check: (
34
+ doc: unknown,
35
+ direction: "incoming" | "outgoing"
36
+ ) => Effect.Effect<DocNode, AdfSchemaError>
37
+ }
38
+ >()("@knpkv/confluence-to-markdown/AdfSchemaValidator") {}
39
+
40
+ /**
41
+ * Live Layer for `AdfSchemaValidator`. The Ajv validator is compiled once at
42
+ * module load.
43
+ *
44
+ * @category Layers
45
+ */
46
+ export const layer: Layer.Layer<AdfSchemaValidator> = Layer.succeed(
47
+ AdfSchemaValidator,
48
+ AdfSchemaValidator.of({
49
+ check: (doc, direction) =>
50
+ validate(doc)
51
+ ? Effect.succeed(doc as DocNode)
52
+ : Effect.fail(
53
+ new AdfSchemaError({
54
+ direction,
55
+ issues: (validate.errors ?? []).map((e): AdfSchemaIssue => ({
56
+ instancePath: e.instancePath,
57
+ schemaPath: e.schemaPath,
58
+ keyword: e.keyword,
59
+ params: e.params,
60
+ ...(e.message !== undefined ? { message: e.message } : {})
61
+ }))
62
+ })
63
+ )
64
+ })
65
+ )