@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.
- package/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +282 -14
- package/dist/ConfluenceAuth.d.ts +76 -0
- package/dist/ConfluenceAuth.d.ts.map +1 -0
- package/dist/ConfluenceAuth.js +356 -0
- package/dist/ConfluenceAuth.js.map +1 -0
- package/dist/ConfluenceClient.d.ts +26 -2
- package/dist/ConfluenceClient.d.ts.map +1 -1
- package/dist/ConfluenceClient.js +98 -92
- package/dist/ConfluenceClient.js.map +1 -1
- package/dist/ConfluenceConfig.d.ts +4 -24
- package/dist/ConfluenceConfig.d.ts.map +1 -1
- package/dist/ConfluenceConfig.js +45 -7
- package/dist/ConfluenceConfig.js.map +1 -1
- package/dist/ConfluenceError.d.ts +89 -6
- package/dist/ConfluenceError.d.ts.map +1 -1
- package/dist/ConfluenceError.js +88 -5
- package/dist/ConfluenceError.js.map +1 -1
- package/dist/GitError.d.ts +103 -0
- package/dist/GitError.d.ts.map +1 -0
- package/dist/GitError.js +85 -0
- package/dist/GitError.js.map +1 -0
- package/dist/GitService.d.ts +175 -0
- package/dist/GitService.d.ts.map +1 -0
- package/dist/GitService.js +431 -0
- package/dist/GitService.js.map +1 -0
- package/dist/LocalFileSystem.d.ts +29 -4
- package/dist/LocalFileSystem.d.ts.map +1 -1
- package/dist/LocalFileSystem.js +80 -6
- package/dist/LocalFileSystem.js.map +1 -1
- package/dist/MarkdownConverter.d.ts +49 -2
- package/dist/MarkdownConverter.d.ts.map +1 -1
- package/dist/MarkdownConverter.js +73 -111
- package/dist/MarkdownConverter.js.map +1 -1
- package/dist/SchemaConverterError.d.ts +108 -0
- package/dist/SchemaConverterError.d.ts.map +1 -0
- package/dist/SchemaConverterError.js +84 -0
- package/dist/SchemaConverterError.js.map +1 -0
- package/dist/Schemas.d.ts +225 -1
- package/dist/Schemas.d.ts.map +1 -1
- package/dist/Schemas.js +155 -6
- package/dist/Schemas.js.map +1 -1
- package/dist/SyncEngine.d.ts +30 -20
- package/dist/SyncEngine.d.ts.map +1 -1
- package/dist/SyncEngine.js +566 -117
- package/dist/SyncEngine.js.map +1 -1
- package/dist/ast/BlockNode.d.ts +468 -0
- package/dist/ast/BlockNode.d.ts.map +1 -0
- package/dist/ast/BlockNode.js +319 -0
- package/dist/ast/BlockNode.js.map +1 -0
- package/dist/ast/Document.d.ts +244 -0
- package/dist/ast/Document.d.ts.map +1 -0
- package/dist/ast/Document.js +69 -0
- package/dist/ast/Document.js.map +1 -0
- package/dist/ast/InlineNode.d.ts +477 -0
- package/dist/ast/InlineNode.d.ts.map +1 -0
- package/dist/ast/InlineNode.js +263 -0
- package/dist/ast/InlineNode.js.map +1 -0
- package/dist/ast/MacroNode.d.ts +267 -0
- package/dist/ast/MacroNode.d.ts.map +1 -0
- package/dist/ast/MacroNode.js +164 -0
- package/dist/ast/MacroNode.js.map +1 -0
- package/dist/ast/index.d.ts +10 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +14 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/bin.js +33 -149
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth.d.ts +15 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +86 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/clone.d.ts +12 -0
- package/dist/commands/clone.d.ts.map +1 -0
- package/dist/commands/clone.js +93 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/delete.d.ts +13 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +48 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/errorHandler.d.ts +14 -0
- package/dist/commands/errorHandler.d.ts.map +1 -0
- package/dist/commands/errorHandler.js +33 -0
- package/dist/commands/errorHandler.js.map +1 -0
- package/dist/commands/git.d.ts +22 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +72 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/layers.d.ts +31 -0
- package/dist/commands/layers.d.ts.map +1 -0
- package/dist/commands/layers.js +137 -0
- package/dist/commands/layers.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +80 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pageTree.d.ts +18 -0
- package/dist/commands/pageTree.d.ts.map +1 -0
- package/dist/commands/pageTree.js +20 -0
- package/dist/commands/pageTree.js.map +1 -0
- package/dist/commands/shared.d.ts +15 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +27 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/sync.d.ts +15 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +101 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/NodeLayers.d.ts +7 -0
- package/dist/internal/NodeLayers.d.ts.map +1 -0
- package/dist/internal/NodeLayers.js +19 -0
- package/dist/internal/NodeLayers.js.map +1 -0
- package/dist/internal/frontmatter.d.ts +10 -0
- package/dist/internal/frontmatter.d.ts.map +1 -1
- package/dist/internal/frontmatter.js +16 -0
- package/dist/internal/frontmatter.js.map +1 -1
- package/dist/internal/gitCommands.d.ts +78 -0
- package/dist/internal/gitCommands.d.ts.map +1 -0
- package/dist/internal/gitCommands.js +156 -0
- package/dist/internal/gitCommands.js.map +1 -0
- package/dist/internal/hashUtils.d.ts +42 -1
- package/dist/internal/hashUtils.d.ts.map +1 -1
- package/dist/internal/hashUtils.js +38 -2
- package/dist/internal/hashUtils.js.map +1 -1
- package/dist/internal/oauthServer.d.ts +55 -0
- package/dist/internal/oauthServer.d.ts.map +1 -0
- package/dist/internal/oauthServer.js +110 -0
- package/dist/internal/oauthServer.js.map +1 -0
- package/dist/internal/pathUtils.d.ts +21 -4
- package/dist/internal/pathUtils.d.ts.map +1 -1
- package/dist/internal/pathUtils.js +24 -13
- package/dist/internal/pathUtils.js.map +1 -1
- package/dist/internal/tokenStorage.d.ts +75 -0
- package/dist/internal/tokenStorage.d.ts.map +1 -0
- package/dist/internal/tokenStorage.js +149 -0
- package/dist/internal/tokenStorage.js.map +1 -0
- package/dist/internal/userCache.d.ts +42 -0
- package/dist/internal/userCache.d.ts.map +1 -0
- package/dist/internal/userCache.js +51 -0
- package/dist/internal/userCache.js.map +1 -0
- package/dist/parsers/ConfluenceParser.d.ts +26 -0
- package/dist/parsers/ConfluenceParser.d.ts.map +1 -0
- package/dist/parsers/ConfluenceParser.js +792 -0
- package/dist/parsers/ConfluenceParser.js.map +1 -0
- package/dist/parsers/MarkdownParser.d.ts +26 -0
- package/dist/parsers/MarkdownParser.d.ts.map +1 -0
- package/dist/parsers/MarkdownParser.js +873 -0
- package/dist/parsers/MarkdownParser.js.map +1 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +8 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/schemas/ConfluenceSchema.d.ts +21 -0
- package/dist/schemas/ConfluenceSchema.d.ts.map +1 -0
- package/dist/schemas/ConfluenceSchema.js +38 -0
- package/dist/schemas/ConfluenceSchema.js.map +1 -0
- package/dist/schemas/ConversionSchema.d.ts +35 -0
- package/dist/schemas/ConversionSchema.d.ts.map +1 -0
- package/dist/schemas/ConversionSchema.js +208 -0
- package/dist/schemas/ConversionSchema.js.map +1 -0
- package/dist/schemas/MarkdownSchema.d.ts +21 -0
- package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
- package/dist/schemas/MarkdownSchema.js +38 -0
- package/dist/schemas/MarkdownSchema.js.map +1 -0
- package/dist/schemas/hast/HastFromHtml.d.ts +27 -0
- package/dist/schemas/hast/HastFromHtml.d.ts.map +1 -0
- package/dist/schemas/hast/HastFromHtml.js +107 -0
- package/dist/schemas/hast/HastFromHtml.js.map +1 -0
- package/dist/schemas/hast/HastSchema.d.ts +195 -0
- package/dist/schemas/hast/HastSchema.d.ts.map +1 -0
- package/dist/schemas/hast/HastSchema.js +183 -0
- package/dist/schemas/hast/HastSchema.js.map +1 -0
- package/dist/schemas/hast/index.d.ts +9 -0
- package/dist/schemas/hast/index.d.ts.map +1 -0
- package/dist/schemas/hast/index.js +3 -0
- package/dist/schemas/hast/index.js.map +1 -0
- package/dist/schemas/index.d.ts +14 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +16 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts +30 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js +79 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js.map +1 -0
- package/dist/schemas/mdast/MdastSchema.d.ts +385 -0
- package/dist/schemas/mdast/MdastSchema.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastSchema.js +266 -0
- package/dist/schemas/mdast/MdastSchema.js.map +1 -0
- package/dist/schemas/mdast/index.d.ts +10 -0
- package/dist/schemas/mdast/index.d.ts.map +1 -0
- package/dist/schemas/mdast/index.js +4 -0
- package/dist/schemas/mdast/index.js.map +1 -0
- package/dist/schemas/mdast/mdastToString.d.ts +13 -0
- package/dist/schemas/mdast/mdastToString.d.ts.map +1 -0
- package/dist/schemas/mdast/mdastToString.js +85 -0
- package/dist/schemas/mdast/mdastToString.js.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts +43 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.js +634 -0
- package/dist/schemas/nodes/block/BlockSchema.js.map +1 -0
- package/dist/schemas/nodes/block/index.d.ts +7 -0
- package/dist/schemas/nodes/block/index.d.ts.map +1 -0
- package/dist/schemas/nodes/block/index.js +7 -0
- package/dist/schemas/nodes/block/index.js.map +1 -0
- package/dist/schemas/nodes/index.d.ts +9 -0
- package/dist/schemas/nodes/index.d.ts.map +1 -0
- package/dist/schemas/nodes/index.js +12 -0
- package/dist/schemas/nodes/index.js.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts +48 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.js +436 -0
- package/dist/schemas/nodes/inline/InlineSchema.js.map +1 -0
- package/dist/schemas/nodes/inline/index.d.ts +7 -0
- package/dist/schemas/nodes/inline/index.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/index.js +7 -0
- package/dist/schemas/nodes/inline/index.js.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts +27 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.js +162 -0
- package/dist/schemas/nodes/macro/MacroSchema.js.map +1 -0
- package/dist/schemas/nodes/macro/index.d.ts +7 -0
- package/dist/schemas/nodes/macro/index.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/index.js +7 -0
- package/dist/schemas/nodes/macro/index.js.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +24 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js +351 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -0
- package/dist/schemas/preprocessing/index.d.ts +8 -0
- package/dist/schemas/preprocessing/index.d.ts.map +1 -0
- package/dist/schemas/preprocessing/index.js +2 -0
- package/dist/schemas/preprocessing/index.js.map +1 -0
- package/dist/serializers/ConfluenceSerializer.d.ts +30 -0
- package/dist/serializers/ConfluenceSerializer.d.ts.map +1 -0
- package/dist/serializers/ConfluenceSerializer.js +551 -0
- package/dist/serializers/ConfluenceSerializer.js.map +1 -0
- package/dist/serializers/MarkdownSerializer.d.ts +34 -0
- package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
- package/dist/serializers/MarkdownSerializer.js +355 -0
- package/dist/serializers/MarkdownSerializer.js.map +1 -0
- package/dist/serializers/index.d.ts +8 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/serializers/index.js +8 -0
- package/dist/serializers/index.js.map +1 -0
- package/package.json +27 -16
- package/src/ConfluenceAuth.ts +571 -0
- package/src/ConfluenceClient.ts +188 -156
- package/src/ConfluenceConfig.ts +63 -7
- package/src/ConfluenceError.ts +110 -14
- package/src/GitError.ts +92 -0
- package/src/GitService.ts +859 -0
- package/src/LocalFileSystem.ts +179 -9
- package/src/MarkdownConverter.ts +126 -122
- package/src/SchemaConverterError.ts +108 -0
- package/src/Schemas.ts +223 -6
- package/src/SyncEngine.ts +745 -162
- package/src/ast/BlockNode.ts +425 -0
- package/src/ast/Document.ts +90 -0
- package/src/ast/InlineNode.ts +323 -0
- package/src/ast/MacroNode.ts +245 -0
- package/src/ast/index.ts +83 -0
- package/src/bin.ts +50 -249
- package/src/commands/auth.ts +117 -0
- package/src/commands/clone.ts +145 -0
- package/src/commands/delete.ts +57 -0
- package/src/commands/errorHandler.ts +32 -0
- package/src/commands/git.ts +114 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/layers.ts +211 -0
- package/src/commands/new.ts +99 -0
- package/src/commands/pageTree.ts +40 -0
- package/src/commands/shared.ts +35 -0
- package/src/commands/sync.ts +129 -0
- package/src/index.ts +21 -1
- package/src/internal/NodeLayers.ts +21 -0
- package/src/internal/frontmatter.ts +21 -0
- package/src/internal/gitCommands.ts +229 -0
- package/src/internal/hashUtils.ts +65 -3
- package/src/internal/oauthServer.ts +199 -0
- package/src/internal/pathUtils.ts +34 -17
- package/src/internal/tokenStorage.ts +240 -0
- package/src/internal/userCache.ts +90 -0
- package/src/parsers/ConfluenceParser.ts +950 -0
- package/src/parsers/MarkdownParser.ts +1198 -0
- package/src/parsers/index.ts +8 -0
- package/src/schemas/ConfluenceSchema.ts +56 -0
- package/src/schemas/ConversionSchema.ts +318 -0
- package/src/schemas/MarkdownSchema.ts +56 -0
- package/src/schemas/hast/HastFromHtml.ts +153 -0
- package/src/schemas/hast/HastSchema.ts +274 -0
- package/src/schemas/hast/index.ts +35 -0
- package/src/schemas/index.ts +20 -0
- package/src/schemas/mdast/MdastFromMarkdown.ts +118 -0
- package/src/schemas/mdast/MdastSchema.ts +566 -0
- package/src/schemas/mdast/index.ts +59 -0
- package/src/schemas/mdast/mdastToString.ts +102 -0
- package/src/schemas/nodes/block/BlockSchema.ts +773 -0
- package/src/schemas/nodes/block/index.ts +13 -0
- package/src/schemas/nodes/index.ts +20 -0
- package/src/schemas/nodes/inline/InlineSchema.ts +523 -0
- package/src/schemas/nodes/inline/index.ts +14 -0
- package/src/schemas/nodes/macro/MacroSchema.ts +226 -0
- package/src/schemas/nodes/macro/index.ts +6 -0
- package/src/schemas/preprocessing/ConfluencePreprocessor.ts +446 -0
- package/src/schemas/preprocessing/index.ts +8 -0
- package/src/serializers/ConfluenceSerializer.ts +717 -0
- package/src/serializers/MarkdownSerializer.ts +493 -0
- package/src/serializers/index.ts +8 -0
- package/test/GitService.test.ts +209 -0
- package/test/MarkdownConverter.test.ts +37 -3
- package/test/Schemas.test.ts +97 -2
- package/test/ast/BlockNode.test.ts +265 -0
- package/test/ast/Document.test.ts +126 -0
- package/test/ast/InlineNode.test.ts +161 -0
- package/test/fixtures/integration-test.html.fixture +103 -0
- package/test/fixtures/integration-test.md.expected +257 -0
- package/test/integration.test.ts +269 -0
- package/test/oauthServer.test.ts +50 -0
- package/test/parsers/ConfluenceParser.test.ts +283 -0
- package/test/schemas/ConfluencePreprocessor.test.ts +180 -0
- package/test/schemas/ConversionSchema.test.ts +159 -0
- package/test/schemas/HastSchema.test.ts +138 -0
- package/test/schemas/MdastSchema.test.ts +145 -0
- package/test/schemas/nodes/block/BlockSchema.test.ts +173 -0
- package/test/schemas/nodes/inline/InlineSchema.test.ts +198 -0
- package/test/schemas/nodes/macro/MacroSchema.test.ts +142 -0
- package/test/tokenStorage.test.ts +99 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git commands (commit, log, diff) for Confluence CLI.
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Options } from "@effect/cli"
|
|
5
|
+
import * as Console from "effect/Console"
|
|
6
|
+
import * as Effect from "effect/Effect"
|
|
7
|
+
import * as Option from "effect/Option"
|
|
8
|
+
import { ConfluenceConfig } from "../ConfluenceConfig.js"
|
|
9
|
+
import { GitService } from "../GitService.js"
|
|
10
|
+
|
|
11
|
+
// === Commit command ===
|
|
12
|
+
const commitMessageOption = Options.text("message").pipe(
|
|
13
|
+
Options.withAlias("m"),
|
|
14
|
+
Options.withDescription("Commit message"),
|
|
15
|
+
Options.optional
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
export const commitCommand = Command.make(
|
|
19
|
+
"commit",
|
|
20
|
+
{ message: commitMessageOption },
|
|
21
|
+
({ message }) =>
|
|
22
|
+
Effect.gen(function*() {
|
|
23
|
+
const git = yield* GitService
|
|
24
|
+
const config = yield* ConfluenceConfig
|
|
25
|
+
|
|
26
|
+
// Sync from external docs path to .confluence/ (skips if docsPath is inside .confluence/)
|
|
27
|
+
yield* git.syncFromDocs(config.docsPath, config.trackedPaths)
|
|
28
|
+
|
|
29
|
+
yield* git.addAll()
|
|
30
|
+
const msg = Option.isSome(message) ? message.value : "Manual commit"
|
|
31
|
+
const hash = yield* git.commit({ message: msg })
|
|
32
|
+
yield* Console.log(`Committed: ${hash.substring(0, 7)}`)
|
|
33
|
+
})
|
|
34
|
+
).pipe(Command.withDescription("Stage and commit current changes"))
|
|
35
|
+
|
|
36
|
+
// === Log command ===
|
|
37
|
+
const logLimitOption = Options.integer("limit").pipe(
|
|
38
|
+
Options.withAlias("n"),
|
|
39
|
+
Options.withDescription("Number of commits to show"),
|
|
40
|
+
Options.withDefault(10)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const logOnelineOption = Options.boolean("oneline").pipe(
|
|
44
|
+
Options.withDescription("Show compact one-line format")
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const logSinceOption = Options.text("since").pipe(
|
|
48
|
+
Options.withDescription("Show commits since date (e.g., '2024-01-01')"),
|
|
49
|
+
Options.optional
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const logFileArg = Args.text({ name: "file" }).pipe(Args.optional) as Args.Args<Option.Option<string>>
|
|
53
|
+
|
|
54
|
+
export const logCommand = Command.make(
|
|
55
|
+
"log",
|
|
56
|
+
{ limit: logLimitOption, oneline: logOnelineOption, since: logSinceOption, file: logFileArg },
|
|
57
|
+
({ file, limit, oneline, since }) =>
|
|
58
|
+
Effect.gen(function*() {
|
|
59
|
+
const git = yield* GitService
|
|
60
|
+
const opts = {
|
|
61
|
+
n: limit,
|
|
62
|
+
oneline,
|
|
63
|
+
...(Option.isSome(since) ? { since: since.value } : {}),
|
|
64
|
+
...(Option.isSome(file) ? { file: file.value } : {})
|
|
65
|
+
}
|
|
66
|
+
const commits = yield* git.log(opts)
|
|
67
|
+
if (commits.length === 0) {
|
|
68
|
+
yield* Console.log("No commits yet")
|
|
69
|
+
} else if (oneline) {
|
|
70
|
+
for (const commit of commits) {
|
|
71
|
+
yield* Console.log(`${commit.hash.substring(0, 7)} ${commit.message}`)
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
for (const commit of commits) {
|
|
75
|
+
yield* Console.log(`commit ${commit.hash}`)
|
|
76
|
+
yield* Console.log(`Author: ${commit.author} <${commit.email}>`)
|
|
77
|
+
yield* Console.log(`Date: ${commit.date.toISOString()}`)
|
|
78
|
+
yield* Console.log(`\n ${commit.message}\n`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
).pipe(Command.withDescription("Show commit history"))
|
|
83
|
+
|
|
84
|
+
// === Diff command ===
|
|
85
|
+
const diffStagedOption = Options.boolean("staged").pipe(
|
|
86
|
+
Options.withDescription("Show staged changes")
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const diffCommitOption = Options.text("commit").pipe(
|
|
90
|
+
Options.withDescription("Compare with specific commit"),
|
|
91
|
+
Options.optional
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const diffFileArg = Args.text({ name: "file" }).pipe(Args.optional) as Args.Args<Option.Option<string>>
|
|
95
|
+
|
|
96
|
+
export const diffCommand = Command.make(
|
|
97
|
+
"diff",
|
|
98
|
+
{ staged: diffStagedOption, commit: diffCommitOption, file: diffFileArg },
|
|
99
|
+
({ commit, file, staged }) =>
|
|
100
|
+
Effect.gen(function*() {
|
|
101
|
+
const git = yield* GitService
|
|
102
|
+
const opts = {
|
|
103
|
+
staged,
|
|
104
|
+
...(Option.isSome(commit) ? { commit: commit.value } : {}),
|
|
105
|
+
...(Option.isSome(file) ? { file: file.value } : {})
|
|
106
|
+
}
|
|
107
|
+
const diff = yield* git.diff(opts)
|
|
108
|
+
if (diff === "") {
|
|
109
|
+
yield* Console.log("No changes")
|
|
110
|
+
} else {
|
|
111
|
+
yield* Console.log(diff)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
).pipe(Command.withDescription("Show changes in working directory"))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI commands for confluence-to-markdown.
|
|
3
|
+
*/
|
|
4
|
+
export { authCommand } from "./auth.js"
|
|
5
|
+
export { cloneCommand } from "./clone.js"
|
|
6
|
+
export { deleteCommand } from "./delete.js"
|
|
7
|
+
export { commitCommand, diffCommand, logCommand } from "./git.js"
|
|
8
|
+
export { newCommand } from "./new.js"
|
|
9
|
+
export { getAuth } from "./shared.js"
|
|
10
|
+
export { pullCommand, pushCommand, statusCommand } from "./sync.js"
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer definitions for CLI commands.
|
|
3
|
+
*/
|
|
4
|
+
import * as NodeContext from "@effect/platform-node/NodeContext"
|
|
5
|
+
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient"
|
|
6
|
+
import * as NodeTerminal from "@effect/platform-node/NodeTerminal"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as Layer from "effect/Layer"
|
|
9
|
+
import type { PageId } from "../Brand.js"
|
|
10
|
+
import { ConfluenceAuth, layer as ConfluenceAuthLayer } from "../ConfluenceAuth.js"
|
|
11
|
+
import { ConfluenceClient, type ConfluenceClientConfig, layer as ConfluenceClientLayer } from "../ConfluenceClient.js"
|
|
12
|
+
import {
|
|
13
|
+
ConfluenceConfig,
|
|
14
|
+
layer as ConfluenceConfigLayer,
|
|
15
|
+
layerFromValues as ConfluenceConfigLayerFromValues
|
|
16
|
+
} from "../ConfluenceConfig.js"
|
|
17
|
+
import { GitService, layer as GitServiceLayer } from "../GitService.js"
|
|
18
|
+
import { UserCacheLayer } from "../internal/userCache.js"
|
|
19
|
+
import { layer as LocalFileSystemLayer } from "../LocalFileSystem.js"
|
|
20
|
+
import { layer as MarkdownConverterLayer } from "../MarkdownConverter.js"
|
|
21
|
+
import { layer as SyncEngineLayer, SyncEngine } from "../SyncEngine.js"
|
|
22
|
+
import { getAuth } from "./shared.js"
|
|
23
|
+
|
|
24
|
+
// Dummy config layer for help/init
|
|
25
|
+
const DummyConfigLayer = ConfluenceConfigLayerFromValues({
|
|
26
|
+
rootPageId: "dummy" as PageId,
|
|
27
|
+
baseUrl: "https://dummy.atlassian.net",
|
|
28
|
+
docsPath: ".confluence/docs",
|
|
29
|
+
excludePatterns: [],
|
|
30
|
+
saveSource: false,
|
|
31
|
+
trackedPaths: ["**/*.md"]
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Dummy client layer for help/init (will fail if actually used)
|
|
35
|
+
const DummyConfluenceClientLayer = Layer.succeed(
|
|
36
|
+
ConfluenceClient,
|
|
37
|
+
ConfluenceClient.of({
|
|
38
|
+
getPage: () => Effect.dieMessage("Not configured"),
|
|
39
|
+
getChildren: () => Effect.dieMessage("Not configured"),
|
|
40
|
+
getAllChildren: () => Effect.dieMessage("Not configured"),
|
|
41
|
+
createPage: () => Effect.dieMessage("Not configured"),
|
|
42
|
+
updatePage: () => Effect.dieMessage("Not configured"),
|
|
43
|
+
deletePage: () => Effect.dieMessage("Not configured"),
|
|
44
|
+
getPageVersions: () => Effect.dieMessage("Not configured"),
|
|
45
|
+
getUser: () => Effect.dieMessage("Not configured"),
|
|
46
|
+
getSpaceId: () => Effect.dieMessage("Not configured"),
|
|
47
|
+
setEditorVersion: () => Effect.dieMessage("Not configured")
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
// Dummy sync engine that will fail if actually used
|
|
52
|
+
const DummySyncEngineLayer = Layer.succeed(
|
|
53
|
+
SyncEngine,
|
|
54
|
+
SyncEngine.of({
|
|
55
|
+
pull: () => Effect.dieMessage("Not configured - run 'confluence clone' first"),
|
|
56
|
+
push: (_options: { dryRun: boolean; message?: string }) =>
|
|
57
|
+
Effect.dieMessage("Not configured - run 'confluence clone' first"),
|
|
58
|
+
status: () => Effect.dieMessage("Not configured - run 'confluence clone' first")
|
|
59
|
+
})
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
// Dummy git layer for auth/minimal
|
|
63
|
+
const notConfigured = () => Effect.dieMessage("Not configured - run 'confluence clone' first")
|
|
64
|
+
const DummyGitServiceLayer = Layer.succeed(
|
|
65
|
+
GitService,
|
|
66
|
+
GitService.of({
|
|
67
|
+
validateGit: notConfigured,
|
|
68
|
+
init: notConfigured,
|
|
69
|
+
isInitialized: () => Effect.succeed(false),
|
|
70
|
+
status: notConfigured,
|
|
71
|
+
commit: notConfigured,
|
|
72
|
+
log: notConfigured,
|
|
73
|
+
diff: notConfigured,
|
|
74
|
+
addAll: notConfigured,
|
|
75
|
+
hasConflicts: () => Effect.succeed(false),
|
|
76
|
+
mergeContinue: notConfigured,
|
|
77
|
+
syncFromDocs: notConfigured,
|
|
78
|
+
syncToDocs: notConfigured,
|
|
79
|
+
getHead: notConfigured,
|
|
80
|
+
getCurrentBranch: notConfigured,
|
|
81
|
+
createBranch: notConfigured,
|
|
82
|
+
checkout: notConfigured,
|
|
83
|
+
reset: notConfigured,
|
|
84
|
+
deleteBranch: notConfigured,
|
|
85
|
+
getParent: notConfigured,
|
|
86
|
+
cherryPick: (_ref: string, _options?: { strategy?: "ours" | "theirs" }) => notConfigured(),
|
|
87
|
+
getChangedFiles: notConfigured,
|
|
88
|
+
showFile: notConfigured,
|
|
89
|
+
amend: notConfigured,
|
|
90
|
+
logRange: notConfigured,
|
|
91
|
+
branchExists: notConfigured,
|
|
92
|
+
updateBranch: notConfigured,
|
|
93
|
+
merge: notConfigured,
|
|
94
|
+
getDeletedFiles: notConfigured,
|
|
95
|
+
getFileContentAt: notConfigured
|
|
96
|
+
})
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Dummy auth layer for init/help
|
|
100
|
+
const DummyConfluenceAuthLayer = Layer.succeed(
|
|
101
|
+
ConfluenceAuth,
|
|
102
|
+
ConfluenceAuth.of({
|
|
103
|
+
configure: () => Effect.dieMessage("Not configured"),
|
|
104
|
+
isConfigured: () => Effect.succeed(false),
|
|
105
|
+
login: () => Effect.dieMessage("Not configured"),
|
|
106
|
+
logout: () => Effect.dieMessage("Not configured"),
|
|
107
|
+
getAccessToken: () => Effect.dieMessage("Not configured"),
|
|
108
|
+
getCloudId: () => Effect.dieMessage("Not configured"),
|
|
109
|
+
getCurrentUser: () => Effect.succeed(null),
|
|
110
|
+
isLoggedIn: () => Effect.succeed(false)
|
|
111
|
+
})
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
// Auth layer with HTTP client
|
|
115
|
+
const AuthLive = ConfluenceAuthLayer.pipe(Layer.provide(NodeHttpClient.layer))
|
|
116
|
+
|
|
117
|
+
// Build client layer dynamically based on auth
|
|
118
|
+
const ConfluenceClientLive = Layer.unwrapEffect(
|
|
119
|
+
Effect.gen(function*() {
|
|
120
|
+
const auth = yield* getAuth()
|
|
121
|
+
const config = yield* ConfluenceConfig
|
|
122
|
+
|
|
123
|
+
const clientConfig: ConfluenceClientConfig = {
|
|
124
|
+
baseUrl: config.baseUrl,
|
|
125
|
+
auth
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return ConfluenceClientLayer(clientConfig)
|
|
129
|
+
})
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Full app layer with all services.
|
|
134
|
+
*/
|
|
135
|
+
export const AppLayer = SyncEngineLayer.pipe(
|
|
136
|
+
Layer.provideMerge(UserCacheLayer),
|
|
137
|
+
Layer.provideMerge(GitServiceLayer),
|
|
138
|
+
Layer.provideMerge(ConfluenceClientLive),
|
|
139
|
+
Layer.provideMerge(MarkdownConverterLayer),
|
|
140
|
+
Layer.provideMerge(LocalFileSystemLayer),
|
|
141
|
+
Layer.provideMerge(ConfluenceConfigLayer()),
|
|
142
|
+
Layer.provideMerge(AuthLive),
|
|
143
|
+
Layer.provideMerge(NodeHttpClient.layer),
|
|
144
|
+
Layer.provideMerge(NodeContext.layer)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Auth-only layer for login/logout commands.
|
|
149
|
+
*/
|
|
150
|
+
export const AuthOnlyLayer = DummySyncEngineLayer.pipe(
|
|
151
|
+
Layer.provideMerge(UserCacheLayer),
|
|
152
|
+
Layer.provideMerge(DummyGitServiceLayer),
|
|
153
|
+
Layer.provideMerge(DummyConfluenceClientLayer),
|
|
154
|
+
Layer.provideMerge(AuthLive),
|
|
155
|
+
Layer.provideMerge(MarkdownConverterLayer),
|
|
156
|
+
Layer.provideMerge(LocalFileSystemLayer),
|
|
157
|
+
Layer.provideMerge(DummyConfigLayer),
|
|
158
|
+
Layer.provideMerge(NodeHttpClient.layer),
|
|
159
|
+
Layer.provideMerge(NodeContext.layer)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Minimal layer for help - uses real GitService for clone.
|
|
164
|
+
*/
|
|
165
|
+
export const MinimalLayer = DummySyncEngineLayer.pipe(
|
|
166
|
+
Layer.provideMerge(UserCacheLayer),
|
|
167
|
+
Layer.provideMerge(GitServiceLayer),
|
|
168
|
+
Layer.provideMerge(DummyConfluenceClientLayer),
|
|
169
|
+
Layer.provideMerge(DummyConfluenceAuthLayer),
|
|
170
|
+
Layer.provideMerge(MarkdownConverterLayer),
|
|
171
|
+
Layer.provideMerge(LocalFileSystemLayer),
|
|
172
|
+
Layer.provideMerge(DummyConfigLayer),
|
|
173
|
+
Layer.provideMerge(NodeTerminal.layer),
|
|
174
|
+
Layer.provideMerge(NodeContext.layer)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Clone layer - needs auth + git but builds SyncEngine dynamically.
|
|
179
|
+
*/
|
|
180
|
+
export const CloneLayer = DummySyncEngineLayer.pipe(
|
|
181
|
+
Layer.provideMerge(UserCacheLayer),
|
|
182
|
+
Layer.provideMerge(GitServiceLayer),
|
|
183
|
+
Layer.provideMerge(DummyConfluenceClientLayer),
|
|
184
|
+
Layer.provideMerge(AuthLive),
|
|
185
|
+
Layer.provideMerge(MarkdownConverterLayer),
|
|
186
|
+
Layer.provideMerge(LocalFileSystemLayer),
|
|
187
|
+
Layer.provideMerge(DummyConfigLayer),
|
|
188
|
+
Layer.provideMerge(NodeHttpClient.layer),
|
|
189
|
+
Layer.provideMerge(NodeTerminal.layer),
|
|
190
|
+
Layer.provideMerge(NodeContext.layer)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Determine which layer to use based on command.
|
|
195
|
+
*/
|
|
196
|
+
export const getLayerType = (): "full" | "auth" | "clone" | "minimal" => {
|
|
197
|
+
const cmd = process.argv[2]
|
|
198
|
+
// auth commands need auth layer only
|
|
199
|
+
if (cmd === "auth") {
|
|
200
|
+
return "auth"
|
|
201
|
+
}
|
|
202
|
+
// clone needs auth + git but not config-dependent services
|
|
203
|
+
if (cmd === "clone") {
|
|
204
|
+
return "clone"
|
|
205
|
+
}
|
|
206
|
+
// --help, -h, --version don't need config
|
|
207
|
+
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "--version") {
|
|
208
|
+
return "minimal"
|
|
209
|
+
}
|
|
210
|
+
return "full"
|
|
211
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* New page command for Confluence CLI.
|
|
3
|
+
*/
|
|
4
|
+
import { Command, Prompt } from "@effect/cli"
|
|
5
|
+
import * as Path from "@effect/platform/Path"
|
|
6
|
+
import * as Console from "effect/Console"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import { ConfluenceConfig } from "../ConfluenceConfig.js"
|
|
9
|
+
import { LocalFileSystem } from "../LocalFileSystem.js"
|
|
10
|
+
import { flattenPageTree } from "./pageTree.js"
|
|
11
|
+
|
|
12
|
+
/** Slugify a title for use as filename */
|
|
13
|
+
const slugify = (title: string): string =>
|
|
14
|
+
title
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
17
|
+
.replace(/\s+/g, "-")
|
|
18
|
+
.replace(/-+/g, "-")
|
|
19
|
+
.replace(/^-|-$/g, "")
|
|
20
|
+
|
|
21
|
+
export const newCommand = Command.make("new", {}, () =>
|
|
22
|
+
Effect.gen(function*() {
|
|
23
|
+
const localFs = yield* LocalFileSystem
|
|
24
|
+
const config = yield* ConfluenceConfig
|
|
25
|
+
const pathService = yield* Path.Path
|
|
26
|
+
|
|
27
|
+
const docsPath = pathService.join(process.cwd(), config.docsPath)
|
|
28
|
+
|
|
29
|
+
// Build page tree
|
|
30
|
+
yield* Console.log("Scanning page structure...")
|
|
31
|
+
const tree = yield* localFs.buildPageTree(docsPath, config.rootPageId, "Root")
|
|
32
|
+
|
|
33
|
+
// Check if we have any pages
|
|
34
|
+
const hasPages = tree.children.length > 0 || tree.pageId !== null
|
|
35
|
+
|
|
36
|
+
if (!hasPages) {
|
|
37
|
+
yield* Console.log("No pages found. Run 'confluence clone' or 'confluence pull' first.")
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Flatten to choices
|
|
42
|
+
const choices = flattenPageTree(tree)
|
|
43
|
+
|
|
44
|
+
// Show parent selector
|
|
45
|
+
const parent = yield* Prompt.select({
|
|
46
|
+
message: "Select parent page for the new page:",
|
|
47
|
+
choices
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Prompt for title
|
|
51
|
+
const title = yield* Prompt.text({
|
|
52
|
+
message: "Enter page title:"
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (!title.trim()) {
|
|
56
|
+
yield* Console.log("Title cannot be empty")
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Generate filename
|
|
61
|
+
const filename = `${slugify(title.trim())}.md`
|
|
62
|
+
|
|
63
|
+
// Determine file path
|
|
64
|
+
// If parent is root (path === ""), create in docsPath
|
|
65
|
+
// If parent has children, create in parent's directory
|
|
66
|
+
let filePath: string
|
|
67
|
+
if (parent.path === "") {
|
|
68
|
+
// Root level
|
|
69
|
+
filePath = pathService.join(docsPath, filename)
|
|
70
|
+
} else {
|
|
71
|
+
// Under a page - check if parent directory exists
|
|
72
|
+
const parentBasename = pathService.basename(parent.path, ".md")
|
|
73
|
+
const parentDir = pathService.dirname(parent.path)
|
|
74
|
+
const targetDir = parentDir === "." ? parentBasename : pathService.join(parentDir, parentBasename)
|
|
75
|
+
filePath = pathService.join(docsPath, targetDir, filename)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if file already exists
|
|
79
|
+
const exists = yield* localFs.exists(filePath)
|
|
80
|
+
if (exists) {
|
|
81
|
+
yield* Console.log(`File already exists: ${filePath}`)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Write new page file with title-only front-matter
|
|
86
|
+
yield* localFs.writeNewPageFile(
|
|
87
|
+
filePath,
|
|
88
|
+
{ title: title.trim() },
|
|
89
|
+
"\n<!-- Write your page content here -->\n"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
const relativePath = pathService.relative(process.cwd(), filePath)
|
|
93
|
+
yield* Console.log(`Created new page: ${relativePath}`)
|
|
94
|
+
yield* Console.log("")
|
|
95
|
+
yield* Console.log("Next steps:")
|
|
96
|
+
yield* Console.log(" 1. Edit the file to add content")
|
|
97
|
+
yield* Console.log(" 2. Run 'confluence commit' to stage changes")
|
|
98
|
+
yield* Console.log(" 3. Run 'confluence push' to create on Confluence")
|
|
99
|
+
})).pipe(Command.withDescription("Create a new page to be pushed to Confluence"))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared page tree utilities for CLI commands.
|
|
3
|
+
*/
|
|
4
|
+
import type { PageTreeNode } from "../LocalFileSystem.js"
|
|
5
|
+
|
|
6
|
+
export interface PageChoice {
|
|
7
|
+
readonly title: string
|
|
8
|
+
readonly value: {
|
|
9
|
+
readonly path: string
|
|
10
|
+
readonly pageId: string | null
|
|
11
|
+
readonly nodeTitle: string
|
|
12
|
+
}
|
|
13
|
+
readonly description?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Flatten page tree to choices with indentation for interactive prompts.
|
|
18
|
+
*/
|
|
19
|
+
export const flattenPageTree = (
|
|
20
|
+
node: PageTreeNode,
|
|
21
|
+
depth: number = 0
|
|
22
|
+
): ReadonlyArray<PageChoice> => {
|
|
23
|
+
const indent = " ".repeat(depth)
|
|
24
|
+
const prefix = depth === 0 ? "" : "├─ "
|
|
25
|
+
const choices: Array<PageChoice> = []
|
|
26
|
+
|
|
27
|
+
choices.push({
|
|
28
|
+
title: `${indent}${prefix}${node.title}`,
|
|
29
|
+
value: { path: node.path, pageId: node.pageId, nodeTitle: node.title },
|
|
30
|
+
description: node.pageId ? `Page ID: ${node.pageId}` : "Root"
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
for (const child of node.children) {
|
|
34
|
+
for (const choice of flattenPageTree(child, depth + 1)) {
|
|
35
|
+
choices.push(choice)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return choices
|
|
40
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for CLI commands.
|
|
3
|
+
*/
|
|
4
|
+
import * as Config from "effect/Config"
|
|
5
|
+
import * as Effect from "effect/Effect"
|
|
6
|
+
import * as Option from "effect/Option"
|
|
7
|
+
import { ConfluenceAuth } from "../ConfluenceAuth.js"
|
|
8
|
+
|
|
9
|
+
const AuthConfig = Config.all({
|
|
10
|
+
apiKey: Config.string("CONFLUENCE_API_KEY"),
|
|
11
|
+
email: Config.string("CONFLUENCE_EMAIL")
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get authentication config from env vars or OAuth.
|
|
16
|
+
*/
|
|
17
|
+
export const getAuth = () =>
|
|
18
|
+
Effect.gen(function*() {
|
|
19
|
+
// 1. Try env vars first (backwards compat)
|
|
20
|
+
const envAuth = yield* AuthConfig.pipe(
|
|
21
|
+
Effect.map(({ apiKey, email }) => ({ type: "token" as const, email, token: apiKey })),
|
|
22
|
+
Effect.option
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if (Option.isSome(envAuth)) {
|
|
26
|
+
return envAuth.value
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. Try OAuth token
|
|
30
|
+
const auth = yield* ConfluenceAuth
|
|
31
|
+
const accessToken = yield* auth.getAccessToken()
|
|
32
|
+
const cloudId = yield* auth.getCloudId()
|
|
33
|
+
|
|
34
|
+
return { type: "oauth2" as const, accessToken, cloudId }
|
|
35
|
+
})
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync commands (pull, push, status) for Confluence CLI.
|
|
3
|
+
*/
|
|
4
|
+
import { Command, Options } from "@effect/cli"
|
|
5
|
+
import * as Console from "effect/Console"
|
|
6
|
+
import * as Effect from "effect/Effect"
|
|
7
|
+
import { GitService } from "../GitService.js"
|
|
8
|
+
import { SyncEngine } from "../SyncEngine.js"
|
|
9
|
+
|
|
10
|
+
// === Pull command ===
|
|
11
|
+
const forceOption = Options.boolean("force").pipe(
|
|
12
|
+
Options.withAlias("f"),
|
|
13
|
+
Options.withDescription("Overwrite local changes")
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const replayHistoryOption = Options.boolean("replay-history").pipe(
|
|
17
|
+
Options.withDescription("Replay version history as individual git commits")
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export const pullCommand = Command.make(
|
|
21
|
+
"pull",
|
|
22
|
+
{ force: forceOption, replayHistory: replayHistoryOption },
|
|
23
|
+
({ force, replayHistory }) =>
|
|
24
|
+
Effect.gen(function*() {
|
|
25
|
+
const engine = yield* SyncEngine
|
|
26
|
+
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
|
+
}
|
|
30
|
+
const result = yield* engine.pull({
|
|
31
|
+
force,
|
|
32
|
+
replayHistory,
|
|
33
|
+
...(replayHistory ? { onProgress } : {})
|
|
34
|
+
})
|
|
35
|
+
if (replayHistory) {
|
|
36
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r")
|
|
37
|
+
}
|
|
38
|
+
yield* Console.log(`Pulled ${result.pulled} pages`)
|
|
39
|
+
if (result.commits > 0) {
|
|
40
|
+
yield* Console.log(`Created ${result.commits} git commits`)
|
|
41
|
+
}
|
|
42
|
+
if (result.errors.length > 0) {
|
|
43
|
+
yield* Console.error("Errors:", result.errors.join("\n"))
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
).pipe(Command.withDescription("Download pages from Confluence to local markdown"))
|
|
47
|
+
|
|
48
|
+
// === Push command ===
|
|
49
|
+
const dryRunOption = Options.boolean("dry-run").pipe(
|
|
50
|
+
Options.withAlias("n"),
|
|
51
|
+
Options.withDescription("Show changes without applying")
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
export const pushCommand = Command.make(
|
|
55
|
+
"push",
|
|
56
|
+
{ dryRun: dryRunOption },
|
|
57
|
+
({ dryRun }) =>
|
|
58
|
+
Effect.gen(function*() {
|
|
59
|
+
const engine = yield* SyncEngine
|
|
60
|
+
const git = yield* GitService
|
|
61
|
+
|
|
62
|
+
// Check for uncommitted changes
|
|
63
|
+
const gitStatus = yield* git.status()
|
|
64
|
+
if (gitStatus.hasChanges) {
|
|
65
|
+
yield* Console.log("Uncommitted changes detected. Run 'confluence commit' first.")
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
yield* Console.log(dryRun ? "Dry run - showing changes..." : "Pushing changes to Confluence...")
|
|
70
|
+
const result = yield* engine.push({ dryRun })
|
|
71
|
+
if (result.pushed === 0 && result.created === 0 && result.deleted === 0) {
|
|
72
|
+
yield* Console.log("Nothing to push")
|
|
73
|
+
} else {
|
|
74
|
+
yield* Console.log(`Pushed: ${result.pushed}, Created: ${result.created}, Deleted: ${result.deleted}`)
|
|
75
|
+
}
|
|
76
|
+
if (result.errors.length > 0) {
|
|
77
|
+
yield* Console.error("Errors:", result.errors.join("\n"))
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
).pipe(Command.withDescription("Upload local markdown changes to Confluence"))
|
|
81
|
+
|
|
82
|
+
// === Status command ===
|
|
83
|
+
export const statusCommand = Command.make("status", {}, () =>
|
|
84
|
+
Effect.gen(function*() {
|
|
85
|
+
const engine = yield* SyncEngine
|
|
86
|
+
const git = yield* GitService
|
|
87
|
+
|
|
88
|
+
// Show git status
|
|
89
|
+
const gitInit = yield* git.isInitialized()
|
|
90
|
+
if (gitInit) {
|
|
91
|
+
const gitStatus = yield* git.status()
|
|
92
|
+
const commitCount = yield* git.log({ n: 1 }).pipe(
|
|
93
|
+
Effect.map((commits) => commits.length > 0 ? "has commits" : "no commits"),
|
|
94
|
+
Effect.catchAll(() => Effect.succeed("unknown"))
|
|
95
|
+
)
|
|
96
|
+
yield* Console.log(`Git: initialized (${commitCount})`)
|
|
97
|
+
if (gitStatus.hasChanges) {
|
|
98
|
+
const staged = gitStatus.entries.filter((e) => e.staged).length
|
|
99
|
+
const unstaged = gitStatus.entries.filter((e) => !e.staged).length
|
|
100
|
+
yield* Console.log(` Changes: ${staged} staged, ${unstaged} unstaged`)
|
|
101
|
+
}
|
|
102
|
+
if (gitStatus.hasConflicts) {
|
|
103
|
+
yield* Console.log(` Conflicts: ${gitStatus.conflictedFiles.length} files`)
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
yield* Console.log("Git: not initialized (run 'confluence git init')")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = yield* engine.status()
|
|
110
|
+
yield* Console.log(`
|
|
111
|
+
Sync Status:
|
|
112
|
+
Synced: ${result.synced}
|
|
113
|
+
Local Modified: ${result.localModified}
|
|
114
|
+
Remote Modified: ${result.remoteModified}
|
|
115
|
+
Conflicts: ${result.conflicts}
|
|
116
|
+
Local Only: ${result.localOnly}
|
|
117
|
+
Remote Only: ${result.remoteOnly}
|
|
118
|
+
`)
|
|
119
|
+
if (result.files.length > 0 && result.synced < result.files.length) {
|
|
120
|
+
yield* Console.log("Changed files:")
|
|
121
|
+
for (const file of result.files) {
|
|
122
|
+
if (file._tag !== "Synced" && file._tag !== "RemoteOnly") {
|
|
123
|
+
yield* Console.log(` [${file._tag}] ${file.path}`)
|
|
124
|
+
} else if (file._tag === "RemoteOnly") {
|
|
125
|
+
yield* Console.log(` [${file._tag}] ${file.page.title}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
})).pipe(Command.withDescription("Show sync status"))
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export * from "./Brand.js"
|
|
10
|
+
export { ConfluenceAuth, type ConfluenceAuthService, layer as ConfluenceAuthLayer } from "./ConfluenceAuth.js"
|
|
10
11
|
export {
|
|
11
12
|
ConfluenceClient,
|
|
12
13
|
type ConfluenceClientConfig,
|
|
@@ -26,10 +27,29 @@ export { layer as MarkdownConverterLayer, MarkdownConverter } from "./MarkdownCo
|
|
|
26
27
|
export * from "./Schemas.js"
|
|
27
28
|
export {
|
|
28
29
|
layer as SyncEngineLayer,
|
|
30
|
+
type ProgressCallback,
|
|
31
|
+
type PullOptions,
|
|
29
32
|
type PullResult,
|
|
30
33
|
type PushResult,
|
|
31
34
|
type StatusResult,
|
|
32
35
|
SyncEngine,
|
|
33
|
-
type SyncResult,
|
|
34
36
|
type SyncStatus
|
|
35
37
|
} from "./SyncEngine.js"
|
|
38
|
+
|
|
39
|
+
// AST types
|
|
40
|
+
export * from "./ast/index.js"
|
|
41
|
+
|
|
42
|
+
// Parsers
|
|
43
|
+
export { parseConfluenceHtml } from "./parsers/ConfluenceParser.js"
|
|
44
|
+
export { parseMarkdown } from "./parsers/MarkdownParser.js"
|
|
45
|
+
|
|
46
|
+
// Serializers
|
|
47
|
+
export { serializeToConfluence } from "./serializers/ConfluenceSerializer.js"
|
|
48
|
+
export { serializeToMarkdown } from "./serializers/MarkdownSerializer.js"
|
|
49
|
+
|
|
50
|
+
// Bi-directional schemas
|
|
51
|
+
export { DocumentFromConfluence } from "./schemas/ConfluenceSchema.js"
|
|
52
|
+
export { DocumentFromMarkdown } from "./schemas/MarkdownSchema.js"
|
|
53
|
+
|
|
54
|
+
// Schema converter errors
|
|
55
|
+
export { MigrationError, ParseError, SerializeError } from "./SchemaConverterError.js"
|