@knpkv/confluence-to-markdown 0.6.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 (357) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +22 -13
  4. package/dist/AdfPlaceholders.d.ts +42 -0
  5. package/dist/AdfPlaceholders.d.ts.map +1 -0
  6. package/dist/AdfPlaceholders.js +547 -0
  7. package/dist/AdfPlaceholders.js.map +1 -0
  8. package/dist/AdfSchemaValidator.d.ts +37 -0
  9. package/dist/AdfSchemaValidator.d.ts.map +1 -0
  10. package/dist/AdfSchemaValidator.js +37 -0
  11. package/dist/AdfSchemaValidator.js.map +1 -0
  12. package/dist/AdfWalker.d.ts +39 -0
  13. package/dist/AdfWalker.d.ts.map +1 -0
  14. package/dist/AdfWalker.js +527 -0
  15. package/dist/AdfWalker.js.map +1 -0
  16. package/dist/AtlaskitTransformers.d.ts +35 -0
  17. package/dist/AtlaskitTransformers.d.ts.map +1 -0
  18. package/dist/AtlaskitTransformers.js +48 -0
  19. package/dist/AtlaskitTransformers.js.map +1 -0
  20. package/dist/Brand.d.ts +6 -6
  21. package/dist/Brand.d.ts.map +1 -1
  22. package/dist/Brand.js +8 -6
  23. package/dist/Brand.js.map +1 -1
  24. package/dist/ConfluenceAuth.d.ts +4 -4
  25. package/dist/ConfluenceAuth.d.ts.map +1 -1
  26. package/dist/ConfluenceAuth.js +37 -39
  27. package/dist/ConfluenceAuth.js.map +1 -1
  28. package/dist/ConfluenceClient.d.ts +7 -17
  29. package/dist/ConfluenceClient.d.ts.map +1 -1
  30. package/dist/ConfluenceClient.js +81 -38
  31. package/dist/ConfluenceClient.js.map +1 -1
  32. package/dist/ConfluenceConfig.d.ts +3 -3
  33. package/dist/ConfluenceConfig.d.ts.map +1 -1
  34. package/dist/ConfluenceConfig.js +13 -11
  35. package/dist/ConfluenceConfig.js.map +1 -1
  36. package/dist/ConfluenceError.d.ts +68 -16
  37. package/dist/ConfluenceError.d.ts.map +1 -1
  38. package/dist/ConfluenceError.js +30 -1
  39. package/dist/ConfluenceError.js.map +1 -1
  40. package/dist/GitError.d.ts +5 -5
  41. package/dist/GitService.d.ts +11 -3
  42. package/dist/GitService.d.ts.map +1 -1
  43. package/dist/GitService.js +22 -27
  44. package/dist/GitService.js.map +1 -1
  45. package/dist/LocalFileSystem.d.ts +3 -3
  46. package/dist/LocalFileSystem.d.ts.map +1 -1
  47. package/dist/LocalFileSystem.js +6 -6
  48. package/dist/LocalFileSystem.js.map +1 -1
  49. package/dist/MarkdownConverter.d.ts +16 -65
  50. package/dist/MarkdownConverter.d.ts.map +1 -1
  51. package/dist/MarkdownConverter.js +64 -85
  52. package/dist/MarkdownConverter.js.map +1 -1
  53. package/dist/Schemas.d.ts +128 -141
  54. package/dist/Schemas.d.ts.map +1 -1
  55. package/dist/Schemas.js +21 -23
  56. package/dist/Schemas.js.map +1 -1
  57. package/dist/SyncEngine.d.ts +8 -5
  58. package/dist/SyncEngine.d.ts.map +1 -1
  59. package/dist/SyncEngine.js +189 -113
  60. package/dist/SyncEngine.js.map +1 -1
  61. package/dist/bin.js +23 -35
  62. package/dist/bin.js.map +1 -1
  63. package/dist/commands/auth.d.ts +2 -14
  64. package/dist/commands/auth.d.ts.map +1 -1
  65. package/dist/commands/auth.js +11 -16
  66. package/dist/commands/auth.js.map +1 -1
  67. package/dist/commands/clone.d.ts +4 -6
  68. package/dist/commands/clone.d.ts.map +1 -1
  69. package/dist/commands/clone.js +34 -32
  70. package/dist/commands/clone.js.map +1 -1
  71. package/dist/commands/delete.d.ts +2 -10
  72. package/dist/commands/delete.d.ts.map +1 -1
  73. package/dist/commands/delete.js +5 -4
  74. package/dist/commands/delete.js.map +1 -1
  75. package/dist/commands/errorHandler.d.ts +2 -1
  76. package/dist/commands/errorHandler.d.ts.map +1 -1
  77. package/dist/commands/errorHandler.js +22 -15
  78. package/dist/commands/errorHandler.js.map +1 -1
  79. package/dist/commands/fetch.d.ts +27 -0
  80. package/dist/commands/fetch.d.ts.map +1 -0
  81. package/dist/commands/fetch.js +48 -0
  82. package/dist/commands/fetch.js.map +1 -0
  83. package/dist/commands/git.d.ts +7 -10
  84. package/dist/commands/git.d.ts.map +1 -1
  85. package/dist/commands/git.js +6 -6
  86. package/dist/commands/git.js.map +1 -1
  87. package/dist/commands/index.d.ts +1 -0
  88. package/dist/commands/index.d.ts.map +1 -1
  89. package/dist/commands/index.js +1 -0
  90. package/dist/commands/index.js.map +1 -1
  91. package/dist/commands/layers.d.ts +10 -9
  92. package/dist/commands/layers.d.ts.map +1 -1
  93. package/dist/commands/layers.js +41 -30
  94. package/dist/commands/layers.js.map +1 -1
  95. package/dist/commands/new.d.ts +2 -6
  96. package/dist/commands/new.d.ts.map +1 -1
  97. package/dist/commands/new.js +5 -4
  98. package/dist/commands/new.js.map +1 -1
  99. package/dist/commands/pageInput.d.ts +19 -0
  100. package/dist/commands/pageInput.d.ts.map +1 -0
  101. package/dist/commands/pageInput.js +68 -0
  102. package/dist/commands/pageInput.js.map +1 -0
  103. package/dist/commands/root.d.ts +8 -0
  104. package/dist/commands/root.d.ts.map +1 -0
  105. package/dist/commands/root.js +29 -0
  106. package/dist/commands/root.js.map +1 -0
  107. package/dist/commands/sync.d.ts +6 -9
  108. package/dist/commands/sync.d.ts.map +1 -1
  109. package/dist/commands/sync.js +5 -6
  110. package/dist/commands/sync.js.map +1 -1
  111. package/dist/index.d.ts +3 -8
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +2 -13
  114. package/dist/index.js.map +1 -1
  115. package/dist/internal/NodeLayers.d.ts.map +1 -1
  116. package/dist/internal/NodeLayers.js +1 -2
  117. package/dist/internal/NodeLayers.js.map +1 -1
  118. package/dist/internal/adfMetadata.d.ts +30 -0
  119. package/dist/internal/adfMetadata.d.ts.map +1 -0
  120. package/dist/internal/adfMetadata.js +126 -0
  121. package/dist/internal/adfMetadata.js.map +1 -0
  122. package/dist/internal/cleanMarkdown.d.ts +5 -0
  123. package/dist/internal/cleanMarkdown.d.ts.map +1 -0
  124. package/dist/internal/cleanMarkdown.js +13 -0
  125. package/dist/internal/cleanMarkdown.js.map +1 -0
  126. package/dist/internal/frontmatter.d.ts.map +1 -1
  127. package/dist/internal/frontmatter.js +41 -8
  128. package/dist/internal/frontmatter.js.map +1 -1
  129. package/dist/internal/gitCommands.d.ts +9 -3
  130. package/dist/internal/gitCommands.d.ts.map +1 -1
  131. package/dist/internal/gitCommands.js +18 -9
  132. package/dist/internal/gitCommands.js.map +1 -1
  133. package/dist/internal/hashUtils.d.ts +1 -1
  134. package/dist/internal/hashUtils.d.ts.map +1 -1
  135. package/dist/internal/hashUtils.js +1 -1
  136. package/dist/internal/hashUtils.js.map +1 -1
  137. package/dist/internal/oauthServer.d.ts +10 -5
  138. package/dist/internal/oauthServer.d.ts.map +1 -1
  139. package/dist/internal/oauthServer.js +19 -40
  140. package/dist/internal/oauthServer.js.map +1 -1
  141. package/dist/internal/pathUtils.d.ts +1 -1
  142. package/dist/internal/pathUtils.d.ts.map +1 -1
  143. package/dist/internal/pathUtils.js +1 -1
  144. package/dist/internal/pathUtils.js.map +1 -1
  145. package/dist/internal/process.d.ts +15 -0
  146. package/dist/internal/process.d.ts.map +1 -0
  147. package/dist/internal/process.js +10 -0
  148. package/dist/internal/process.js.map +1 -0
  149. package/dist/internal/stdio.d.ts +6 -0
  150. package/dist/internal/stdio.d.ts.map +1 -0
  151. package/dist/internal/stdio.js +15 -0
  152. package/dist/internal/stdio.js.map +1 -0
  153. package/dist/internal/tokenStorage.d.ts +3 -13
  154. package/dist/internal/tokenStorage.d.ts.map +1 -1
  155. package/dist/internal/tokenStorage.js +26 -24
  156. package/dist/internal/tokenStorage.js.map +1 -1
  157. package/dist/internal/userCache.d.ts +1 -1
  158. package/dist/internal/userCache.d.ts.map +1 -1
  159. package/dist/internal/userCache.js +1 -1
  160. package/dist/internal/userCache.js.map +1 -1
  161. package/package.json +30 -30
  162. package/skills/confluence/SKILL.md +143 -0
  163. package/skills/confluence/agents/openai.yaml +4 -0
  164. package/src/AdfPlaceholders.ts +310 -13
  165. package/src/AdfSchemaValidator.ts +2 -4
  166. package/src/AdfWalker.ts +122 -42
  167. package/src/AtlaskitTransformers.ts +2 -4
  168. package/src/Brand.ts +11 -16
  169. package/src/ConfluenceAuth.ts +22 -30
  170. package/src/ConfluenceClient.ts +24 -20
  171. package/src/ConfluenceConfig.ts +14 -14
  172. package/src/GitService.ts +39 -49
  173. package/src/LocalFileSystem.ts +7 -9
  174. package/src/MarkdownConverter.ts +2 -4
  175. package/src/Schemas.ts +13 -12
  176. package/src/SyncEngine.ts +151 -53
  177. package/src/bin.ts +30 -56
  178. package/src/commands/auth.ts +21 -18
  179. package/src/commands/clone.ts +38 -37
  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 +53 -33
  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/internal/NodeLayers.ts +1 -2
  191. package/src/internal/adfMetadata.ts +145 -0
  192. package/src/internal/cleanMarkdown.ts +15 -0
  193. package/src/internal/frontmatter.ts +45 -8
  194. package/src/internal/gitCommands.ts +23 -17
  195. package/src/internal/hashUtils.ts +2 -2
  196. package/src/internal/oauthServer.ts +84 -105
  197. package/src/internal/pathUtils.ts +1 -1
  198. package/src/internal/process.ts +15 -0
  199. package/src/internal/stdio.ts +22 -0
  200. package/src/internal/tokenStorage.ts +39 -29
  201. package/src/internal/userCache.ts +2 -2
  202. package/test/AdfPlaceholders.test.ts +213 -0
  203. package/test/AdfSchemaValidator.test.ts +6 -6
  204. package/test/AdfWalker.test.ts +167 -21
  205. package/test/AtlaskitTransformers.test.ts +4 -4
  206. package/test/Brand.test.ts +11 -11
  207. package/test/GitService.test.ts +6 -2
  208. package/test/MarkdownConverter.test.ts +12 -11
  209. package/test/RoundTrip.test.ts +258 -3
  210. package/test/Schemas.test.ts +40 -40
  211. package/test/adfMetadata.test.ts +110 -0
  212. package/test/cleanMarkdown.test.ts +36 -0
  213. package/test/commandHarness.test.ts +79 -0
  214. package/test/commandHarness.ts +147 -0
  215. package/test/fetch.test.ts +61 -0
  216. package/test/frontmatter.test.ts +41 -0
  217. package/test/integration.test.ts +569 -156
  218. package/test/layers.test.ts +12 -0
  219. package/test/oauthServer.test.ts +4 -5
  220. package/test/pageInput.test.ts +83 -0
  221. package/test/tokenStorage.test.ts +17 -17
  222. package/dist/SchemaConverterError.d.ts +0 -108
  223. package/dist/SchemaConverterError.d.ts.map +0 -1
  224. package/dist/SchemaConverterError.js +0 -84
  225. package/dist/SchemaConverterError.js.map +0 -1
  226. package/dist/ast/BlockNode.d.ts +0 -468
  227. package/dist/ast/BlockNode.d.ts.map +0 -1
  228. package/dist/ast/BlockNode.js +0 -319
  229. package/dist/ast/BlockNode.js.map +0 -1
  230. package/dist/ast/Document.d.ts +0 -244
  231. package/dist/ast/Document.d.ts.map +0 -1
  232. package/dist/ast/Document.js +0 -69
  233. package/dist/ast/Document.js.map +0 -1
  234. package/dist/ast/InlineNode.d.ts +0 -477
  235. package/dist/ast/InlineNode.d.ts.map +0 -1
  236. package/dist/ast/InlineNode.js +0 -263
  237. package/dist/ast/InlineNode.js.map +0 -1
  238. package/dist/ast/MacroNode.d.ts +0 -267
  239. package/dist/ast/MacroNode.d.ts.map +0 -1
  240. package/dist/ast/MacroNode.js +0 -164
  241. package/dist/ast/MacroNode.js.map +0 -1
  242. package/dist/ast/index.d.ts +0 -10
  243. package/dist/ast/index.d.ts.map +0 -1
  244. package/dist/ast/index.js +0 -14
  245. package/dist/ast/index.js.map +0 -1
  246. package/dist/parsers/ConfluenceParser.d.ts +0 -26
  247. package/dist/parsers/ConfluenceParser.d.ts.map +0 -1
  248. package/dist/parsers/ConfluenceParser.js +0 -792
  249. package/dist/parsers/ConfluenceParser.js.map +0 -1
  250. package/dist/parsers/MarkdownParser.d.ts +0 -26
  251. package/dist/parsers/MarkdownParser.d.ts.map +0 -1
  252. package/dist/parsers/MarkdownParser.js +0 -873
  253. package/dist/parsers/MarkdownParser.js.map +0 -1
  254. package/dist/parsers/index.d.ts +0 -8
  255. package/dist/parsers/index.d.ts.map +0 -1
  256. package/dist/parsers/index.js +0 -8
  257. package/dist/parsers/index.js.map +0 -1
  258. package/dist/parsers/preprocessing/ConfluencePreprocessing.d.ts +0 -23
  259. package/dist/parsers/preprocessing/ConfluencePreprocessing.d.ts.map +0 -1
  260. package/dist/parsers/preprocessing/ConfluencePreprocessing.js +0 -323
  261. package/dist/parsers/preprocessing/ConfluencePreprocessing.js.map +0 -1
  262. package/dist/parsers/preprocessing/index.d.ts +0 -7
  263. package/dist/parsers/preprocessing/index.d.ts.map +0 -1
  264. package/dist/parsers/preprocessing/index.js +0 -7
  265. package/dist/parsers/preprocessing/index.js.map +0 -1
  266. package/dist/schemas/ConfluenceSchema.d.ts +0 -21
  267. package/dist/schemas/ConfluenceSchema.d.ts.map +0 -1
  268. package/dist/schemas/ConfluenceSchema.js +0 -38
  269. package/dist/schemas/ConfluenceSchema.js.map +0 -1
  270. package/dist/schemas/ConversionSchema.d.ts +0 -35
  271. package/dist/schemas/ConversionSchema.d.ts.map +0 -1
  272. package/dist/schemas/ConversionSchema.js +0 -208
  273. package/dist/schemas/ConversionSchema.js.map +0 -1
  274. package/dist/schemas/MarkdownSchema.d.ts +0 -21
  275. package/dist/schemas/MarkdownSchema.d.ts.map +0 -1
  276. package/dist/schemas/MarkdownSchema.js +0 -38
  277. package/dist/schemas/MarkdownSchema.js.map +0 -1
  278. package/dist/schemas/hast/HastFromHtml.d.ts +0 -27
  279. package/dist/schemas/hast/HastFromHtml.d.ts.map +0 -1
  280. package/dist/schemas/hast/HastFromHtml.js +0 -107
  281. package/dist/schemas/hast/HastFromHtml.js.map +0 -1
  282. package/dist/schemas/hast/HastSchema.d.ts +0 -195
  283. package/dist/schemas/hast/HastSchema.d.ts.map +0 -1
  284. package/dist/schemas/hast/HastSchema.js +0 -183
  285. package/dist/schemas/hast/HastSchema.js.map +0 -1
  286. package/dist/schemas/hast/index.d.ts +0 -9
  287. package/dist/schemas/hast/index.d.ts.map +0 -1
  288. package/dist/schemas/hast/index.js +0 -3
  289. package/dist/schemas/hast/index.js.map +0 -1
  290. package/dist/schemas/index.d.ts +0 -14
  291. package/dist/schemas/index.d.ts.map +0 -1
  292. package/dist/schemas/index.js +0 -16
  293. package/dist/schemas/index.js.map +0 -1
  294. package/dist/schemas/mdast/MdastFromMarkdown.d.ts +0 -30
  295. package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +0 -1
  296. package/dist/schemas/mdast/MdastFromMarkdown.js +0 -79
  297. package/dist/schemas/mdast/MdastFromMarkdown.js.map +0 -1
  298. package/dist/schemas/mdast/MdastSchema.d.ts +0 -385
  299. package/dist/schemas/mdast/MdastSchema.d.ts.map +0 -1
  300. package/dist/schemas/mdast/MdastSchema.js +0 -266
  301. package/dist/schemas/mdast/MdastSchema.js.map +0 -1
  302. package/dist/schemas/mdast/index.d.ts +0 -10
  303. package/dist/schemas/mdast/index.d.ts.map +0 -1
  304. package/dist/schemas/mdast/index.js +0 -4
  305. package/dist/schemas/mdast/index.js.map +0 -1
  306. package/dist/schemas/mdast/mdastToString.d.ts +0 -13
  307. package/dist/schemas/mdast/mdastToString.d.ts.map +0 -1
  308. package/dist/schemas/mdast/mdastToString.js +0 -85
  309. package/dist/schemas/mdast/mdastToString.js.map +0 -1
  310. package/dist/schemas/nodes/block/BlockSchema.d.ts +0 -43
  311. package/dist/schemas/nodes/block/BlockSchema.d.ts.map +0 -1
  312. package/dist/schemas/nodes/block/BlockSchema.js +0 -634
  313. package/dist/schemas/nodes/block/BlockSchema.js.map +0 -1
  314. package/dist/schemas/nodes/block/index.d.ts +0 -7
  315. package/dist/schemas/nodes/block/index.d.ts.map +0 -1
  316. package/dist/schemas/nodes/block/index.js +0 -7
  317. package/dist/schemas/nodes/block/index.js.map +0 -1
  318. package/dist/schemas/nodes/index.d.ts +0 -9
  319. package/dist/schemas/nodes/index.d.ts.map +0 -1
  320. package/dist/schemas/nodes/index.js +0 -12
  321. package/dist/schemas/nodes/index.js.map +0 -1
  322. package/dist/schemas/nodes/inline/InlineSchema.d.ts +0 -48
  323. package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +0 -1
  324. package/dist/schemas/nodes/inline/InlineSchema.js +0 -436
  325. package/dist/schemas/nodes/inline/InlineSchema.js.map +0 -1
  326. package/dist/schemas/nodes/inline/index.d.ts +0 -7
  327. package/dist/schemas/nodes/inline/index.d.ts.map +0 -1
  328. package/dist/schemas/nodes/inline/index.js +0 -7
  329. package/dist/schemas/nodes/inline/index.js.map +0 -1
  330. package/dist/schemas/nodes/macro/MacroSchema.d.ts +0 -27
  331. package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +0 -1
  332. package/dist/schemas/nodes/macro/MacroSchema.js +0 -162
  333. package/dist/schemas/nodes/macro/MacroSchema.js.map +0 -1
  334. package/dist/schemas/nodes/macro/index.d.ts +0 -7
  335. package/dist/schemas/nodes/macro/index.d.ts.map +0 -1
  336. package/dist/schemas/nodes/macro/index.js +0 -7
  337. package/dist/schemas/nodes/macro/index.js.map +0 -1
  338. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +0 -53
  339. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +0 -1
  340. package/dist/schemas/preprocessing/ConfluencePreprocessor.js +0 -349
  341. package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +0 -1
  342. package/dist/schemas/preprocessing/index.d.ts +0 -8
  343. package/dist/schemas/preprocessing/index.d.ts.map +0 -1
  344. package/dist/schemas/preprocessing/index.js +0 -2
  345. package/dist/schemas/preprocessing/index.js.map +0 -1
  346. package/dist/serializers/ConfluenceSerializer.d.ts +0 -30
  347. package/dist/serializers/ConfluenceSerializer.d.ts.map +0 -1
  348. package/dist/serializers/ConfluenceSerializer.js +0 -551
  349. package/dist/serializers/ConfluenceSerializer.js.map +0 -1
  350. package/dist/serializers/MarkdownSerializer.d.ts +0 -34
  351. package/dist/serializers/MarkdownSerializer.d.ts.map +0 -1
  352. package/dist/serializers/MarkdownSerializer.js +0 -355
  353. package/dist/serializers/MarkdownSerializer.js.map +0 -1
  354. package/dist/serializers/index.d.ts +0 -8
  355. package/dist/serializers/index.d.ts.map +0 -1
  356. package/dist/serializers/index.js +0 -8
  357. package/dist/serializers/index.js.map +0 -1
@@ -1,8 +1,8 @@
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"
@@ -42,16 +42,16 @@ const DummyConfigLayer = ConfluenceConfigLayerFromValues({
42
42
  const DummyConfluenceClientLayer = Layer.succeed(
43
43
  ConfluenceClient,
44
44
  ConfluenceClient.of({
45
- getPage: () => Effect.dieMessage("Not configured"),
46
- getChildren: () => Effect.dieMessage("Not configured"),
47
- getAllChildren: () => Effect.dieMessage("Not configured"),
48
- createPage: () => Effect.dieMessage("Not configured"),
49
- updatePage: () => Effect.dieMessage("Not configured"),
50
- deletePage: () => Effect.dieMessage("Not configured"),
51
- getPageVersions: () => Effect.dieMessage("Not configured"),
52
- getUser: () => Effect.dieMessage("Not configured"),
53
- getSpaceId: () => Effect.dieMessage("Not configured"),
54
- 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")
55
55
  })
56
56
  )
57
57
 
@@ -59,15 +59,15 @@ const DummyConfluenceClientLayer = Layer.succeed(
59
59
  const DummySyncEngineLayer = Layer.succeed(
60
60
  SyncEngine,
61
61
  SyncEngine.of({
62
- pull: () => Effect.dieMessage("Not configured - run 'confluence clone' first"),
62
+ pull: () => Effect.die("Not configured - run 'confluence clone' first"),
63
63
  push: (_options: { dryRun: boolean; message?: string }) =>
64
- Effect.dieMessage("Not configured - run 'confluence clone' first"),
65
- 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")
66
66
  })
67
67
  )
68
68
 
69
69
  // Dummy git layer for auth/minimal
70
- const notConfigured = () => Effect.dieMessage("Not configured - run 'confluence clone' first")
70
+ const notConfigured = () => Effect.die("Not configured - run 'confluence clone' first")
71
71
  const DummyGitServiceLayer = Layer.succeed(
72
72
  GitService,
73
73
  GitService.of({
@@ -107,22 +107,22 @@ const DummyGitServiceLayer = Layer.succeed(
107
107
  const DummyConfluenceAuthLayer = Layer.succeed(
108
108
  ConfluenceAuth,
109
109
  ConfluenceAuth.of({
110
- configure: () => Effect.dieMessage("Not configured"),
110
+ configure: () => Effect.die("Not configured"),
111
111
  isConfigured: () => Effect.succeed(false),
112
- login: () => Effect.dieMessage("Not configured"),
113
- logout: () => Effect.dieMessage("Not configured"),
114
- getAccessToken: () => Effect.dieMessage("Not configured"),
115
- 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"),
116
116
  getCurrentUser: () => Effect.succeed(null),
117
117
  isLoggedIn: () => Effect.succeed(false)
118
118
  })
119
119
  )
120
120
 
121
121
  // Auth layer with HTTP client
122
- const AuthLive = ConfluenceAuthLayer.pipe(Layer.provide(NodeHttpClient.layer))
122
+ const AuthLive = ConfluenceAuthLayer.pipe(Layer.provide(NodeHttpClient.layerFetch))
123
123
 
124
124
  // Build client layer dynamically based on auth
125
- const ConfluenceClientLive = Layer.unwrapEffect(
125
+ const ConfluenceClientLive = Layer.unwrap(
126
126
  Effect.gen(function*() {
127
127
  const auth = yield* getAuth()
128
128
  const config = yield* ConfluenceConfig
@@ -147,8 +147,8 @@ export const AppLayer = SyncEngineLayer.pipe(
147
147
  Layer.provideMerge(LocalFileSystemLayer),
148
148
  Layer.provideMerge(ConfluenceConfigLayer()),
149
149
  Layer.provideMerge(AuthLive),
150
- Layer.provideMerge(NodeHttpClient.layer),
151
- Layer.provideMerge(NodeContext.layer)
150
+ Layer.provideMerge(NodeHttpClient.layerFetch),
151
+ Layer.provideMerge(NodeServices.layer)
152
152
  )
153
153
 
154
154
  /**
@@ -162,8 +162,8 @@ export const AuthOnlyLayer = DummySyncEngineLayer.pipe(
162
162
  Layer.provideMerge(ConverterPipeline),
163
163
  Layer.provideMerge(LocalFileSystemLayer),
164
164
  Layer.provideMerge(DummyConfigLayer),
165
- Layer.provideMerge(NodeHttpClient.layer),
166
- Layer.provideMerge(NodeContext.layer)
165
+ Layer.provideMerge(NodeHttpClient.layerFetch),
166
+ Layer.provideMerge(NodeServices.layer)
167
167
  )
168
168
 
169
169
  /**
@@ -178,7 +178,7 @@ export const MinimalLayer = DummySyncEngineLayer.pipe(
178
178
  Layer.provideMerge(LocalFileSystemLayer),
179
179
  Layer.provideMerge(DummyConfigLayer),
180
180
  Layer.provideMerge(NodeTerminal.layer),
181
- Layer.provideMerge(NodeContext.layer)
181
+ Layer.provideMerge(NodeServices.layer)
182
182
  )
183
183
 
184
184
  /**
@@ -192,16 +192,32 @@ export const CloneLayer = DummySyncEngineLayer.pipe(
192
192
  Layer.provideMerge(ConverterPipeline),
193
193
  Layer.provideMerge(LocalFileSystemLayer),
194
194
  Layer.provideMerge(DummyConfigLayer),
195
- Layer.provideMerge(NodeHttpClient.layer),
195
+ Layer.provideMerge(NodeHttpClient.layerFetch),
196
196
  Layer.provideMerge(NodeTerminal.layer),
197
- Layer.provideMerge(NodeContext.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),
209
+ Layer.provideMerge(LocalFileSystemLayer),
210
+ Layer.provideMerge(DummyConfigLayer),
211
+ Layer.provideMerge(NodeHttpClient.layerFetch),
212
+ Layer.provideMerge(NodeTerminal.layer),
213
+ Layer.provideMerge(NodeServices.layer)
198
214
  )
199
215
 
200
216
  /**
201
217
  * Determine which layer to use based on command.
202
218
  */
203
- export const getLayerType = (): "full" | "auth" | "clone" | "minimal" => {
204
- const cmd = process.argv[2]
219
+ export const getLayerType = (argv: ReadonlyArray<string>): "full" | "auth" | "clone" | "fetch" | "minimal" => {
220
+ const cmd = argv[0]
205
221
  // auth commands need auth layer only
206
222
  if (cmd === "auth") {
207
223
  return "auth"
@@ -210,8 +226,12 @@ export const getLayerType = (): "full" | "auth" | "clone" | "minimal" => {
210
226
  if (cmd === "clone") {
211
227
  return "clone"
212
228
  }
213
- // --help, -h, --version don't need config
214
- 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") {
215
235
  return "minimal"
216
236
  }
217
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) {
@@ -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
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import * as Effect from "effect/Effect"
8
8
  import * as Schema from "effect/Schema"
9
- import matter from "gray-matter"
9
+ import * as yaml from "js-yaml"
10
10
  import { FrontMatterError } from "../ConfluenceError.js"
11
11
  import type { NewPageFrontMatter, PageFrontMatter } from "../Schemas.js"
12
12
  import { NewPageFrontMatterSchema, PageFrontMatterSchema } from "../Schemas.js"
@@ -20,6 +20,33 @@ export interface ParsedMarkdown {
20
20
  readonly isNew: boolean
21
21
  }
22
22
 
23
+ const parseRawMarkdown = (content: string): { readonly data: Record<string, unknown>; readonly content: string } => {
24
+ if (!content.startsWith("---\n") && !content.startsWith("---\r\n")) {
25
+ return { data: {}, content }
26
+ }
27
+
28
+ const newline = content.startsWith("---\r\n") ? "\r\n" : "\n"
29
+ const headerStart = 3 + newline.length
30
+ const closingMarker = `${newline}---`
31
+ const closingStart = content.indexOf(closingMarker, headerStart)
32
+ if (closingStart === -1) {
33
+ return { data: {}, content }
34
+ }
35
+
36
+ const header = content.slice(headerStart, closingStart)
37
+ const afterClosingStart = closingStart + closingMarker.length
38
+ const afterClosing = content.startsWith("\r\n", afterClosingStart)
39
+ ? content.slice(afterClosingStart + 2)
40
+ : content.startsWith("\n", afterClosingStart)
41
+ ? content.slice(afterClosingStart + 1)
42
+ : content.slice(afterClosingStart)
43
+ const loaded = yaml.load(header)
44
+ const data = loaded !== null && typeof loaded === "object" && !Array.isArray(loaded)
45
+ ? loaded as Record<string, unknown>
46
+ : {}
47
+ return { data, content: afterClosing }
48
+ }
49
+
23
50
  /**
24
51
  * Parse a markdown file with YAML front-matter.
25
52
  *
@@ -35,7 +62,7 @@ export const parseMarkdown = (
35
62
  ): Effect.Effect<ParsedMarkdown, FrontMatterError> =>
36
63
  Effect.gen(function*() {
37
64
  const parsed = yield* Effect.try({
38
- try: () => matter(content),
65
+ try: () => parseRawMarkdown(content),
39
66
  catch: (cause) => new FrontMatterError({ path: filePath, cause })
40
67
  })
41
68
 
@@ -49,21 +76,21 @@ export const parseMarkdown = (
49
76
  }
50
77
 
51
78
  // Try to parse as existing page front-matter
52
- const existingResult = yield* Schema.decodeUnknown(PageFrontMatterSchema)(parsed.data).pipe(
79
+ const existingResult = yield* Schema.decodeUnknownEffect(PageFrontMatterSchema)(parsed.data).pipe(
53
80
  Effect.map((fm) => ({
54
81
  frontMatter: fm,
55
82
  content: parsed.content.trim(),
56
83
  isNew: false
57
84
  })),
58
- Effect.catchAll(() =>
85
+ Effect.catchCause(() =>
59
86
  // Try to parse as new page front-matter
60
- Schema.decodeUnknown(NewPageFrontMatterSchema)(parsed.data).pipe(
87
+ Schema.decodeUnknownEffect(NewPageFrontMatterSchema)(parsed.data).pipe(
61
88
  Effect.map((fm) => ({
62
89
  frontMatter: fm as NewPageFrontMatter,
63
90
  content: parsed.content.trim(),
64
91
  isNew: true
65
92
  })),
66
- Effect.catchAll((cause) => Effect.fail(new FrontMatterError({ path: filePath, cause })))
93
+ Effect.catchCause((cause) => Effect.fail(new FrontMatterError({ path: filePath, cause })))
67
94
  )
68
95
  )
69
96
  )
@@ -94,7 +121,7 @@ export const serializeMarkdown = (
94
121
  contentHash: frontMatter.contentHash
95
122
  }
96
123
 
97
- return matter.stringify(content, fm)
124
+ return stringifyFrontmatter(content, fm)
98
125
  }
99
126
 
100
127
  /**
@@ -115,5 +142,15 @@ export const serializeNewPageMarkdown = (
115
142
  ...(frontMatter.parentId !== undefined ? { parentId: frontMatter.parentId } : {})
116
143
  }
117
144
 
118
- return matter.stringify(content, fm)
145
+ return stringifyFrontmatter(content, fm)
146
+ }
147
+
148
+ const stringifyFrontmatter = (content: string, frontMatter: Record<string, unknown>): string => {
149
+ const header = yaml.dump(frontMatter, {
150
+ lineWidth: -1,
151
+ noRefs: true,
152
+ sortKeys: false
153
+ }).trimEnd()
154
+ const body = content.endsWith("\n") ? content : `${content}\n`
155
+ return `---\n${header}\n---\n${body}`
119
156
  }