@knpkv/confluence-to-markdown 0.2.0 → 0.4.2
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 +73 -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 +366 -0
- package/dist/ConfluenceAuth.js.map +1 -0
- package/dist/ConfluenceClient.d.ts +26 -12
- package/dist/ConfluenceClient.d.ts.map +1 -1
- package/dist/ConfluenceClient.js +139 -97
- 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 +99 -16
- 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 +32 -21
- package/src/ConfluenceAuth.ts +581 -0
- package/src/ConfluenceClient.ts +230 -165
- 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/ConfluenceClient.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Confluence REST API v2 client service.
|
|
3
3
|
*
|
|
4
|
+
* Wraps @knpkv/confluence-api-client with rate limit retry logic and pagination helpers.
|
|
5
|
+
*
|
|
4
6
|
* @module
|
|
5
7
|
*/
|
|
6
|
-
import
|
|
7
|
-
import * as HttpClientRequest from "@effect/platform/HttpClientRequest"
|
|
8
|
+
import { ConfluenceApiClient, ConfluenceApiConfig, type FetchClientError, toEffect } from "@knpkv/confluence-api-client"
|
|
8
9
|
import * as Context from "effect/Context"
|
|
9
10
|
import * as Effect from "effect/Effect"
|
|
10
11
|
import * as Layer from "effect/Layer"
|
|
12
|
+
import * as Redacted from "effect/Redacted"
|
|
11
13
|
import * as Schedule from "effect/Schedule"
|
|
12
|
-
import * as Schema from "effect/Schema"
|
|
13
14
|
import type { PageId } from "./Brand.js"
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
15
|
+
import type { RateLimitError } from "./ConfluenceError.js"
|
|
16
|
+
import { ApiError } from "./ConfluenceError.js"
|
|
17
|
+
import type { AtlassianUser, PageChildrenResponse, PageListItem, PageResponse, PageVersion } from "./Schemas.js"
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Request to create a new page.
|
|
@@ -100,6 +101,30 @@ export class ConfluenceClient extends Context.Tag(
|
|
|
100
101
|
* Delete a page.
|
|
101
102
|
*/
|
|
102
103
|
readonly deletePage: (id: PageId) => Effect.Effect<void, ApiError | RateLimitError>
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get version history for a page.
|
|
107
|
+
*/
|
|
108
|
+
readonly getPageVersions: (
|
|
109
|
+
id: PageId,
|
|
110
|
+
options?: { since?: number; includeBody?: boolean }
|
|
111
|
+
) => Effect.Effect<ReadonlyArray<PageVersion>, ApiError | RateLimitError>
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get user info by account ID.
|
|
115
|
+
*/
|
|
116
|
+
readonly getUser: (accountId: string) => Effect.Effect<AtlassianUser, ApiError | RateLimitError>
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get space ID for a page.
|
|
120
|
+
*/
|
|
121
|
+
readonly getSpaceId: (pageId: PageId) => Effect.Effect<string, ApiError | RateLimitError>
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set editor version for a page (v1 or v2).
|
|
125
|
+
* Uses V2 page properties API.
|
|
126
|
+
*/
|
|
127
|
+
readonly setEditorVersion: (pageId: PageId, version: "v1" | "v2") => Effect.Effect<void, ApiError | RateLimitError>
|
|
103
128
|
}
|
|
104
129
|
>() {}
|
|
105
130
|
|
|
@@ -117,9 +142,16 @@ export interface ConfluenceClientConfig {
|
|
|
117
142
|
} | {
|
|
118
143
|
readonly type: "oauth2"
|
|
119
144
|
readonly accessToken: string
|
|
145
|
+
readonly cloudId: string
|
|
120
146
|
}
|
|
121
147
|
}
|
|
122
148
|
|
|
149
|
+
/** Maximum pagination iterations to prevent infinite loops */
|
|
150
|
+
const MAX_PAGINATION_ITERATIONS = 100
|
|
151
|
+
|
|
152
|
+
/** Default page size for version fetching */
|
|
153
|
+
const VERSIONS_PAGE_SIZE = 50
|
|
154
|
+
|
|
123
155
|
/**
|
|
124
156
|
* Rate limit retry schedule with exponential backoff.
|
|
125
157
|
*/
|
|
@@ -129,216 +161,247 @@ const rateLimitSchedule = Schedule.exponential("1 second").pipe(
|
|
|
129
161
|
Schedule.intersect(Schedule.recurs(3))
|
|
130
162
|
)
|
|
131
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Map API client errors to domain errors.
|
|
166
|
+
*/
|
|
167
|
+
const mapApiError = (error: FetchClientError, endpoint: string, pageId?: string): ApiError =>
|
|
168
|
+
new ApiError({
|
|
169
|
+
status: error.status,
|
|
170
|
+
message: error.message,
|
|
171
|
+
endpoint,
|
|
172
|
+
...(pageId !== undefined && { pageId })
|
|
173
|
+
})
|
|
174
|
+
|
|
132
175
|
/**
|
|
133
176
|
* Create the Confluence client service.
|
|
134
177
|
*/
|
|
135
178
|
const make = (
|
|
136
179
|
config: ConfluenceClientConfig
|
|
137
|
-
): Effect.Effect<Context.Tag.Service<typeof ConfluenceClient
|
|
180
|
+
): Effect.Effect<Context.Tag.Service<typeof ConfluenceClient>> =>
|
|
138
181
|
Effect.gen(function*() {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
182
|
+
// Create underlying API client
|
|
183
|
+
const apiConfigLayer = Layer.succeed(ConfluenceApiConfig, {
|
|
184
|
+
baseUrl: config.baseUrl,
|
|
185
|
+
auth: config.auth.type === "token"
|
|
186
|
+
? { type: "basic", email: config.auth.email, apiToken: Redacted.make(config.auth.token) }
|
|
187
|
+
: { type: "oauth2", accessToken: Redacted.make(config.auth.accessToken), cloudId: config.auth.cloudId }
|
|
188
|
+
})
|
|
144
189
|
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
HttpClientRequest.setHeader("Content-Type", "application/json")
|
|
190
|
+
const apiClient = yield* ConfluenceApiClient.pipe(
|
|
191
|
+
Effect.provide(ConfluenceApiClient.layer),
|
|
192
|
+
Effect.provide(apiConfigLayer)
|
|
149
193
|
)
|
|
150
194
|
|
|
151
|
-
/**
|
|
152
|
-
* Make an HTTP request to the Confluence API.
|
|
153
|
-
* Returns raw JSON - callers must validate with Schema.decodeUnknown.
|
|
154
|
-
*/
|
|
155
|
-
const request = (
|
|
156
|
-
method: "GET" | "POST" | "PUT" | "DELETE",
|
|
157
|
-
path: string,
|
|
158
|
-
body?: unknown
|
|
159
|
-
): Effect.Effect<unknown, ApiError | RateLimitError, never> =>
|
|
160
|
-
Effect.gen(function*() {
|
|
161
|
-
let req = baseRequest.pipe(
|
|
162
|
-
HttpClientRequest.setMethod(method),
|
|
163
|
-
HttpClientRequest.setUrl(`${config.baseUrl}/wiki/api/v2${path}`)
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
if (body !== undefined) {
|
|
167
|
-
req = HttpClientRequest.bodyJson(req, body).pipe(
|
|
168
|
-
Effect.catchAll(() => Effect.succeed(req)),
|
|
169
|
-
Effect.runSync
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const response = yield* httpClient.execute(req).pipe(
|
|
174
|
-
Effect.mapError((error) =>
|
|
175
|
-
new ApiError({
|
|
176
|
-
status: 0,
|
|
177
|
-
message: `Request failed: ${error.message}`,
|
|
178
|
-
endpoint: path
|
|
179
|
-
})
|
|
180
|
-
)
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
if (response.status === 429) {
|
|
184
|
-
const retryAfterHeader = response.headers["retry-after"]
|
|
185
|
-
const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : undefined
|
|
186
|
-
return yield* Effect.fail(
|
|
187
|
-
retryAfter !== undefined
|
|
188
|
-
? new RateLimitError({ retryAfter })
|
|
189
|
-
: new RateLimitError({})
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (response.status >= 400) {
|
|
194
|
-
const text = yield* response.text.pipe(
|
|
195
|
-
Effect.catchAll(() => Effect.succeed(""))
|
|
196
|
-
)
|
|
197
|
-
return yield* Effect.fail(
|
|
198
|
-
new ApiError({
|
|
199
|
-
status: response.status,
|
|
200
|
-
message: text || `HTTP ${response.status}`,
|
|
201
|
-
endpoint: path
|
|
202
|
-
})
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (method === "DELETE" && response.status === 204) {
|
|
207
|
-
return undefined
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const json = yield* response.json.pipe(
|
|
211
|
-
Effect.mapError((error) =>
|
|
212
|
-
new ApiError({
|
|
213
|
-
status: response.status,
|
|
214
|
-
message: `Failed to parse response: ${error}`,
|
|
215
|
-
endpoint: path
|
|
216
|
-
})
|
|
217
|
-
)
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
return json
|
|
221
|
-
}).pipe(
|
|
222
|
-
Effect.retry(rateLimitSchedule)
|
|
223
|
-
)
|
|
224
|
-
|
|
225
195
|
const getPage = (id: PageId): Effect.Effect<PageResponse, ApiError | RateLimitError> =>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
Effect.mapError((error) =>
|
|
233
|
-
new ApiError({
|
|
234
|
-
status: 0,
|
|
235
|
-
message: `Invalid response schema: ${error.message}`,
|
|
236
|
-
endpoint: `/pages/${id}`,
|
|
237
|
-
pageId: id
|
|
238
|
-
})
|
|
239
|
-
)
|
|
240
|
-
)
|
|
241
|
-
})
|
|
196
|
+
toEffect(apiClient.v2.client.GET("/pages/{id}", {
|
|
197
|
+
params: { path: { id: Number(id) }, query: { "body-format": "storage" } }
|
|
198
|
+
})).pipe(
|
|
199
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${id}`, id)),
|
|
200
|
+
Effect.retry(rateLimitSchedule)
|
|
201
|
+
) as Effect.Effect<PageResponse, ApiError | RateLimitError>
|
|
242
202
|
|
|
243
203
|
const getChildren = (id: PageId): Effect.Effect<PageChildrenResponse, ApiError | RateLimitError> =>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
Effect.mapError((error) =>
|
|
251
|
-
new ApiError({
|
|
252
|
-
status: 0,
|
|
253
|
-
message: `Invalid response schema: ${error.message}`,
|
|
254
|
-
endpoint: `/pages/${id}/children`,
|
|
255
|
-
pageId: id
|
|
256
|
-
})
|
|
257
|
-
)
|
|
258
|
-
)
|
|
259
|
-
})
|
|
204
|
+
toEffect(apiClient.v2.client.GET("/pages/{id}/children", {
|
|
205
|
+
params: { path: { id: Number(id) } }
|
|
206
|
+
})).pipe(
|
|
207
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${id}/children`, id)),
|
|
208
|
+
Effect.retry(rateLimitSchedule)
|
|
209
|
+
) as Effect.Effect<PageChildrenResponse, ApiError | RateLimitError>
|
|
260
210
|
|
|
261
211
|
const getAllChildren = (id: PageId): Effect.Effect<ReadonlyArray<PageListItem>, ApiError | RateLimitError> =>
|
|
262
212
|
Effect.gen(function*() {
|
|
263
213
|
const allChildren: Array<PageListItem> = []
|
|
264
214
|
let cursor: string | undefined
|
|
265
215
|
let iterations = 0
|
|
266
|
-
const maxIterations = 100 // Prevent unbounded pagination
|
|
267
216
|
|
|
268
217
|
do {
|
|
269
|
-
if (iterations >=
|
|
218
|
+
if (iterations >= MAX_PAGINATION_ITERATIONS) {
|
|
270
219
|
return yield* Effect.fail(
|
|
271
220
|
new ApiError({
|
|
272
221
|
status: 0,
|
|
273
|
-
message: `Pagination limit exceeded: more than ${
|
|
222
|
+
message: `Pagination limit exceeded: more than ${MAX_PAGINATION_ITERATIONS} pages of children`,
|
|
274
223
|
endpoint: `/pages/${id}/children`,
|
|
275
224
|
pageId: id
|
|
276
225
|
})
|
|
277
226
|
)
|
|
278
227
|
}
|
|
279
228
|
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
229
|
+
const response = yield* toEffect(apiClient.v2.client.GET("/pages/{id}/children", {
|
|
230
|
+
params: { path: { id: Number(id) }, query: { ...(cursor ? { cursor } : {}) } }
|
|
231
|
+
})).pipe(
|
|
232
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${id}/children`, id)),
|
|
233
|
+
Effect.retry(rateLimitSchedule)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
for (const child of (response as { results?: Array<PageListItem> }).results ?? []) {
|
|
237
|
+
allChildren.push(child)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
cursor = (response as { _links?: { next?: string } })._links?.next
|
|
241
|
+
? new URL((response as { _links: { next: string } })._links.next, config.baseUrl).searchParams.get(
|
|
242
|
+
"cursor"
|
|
243
|
+
) ?? undefined
|
|
244
|
+
: undefined
|
|
245
|
+
|
|
246
|
+
iterations++
|
|
247
|
+
} while (cursor)
|
|
248
|
+
|
|
249
|
+
return allChildren
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
const createPage = (req: CreatePageRequest): Effect.Effect<PageResponse, ApiError | RateLimitError> =>
|
|
253
|
+
toEffect(apiClient.v2.client.POST("/pages", {
|
|
254
|
+
body: {
|
|
255
|
+
spaceId: req.spaceId,
|
|
256
|
+
title: req.title,
|
|
257
|
+
...(req.parentId ? { parentId: req.parentId } : {}),
|
|
258
|
+
body: { representation: req.body.representation, value: req.body.value },
|
|
259
|
+
status: "current"
|
|
260
|
+
}
|
|
261
|
+
})).pipe(
|
|
262
|
+
Effect.mapError((e) => mapApiError(e, "/pages")),
|
|
263
|
+
Effect.retry(rateLimitSchedule)
|
|
264
|
+
) as Effect.Effect<PageResponse, ApiError | RateLimitError>
|
|
265
|
+
|
|
266
|
+
const updatePage = (req: UpdatePageRequest): Effect.Effect<PageResponse, ApiError | RateLimitError> =>
|
|
267
|
+
toEffect(apiClient.v2.client.PUT("/pages/{id}", {
|
|
268
|
+
params: { path: { id: Number(req.id) } },
|
|
269
|
+
body: {
|
|
270
|
+
id: req.id,
|
|
271
|
+
title: req.title,
|
|
272
|
+
status: req.status ?? "current",
|
|
273
|
+
body: { representation: req.body.representation, value: req.body.value },
|
|
274
|
+
version: { number: req.version.number, ...(req.version.message ? { message: req.version.message } : {}) }
|
|
275
|
+
}
|
|
276
|
+
})).pipe(
|
|
277
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${req.id}`, req.id)),
|
|
278
|
+
Effect.retry(rateLimitSchedule)
|
|
279
|
+
) as Effect.Effect<PageResponse, ApiError | RateLimitError>
|
|
280
|
+
|
|
281
|
+
const deletePage = (id: PageId): Effect.Effect<void, ApiError | RateLimitError> =>
|
|
282
|
+
toEffect(apiClient.v2.client.DELETE("/pages/{id}", {
|
|
283
|
+
params: { path: { id: Number(id) } }
|
|
284
|
+
})).pipe(
|
|
285
|
+
Effect.map(() => void 0),
|
|
286
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${id}`, id)),
|
|
287
|
+
Effect.retry(rateLimitSchedule)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const getPageVersions = (
|
|
291
|
+
id: PageId,
|
|
292
|
+
options?: { since?: number; includeBody?: boolean }
|
|
293
|
+
): Effect.Effect<ReadonlyArray<PageVersion>, ApiError | RateLimitError> =>
|
|
294
|
+
Effect.gen(function*() {
|
|
295
|
+
const allVersions: Array<PageVersion> = []
|
|
296
|
+
let cursor: string | undefined
|
|
297
|
+
let iterations = 0
|
|
283
298
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
Effect.
|
|
299
|
+
do {
|
|
300
|
+
if (iterations >= MAX_PAGINATION_ITERATIONS) {
|
|
301
|
+
return yield* Effect.fail(
|
|
287
302
|
new ApiError({
|
|
288
303
|
status: 0,
|
|
289
|
-
message: `
|
|
290
|
-
endpoint:
|
|
304
|
+
message: `Pagination limit exceeded: more than ${MAX_PAGINATION_ITERATIONS} pages of versions`,
|
|
305
|
+
endpoint: `/pages/${id}/versions`,
|
|
291
306
|
pageId: id
|
|
292
307
|
})
|
|
293
308
|
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const response = yield* toEffect(apiClient.v2.client.GET("/pages/{id}/versions", {
|
|
312
|
+
params: {
|
|
313
|
+
path: { id: Number(id) },
|
|
314
|
+
query: {
|
|
315
|
+
...(options?.includeBody ? { "body-format": "storage" as const } : {}),
|
|
316
|
+
...(cursor ? { cursor } : {}),
|
|
317
|
+
limit: VERSIONS_PAGE_SIZE
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
})).pipe(
|
|
321
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${id}/versions`, id)),
|
|
322
|
+
Effect.retry(rateLimitSchedule)
|
|
294
323
|
)
|
|
295
324
|
|
|
296
|
-
for (const
|
|
297
|
-
|
|
325
|
+
for (const version of (response as { results?: Array<PageVersion> }).results ?? []) {
|
|
326
|
+
if (options?.since === undefined || (version.number ?? 0) > options.since) {
|
|
327
|
+
allVersions.push(version)
|
|
328
|
+
}
|
|
298
329
|
}
|
|
299
330
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
331
|
+
cursor = (response as { _links?: { next?: string } })._links?.next
|
|
332
|
+
? new URL((response as { _links: { next: string } })._links.next, config.baseUrl).searchParams.get(
|
|
333
|
+
"cursor"
|
|
334
|
+
) ?? undefined
|
|
303
335
|
: undefined
|
|
304
336
|
|
|
305
337
|
iterations++
|
|
306
338
|
} while (cursor)
|
|
307
339
|
|
|
308
|
-
return
|
|
340
|
+
return allVersions
|
|
309
341
|
})
|
|
310
342
|
|
|
311
|
-
const
|
|
343
|
+
const getUser = (accountId: string): Effect.Effect<AtlassianUser, ApiError | RateLimitError> =>
|
|
344
|
+
toEffect(apiClient.v1.client.GET("/wiki/rest/api/user", {
|
|
345
|
+
params: { query: { accountId } }
|
|
346
|
+
})).pipe(
|
|
347
|
+
Effect.mapError((e) => mapApiError(e, `/user?accountId=${accountId}`)),
|
|
348
|
+
Effect.retry(rateLimitSchedule)
|
|
349
|
+
) as Effect.Effect<AtlassianUser, ApiError | RateLimitError>
|
|
350
|
+
|
|
351
|
+
const getSpaceId = (pageId: PageId): Effect.Effect<string, ApiError | RateLimitError> =>
|
|
312
352
|
Effect.gen(function*() {
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
Effect.
|
|
353
|
+
const page = yield* getPage(pageId)
|
|
354
|
+
if (!page.spaceId) {
|
|
355
|
+
return yield* Effect.fail(
|
|
316
356
|
new ApiError({
|
|
317
357
|
status: 0,
|
|
318
|
-
message: `
|
|
319
|
-
endpoint:
|
|
358
|
+
message: `Page ${pageId} does not have spaceId`,
|
|
359
|
+
endpoint: `/pages/${pageId}`,
|
|
360
|
+
pageId
|
|
320
361
|
})
|
|
321
362
|
)
|
|
322
|
-
|
|
363
|
+
}
|
|
364
|
+
return page.spaceId
|
|
323
365
|
})
|
|
324
366
|
|
|
325
|
-
const
|
|
367
|
+
const setEditorVersion = (pageId: PageId, version: "v1" | "v2"): Effect.Effect<void, ApiError | RateLimitError> =>
|
|
326
368
|
Effect.gen(function*() {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
369
|
+
// Try to get existing property by key, treat 404 as "not exists"
|
|
370
|
+
const existing = yield* toEffect(apiClient.v2.client.GET("/pages/{page-id}/properties", {
|
|
371
|
+
params: { path: { "page-id": Number(pageId) }, query: { key: "editor" } }
|
|
372
|
+
})).pipe(
|
|
373
|
+
Effect.map((resp) => {
|
|
374
|
+
const results = (resp as { results?: Array<{ id?: string; version?: { number?: number } }> }).results
|
|
375
|
+
return results?.[0]
|
|
376
|
+
}),
|
|
377
|
+
Effect.catchIf(
|
|
378
|
+
(e: FetchClientError) => e.status === 404,
|
|
379
|
+
() => Effect.succeed(undefined)
|
|
380
|
+
),
|
|
381
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${pageId}/properties?key=editor`, pageId))
|
|
337
382
|
)
|
|
338
|
-
})
|
|
339
383
|
|
|
340
|
-
|
|
341
|
-
|
|
384
|
+
if (existing?.id) {
|
|
385
|
+
// Update existing property
|
|
386
|
+
const nextVersion = (existing.version?.number ?? 0) + 1
|
|
387
|
+
yield* toEffect(apiClient.v2.client.PUT("/pages/{page-id}/properties/{property-id}", {
|
|
388
|
+
params: {
|
|
389
|
+
path: { "page-id": Number(pageId), "property-id": Number(existing.id) }
|
|
390
|
+
},
|
|
391
|
+
body: { key: "editor", value: version, version: { number: nextVersion } }
|
|
392
|
+
})).pipe(
|
|
393
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${pageId}/properties/editor`, pageId))
|
|
394
|
+
)
|
|
395
|
+
} else {
|
|
396
|
+
// Create new property
|
|
397
|
+
yield* toEffect(apiClient.v2.client.POST("/pages/{page-id}/properties", {
|
|
398
|
+
params: { path: { "page-id": Number(pageId) } },
|
|
399
|
+
body: { key: "editor", value: version }
|
|
400
|
+
})).pipe(
|
|
401
|
+
Effect.mapError((e) => mapApiError(e, `/pages/${pageId}/properties/editor`, pageId))
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
}).pipe(Effect.retry(rateLimitSchedule))
|
|
342
405
|
|
|
343
406
|
return ConfluenceClient.of({
|
|
344
407
|
getPage,
|
|
@@ -346,7 +409,11 @@ const make = (
|
|
|
346
409
|
getAllChildren,
|
|
347
410
|
createPage,
|
|
348
411
|
updatePage,
|
|
349
|
-
deletePage
|
|
412
|
+
deletePage,
|
|
413
|
+
getPageVersions,
|
|
414
|
+
getUser,
|
|
415
|
+
getSpaceId,
|
|
416
|
+
setEditorVersion
|
|
350
417
|
})
|
|
351
418
|
})
|
|
352
419
|
|
|
@@ -356,7 +423,6 @@ const make = (
|
|
|
356
423
|
* @example
|
|
357
424
|
* ```typescript
|
|
358
425
|
* import { ConfluenceClient } from "@knpkv/confluence-to-markdown/ConfluenceClient"
|
|
359
|
-
* import { NodeHttpClient } from "@effect/platform-node"
|
|
360
426
|
* import { Effect } from "effect"
|
|
361
427
|
*
|
|
362
428
|
* const program = Effect.gen(function* () {
|
|
@@ -374,8 +440,7 @@ const make = (
|
|
|
374
440
|
* email: "you@example.com",
|
|
375
441
|
* token: process.env.CONFLUENCE_API_KEY
|
|
376
442
|
* }
|
|
377
|
-
* }))
|
|
378
|
-
* Effect.provide(NodeHttpClient.layer)
|
|
443
|
+
* }))
|
|
379
444
|
* )
|
|
380
445
|
* )
|
|
381
446
|
* ```
|
|
@@ -384,4 +449,4 @@ const make = (
|
|
|
384
449
|
*/
|
|
385
450
|
export const layer = (
|
|
386
451
|
config: ConfluenceClientConfig
|
|
387
|
-
): Layer.Layer<ConfluenceClient
|
|
452
|
+
): Layer.Layer<ConfluenceClient> => Layer.effect(ConfluenceClient, make(config))
|
package/src/ConfluenceConfig.ts
CHANGED
|
@@ -46,13 +46,22 @@ export class ConfluenceConfig extends Context.Tag(
|
|
|
46
46
|
readonly docsPath: string
|
|
47
47
|
/** Glob patterns to exclude */
|
|
48
48
|
readonly excludePatterns: ReadonlyArray<string>
|
|
49
|
+
/** Save original Confluence HTML alongside markdown */
|
|
50
|
+
readonly saveSource: boolean
|
|
51
|
+
/** Glob patterns for files to track in git */
|
|
52
|
+
readonly trackedPaths: ReadonlyArray<string>
|
|
49
53
|
}
|
|
50
54
|
>() {}
|
|
51
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Default config directory.
|
|
58
|
+
*/
|
|
59
|
+
const CONFIG_DIR = ".confluence"
|
|
60
|
+
|
|
52
61
|
/**
|
|
53
62
|
* Default config file name.
|
|
54
63
|
*/
|
|
55
|
-
const CONFIG_FILE_NAME = ".
|
|
64
|
+
const CONFIG_FILE_NAME = "config.json"
|
|
56
65
|
|
|
57
66
|
/**
|
|
58
67
|
* Load configuration from a file.
|
|
@@ -108,6 +117,35 @@ const loadConfig = (
|
|
|
108
117
|
*
|
|
109
118
|
* @category Layers
|
|
110
119
|
*/
|
|
120
|
+
/**
|
|
121
|
+
* Validate docsPath configuration.
|
|
122
|
+
* docsPath must be either:
|
|
123
|
+
* - ".confluence/docs" (default, files in git repo)
|
|
124
|
+
* - A path outside ".confluence/" (external docs directory)
|
|
125
|
+
* NOT: ".confluence/other" which would cause confusion
|
|
126
|
+
*/
|
|
127
|
+
const validateDocsPath = (
|
|
128
|
+
docsPath: string,
|
|
129
|
+
configPath: string
|
|
130
|
+
): Effect.Effect<void, ConfigParseError> => {
|
|
131
|
+
const isDefault = docsPath === ".confluence/docs"
|
|
132
|
+
const isInsideConfluence = docsPath.startsWith(".confluence/") || docsPath.startsWith(".confluence\\")
|
|
133
|
+
const isOutside = !isInsideConfluence
|
|
134
|
+
|
|
135
|
+
if (isDefault || isOutside) {
|
|
136
|
+
return Effect.void
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// docsPath is inside .confluence/ but not the default - this is invalid
|
|
140
|
+
return Effect.fail(
|
|
141
|
+
new ConfigParseError({
|
|
142
|
+
path: configPath,
|
|
143
|
+
cause:
|
|
144
|
+
`Invalid docsPath "${docsPath}". Must be either ".confluence/docs" (default) or a path outside ".confluence/" (e.g., "docs" for external docs).`
|
|
145
|
+
})
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
111
149
|
export const layer = (
|
|
112
150
|
configPath?: string
|
|
113
151
|
): Layer.Layer<ConfluenceConfig, ConfigNotFoundError | ConfigParseError, FileSystem.FileSystem | Path.Path> =>
|
|
@@ -115,15 +153,20 @@ export const layer = (
|
|
|
115
153
|
ConfluenceConfig,
|
|
116
154
|
Effect.gen(function*() {
|
|
117
155
|
const path = yield* Path.Path
|
|
118
|
-
const resolvedPath = configPath ?? path.join(process.cwd(), CONFIG_FILE_NAME)
|
|
156
|
+
const resolvedPath = configPath ?? path.join(process.cwd(), CONFIG_DIR, CONFIG_FILE_NAME)
|
|
119
157
|
const config = yield* loadConfig(resolvedPath)
|
|
120
158
|
|
|
159
|
+
// Validate docsPath
|
|
160
|
+
yield* validateDocsPath(config.docsPath, resolvedPath)
|
|
161
|
+
|
|
121
162
|
return ConfluenceConfig.of({
|
|
122
163
|
rootPageId: config.rootPageId,
|
|
123
164
|
baseUrl: config.baseUrl,
|
|
124
165
|
...(config.spaceKey !== undefined ? { spaceKey: config.spaceKey } : {}),
|
|
125
166
|
docsPath: config.docsPath,
|
|
126
|
-
excludePatterns: config.excludePatterns
|
|
167
|
+
excludePatterns: config.excludePatterns,
|
|
168
|
+
saveSource: config.saveSource,
|
|
169
|
+
trackedPaths: config.trackedPaths
|
|
127
170
|
})
|
|
128
171
|
})
|
|
129
172
|
)
|
|
@@ -143,7 +186,9 @@ export const layerFromValues = (
|
|
|
143
186
|
baseUrl: config.baseUrl,
|
|
144
187
|
...(config.spaceKey !== undefined ? { spaceKey: config.spaceKey } : {}),
|
|
145
188
|
docsPath: config.docsPath,
|
|
146
|
-
excludePatterns: config.excludePatterns
|
|
189
|
+
excludePatterns: config.excludePatterns,
|
|
190
|
+
saveSource: config.saveSource,
|
|
191
|
+
trackedPaths: config.trackedPaths
|
|
147
192
|
})
|
|
148
193
|
)
|
|
149
194
|
|
|
@@ -161,13 +206,16 @@ export const createConfigFile = (
|
|
|
161
206
|
const fs = yield* FileSystem.FileSystem
|
|
162
207
|
const pathService = yield* Path.Path
|
|
163
208
|
|
|
164
|
-
const
|
|
209
|
+
const configDir = pathService.join(process.cwd(), CONFIG_DIR)
|
|
210
|
+
const resolvedPath = configPath ?? pathService.join(configDir, CONFIG_FILE_NAME)
|
|
165
211
|
|
|
166
212
|
const config: ConfluenceConfigFile = {
|
|
167
213
|
rootPageId: rootPageId as PageId,
|
|
168
214
|
baseUrl,
|
|
169
|
-
docsPath: ".docs
|
|
170
|
-
excludePatterns: []
|
|
215
|
+
docsPath: ".confluence/docs",
|
|
216
|
+
excludePatterns: [],
|
|
217
|
+
saveSource: false,
|
|
218
|
+
trackedPaths: ["**/*.md"]
|
|
171
219
|
}
|
|
172
220
|
|
|
173
221
|
// Validate the config
|
|
@@ -175,6 +223,14 @@ export const createConfigFile = (
|
|
|
175
223
|
Effect.mapError((cause) => new ConfigParseError({ path: resolvedPath, cause }))
|
|
176
224
|
)
|
|
177
225
|
|
|
226
|
+
// Create .confluence directory if it doesn't exist
|
|
227
|
+
const dirExists = yield* fs.exists(configDir).pipe(Effect.catchAll(() => Effect.succeed(false)))
|
|
228
|
+
if (!dirExists) {
|
|
229
|
+
yield* fs.makeDirectory(configDir, { recursive: true }).pipe(
|
|
230
|
+
Effect.mapError((cause) => new ConfigParseError({ path: configDir, cause }))
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
178
234
|
const content = JSON.stringify(config, null, 2)
|
|
179
235
|
yield* fs.writeFileString(resolvedPath, content).pipe(
|
|
180
236
|
Effect.mapError((cause) => new ConfigParseError({ path: resolvedPath, cause }))
|