@knpkv/confluence-to-markdown 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +22 -13
- package/dist/AdfPlaceholders.d.ts +42 -0
- package/dist/AdfPlaceholders.d.ts.map +1 -0
- package/dist/AdfPlaceholders.js +547 -0
- package/dist/AdfPlaceholders.js.map +1 -0
- package/dist/AdfSchemaValidator.d.ts +37 -0
- package/dist/AdfSchemaValidator.d.ts.map +1 -0
- package/dist/AdfSchemaValidator.js +37 -0
- package/dist/AdfSchemaValidator.js.map +1 -0
- package/dist/AdfWalker.d.ts +39 -0
- package/dist/AdfWalker.d.ts.map +1 -0
- package/dist/AdfWalker.js +527 -0
- package/dist/AdfWalker.js.map +1 -0
- package/dist/AtlaskitTransformers.d.ts +35 -0
- package/dist/AtlaskitTransformers.d.ts.map +1 -0
- package/dist/AtlaskitTransformers.js +48 -0
- package/dist/AtlaskitTransformers.js.map +1 -0
- package/dist/Brand.d.ts +6 -6
- package/dist/Brand.d.ts.map +1 -1
- package/dist/Brand.js +8 -6
- package/dist/Brand.js.map +1 -1
- package/dist/ConfluenceAuth.d.ts +4 -4
- package/dist/ConfluenceAuth.d.ts.map +1 -1
- package/dist/ConfluenceAuth.js +37 -39
- package/dist/ConfluenceAuth.js.map +1 -1
- package/dist/ConfluenceClient.d.ts +7 -17
- package/dist/ConfluenceClient.d.ts.map +1 -1
- package/dist/ConfluenceClient.js +81 -38
- package/dist/ConfluenceClient.js.map +1 -1
- package/dist/ConfluenceConfig.d.ts +3 -3
- package/dist/ConfluenceConfig.d.ts.map +1 -1
- package/dist/ConfluenceConfig.js +13 -11
- package/dist/ConfluenceConfig.js.map +1 -1
- package/dist/ConfluenceError.d.ts +68 -16
- package/dist/ConfluenceError.d.ts.map +1 -1
- package/dist/ConfluenceError.js +30 -1
- package/dist/ConfluenceError.js.map +1 -1
- package/dist/GitError.d.ts +5 -5
- package/dist/GitService.d.ts +11 -3
- package/dist/GitService.d.ts.map +1 -1
- package/dist/GitService.js +22 -27
- package/dist/GitService.js.map +1 -1
- package/dist/LocalFileSystem.d.ts +3 -3
- package/dist/LocalFileSystem.d.ts.map +1 -1
- package/dist/LocalFileSystem.js +6 -6
- package/dist/LocalFileSystem.js.map +1 -1
- package/dist/MarkdownConverter.d.ts +16 -65
- package/dist/MarkdownConverter.d.ts.map +1 -1
- package/dist/MarkdownConverter.js +64 -85
- package/dist/MarkdownConverter.js.map +1 -1
- package/dist/Schemas.d.ts +128 -141
- package/dist/Schemas.d.ts.map +1 -1
- package/dist/Schemas.js +21 -23
- package/dist/Schemas.js.map +1 -1
- package/dist/SyncEngine.d.ts +8 -5
- package/dist/SyncEngine.d.ts.map +1 -1
- package/dist/SyncEngine.js +189 -113
- package/dist/SyncEngine.js.map +1 -1
- package/dist/bin.js +23 -35
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth.d.ts +2 -14
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +11 -16
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/clone.d.ts +4 -6
- package/dist/commands/clone.d.ts.map +1 -1
- package/dist/commands/clone.js +34 -32
- package/dist/commands/clone.js.map +1 -1
- package/dist/commands/delete.d.ts +2 -10
- package/dist/commands/delete.d.ts.map +1 -1
- package/dist/commands/delete.js +5 -4
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/errorHandler.d.ts +2 -1
- package/dist/commands/errorHandler.d.ts.map +1 -1
- package/dist/commands/errorHandler.js +22 -15
- package/dist/commands/errorHandler.js.map +1 -1
- package/dist/commands/fetch.d.ts +27 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +48 -0
- package/dist/commands/fetch.js.map +1 -0
- package/dist/commands/git.d.ts +7 -10
- package/dist/commands/git.d.ts.map +1 -1
- package/dist/commands/git.js +6 -6
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/layers.d.ts +10 -9
- package/dist/commands/layers.d.ts.map +1 -1
- package/dist/commands/layers.js +41 -30
- package/dist/commands/layers.js.map +1 -1
- package/dist/commands/new.d.ts +2 -6
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +5 -4
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/pageInput.d.ts +19 -0
- package/dist/commands/pageInput.d.ts.map +1 -0
- package/dist/commands/pageInput.js +68 -0
- package/dist/commands/pageInput.js.map +1 -0
- package/dist/commands/root.d.ts +8 -0
- package/dist/commands/root.d.ts.map +1 -0
- package/dist/commands/root.js +29 -0
- package/dist/commands/root.js.map +1 -0
- package/dist/commands/sync.d.ts +6 -9
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +5 -6
- package/dist/commands/sync.js.map +1 -1
- package/dist/index.d.ts +3 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -13
- package/dist/index.js.map +1 -1
- package/dist/internal/NodeLayers.d.ts.map +1 -1
- package/dist/internal/NodeLayers.js +1 -2
- package/dist/internal/NodeLayers.js.map +1 -1
- package/dist/internal/adfMetadata.d.ts +30 -0
- package/dist/internal/adfMetadata.d.ts.map +1 -0
- package/dist/internal/adfMetadata.js +126 -0
- package/dist/internal/adfMetadata.js.map +1 -0
- package/dist/internal/cleanMarkdown.d.ts +5 -0
- package/dist/internal/cleanMarkdown.d.ts.map +1 -0
- package/dist/internal/cleanMarkdown.js +13 -0
- package/dist/internal/cleanMarkdown.js.map +1 -0
- package/dist/internal/frontmatter.d.ts.map +1 -1
- package/dist/internal/frontmatter.js +41 -8
- package/dist/internal/frontmatter.js.map +1 -1
- package/dist/internal/gitCommands.d.ts +9 -3
- package/dist/internal/gitCommands.d.ts.map +1 -1
- package/dist/internal/gitCommands.js +18 -9
- package/dist/internal/gitCommands.js.map +1 -1
- package/dist/internal/hashUtils.d.ts +1 -1
- package/dist/internal/hashUtils.d.ts.map +1 -1
- package/dist/internal/hashUtils.js +1 -1
- package/dist/internal/hashUtils.js.map +1 -1
- package/dist/internal/oauthServer.d.ts +10 -5
- package/dist/internal/oauthServer.d.ts.map +1 -1
- package/dist/internal/oauthServer.js +19 -40
- package/dist/internal/oauthServer.js.map +1 -1
- package/dist/internal/pathUtils.d.ts +1 -1
- package/dist/internal/pathUtils.d.ts.map +1 -1
- package/dist/internal/pathUtils.js +1 -1
- package/dist/internal/pathUtils.js.map +1 -1
- package/dist/internal/process.d.ts +15 -0
- package/dist/internal/process.d.ts.map +1 -0
- package/dist/internal/process.js +10 -0
- package/dist/internal/process.js.map +1 -0
- package/dist/internal/stdio.d.ts +6 -0
- package/dist/internal/stdio.d.ts.map +1 -0
- package/dist/internal/stdio.js +15 -0
- package/dist/internal/stdio.js.map +1 -0
- package/dist/internal/tokenStorage.d.ts +3 -13
- package/dist/internal/tokenStorage.d.ts.map +1 -1
- package/dist/internal/tokenStorage.js +26 -24
- package/dist/internal/tokenStorage.js.map +1 -1
- package/dist/internal/userCache.d.ts +1 -1
- package/dist/internal/userCache.d.ts.map +1 -1
- package/dist/internal/userCache.js +1 -1
- package/dist/internal/userCache.js.map +1 -1
- package/package.json +30 -30
- package/skills/confluence/SKILL.md +143 -0
- package/skills/confluence/agents/openai.yaml +4 -0
- package/src/AdfPlaceholders.ts +310 -13
- package/src/AdfSchemaValidator.ts +2 -4
- package/src/AdfWalker.ts +122 -42
- package/src/AtlaskitTransformers.ts +2 -4
- package/src/Brand.ts +11 -16
- package/src/ConfluenceAuth.ts +22 -30
- package/src/ConfluenceClient.ts +24 -20
- package/src/ConfluenceConfig.ts +14 -14
- package/src/GitService.ts +39 -49
- package/src/LocalFileSystem.ts +7 -9
- package/src/MarkdownConverter.ts +2 -4
- package/src/Schemas.ts +13 -12
- package/src/SyncEngine.ts +151 -53
- package/src/bin.ts +30 -56
- package/src/commands/auth.ts +21 -18
- package/src/commands/clone.ts +38 -37
- package/src/commands/delete.ts +5 -4
- package/src/commands/errorHandler.ts +25 -18
- package/src/commands/fetch.ts +90 -0
- package/src/commands/git.ts +6 -6
- package/src/commands/index.ts +1 -0
- package/src/commands/layers.ts +53 -33
- package/src/commands/new.ts +5 -4
- package/src/commands/pageInput.ts +103 -0
- package/src/commands/root.ts +59 -0
- package/src/commands/sync.ts +7 -6
- package/src/internal/NodeLayers.ts +1 -2
- package/src/internal/adfMetadata.ts +145 -0
- package/src/internal/cleanMarkdown.ts +15 -0
- package/src/internal/frontmatter.ts +45 -8
- package/src/internal/gitCommands.ts +23 -17
- package/src/internal/hashUtils.ts +2 -2
- package/src/internal/oauthServer.ts +84 -105
- package/src/internal/pathUtils.ts +1 -1
- package/src/internal/process.ts +15 -0
- package/src/internal/stdio.ts +22 -0
- package/src/internal/tokenStorage.ts +39 -29
- package/src/internal/userCache.ts +2 -2
- package/test/AdfPlaceholders.test.ts +213 -0
- package/test/AdfSchemaValidator.test.ts +6 -6
- package/test/AdfWalker.test.ts +167 -21
- package/test/AtlaskitTransformers.test.ts +4 -4
- package/test/Brand.test.ts +11 -11
- package/test/GitService.test.ts +6 -2
- package/test/MarkdownConverter.test.ts +12 -11
- package/test/RoundTrip.test.ts +258 -3
- package/test/Schemas.test.ts +40 -40
- package/test/adfMetadata.test.ts +110 -0
- package/test/cleanMarkdown.test.ts +36 -0
- package/test/commandHarness.test.ts +79 -0
- package/test/commandHarness.ts +147 -0
- package/test/fetch.test.ts +61 -0
- package/test/frontmatter.test.ts +41 -0
- package/test/integration.test.ts +569 -156
- package/test/layers.test.ts +12 -0
- package/test/oauthServer.test.ts +4 -5
- package/test/pageInput.test.ts +83 -0
- package/test/tokenStorage.test.ts +17 -17
- package/dist/SchemaConverterError.d.ts +0 -108
- package/dist/SchemaConverterError.d.ts.map +0 -1
- package/dist/SchemaConverterError.js +0 -84
- package/dist/SchemaConverterError.js.map +0 -1
- package/dist/ast/BlockNode.d.ts +0 -468
- package/dist/ast/BlockNode.d.ts.map +0 -1
- package/dist/ast/BlockNode.js +0 -319
- package/dist/ast/BlockNode.js.map +0 -1
- package/dist/ast/Document.d.ts +0 -244
- package/dist/ast/Document.d.ts.map +0 -1
- package/dist/ast/Document.js +0 -69
- package/dist/ast/Document.js.map +0 -1
- package/dist/ast/InlineNode.d.ts +0 -477
- package/dist/ast/InlineNode.d.ts.map +0 -1
- package/dist/ast/InlineNode.js +0 -263
- package/dist/ast/InlineNode.js.map +0 -1
- package/dist/ast/MacroNode.d.ts +0 -267
- package/dist/ast/MacroNode.d.ts.map +0 -1
- package/dist/ast/MacroNode.js +0 -164
- package/dist/ast/MacroNode.js.map +0 -1
- package/dist/ast/index.d.ts +0 -10
- package/dist/ast/index.d.ts.map +0 -1
- package/dist/ast/index.js +0 -14
- package/dist/ast/index.js.map +0 -1
- package/dist/parsers/ConfluenceParser.d.ts +0 -26
- package/dist/parsers/ConfluenceParser.d.ts.map +0 -1
- package/dist/parsers/ConfluenceParser.js +0 -792
- package/dist/parsers/ConfluenceParser.js.map +0 -1
- package/dist/parsers/MarkdownParser.d.ts +0 -26
- package/dist/parsers/MarkdownParser.d.ts.map +0 -1
- package/dist/parsers/MarkdownParser.js +0 -873
- package/dist/parsers/MarkdownParser.js.map +0 -1
- package/dist/parsers/index.d.ts +0 -8
- package/dist/parsers/index.d.ts.map +0 -1
- package/dist/parsers/index.js +0 -8
- package/dist/parsers/index.js.map +0 -1
- package/dist/parsers/preprocessing/ConfluencePreprocessing.d.ts +0 -23
- package/dist/parsers/preprocessing/ConfluencePreprocessing.d.ts.map +0 -1
- package/dist/parsers/preprocessing/ConfluencePreprocessing.js +0 -323
- package/dist/parsers/preprocessing/ConfluencePreprocessing.js.map +0 -1
- package/dist/parsers/preprocessing/index.d.ts +0 -7
- package/dist/parsers/preprocessing/index.d.ts.map +0 -1
- package/dist/parsers/preprocessing/index.js +0 -7
- package/dist/parsers/preprocessing/index.js.map +0 -1
- package/dist/schemas/ConfluenceSchema.d.ts +0 -21
- package/dist/schemas/ConfluenceSchema.d.ts.map +0 -1
- package/dist/schemas/ConfluenceSchema.js +0 -38
- package/dist/schemas/ConfluenceSchema.js.map +0 -1
- package/dist/schemas/ConversionSchema.d.ts +0 -35
- package/dist/schemas/ConversionSchema.d.ts.map +0 -1
- package/dist/schemas/ConversionSchema.js +0 -208
- package/dist/schemas/ConversionSchema.js.map +0 -1
- package/dist/schemas/MarkdownSchema.d.ts +0 -21
- package/dist/schemas/MarkdownSchema.d.ts.map +0 -1
- package/dist/schemas/MarkdownSchema.js +0 -38
- package/dist/schemas/MarkdownSchema.js.map +0 -1
- package/dist/schemas/hast/HastFromHtml.d.ts +0 -27
- package/dist/schemas/hast/HastFromHtml.d.ts.map +0 -1
- package/dist/schemas/hast/HastFromHtml.js +0 -107
- package/dist/schemas/hast/HastFromHtml.js.map +0 -1
- package/dist/schemas/hast/HastSchema.d.ts +0 -195
- package/dist/schemas/hast/HastSchema.d.ts.map +0 -1
- package/dist/schemas/hast/HastSchema.js +0 -183
- package/dist/schemas/hast/HastSchema.js.map +0 -1
- package/dist/schemas/hast/index.d.ts +0 -9
- package/dist/schemas/hast/index.d.ts.map +0 -1
- package/dist/schemas/hast/index.js +0 -3
- package/dist/schemas/hast/index.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -14
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -16
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts +0 -30
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +0 -1
- package/dist/schemas/mdast/MdastFromMarkdown.js +0 -79
- package/dist/schemas/mdast/MdastFromMarkdown.js.map +0 -1
- package/dist/schemas/mdast/MdastSchema.d.ts +0 -385
- package/dist/schemas/mdast/MdastSchema.d.ts.map +0 -1
- package/dist/schemas/mdast/MdastSchema.js +0 -266
- package/dist/schemas/mdast/MdastSchema.js.map +0 -1
- package/dist/schemas/mdast/index.d.ts +0 -10
- package/dist/schemas/mdast/index.d.ts.map +0 -1
- package/dist/schemas/mdast/index.js +0 -4
- package/dist/schemas/mdast/index.js.map +0 -1
- package/dist/schemas/mdast/mdastToString.d.ts +0 -13
- package/dist/schemas/mdast/mdastToString.d.ts.map +0 -1
- package/dist/schemas/mdast/mdastToString.js +0 -85
- package/dist/schemas/mdast/mdastToString.js.map +0 -1
- package/dist/schemas/nodes/block/BlockSchema.d.ts +0 -43
- package/dist/schemas/nodes/block/BlockSchema.d.ts.map +0 -1
- package/dist/schemas/nodes/block/BlockSchema.js +0 -634
- package/dist/schemas/nodes/block/BlockSchema.js.map +0 -1
- package/dist/schemas/nodes/block/index.d.ts +0 -7
- package/dist/schemas/nodes/block/index.d.ts.map +0 -1
- package/dist/schemas/nodes/block/index.js +0 -7
- package/dist/schemas/nodes/block/index.js.map +0 -1
- package/dist/schemas/nodes/index.d.ts +0 -9
- package/dist/schemas/nodes/index.d.ts.map +0 -1
- package/dist/schemas/nodes/index.js +0 -12
- package/dist/schemas/nodes/index.js.map +0 -1
- package/dist/schemas/nodes/inline/InlineSchema.d.ts +0 -48
- package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +0 -1
- package/dist/schemas/nodes/inline/InlineSchema.js +0 -436
- package/dist/schemas/nodes/inline/InlineSchema.js.map +0 -1
- package/dist/schemas/nodes/inline/index.d.ts +0 -7
- package/dist/schemas/nodes/inline/index.d.ts.map +0 -1
- package/dist/schemas/nodes/inline/index.js +0 -7
- package/dist/schemas/nodes/inline/index.js.map +0 -1
- package/dist/schemas/nodes/macro/MacroSchema.d.ts +0 -27
- package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +0 -1
- package/dist/schemas/nodes/macro/MacroSchema.js +0 -162
- package/dist/schemas/nodes/macro/MacroSchema.js.map +0 -1
- package/dist/schemas/nodes/macro/index.d.ts +0 -7
- package/dist/schemas/nodes/macro/index.d.ts.map +0 -1
- package/dist/schemas/nodes/macro/index.js +0 -7
- package/dist/schemas/nodes/macro/index.js.map +0 -1
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +0 -53
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +0 -1
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js +0 -349
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +0 -1
- package/dist/schemas/preprocessing/index.d.ts +0 -8
- package/dist/schemas/preprocessing/index.d.ts.map +0 -1
- package/dist/schemas/preprocessing/index.js +0 -2
- package/dist/schemas/preprocessing/index.js.map +0 -1
- package/dist/serializers/ConfluenceSerializer.d.ts +0 -30
- package/dist/serializers/ConfluenceSerializer.d.ts.map +0 -1
- package/dist/serializers/ConfluenceSerializer.js +0 -551
- package/dist/serializers/ConfluenceSerializer.js.map +0 -1
- package/dist/serializers/MarkdownSerializer.d.ts +0 -34
- package/dist/serializers/MarkdownSerializer.d.ts.map +0 -1
- package/dist/serializers/MarkdownSerializer.js +0 -355
- package/dist/serializers/MarkdownSerializer.js.map +0 -1
- package/dist/serializers/index.d.ts +0 -8
- package/dist/serializers/index.d.ts.map +0 -1
- package/dist/serializers/index.js +0 -8
- package/dist/serializers/index.js.map +0 -1
package/src/commands/layers.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Layer definitions for CLI commands.
|
|
3
3
|
*/
|
|
4
|
-
import * as NodeContext from "@effect/platform-node/NodeContext"
|
|
5
4
|
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient"
|
|
5
|
+
import * as NodeServices from "@effect/platform-node/NodeServices"
|
|
6
6
|
import * as NodeTerminal from "@effect/platform-node/NodeTerminal"
|
|
7
7
|
import * as Effect from "effect/Effect"
|
|
8
8
|
import * as Layer from "effect/Layer"
|
|
@@ -42,16 +42,16 @@ const DummyConfigLayer = ConfluenceConfigLayerFromValues({
|
|
|
42
42
|
const DummyConfluenceClientLayer = Layer.succeed(
|
|
43
43
|
ConfluenceClient,
|
|
44
44
|
ConfluenceClient.of({
|
|
45
|
-
getPage: () => Effect.
|
|
46
|
-
getChildren: () => Effect.
|
|
47
|
-
getAllChildren: () => Effect.
|
|
48
|
-
createPage: () => Effect.
|
|
49
|
-
updatePage: () => Effect.
|
|
50
|
-
deletePage: () => Effect.
|
|
51
|
-
getPageVersions: () => Effect.
|
|
52
|
-
getUser: () => Effect.
|
|
53
|
-
getSpaceId: () => Effect.
|
|
54
|
-
setEditorVersion: () => Effect.
|
|
45
|
+
getPage: () => Effect.die("Not configured"),
|
|
46
|
+
getChildren: () => Effect.die("Not configured"),
|
|
47
|
+
getAllChildren: () => Effect.die("Not configured"),
|
|
48
|
+
createPage: () => Effect.die("Not configured"),
|
|
49
|
+
updatePage: () => Effect.die("Not configured"),
|
|
50
|
+
deletePage: () => Effect.die("Not configured"),
|
|
51
|
+
getPageVersions: () => Effect.die("Not configured"),
|
|
52
|
+
getUser: () => Effect.die("Not configured"),
|
|
53
|
+
getSpaceId: () => Effect.die("Not configured"),
|
|
54
|
+
setEditorVersion: () => Effect.die("Not configured")
|
|
55
55
|
})
|
|
56
56
|
)
|
|
57
57
|
|
|
@@ -59,15 +59,15 @@ const DummyConfluenceClientLayer = Layer.succeed(
|
|
|
59
59
|
const DummySyncEngineLayer = Layer.succeed(
|
|
60
60
|
SyncEngine,
|
|
61
61
|
SyncEngine.of({
|
|
62
|
-
pull: () => Effect.
|
|
62
|
+
pull: () => Effect.die("Not configured - run 'confluence clone' first"),
|
|
63
63
|
push: (_options: { dryRun: boolean; message?: string }) =>
|
|
64
|
-
Effect.
|
|
65
|
-
status: () => Effect.
|
|
64
|
+
Effect.die("Not configured - run 'confluence clone' first"),
|
|
65
|
+
status: () => Effect.die("Not configured - run 'confluence clone' first")
|
|
66
66
|
})
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
// Dummy git layer for auth/minimal
|
|
70
|
-
const notConfigured = () => Effect.
|
|
70
|
+
const notConfigured = () => Effect.die("Not configured - run 'confluence clone' first")
|
|
71
71
|
const DummyGitServiceLayer = Layer.succeed(
|
|
72
72
|
GitService,
|
|
73
73
|
GitService.of({
|
|
@@ -107,22 +107,22 @@ const DummyGitServiceLayer = Layer.succeed(
|
|
|
107
107
|
const DummyConfluenceAuthLayer = Layer.succeed(
|
|
108
108
|
ConfluenceAuth,
|
|
109
109
|
ConfluenceAuth.of({
|
|
110
|
-
configure: () => Effect.
|
|
110
|
+
configure: () => Effect.die("Not configured"),
|
|
111
111
|
isConfigured: () => Effect.succeed(false),
|
|
112
|
-
login: () => Effect.
|
|
113
|
-
logout: () => Effect.
|
|
114
|
-
getAccessToken: () => Effect.
|
|
115
|
-
getCloudId: () => Effect.
|
|
112
|
+
login: () => Effect.die("Not configured"),
|
|
113
|
+
logout: () => Effect.die("Not configured"),
|
|
114
|
+
getAccessToken: () => Effect.die("Not configured"),
|
|
115
|
+
getCloudId: () => Effect.die("Not configured"),
|
|
116
116
|
getCurrentUser: () => Effect.succeed(null),
|
|
117
117
|
isLoggedIn: () => Effect.succeed(false)
|
|
118
118
|
})
|
|
119
119
|
)
|
|
120
120
|
|
|
121
121
|
// Auth layer with HTTP client
|
|
122
|
-
const AuthLive = ConfluenceAuthLayer.pipe(Layer.provide(NodeHttpClient.
|
|
122
|
+
const AuthLive = ConfluenceAuthLayer.pipe(Layer.provide(NodeHttpClient.layerFetch))
|
|
123
123
|
|
|
124
124
|
// Build client layer dynamically based on auth
|
|
125
|
-
const ConfluenceClientLive = Layer.
|
|
125
|
+
const ConfluenceClientLive = Layer.unwrap(
|
|
126
126
|
Effect.gen(function*() {
|
|
127
127
|
const auth = yield* getAuth()
|
|
128
128
|
const config = yield* ConfluenceConfig
|
|
@@ -147,8 +147,8 @@ export const AppLayer = SyncEngineLayer.pipe(
|
|
|
147
147
|
Layer.provideMerge(LocalFileSystemLayer),
|
|
148
148
|
Layer.provideMerge(ConfluenceConfigLayer()),
|
|
149
149
|
Layer.provideMerge(AuthLive),
|
|
150
|
-
Layer.provideMerge(NodeHttpClient.
|
|
151
|
-
Layer.provideMerge(
|
|
150
|
+
Layer.provideMerge(NodeHttpClient.layerFetch),
|
|
151
|
+
Layer.provideMerge(NodeServices.layer)
|
|
152
152
|
)
|
|
153
153
|
|
|
154
154
|
/**
|
|
@@ -162,8 +162,8 @@ export const AuthOnlyLayer = DummySyncEngineLayer.pipe(
|
|
|
162
162
|
Layer.provideMerge(ConverterPipeline),
|
|
163
163
|
Layer.provideMerge(LocalFileSystemLayer),
|
|
164
164
|
Layer.provideMerge(DummyConfigLayer),
|
|
165
|
-
Layer.provideMerge(NodeHttpClient.
|
|
166
|
-
Layer.provideMerge(
|
|
165
|
+
Layer.provideMerge(NodeHttpClient.layerFetch),
|
|
166
|
+
Layer.provideMerge(NodeServices.layer)
|
|
167
167
|
)
|
|
168
168
|
|
|
169
169
|
/**
|
|
@@ -178,7 +178,7 @@ export const MinimalLayer = DummySyncEngineLayer.pipe(
|
|
|
178
178
|
Layer.provideMerge(LocalFileSystemLayer),
|
|
179
179
|
Layer.provideMerge(DummyConfigLayer),
|
|
180
180
|
Layer.provideMerge(NodeTerminal.layer),
|
|
181
|
-
Layer.provideMerge(
|
|
181
|
+
Layer.provideMerge(NodeServices.layer)
|
|
182
182
|
)
|
|
183
183
|
|
|
184
184
|
/**
|
|
@@ -192,16 +192,32 @@ export const CloneLayer = DummySyncEngineLayer.pipe(
|
|
|
192
192
|
Layer.provideMerge(ConverterPipeline),
|
|
193
193
|
Layer.provideMerge(LocalFileSystemLayer),
|
|
194
194
|
Layer.provideMerge(DummyConfigLayer),
|
|
195
|
-
Layer.provideMerge(NodeHttpClient.
|
|
195
|
+
Layer.provideMerge(NodeHttpClient.layerFetch),
|
|
196
196
|
Layer.provideMerge(NodeTerminal.layer),
|
|
197
|
-
Layer.provideMerge(
|
|
197
|
+
Layer.provideMerge(NodeServices.layer)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Fetch layer - needs auth + converter but no config, sync engine, or git workspace.
|
|
202
|
+
*/
|
|
203
|
+
export const FetchLayer = DummySyncEngineLayer.pipe(
|
|
204
|
+
Layer.provideMerge(UserCacheLayer),
|
|
205
|
+
Layer.provideMerge(DummyGitServiceLayer),
|
|
206
|
+
Layer.provideMerge(DummyConfluenceClientLayer),
|
|
207
|
+
Layer.provideMerge(AuthLive),
|
|
208
|
+
Layer.provideMerge(ConverterPipeline),
|
|
209
|
+
Layer.provideMerge(LocalFileSystemLayer),
|
|
210
|
+
Layer.provideMerge(DummyConfigLayer),
|
|
211
|
+
Layer.provideMerge(NodeHttpClient.layerFetch),
|
|
212
|
+
Layer.provideMerge(NodeTerminal.layer),
|
|
213
|
+
Layer.provideMerge(NodeServices.layer)
|
|
198
214
|
)
|
|
199
215
|
|
|
200
216
|
/**
|
|
201
217
|
* Determine which layer to use based on command.
|
|
202
218
|
*/
|
|
203
|
-
export const getLayerType = (): "full" | "auth" | "clone" | "minimal" => {
|
|
204
|
-
const cmd =
|
|
219
|
+
export const getLayerType = (argv: ReadonlyArray<string>): "full" | "auth" | "clone" | "fetch" | "minimal" => {
|
|
220
|
+
const cmd = argv[0]
|
|
205
221
|
// auth commands need auth layer only
|
|
206
222
|
if (cmd === "auth") {
|
|
207
223
|
return "auth"
|
|
@@ -210,8 +226,12 @@ export const getLayerType = (): "full" | "auth" | "clone" | "minimal" => {
|
|
|
210
226
|
if (cmd === "clone") {
|
|
211
227
|
return "clone"
|
|
212
228
|
}
|
|
213
|
-
//
|
|
214
|
-
if (
|
|
229
|
+
// fetch needs auth + converter but no existing config
|
|
230
|
+
if (cmd === "fetch") {
|
|
231
|
+
return "fetch"
|
|
232
|
+
}
|
|
233
|
+
// skills/help/version don't need config
|
|
234
|
+
if (!cmd || cmd === "skills" || cmd === "--help" || cmd === "-h" || cmd === "--version") {
|
|
215
235
|
return "minimal"
|
|
216
236
|
}
|
|
217
237
|
return "full"
|
package/src/commands/new.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* New page command for Confluence CLI.
|
|
3
3
|
*/
|
|
4
|
-
import { Command, Prompt } from "@effect/cli"
|
|
5
|
-
import * as Path from "@effect/platform/Path"
|
|
6
4
|
import * as Console from "effect/Console"
|
|
7
5
|
import * as Effect from "effect/Effect"
|
|
6
|
+
import * as Path from "effect/Path"
|
|
7
|
+
import { Command, Prompt } from "effect/unstable/cli"
|
|
8
8
|
import { ConfluenceConfig } from "../ConfluenceConfig.js"
|
|
9
9
|
import { LocalFileSystem } from "../LocalFileSystem.js"
|
|
10
10
|
import { flattenPageTree } from "./pageTree.js"
|
|
@@ -24,7 +24,8 @@ export const newCommand = Command.make("new", {}, () =>
|
|
|
24
24
|
const config = yield* ConfluenceConfig
|
|
25
25
|
const pathService = yield* Path.Path
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const cwd = pathService.resolve(".")
|
|
28
|
+
const docsPath = pathService.join(cwd, config.docsPath)
|
|
28
29
|
|
|
29
30
|
// Build page tree
|
|
30
31
|
yield* Console.log("Scanning page structure...")
|
|
@@ -89,7 +90,7 @@ export const newCommand = Command.make("new", {}, () =>
|
|
|
89
90
|
"\n<!-- Write your page content here -->\n"
|
|
90
91
|
)
|
|
91
92
|
|
|
92
|
-
const relativePath = pathService.relative(
|
|
93
|
+
const relativePath = pathService.relative(cwd, filePath)
|
|
93
94
|
yield* Console.log(`Created new page: ${relativePath}`)
|
|
94
95
|
yield* Console.log("")
|
|
95
96
|
yield* Console.log("Next steps:")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared page input parsing for commands that accept Confluence page IDs.
|
|
3
|
+
*/
|
|
4
|
+
import * as Effect from "effect/Effect"
|
|
5
|
+
import { ConfigError } from "../ConfluenceError.js"
|
|
6
|
+
|
|
7
|
+
export interface PageInput {
|
|
8
|
+
readonly url?: string | undefined
|
|
9
|
+
readonly pageId?: string | undefined
|
|
10
|
+
readonly baseUrl?: string | undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ResolvedPageInput {
|
|
14
|
+
readonly pageId: string
|
|
15
|
+
readonly baseUrl: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const isSupportedHost = (host: string): boolean => /^[a-z0-9-]+\.atlassian\.(?:net|com)$/.test(host)
|
|
19
|
+
|
|
20
|
+
const isNumericPageId = (segment: string): boolean => /^[0-9]+$/.test(segment)
|
|
21
|
+
|
|
22
|
+
export const validatePageId = (input: string): Effect.Effect<string, ConfigError> => {
|
|
23
|
+
const pageId = input.trim()
|
|
24
|
+
return pageId.length > 0 && isNumericPageId(pageId)
|
|
25
|
+
? Effect.succeed(pageId)
|
|
26
|
+
: Effect.fail(new ConfigError({ message: `Invalid Confluence page ID: ${input}` }))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const validateBaseUrl = (input: string): Effect.Effect<string, ConfigError> =>
|
|
30
|
+
Effect.gen(function*() {
|
|
31
|
+
const url = yield* Effect.try({
|
|
32
|
+
try: () => new URL(input.trim()),
|
|
33
|
+
catch: () => new ConfigError({ message: `Invalid Confluence URL: ${input}` })
|
|
34
|
+
})
|
|
35
|
+
if (url.protocol !== "https:" || url.pathname !== "/" || !isSupportedHost(url.host)) {
|
|
36
|
+
return yield* Effect.fail(
|
|
37
|
+
new ConfigError({
|
|
38
|
+
message: `Invalid Confluence URL: ${input}. Expected format: https://yoursite.atlassian.net`
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
return `${url.protocol}//${url.host}`
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const parseConfluencePageUrl = (input: string): Effect.Effect<ResolvedPageInput, ConfigError> =>
|
|
46
|
+
Effect.gen(function*() {
|
|
47
|
+
const url = yield* Effect.try({
|
|
48
|
+
try: () => new URL(input.trim()),
|
|
49
|
+
catch: () => new ConfigError({ message: `Invalid Confluence page URL: ${input}` })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (url.protocol !== "https:" || !isSupportedHost(url.host)) {
|
|
53
|
+
return yield* Effect.fail(
|
|
54
|
+
new ConfigError({
|
|
55
|
+
message: `Unsupported Confluence page URL: ${input}. Expected an https Atlassian Cloud URL.`
|
|
56
|
+
})
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const segments = url.pathname.split("/").filter((segment) => segment.length > 0)
|
|
61
|
+
const pagesIndex = segments.indexOf("pages")
|
|
62
|
+
const pageIdFromPages = pagesIndex >= 0 ? segments[pagesIndex + 1] : undefined
|
|
63
|
+
const pageId = pagesIndex >= 0
|
|
64
|
+
? pageIdFromPages && isNumericPageId(pageIdFromPages) ? pageIdFromPages : undefined
|
|
65
|
+
: segments.find(isNumericPageId)
|
|
66
|
+
|
|
67
|
+
if (!pageId) {
|
|
68
|
+
return yield* Effect.fail(new ConfigError({ message: `Could not find a page ID in URL: ${input}` }))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
pageId,
|
|
73
|
+
baseUrl: `${url.protocol}//${url.host}`
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export const resolvePageInput = (input: PageInput): Effect.Effect<ResolvedPageInput, ConfigError> =>
|
|
78
|
+
Effect.gen(function*() {
|
|
79
|
+
const url = input.url?.trim()
|
|
80
|
+
const pageId = input.pageId?.trim()
|
|
81
|
+
const baseUrl = input.baseUrl?.trim()
|
|
82
|
+
|
|
83
|
+
if (url && (pageId || baseUrl)) {
|
|
84
|
+
return yield* Effect.fail(
|
|
85
|
+
new ConfigError({ message: "Use either --url or --page-id/--base-url, not both." })
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (url) {
|
|
90
|
+
return yield* parseConfluencePageUrl(url)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!pageId || !baseUrl) {
|
|
94
|
+
return yield* Effect.fail(
|
|
95
|
+
new ConfigError({ message: "Both --page-id and --base-url are required when --url is not provided." })
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
pageId: yield* validatePageId(pageId),
|
|
101
|
+
baseUrl: yield* validateBaseUrl(baseUrl)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root CLI command composition.
|
|
3
|
+
*/
|
|
4
|
+
import { makeInstallCommand } from "@knpkv/agent-skills"
|
|
5
|
+
import * as Console from "effect/Console"
|
|
6
|
+
import { Command } from "effect/unstable/cli"
|
|
7
|
+
import {
|
|
8
|
+
authCommand,
|
|
9
|
+
cloneCommand,
|
|
10
|
+
commitCommand,
|
|
11
|
+
deleteCommand,
|
|
12
|
+
diffCommand,
|
|
13
|
+
fetchCommand,
|
|
14
|
+
logCommand,
|
|
15
|
+
newCommand,
|
|
16
|
+
pullCommand,
|
|
17
|
+
pushCommand,
|
|
18
|
+
statusCommand
|
|
19
|
+
} from "./index.js"
|
|
20
|
+
|
|
21
|
+
export interface ConfluenceCommandOptions {
|
|
22
|
+
readonly fetch?: typeof fetchCommand
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const skillsInstall = makeInstallCommand({
|
|
26
|
+
description: "Install the Confluence agent skill",
|
|
27
|
+
name: "install",
|
|
28
|
+
skills: ["confluence"]
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const skillsCommand = Command.make(
|
|
32
|
+
"skills",
|
|
33
|
+
{},
|
|
34
|
+
() => Console.log("Usage: confluence skills install")
|
|
35
|
+
).pipe(
|
|
36
|
+
Command.withDescription("Agent skill commands"),
|
|
37
|
+
Command.withSubcommands([skillsInstall])
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
export const makeConfluenceCommand = (options: ConfluenceCommandOptions = {}) =>
|
|
41
|
+
Command.make("confluence").pipe(
|
|
42
|
+
Command.withDescription("Sync Confluence pages to local markdown"),
|
|
43
|
+
Command.withSubcommands([
|
|
44
|
+
cloneCommand,
|
|
45
|
+
authCommand,
|
|
46
|
+
pullCommand,
|
|
47
|
+
pushCommand,
|
|
48
|
+
statusCommand,
|
|
49
|
+
commitCommand,
|
|
50
|
+
logCommand,
|
|
51
|
+
diffCommand,
|
|
52
|
+
options.fetch ?? fetchCommand,
|
|
53
|
+
newCommand,
|
|
54
|
+
deleteCommand,
|
|
55
|
+
skillsCommand
|
|
56
|
+
])
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
export const confluenceCommand = makeConfluenceCommand()
|
package/src/commands/sync.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sync commands (pull, push, status) for Confluence CLI.
|
|
3
3
|
*/
|
|
4
|
-
import { Command, Options } from "@effect/cli"
|
|
5
4
|
import * as Console from "effect/Console"
|
|
6
5
|
import * as Effect from "effect/Effect"
|
|
6
|
+
import { Command, Flag as Options } from "effect/unstable/cli"
|
|
7
7
|
import { GitService } from "../GitService.js"
|
|
8
|
+
import { writeStdout } from "../internal/stdio.js"
|
|
9
|
+
import type { ProgressCallback } from "../SyncEngine.js"
|
|
8
10
|
import { SyncEngine } from "../SyncEngine.js"
|
|
9
11
|
|
|
10
12
|
// === Pull command ===
|
|
@@ -24,16 +26,15 @@ export const pullCommand = Command.make(
|
|
|
24
26
|
Effect.gen(function*() {
|
|
25
27
|
const engine = yield* SyncEngine
|
|
26
28
|
yield* Console.log("Pulling pages from Confluence...")
|
|
27
|
-
const onProgress = (current
|
|
28
|
-
|
|
29
|
-
}
|
|
29
|
+
const onProgress: ProgressCallback = (current, total, message) =>
|
|
30
|
+
writeStdout(`\r Replaying history: ${current}/${total} - ${message}`)
|
|
30
31
|
const result = yield* engine.pull({
|
|
31
32
|
force,
|
|
32
33
|
replayHistory,
|
|
33
34
|
...(replayHistory ? { onProgress } : {})
|
|
34
35
|
})
|
|
35
36
|
if (replayHistory) {
|
|
36
|
-
|
|
37
|
+
yield* writeStdout("\r" + " ".repeat(80) + "\r")
|
|
37
38
|
}
|
|
38
39
|
yield* Console.log(`Pulled ${result.pulled} pages`)
|
|
39
40
|
if (result.commits > 0) {
|
|
@@ -91,7 +92,7 @@ export const statusCommand = Command.make("status", {}, () =>
|
|
|
91
92
|
const gitStatus = yield* git.status()
|
|
92
93
|
const commitCount = yield* git.log({ n: 1 }).pipe(
|
|
93
94
|
Effect.map((commits) => commits.length > 0 ? "has commits" : "no commits"),
|
|
94
|
-
Effect.
|
|
95
|
+
Effect.catchIf(() => true, () => Effect.succeed("unknown"))
|
|
95
96
|
)
|
|
96
97
|
yield* Console.log(`Git: initialized (${commitCount})`)
|
|
97
98
|
if (gitStatus.hasChanges) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node.js-specific layer implementations.
|
|
3
3
|
*
|
|
4
|
-
* This
|
|
5
|
-
* All other code should use Effect platform abstractions.
|
|
4
|
+
* This file wires package-specific Node runtime layers.
|
|
6
5
|
*
|
|
7
6
|
* @module
|
|
8
7
|
* @internal
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External sidecar storage for ADF placeholder metadata.
|
|
3
|
+
*
|
|
4
|
+
* Markdown stays readable by replacing large `attrs={...}` / `node={...}`
|
|
5
|
+
* blobs with `ref=./page.adf.json#id`; the sidecar stores decoded JSON.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import * as Schema from "effect/Schema"
|
|
10
|
+
|
|
11
|
+
export type AdfMetadataKind = "attrs" | "marks" | "node"
|
|
12
|
+
|
|
13
|
+
export const AdfMetadataEntrySchema = Schema.Struct({
|
|
14
|
+
kind: Schema.Literals(["attrs", "marks", "node"]),
|
|
15
|
+
value: Schema.Unknown
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const AdfMetadataSidecarSchema = Schema.Struct({
|
|
19
|
+
version: Schema.Literal(1),
|
|
20
|
+
entries: Schema.Record(Schema.String, AdfMetadataEntrySchema)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export type AdfMetadataEntry = typeof AdfMetadataEntrySchema.Type
|
|
24
|
+
export type AdfMetadataSidecar = typeof AdfMetadataSidecarSchema.Type
|
|
25
|
+
|
|
26
|
+
const stableStringify = (v: unknown): string => {
|
|
27
|
+
if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`
|
|
28
|
+
if (v !== null && typeof v === "object") {
|
|
29
|
+
const entries = Object.entries(v as Record<string, unknown>)
|
|
30
|
+
.filter(([, value]) => value !== undefined)
|
|
31
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
32
|
+
.map(([k, value]) => `${JSON.stringify(k)}:${stableStringify(value)}`)
|
|
33
|
+
return `{${entries.join(",")}}`
|
|
34
|
+
}
|
|
35
|
+
return JSON.stringify(v) ?? "null"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const markerKinds: ReadonlyArray<AdfMetadataKind> = ["node", "attrs", "marks"]
|
|
39
|
+
|
|
40
|
+
const markerType = (line: string): string => {
|
|
41
|
+
const match = /<!--\s*adf:([A-Za-z][A-Za-z0-9]*)/.exec(line)
|
|
42
|
+
return match?.[1] ?? "metadata"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fromBase64 = (b64: string): string => {
|
|
46
|
+
const bin = atob(b64)
|
|
47
|
+
return new TextDecoder().decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const parseMetadataValue = (raw: string): unknown | null => {
|
|
51
|
+
const trimmed = raw.trim()
|
|
52
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(trimmed) as unknown
|
|
55
|
+
} catch {
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const decoded = fromBase64(trimmed)
|
|
62
|
+
if (!decoded.startsWith("{") && !decoded.startsWith("[")) return null
|
|
63
|
+
return JSON.parse(decoded) as unknown
|
|
64
|
+
} catch {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const externalizeLine = (
|
|
70
|
+
line: string,
|
|
71
|
+
sidecarHref: string,
|
|
72
|
+
nextId: (type: string) => string,
|
|
73
|
+
entries: Record<string, AdfMetadataEntry>
|
|
74
|
+
): string => {
|
|
75
|
+
if (!line.includes("<!-- adf:") || line.includes("<!-- adf:/")) return line
|
|
76
|
+
const end = line.lastIndexOf("-->")
|
|
77
|
+
if (end === -1) return line
|
|
78
|
+
|
|
79
|
+
for (const kind of markerKinds) {
|
|
80
|
+
const needle = ` ${kind}=`
|
|
81
|
+
const keyStart = line.indexOf(needle)
|
|
82
|
+
if (keyStart === -1 || keyStart > end) continue
|
|
83
|
+
|
|
84
|
+
const valueStart = keyStart + needle.length
|
|
85
|
+
const raw = line.slice(valueStart, end).trim()
|
|
86
|
+
const value = parseMetadataValue(raw)
|
|
87
|
+
if (value === null) return line
|
|
88
|
+
|
|
89
|
+
const id = nextId(markerType(line))
|
|
90
|
+
entries[id] = { kind, value }
|
|
91
|
+
return `${line.slice(0, keyStart)} ref=${sidecarHref}#${id} ${line.slice(end)}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return line
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const externalizeAdfMetadata = (
|
|
98
|
+
markdown: string,
|
|
99
|
+
sidecarHref: string
|
|
100
|
+
): { readonly markdown: string; readonly sidecar: AdfMetadataSidecar | null } => {
|
|
101
|
+
const entries: Record<string, AdfMetadataEntry> = {}
|
|
102
|
+
let counter = 0
|
|
103
|
+
const nextId = (type: string): string => `${type}-${++counter}`
|
|
104
|
+
const lines = markdown.split("\n").map((line) => externalizeLine(line, sidecarHref, nextId, entries))
|
|
105
|
+
return {
|
|
106
|
+
markdown: lines.join("\n"),
|
|
107
|
+
sidecar: Object.keys(entries).length > 0 ? { version: 1, entries } : null
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const collectAdfMetadataHrefs = (markdown: string): ReadonlySet<string> => {
|
|
112
|
+
const hrefs = new Set<string>()
|
|
113
|
+
for (const line of markdown.split("\n")) {
|
|
114
|
+
if (!line.includes("<!-- adf:") || !line.includes(" ref=")) continue
|
|
115
|
+
const end = line.lastIndexOf("-->")
|
|
116
|
+
const refStart = line.indexOf(" ref=")
|
|
117
|
+
if (end === -1 || refStart === -1 || refStart > end) continue
|
|
118
|
+
const rawRef = line.slice(refStart + " ref=".length, end).trim()
|
|
119
|
+
const href = rawRef.includes("#") ? rawRef.slice(0, rawRef.lastIndexOf("#")) : rawRef
|
|
120
|
+
if (href.length > 0) hrefs.add(href)
|
|
121
|
+
}
|
|
122
|
+
return hrefs
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const hydrateLine = (line: string, sidecars: ReadonlyMap<string, AdfMetadataSidecar>): string => {
|
|
126
|
+
if (!line.includes("<!-- adf:") || !line.includes(" ref=")) return line
|
|
127
|
+
const end = line.lastIndexOf("-->")
|
|
128
|
+
const refStart = line.indexOf(" ref=")
|
|
129
|
+
if (end === -1 || refStart === -1 || refStart > end) return line
|
|
130
|
+
|
|
131
|
+
const rawRef = line.slice(refStart + " ref=".length, end).trim()
|
|
132
|
+
const href = rawRef.includes("#") ? rawRef.slice(0, rawRef.lastIndexOf("#")) : rawRef
|
|
133
|
+
const id = rawRef.includes("#") ? rawRef.slice(rawRef.lastIndexOf("#") + 1) : rawRef
|
|
134
|
+
const sidecar = sidecars.get(href)
|
|
135
|
+
if (!sidecar) return line
|
|
136
|
+
const entry = sidecar.entries[id]
|
|
137
|
+
if (!entry) return line
|
|
138
|
+
|
|
139
|
+
return `${line.slice(0, refStart)} ${entry.kind}=${stableStringify(entry.value)} ${line.slice(end)}`
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const hydrateAdfMetadata = (
|
|
143
|
+
markdown: string,
|
|
144
|
+
sidecars: ReadonlyMap<string, AdfMetadataSidecar>
|
|
145
|
+
): string => markdown.split("\n").map((line) => hydrateLine(line, sidecars)).join("\n")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove Confluence round-trip metadata from markdown intended for reading.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const ADF_COMMENT_PATTERN = /<!--\s*adf:[\s\S]*?-->/g
|
|
6
|
+
|
|
7
|
+
export const cleanMarkdown = (markdown: string): string => {
|
|
8
|
+
const cleaned = markdown
|
|
9
|
+
.replace(ADF_COMMENT_PATTERN, "")
|
|
10
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
11
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
12
|
+
.trim()
|
|
13
|
+
|
|
14
|
+
return cleaned.length > 0 ? `${cleaned}\n` : ""
|
|
15
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as Effect from "effect/Effect"
|
|
8
8
|
import * as Schema from "effect/Schema"
|
|
9
|
-
import
|
|
9
|
+
import * as yaml from "js-yaml"
|
|
10
10
|
import { FrontMatterError } from "../ConfluenceError.js"
|
|
11
11
|
import type { NewPageFrontMatter, PageFrontMatter } from "../Schemas.js"
|
|
12
12
|
import { NewPageFrontMatterSchema, PageFrontMatterSchema } from "../Schemas.js"
|
|
@@ -20,6 +20,33 @@ export interface ParsedMarkdown {
|
|
|
20
20
|
readonly isNew: boolean
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
const parseRawMarkdown = (content: string): { readonly data: Record<string, unknown>; readonly content: string } => {
|
|
24
|
+
if (!content.startsWith("---\n") && !content.startsWith("---\r\n")) {
|
|
25
|
+
return { data: {}, content }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const newline = content.startsWith("---\r\n") ? "\r\n" : "\n"
|
|
29
|
+
const headerStart = 3 + newline.length
|
|
30
|
+
const closingMarker = `${newline}---`
|
|
31
|
+
const closingStart = content.indexOf(closingMarker, headerStart)
|
|
32
|
+
if (closingStart === -1) {
|
|
33
|
+
return { data: {}, content }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const header = content.slice(headerStart, closingStart)
|
|
37
|
+
const afterClosingStart = closingStart + closingMarker.length
|
|
38
|
+
const afterClosing = content.startsWith("\r\n", afterClosingStart)
|
|
39
|
+
? content.slice(afterClosingStart + 2)
|
|
40
|
+
: content.startsWith("\n", afterClosingStart)
|
|
41
|
+
? content.slice(afterClosingStart + 1)
|
|
42
|
+
: content.slice(afterClosingStart)
|
|
43
|
+
const loaded = yaml.load(header)
|
|
44
|
+
const data = loaded !== null && typeof loaded === "object" && !Array.isArray(loaded)
|
|
45
|
+
? loaded as Record<string, unknown>
|
|
46
|
+
: {}
|
|
47
|
+
return { data, content: afterClosing }
|
|
48
|
+
}
|
|
49
|
+
|
|
23
50
|
/**
|
|
24
51
|
* Parse a markdown file with YAML front-matter.
|
|
25
52
|
*
|
|
@@ -35,7 +62,7 @@ export const parseMarkdown = (
|
|
|
35
62
|
): Effect.Effect<ParsedMarkdown, FrontMatterError> =>
|
|
36
63
|
Effect.gen(function*() {
|
|
37
64
|
const parsed = yield* Effect.try({
|
|
38
|
-
try: () =>
|
|
65
|
+
try: () => parseRawMarkdown(content),
|
|
39
66
|
catch: (cause) => new FrontMatterError({ path: filePath, cause })
|
|
40
67
|
})
|
|
41
68
|
|
|
@@ -49,21 +76,21 @@ export const parseMarkdown = (
|
|
|
49
76
|
}
|
|
50
77
|
|
|
51
78
|
// Try to parse as existing page front-matter
|
|
52
|
-
const existingResult = yield* Schema.
|
|
79
|
+
const existingResult = yield* Schema.decodeUnknownEffect(PageFrontMatterSchema)(parsed.data).pipe(
|
|
53
80
|
Effect.map((fm) => ({
|
|
54
81
|
frontMatter: fm,
|
|
55
82
|
content: parsed.content.trim(),
|
|
56
83
|
isNew: false
|
|
57
84
|
})),
|
|
58
|
-
Effect.
|
|
85
|
+
Effect.catchCause(() =>
|
|
59
86
|
// Try to parse as new page front-matter
|
|
60
|
-
Schema.
|
|
87
|
+
Schema.decodeUnknownEffect(NewPageFrontMatterSchema)(parsed.data).pipe(
|
|
61
88
|
Effect.map((fm) => ({
|
|
62
89
|
frontMatter: fm as NewPageFrontMatter,
|
|
63
90
|
content: parsed.content.trim(),
|
|
64
91
|
isNew: true
|
|
65
92
|
})),
|
|
66
|
-
Effect.
|
|
93
|
+
Effect.catchCause((cause) => Effect.fail(new FrontMatterError({ path: filePath, cause })))
|
|
67
94
|
)
|
|
68
95
|
)
|
|
69
96
|
)
|
|
@@ -94,7 +121,7 @@ export const serializeMarkdown = (
|
|
|
94
121
|
contentHash: frontMatter.contentHash
|
|
95
122
|
}
|
|
96
123
|
|
|
97
|
-
return
|
|
124
|
+
return stringifyFrontmatter(content, fm)
|
|
98
125
|
}
|
|
99
126
|
|
|
100
127
|
/**
|
|
@@ -115,5 +142,15 @@ export const serializeNewPageMarkdown = (
|
|
|
115
142
|
...(frontMatter.parentId !== undefined ? { parentId: frontMatter.parentId } : {})
|
|
116
143
|
}
|
|
117
144
|
|
|
118
|
-
return
|
|
145
|
+
return stringifyFrontmatter(content, fm)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const stringifyFrontmatter = (content: string, frontMatter: Record<string, unknown>): string => {
|
|
149
|
+
const header = yaml.dump(frontMatter, {
|
|
150
|
+
lineWidth: -1,
|
|
151
|
+
noRefs: true,
|
|
152
|
+
sortKeys: false
|
|
153
|
+
}).trimEnd()
|
|
154
|
+
const body = content.endsWith("\n") ? content : `${content}\n`
|
|
155
|
+
return `---\n${header}\n---\n${body}`
|
|
119
156
|
}
|