@knpkv/confluence-to-markdown 0.2.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 +12 -0
- package/README.md +62 -0
- package/dist/Brand.d.ts +77 -0
- package/dist/Brand.d.ts.map +1 -0
- package/dist/Brand.js +44 -0
- package/dist/Brand.js.map +1 -0
- package/dist/ConfluenceClient.d.ts +140 -0
- package/dist/ConfluenceClient.d.ts.map +1 -0
- package/dist/ConfluenceClient.js +195 -0
- package/dist/ConfluenceClient.js.map +1 -0
- package/dist/ConfluenceConfig.d.ts +83 -0
- package/dist/ConfluenceConfig.d.ts.map +1 -0
- package/dist/ConfluenceConfig.js +122 -0
- package/dist/ConfluenceConfig.js.map +1 -0
- package/dist/ConfluenceError.d.ts +178 -0
- package/dist/ConfluenceError.d.ts.map +1 -0
- package/dist/ConfluenceError.js +131 -0
- package/dist/ConfluenceError.js.map +1 -0
- package/dist/LocalFileSystem.d.ts +85 -0
- package/dist/LocalFileSystem.d.ts.map +1 -0
- package/dist/LocalFileSystem.js +101 -0
- package/dist/LocalFileSystem.js.map +1 -0
- package/dist/MarkdownConverter.d.ts +50 -0
- package/dist/MarkdownConverter.d.ts.map +1 -0
- package/dist/MarkdownConverter.js +151 -0
- package/dist/MarkdownConverter.js.map +1 -0
- package/dist/Schemas.d.ts +225 -0
- package/dist/Schemas.d.ts.map +1 -0
- package/dist/Schemas.js +164 -0
- package/dist/Schemas.js.map +1 -0
- package/dist/SyncEngine.d.ts +132 -0
- package/dist/SyncEngine.d.ts.map +1 -0
- package/dist/SyncEngine.js +267 -0
- package/dist/SyncEngine.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +163 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/frontmatter.d.ts +38 -0
- package/dist/internal/frontmatter.d.ts.map +1 -0
- package/dist/internal/frontmatter.js +69 -0
- package/dist/internal/frontmatter.js.map +1 -0
- package/dist/internal/hashUtils.d.ts +11 -0
- package/dist/internal/hashUtils.d.ts.map +1 -0
- package/dist/internal/hashUtils.js +17 -0
- package/dist/internal/hashUtils.js.map +1 -0
- package/dist/internal/pathUtils.d.ts +41 -0
- package/dist/internal/pathUtils.d.ts.map +1 -0
- package/dist/internal/pathUtils.js +69 -0
- package/dist/internal/pathUtils.js.map +1 -0
- package/package.json +113 -0
- package/src/Brand.ts +104 -0
- package/src/ConfluenceClient.ts +387 -0
- package/src/ConfluenceConfig.ts +184 -0
- package/src/ConfluenceError.ts +193 -0
- package/src/LocalFileSystem.ts +225 -0
- package/src/MarkdownConverter.ts +187 -0
- package/src/Schemas.ts +235 -0
- package/src/SyncEngine.ts +429 -0
- package/src/bin.ts +269 -0
- package/src/index.ts +35 -0
- package/src/internal/frontmatter.ts +98 -0
- package/src/internal/hashUtils.ts +19 -0
- package/src/internal/pathUtils.ts +77 -0
- package/test/Brand.test.ts +72 -0
- package/test/MarkdownConverter.test.ts +108 -0
- package/test/Schemas.test.ts +99 -0
- package/tsconfig.json +32 -0
- package/vitest.config.integration.ts +12 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Front-matter parsing and serialization utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import { FrontMatterError } from "../ConfluenceError.js";
|
|
9
|
+
import type { NewPageFrontMatter, PageFrontMatter } from "../Schemas.js";
|
|
10
|
+
/**
|
|
11
|
+
* Parsed markdown file with front-matter.
|
|
12
|
+
*/
|
|
13
|
+
export interface ParsedMarkdown {
|
|
14
|
+
readonly frontMatter: PageFrontMatter | NewPageFrontMatter | null;
|
|
15
|
+
readonly content: string;
|
|
16
|
+
readonly isNew: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse a markdown file with YAML front-matter.
|
|
20
|
+
*
|
|
21
|
+
* @param filePath - Path to the file (for error messages)
|
|
22
|
+
* @param content - The file content
|
|
23
|
+
* @returns Parsed markdown with front-matter and content
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export declare const parseMarkdown: (filePath: string, content: string) => Effect.Effect<ParsedMarkdown, FrontMatterError>;
|
|
28
|
+
/**
|
|
29
|
+
* Serialize markdown with YAML front-matter.
|
|
30
|
+
*
|
|
31
|
+
* @param frontMatter - The front-matter data
|
|
32
|
+
* @param content - The markdown content
|
|
33
|
+
* @returns The serialized markdown file content
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare const serializeMarkdown: (frontMatter: PageFrontMatter, content: string) => string;
|
|
38
|
+
//# sourceMappingURL=frontmatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/internal/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAGxE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,eAAe,GAAG,kBAAkB,GAAG,IAAI,CAAA;IACjE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CACxB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,MAAM,EAChB,SAAS,MAAM,KACd,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAqC7C,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAC5B,aAAa,eAAe,EAC5B,SAAS,MAAM,KACd,MAYF,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Front-matter parsing and serialization utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Schema from "effect/Schema";
|
|
9
|
+
import matter from "gray-matter";
|
|
10
|
+
import { FrontMatterError } from "../ConfluenceError.js";
|
|
11
|
+
import { NewPageFrontMatterSchema, PageFrontMatterSchema } from "../Schemas.js";
|
|
12
|
+
/**
|
|
13
|
+
* Parse a markdown file with YAML front-matter.
|
|
14
|
+
*
|
|
15
|
+
* @param filePath - Path to the file (for error messages)
|
|
16
|
+
* @param content - The file content
|
|
17
|
+
* @returns Parsed markdown with front-matter and content
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export const parseMarkdown = (filePath, content) => Effect.gen(function* () {
|
|
22
|
+
const parsed = yield* Effect.try({
|
|
23
|
+
try: () => matter(content),
|
|
24
|
+
catch: (cause) => new FrontMatterError({ path: filePath, cause })
|
|
25
|
+
});
|
|
26
|
+
// If no front-matter or empty, treat as new page
|
|
27
|
+
if (!parsed.data || Object.keys(parsed.data).length === 0) {
|
|
28
|
+
return {
|
|
29
|
+
frontMatter: null,
|
|
30
|
+
content: parsed.content.trim(),
|
|
31
|
+
isNew: true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// Try to parse as existing page front-matter
|
|
35
|
+
const existingResult = yield* Schema.decodeUnknown(PageFrontMatterSchema)(parsed.data).pipe(Effect.map((fm) => ({
|
|
36
|
+
frontMatter: fm,
|
|
37
|
+
content: parsed.content.trim(),
|
|
38
|
+
isNew: false
|
|
39
|
+
})), Effect.catchAll(() =>
|
|
40
|
+
// Try to parse as new page front-matter
|
|
41
|
+
Schema.decodeUnknown(NewPageFrontMatterSchema)(parsed.data).pipe(Effect.map((fm) => ({
|
|
42
|
+
frontMatter: fm,
|
|
43
|
+
content: parsed.content.trim(),
|
|
44
|
+
isNew: true
|
|
45
|
+
})), Effect.catchAll((cause) => Effect.fail(new FrontMatterError({ path: filePath, cause }))))));
|
|
46
|
+
return existingResult;
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* Serialize markdown with YAML front-matter.
|
|
50
|
+
*
|
|
51
|
+
* @param frontMatter - The front-matter data
|
|
52
|
+
* @param content - The markdown content
|
|
53
|
+
* @returns The serialized markdown file content
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export const serializeMarkdown = (frontMatter, content) => {
|
|
58
|
+
const fm = {
|
|
59
|
+
pageId: frontMatter.pageId,
|
|
60
|
+
version: frontMatter.version,
|
|
61
|
+
title: frontMatter.title,
|
|
62
|
+
updated: frontMatter.updated.toISOString(),
|
|
63
|
+
...(frontMatter.parentId !== undefined ? { parentId: frontMatter.parentId } : {}),
|
|
64
|
+
...(frontMatter.position !== undefined ? { position: frontMatter.position } : {}),
|
|
65
|
+
contentHash: frontMatter.contentHash
|
|
66
|
+
};
|
|
67
|
+
return matter.stringify(content, fm);
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/internal/frontmatter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ContentHash } from "../Brand.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compute SHA256 hash of content.
|
|
4
|
+
*
|
|
5
|
+
* @param content - The content to hash
|
|
6
|
+
* @returns The hex-encoded SHA256 hash
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare const computeHash: (content: string) => ContentHash;
|
|
11
|
+
//# sourceMappingURL=hashUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hashUtils.d.ts","sourceRoot":"","sources":["../../src/internal/hashUtils.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,KAAG,WACoC,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content hashing utilities for change detection.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import * as crypto from "node:crypto";
|
|
8
|
+
/**
|
|
9
|
+
* Compute SHA256 hash of content.
|
|
10
|
+
*
|
|
11
|
+
* @param content - The content to hash
|
|
12
|
+
* @returns The hex-encoded SHA256 hash
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export const computeHash = (content) => crypto.createHash("sha256").update(content, "utf8").digest("hex");
|
|
17
|
+
//# sourceMappingURL=hashUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hashUtils.js","sourceRoot":"","sources":["../../src/internal/hashUtils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAGrC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAAe,EAAE,CAC1D,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAgB,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a page title to a URL-safe slug.
|
|
3
|
+
* Prevents path traversal by only allowing alphanumeric characters and hyphens.
|
|
4
|
+
*
|
|
5
|
+
* @param title - The page title
|
|
6
|
+
* @returns A slugified version of the title
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare const slugify: (title: string) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Convert a page to a local file path.
|
|
13
|
+
*
|
|
14
|
+
* @param title - The page title
|
|
15
|
+
* @param hasChildren - Whether the page has child pages
|
|
16
|
+
* @param parentPath - The parent directory path
|
|
17
|
+
* @returns The local file path for the page
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export declare const pageToPath: (title: string, hasChildren: boolean, parentPath: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Get the directory path for a page (used when creating children).
|
|
24
|
+
*
|
|
25
|
+
* @param title - The page title
|
|
26
|
+
* @param parentPath - The parent directory path
|
|
27
|
+
* @returns The directory path for the page's children
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export declare const pageToDir: (title: string, parentPath: string) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Extract page slug from a file path.
|
|
34
|
+
*
|
|
35
|
+
* @param filePath - The file path
|
|
36
|
+
* @returns The page slug
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare const pathToSlug: (filePath: string) => string;
|
|
41
|
+
//# sourceMappingURL=pathUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathUtils.d.ts","sourceRoot":"","sources":["../../src/internal/pathUtils.ts"],"names":[],"mappings":"AAQA;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAWvC,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GACrB,OAAO,MAAM,EACb,aAAa,OAAO,EACpB,YAAY,MAAM,KACjB,MAKF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,EAAE,YAAY,MAAM,KAAG,MAG7D,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAI,UAAU,MAAM,KAAG,MAG7C,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utilities for converting page titles to file paths.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
/**
|
|
9
|
+
* Convert a page title to a URL-safe slug.
|
|
10
|
+
* Prevents path traversal by only allowing alphanumeric characters and hyphens.
|
|
11
|
+
*
|
|
12
|
+
* @param title - The page title
|
|
13
|
+
* @returns A slugified version of the title
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export const slugify = (title) => {
|
|
18
|
+
const slug = title
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.normalize("NFD")
|
|
21
|
+
.replace(/[\u0300-\u036f]/g, "") // Remove diacritics
|
|
22
|
+
.replace(/[^a-z0-9]+/g, "-") // Replace non-alphanumeric with hyphens (prevents ../ path traversal)
|
|
23
|
+
.replace(/^-+|-+$/g, "") // Trim leading/trailing hyphens
|
|
24
|
+
.substring(0, 100); // Limit length
|
|
25
|
+
// Ensure we have a valid slug (not empty after sanitization)
|
|
26
|
+
return slug || "untitled";
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Convert a page to a local file path.
|
|
30
|
+
*
|
|
31
|
+
* @param title - The page title
|
|
32
|
+
* @param hasChildren - Whether the page has child pages
|
|
33
|
+
* @param parentPath - The parent directory path
|
|
34
|
+
* @returns The local file path for the page
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export const pageToPath = (title, hasChildren, parentPath) => {
|
|
39
|
+
const slug = slugify(title);
|
|
40
|
+
return hasChildren
|
|
41
|
+
? path.join(parentPath, slug, "index.md")
|
|
42
|
+
: path.join(parentPath, `${slug}.md`);
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Get the directory path for a page (used when creating children).
|
|
46
|
+
*
|
|
47
|
+
* @param title - The page title
|
|
48
|
+
* @param parentPath - The parent directory path
|
|
49
|
+
* @returns The directory path for the page's children
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
export const pageToDir = (title, parentPath) => {
|
|
54
|
+
const slug = slugify(title);
|
|
55
|
+
return path.join(parentPath, slug);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Extract page slug from a file path.
|
|
59
|
+
*
|
|
60
|
+
* @param filePath - The file path
|
|
61
|
+
* @returns The page slug
|
|
62
|
+
*
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
export const pathToSlug = (filePath) => {
|
|
66
|
+
const basename = path.basename(filePath, ".md");
|
|
67
|
+
return basename === "index" ? path.basename(path.dirname(filePath)) : basename;
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=pathUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathUtils.js","sourceRoot":"","sources":["../../src/internal/pathUtils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAa,EAAU,EAAE;IAC/C,MAAM,IAAI,GAAG,KAAK;SACf,WAAW,EAAE;SACb,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,oBAAoB;SACpD,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,sEAAsE;SAClG,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,gCAAgC;SACxD,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA,CAAC,eAAe;IAEpC,6DAA6D;IAC7D,OAAO,IAAI,IAAI,UAAU,CAAA;AAC3B,CAAC,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,KAAa,EACb,WAAoB,EACpB,UAAkB,EACV,EAAE;IACV,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAC3B,OAAO,WAAW;QAChB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC;QACzC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,KAAK,CAAC,CAAA;AACzC,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,UAAkB,EAAU,EAAE;IACrE,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;AACpC,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,QAAgB,EAAU,EAAE;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAC/C,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;AAChF,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@knpkv/confluence-to-markdown",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Sync Confluence Cloud pages to local GitHub Flavored Markdown files",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "knpkv",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/knpkv/npm.git",
|
|
10
|
+
"directory": "packages/confluence-to-markdown"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/knpkv/npm/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/knpkv/npm/tree/main/packages/confluence-to-markdown#readme",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"bin": {
|
|
21
|
+
"confluence": "./dist/bin.js"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./Brand": {
|
|
29
|
+
"import": "./dist/Brand.js",
|
|
30
|
+
"types": "./dist/Brand.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./Schemas": {
|
|
33
|
+
"import": "./dist/Schemas.js",
|
|
34
|
+
"types": "./dist/Schemas.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./ConfluenceError": {
|
|
37
|
+
"import": "./dist/ConfluenceError.js",
|
|
38
|
+
"types": "./dist/ConfluenceError.d.ts"
|
|
39
|
+
},
|
|
40
|
+
"./ConfluenceConfig": {
|
|
41
|
+
"import": "./dist/ConfluenceConfig.js",
|
|
42
|
+
"types": "./dist/ConfluenceConfig.d.ts"
|
|
43
|
+
},
|
|
44
|
+
"./ConfluenceAuth": {
|
|
45
|
+
"import": "./dist/ConfluenceAuth.js",
|
|
46
|
+
"types": "./dist/ConfluenceAuth.d.ts"
|
|
47
|
+
},
|
|
48
|
+
"./ConfluenceClient": {
|
|
49
|
+
"import": "./dist/ConfluenceClient.js",
|
|
50
|
+
"types": "./dist/ConfluenceClient.d.ts"
|
|
51
|
+
},
|
|
52
|
+
"./MarkdownConverter": {
|
|
53
|
+
"import": "./dist/MarkdownConverter.js",
|
|
54
|
+
"types": "./dist/MarkdownConverter.d.ts"
|
|
55
|
+
},
|
|
56
|
+
"./LocalFileSystem": {
|
|
57
|
+
"import": "./dist/LocalFileSystem.js",
|
|
58
|
+
"types": "./dist/LocalFileSystem.d.ts"
|
|
59
|
+
},
|
|
60
|
+
"./SyncEngine": {
|
|
61
|
+
"import": "./dist/SyncEngine.js",
|
|
62
|
+
"types": "./dist/SyncEngine.d.ts"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsc",
|
|
70
|
+
"clean": "rimraf dist dist-test .tsbuildinfo",
|
|
71
|
+
"test": "vitest run",
|
|
72
|
+
"test:watch": "vitest",
|
|
73
|
+
"test:integration": "vitest run --config vitest.config.integration.ts",
|
|
74
|
+
"lint": "eslint src",
|
|
75
|
+
"lint:fix": "eslint src --fix",
|
|
76
|
+
"check": "tsc --noEmit",
|
|
77
|
+
"link": "npm link"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"effect": "^3.19.3"
|
|
81
|
+
},
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"@effect/cli": "^0.61.0",
|
|
84
|
+
"@effect/platform": "^0.93.0",
|
|
85
|
+
"@effect/platform-node": "^0.87.0",
|
|
86
|
+
"gray-matter": "^4.0.3",
|
|
87
|
+
"rehype-parse": "^9.0.1",
|
|
88
|
+
"rehype-remark": "^10.0.1",
|
|
89
|
+
"rehype-stringify": "^10.0.1",
|
|
90
|
+
"remark-gfm": "^4.0.1",
|
|
91
|
+
"remark-parse": "^11.0.0",
|
|
92
|
+
"remark-rehype": "^11.1.1",
|
|
93
|
+
"remark-stringify": "^11.0.0",
|
|
94
|
+
"unified": "^11.0.5"
|
|
95
|
+
},
|
|
96
|
+
"devDependencies": {
|
|
97
|
+
"@effect/vitest": "^0.27.0",
|
|
98
|
+
"@types/node": "^22.15.21",
|
|
99
|
+
"effect": "^3.19.3",
|
|
100
|
+
"typescript": "~5.9.0",
|
|
101
|
+
"vitest": "^4.0.13"
|
|
102
|
+
},
|
|
103
|
+
"keywords": [
|
|
104
|
+
"effect",
|
|
105
|
+
"effect-ts",
|
|
106
|
+
"confluence",
|
|
107
|
+
"markdown",
|
|
108
|
+
"sync",
|
|
109
|
+
"atlassian",
|
|
110
|
+
"documentation",
|
|
111
|
+
"cli"
|
|
112
|
+
]
|
|
113
|
+
}
|
package/src/Brand.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded types for type-safe Confluence identifiers.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import * as Brand from "effect/Brand"
|
|
7
|
+
import * as Schema from "effect/Schema"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Branded type for Confluence page IDs.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { PageId } from "@knpkv/confluence-to-markdown/Brand"
|
|
15
|
+
*
|
|
16
|
+
* const id = PageId("12345") // Valid
|
|
17
|
+
* const invalid = PageId("") // Throws BrandErrors
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @category Brand
|
|
21
|
+
*/
|
|
22
|
+
export type PageId = string & Brand.Brand<"PageId">
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Refined brand constructor for PageId.
|
|
26
|
+
*
|
|
27
|
+
* @category Brand
|
|
28
|
+
*/
|
|
29
|
+
export const PageId = Brand.refined<PageId>(
|
|
30
|
+
(s): s is PageId => typeof s === "string" && s.length > 0,
|
|
31
|
+
(s) => Brand.error(`Invalid page ID: "${s}" (must be non-empty string)`)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Schema for PageId validation and parsing.
|
|
36
|
+
*
|
|
37
|
+
* @category Schema
|
|
38
|
+
*/
|
|
39
|
+
export const PageIdSchema = Schema.String.pipe(
|
|
40
|
+
Schema.nonEmptyString(),
|
|
41
|
+
Schema.brand("PageId")
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Branded type for Confluence space keys.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import { SpaceKey } from "@knpkv/confluence-to-markdown/Brand"
|
|
50
|
+
*
|
|
51
|
+
* const key = SpaceKey("DOCS") // Valid
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @category Brand
|
|
55
|
+
*/
|
|
56
|
+
export type SpaceKey = string & Brand.Brand<"SpaceKey">
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Refined brand constructor for SpaceKey.
|
|
60
|
+
*
|
|
61
|
+
* @category Brand
|
|
62
|
+
*/
|
|
63
|
+
export const SpaceKey = Brand.refined<SpaceKey>(
|
|
64
|
+
(s): s is SpaceKey => typeof s === "string" && s.length > 0 && /^[A-Z0-9]+$/.test(s),
|
|
65
|
+
(s) => Brand.error(`Invalid space key: "${s}" (must be uppercase alphanumeric)`)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Schema for SpaceKey validation.
|
|
70
|
+
*
|
|
71
|
+
* @category Schema
|
|
72
|
+
*/
|
|
73
|
+
export const SpaceKeySchema = Schema.String.pipe(
|
|
74
|
+
Schema.nonEmptyString(),
|
|
75
|
+
Schema.pattern(/^[A-Z0-9]+$/),
|
|
76
|
+
Schema.brand("SpaceKey")
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Branded type for content hash (SHA256).
|
|
81
|
+
*
|
|
82
|
+
* @category Brand
|
|
83
|
+
*/
|
|
84
|
+
export type ContentHash = string & Brand.Brand<"ContentHash">
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Refined brand constructor for ContentHash.
|
|
88
|
+
*
|
|
89
|
+
* @category Brand
|
|
90
|
+
*/
|
|
91
|
+
export const ContentHash = Brand.refined<ContentHash>(
|
|
92
|
+
(s): s is ContentHash => typeof s === "string" && /^[a-f0-9]{64}$/.test(s),
|
|
93
|
+
(s) => Brand.error(`Invalid content hash: "${s}" (must be 64-char hex string)`)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Schema for ContentHash validation.
|
|
98
|
+
*
|
|
99
|
+
* @category Schema
|
|
100
|
+
*/
|
|
101
|
+
export const ContentHashSchema = Schema.String.pipe(
|
|
102
|
+
Schema.pattern(/^[a-f0-9]{64}$/),
|
|
103
|
+
Schema.brand("ContentHash")
|
|
104
|
+
)
|