@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
package/src/bin.ts
CHANGED
|
@@ -2,237 +2,41 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* CLI entry point for confluence-to-markdown.
|
|
4
4
|
*/
|
|
5
|
-
import { Command
|
|
6
|
-
import * as NodeContext from "@effect/platform-node/NodeContext"
|
|
7
|
-
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient"
|
|
8
|
-
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
|
|
9
|
-
import * as NodeTerminal from "@effect/platform-node/NodeTerminal"
|
|
10
|
-
import * as Config from "effect/Config"
|
|
11
|
-
import * as Console from "effect/Console"
|
|
5
|
+
import { Command } from "@effect/cli"
|
|
12
6
|
import * as Effect from "effect/Effect"
|
|
13
|
-
import * as
|
|
14
|
-
import * as
|
|
7
|
+
import * as Logger from "effect/Logger"
|
|
8
|
+
import * as LogLevel from "effect/LogLevel"
|
|
15
9
|
import pkg from "../package.json" with { type: "json" }
|
|
16
|
-
import
|
|
17
|
-
import { ConfluenceClient, type ConfluenceClientConfig, layer as ConfluenceClientLayer } from "./ConfluenceClient.js"
|
|
10
|
+
import { handleError } from "./commands/errorHandler.js"
|
|
18
11
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
apiKey: Config.string("CONFLUENCE_API_KEY"),
|
|
32
|
-
email: Config.string("CONFLUENCE_EMAIL")
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const getAuth = (): Effect.Effect<ConfluenceClientConfig["auth"], AuthMissingError> =>
|
|
36
|
-
AuthConfig.pipe(
|
|
37
|
-
Effect.map(({ apiKey, email }) => ({ type: "token" as const, email, token: apiKey })),
|
|
38
|
-
Effect.mapError(() => new AuthMissingError())
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
// === Init command ===
|
|
42
|
-
const rootPageIdOption = Options.text("root-page-id").pipe(
|
|
43
|
-
Options.withDescription("Confluence root page ID to sync from"),
|
|
44
|
-
Options.optional
|
|
45
|
-
)
|
|
46
|
-
const baseUrlOption = Options.text("base-url").pipe(
|
|
47
|
-
Options.withDescription("Confluence Cloud base URL (e.g., https://yoursite.atlassian.net)"),
|
|
48
|
-
Options.optional
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
/** Validate page ID format */
|
|
52
|
-
const validatePageId = (input: string): Effect.Effect<string, ConfigError> =>
|
|
53
|
-
input.trim().length > 0
|
|
54
|
-
? Effect.succeed(input.trim())
|
|
55
|
-
: Effect.fail(new ConfigError({ message: "Page ID cannot be empty" }))
|
|
56
|
-
|
|
57
|
-
/** Validate base URL format */
|
|
58
|
-
const validateBaseUrl = (input: string): Effect.Effect<string, ConfigError> => {
|
|
59
|
-
const pattern = /^https:\/\/[a-z0-9-]+\.atlassian\.net$/
|
|
60
|
-
return pattern.test(input)
|
|
61
|
-
? Effect.succeed(input)
|
|
62
|
-
: Effect.fail(
|
|
63
|
-
new ConfigError({
|
|
64
|
-
message: `Invalid Confluence URL: ${input}. Expected format: https://yoursite.atlassian.net`
|
|
65
|
-
})
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const initCommand = Command.make(
|
|
70
|
-
"init",
|
|
71
|
-
{ rootPageId: rootPageIdOption, baseUrl: baseUrlOption },
|
|
72
|
-
({ baseUrl, rootPageId }) =>
|
|
73
|
-
Effect.gen(function*() {
|
|
74
|
-
const rawPageId = Option.isSome(rootPageId)
|
|
75
|
-
? rootPageId.value
|
|
76
|
-
: yield* Prompt.text({ message: "Enter Confluence root page ID:" })
|
|
77
|
-
const rawUrl = Option.isSome(baseUrl)
|
|
78
|
-
? baseUrl.value
|
|
79
|
-
: yield* Prompt.text({ message: "Enter Confluence base URL (e.g., https://yoursite.atlassian.net):" })
|
|
80
|
-
|
|
81
|
-
const pageId = yield* validatePageId(rawPageId)
|
|
82
|
-
const url = yield* validateBaseUrl(rawUrl)
|
|
83
|
-
|
|
84
|
-
const path = yield* createConfigFile(pageId, url)
|
|
85
|
-
yield* Console.log(`Created configuration file: ${path}`)
|
|
86
|
-
})
|
|
87
|
-
).pipe(Command.withDescription("Initialize .confluence.json configuration"))
|
|
88
|
-
|
|
89
|
-
// === Pull command ===
|
|
90
|
-
const forceOption = Options.boolean("force").pipe(
|
|
91
|
-
Options.withAlias("f"),
|
|
92
|
-
Options.withDescription("Overwrite local changes")
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
const pullCommand = Command.make("pull", { force: forceOption }, ({ force }) =>
|
|
96
|
-
Effect.gen(function*() {
|
|
97
|
-
const engine = yield* SyncEngine
|
|
98
|
-
yield* Console.log("Pulling pages from Confluence...")
|
|
99
|
-
const result = yield* engine.pull({ force })
|
|
100
|
-
yield* Console.log(`Pulled ${result.pulled} pages`)
|
|
101
|
-
if (result.errors.length > 0) {
|
|
102
|
-
yield* Console.error("Errors:", result.errors.join("\n"))
|
|
103
|
-
}
|
|
104
|
-
})).pipe(Command.withDescription("Download pages from Confluence to local markdown"))
|
|
105
|
-
|
|
106
|
-
// === Push command ===
|
|
107
|
-
const dryRunOption = Options.boolean("dry-run").pipe(
|
|
108
|
-
Options.withAlias("n"),
|
|
109
|
-
Options.withDescription("Show changes without applying")
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
const pushCommand = Command.make("push", { dryRun: dryRunOption }, ({ dryRun }) =>
|
|
113
|
-
Effect.gen(function*() {
|
|
114
|
-
const engine = yield* SyncEngine
|
|
115
|
-
yield* Console.log(dryRun ? "Dry run - showing changes..." : "Pushing changes to Confluence...")
|
|
116
|
-
const result = yield* engine.push({ dryRun })
|
|
117
|
-
yield* Console.log(`Pushed: ${result.pushed}, Created: ${result.created}, Skipped: ${result.skipped}`)
|
|
118
|
-
if (result.errors.length > 0) {
|
|
119
|
-
yield* Console.error("Errors:", result.errors.join("\n"))
|
|
120
|
-
}
|
|
121
|
-
})).pipe(Command.withDescription("Upload local markdown changes to Confluence"))
|
|
122
|
-
|
|
123
|
-
// === Sync command ===
|
|
124
|
-
const syncCommand = Command.make("sync", {}, () =>
|
|
125
|
-
Effect.gen(function*() {
|
|
126
|
-
const engine = yield* SyncEngine
|
|
127
|
-
yield* Console.log("Syncing with Confluence...")
|
|
128
|
-
const result = yield* engine.sync()
|
|
129
|
-
yield* Console.log(`Pulled: ${result.pulled}, Pushed: ${result.pushed}, Created: ${result.created}`)
|
|
130
|
-
if (result.conflicts > 0) {
|
|
131
|
-
yield* Console.warn(`Conflicts: ${result.conflicts}`)
|
|
132
|
-
}
|
|
133
|
-
if (result.errors.length > 0) {
|
|
134
|
-
yield* Console.error("Errors:", result.errors.join("\n"))
|
|
135
|
-
}
|
|
136
|
-
})).pipe(Command.withDescription("Bidirectional sync with conflict detection"))
|
|
137
|
-
|
|
138
|
-
// === Status command ===
|
|
139
|
-
const statusCommand = Command.make("status", {}, () =>
|
|
140
|
-
Effect.gen(function*() {
|
|
141
|
-
const engine = yield* SyncEngine
|
|
142
|
-
const result = yield* engine.status()
|
|
143
|
-
yield* Console.log(`
|
|
144
|
-
Sync Status:
|
|
145
|
-
Synced: ${result.synced}
|
|
146
|
-
Local Modified: ${result.localModified}
|
|
147
|
-
Remote Modified: ${result.remoteModified}
|
|
148
|
-
Conflicts: ${result.conflicts}
|
|
149
|
-
Local Only: ${result.localOnly}
|
|
150
|
-
Remote Only: ${result.remoteOnly}
|
|
151
|
-
`)
|
|
152
|
-
if (result.files.length > 0 && result.synced < result.files.length) {
|
|
153
|
-
yield* Console.log("Changed files:")
|
|
154
|
-
for (const file of result.files) {
|
|
155
|
-
if (file._tag !== "Synced" && file._tag !== "RemoteOnly") {
|
|
156
|
-
yield* Console.log(` [${file._tag}] ${file.path}`)
|
|
157
|
-
} else if (file._tag === "RemoteOnly") {
|
|
158
|
-
yield* Console.log(` [${file._tag}] ${file.page.title}`)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
})).pipe(Command.withDescription("Show sync status"))
|
|
12
|
+
authCommand,
|
|
13
|
+
cloneCommand,
|
|
14
|
+
commitCommand,
|
|
15
|
+
deleteCommand,
|
|
16
|
+
diffCommand,
|
|
17
|
+
logCommand,
|
|
18
|
+
newCommand,
|
|
19
|
+
pullCommand,
|
|
20
|
+
pushCommand,
|
|
21
|
+
statusCommand
|
|
22
|
+
} from "./commands/index.js"
|
|
23
|
+
import { AppLayer, AuthOnlyLayer, CloneLayer, getLayerType, MinimalLayer } from "./commands/layers.js"
|
|
163
24
|
|
|
164
25
|
// === Main command ===
|
|
165
26
|
const confluence = Command.make("confluence").pipe(
|
|
166
27
|
Command.withDescription("Sync Confluence pages to local markdown"),
|
|
167
|
-
Command.withSubcommands([
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// Dummy client layer for help/init (will fail if actually used)
|
|
180
|
-
const DummyConfluenceClientLayer = Layer.succeed(
|
|
181
|
-
ConfluenceClient,
|
|
182
|
-
ConfluenceClient.of({
|
|
183
|
-
getPage: () => Effect.dieMessage("Not configured"),
|
|
184
|
-
getChildren: () => Effect.dieMessage("Not configured"),
|
|
185
|
-
getAllChildren: () => Effect.dieMessage("Not configured"),
|
|
186
|
-
createPage: () => Effect.dieMessage("Not configured"),
|
|
187
|
-
updatePage: () => Effect.dieMessage("Not configured"),
|
|
188
|
-
deletePage: () => Effect.dieMessage("Not configured")
|
|
189
|
-
})
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
// Dummy sync engine that will fail if actually used
|
|
193
|
-
const DummySyncEngineLayer = Layer.succeed(
|
|
194
|
-
SyncEngine,
|
|
195
|
-
SyncEngine.of({
|
|
196
|
-
pull: () => Effect.dieMessage("Not configured - run 'confluence init' first"),
|
|
197
|
-
push: () => Effect.dieMessage("Not configured - run 'confluence init' first"),
|
|
198
|
-
sync: () => Effect.dieMessage("Not configured - run 'confluence init' first"),
|
|
199
|
-
status: () => Effect.dieMessage("Not configured - run 'confluence init' first")
|
|
200
|
-
})
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
// Build client layer dynamically based on auth
|
|
204
|
-
const ConfluenceClientLive = Layer.unwrapEffect(
|
|
205
|
-
Effect.gen(function*() {
|
|
206
|
-
const auth = yield* getAuth()
|
|
207
|
-
const config = yield* ConfluenceConfig
|
|
208
|
-
|
|
209
|
-
const clientConfig: ConfluenceClientConfig = {
|
|
210
|
-
baseUrl: config.baseUrl,
|
|
211
|
-
auth
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return ConfluenceClientLayer(clientConfig)
|
|
215
|
-
})
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
// Full app layer with all services
|
|
219
|
-
const AppLayer = SyncEngineLayer.pipe(
|
|
220
|
-
Layer.provideMerge(ConfluenceClientLive),
|
|
221
|
-
Layer.provideMerge(MarkdownConverterLayer),
|
|
222
|
-
Layer.provideMerge(LocalFileSystemLayer),
|
|
223
|
-
Layer.provideMerge(ConfluenceConfigLayer()),
|
|
224
|
-
Layer.provideMerge(NodeHttpClient.layer),
|
|
225
|
-
Layer.provideMerge(NodeContext.layer)
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
// Minimal layer for init/help (dummy services, won't be invoked)
|
|
229
|
-
const MinimalLayer = DummySyncEngineLayer.pipe(
|
|
230
|
-
Layer.provideMerge(DummyConfluenceClientLayer),
|
|
231
|
-
Layer.provideMerge(MarkdownConverterLayer),
|
|
232
|
-
Layer.provideMerge(LocalFileSystemLayer),
|
|
233
|
-
Layer.provideMerge(DummyConfigLayer),
|
|
234
|
-
Layer.provideMerge(NodeTerminal.layer),
|
|
235
|
-
Layer.provideMerge(NodeContext.layer)
|
|
28
|
+
Command.withSubcommands([
|
|
29
|
+
cloneCommand,
|
|
30
|
+
authCommand,
|
|
31
|
+
pullCommand,
|
|
32
|
+
pushCommand,
|
|
33
|
+
statusCommand,
|
|
34
|
+
commitCommand,
|
|
35
|
+
logCommand,
|
|
36
|
+
diffCommand,
|
|
37
|
+
newCommand,
|
|
38
|
+
deleteCommand
|
|
39
|
+
])
|
|
236
40
|
)
|
|
237
41
|
|
|
238
42
|
// === Run CLI ===
|
|
@@ -241,29 +45,26 @@ const cli = Command.run(confluence, {
|
|
|
241
45
|
version: pkg.version
|
|
242
46
|
})
|
|
243
47
|
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
48
|
+
const layerType = getLayerType()
|
|
49
|
+
const layer = layerType === "full"
|
|
50
|
+
? AppLayer
|
|
51
|
+
: layerType === "auth"
|
|
52
|
+
? AuthOnlyLayer
|
|
53
|
+
: layerType === "clone"
|
|
54
|
+
? CloneLayer
|
|
55
|
+
: MinimalLayer
|
|
56
|
+
|
|
57
|
+
// Suppress verbose Effect logs (e.g. token refresh messages)
|
|
58
|
+
const SilentLogger = Logger.replace(Logger.defaultLogger, Logger.none)
|
|
59
|
+
|
|
60
|
+
Effect.suspend(() => cli(process.argv)).pipe(
|
|
61
|
+
Effect.provide(layer),
|
|
62
|
+
Effect.provide(SilentLogger),
|
|
63
|
+
Logger.withMinimumLogLevel(LogLevel.None),
|
|
64
|
+
Effect.runPromiseExit
|
|
65
|
+
).then((exit) => {
|
|
66
|
+
if (exit._tag === "Failure") {
|
|
67
|
+
handleError(exit.cause)
|
|
68
|
+
process.exit(1)
|
|
251
69
|
}
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const main = Effect.suspend(() => cli(process.argv))
|
|
256
|
-
|
|
257
|
-
if (needsFullLayer()) {
|
|
258
|
-
main.pipe(
|
|
259
|
-
Effect.provide(AppLayer),
|
|
260
|
-
Effect.tapErrorCause(Effect.logError),
|
|
261
|
-
NodeRuntime.runMain
|
|
262
|
-
)
|
|
263
|
-
} else {
|
|
264
|
-
main.pipe(
|
|
265
|
-
Effect.provide(MinimalLayer),
|
|
266
|
-
Effect.tapErrorCause(Effect.logError),
|
|
267
|
-
NodeRuntime.runMain
|
|
268
|
-
)
|
|
269
|
-
}
|
|
70
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication commands for Confluence CLI.
|
|
3
|
+
*/
|
|
4
|
+
import { Command, Options, Prompt } 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 { ConfluenceAuth } from "../ConfluenceAuth.js"
|
|
9
|
+
|
|
10
|
+
// === Auth create command ===
|
|
11
|
+
const createCommand = Command.make("create", {}, () =>
|
|
12
|
+
Effect.gen(function*() {
|
|
13
|
+
yield* Console.log(`
|
|
14
|
+
Creating OAuth app in Atlassian Developer Console...
|
|
15
|
+
|
|
16
|
+
1. Browser will open to create a new OAuth 2.0 (3LO) app
|
|
17
|
+
2. Enter app name (e.g., "Confluence CLI")
|
|
18
|
+
3. After creation, go to "Permissions" and add:
|
|
19
|
+
- Confluence API (granular): read:page:confluence, write:page:confluence, delete:page:confluence
|
|
20
|
+
- User Identity API: read:me
|
|
21
|
+
4. Go to "Authorization" and set callback URL:
|
|
22
|
+
http://localhost:8585/callback
|
|
23
|
+
5. Go to "Settings" and copy Client ID and Secret
|
|
24
|
+
6. Run: confluence auth configure --client-id <ID> --client-secret <SECRET>
|
|
25
|
+
`)
|
|
26
|
+
const url = "https://developer.atlassian.com/console/myapps/create-3lo-app/"
|
|
27
|
+
yield* Effect.promise(() =>
|
|
28
|
+
import("node:child_process").then((cp) =>
|
|
29
|
+
new Promise<void>((resolve, reject) => {
|
|
30
|
+
const platform = process.platform
|
|
31
|
+
if (platform === "darwin") {
|
|
32
|
+
cp.execFile("open", [url], (err) => err ? reject(err) : resolve())
|
|
33
|
+
} else if (platform === "win32") {
|
|
34
|
+
cp.execFile("cmd", ["/c", "start", "", url], (err) => err ? reject(err) : resolve())
|
|
35
|
+
} else {
|
|
36
|
+
cp.execFile("xdg-open", [url], (err) => err ? reject(err) : resolve())
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
})).pipe(Command.withDescription("Create OAuth app in Atlassian Developer Console"))
|
|
42
|
+
|
|
43
|
+
// === Auth configure command ===
|
|
44
|
+
const clientIdOption = Options.text("client-id").pipe(
|
|
45
|
+
Options.withDescription("OAuth client ID from Atlassian Developer Console"),
|
|
46
|
+
Options.optional
|
|
47
|
+
)
|
|
48
|
+
const clientSecretOption = Options.text("client-secret").pipe(
|
|
49
|
+
Options.withDescription("OAuth client secret"),
|
|
50
|
+
Options.optional
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const configureCommand = Command.make(
|
|
54
|
+
"configure",
|
|
55
|
+
{ clientId: clientIdOption, clientSecret: clientSecretOption },
|
|
56
|
+
({ clientId, clientSecret }) =>
|
|
57
|
+
Effect.gen(function*() {
|
|
58
|
+
const auth = yield* ConfluenceAuth
|
|
59
|
+
|
|
60
|
+
const rawClientId = Option.isSome(clientId)
|
|
61
|
+
? clientId.value
|
|
62
|
+
: yield* Prompt.text({ message: "Enter OAuth client ID:" })
|
|
63
|
+
const rawClientSecret = Option.isSome(clientSecret)
|
|
64
|
+
? clientSecret.value
|
|
65
|
+
: yield* Prompt.text({ message: "Enter OAuth client secret:" })
|
|
66
|
+
|
|
67
|
+
yield* auth.configure({ clientId: rawClientId, clientSecret: rawClientSecret })
|
|
68
|
+
yield* Console.log("OAuth configured. Run 'confluence auth login' to authenticate.")
|
|
69
|
+
})
|
|
70
|
+
).pipe(Command.withDescription("Configure OAuth client credentials"))
|
|
71
|
+
|
|
72
|
+
// === Auth login command ===
|
|
73
|
+
const siteOption = Options.text("site").pipe(
|
|
74
|
+
Options.withDescription("Confluence site URL to use (for accounts with multiple sites)"),
|
|
75
|
+
Options.optional
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const loginCommand = Command.make("login", { site: siteOption }, ({ site }) =>
|
|
79
|
+
Effect.gen(function*() {
|
|
80
|
+
const auth = yield* ConfluenceAuth
|
|
81
|
+
const result = yield* auth.login(Option.isSome(site) ? { siteUrl: site.value } : undefined)
|
|
82
|
+
if (Array.isArray(result) && result.length > 0) {
|
|
83
|
+
yield* Console.log("\nRe-run with --site to select a specific site.")
|
|
84
|
+
}
|
|
85
|
+
})).pipe(Command.withDescription("Authenticate with Atlassian via OAuth"))
|
|
86
|
+
|
|
87
|
+
// === Auth logout command ===
|
|
88
|
+
const logoutCommand = Command.make("logout", {}, () =>
|
|
89
|
+
Effect.gen(function*() {
|
|
90
|
+
const auth = yield* ConfluenceAuth
|
|
91
|
+
yield* auth.logout()
|
|
92
|
+
yield* Console.log("Logged out")
|
|
93
|
+
})).pipe(Command.withDescription("Remove stored authentication"))
|
|
94
|
+
|
|
95
|
+
// === Auth status command ===
|
|
96
|
+
const statusCommand = Command.make("status", {}, () =>
|
|
97
|
+
Effect.gen(function*() {
|
|
98
|
+
const auth = yield* ConfluenceAuth
|
|
99
|
+
const user = yield* auth.getCurrentUser()
|
|
100
|
+
if (user) {
|
|
101
|
+
yield* Console.log(`Logged in as: ${user.name} (${user.email})`)
|
|
102
|
+
} else {
|
|
103
|
+
yield* Console.log("Not logged in. Use 'confluence auth login' to authenticate.")
|
|
104
|
+
}
|
|
105
|
+
})).pipe(Command.withDescription("Show authentication status"))
|
|
106
|
+
|
|
107
|
+
// === Auth command group ===
|
|
108
|
+
export const authCommand = Command.make("auth").pipe(
|
|
109
|
+
Command.withDescription("Manage OAuth authentication"),
|
|
110
|
+
Command.withSubcommands([
|
|
111
|
+
createCommand,
|
|
112
|
+
configureCommand,
|
|
113
|
+
loginCommand,
|
|
114
|
+
logoutCommand,
|
|
115
|
+
statusCommand
|
|
116
|
+
])
|
|
117
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone command for Confluence CLI.
|
|
3
|
+
*/
|
|
4
|
+
import { Command, Options, Prompt } from "@effect/cli"
|
|
5
|
+
import * as NodeContext from "@effect/platform-node/NodeContext"
|
|
6
|
+
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient"
|
|
7
|
+
import * as Console from "effect/Console"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as Layer from "effect/Layer"
|
|
10
|
+
import * as Option from "effect/Option"
|
|
11
|
+
import type { PageId } from "../Brand.js"
|
|
12
|
+
import { type ConfluenceClientConfig, layer as ConfluenceClientLayer } from "../ConfluenceClient.js"
|
|
13
|
+
import { createConfigFile, layerFromValues as ConfluenceConfigLayerFromValues } from "../ConfluenceConfig.js"
|
|
14
|
+
import { ConfigError } from "../ConfluenceError.js"
|
|
15
|
+
import { GitService, layer as GitServiceLayer } from "../GitService.js"
|
|
16
|
+
import { UserCacheLayer } from "../internal/userCache.js"
|
|
17
|
+
import { layer as LocalFileSystemLayer } from "../LocalFileSystem.js"
|
|
18
|
+
import { layer as MarkdownConverterLayer } from "../MarkdownConverter.js"
|
|
19
|
+
import { layer as SyncEngineLayer, SyncEngine } from "../SyncEngine.js"
|
|
20
|
+
import { getAuth } from "./shared.js"
|
|
21
|
+
|
|
22
|
+
const rootPageIdOption = Options.text("root-page-id").pipe(
|
|
23
|
+
Options.withDescription("Confluence root page ID to sync from"),
|
|
24
|
+
Options.optional
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const baseUrlOption = Options.text("base-url").pipe(
|
|
28
|
+
Options.withDescription("Confluence Cloud base URL (e.g., https://yoursite.atlassian.net)"),
|
|
29
|
+
Options.optional
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
/** Validate page ID format */
|
|
33
|
+
const validatePageId = (input: string): Effect.Effect<string, ConfigError> =>
|
|
34
|
+
input.trim().length > 0
|
|
35
|
+
? Effect.succeed(input.trim())
|
|
36
|
+
: Effect.fail(new ConfigError({ message: "Page ID cannot be empty" }))
|
|
37
|
+
|
|
38
|
+
/** Validate base URL format */
|
|
39
|
+
const validateBaseUrl = (input: string): Effect.Effect<string, ConfigError> => {
|
|
40
|
+
const pattern = /^https:\/\/[a-z0-9-]+\.atlassian\.net$/
|
|
41
|
+
return pattern.test(input)
|
|
42
|
+
? Effect.succeed(input)
|
|
43
|
+
: Effect.fail(
|
|
44
|
+
new ConfigError({
|
|
45
|
+
message: `Invalid Confluence URL: ${input}. Expected format: https://yoursite.atlassian.net`
|
|
46
|
+
})
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const cloneCommand = Command.make(
|
|
51
|
+
"clone",
|
|
52
|
+
{ rootPageId: rootPageIdOption, baseUrl: baseUrlOption },
|
|
53
|
+
({ baseUrl, rootPageId }) =>
|
|
54
|
+
Effect.gen(function*() {
|
|
55
|
+
const git = yield* GitService
|
|
56
|
+
|
|
57
|
+
// Fail if .confluence already exists
|
|
58
|
+
const isGitInit = yield* git.isInitialized()
|
|
59
|
+
if (isGitInit) {
|
|
60
|
+
return yield* Effect.fail(
|
|
61
|
+
new ConfigError({ message: "Already cloned. Use 'confluence pull' to update." })
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validate git is installed
|
|
66
|
+
yield* Console.log("Checking git installation...")
|
|
67
|
+
const gitVersion = yield* git.validateGit().pipe(
|
|
68
|
+
Effect.mapError(() =>
|
|
69
|
+
new ConfigError({ message: "Git is required but not installed. Please install git first." })
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
yield* Console.log(`Found git ${gitVersion}`)
|
|
73
|
+
|
|
74
|
+
const rawPageId = Option.isSome(rootPageId)
|
|
75
|
+
? rootPageId.value
|
|
76
|
+
: yield* Prompt.text({ message: "Enter Confluence root page ID:" })
|
|
77
|
+
const rawUrl = Option.isSome(baseUrl)
|
|
78
|
+
? baseUrl.value
|
|
79
|
+
: yield* Prompt.text({ message: "Enter Confluence base URL (e.g., https://yoursite.atlassian.net):" })
|
|
80
|
+
|
|
81
|
+
const pageId = yield* validatePageId(rawPageId)
|
|
82
|
+
const url = yield* validateBaseUrl(rawUrl)
|
|
83
|
+
|
|
84
|
+
const path = yield* createConfigFile(pageId, url)
|
|
85
|
+
yield* Console.log(`Created configuration file: ${path}`)
|
|
86
|
+
|
|
87
|
+
// Initialize git repo
|
|
88
|
+
yield* Console.log("Initializing git repository...")
|
|
89
|
+
yield* git.init().pipe(
|
|
90
|
+
Effect.mapError(() => new ConfigError({ message: "Failed to initialize git repository" }))
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// Build services dynamically with the new config
|
|
94
|
+
yield* Console.log("Cloning pages from Confluence with full history...")
|
|
95
|
+
|
|
96
|
+
// Get auth
|
|
97
|
+
const auth = yield* getAuth()
|
|
98
|
+
const clientConfig: ConfluenceClientConfig = { baseUrl: url, auth }
|
|
99
|
+
|
|
100
|
+
// Build layers for the clone operation
|
|
101
|
+
const configLayer = ConfluenceConfigLayerFromValues({
|
|
102
|
+
rootPageId: pageId as PageId,
|
|
103
|
+
baseUrl: url,
|
|
104
|
+
docsPath: ".confluence/docs",
|
|
105
|
+
excludePatterns: [],
|
|
106
|
+
saveSource: false,
|
|
107
|
+
trackedPaths: ["**/*.md"]
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const clientLayer = ConfluenceClientLayer(clientConfig).pipe(
|
|
111
|
+
Layer.provide(NodeHttpClient.layer)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const cloneLayer = SyncEngineLayer.pipe(
|
|
115
|
+
Layer.provideMerge(UserCacheLayer),
|
|
116
|
+
Layer.provideMerge(GitServiceLayer),
|
|
117
|
+
Layer.provideMerge(clientLayer),
|
|
118
|
+
Layer.provideMerge(MarkdownConverterLayer),
|
|
119
|
+
Layer.provideMerge(LocalFileSystemLayer),
|
|
120
|
+
Layer.provideMerge(configLayer),
|
|
121
|
+
Layer.provideMerge(NodeContext.layer)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
const result = yield* Effect.gen(function*() {
|
|
125
|
+
const engine = yield* SyncEngine
|
|
126
|
+
const gitService = yield* GitService
|
|
127
|
+
const pullResult = yield* engine.pull({
|
|
128
|
+
force: true,
|
|
129
|
+
replayHistory: true,
|
|
130
|
+
onProgress: (current, total, message) => {
|
|
131
|
+
process.stdout.write(`\r Replaying history: ${current}/${total} - ${message}`)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Create origin/confluence branch at HEAD to track remote state
|
|
136
|
+
yield* gitService.createBranch("origin/confluence")
|
|
137
|
+
|
|
138
|
+
return pullResult
|
|
139
|
+
}).pipe(Effect.provide(cloneLayer))
|
|
140
|
+
|
|
141
|
+
// Clear progress line and print final result
|
|
142
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r")
|
|
143
|
+
yield* Console.log(`Cloned ${result.pulled} pages with ${result.commits} commits`)
|
|
144
|
+
})
|
|
145
|
+
).pipe(Command.withDescription("Clone Confluence pages with full version history"))
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete command for Confluence CLI.
|
|
3
|
+
*
|
|
4
|
+
* Interactive mode: select page from tree, delete local file only.
|
|
5
|
+
* Push to actually delete from Confluence.
|
|
6
|
+
*/
|
|
7
|
+
import { Command, Prompt } from "@effect/cli"
|
|
8
|
+
import * as FileSystem from "@effect/platform/FileSystem"
|
|
9
|
+
import * as Path from "@effect/platform/Path"
|
|
10
|
+
import * as Console from "effect/Console"
|
|
11
|
+
import * as Effect from "effect/Effect"
|
|
12
|
+
import { ConfluenceConfig } from "../ConfluenceConfig.js"
|
|
13
|
+
import { LocalFileSystem } from "../LocalFileSystem.js"
|
|
14
|
+
import { flattenPageTree } from "./pageTree.js"
|
|
15
|
+
|
|
16
|
+
export const deleteCommand = Command.make("delete", {}, () =>
|
|
17
|
+
Effect.gen(function*() {
|
|
18
|
+
const localFs = yield* LocalFileSystem
|
|
19
|
+
const config = yield* ConfluenceConfig
|
|
20
|
+
const pathService = yield* Path.Path
|
|
21
|
+
const fs = yield* FileSystem.FileSystem
|
|
22
|
+
|
|
23
|
+
const docsPath = pathService.join(process.cwd(), config.docsPath)
|
|
24
|
+
|
|
25
|
+
// Interactive mode - show page tree, delete local file
|
|
26
|
+
yield* Console.log("Scanning page structure...")
|
|
27
|
+
const tree = yield* localFs.buildPageTree(docsPath, config.rootPageId, "Root")
|
|
28
|
+
|
|
29
|
+
// Flatten to choices (exclude pages without pageId - they're not on Confluence)
|
|
30
|
+
const allChoices = flattenPageTree(tree)
|
|
31
|
+
const choices = allChoices.filter((c) => c.value.pageId !== null)
|
|
32
|
+
|
|
33
|
+
if (choices.length === 0) {
|
|
34
|
+
yield* Console.log("No pages found to delete. Run 'confluence clone' or 'confluence pull' first.")
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const selected = yield* Prompt.select({
|
|
39
|
+
message: "Select page to delete:",
|
|
40
|
+
choices
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if (!selected.pageId || !selected.path) {
|
|
44
|
+
yield* Console.log("Selected page has no pageId - cannot delete.")
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Delete local file
|
|
49
|
+
const filePath = pathService.join(docsPath, selected.path)
|
|
50
|
+
yield* fs.remove(filePath)
|
|
51
|
+
|
|
52
|
+
yield* Console.log(`Deleted: ${selected.path}`)
|
|
53
|
+
yield* Console.log("")
|
|
54
|
+
yield* Console.log("Next steps:")
|
|
55
|
+
yield* Console.log(" 1. Run 'confluence commit' to stage the deletion")
|
|
56
|
+
yield* Console.log(" 2. Run 'confluence push' to delete from Confluence")
|
|
57
|
+
})).pipe(Command.withDescription("Delete a page locally (push to remove from Confluence)"))
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI error handler.
|
|
3
|
+
*
|
|
4
|
+
* We use custom error handling instead of built-in options because:
|
|
5
|
+
* - Cause.pretty() includes stack traces which are noisy for CLI users
|
|
6
|
+
* - Logger.pretty is for logging, not error output
|
|
7
|
+
* - NodeRuntime.runMain error reporting shows full cause structure
|
|
8
|
+
*/
|
|
9
|
+
import * as Cause from "effect/Cause"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Print errors to stderr without stack traces.
|
|
13
|
+
*/
|
|
14
|
+
export const handleError = <E>(cause: Cause.Cause<E>): void => {
|
|
15
|
+
if (Cause.isEmpty(cause)) return
|
|
16
|
+
|
|
17
|
+
for (const error of Cause.failures(cause)) {
|
|
18
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
19
|
+
process.stderr.write(`${(error as { message: string }).message}\n`)
|
|
20
|
+
} else {
|
|
21
|
+
process.stderr.write(`${String(error)}\n`)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const defect of Cause.defects(cause)) {
|
|
26
|
+
if (defect instanceof Error) {
|
|
27
|
+
process.stderr.write(`Error: ${defect.message}\n`)
|
|
28
|
+
} else {
|
|
29
|
+
process.stderr.write(`Error: ${String(defect)}\n`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|