@knpkv/confluence-to-markdown 0.2.0 → 0.4.1

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 (336) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +282 -14
  4. package/dist/ConfluenceAuth.d.ts +76 -0
  5. package/dist/ConfluenceAuth.d.ts.map +1 -0
  6. package/dist/ConfluenceAuth.js +356 -0
  7. package/dist/ConfluenceAuth.js.map +1 -0
  8. package/dist/ConfluenceClient.d.ts +26 -2
  9. package/dist/ConfluenceClient.d.ts.map +1 -1
  10. package/dist/ConfluenceClient.js +98 -92
  11. package/dist/ConfluenceClient.js.map +1 -1
  12. package/dist/ConfluenceConfig.d.ts +4 -24
  13. package/dist/ConfluenceConfig.d.ts.map +1 -1
  14. package/dist/ConfluenceConfig.js +45 -7
  15. package/dist/ConfluenceConfig.js.map +1 -1
  16. package/dist/ConfluenceError.d.ts +89 -6
  17. package/dist/ConfluenceError.d.ts.map +1 -1
  18. package/dist/ConfluenceError.js +88 -5
  19. package/dist/ConfluenceError.js.map +1 -1
  20. package/dist/GitError.d.ts +103 -0
  21. package/dist/GitError.d.ts.map +1 -0
  22. package/dist/GitError.js +85 -0
  23. package/dist/GitError.js.map +1 -0
  24. package/dist/GitService.d.ts +175 -0
  25. package/dist/GitService.d.ts.map +1 -0
  26. package/dist/GitService.js +431 -0
  27. package/dist/GitService.js.map +1 -0
  28. package/dist/LocalFileSystem.d.ts +29 -4
  29. package/dist/LocalFileSystem.d.ts.map +1 -1
  30. package/dist/LocalFileSystem.js +80 -6
  31. package/dist/LocalFileSystem.js.map +1 -1
  32. package/dist/MarkdownConverter.d.ts +49 -2
  33. package/dist/MarkdownConverter.d.ts.map +1 -1
  34. package/dist/MarkdownConverter.js +73 -111
  35. package/dist/MarkdownConverter.js.map +1 -1
  36. package/dist/SchemaConverterError.d.ts +108 -0
  37. package/dist/SchemaConverterError.d.ts.map +1 -0
  38. package/dist/SchemaConverterError.js +84 -0
  39. package/dist/SchemaConverterError.js.map +1 -0
  40. package/dist/Schemas.d.ts +225 -1
  41. package/dist/Schemas.d.ts.map +1 -1
  42. package/dist/Schemas.js +155 -6
  43. package/dist/Schemas.js.map +1 -1
  44. package/dist/SyncEngine.d.ts +30 -20
  45. package/dist/SyncEngine.d.ts.map +1 -1
  46. package/dist/SyncEngine.js +566 -117
  47. package/dist/SyncEngine.js.map +1 -1
  48. package/dist/ast/BlockNode.d.ts +468 -0
  49. package/dist/ast/BlockNode.d.ts.map +1 -0
  50. package/dist/ast/BlockNode.js +319 -0
  51. package/dist/ast/BlockNode.js.map +1 -0
  52. package/dist/ast/Document.d.ts +244 -0
  53. package/dist/ast/Document.d.ts.map +1 -0
  54. package/dist/ast/Document.js +69 -0
  55. package/dist/ast/Document.js.map +1 -0
  56. package/dist/ast/InlineNode.d.ts +477 -0
  57. package/dist/ast/InlineNode.d.ts.map +1 -0
  58. package/dist/ast/InlineNode.js +263 -0
  59. package/dist/ast/InlineNode.js.map +1 -0
  60. package/dist/ast/MacroNode.d.ts +267 -0
  61. package/dist/ast/MacroNode.d.ts.map +1 -0
  62. package/dist/ast/MacroNode.js +164 -0
  63. package/dist/ast/MacroNode.js.map +1 -0
  64. package/dist/ast/index.d.ts +10 -0
  65. package/dist/ast/index.d.ts.map +1 -0
  66. package/dist/ast/index.js +14 -0
  67. package/dist/ast/index.js.map +1 -0
  68. package/dist/bin.js +33 -149
  69. package/dist/bin.js.map +1 -1
  70. package/dist/commands/auth.d.ts +15 -0
  71. package/dist/commands/auth.d.ts.map +1 -0
  72. package/dist/commands/auth.js +86 -0
  73. package/dist/commands/auth.js.map +1 -0
  74. package/dist/commands/clone.d.ts +12 -0
  75. package/dist/commands/clone.d.ts.map +1 -0
  76. package/dist/commands/clone.js +93 -0
  77. package/dist/commands/clone.js.map +1 -0
  78. package/dist/commands/delete.d.ts +13 -0
  79. package/dist/commands/delete.d.ts.map +1 -0
  80. package/dist/commands/delete.js +48 -0
  81. package/dist/commands/delete.js.map +1 -0
  82. package/dist/commands/errorHandler.d.ts +14 -0
  83. package/dist/commands/errorHandler.d.ts.map +1 -0
  84. package/dist/commands/errorHandler.js +33 -0
  85. package/dist/commands/errorHandler.js.map +1 -0
  86. package/dist/commands/git.d.ts +22 -0
  87. package/dist/commands/git.d.ts.map +1 -0
  88. package/dist/commands/git.js +72 -0
  89. package/dist/commands/git.js.map +1 -0
  90. package/dist/commands/index.d.ts +11 -0
  91. package/dist/commands/index.d.ts.map +1 -0
  92. package/dist/commands/index.js +11 -0
  93. package/dist/commands/index.js.map +1 -0
  94. package/dist/commands/layers.d.ts +31 -0
  95. package/dist/commands/layers.d.ts.map +1 -0
  96. package/dist/commands/layers.js +137 -0
  97. package/dist/commands/layers.js.map +1 -0
  98. package/dist/commands/new.d.ts +9 -0
  99. package/dist/commands/new.d.ts.map +1 -0
  100. package/dist/commands/new.js +80 -0
  101. package/dist/commands/new.js.map +1 -0
  102. package/dist/commands/pageTree.d.ts +18 -0
  103. package/dist/commands/pageTree.d.ts.map +1 -0
  104. package/dist/commands/pageTree.js +20 -0
  105. package/dist/commands/pageTree.js.map +1 -0
  106. package/dist/commands/shared.d.ts +15 -0
  107. package/dist/commands/shared.d.ts.map +1 -0
  108. package/dist/commands/shared.js +27 -0
  109. package/dist/commands/shared.js.map +1 -0
  110. package/dist/commands/sync.d.ts +15 -0
  111. package/dist/commands/sync.d.ts.map +1 -0
  112. package/dist/commands/sync.js +101 -0
  113. package/dist/commands/sync.js.map +1 -0
  114. package/dist/index.d.ts +10 -1
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +14 -0
  117. package/dist/index.js.map +1 -1
  118. package/dist/internal/NodeLayers.d.ts +7 -0
  119. package/dist/internal/NodeLayers.d.ts.map +1 -0
  120. package/dist/internal/NodeLayers.js +19 -0
  121. package/dist/internal/NodeLayers.js.map +1 -0
  122. package/dist/internal/frontmatter.d.ts +10 -0
  123. package/dist/internal/frontmatter.d.ts.map +1 -1
  124. package/dist/internal/frontmatter.js +16 -0
  125. package/dist/internal/frontmatter.js.map +1 -1
  126. package/dist/internal/gitCommands.d.ts +78 -0
  127. package/dist/internal/gitCommands.d.ts.map +1 -0
  128. package/dist/internal/gitCommands.js +156 -0
  129. package/dist/internal/gitCommands.js.map +1 -0
  130. package/dist/internal/hashUtils.d.ts +42 -1
  131. package/dist/internal/hashUtils.d.ts.map +1 -1
  132. package/dist/internal/hashUtils.js +38 -2
  133. package/dist/internal/hashUtils.js.map +1 -1
  134. package/dist/internal/oauthServer.d.ts +55 -0
  135. package/dist/internal/oauthServer.d.ts.map +1 -0
  136. package/dist/internal/oauthServer.js +110 -0
  137. package/dist/internal/oauthServer.js.map +1 -0
  138. package/dist/internal/pathUtils.d.ts +21 -4
  139. package/dist/internal/pathUtils.d.ts.map +1 -1
  140. package/dist/internal/pathUtils.js +24 -13
  141. package/dist/internal/pathUtils.js.map +1 -1
  142. package/dist/internal/tokenStorage.d.ts +75 -0
  143. package/dist/internal/tokenStorage.d.ts.map +1 -0
  144. package/dist/internal/tokenStorage.js +149 -0
  145. package/dist/internal/tokenStorage.js.map +1 -0
  146. package/dist/internal/userCache.d.ts +42 -0
  147. package/dist/internal/userCache.d.ts.map +1 -0
  148. package/dist/internal/userCache.js +51 -0
  149. package/dist/internal/userCache.js.map +1 -0
  150. package/dist/parsers/ConfluenceParser.d.ts +26 -0
  151. package/dist/parsers/ConfluenceParser.d.ts.map +1 -0
  152. package/dist/parsers/ConfluenceParser.js +792 -0
  153. package/dist/parsers/ConfluenceParser.js.map +1 -0
  154. package/dist/parsers/MarkdownParser.d.ts +26 -0
  155. package/dist/parsers/MarkdownParser.d.ts.map +1 -0
  156. package/dist/parsers/MarkdownParser.js +873 -0
  157. package/dist/parsers/MarkdownParser.js.map +1 -0
  158. package/dist/parsers/index.d.ts +8 -0
  159. package/dist/parsers/index.d.ts.map +1 -0
  160. package/dist/parsers/index.js +8 -0
  161. package/dist/parsers/index.js.map +1 -0
  162. package/dist/schemas/ConfluenceSchema.d.ts +21 -0
  163. package/dist/schemas/ConfluenceSchema.d.ts.map +1 -0
  164. package/dist/schemas/ConfluenceSchema.js +38 -0
  165. package/dist/schemas/ConfluenceSchema.js.map +1 -0
  166. package/dist/schemas/ConversionSchema.d.ts +35 -0
  167. package/dist/schemas/ConversionSchema.d.ts.map +1 -0
  168. package/dist/schemas/ConversionSchema.js +208 -0
  169. package/dist/schemas/ConversionSchema.js.map +1 -0
  170. package/dist/schemas/MarkdownSchema.d.ts +21 -0
  171. package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
  172. package/dist/schemas/MarkdownSchema.js +38 -0
  173. package/dist/schemas/MarkdownSchema.js.map +1 -0
  174. package/dist/schemas/hast/HastFromHtml.d.ts +27 -0
  175. package/dist/schemas/hast/HastFromHtml.d.ts.map +1 -0
  176. package/dist/schemas/hast/HastFromHtml.js +107 -0
  177. package/dist/schemas/hast/HastFromHtml.js.map +1 -0
  178. package/dist/schemas/hast/HastSchema.d.ts +195 -0
  179. package/dist/schemas/hast/HastSchema.d.ts.map +1 -0
  180. package/dist/schemas/hast/HastSchema.js +183 -0
  181. package/dist/schemas/hast/HastSchema.js.map +1 -0
  182. package/dist/schemas/hast/index.d.ts +9 -0
  183. package/dist/schemas/hast/index.d.ts.map +1 -0
  184. package/dist/schemas/hast/index.js +3 -0
  185. package/dist/schemas/hast/index.js.map +1 -0
  186. package/dist/schemas/index.d.ts +14 -0
  187. package/dist/schemas/index.d.ts.map +1 -0
  188. package/dist/schemas/index.js +16 -0
  189. package/dist/schemas/index.js.map +1 -0
  190. package/dist/schemas/mdast/MdastFromMarkdown.d.ts +30 -0
  191. package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +1 -0
  192. package/dist/schemas/mdast/MdastFromMarkdown.js +79 -0
  193. package/dist/schemas/mdast/MdastFromMarkdown.js.map +1 -0
  194. package/dist/schemas/mdast/MdastSchema.d.ts +385 -0
  195. package/dist/schemas/mdast/MdastSchema.d.ts.map +1 -0
  196. package/dist/schemas/mdast/MdastSchema.js +266 -0
  197. package/dist/schemas/mdast/MdastSchema.js.map +1 -0
  198. package/dist/schemas/mdast/index.d.ts +10 -0
  199. package/dist/schemas/mdast/index.d.ts.map +1 -0
  200. package/dist/schemas/mdast/index.js +4 -0
  201. package/dist/schemas/mdast/index.js.map +1 -0
  202. package/dist/schemas/mdast/mdastToString.d.ts +13 -0
  203. package/dist/schemas/mdast/mdastToString.d.ts.map +1 -0
  204. package/dist/schemas/mdast/mdastToString.js +85 -0
  205. package/dist/schemas/mdast/mdastToString.js.map +1 -0
  206. package/dist/schemas/nodes/block/BlockSchema.d.ts +43 -0
  207. package/dist/schemas/nodes/block/BlockSchema.d.ts.map +1 -0
  208. package/dist/schemas/nodes/block/BlockSchema.js +634 -0
  209. package/dist/schemas/nodes/block/BlockSchema.js.map +1 -0
  210. package/dist/schemas/nodes/block/index.d.ts +7 -0
  211. package/dist/schemas/nodes/block/index.d.ts.map +1 -0
  212. package/dist/schemas/nodes/block/index.js +7 -0
  213. package/dist/schemas/nodes/block/index.js.map +1 -0
  214. package/dist/schemas/nodes/index.d.ts +9 -0
  215. package/dist/schemas/nodes/index.d.ts.map +1 -0
  216. package/dist/schemas/nodes/index.js +12 -0
  217. package/dist/schemas/nodes/index.js.map +1 -0
  218. package/dist/schemas/nodes/inline/InlineSchema.d.ts +48 -0
  219. package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +1 -0
  220. package/dist/schemas/nodes/inline/InlineSchema.js +436 -0
  221. package/dist/schemas/nodes/inline/InlineSchema.js.map +1 -0
  222. package/dist/schemas/nodes/inline/index.d.ts +7 -0
  223. package/dist/schemas/nodes/inline/index.d.ts.map +1 -0
  224. package/dist/schemas/nodes/inline/index.js +7 -0
  225. package/dist/schemas/nodes/inline/index.js.map +1 -0
  226. package/dist/schemas/nodes/macro/MacroSchema.d.ts +27 -0
  227. package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +1 -0
  228. package/dist/schemas/nodes/macro/MacroSchema.js +162 -0
  229. package/dist/schemas/nodes/macro/MacroSchema.js.map +1 -0
  230. package/dist/schemas/nodes/macro/index.d.ts +7 -0
  231. package/dist/schemas/nodes/macro/index.d.ts.map +1 -0
  232. package/dist/schemas/nodes/macro/index.js +7 -0
  233. package/dist/schemas/nodes/macro/index.js.map +1 -0
  234. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +24 -0
  235. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -0
  236. package/dist/schemas/preprocessing/ConfluencePreprocessor.js +351 -0
  237. package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -0
  238. package/dist/schemas/preprocessing/index.d.ts +8 -0
  239. package/dist/schemas/preprocessing/index.d.ts.map +1 -0
  240. package/dist/schemas/preprocessing/index.js +2 -0
  241. package/dist/schemas/preprocessing/index.js.map +1 -0
  242. package/dist/serializers/ConfluenceSerializer.d.ts +30 -0
  243. package/dist/serializers/ConfluenceSerializer.d.ts.map +1 -0
  244. package/dist/serializers/ConfluenceSerializer.js +551 -0
  245. package/dist/serializers/ConfluenceSerializer.js.map +1 -0
  246. package/dist/serializers/MarkdownSerializer.d.ts +34 -0
  247. package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
  248. package/dist/serializers/MarkdownSerializer.js +355 -0
  249. package/dist/serializers/MarkdownSerializer.js.map +1 -0
  250. package/dist/serializers/index.d.ts +8 -0
  251. package/dist/serializers/index.d.ts.map +1 -0
  252. package/dist/serializers/index.js +8 -0
  253. package/dist/serializers/index.js.map +1 -0
  254. package/package.json +27 -16
  255. package/src/ConfluenceAuth.ts +571 -0
  256. package/src/ConfluenceClient.ts +188 -156
  257. package/src/ConfluenceConfig.ts +63 -7
  258. package/src/ConfluenceError.ts +110 -14
  259. package/src/GitError.ts +92 -0
  260. package/src/GitService.ts +859 -0
  261. package/src/LocalFileSystem.ts +179 -9
  262. package/src/MarkdownConverter.ts +126 -122
  263. package/src/SchemaConverterError.ts +108 -0
  264. package/src/Schemas.ts +223 -6
  265. package/src/SyncEngine.ts +745 -162
  266. package/src/ast/BlockNode.ts +425 -0
  267. package/src/ast/Document.ts +90 -0
  268. package/src/ast/InlineNode.ts +323 -0
  269. package/src/ast/MacroNode.ts +245 -0
  270. package/src/ast/index.ts +83 -0
  271. package/src/bin.ts +50 -249
  272. package/src/commands/auth.ts +117 -0
  273. package/src/commands/clone.ts +145 -0
  274. package/src/commands/delete.ts +57 -0
  275. package/src/commands/errorHandler.ts +32 -0
  276. package/src/commands/git.ts +114 -0
  277. package/src/commands/index.ts +10 -0
  278. package/src/commands/layers.ts +211 -0
  279. package/src/commands/new.ts +99 -0
  280. package/src/commands/pageTree.ts +40 -0
  281. package/src/commands/shared.ts +35 -0
  282. package/src/commands/sync.ts +129 -0
  283. package/src/index.ts +21 -1
  284. package/src/internal/NodeLayers.ts +21 -0
  285. package/src/internal/frontmatter.ts +21 -0
  286. package/src/internal/gitCommands.ts +229 -0
  287. package/src/internal/hashUtils.ts +65 -3
  288. package/src/internal/oauthServer.ts +199 -0
  289. package/src/internal/pathUtils.ts +34 -17
  290. package/src/internal/tokenStorage.ts +240 -0
  291. package/src/internal/userCache.ts +90 -0
  292. package/src/parsers/ConfluenceParser.ts +950 -0
  293. package/src/parsers/MarkdownParser.ts +1198 -0
  294. package/src/parsers/index.ts +8 -0
  295. package/src/schemas/ConfluenceSchema.ts +56 -0
  296. package/src/schemas/ConversionSchema.ts +318 -0
  297. package/src/schemas/MarkdownSchema.ts +56 -0
  298. package/src/schemas/hast/HastFromHtml.ts +153 -0
  299. package/src/schemas/hast/HastSchema.ts +274 -0
  300. package/src/schemas/hast/index.ts +35 -0
  301. package/src/schemas/index.ts +20 -0
  302. package/src/schemas/mdast/MdastFromMarkdown.ts +118 -0
  303. package/src/schemas/mdast/MdastSchema.ts +566 -0
  304. package/src/schemas/mdast/index.ts +59 -0
  305. package/src/schemas/mdast/mdastToString.ts +102 -0
  306. package/src/schemas/nodes/block/BlockSchema.ts +773 -0
  307. package/src/schemas/nodes/block/index.ts +13 -0
  308. package/src/schemas/nodes/index.ts +20 -0
  309. package/src/schemas/nodes/inline/InlineSchema.ts +523 -0
  310. package/src/schemas/nodes/inline/index.ts +14 -0
  311. package/src/schemas/nodes/macro/MacroSchema.ts +226 -0
  312. package/src/schemas/nodes/macro/index.ts +6 -0
  313. package/src/schemas/preprocessing/ConfluencePreprocessor.ts +446 -0
  314. package/src/schemas/preprocessing/index.ts +8 -0
  315. package/src/serializers/ConfluenceSerializer.ts +717 -0
  316. package/src/serializers/MarkdownSerializer.ts +493 -0
  317. package/src/serializers/index.ts +8 -0
  318. package/test/GitService.test.ts +209 -0
  319. package/test/MarkdownConverter.test.ts +37 -3
  320. package/test/Schemas.test.ts +97 -2
  321. package/test/ast/BlockNode.test.ts +265 -0
  322. package/test/ast/Document.test.ts +126 -0
  323. package/test/ast/InlineNode.test.ts +161 -0
  324. package/test/fixtures/integration-test.html.fixture +103 -0
  325. package/test/fixtures/integration-test.md.expected +257 -0
  326. package/test/integration.test.ts +269 -0
  327. package/test/oauthServer.test.ts +50 -0
  328. package/test/parsers/ConfluenceParser.test.ts +283 -0
  329. package/test/schemas/ConfluencePreprocessor.test.ts +180 -0
  330. package/test/schemas/ConversionSchema.test.ts +159 -0
  331. package/test/schemas/HastSchema.test.ts +138 -0
  332. package/test/schemas/MdastSchema.test.ts +145 -0
  333. package/test/schemas/nodes/block/BlockSchema.test.ts +173 -0
  334. package/test/schemas/nodes/inline/InlineSchema.test.ts +198 -0
  335. package/test/schemas/nodes/macro/MacroSchema.test.ts +142 -0
  336. package/test/tokenStorage.test.ts +99 -0
@@ -0,0 +1,859 @@
1
+ /**
2
+ * Git operations service for internal version control.
3
+ *
4
+ * @module
5
+ */
6
+ import * as NodeCommandExecutor from "@effect/platform-node/NodeCommandExecutor"
7
+ import * as NodeContext from "@effect/platform-node/NodeContext"
8
+ import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"
9
+ import * as NodePath from "@effect/platform-node/NodePath"
10
+ import * as CommandExecutor from "@effect/platform/CommandExecutor"
11
+ import type * as PlatformError from "@effect/platform/Error"
12
+ import * as FileSystem from "@effect/platform/FileSystem"
13
+ import * as Path from "@effect/platform/Path"
14
+ import * as Context from "effect/Context"
15
+ import * as Effect from "effect/Effect"
16
+ import * as Layer from "effect/Layer"
17
+ import {
18
+ GitError,
19
+ GitMergeConflictError,
20
+ GitNoChangesError,
21
+ GitNotInitializedError,
22
+ GitNotInstalledError
23
+ } from "./GitError.js"
24
+ import {
25
+ getConflictedFiles,
26
+ GIT_LOG_FORMAT,
27
+ type GitLogEntry,
28
+ type GitStatusEntry,
29
+ parseGitLog,
30
+ parseGitStatus,
31
+ runGit,
32
+ runGitAllowEmpty
33
+ } from "./internal/gitCommands.js"
34
+
35
+ /**
36
+ * Options for git commit.
37
+ *
38
+ * @category Types
39
+ */
40
+ export interface GitCommitOptions {
41
+ readonly message: string
42
+ readonly author?: {
43
+ readonly name: string
44
+ readonly email: string
45
+ }
46
+ readonly date?: Date
47
+ }
48
+
49
+ /**
50
+ * Options for git log.
51
+ *
52
+ * @category Types
53
+ */
54
+ export interface GitLogOptions {
55
+ readonly oneline?: boolean
56
+ readonly n?: number
57
+ readonly since?: string
58
+ readonly file?: string
59
+ }
60
+
61
+ /**
62
+ * Options for git diff.
63
+ *
64
+ * @category Types
65
+ */
66
+ export interface GitDiffOptions {
67
+ readonly staged?: boolean
68
+ readonly commit?: string
69
+ readonly commit2?: string
70
+ readonly file?: string
71
+ }
72
+
73
+ /**
74
+ * Options for git commit --amend.
75
+ *
76
+ * @category Types
77
+ */
78
+ export interface GitAmendOptions {
79
+ readonly noEdit?: boolean
80
+ readonly message?: string
81
+ }
82
+
83
+ /**
84
+ * Options for git reset.
85
+ *
86
+ * @category Types
87
+ */
88
+ export interface GitResetOptions {
89
+ readonly hard?: boolean
90
+ }
91
+
92
+ /**
93
+ * Git status result.
94
+ *
95
+ * @category Types
96
+ */
97
+ export interface GitStatus {
98
+ readonly entries: ReadonlyArray<GitStatusEntry>
99
+ readonly hasChanges: boolean
100
+ readonly hasConflicts: boolean
101
+ readonly conflictedFiles: ReadonlyArray<string>
102
+ }
103
+
104
+ /**
105
+ * Union of all git-related errors.
106
+ *
107
+ * @category Types
108
+ */
109
+ export type GitServiceError =
110
+ | GitError
111
+ | GitNotInstalledError
112
+ | GitNotInitializedError
113
+ | GitNoChangesError
114
+ | GitMergeConflictError
115
+
116
+ /**
117
+ * Git service interface.
118
+ *
119
+ * @category Service
120
+ */
121
+ export interface GitServiceShape {
122
+ /** Validate git is installed, return version. */
123
+ readonly validateGit: () => Effect.Effect<string, GitNotInstalledError>
124
+
125
+ /** Check if .confluence/.git exists. */
126
+ readonly isInitialized: () => Effect.Effect<boolean>
127
+
128
+ /** Initialize git repo in .confluence/. */
129
+ readonly init: () => Effect.Effect<void, GitServiceError>
130
+
131
+ /** Stage all files. */
132
+ readonly addAll: () => Effect.Effect<void, GitServiceError>
133
+
134
+ /** Commit with options. */
135
+ readonly commit: (options: GitCommitOptions) => Effect.Effect<string, GitServiceError>
136
+
137
+ /** Get status. */
138
+ readonly status: () => Effect.Effect<GitStatus, GitServiceError>
139
+
140
+ /** Get log. */
141
+ readonly log: (options?: GitLogOptions) => Effect.Effect<ReadonlyArray<GitLogEntry>, GitServiceError>
142
+
143
+ /** Get diff. */
144
+ readonly diff: (options?: GitDiffOptions) => Effect.Effect<string, GitServiceError>
145
+
146
+ /** Check for merge conflicts. */
147
+ readonly hasConflicts: () => Effect.Effect<boolean, GitServiceError>
148
+
149
+ /** Continue merge after conflict resolution. */
150
+ readonly mergeContinue: () => Effect.Effect<void, GitServiceError>
151
+
152
+ /** Copy files from docsPath to .confluence/. */
153
+ readonly syncFromDocs: (
154
+ docsPath: string,
155
+ trackedPaths: ReadonlyArray<string>
156
+ ) => Effect.Effect<void, GitServiceError>
157
+
158
+ /** Copy files from .confluence/ to docsPath. */
159
+ readonly syncToDocs: (
160
+ docsPath: string,
161
+ trackedPaths: ReadonlyArray<string>
162
+ ) => Effect.Effect<void, GitServiceError>
163
+
164
+ /** Get current HEAD commit hash. */
165
+ readonly getHead: () => Effect.Effect<string, GitServiceError>
166
+
167
+ /** Get current branch name. */
168
+ readonly getCurrentBranch: () => Effect.Effect<string, GitServiceError>
169
+
170
+ /** Create a new branch at current HEAD. */
171
+ readonly createBranch: (name: string) => Effect.Effect<void, GitServiceError>
172
+
173
+ /** Checkout a branch or commit. */
174
+ readonly checkout: (ref: string) => Effect.Effect<void, GitServiceError>
175
+
176
+ /** Reset to a commit. */
177
+ readonly reset: (ref: string, options?: GitResetOptions) => Effect.Effect<void, GitServiceError>
178
+
179
+ /** Delete a branch. */
180
+ readonly deleteBranch: (name: string) => Effect.Effect<void, GitServiceError>
181
+
182
+ /** Get parent commit hash. */
183
+ readonly getParent: (ref: string) => Effect.Effect<string, GitServiceError>
184
+
185
+ /** Cherry-pick a commit. */
186
+ readonly cherryPick: (
187
+ ref: string,
188
+ options?: { strategy?: "ours" | "theirs" }
189
+ ) => Effect.Effect<void, GitServiceError>
190
+
191
+ /** Get files changed in a commit. */
192
+ readonly getChangedFiles: (ref: string) => Effect.Effect<ReadonlyArray<string>, GitServiceError>
193
+
194
+ /** Get file content at a specific ref. */
195
+ readonly showFile: (ref: string, filePath: string) => Effect.Effect<string, GitServiceError>
196
+
197
+ /** Amend the current commit. */
198
+ readonly amend: (options?: GitAmendOptions) => Effect.Effect<void, GitServiceError>
199
+
200
+ /** Get commits between two refs (exclusive..inclusive). */
201
+ readonly logRange: (
202
+ from: string,
203
+ to: string
204
+ ) => Effect.Effect<ReadonlyArray<GitLogEntry>, GitServiceError>
205
+
206
+ /** Check if a branch exists. */
207
+ readonly branchExists: (name: string) => Effect.Effect<boolean, GitServiceError>
208
+
209
+ /** Update a branch to point to a commit (without checkout). */
210
+ readonly updateBranch: (name: string, ref: string) => Effect.Effect<void, GitServiceError>
211
+
212
+ /** Merge a branch into current. */
213
+ readonly merge: (
214
+ branch: string,
215
+ options?: { noCommit?: boolean; message?: string }
216
+ ) => Effect.Effect<void, GitServiceError>
217
+
218
+ /** Get deleted files between two refs (files in `from` but not in `to`). */
219
+ readonly getDeletedFiles: (
220
+ from: string,
221
+ to: string,
222
+ pathPrefix?: string
223
+ ) => Effect.Effect<ReadonlyArray<string>, GitServiceError>
224
+
225
+ /** Get file content at a specific ref. */
226
+ readonly getFileContentAt: (
227
+ ref: string,
228
+ filePath: string
229
+ ) => Effect.Effect<string, GitServiceError>
230
+ }
231
+
232
+ /**
233
+ * Git service for internal version control operations.
234
+ *
235
+ * Manages a git repository at `.confluence/` for tracking synced files.
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * import { GitService } from "@knpkv/confluence-to-markdown/GitService"
240
+ * import { Effect } from "effect"
241
+ *
242
+ * const program = Effect.gen(function* () {
243
+ * const git = yield* GitService
244
+ * yield* git.init()
245
+ * yield* git.addAll()
246
+ * yield* git.commit({ message: "Initial commit" })
247
+ * })
248
+ * ```
249
+ *
250
+ * @category Service
251
+ */
252
+ export class GitService extends Context.Tag("@knpkv/confluence-to-markdown/GitService")<
253
+ GitService,
254
+ GitServiceShape
255
+ >() {}
256
+
257
+ /**
258
+ * Map PlatformError to GitError.
259
+ */
260
+ const mapFsError = (error: PlatformError.PlatformError): GitError =>
261
+ new GitError({ command: "filesystem", message: error.message })
262
+
263
+ // Dependencies layer for git operations (CommandExecutor + FileSystem + Path + NodeContext)
264
+ // NodeCommandExecutor requires FileSystem, so use provideMerge to satisfy its dependencies
265
+ const GitDepsLive = NodeCommandExecutor.layer.pipe(
266
+ Layer.provideMerge(NodeFileSystem.layer),
267
+ Layer.provideMerge(NodePath.layer),
268
+ Layer.provideMerge(NodeContext.layer)
269
+ )
270
+
271
+ /**
272
+ * Copy a single file, creating parent directories as needed.
273
+ */
274
+ const copyFileFn = (
275
+ fs: FileSystem.FileSystem,
276
+ pathService: Path.Path,
277
+ source: string,
278
+ dest: string
279
+ ): Effect.Effect<void, GitError> =>
280
+ Effect.gen(function*() {
281
+ const destDir = pathService.dirname(dest)
282
+ yield* fs.makeDirectory(destDir, { recursive: true }).pipe(Effect.catchAll(() => Effect.void))
283
+ yield* fs.copyFile(source, dest).pipe(Effect.mapError(mapFsError))
284
+ })
285
+
286
+ /**
287
+ * Copy a directory recursively.
288
+ */
289
+ const copyDirectoryFn = (
290
+ fs: FileSystem.FileSystem,
291
+ pathService: Path.Path,
292
+ source: string,
293
+ dest: string
294
+ ): Effect.Effect<void, GitError> =>
295
+ Effect.gen(function*() {
296
+ yield* fs.makeDirectory(dest, { recursive: true }).pipe(Effect.catchAll(() => Effect.void))
297
+
298
+ const entries = yield* fs.readDirectory(source).pipe(Effect.mapError(mapFsError))
299
+
300
+ for (const entry of entries) {
301
+ const sourcePath = pathService.join(source, entry)
302
+ const destPath = pathService.join(dest, entry)
303
+
304
+ const stat = yield* fs.stat(sourcePath).pipe(Effect.mapError(mapFsError))
305
+
306
+ if (stat.type === "Directory") {
307
+ yield* copyDirectoryFn(fs, pathService, sourcePath, destPath)
308
+ } else {
309
+ yield* copyFileFn(fs, pathService, sourcePath, destPath)
310
+ }
311
+ }
312
+ })
313
+
314
+ /**
315
+ * Copy all markdown files recursively.
316
+ */
317
+ const copyMarkdownFilesFn = (
318
+ fs: FileSystem.FileSystem,
319
+ pathService: Path.Path,
320
+ source: string,
321
+ dest: string
322
+ ): Effect.Effect<void, GitError> =>
323
+ Effect.gen(function*() {
324
+ const exists = yield* fs.exists(source).pipe(Effect.catchAll(() => Effect.succeed(false)))
325
+ if (!exists) {
326
+ return
327
+ }
328
+
329
+ const entries = yield* fs.readDirectory(source).pipe(Effect.mapError(mapFsError))
330
+
331
+ for (const entry of entries) {
332
+ // Skip .git directory and config.json
333
+ if (entry === ".git" || entry === "config.json") {
334
+ continue
335
+ }
336
+
337
+ const sourcePath = pathService.join(source, entry)
338
+ const destPath = pathService.join(dest, entry)
339
+
340
+ const stat = yield* fs.stat(sourcePath).pipe(Effect.mapError(mapFsError))
341
+
342
+ if (stat.type === "Directory") {
343
+ yield* copyMarkdownFilesFn(fs, pathService, sourcePath, destPath)
344
+ } else if (entry.endsWith(".md")) {
345
+ yield* copyFileFn(fs, pathService, sourcePath, destPath)
346
+ }
347
+ }
348
+ })
349
+
350
+ // Get paths helper - takes pathService as param
351
+ const getPaths = (pathService: Path.Path) => {
352
+ const cwd = process.cwd()
353
+ const confluenceDir = pathService.join(cwd, ".confluence")
354
+ const gitDir = pathService.join(confluenceDir, ".git")
355
+ return { cwd, confluenceDir, gitDir, pathService }
356
+ }
357
+
358
+ // Ensure initialized helper - takes fs and gitDir
359
+ const ensureInitializedFn = (fs: FileSystem.FileSystem, gitDir: string) =>
360
+ Effect.gen(function*() {
361
+ const initialized = yield* fs.exists(gitDir).pipe(Effect.catchAll(() => Effect.succeed(false)))
362
+ if (!initialized) {
363
+ return yield* Effect.fail(new GitNotInitializedError())
364
+ }
365
+ })
366
+
367
+ // Create the service - capture deps at construction time
368
+ const make = Effect.gen(function*() {
369
+ const fs = yield* FileSystem.FileSystem
370
+ const pathService = yield* Path.Path
371
+ const commandExecutor = yield* CommandExecutor.CommandExecutor
372
+ const paths = getPaths(pathService)
373
+ const { confluenceDir, cwd, gitDir } = paths
374
+
375
+ // Create context with captured services for providing to returned Effects
376
+ const depsContext: Context.Context<FileSystem.FileSystem | Path.Path | CommandExecutor.CommandExecutor> = Context
377
+ .empty().pipe(
378
+ Context.add(FileSystem.FileSystem, fs),
379
+ Context.add(Path.Path, pathService),
380
+ Context.add(CommandExecutor.CommandExecutor, commandExecutor)
381
+ )
382
+
383
+ // Helper to provide deps to an effect - uses any for R since we know depsContext covers all needs
384
+ const provideDeps = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E> =>
385
+ Effect.provide(effect, depsContext) as Effect.Effect<A, E>
386
+
387
+ // Ensure initialized helper using captured values
388
+ const ensureInitialized = ensureInitializedFn(fs, gitDir)
389
+
390
+ // Service implementation using captured deps - each returns Effect with deps provided
391
+ const validateGit = () =>
392
+ provideDeps(
393
+ runGit(["--version"], cwd).pipe(
394
+ Effect.map((output) => output.trim()),
395
+ Effect.catchTag("GitError", () => Effect.fail(new GitNotInstalledError({ message: "Git not found" })))
396
+ )
397
+ )
398
+
399
+ const isInitialized = () =>
400
+ Effect.succeed(fs).pipe(
401
+ Effect.flatMap((f) => f.exists(gitDir)),
402
+ Effect.catchAll(() => Effect.succeed(false))
403
+ )
404
+
405
+ const init = () =>
406
+ provideDeps(
407
+ Effect.gen(function*() {
408
+ // Check git is installed
409
+ yield* runGit(["--version"], cwd).pipe(
410
+ Effect.catchTag("GitError", () => Effect.fail(new GitNotInstalledError({ message: "Git not found" })))
411
+ )
412
+
413
+ const exists = yield* fs.exists(confluenceDir).pipe(Effect.catchAll(() => Effect.succeed(false)))
414
+ if (!exists) {
415
+ yield* fs.makeDirectory(confluenceDir, { recursive: true }).pipe(Effect.mapError(mapFsError))
416
+ }
417
+
418
+ const gitExists = yield* fs.exists(gitDir).pipe(Effect.catchAll(() => Effect.succeed(false)))
419
+ if (!gitExists) {
420
+ yield* runGit(["init"], confluenceDir)
421
+ // Configure local git user for commits (needed in CI environments)
422
+ yield* runGit(["config", "user.name", "Confluence Sync"], confluenceDir)
423
+ yield* runGit(["config", "user.email", "confluence-sync@local"], confluenceDir)
424
+ }
425
+ })
426
+ )
427
+
428
+ const addAll = () =>
429
+ provideDeps(
430
+ Effect.gen(function*() {
431
+ yield* ensureInitialized
432
+ yield* runGit(["add", "."], confluenceDir)
433
+ })
434
+ )
435
+
436
+ const commit = (options: GitCommitOptions) =>
437
+ provideDeps(
438
+ Effect.gen(function*() {
439
+ yield* ensureInitialized
440
+
441
+ // Check for changes
442
+ const statusOutput = yield* runGitAllowEmpty(["status", "--porcelain"], confluenceDir)
443
+ if (statusOutput.trim() === "") {
444
+ return yield* Effect.fail(new GitNoChangesError())
445
+ }
446
+
447
+ const args: Array<string> = ["commit", "-m", options.message]
448
+
449
+ if (options.author) {
450
+ args.push("--author", `${options.author.name} <${options.author.email}>`)
451
+ }
452
+
453
+ if (options.date) {
454
+ args.push("--date", options.date.toISOString())
455
+ }
456
+
457
+ const output = yield* runGit(args, confluenceDir)
458
+
459
+ // Extract commit hash from output
460
+ const match = output.match(/\[[\w-]+\s+([a-f0-9]+)\]/)
461
+ return match?.[1] ?? output.trim().slice(0, 7)
462
+ })
463
+ )
464
+
465
+ const status = () =>
466
+ provideDeps(
467
+ Effect.gen(function*() {
468
+ yield* ensureInitialized
469
+
470
+ const output = yield* runGitAllowEmpty(["status", "--porcelain"], confluenceDir)
471
+ const entries = parseGitStatus(output)
472
+ const conflictedFiles = getConflictedFiles(output)
473
+
474
+ return {
475
+ entries,
476
+ hasChanges: entries.length > 0,
477
+ hasConflicts: conflictedFiles.length > 0,
478
+ conflictedFiles
479
+ }
480
+ })
481
+ )
482
+
483
+ const log = (options?: GitLogOptions) =>
484
+ provideDeps(
485
+ Effect.gen(function*() {
486
+ yield* ensureInitialized
487
+
488
+ const args: Array<string> = ["log", `--format=${GIT_LOG_FORMAT}`]
489
+
490
+ if (options?.n !== undefined) {
491
+ args.push("-n", String(options.n))
492
+ }
493
+
494
+ if (options?.since) {
495
+ args.push("--since", options.since)
496
+ }
497
+
498
+ if (options?.file) {
499
+ args.push("--", options.file)
500
+ }
501
+
502
+ const output = yield* runGitAllowEmpty(args, confluenceDir)
503
+ return parseGitLog(output)
504
+ })
505
+ )
506
+
507
+ const diff = (options?: GitDiffOptions) =>
508
+ provideDeps(
509
+ Effect.gen(function*() {
510
+ yield* ensureInitialized
511
+
512
+ const args: Array<string> = ["diff"]
513
+
514
+ if (options?.staged) {
515
+ args.push("--staged")
516
+ }
517
+
518
+ if (options?.commit) {
519
+ args.push(options.commit)
520
+ }
521
+
522
+ if (options?.commit2) {
523
+ args.push(options.commit2)
524
+ }
525
+
526
+ if (options?.file) {
527
+ args.push("--", options.file)
528
+ }
529
+
530
+ return yield* runGitAllowEmpty(args, confluenceDir)
531
+ })
532
+ )
533
+
534
+ const hasConflicts = () =>
535
+ provideDeps(
536
+ Effect.gen(function*() {
537
+ yield* ensureInitialized
538
+
539
+ const output = yield* runGitAllowEmpty(["status", "--porcelain"], confluenceDir)
540
+ const conflicted = getConflictedFiles(output)
541
+ return conflicted.length > 0
542
+ })
543
+ )
544
+
545
+ const mergeContinue = () =>
546
+ provideDeps(
547
+ Effect.gen(function*() {
548
+ yield* ensureInitialized
549
+
550
+ // Check if there are still conflicts
551
+ const output = yield* runGitAllowEmpty(["status", "--porcelain"], confluenceDir)
552
+ const files = getConflictedFiles(output)
553
+ if (files.length > 0) {
554
+ return yield* Effect.fail(new GitMergeConflictError({ files }))
555
+ }
556
+
557
+ yield* runGit(["commit", "--no-edit"], confluenceDir)
558
+ })
559
+ )
560
+
561
+ const syncFromDocs = (docsPath: string, trackedPaths: ReadonlyArray<string>) =>
562
+ provideDeps(
563
+ Effect.gen(function*() {
564
+ yield* ensureInitialized
565
+
566
+ const absoluteDocsPath = pathService.isAbsolute(docsPath)
567
+ ? docsPath
568
+ : pathService.join(cwd, docsPath)
569
+
570
+ // Skip if docsPath is inside .confluence/ - config validation ensures
571
+ // only ".confluence/docs" is allowed inside, which means no sync needed
572
+ if (absoluteDocsPath.startsWith(confluenceDir)) {
573
+ return
574
+ }
575
+
576
+ // For each tracked path pattern, copy matching files
577
+ for (const pattern of trackedPaths) {
578
+ // Simple glob handling - for now just support **/*.md
579
+ if (pattern === "**/*.md") {
580
+ yield* copyMarkdownFilesFn(fs, pathService, absoluteDocsPath, confluenceDir)
581
+ } else {
582
+ // Direct file/directory copy
583
+ const sourcePath = pathService.join(absoluteDocsPath, pattern)
584
+ const destPath = pathService.join(confluenceDir, pattern)
585
+
586
+ const exists = yield* fs.exists(sourcePath).pipe(Effect.catchAll(() => Effect.succeed(false)))
587
+ if (exists) {
588
+ const stat = yield* fs.stat(sourcePath).pipe(Effect.mapError(mapFsError))
589
+ if (stat.type === "Directory") {
590
+ yield* copyDirectoryFn(fs, pathService, sourcePath, destPath)
591
+ } else {
592
+ yield* copyFileFn(fs, pathService, sourcePath, destPath)
593
+ }
594
+ }
595
+ }
596
+ }
597
+ })
598
+ )
599
+
600
+ const syncToDocs = (docsPath: string, trackedPaths: ReadonlyArray<string>) =>
601
+ provideDeps(
602
+ Effect.gen(function*() {
603
+ yield* ensureInitialized
604
+
605
+ const absoluteDocsPath = pathService.isAbsolute(docsPath)
606
+ ? docsPath
607
+ : pathService.join(cwd, docsPath)
608
+
609
+ // Copy from .confluence/ back to docsPath
610
+ for (const pattern of trackedPaths) {
611
+ if (pattern === "**/*.md") {
612
+ yield* copyMarkdownFilesFn(fs, pathService, confluenceDir, absoluteDocsPath)
613
+ } else {
614
+ const sourcePath = pathService.join(confluenceDir, pattern)
615
+ const destPath = pathService.join(absoluteDocsPath, pattern)
616
+
617
+ const exists = yield* fs.exists(sourcePath).pipe(Effect.catchAll(() => Effect.succeed(false)))
618
+ if (exists) {
619
+ const stat = yield* fs.stat(sourcePath).pipe(Effect.mapError(mapFsError))
620
+ if (stat.type === "Directory") {
621
+ yield* copyDirectoryFn(fs, pathService, sourcePath, destPath)
622
+ } else {
623
+ yield* copyFileFn(fs, pathService, sourcePath, destPath)
624
+ }
625
+ }
626
+ }
627
+ }
628
+ })
629
+ )
630
+
631
+ const getHead = () =>
632
+ provideDeps(
633
+ Effect.gen(function*() {
634
+ yield* ensureInitialized
635
+ const output = yield* runGit(["rev-parse", "HEAD"], confluenceDir)
636
+ return output.trim()
637
+ })
638
+ )
639
+
640
+ const getCurrentBranch = () =>
641
+ provideDeps(
642
+ Effect.gen(function*() {
643
+ yield* ensureInitialized
644
+ const output = yield* runGit(["rev-parse", "--abbrev-ref", "HEAD"], confluenceDir)
645
+ return output.trim()
646
+ })
647
+ )
648
+
649
+ const createBranch = (name: string) =>
650
+ provideDeps(
651
+ Effect.gen(function*() {
652
+ yield* ensureInitialized
653
+ yield* runGit(["branch", name], confluenceDir)
654
+ })
655
+ )
656
+
657
+ const checkout = (ref: string) =>
658
+ provideDeps(
659
+ Effect.gen(function*() {
660
+ yield* ensureInitialized
661
+ yield* runGit(["checkout", ref], confluenceDir)
662
+ })
663
+ )
664
+
665
+ const reset = (ref: string, options?: GitResetOptions) =>
666
+ provideDeps(
667
+ Effect.gen(function*() {
668
+ yield* ensureInitialized
669
+ const args = ["reset"]
670
+ if (options?.hard) args.push("--hard")
671
+ args.push(ref)
672
+ yield* runGit(args, confluenceDir)
673
+ })
674
+ )
675
+
676
+ const deleteBranch = (name: string) =>
677
+ provideDeps(
678
+ Effect.gen(function*() {
679
+ yield* ensureInitialized
680
+ yield* runGit(["branch", "-D", name], confluenceDir)
681
+ })
682
+ )
683
+
684
+ const getParent = (ref: string) =>
685
+ provideDeps(
686
+ Effect.gen(function*() {
687
+ yield* ensureInitialized
688
+ const output = yield* runGit(["rev-parse", `${ref}^`], confluenceDir)
689
+ return output.trim()
690
+ })
691
+ )
692
+
693
+ const cherryPick = (ref: string, options?: { strategy?: "ours" | "theirs" }) =>
694
+ provideDeps(
695
+ Effect.gen(function*() {
696
+ yield* ensureInitialized
697
+ const args = ["cherry-pick"]
698
+ if (options?.strategy) {
699
+ args.push("-X", options.strategy)
700
+ }
701
+ args.push(ref)
702
+ yield* runGit(args, confluenceDir)
703
+ })
704
+ )
705
+
706
+ const getChangedFiles = (ref: string) =>
707
+ provideDeps(
708
+ Effect.gen(function*() {
709
+ yield* ensureInitialized
710
+ const output = yield* runGitAllowEmpty(
711
+ ["diff-tree", "--no-commit-id", "--name-only", "-r", ref],
712
+ confluenceDir
713
+ )
714
+ return output.trim().split("\n").filter(Boolean)
715
+ })
716
+ )
717
+
718
+ const showFile = (ref: string, filePath: string) =>
719
+ provideDeps(
720
+ Effect.gen(function*() {
721
+ yield* ensureInitialized
722
+ const output = yield* runGit(["show", `${ref}:${filePath}`], confluenceDir)
723
+ return output
724
+ })
725
+ )
726
+
727
+ const amend = (options?: GitAmendOptions) =>
728
+ provideDeps(
729
+ Effect.gen(function*() {
730
+ yield* ensureInitialized
731
+ const args = ["commit", "--amend"]
732
+ if (options?.noEdit) {
733
+ args.push("--no-edit")
734
+ }
735
+ if (options?.message) {
736
+ args.push("-m", options.message)
737
+ }
738
+ yield* runGit(args, confluenceDir)
739
+ })
740
+ )
741
+
742
+ const logRange = (from: string, to: string) =>
743
+ provideDeps(
744
+ Effect.gen(function*() {
745
+ yield* ensureInitialized
746
+ const output = yield* runGitAllowEmpty(
747
+ ["log", `--format=${GIT_LOG_FORMAT}`, `${from}..${to}`],
748
+ confluenceDir
749
+ )
750
+ return parseGitLog(output)
751
+ })
752
+ )
753
+
754
+ const branchExists = (name: string) =>
755
+ provideDeps(
756
+ Effect.gen(function*() {
757
+ yield* ensureInitialized
758
+ const output = yield* runGitAllowEmpty(
759
+ ["branch", "--list", name],
760
+ confluenceDir
761
+ )
762
+ return output.trim().length > 0
763
+ })
764
+ )
765
+
766
+ const updateBranch = (name: string, ref: string) =>
767
+ provideDeps(
768
+ Effect.gen(function*() {
769
+ yield* ensureInitialized
770
+ yield* runGit(["branch", "-f", name, ref], confluenceDir)
771
+ })
772
+ )
773
+
774
+ const merge = (branch: string, options?: { noCommit?: boolean; message?: string }) =>
775
+ provideDeps(
776
+ Effect.gen(function*() {
777
+ yield* ensureInitialized
778
+ const args = ["merge", branch]
779
+ if (options?.noCommit) {
780
+ args.push("--no-commit")
781
+ }
782
+ if (options?.message) {
783
+ args.push("-m", options.message)
784
+ }
785
+ yield* runGit(args, confluenceDir)
786
+ })
787
+ )
788
+
789
+ const getDeletedFiles = (from: string, to: string, pathPrefix?: string) =>
790
+ provideDeps(
791
+ Effect.gen(function*() {
792
+ yield* ensureInitialized
793
+ // git diff --name-only --diff-filter=D from..to -- pathPrefix
794
+ const args = ["diff", "--name-only", "--diff-filter=D", `${from}..${to}`]
795
+ if (pathPrefix) {
796
+ args.push("--", pathPrefix)
797
+ }
798
+ const output = yield* runGitAllowEmpty(args, confluenceDir)
799
+ return output.trim().split("\n").filter((line) => line.length > 0)
800
+ })
801
+ )
802
+
803
+ const getFileContentAt = (ref: string, filePath: string) =>
804
+ provideDeps(
805
+ Effect.gen(function*() {
806
+ yield* ensureInitialized
807
+ // git show ref:filePath
808
+ return yield* runGit(["show", `${ref}:${filePath}`], confluenceDir)
809
+ })
810
+ )
811
+
812
+ return GitService.of({
813
+ validateGit,
814
+ isInitialized,
815
+ init,
816
+ addAll,
817
+ commit,
818
+ status,
819
+ log,
820
+ diff,
821
+ hasConflicts,
822
+ mergeContinue,
823
+ syncFromDocs,
824
+ syncToDocs,
825
+ getHead,
826
+ getCurrentBranch,
827
+ createBranch,
828
+ checkout,
829
+ reset,
830
+ deleteBranch,
831
+ getParent,
832
+ cherryPick,
833
+ getChangedFiles,
834
+ showFile,
835
+ amend,
836
+ logRange,
837
+ branchExists,
838
+ updateBranch,
839
+ merge,
840
+ getDeletedFiles,
841
+ getFileContentAt
842
+ })
843
+ })
844
+
845
+ // Layer with deps - requires FileSystem, Path, CommandExecutor
846
+ const layerWithDeps: Layer.Layer<
847
+ GitService,
848
+ never,
849
+ FileSystem.FileSystem | Path.Path | CommandExecutor.CommandExecutor
850
+ > = Layer.effect(GitService, make)
851
+
852
+ /**
853
+ * Create GitService layer with all platform dependencies.
854
+ *
855
+ * @category Layers
856
+ */
857
+ export const layer = layerWithDeps.pipe(
858
+ Layer.provide(GitDepsLive)
859
+ )