@knpkv/confluence-to-markdown 0.2.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +282 -14
- package/dist/ConfluenceAuth.d.ts +76 -0
- package/dist/ConfluenceAuth.d.ts.map +1 -0
- package/dist/ConfluenceAuth.js +356 -0
- package/dist/ConfluenceAuth.js.map +1 -0
- package/dist/ConfluenceClient.d.ts +26 -2
- package/dist/ConfluenceClient.d.ts.map +1 -1
- package/dist/ConfluenceClient.js +98 -92
- package/dist/ConfluenceClient.js.map +1 -1
- package/dist/ConfluenceConfig.d.ts +4 -24
- package/dist/ConfluenceConfig.d.ts.map +1 -1
- package/dist/ConfluenceConfig.js +45 -7
- package/dist/ConfluenceConfig.js.map +1 -1
- package/dist/ConfluenceError.d.ts +89 -6
- package/dist/ConfluenceError.d.ts.map +1 -1
- package/dist/ConfluenceError.js +88 -5
- package/dist/ConfluenceError.js.map +1 -1
- package/dist/GitError.d.ts +103 -0
- package/dist/GitError.d.ts.map +1 -0
- package/dist/GitError.js +85 -0
- package/dist/GitError.js.map +1 -0
- package/dist/GitService.d.ts +175 -0
- package/dist/GitService.d.ts.map +1 -0
- package/dist/GitService.js +431 -0
- package/dist/GitService.js.map +1 -0
- package/dist/LocalFileSystem.d.ts +29 -4
- package/dist/LocalFileSystem.d.ts.map +1 -1
- package/dist/LocalFileSystem.js +80 -6
- package/dist/LocalFileSystem.js.map +1 -1
- package/dist/MarkdownConverter.d.ts +49 -2
- package/dist/MarkdownConverter.d.ts.map +1 -1
- package/dist/MarkdownConverter.js +73 -111
- package/dist/MarkdownConverter.js.map +1 -1
- package/dist/SchemaConverterError.d.ts +108 -0
- package/dist/SchemaConverterError.d.ts.map +1 -0
- package/dist/SchemaConverterError.js +84 -0
- package/dist/SchemaConverterError.js.map +1 -0
- package/dist/Schemas.d.ts +225 -1
- package/dist/Schemas.d.ts.map +1 -1
- package/dist/Schemas.js +155 -6
- package/dist/Schemas.js.map +1 -1
- package/dist/SyncEngine.d.ts +30 -20
- package/dist/SyncEngine.d.ts.map +1 -1
- package/dist/SyncEngine.js +566 -117
- package/dist/SyncEngine.js.map +1 -1
- package/dist/ast/BlockNode.d.ts +468 -0
- package/dist/ast/BlockNode.d.ts.map +1 -0
- package/dist/ast/BlockNode.js +319 -0
- package/dist/ast/BlockNode.js.map +1 -0
- package/dist/ast/Document.d.ts +244 -0
- package/dist/ast/Document.d.ts.map +1 -0
- package/dist/ast/Document.js +69 -0
- package/dist/ast/Document.js.map +1 -0
- package/dist/ast/InlineNode.d.ts +477 -0
- package/dist/ast/InlineNode.d.ts.map +1 -0
- package/dist/ast/InlineNode.js +263 -0
- package/dist/ast/InlineNode.js.map +1 -0
- package/dist/ast/MacroNode.d.ts +267 -0
- package/dist/ast/MacroNode.d.ts.map +1 -0
- package/dist/ast/MacroNode.js +164 -0
- package/dist/ast/MacroNode.js.map +1 -0
- package/dist/ast/index.d.ts +10 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +14 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/bin.js +33 -149
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth.d.ts +15 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +86 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/clone.d.ts +12 -0
- package/dist/commands/clone.d.ts.map +1 -0
- package/dist/commands/clone.js +93 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/delete.d.ts +13 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +48 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/errorHandler.d.ts +14 -0
- package/dist/commands/errorHandler.d.ts.map +1 -0
- package/dist/commands/errorHandler.js +33 -0
- package/dist/commands/errorHandler.js.map +1 -0
- package/dist/commands/git.d.ts +22 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +72 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/layers.d.ts +31 -0
- package/dist/commands/layers.d.ts.map +1 -0
- package/dist/commands/layers.js +137 -0
- package/dist/commands/layers.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +80 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pageTree.d.ts +18 -0
- package/dist/commands/pageTree.d.ts.map +1 -0
- package/dist/commands/pageTree.js +20 -0
- package/dist/commands/pageTree.js.map +1 -0
- package/dist/commands/shared.d.ts +15 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +27 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/sync.d.ts +15 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +101 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/NodeLayers.d.ts +7 -0
- package/dist/internal/NodeLayers.d.ts.map +1 -0
- package/dist/internal/NodeLayers.js +19 -0
- package/dist/internal/NodeLayers.js.map +1 -0
- package/dist/internal/frontmatter.d.ts +10 -0
- package/dist/internal/frontmatter.d.ts.map +1 -1
- package/dist/internal/frontmatter.js +16 -0
- package/dist/internal/frontmatter.js.map +1 -1
- package/dist/internal/gitCommands.d.ts +78 -0
- package/dist/internal/gitCommands.d.ts.map +1 -0
- package/dist/internal/gitCommands.js +156 -0
- package/dist/internal/gitCommands.js.map +1 -0
- package/dist/internal/hashUtils.d.ts +42 -1
- package/dist/internal/hashUtils.d.ts.map +1 -1
- package/dist/internal/hashUtils.js +38 -2
- package/dist/internal/hashUtils.js.map +1 -1
- package/dist/internal/oauthServer.d.ts +55 -0
- package/dist/internal/oauthServer.d.ts.map +1 -0
- package/dist/internal/oauthServer.js +110 -0
- package/dist/internal/oauthServer.js.map +1 -0
- package/dist/internal/pathUtils.d.ts +21 -4
- package/dist/internal/pathUtils.d.ts.map +1 -1
- package/dist/internal/pathUtils.js +24 -13
- package/dist/internal/pathUtils.js.map +1 -1
- package/dist/internal/tokenStorage.d.ts +75 -0
- package/dist/internal/tokenStorage.d.ts.map +1 -0
- package/dist/internal/tokenStorage.js +149 -0
- package/dist/internal/tokenStorage.js.map +1 -0
- package/dist/internal/userCache.d.ts +42 -0
- package/dist/internal/userCache.d.ts.map +1 -0
- package/dist/internal/userCache.js +51 -0
- package/dist/internal/userCache.js.map +1 -0
- package/dist/parsers/ConfluenceParser.d.ts +26 -0
- package/dist/parsers/ConfluenceParser.d.ts.map +1 -0
- package/dist/parsers/ConfluenceParser.js +792 -0
- package/dist/parsers/ConfluenceParser.js.map +1 -0
- package/dist/parsers/MarkdownParser.d.ts +26 -0
- package/dist/parsers/MarkdownParser.d.ts.map +1 -0
- package/dist/parsers/MarkdownParser.js +873 -0
- package/dist/parsers/MarkdownParser.js.map +1 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +8 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/schemas/ConfluenceSchema.d.ts +21 -0
- package/dist/schemas/ConfluenceSchema.d.ts.map +1 -0
- package/dist/schemas/ConfluenceSchema.js +38 -0
- package/dist/schemas/ConfluenceSchema.js.map +1 -0
- package/dist/schemas/ConversionSchema.d.ts +35 -0
- package/dist/schemas/ConversionSchema.d.ts.map +1 -0
- package/dist/schemas/ConversionSchema.js +208 -0
- package/dist/schemas/ConversionSchema.js.map +1 -0
- package/dist/schemas/MarkdownSchema.d.ts +21 -0
- package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
- package/dist/schemas/MarkdownSchema.js +38 -0
- package/dist/schemas/MarkdownSchema.js.map +1 -0
- package/dist/schemas/hast/HastFromHtml.d.ts +27 -0
- package/dist/schemas/hast/HastFromHtml.d.ts.map +1 -0
- package/dist/schemas/hast/HastFromHtml.js +107 -0
- package/dist/schemas/hast/HastFromHtml.js.map +1 -0
- package/dist/schemas/hast/HastSchema.d.ts +195 -0
- package/dist/schemas/hast/HastSchema.d.ts.map +1 -0
- package/dist/schemas/hast/HastSchema.js +183 -0
- package/dist/schemas/hast/HastSchema.js.map +1 -0
- package/dist/schemas/hast/index.d.ts +9 -0
- package/dist/schemas/hast/index.d.ts.map +1 -0
- package/dist/schemas/hast/index.js +3 -0
- package/dist/schemas/hast/index.js.map +1 -0
- package/dist/schemas/index.d.ts +14 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +16 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts +30 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js +79 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js.map +1 -0
- package/dist/schemas/mdast/MdastSchema.d.ts +385 -0
- package/dist/schemas/mdast/MdastSchema.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastSchema.js +266 -0
- package/dist/schemas/mdast/MdastSchema.js.map +1 -0
- package/dist/schemas/mdast/index.d.ts +10 -0
- package/dist/schemas/mdast/index.d.ts.map +1 -0
- package/dist/schemas/mdast/index.js +4 -0
- package/dist/schemas/mdast/index.js.map +1 -0
- package/dist/schemas/mdast/mdastToString.d.ts +13 -0
- package/dist/schemas/mdast/mdastToString.d.ts.map +1 -0
- package/dist/schemas/mdast/mdastToString.js +85 -0
- package/dist/schemas/mdast/mdastToString.js.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts +43 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.js +634 -0
- package/dist/schemas/nodes/block/BlockSchema.js.map +1 -0
- package/dist/schemas/nodes/block/index.d.ts +7 -0
- package/dist/schemas/nodes/block/index.d.ts.map +1 -0
- package/dist/schemas/nodes/block/index.js +7 -0
- package/dist/schemas/nodes/block/index.js.map +1 -0
- package/dist/schemas/nodes/index.d.ts +9 -0
- package/dist/schemas/nodes/index.d.ts.map +1 -0
- package/dist/schemas/nodes/index.js +12 -0
- package/dist/schemas/nodes/index.js.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts +48 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.js +436 -0
- package/dist/schemas/nodes/inline/InlineSchema.js.map +1 -0
- package/dist/schemas/nodes/inline/index.d.ts +7 -0
- package/dist/schemas/nodes/inline/index.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/index.js +7 -0
- package/dist/schemas/nodes/inline/index.js.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts +27 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.js +162 -0
- package/dist/schemas/nodes/macro/MacroSchema.js.map +1 -0
- package/dist/schemas/nodes/macro/index.d.ts +7 -0
- package/dist/schemas/nodes/macro/index.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/index.js +7 -0
- package/dist/schemas/nodes/macro/index.js.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +24 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js +351 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -0
- package/dist/schemas/preprocessing/index.d.ts +8 -0
- package/dist/schemas/preprocessing/index.d.ts.map +1 -0
- package/dist/schemas/preprocessing/index.js +2 -0
- package/dist/schemas/preprocessing/index.js.map +1 -0
- package/dist/serializers/ConfluenceSerializer.d.ts +30 -0
- package/dist/serializers/ConfluenceSerializer.d.ts.map +1 -0
- package/dist/serializers/ConfluenceSerializer.js +551 -0
- package/dist/serializers/ConfluenceSerializer.js.map +1 -0
- package/dist/serializers/MarkdownSerializer.d.ts +34 -0
- package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
- package/dist/serializers/MarkdownSerializer.js +355 -0
- package/dist/serializers/MarkdownSerializer.js.map +1 -0
- package/dist/serializers/index.d.ts +8 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/serializers/index.js +8 -0
- package/dist/serializers/index.js.map +1 -0
- package/package.json +27 -16
- package/src/ConfluenceAuth.ts +571 -0
- package/src/ConfluenceClient.ts +188 -156
- package/src/ConfluenceConfig.ts +63 -7
- package/src/ConfluenceError.ts +110 -14
- package/src/GitError.ts +92 -0
- package/src/GitService.ts +859 -0
- package/src/LocalFileSystem.ts +179 -9
- package/src/MarkdownConverter.ts +126 -122
- package/src/SchemaConverterError.ts +108 -0
- package/src/Schemas.ts +223 -6
- package/src/SyncEngine.ts +745 -162
- package/src/ast/BlockNode.ts +425 -0
- package/src/ast/Document.ts +90 -0
- package/src/ast/InlineNode.ts +323 -0
- package/src/ast/MacroNode.ts +245 -0
- package/src/ast/index.ts +83 -0
- package/src/bin.ts +50 -249
- package/src/commands/auth.ts +117 -0
- package/src/commands/clone.ts +145 -0
- package/src/commands/delete.ts +57 -0
- package/src/commands/errorHandler.ts +32 -0
- package/src/commands/git.ts +114 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/layers.ts +211 -0
- package/src/commands/new.ts +99 -0
- package/src/commands/pageTree.ts +40 -0
- package/src/commands/shared.ts +35 -0
- package/src/commands/sync.ts +129 -0
- package/src/index.ts +21 -1
- package/src/internal/NodeLayers.ts +21 -0
- package/src/internal/frontmatter.ts +21 -0
- package/src/internal/gitCommands.ts +229 -0
- package/src/internal/hashUtils.ts +65 -3
- package/src/internal/oauthServer.ts +199 -0
- package/src/internal/pathUtils.ts +34 -17
- package/src/internal/tokenStorage.ts +240 -0
- package/src/internal/userCache.ts +90 -0
- package/src/parsers/ConfluenceParser.ts +950 -0
- package/src/parsers/MarkdownParser.ts +1198 -0
- package/src/parsers/index.ts +8 -0
- package/src/schemas/ConfluenceSchema.ts +56 -0
- package/src/schemas/ConversionSchema.ts +318 -0
- package/src/schemas/MarkdownSchema.ts +56 -0
- package/src/schemas/hast/HastFromHtml.ts +153 -0
- package/src/schemas/hast/HastSchema.ts +274 -0
- package/src/schemas/hast/index.ts +35 -0
- package/src/schemas/index.ts +20 -0
- package/src/schemas/mdast/MdastFromMarkdown.ts +118 -0
- package/src/schemas/mdast/MdastSchema.ts +566 -0
- package/src/schemas/mdast/index.ts +59 -0
- package/src/schemas/mdast/mdastToString.ts +102 -0
- package/src/schemas/nodes/block/BlockSchema.ts +773 -0
- package/src/schemas/nodes/block/index.ts +13 -0
- package/src/schemas/nodes/index.ts +20 -0
- package/src/schemas/nodes/inline/InlineSchema.ts +523 -0
- package/src/schemas/nodes/inline/index.ts +14 -0
- package/src/schemas/nodes/macro/MacroSchema.ts +226 -0
- package/src/schemas/nodes/macro/index.ts +6 -0
- package/src/schemas/preprocessing/ConfluencePreprocessor.ts +446 -0
- package/src/schemas/preprocessing/index.ts +8 -0
- package/src/serializers/ConfluenceSerializer.ts +717 -0
- package/src/serializers/MarkdownSerializer.ts +493 -0
- package/src/serializers/index.ts +8 -0
- package/test/GitService.test.ts +209 -0
- package/test/MarkdownConverter.test.ts +37 -3
- package/test/Schemas.test.ts +97 -2
- package/test/ast/BlockNode.test.ts +265 -0
- package/test/ast/Document.test.ts +126 -0
- package/test/ast/InlineNode.test.ts +161 -0
- package/test/fixtures/integration-test.html.fixture +103 -0
- package/test/fixtures/integration-test.md.expected +257 -0
- package/test/integration.test.ts +269 -0
- package/test/oauthServer.test.ts +50 -0
- package/test/parsers/ConfluenceParser.test.ts +283 -0
- package/test/schemas/ConfluencePreprocessor.test.ts +180 -0
- package/test/schemas/ConversionSchema.test.ts +159 -0
- package/test/schemas/HastSchema.test.ts +138 -0
- package/test/schemas/MdastSchema.test.ts +145 -0
- package/test/schemas/nodes/block/BlockSchema.test.ts +173 -0
- package/test/schemas/nodes/inline/InlineSchema.test.ts +198 -0
- package/test/schemas/nodes/macro/MacroSchema.test.ts +142 -0
- package/test/tokenStorage.test.ts +99 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js-specific layer implementations.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY file that should import directly from node:* modules.
|
|
5
|
+
* All other code should use Effect platform abstractions.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"
|
|
11
|
+
import { createServer } from "node:http"
|
|
12
|
+
import { makeHttpServerFactory } from "./oauthServer.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* HTTP Server factory layer using Node.js http module.
|
|
16
|
+
*
|
|
17
|
+
* @category Layers
|
|
18
|
+
*/
|
|
19
|
+
export const HttpServerFactoryLive = makeHttpServerFactory(
|
|
20
|
+
(port) => NodeHttpServer.layerServer(createServer, { port })
|
|
21
|
+
)
|
|
@@ -96,3 +96,24 @@ export const serializeMarkdown = (
|
|
|
96
96
|
|
|
97
97
|
return matter.stringify(content, fm)
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Serialize a new page markdown with minimal front-matter.
|
|
102
|
+
*
|
|
103
|
+
* @param frontMatter - The new page front-matter (title only)
|
|
104
|
+
* @param content - The markdown content
|
|
105
|
+
* @returns The serialized markdown file content
|
|
106
|
+
*
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
export const serializeNewPageMarkdown = (
|
|
110
|
+
frontMatter: NewPageFrontMatter,
|
|
111
|
+
content: string
|
|
112
|
+
): string => {
|
|
113
|
+
const fm = {
|
|
114
|
+
title: frontMatter.title,
|
|
115
|
+
...(frontMatter.parentId !== undefined ? { parentId: frontMatter.parentId } : {})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return matter.stringify(content, fm)
|
|
119
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git shell command helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import * as Command from "@effect/platform/Command"
|
|
8
|
+
import type * as CommandExecutor from "@effect/platform/CommandExecutor"
|
|
9
|
+
import type * as PlatformError from "@effect/platform/Error"
|
|
10
|
+
import * as Effect from "effect/Effect"
|
|
11
|
+
import * as String from "effect/String"
|
|
12
|
+
import { GitError, GitNotInstalledError } from "../GitError.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Git file status codes from `git status --porcelain`.
|
|
16
|
+
*/
|
|
17
|
+
export type GitFileStatus =
|
|
18
|
+
| "modified"
|
|
19
|
+
| "added"
|
|
20
|
+
| "deleted"
|
|
21
|
+
| "renamed"
|
|
22
|
+
| "copied"
|
|
23
|
+
| "untracked"
|
|
24
|
+
| "ignored"
|
|
25
|
+
| "unmerged"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parsed git status entry.
|
|
29
|
+
*/
|
|
30
|
+
export interface GitStatusEntry {
|
|
31
|
+
readonly status: GitFileStatus
|
|
32
|
+
readonly path: string
|
|
33
|
+
readonly staged: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parsed git log entry.
|
|
38
|
+
*/
|
|
39
|
+
export interface GitLogEntry {
|
|
40
|
+
readonly hash: string
|
|
41
|
+
readonly author: string
|
|
42
|
+
readonly email: string
|
|
43
|
+
readonly date: Date
|
|
44
|
+
readonly message: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Convert PlatformError to GitError or GitNotInstalledError.
|
|
49
|
+
*/
|
|
50
|
+
const mapPlatformError = (
|
|
51
|
+
error: PlatformError.PlatformError,
|
|
52
|
+
commandStr: string
|
|
53
|
+
): GitError | GitNotInstalledError => {
|
|
54
|
+
if (error._tag === "SystemError" && error.reason === "NotFound") {
|
|
55
|
+
return new GitNotInstalledError({
|
|
56
|
+
message: `Git not found: ${error.message}. Please install git.`
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
return new GitError({
|
|
60
|
+
command: commandStr,
|
|
61
|
+
message: error.message
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Run a git command in the specified working directory.
|
|
67
|
+
*
|
|
68
|
+
* @param args - Git command arguments
|
|
69
|
+
* @param cwd - Working directory
|
|
70
|
+
* @returns Command output
|
|
71
|
+
*
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export const runGit = (
|
|
75
|
+
args: ReadonlyArray<string>,
|
|
76
|
+
cwd: string
|
|
77
|
+
): Effect.Effect<string, GitError | GitNotInstalledError, CommandExecutor.CommandExecutor> =>
|
|
78
|
+
Effect.gen(function*() {
|
|
79
|
+
const command = Command.make("git", ...args).pipe(
|
|
80
|
+
Command.workingDirectory(cwd)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const commandStr = `git ${args.join(" ")}`
|
|
84
|
+
|
|
85
|
+
const result = yield* Command.string(command).pipe(
|
|
86
|
+
Effect.mapError((error) => mapPlatformError(error, commandStr))
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return result
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Run a git command that may fail with exit code 1 (e.g., diff with no changes).
|
|
94
|
+
* Returns empty string on exit code 1.
|
|
95
|
+
*
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
export const runGitAllowEmpty = (
|
|
99
|
+
args: ReadonlyArray<string>,
|
|
100
|
+
cwd: string
|
|
101
|
+
): Effect.Effect<string, GitError | GitNotInstalledError, CommandExecutor.CommandExecutor> =>
|
|
102
|
+
runGit(args, cwd).pipe(
|
|
103
|
+
Effect.catchIf(
|
|
104
|
+
(e): e is GitError => e._tag === "GitError",
|
|
105
|
+
() => Effect.succeed("")
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse git status --porcelain output.
|
|
111
|
+
*
|
|
112
|
+
* @param output - Raw output from `git status --porcelain`
|
|
113
|
+
* @returns Parsed status entries
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
export const parseGitStatus = (output: string): ReadonlyArray<GitStatusEntry> => {
|
|
118
|
+
if (String.isEmpty(output.trim())) {
|
|
119
|
+
return []
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return output
|
|
123
|
+
.split("\n")
|
|
124
|
+
.filter((line) => line.length >= 3) // Valid lines have XY + space + path
|
|
125
|
+
.map((line) => {
|
|
126
|
+
const indexStatus = line[0] ?? " "
|
|
127
|
+
const workTreeStatus = line[1] ?? " "
|
|
128
|
+
const path = line.slice(3)
|
|
129
|
+
|
|
130
|
+
const staged = indexStatus !== " " && indexStatus !== "?"
|
|
131
|
+
const status = parseStatusCode(staged ? indexStatus : workTreeStatus)
|
|
132
|
+
|
|
133
|
+
return { status, path, staged }
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Parse a single status code character.
|
|
139
|
+
*/
|
|
140
|
+
const parseStatusCode = (code: string): GitFileStatus => {
|
|
141
|
+
switch (code) {
|
|
142
|
+
case "M":
|
|
143
|
+
return "modified"
|
|
144
|
+
case "A":
|
|
145
|
+
return "added"
|
|
146
|
+
case "D":
|
|
147
|
+
return "deleted"
|
|
148
|
+
case "R":
|
|
149
|
+
return "renamed"
|
|
150
|
+
case "C":
|
|
151
|
+
return "copied"
|
|
152
|
+
case "?":
|
|
153
|
+
return "untracked"
|
|
154
|
+
case "!":
|
|
155
|
+
return "ignored"
|
|
156
|
+
case "U":
|
|
157
|
+
return "unmerged"
|
|
158
|
+
default:
|
|
159
|
+
return "modified"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Parse git log output with custom format.
|
|
165
|
+
*
|
|
166
|
+
* Uses format: hash<|>author<|>email<|>date<|>message
|
|
167
|
+
*
|
|
168
|
+
* @param output - Raw output from git log
|
|
169
|
+
* @returns Parsed log entries
|
|
170
|
+
*
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
export const parseGitLog = (output: string): ReadonlyArray<GitLogEntry> => {
|
|
174
|
+
if (String.isEmpty(output.trim())) {
|
|
175
|
+
return []
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return output
|
|
179
|
+
.trim()
|
|
180
|
+
.split("\n")
|
|
181
|
+
.flatMap((line) => {
|
|
182
|
+
const parts = line.split("<|>")
|
|
183
|
+
if (parts.length < 5) {
|
|
184
|
+
return []
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const hash = parts[0]
|
|
188
|
+
const author = parts[1]
|
|
189
|
+
const email = parts[2]
|
|
190
|
+
const dateStr = parts[3]
|
|
191
|
+
const messageParts = parts.slice(4)
|
|
192
|
+
|
|
193
|
+
if (hash === undefined || author === undefined || email === undefined || dateStr === undefined) {
|
|
194
|
+
return []
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const message = messageParts.join("<|>")
|
|
198
|
+
|
|
199
|
+
return [{
|
|
200
|
+
hash: hash.trim(),
|
|
201
|
+
author: author.trim(),
|
|
202
|
+
email: email.trim(),
|
|
203
|
+
date: new Date(dateStr.trim()),
|
|
204
|
+
message: message.trim()
|
|
205
|
+
}]
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Git log format string for parseGitLog.
|
|
211
|
+
*
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
export const GIT_LOG_FORMAT = "%H<|>%an<|>%ae<|>%aI<|>%s"
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Check if output indicates merge conflicts.
|
|
218
|
+
*
|
|
219
|
+
* @param statusOutput - Output from `git status --porcelain`
|
|
220
|
+
* @returns List of conflicted files
|
|
221
|
+
*
|
|
222
|
+
* @internal
|
|
223
|
+
*/
|
|
224
|
+
export const getConflictedFiles = (statusOutput: string): ReadonlyArray<string> => {
|
|
225
|
+
const entries = parseGitStatus(statusOutput)
|
|
226
|
+
return entries
|
|
227
|
+
.filter((e) => e.status === "unmerged")
|
|
228
|
+
.map((e) => e.path)
|
|
229
|
+
}
|
|
@@ -4,9 +4,68 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
* @internal
|
|
6
6
|
*/
|
|
7
|
-
import * as
|
|
7
|
+
import * as Context from "effect/Context"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as Layer from "effect/Layer"
|
|
8
10
|
import type { ContentHash } from "../Brand.js"
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Hash computation service.
|
|
14
|
+
* This allows mocking in tests.
|
|
15
|
+
*
|
|
16
|
+
* @category Services
|
|
17
|
+
*/
|
|
18
|
+
export interface HashService {
|
|
19
|
+
readonly computeSha256: (content: string) => Effect.Effect<ContentHash>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tag for the HashService.
|
|
24
|
+
*
|
|
25
|
+
* @category Services
|
|
26
|
+
*/
|
|
27
|
+
export class HashServiceTag extends Context.Tag("@knpkv/confluence-to-markdown/HashService")<
|
|
28
|
+
HashServiceTag,
|
|
29
|
+
HashService
|
|
30
|
+
>() {}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a HashService layer from a hash function.
|
|
34
|
+
* This allows injecting platform-specific implementations.
|
|
35
|
+
*
|
|
36
|
+
* @param hashFn - Function that computes SHA256 hash
|
|
37
|
+
* @category Layers
|
|
38
|
+
*/
|
|
39
|
+
export const makeHashServiceLive = (
|
|
40
|
+
hashFn: (content: string) => Promise<string>
|
|
41
|
+
): Layer.Layer<HashServiceTag> =>
|
|
42
|
+
Layer.succeed(
|
|
43
|
+
HashServiceTag,
|
|
44
|
+
{
|
|
45
|
+
computeSha256: (content: string) =>
|
|
46
|
+
Effect.promise(() => hashFn(content)).pipe(
|
|
47
|
+
Effect.map((hash) => hash as ContentHash)
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default implementation using Web Crypto API (available in all modern runtimes).
|
|
54
|
+
*
|
|
55
|
+
* @category Layers
|
|
56
|
+
*/
|
|
57
|
+
export const HashServiceLive: Layer.Layer<HashServiceTag> = makeHashServiceLive(
|
|
58
|
+
async (content: string) => {
|
|
59
|
+
const encoder = new TextEncoder()
|
|
60
|
+
const data = encoder.encode(content)
|
|
61
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data)
|
|
62
|
+
const hashArray = new Uint8Array(hashBuffer)
|
|
63
|
+
return Array.from(hashArray)
|
|
64
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
65
|
+
.join("")
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
10
69
|
/**
|
|
11
70
|
* Compute SHA256 hash of content.
|
|
12
71
|
*
|
|
@@ -15,5 +74,8 @@ import type { ContentHash } from "../Brand.js"
|
|
|
15
74
|
*
|
|
16
75
|
* @internal
|
|
17
76
|
*/
|
|
18
|
-
export const computeHash = (content: string): ContentHash =>
|
|
19
|
-
|
|
77
|
+
export const computeHash = (content: string): Effect.Effect<ContentHash, never, HashServiceTag> =>
|
|
78
|
+
Effect.gen(function*() {
|
|
79
|
+
const hashService = yield* HashServiceTag
|
|
80
|
+
return yield* hashService.computeSha256(content)
|
|
81
|
+
})
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local HTTP server for OAuth callback.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import * as HttpRouter from "@effect/platform/HttpRouter"
|
|
7
|
+
import * as HttpServer from "@effect/platform/HttpServer"
|
|
8
|
+
import type { ServeError } from "@effect/platform/HttpServerError"
|
|
9
|
+
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
10
|
+
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
11
|
+
import * as Context from "effect/Context"
|
|
12
|
+
import * as Deferred from "effect/Deferred"
|
|
13
|
+
import * as Effect from "effect/Effect"
|
|
14
|
+
import * as Fiber from "effect/Fiber"
|
|
15
|
+
import * as Layer from "effect/Layer"
|
|
16
|
+
import { OAuthError } from "../ConfluenceError.js"
|
|
17
|
+
|
|
18
|
+
const DEFAULT_PORT = 8585
|
|
19
|
+
const MAX_PORT_ATTEMPTS = 10
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Factory service for creating HTTP servers.
|
|
23
|
+
* This allows mocking the server creation in tests.
|
|
24
|
+
*
|
|
25
|
+
* @category Services
|
|
26
|
+
*/
|
|
27
|
+
export interface HttpServerFactory {
|
|
28
|
+
readonly createServerLayer: (port: number) => Layer.Layer<
|
|
29
|
+
HttpServer.HttpServer,
|
|
30
|
+
ServeError,
|
|
31
|
+
never
|
|
32
|
+
>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tag for the HttpServerFactory service.
|
|
37
|
+
*
|
|
38
|
+
* @category Services
|
|
39
|
+
*/
|
|
40
|
+
export class HttpServerFactoryTag extends Context.Tag("@knpkv/confluence-to-markdown/HttpServerFactory")<
|
|
41
|
+
HttpServerFactoryTag,
|
|
42
|
+
HttpServerFactory
|
|
43
|
+
>() {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a HttpServerFactory layer from a layer factory function.
|
|
47
|
+
* This allows injecting platform-specific implementations.
|
|
48
|
+
*
|
|
49
|
+
* @param createLayerFn - Function that creates HttpServer layer for a given port
|
|
50
|
+
* @returns Layer providing HttpServerFactory
|
|
51
|
+
*
|
|
52
|
+
* @category Layers
|
|
53
|
+
*/
|
|
54
|
+
export const makeHttpServerFactory = (
|
|
55
|
+
createLayerFn: (port: number) => Layer.Layer<HttpServer.HttpServer, ServeError, never>
|
|
56
|
+
): Layer.Layer<HttpServerFactoryTag> =>
|
|
57
|
+
Layer.succeed(HttpServerFactoryTag, {
|
|
58
|
+
createServerLayer: createLayerFn
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if a port is available by attempting to start a server.
|
|
63
|
+
*/
|
|
64
|
+
const isPortAvailable = (port: number): Effect.Effect<boolean, never, HttpServerFactoryTag> =>
|
|
65
|
+
Effect.gen(function*() {
|
|
66
|
+
const factory = yield* HttpServerFactoryTag
|
|
67
|
+
const serverLayer = factory.createServerLayer(port)
|
|
68
|
+
|
|
69
|
+
// Try to acquire and immediately release
|
|
70
|
+
const result = yield* Layer.build(serverLayer).pipe(
|
|
71
|
+
Effect.scoped,
|
|
72
|
+
Effect.as(true),
|
|
73
|
+
Effect.catchAll(() => Effect.succeed(false))
|
|
74
|
+
)
|
|
75
|
+
return result
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find an available port starting from the default.
|
|
80
|
+
*/
|
|
81
|
+
const findAvailablePort = (): Effect.Effect<number, OAuthError, HttpServerFactoryTag> =>
|
|
82
|
+
Effect.gen(function*() {
|
|
83
|
+
for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt++) {
|
|
84
|
+
const port = DEFAULT_PORT + attempt
|
|
85
|
+
const available = yield* isPortAvailable(port)
|
|
86
|
+
if (available) {
|
|
87
|
+
return port
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return yield* Effect.fail(
|
|
91
|
+
new OAuthError({
|
|
92
|
+
step: "authorize",
|
|
93
|
+
cause: `Could not find available port (tried ${DEFAULT_PORT}-${
|
|
94
|
+
DEFAULT_PORT + MAX_PORT_ATTEMPTS - 1
|
|
95
|
+
}). Close other applications using these ports.`
|
|
96
|
+
})
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Result from the OAuth callback server.
|
|
102
|
+
*/
|
|
103
|
+
export interface CallbackServerResult {
|
|
104
|
+
/** Promise that resolves with the authorization code */
|
|
105
|
+
readonly codePromise: Effect.Effect<string, OAuthError>
|
|
106
|
+
/** Shutdown the callback server */
|
|
107
|
+
readonly shutdown: Effect.Effect<void, never>
|
|
108
|
+
/** The port the server is listening on */
|
|
109
|
+
readonly port: number
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Start a local HTTP server to receive OAuth callback.
|
|
114
|
+
*
|
|
115
|
+
* @param expectedState - The state parameter to verify against CSRF
|
|
116
|
+
* @returns Server control interface with code promise, shutdown, and port
|
|
117
|
+
*
|
|
118
|
+
* @category OAuth
|
|
119
|
+
*/
|
|
120
|
+
export const startCallbackServer = (
|
|
121
|
+
expectedState: string
|
|
122
|
+
): Effect.Effect<CallbackServerResult, OAuthError, HttpServerFactoryTag> =>
|
|
123
|
+
Effect.gen(function*() {
|
|
124
|
+
const factory = yield* HttpServerFactoryTag
|
|
125
|
+
const port = yield* findAvailablePort()
|
|
126
|
+
const deferred = yield* Deferred.make<string, OAuthError>()
|
|
127
|
+
const readyDeferred = yield* Deferred.make<void, OAuthError>()
|
|
128
|
+
|
|
129
|
+
const app = HttpRouter.empty.pipe(
|
|
130
|
+
HttpRouter.get(
|
|
131
|
+
"/callback",
|
|
132
|
+
Effect.gen(function*() {
|
|
133
|
+
const req = yield* HttpServerRequest.HttpServerRequest
|
|
134
|
+
const url = new URL(req.url, `http://localhost:${port}`)
|
|
135
|
+
const code = url.searchParams.get("code")
|
|
136
|
+
const state = url.searchParams.get("state")
|
|
137
|
+
const error = url.searchParams.get("error")
|
|
138
|
+
const errorDescription = url.searchParams.get("error_description")
|
|
139
|
+
|
|
140
|
+
if (error) {
|
|
141
|
+
yield* Deferred.fail(
|
|
142
|
+
deferred,
|
|
143
|
+
new OAuthError({ step: "authorize", cause: errorDescription || error })
|
|
144
|
+
)
|
|
145
|
+
return HttpServerResponse.html(
|
|
146
|
+
"<html><body><h1>Authorization Failed</h1><p>You can close this window.</p></body></html>"
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (state !== expectedState) {
|
|
151
|
+
yield* Deferred.fail(
|
|
152
|
+
deferred,
|
|
153
|
+
new OAuthError({ step: "authorize", cause: "State mismatch - possible CSRF attack" })
|
|
154
|
+
)
|
|
155
|
+
return HttpServerResponse.html(
|
|
156
|
+
"<html><body><h1>Security Error</h1><p>State verification failed.</p></body></html>"
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!code) {
|
|
161
|
+
yield* Deferred.fail(
|
|
162
|
+
deferred,
|
|
163
|
+
new OAuthError({ step: "authorize", cause: "No authorization code received" })
|
|
164
|
+
)
|
|
165
|
+
return HttpServerResponse.html(
|
|
166
|
+
"<html><body><h1>Error</h1><p>No authorization code received.</p></body></html>"
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
yield* Deferred.succeed(deferred, code)
|
|
171
|
+
return HttpServerResponse.html(
|
|
172
|
+
"<html><body><h1>Success!</h1><p>You can close this window and return to the terminal.</p></body></html>"
|
|
173
|
+
)
|
|
174
|
+
})
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
const serverLayer = factory.createServerLayer(port)
|
|
179
|
+
|
|
180
|
+
const serverFiber = yield* HttpServer.serve(app).pipe(
|
|
181
|
+
Layer.provide(serverLayer),
|
|
182
|
+
Layer.build,
|
|
183
|
+
Effect.tap(() => Deferred.succeed(readyDeferred, undefined)),
|
|
184
|
+
Effect.tapError((err) => Deferred.fail(readyDeferred, new OAuthError({ step: "authorize", cause: err }))),
|
|
185
|
+
// Keep the layer alive until fiber is interrupted
|
|
186
|
+
Effect.flatMap(() => Effect.never),
|
|
187
|
+
Effect.scoped,
|
|
188
|
+
Effect.fork
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// Wait for server to be ready (or fail)
|
|
192
|
+
yield* Deferred.await(readyDeferred)
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
codePromise: Deferred.await(deferred),
|
|
196
|
+
shutdown: Fiber.interrupt(serverFiber).pipe(Effect.asVoid),
|
|
197
|
+
port
|
|
198
|
+
}
|
|
199
|
+
})
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
* @internal
|
|
6
6
|
*/
|
|
7
|
-
import * as
|
|
7
|
+
import * as Path from "@effect/platform/Path"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Convert a page title to a URL-safe slug.
|
|
@@ -31,8 +32,17 @@ export const slugify = (title: string): string => {
|
|
|
31
32
|
/**
|
|
32
33
|
* Convert a page to a local file path.
|
|
33
34
|
*
|
|
35
|
+
* Pages are always stored as `slug.md`. If a page has children,
|
|
36
|
+
* the children are stored in a `slug/` directory alongside the parent file.
|
|
37
|
+
*
|
|
38
|
+
* Example structure:
|
|
39
|
+
* - guide.md (page with children)
|
|
40
|
+
* - guide/ (directory for children)
|
|
41
|
+
* - getting-started.md
|
|
42
|
+
* - advanced.md
|
|
43
|
+
*
|
|
34
44
|
* @param title - The page title
|
|
35
|
-
* @param
|
|
45
|
+
* @param _hasChildren - Whether the page has child pages (unused, kept for API compat)
|
|
36
46
|
* @param parentPath - The parent directory path
|
|
37
47
|
* @returns The local file path for the page
|
|
38
48
|
*
|
|
@@ -40,14 +50,15 @@ export const slugify = (title: string): string => {
|
|
|
40
50
|
*/
|
|
41
51
|
export const pageToPath = (
|
|
42
52
|
title: string,
|
|
43
|
-
|
|
53
|
+
_hasChildren: boolean,
|
|
44
54
|
parentPath: string
|
|
45
|
-
): string =>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
55
|
+
): Effect.Effect<string, never, Path.Path> =>
|
|
56
|
+
Effect.gen(function*() {
|
|
57
|
+
const path = yield* Path.Path
|
|
58
|
+
const slug = slugify(title)
|
|
59
|
+
// Always use slug.md, children go in slug/ directory
|
|
60
|
+
return path.join(parentPath, `${slug}.md`)
|
|
61
|
+
})
|
|
51
62
|
|
|
52
63
|
/**
|
|
53
64
|
* Get the directory path for a page (used when creating children).
|
|
@@ -58,10 +69,15 @@ export const pageToPath = (
|
|
|
58
69
|
*
|
|
59
70
|
* @internal
|
|
60
71
|
*/
|
|
61
|
-
export const pageToDir = (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
export const pageToDir = (
|
|
73
|
+
title: string,
|
|
74
|
+
parentPath: string
|
|
75
|
+
): Effect.Effect<string, never, Path.Path> =>
|
|
76
|
+
Effect.gen(function*() {
|
|
77
|
+
const path = yield* Path.Path
|
|
78
|
+
const slug = slugify(title)
|
|
79
|
+
return path.join(parentPath, slug)
|
|
80
|
+
})
|
|
65
81
|
|
|
66
82
|
/**
|
|
67
83
|
* Extract page slug from a file path.
|
|
@@ -71,7 +87,8 @@ export const pageToDir = (title: string, parentPath: string): string => {
|
|
|
71
87
|
*
|
|
72
88
|
* @internal
|
|
73
89
|
*/
|
|
74
|
-
export const pathToSlug = (filePath: string): string =>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
export const pathToSlug = (filePath: string): Effect.Effect<string, never, Path.Path> =>
|
|
91
|
+
Effect.gen(function*() {
|
|
92
|
+
const path = yield* Path.Path
|
|
93
|
+
return path.basename(filePath, ".md")
|
|
94
|
+
})
|