@vdfor/confluence-sync 0.3.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.
Potentially problematic release.
This version of @vdfor/confluence-sync might be problematic. Click here for more details.
- package/README.md +38 -26
- package/package.json +3 -2
- package/skills/confluence-sync.md +21 -25
- package/src/confluence.mjs +216 -99
- package/src/fonts/SourceHanSansSC-VF.ttf +0 -0
- package/src/fonts/default.profraw +0 -0
- package/upload.mjs +42 -53
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @vdfor/confluence-sync
|
|
2
2
|
|
|
3
|
-
Confluence 文档同步 CLI 工具,支持将本地 Markdown 文件上传至 Confluence 并自动转换为 Confluence
|
|
3
|
+
Confluence 文档同步 CLI 工具,支持将本地 Markdown 文件上传至 Confluence 并自动转换为 Confluence 页面格式。支持 Mermaid 图表自动渲染为图片。
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
@@ -20,12 +20,9 @@ yarn global add @vdfor/confluence-sync
|
|
|
20
20
|
### 方式1:环境变量
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
#
|
|
23
|
+
# Confluence 服务地址
|
|
24
24
|
export CONFLUENCE_BASE_URL="https://your-confluence-instance.com"
|
|
25
25
|
|
|
26
|
-
# 必填:Space Key
|
|
27
|
-
export CONFLUENCE_SPACE_KEY="YOUR_SPACE_KEY"
|
|
28
|
-
|
|
29
26
|
# 认证方式(二选一)
|
|
30
27
|
# 方式1(推荐):Personal Access Token
|
|
31
28
|
export CONFLUENCE_PAT="your-personal-access-token"
|
|
@@ -38,24 +35,23 @@ export CONFLUENCE_API_TOKEN="your-api-token"
|
|
|
38
35
|
### 方式2:CLI 参数(优先于环境变量)
|
|
39
36
|
|
|
40
37
|
```bash
|
|
41
|
-
confluence-sync upload --file test.md \
|
|
38
|
+
confluence-sync upload --file test.md --parent 123456 \
|
|
42
39
|
--base-url https://your-confluence-instance.com \
|
|
43
|
-
--space-key YOUR_SPACE_KEY \
|
|
44
40
|
--pat your-token
|
|
45
41
|
```
|
|
46
42
|
|
|
47
43
|
## 命令
|
|
48
44
|
|
|
49
|
-
### upload - 上传 Markdown
|
|
45
|
+
### upload - 上传 Markdown 为新页面(已存在同名页面则自动更新)
|
|
50
46
|
|
|
51
47
|
```bash
|
|
52
|
-
confluence-sync upload --file <path>
|
|
48
|
+
confluence-sync upload --file <path> --parent <页面ID> [--title <标题>] [--dry-run] [--no-mermaid-to-img]
|
|
53
49
|
```
|
|
54
50
|
|
|
55
51
|
### update - 更新已有页面
|
|
56
52
|
|
|
57
53
|
```bash
|
|
58
|
-
confluence-sync update --file <path>
|
|
54
|
+
confluence-sync update --file <path> --page-id <ID> [--minor-edit] [--no-mermaid-to-img]
|
|
59
55
|
```
|
|
60
56
|
|
|
61
57
|
### upload-file - 上传附件
|
|
@@ -64,10 +60,10 @@ confluence-sync update --file <path> (--page-id <ID> | --title <标题>) [--spac
|
|
|
64
60
|
confluence-sync upload-file --page-id <ID> --file <path> [--name <文件名>]
|
|
65
61
|
```
|
|
66
62
|
|
|
67
|
-
### list-pages -
|
|
63
|
+
### list-pages - 列出 parent 下的子页面
|
|
68
64
|
|
|
69
65
|
```bash
|
|
70
|
-
confluence-sync list-pages
|
|
66
|
+
confluence-sync list-pages --parent <页面ID>
|
|
71
67
|
```
|
|
72
68
|
|
|
73
69
|
### list-files - 列出页面附件
|
|
@@ -84,29 +80,44 @@ confluence-sync get-content --page-id <ID> [--markdown]
|
|
|
84
80
|
|
|
85
81
|
使用 `--markdown` 参数可将 Confluence 内容转换为 Markdown 格式输出。
|
|
86
82
|
|
|
83
|
+
## Mermaid 图表支持
|
|
84
|
+
|
|
85
|
+
Markdown 中的 ```` ```mermaid ```` 代码块会自动通过 [sebastianjs](https://github.com/creadri/sebastianjs) 渲染为 PNG 图片(本地离线渲染,无需外网、无需浏览器),上传后页面中显示为图片附件。
|
|
86
|
+
|
|
87
|
+
- 默认自动检测并转换,无 Mermaid 块时不影响正常流程
|
|
88
|
+
- Mermaid 渲染失败时保留原始代码块(降级处理,不阻塞上传)
|
|
89
|
+
- 使用 `--no-mermaid-to-img` 可禁用自动渲染
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# 自动处理 mermaid(默认开启)
|
|
93
|
+
confluence-sync upload --file README.md --parent 27263539
|
|
94
|
+
|
|
95
|
+
# 禁用 mermaid 渲染
|
|
96
|
+
confluence-sync upload --file README.md --parent 27263539 --no-mermaid-to-img
|
|
97
|
+
```
|
|
98
|
+
|
|
87
99
|
## 配置参数
|
|
88
100
|
|
|
89
101
|
| 参数 | 说明 |
|
|
90
102
|
|------|------|
|
|
91
|
-
| `--base-url` | Confluence
|
|
92
|
-
| `--
|
|
93
|
-
| `--
|
|
94
|
-
| `--
|
|
95
|
-
| `--api-token` | API Token 或密码 |
|
|
96
|
-
| `--space` | Space Key(覆盖默认值) |
|
|
103
|
+
| `--base-url` | Confluence 服务地址(覆盖环境变量) |
|
|
104
|
+
| `--pat` | Personal Access Token(覆盖环境变量) |
|
|
105
|
+
| `--username` | Confluence 登录名(覆盖环境变量) |
|
|
106
|
+
| `--api-token` | API Token 或密码(覆盖环境变量) |
|
|
97
107
|
| `--file` | 本地文件路径 |
|
|
98
|
-
| `--title` |
|
|
99
|
-
| `--
|
|
100
|
-
| `--
|
|
108
|
+
| `--title` | 页面标题(upload 时可选,默认取文件名) |
|
|
109
|
+
| `--parent` | 父页面 ID(数字,upload / list-pages 必填) |
|
|
110
|
+
| `--page-id` | 页面 ID(update / upload-file / list-files / get-content 必填) |
|
|
101
111
|
| `--dry-run` | 仅打印转换结果,不实际上传 |
|
|
102
112
|
| `--minor-edit` | 标记为小修改(不通知关注者) |
|
|
103
113
|
| `--markdown` | get-content 时输出为 Markdown 格式 |
|
|
114
|
+
| `--no-mermaid-to-img` | 禁用 Mermaid 图表自动渲染 |
|
|
104
115
|
|
|
105
116
|
## 示例
|
|
106
117
|
|
|
107
118
|
```bash
|
|
108
|
-
# 上传 Markdown
|
|
109
|
-
confluence-sync upload --file README.md --title "项目文档" --
|
|
119
|
+
# 上传 Markdown 文件(自动按 title 匹配 parent 下已有页面则更新)
|
|
120
|
+
confluence-sync upload --file README.md --title "项目文档" --parent 27263539
|
|
110
121
|
|
|
111
122
|
# 更新已有页面
|
|
112
123
|
confluence-sync update --file README.md --page-id 123456
|
|
@@ -114,8 +125,8 @@ confluence-sync update --file README.md --page-id 123456
|
|
|
114
125
|
# 上传附件
|
|
115
126
|
confluence-sync upload-file --page-id 123456 --file image.png
|
|
116
127
|
|
|
117
|
-
#
|
|
118
|
-
confluence-sync list-pages --
|
|
128
|
+
# 列出 parent 下的子页面
|
|
129
|
+
confluence-sync list-pages --parent 27263539
|
|
119
130
|
|
|
120
131
|
# 获取页面内容
|
|
121
132
|
confluence-sync get-content --page-id 123456
|
|
@@ -139,7 +150,8 @@ pnpm add -g @vdfor/confluence-sync
|
|
|
139
150
|
安装后,智能体可直接调用 `confluence-sync` 命令:
|
|
140
151
|
|
|
141
152
|
```bash
|
|
142
|
-
confluence-sync upload --file README.md --title "项目文档" --
|
|
153
|
+
confluence-sync upload --file README.md --title "项目文档" --parent 27263539
|
|
154
|
+
confluence-sync update --file README.md --page-id 123456
|
|
143
155
|
confluence-sync get-content --page-id 123456 --markdown
|
|
144
156
|
```
|
|
145
157
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vdfor/confluence-sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"author": "vdfor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"axios": "^1.7.0",
|
|
31
31
|
"form-data": "^4.0.0",
|
|
32
|
-
"marked": "^15.0.0"
|
|
32
|
+
"marked": "^15.0.0",
|
|
33
|
+
"sebastianjs": "^0.4.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"rimraf": "^6.1.3"
|
|
@@ -1,48 +1,43 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "confluence-sync"
|
|
3
|
-
description: "Confluence 文档同步 CLI 工具,支持将本地 Markdown 文件上传至 Confluence 并自动转换为 Confluence
|
|
3
|
+
description: "Confluence 文档同步 CLI 工具,支持将本地 Markdown 文件上传至 Confluence 并自动转换为 Confluence 页面格式,支持 Mermaid 图表自动渲染。当用户需要上传、更新、获取 Confluence 页面内容或管理附件时调用。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Confluence Sync
|
|
7
7
|
|
|
8
|
-
Confluence 文档同步 CLI 工具,支持将本地 Markdown 文件上传至 Confluence 并自动转换为 Confluence
|
|
8
|
+
Confluence 文档同步 CLI 工具,支持将本地 Markdown 文件上传至 Confluence 并自动转换为 Confluence 页面格式。支持 Mermaid 图表自动渲染为图片。
|
|
9
9
|
|
|
10
10
|
## 功能特性
|
|
11
11
|
|
|
12
|
-
- 上传 Markdown 文件为 Confluence
|
|
12
|
+
- 上传 Markdown 文件为 Confluence 页面(已存在同名页面则自动更新)
|
|
13
13
|
- 更新已有页面内容
|
|
14
14
|
- 上传文件作为页面附件
|
|
15
|
-
-
|
|
15
|
+
- 列出 parent 下的子页面
|
|
16
|
+
- 列出页面附件
|
|
16
17
|
- 获取页面内容(支持转换为 Markdown 格式)
|
|
18
|
+
- Mermaid 图表自动渲染为 PNG 图片
|
|
17
19
|
|
|
18
20
|
## 配置要求
|
|
19
21
|
|
|
20
22
|
使用前需要配置以下环境变量或 CLI 参数:
|
|
21
23
|
|
|
22
|
-
### 必填配置
|
|
23
|
-
|
|
24
24
|
- `CONFLUENCE_BASE_URL`: Confluence 服务地址
|
|
25
|
-
- `CONFLUENCE_SPACE_KEY`: Space Key
|
|
26
25
|
- 认证方式(二选一):
|
|
27
26
|
- `CONFLUENCE_PAT`: Personal Access Token(推荐)
|
|
28
27
|
- `CONFLUENCE_USERNAME` + `CONFLUENCE_API_TOKEN`: Basic Auth 认证
|
|
29
28
|
|
|
30
|
-
### 可选配置
|
|
31
|
-
|
|
32
|
-
- `--space`: Space Key(覆盖默认值)
|
|
33
|
-
|
|
34
29
|
## 命令列表
|
|
35
30
|
|
|
36
|
-
### upload - 上传 Markdown
|
|
31
|
+
### upload - 上传 Markdown 为新页面(已存在同名页面则自动更新)
|
|
37
32
|
|
|
38
33
|
```bash
|
|
39
|
-
confluence-sync upload --file <path>
|
|
34
|
+
confluence-sync upload --file <path> --parent <页面ID> [--title <标题>] [--dry-run] [--no-mermaid-to-img]
|
|
40
35
|
```
|
|
41
36
|
|
|
42
37
|
### update - 更新已有页面
|
|
43
38
|
|
|
44
39
|
```bash
|
|
45
|
-
confluence-sync update --file <path>
|
|
40
|
+
confluence-sync update --file <path> --page-id <ID> [--minor-edit] [--no-mermaid-to-img]
|
|
46
41
|
```
|
|
47
42
|
|
|
48
43
|
### upload-file - 上传附件
|
|
@@ -51,10 +46,10 @@ confluence-sync update --file <path> (--page-id <ID> | --title <标题>) [--spac
|
|
|
51
46
|
confluence-sync upload-file --page-id <ID> --file <path> [--name <文件名>]
|
|
52
47
|
```
|
|
53
48
|
|
|
54
|
-
### list-pages -
|
|
49
|
+
### list-pages - 列出 parent 下的子页面
|
|
55
50
|
|
|
56
51
|
```bash
|
|
57
|
-
confluence-sync list-pages
|
|
52
|
+
confluence-sync list-pages --parent <页面ID>
|
|
58
53
|
```
|
|
59
54
|
|
|
60
55
|
### list-files - 列出页面附件
|
|
@@ -71,25 +66,26 @@ confluence-sync get-content --page-id <ID> [--markdown]
|
|
|
71
66
|
|
|
72
67
|
使用 `--markdown` 参数可将 Confluence 内容转换为 Markdown 格式输出。
|
|
73
68
|
|
|
69
|
+
## Mermaid 支持
|
|
70
|
+
|
|
71
|
+
Markdown 中的 ```` ```mermaid ```` 代码块自动通过 sebastianjs 渲染为 PNG 图片(本地离线渲染,无需外网、无需浏览器),作为附件上传。默认开启,使用 `--no-mermaid-to-img` 可禁用。渲染失败时保留原始代码块,不阻塞上传。
|
|
72
|
+
|
|
74
73
|
## 使用示例
|
|
75
74
|
|
|
76
75
|
```bash
|
|
77
|
-
#
|
|
78
|
-
confluence-sync upload --file README.md --title "项目文档" --
|
|
76
|
+
# 上传(--parent 必填,已存在同名子页面则自动更新)
|
|
77
|
+
confluence-sync upload --file README.md --title "项目文档" --parent 27263539
|
|
79
78
|
|
|
80
|
-
#
|
|
79
|
+
# 更新(--page-id 必填)
|
|
81
80
|
confluence-sync update --file README.md --page-id 123456
|
|
82
81
|
|
|
82
|
+
# 列出 parent 下的子页面
|
|
83
|
+
confluence-sync list-pages --parent 27263539
|
|
84
|
+
|
|
83
85
|
# 上传附件
|
|
84
86
|
confluence-sync upload-file --page-id 123456 --file image.png
|
|
85
87
|
|
|
86
|
-
# 列出空间所有页面
|
|
87
|
-
confluence-sync list-pages --space MYSPACE
|
|
88
|
-
|
|
89
88
|
# 获取页面内容
|
|
90
|
-
confluence-sync get-content --page-id 123456
|
|
91
|
-
|
|
92
|
-
# 获取页面内容并转换为 Markdown
|
|
93
89
|
confluence-sync get-content --page-id 123456 --markdown
|
|
94
90
|
```
|
|
95
91
|
|
package/src/confluence.mjs
CHANGED
|
@@ -1,10 +1,113 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { marked } from 'marked';
|
|
3
|
-
import { extname } from 'node:path';
|
|
3
|
+
import { extname, dirname, join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { writeFile, unlink } from 'node:fs/promises';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
// ── Mermaid 处理 ─────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
let sebastianRenderer = null;
|
|
11
|
+
let cjkFontRegistry = null;
|
|
12
|
+
|
|
13
|
+
/** 项目内置 CJK 字体(思源黑体可变) */
|
|
14
|
+
const PROJECT_FONT_DIR = join(dirname(fileURLToPath(import.meta.url)), 'fonts');
|
|
15
|
+
const CJK_FONT_FILE = join(PROJECT_FONT_DIR, 'SourceHanSansSC-VF.ttf');
|
|
16
|
+
|
|
17
|
+
async function getSebastianRenderer() {
|
|
18
|
+
if (!sebastianRenderer) {
|
|
19
|
+
const sebastian = await import('sebastianjs');
|
|
20
|
+
sebastianRenderer = { renderPng: sebastian.renderPng, dispose: sebastian.dispose };
|
|
21
|
+
}
|
|
22
|
+
return sebastianRenderer;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 创建注册了项目内置 CJK 字体的 FontRegistry
|
|
27
|
+
* mermaid 节点标题默认用 bold,所以需要同时覆盖 regular + bold + italic
|
|
28
|
+
*/
|
|
29
|
+
async function getCjkFontRegistry() {
|
|
30
|
+
if (cjkFontRegistry) return cjkFontRegistry;
|
|
31
|
+
|
|
32
|
+
const { FontRegistry } = await import('sebastianjs/src/geometry/fonts.js');
|
|
33
|
+
const registry = new FontRegistry();
|
|
34
|
+
|
|
35
|
+
// 注册思源黑体 SC 可变字体,覆盖所有字重(variable font 可以自己插值不同 weight)
|
|
36
|
+
// 家族名用常见别名,CSS font-family 匹配时能命中
|
|
37
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'Source Han Sans SC', weight: 400 });
|
|
38
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'Source Han Sans SC', weight: 700 });
|
|
39
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'Source Han Sans SC', weight: 400, italic: true });
|
|
40
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'Source Han Sans SC', weight: 700, italic: true });
|
|
41
|
+
|
|
42
|
+
// 也注册成 generic 别名(mermaid 默认字体栈里有 sans-serif)
|
|
43
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'sans-serif', weight: 400 });
|
|
44
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'sans-serif', weight: 700 });
|
|
45
|
+
|
|
46
|
+
registry.setDefaultFamily('Source Han Sans SC');
|
|
47
|
+
cjkFontRegistry = registry;
|
|
48
|
+
return registry;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 检测并替换 markdown 中的 ```mermaid 代码块为图片占位符
|
|
53
|
+
* 使用 sebastianjs(本地、零浏览器、零外网依赖)渲染为 PNG
|
|
54
|
+
*
|
|
55
|
+
* @param {string} md - 原始 markdown
|
|
56
|
+
* @returns {{ md: string, images: { file: string, name: string }[], hasMermaid: boolean }}
|
|
57
|
+
*/
|
|
58
|
+
export async function preprocessMermaid(md) {
|
|
59
|
+
const mermaidRe = /```mermaid\s*\n([\s\S]*?)```/gi;
|
|
60
|
+
const matches = [...md.matchAll(mermaidRe)];
|
|
61
|
+
if (!matches.length) return { md, images: [], hasMermaid: false };
|
|
62
|
+
|
|
63
|
+
const { renderPng } = await getSebastianRenderer();
|
|
64
|
+
const fontRegistry = await getCjkFontRegistry();
|
|
65
|
+
const images = [];
|
|
66
|
+
let result = md;
|
|
67
|
+
let offset = 0;
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < matches.length; i++) {
|
|
70
|
+
const block = matches[i];
|
|
71
|
+
const code = block[1];
|
|
72
|
+
const name = `mermaid-${i + 1}.png`;
|
|
73
|
+
const file = `${tmpdir()}/${name}`;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const t0 = Date.now();
|
|
77
|
+
const { data: pngBuffer } = await renderPng(code, {
|
|
78
|
+
background: 'white',
|
|
79
|
+
fontFamily: 'Source Han Sans SC',
|
|
80
|
+
fontRegistry,
|
|
81
|
+
});
|
|
82
|
+
const t1 = Date.now();
|
|
83
|
+
await writeFile(file, pngBuffer);
|
|
84
|
+
images.push({ file, name });
|
|
85
|
+
|
|
86
|
+
const replacement = ``;
|
|
87
|
+
const start = block.index + offset;
|
|
88
|
+
const end = start + block[0].length;
|
|
89
|
+
result = result.slice(0, start) + replacement + result.slice(end);
|
|
90
|
+
offset += replacement.length - block[0].length;
|
|
91
|
+
console.log(` 📐 Mermaid 图表 ${i + 1} 渲染完成 → ${name} (${pngBuffer.length}B, ${t1 - t0}ms)`);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.warn(` ⚠️ Mermaid 图表 ${i + 1} 渲染失败:${err.message},保留原始代码块`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { md: result, images, hasMermaid: images.length > 0 };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 清理临时图片文件
|
|
102
|
+
*/
|
|
103
|
+
async function cleanupMermaidImages(images) {
|
|
104
|
+
for (const img of images) {
|
|
105
|
+
try { await unlink(img.file); } catch {}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
4
108
|
|
|
5
109
|
// ── 配置 ──────────────────────────────────────────────────────────────────────
|
|
6
110
|
let baseUrl = process.env.CONFLUENCE_BASE_URL;
|
|
7
|
-
let spaceKey = process.env.CONFLUENCE_SPACE_KEY;
|
|
8
111
|
let pat = process.env.CONFLUENCE_PAT;
|
|
9
112
|
let username = process.env.CONFLUENCE_USERNAME;
|
|
10
113
|
let apiToken = process.env.CONFLUENCE_API_TOKEN;
|
|
@@ -14,14 +117,12 @@ let api;
|
|
|
14
117
|
* 用 CLI 参数覆盖环境变量配置,需在其他函数调用前执行
|
|
15
118
|
* @param {object} [opts]
|
|
16
119
|
* @param {string} [opts.baseUrl]
|
|
17
|
-
* @param {string} [opts.spaceKey]
|
|
18
120
|
* @param {string} [opts.pat]
|
|
19
121
|
* @param {string} [opts.username]
|
|
20
122
|
* @param {string} [opts.apiToken]
|
|
21
123
|
*/
|
|
22
124
|
export function configure(opts = {}) {
|
|
23
125
|
if (opts.baseUrl) baseUrl = opts.baseUrl;
|
|
24
|
-
if (opts.spaceKey) spaceKey = opts.spaceKey;
|
|
25
126
|
if (opts.pat) pat = opts.pat;
|
|
26
127
|
if (opts.username) username = opts.username;
|
|
27
128
|
if (opts.apiToken) apiToken = opts.apiToken;
|
|
@@ -61,44 +162,23 @@ function logAxiosError(err) {
|
|
|
61
162
|
// ── 页面查询 ──────────────────────────────────────────────────────────────────
|
|
62
163
|
|
|
63
164
|
/**
|
|
64
|
-
* 按 ID
|
|
165
|
+
* 按 ID 获取页面元信息(含版本号、space)
|
|
65
166
|
*/
|
|
66
167
|
export async function getPageById(id) {
|
|
67
168
|
ensureApi();
|
|
68
169
|
const { data } = await api.get(`/content/${id}`, {
|
|
69
|
-
params: { expand: 'version,ancestors' },
|
|
170
|
+
params: { expand: 'version,ancestors,space' },
|
|
70
171
|
});
|
|
71
172
|
return data;
|
|
72
173
|
}
|
|
73
174
|
|
|
74
175
|
/**
|
|
75
|
-
*
|
|
176
|
+
* 列出指定 parent 下的子页面
|
|
76
177
|
*/
|
|
77
|
-
export async function
|
|
178
|
+
export async function listChildPages(parentId, limit = 50) {
|
|
78
179
|
ensureApi();
|
|
79
|
-
const { data } = await api.get(
|
|
80
|
-
params: {
|
|
81
|
-
spaceKey,
|
|
82
|
-
title,
|
|
83
|
-
expand: 'version',
|
|
84
|
-
limit: 1,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
return data.results?.[0] || null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* 列出 space 下所有页面(分页)
|
|
92
|
-
*/
|
|
93
|
-
export async function listPages(spaceKey, limit = 25) {
|
|
94
|
-
ensureApi();
|
|
95
|
-
const { data } = await api.get('/content', {
|
|
96
|
-
params: {
|
|
97
|
-
spaceKey,
|
|
98
|
-
type: 'page',
|
|
99
|
-
expand: 'version',
|
|
100
|
-
limit,
|
|
101
|
-
},
|
|
180
|
+
const { data } = await api.get(`/content/${parentId}/child/page`, {
|
|
181
|
+
params: { limit, expand: 'version' },
|
|
102
182
|
});
|
|
103
183
|
return data.results;
|
|
104
184
|
}
|
|
@@ -117,19 +197,16 @@ export async function getPageContent(id) {
|
|
|
117
197
|
// ── 页面创建 / 更新 ───────────────────────────────────────────────────────────
|
|
118
198
|
|
|
119
199
|
/**
|
|
120
|
-
*
|
|
200
|
+
* 创建新页面(有 parentId 即可,Confluence 自动推断 space)
|
|
121
201
|
*/
|
|
122
|
-
export async function createPage(
|
|
202
|
+
export async function createPage(parentId, title, bodyHtml) {
|
|
123
203
|
ensureApi();
|
|
124
204
|
const payload = {
|
|
125
205
|
type: 'page',
|
|
126
206
|
title,
|
|
127
|
-
space: { key: spaceKey },
|
|
128
207
|
body: { storage: { value: bodyHtml, representation: 'storage' } },
|
|
208
|
+
ancestors: parentId ? [{ id: parentId }] : undefined,
|
|
129
209
|
};
|
|
130
|
-
if (parentId) {
|
|
131
|
-
payload.ancestors = [{ id: parentId }];
|
|
132
|
-
}
|
|
133
210
|
try {
|
|
134
211
|
const { data } = await api.post('/content', payload);
|
|
135
212
|
return data;
|
|
@@ -162,6 +239,16 @@ export async function updatePage(id, title, bodyHtml, currentVersion, minorEdit
|
|
|
162
239
|
}
|
|
163
240
|
}
|
|
164
241
|
|
|
242
|
+
// ── HTML 转义 ────────────────────────────────────────────────────────────────
|
|
243
|
+
function escapeHtml(str) {
|
|
244
|
+
return String(str)
|
|
245
|
+
.replace(/&/g, '&')
|
|
246
|
+
.replace(/</g, '<')
|
|
247
|
+
.replace(/>/g, '>')
|
|
248
|
+
.replace(/"/g, '"')
|
|
249
|
+
.replace(/'/g, ''');
|
|
250
|
+
}
|
|
251
|
+
|
|
165
252
|
// ── Markdown → Confluence Storage Format ───────────────────────────────────────
|
|
166
253
|
|
|
167
254
|
/**
|
|
@@ -222,7 +309,7 @@ export function mdToConfluence(md) {
|
|
|
222
309
|
break;
|
|
223
310
|
default:
|
|
224
311
|
if (tok.tokens) out += renderTokens(tok.tokens, listDepth);
|
|
225
|
-
else if (tok.text) out += tok.text;
|
|
312
|
+
else if (tok.text) out += escapeHtml(tok.text);
|
|
226
313
|
}
|
|
227
314
|
}
|
|
228
315
|
return out;
|
|
@@ -234,7 +321,7 @@ export function mdToConfluence(md) {
|
|
|
234
321
|
for (const tok of toks) {
|
|
235
322
|
switch (tok.type) {
|
|
236
323
|
case 'text':
|
|
237
|
-
out += tok.tokens ? renderInline(tok.tokens) : tok.text;
|
|
324
|
+
out += tok.tokens ? renderInline(tok.tokens) : escapeHtml(tok.text);
|
|
238
325
|
break;
|
|
239
326
|
case 'strong':
|
|
240
327
|
out += `<strong>${renderInline(tok.tokens)}</strong>`;
|
|
@@ -243,16 +330,16 @@ export function mdToConfluence(md) {
|
|
|
243
330
|
out += `<em>${renderInline(tok.tokens)}</em>`;
|
|
244
331
|
break;
|
|
245
332
|
case 'codespan':
|
|
246
|
-
out += `<code>${tok.text}</code>`;
|
|
333
|
+
out += `<code>${escapeHtml(tok.text)}</code>`;
|
|
247
334
|
break;
|
|
248
335
|
case 'del':
|
|
249
336
|
out += `<s>${renderInline(tok.tokens)}</s>`;
|
|
250
337
|
break;
|
|
251
338
|
case 'link':
|
|
252
|
-
out += `<a href="${tok.href}">${renderInline(tok.tokens)}</a>`;
|
|
339
|
+
out += `<a href="${escapeHtml(tok.href)}">${renderInline(tok.tokens)}</a>`;
|
|
253
340
|
break;
|
|
254
341
|
case 'image':
|
|
255
|
-
out += `<ac:image><ri:attachment ri:filename="${tok.href}" /></ac:image>`;
|
|
342
|
+
out += `<ac:image><ri:attachment ri:filename="${escapeHtml(tok.href)}" /></ac:image>`;
|
|
256
343
|
break;
|
|
257
344
|
case 'br':
|
|
258
345
|
out += '<br/>';
|
|
@@ -260,6 +347,11 @@ export function mdToConfluence(md) {
|
|
|
260
347
|
case 'escape':
|
|
261
348
|
out += tok.text;
|
|
262
349
|
break;
|
|
350
|
+
case 'html':
|
|
351
|
+
// marked 识别的 inline HTML(如 <div>foo</div>、<即实际未使用>)
|
|
352
|
+
// 我们不支持用户写原生 HTML → 一律转义
|
|
353
|
+
out += escapeHtml(tok.text);
|
|
354
|
+
break;
|
|
263
355
|
default:
|
|
264
356
|
out += tok.text || '';
|
|
265
357
|
}
|
|
@@ -448,97 +540,122 @@ export async function listAttachments(pageId) {
|
|
|
448
540
|
|
|
449
541
|
// ── 高级业务流程 ──────────────────────────────────────────────────────────────
|
|
450
542
|
|
|
451
|
-
/**
|
|
452
|
-
* 查找父页面 ID(支持按 title 或直接 ID)
|
|
453
|
-
*/
|
|
454
|
-
async function resolveParentId(parentIdentifier, spaceKey) {
|
|
455
|
-
if (!parentIdentifier) return null;
|
|
456
|
-
// 如果是纯数字,当作 ID
|
|
457
|
-
if (/^\d+$/.test(parentIdentifier)) {
|
|
458
|
-
const page = await getPageById(parentIdentifier);
|
|
459
|
-
console.log(` 父页面:${page.title} (ID: ${page.id})`);
|
|
460
|
-
return page.id;
|
|
461
|
-
}
|
|
462
|
-
// 否则按 title 查找
|
|
463
|
-
const page = await getPageByTitle(spaceKey, parentIdentifier);
|
|
464
|
-
if (!page) {
|
|
465
|
-
throw new Error(`未找到父页面「${parentIdentifier}」(space: ${spaceKey})`);
|
|
466
|
-
}
|
|
467
|
-
console.log(` 父页面:${page.title} (ID: ${page.id})`);
|
|
468
|
-
return page.id;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
543
|
/**
|
|
472
544
|
* 上传 Markdown 文件到 Confluence
|
|
473
545
|
* @param {object} opts
|
|
474
546
|
* @param {string} opts.filePath - 本地 .md 文件路径
|
|
475
547
|
* @param {string} [opts.title] - 页面标题(默认取文件名)
|
|
476
|
-
* @param {string}
|
|
477
|
-
* @param {string} [opts.parentId] - 父页面 ID 或标题
|
|
548
|
+
* @param {string} opts.parentId - 父页面 ID(数字)
|
|
478
549
|
* @param {boolean} [opts.dryRun] - 仅打印转换结果
|
|
479
550
|
*/
|
|
480
551
|
export async function uploadMarkdown(opts) {
|
|
481
|
-
const { filePath, title: optTitle,
|
|
482
|
-
const effectiveSpaceKey = spaceKeyOpt || spaceKey;
|
|
552
|
+
const { filePath, title: optTitle, parentId: parentRaw, dryRun = false, mermaid = true } = opts;
|
|
483
553
|
const fs = await import('node:fs');
|
|
484
554
|
const path = await import('node:path');
|
|
485
555
|
|
|
486
|
-
|
|
556
|
+
let md = fs.readFileSync(filePath, 'utf-8');
|
|
487
557
|
const title = optTitle || path.basename(filePath, extname(filePath));
|
|
558
|
+
|
|
559
|
+
// 必须有 parentId
|
|
560
|
+
if (!parentRaw || !/^\d+$/.test(parentRaw)) {
|
|
561
|
+
throw new Error('upload 需要 --parent <数字页面ID> 作为父页面');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const parentPage = await getPageById(parentRaw);
|
|
565
|
+
console.log(` 父页面:${parentPage.title} (ID: ${parentPage.id}, Space: ${parentPage.space?.key})`);
|
|
566
|
+
|
|
567
|
+
// Mermaid 预处理
|
|
568
|
+
let mermaidImages = [];
|
|
569
|
+
if (mermaid) {
|
|
570
|
+
const result = await preprocessMermaid(md);
|
|
571
|
+
md = result.md;
|
|
572
|
+
mermaidImages = result.images;
|
|
573
|
+
}
|
|
574
|
+
|
|
488
575
|
const bodyHtml = mdToConfluence(md);
|
|
489
576
|
|
|
490
577
|
if (dryRun) {
|
|
491
578
|
console.log('── 转换结果(dry-run)────────────────────────');
|
|
492
579
|
console.log(` 标题: ${title}`);
|
|
493
|
-
console.log(`
|
|
580
|
+
console.log(` 父页面: ${parentPage.title} (ID: ${parentPage.id})`);
|
|
581
|
+
if (mermaidImages.length) console.log(` Mermaid 图片: ${mermaidImages.length} 张`);
|
|
494
582
|
console.log(' Body:\n' + bodyHtml);
|
|
495
583
|
return null;
|
|
496
584
|
}
|
|
497
585
|
|
|
498
|
-
|
|
499
|
-
|
|
586
|
+
try {
|
|
587
|
+
// 检查 parent 下是否已有同名子页面
|
|
588
|
+
const children = await listChildPages(parentPage.id, 500);
|
|
589
|
+
const existing = children.find(c => c.title === title);
|
|
590
|
+
|
|
591
|
+
let page;
|
|
592
|
+
if (existing) {
|
|
593
|
+
console.log(` 父页面下已存在同名页面「${title}」(ID: ${existing.id}),将更新…`);
|
|
594
|
+
page = await updatePage(existing.id, title, bodyHtml, existing.version.number);
|
|
595
|
+
} else {
|
|
596
|
+
page = await createPage(parentPage.id, title, bodyHtml);
|
|
597
|
+
}
|
|
500
598
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
599
|
+
// 上传 mermaid 图片作为附件
|
|
600
|
+
if (mermaidImages.length) {
|
|
601
|
+
console.log(' 📎 上传 Mermaid 图片附件…');
|
|
602
|
+
for (const img of mermaidImages) {
|
|
603
|
+
await uploadAttachment(page.id, img.file, img.name);
|
|
604
|
+
console.log(` ✅ ${img.name}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
509
607
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
608
|
+
console.log(` ✅ ${existing ? '更新' : '创建'}完成 → ${baseUrl}/pages/viewpage.action?pageId=${page.id}`);
|
|
609
|
+
return page;
|
|
610
|
+
} finally {
|
|
611
|
+
await cleanupMermaidImages(mermaidImages);
|
|
612
|
+
}
|
|
513
613
|
}
|
|
514
614
|
|
|
515
615
|
/**
|
|
516
|
-
* 更新指定页面(通过
|
|
616
|
+
* 更新指定页面(通过 page-id 定位)
|
|
517
617
|
*/
|
|
518
618
|
export async function updateMarkdown(opts) {
|
|
519
|
-
const { pageId,
|
|
520
|
-
const effectiveSpaceKey = spaceKeyOpt || spaceKey;
|
|
619
|
+
const { pageId, filePath, minorEdit = false, mermaid = true } = opts;
|
|
521
620
|
const fs = await import('node:fs');
|
|
522
621
|
|
|
523
|
-
|
|
524
|
-
|
|
622
|
+
if (!pageId || !/^\d+$/.test(pageId)) {
|
|
623
|
+
throw new Error('update 需要 --page-id <数字页面ID>');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
let md = fs.readFileSync(filePath, 'utf-8');
|
|
525
627
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
} else {
|
|
533
|
-
throw new Error('必须指定 --page-id 或 --title 来定位目标页面');
|
|
628
|
+
// Mermaid 预处理
|
|
629
|
+
let mermaidImages = [];
|
|
630
|
+
if (mermaid) {
|
|
631
|
+
const result = await preprocessMermaid(md);
|
|
632
|
+
md = result.md;
|
|
633
|
+
mermaidImages = result.images;
|
|
534
634
|
}
|
|
535
635
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
636
|
+
const bodyHtml = mdToConfluence(md);
|
|
637
|
+
|
|
638
|
+
const page = await getPageById(pageId);
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
console.log(` 目标页面:${page.title} (ID: ${page.id}, Space: ${page.space?.key}, 当前版本: ${page.version.number})`);
|
|
642
|
+
const updated = await updatePage(page.id, page.title, bodyHtml, page.version.number, minorEdit);
|
|
643
|
+
|
|
644
|
+
// 上传 mermaid 图片作为附件
|
|
645
|
+
if (mermaidImages.length) {
|
|
646
|
+
console.log(' 📎 上传 Mermaid 图片附件…');
|
|
647
|
+
for (const img of mermaidImages) {
|
|
648
|
+
await uploadAttachment(page.id, img.file, img.name);
|
|
649
|
+
console.log(` ✅ ${img.name}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
console.log(` ✅ 更新完成 → ${baseUrl}/pages/viewpage.action?pageId=${updated.id}`);
|
|
654
|
+
return updated;
|
|
655
|
+
} finally {
|
|
656
|
+
await cleanupMermaidImages(mermaidImages);
|
|
657
|
+
}
|
|
540
658
|
}
|
|
541
659
|
|
|
542
660
|
// ── 导出配置 ──────────────────────────────────────────────────────────────────
|
|
543
661
|
export function getBaseUrl() { return baseUrl; }
|
|
544
|
-
export function getSpaceKey() { return spaceKey; }
|
|
Binary file
|
|
Binary file
|
package/upload.mjs
CHANGED
|
@@ -2,40 +2,37 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Confluence 文档同步 CLI
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 方式2 Basic Auth: CONFLUENCE_USERNAME - Confluence 登录名(工号)
|
|
11
|
-
* CONFLUENCE_API_TOKEN - API Token 或密码
|
|
5
|
+
* 环境变量:
|
|
6
|
+
* CONFLUENCE_BASE_URL - Confluence 服务地址
|
|
7
|
+
* 认证方式(二选一):
|
|
8
|
+
* 方式1 PAT: CONFLUENCE_PAT - Personal Access Token
|
|
9
|
+
* 方式2 Auth: CONFLUENCE_USERNAME + CONFLUENCE_API_TOKEN
|
|
12
10
|
*
|
|
13
11
|
* 用法:
|
|
14
|
-
* confluence-sync upload
|
|
15
|
-
* confluence-sync update
|
|
12
|
+
* confluence-sync upload --file <path> --parent <页面ID> [--title <标题>] [--dry-run] [--no-mermaid-to-img]
|
|
13
|
+
* confluence-sync update --file <path> --page-id <ID> [--minor-edit] [--no-mermaid-to-img]
|
|
16
14
|
* confluence-sync upload-file --page-id <ID> --file <path> [--name <文件名>]
|
|
17
|
-
* confluence-sync list-pages
|
|
18
|
-
* confluence-sync list-files
|
|
15
|
+
* confluence-sync list-pages --parent <页面ID>
|
|
16
|
+
* confluence-sync list-files --page-id <ID>
|
|
19
17
|
* confluence-sync get-content --page-id <ID> [--markdown]
|
|
20
18
|
*
|
|
21
19
|
* 可选配置参数(优先于环境变量):
|
|
22
|
-
* --base-url
|
|
23
|
-
* --
|
|
24
|
-
* --
|
|
25
|
-
* --
|
|
26
|
-
* --api-token API Token 或密码
|
|
20
|
+
* --base-url Confluence 服务地址
|
|
21
|
+
* --pat Personal Access Token
|
|
22
|
+
* --username Confluence 登录名
|
|
23
|
+
* --api-token API Token 或密码
|
|
27
24
|
*/
|
|
28
25
|
|
|
29
26
|
import {
|
|
30
27
|
uploadMarkdown,
|
|
31
28
|
updateMarkdown,
|
|
32
29
|
uploadAttachment,
|
|
33
|
-
|
|
30
|
+
listChildPages,
|
|
34
31
|
listAttachments,
|
|
32
|
+
getPageById,
|
|
35
33
|
getPageContent,
|
|
36
34
|
confluenceToMd,
|
|
37
35
|
getBaseUrl,
|
|
38
|
-
getSpaceKey,
|
|
39
36
|
configure,
|
|
40
37
|
} from './src/confluence.mjs';
|
|
41
38
|
|
|
@@ -73,23 +70,22 @@ Confluence 文档同步工具
|
|
|
73
70
|
upload 上传 Markdown 文件为新页面(已存在则更新)
|
|
74
71
|
update 更新已有页面内容
|
|
75
72
|
upload-file 上传文件作为页面附件
|
|
76
|
-
list-pages 列出
|
|
73
|
+
list-pages 列出 parent 页面下的子页面
|
|
77
74
|
list-files 列出页面的所有附件
|
|
78
75
|
get-content 获取页面的 HTML 内容
|
|
79
76
|
|
|
80
77
|
upload 选项:
|
|
81
78
|
--file 本地 .md 文件路径(必填)
|
|
79
|
+
--parent 父页面 ID(数字,必填)
|
|
82
80
|
--title 页面标题(默认取文件名)
|
|
83
|
-
--space Space Key(默认读 config.json)
|
|
84
|
-
--parent 父页面 ID 或标题
|
|
85
81
|
--dry-run 仅打印转换结果,不实际上传
|
|
82
|
+
--no-mermaid-to-img 禁用 Mermaid 图表自动渲染
|
|
86
83
|
|
|
87
84
|
update 选项:
|
|
88
85
|
--file 本地 .md 文件路径(必填)
|
|
89
|
-
--page-id 目标页面 ID
|
|
90
|
-
--title 按标题查找目标页面(与 --page-id 二选一)
|
|
91
|
-
--space Space Key(默认读 config.json)
|
|
86
|
+
--page-id 目标页面 ID(数字,必填)
|
|
92
87
|
--minor-edit 标记为小修改(不通知关注者)
|
|
88
|
+
--no-mermaid-to-img 禁用 Mermaid 图表自动渲染
|
|
93
89
|
|
|
94
90
|
upload-file 选项:
|
|
95
91
|
--page-id 目标页面 ID(必填)
|
|
@@ -97,34 +93,26 @@ upload-file 选项:
|
|
|
97
93
|
--name 覆盖文件名
|
|
98
94
|
|
|
99
95
|
list-pages 选项:
|
|
100
|
-
--
|
|
96
|
+
--parent 父页面 ID(数字,必填)
|
|
101
97
|
|
|
102
98
|
list-files 选项:
|
|
103
99
|
--page-id 页面 ID(必填)
|
|
104
100
|
|
|
105
101
|
get-content 选项:
|
|
106
102
|
--page-id 页面 ID(必填)
|
|
107
|
-
--markdown 输出为 Markdown
|
|
103
|
+
--markdown 输出为 Markdown 格式
|
|
108
104
|
|
|
109
105
|
配置参数(优先于环境变量):
|
|
110
|
-
--base-url
|
|
111
|
-
--
|
|
112
|
-
--
|
|
113
|
-
--
|
|
114
|
-
--api-token API Token 或密码
|
|
106
|
+
--base-url Confluence 服务地址
|
|
107
|
+
--pat Personal Access Token
|
|
108
|
+
--username Confluence 登录名
|
|
109
|
+
--api-token API Token 或密码
|
|
115
110
|
|
|
116
111
|
环境变量:
|
|
117
|
-
|
|
118
|
-
CONFLUENCE_BASE_URL Confluence 服务地址
|
|
119
|
-
CONFLUENCE_SPACE_KEY Space Key
|
|
120
|
-
|
|
112
|
+
CONFLUENCE_BASE_URL Confluence 服务地址
|
|
121
113
|
认证方式(二选一):
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
方式2 - Basic Auth:
|
|
126
|
-
CONFLUENCE_USERNAME Confluence 登录名(工号)
|
|
127
|
-
CONFLUENCE_API_TOKEN API Token 或密码
|
|
114
|
+
CONFLUENCE_PAT Personal Access Token
|
|
115
|
+
CONFLUENCE_USERNAME + CONFLUENCE_API_TOKEN Basic Auth
|
|
128
116
|
|
|
129
117
|
`);
|
|
130
118
|
}
|
|
@@ -143,13 +131,11 @@ async function main() {
|
|
|
143
131
|
// 用 CLI 参数覆盖环境变量配置
|
|
144
132
|
configure({
|
|
145
133
|
baseUrl: args['base-url'],
|
|
146
|
-
spaceKey: args['space-key'],
|
|
147
134
|
pat: args['pat'],
|
|
148
135
|
username: args['username'],
|
|
149
136
|
apiToken: args['api-token'],
|
|
150
137
|
});
|
|
151
138
|
const baseUrl = getBaseUrl();
|
|
152
|
-
const spaceKey = getSpaceKey();
|
|
153
139
|
|
|
154
140
|
try {
|
|
155
141
|
switch (command) {
|
|
@@ -161,9 +147,9 @@ async function main() {
|
|
|
161
147
|
const result = await uploadMarkdown({
|
|
162
148
|
filePath: args.file,
|
|
163
149
|
title: args.title,
|
|
164
|
-
spaceKey: args.space || spaceKey,
|
|
165
150
|
parentId: args.parent,
|
|
166
151
|
dryRun: args['dry-run'] === true,
|
|
152
|
+
mermaid: args['no-mermaid-to-img'] !== true,
|
|
167
153
|
});
|
|
168
154
|
if (result) {
|
|
169
155
|
console.log('\n页面信息:');
|
|
@@ -180,16 +166,15 @@ async function main() {
|
|
|
180
166
|
console.error('错误:--file 必填');
|
|
181
167
|
process.exit(1);
|
|
182
168
|
}
|
|
183
|
-
if (!args['page-id']
|
|
184
|
-
console.error('
|
|
169
|
+
if (!args['page-id']) {
|
|
170
|
+
console.error('错误:--page-id 必填');
|
|
185
171
|
process.exit(1);
|
|
186
172
|
}
|
|
187
173
|
const result = await updateMarkdown({
|
|
188
174
|
pageId: args['page-id'],
|
|
189
|
-
title: args.title,
|
|
190
175
|
filePath: args.file,
|
|
191
|
-
spaceKey: args.space || spaceKey,
|
|
192
176
|
minorEdit: args['minor-edit'] === true,
|
|
177
|
+
mermaid: args['no-mermaid-to-img'] !== true,
|
|
193
178
|
});
|
|
194
179
|
console.log('\n页面信息:');
|
|
195
180
|
console.log(` ID: ${result.id}`);
|
|
@@ -216,18 +201,22 @@ async function main() {
|
|
|
216
201
|
}
|
|
217
202
|
|
|
218
203
|
case 'list-pages': {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
204
|
+
if (!args.parent || !/^\d+$/.test(args.parent)) {
|
|
205
|
+
console.error('错误:--parent <数字页面ID> 必填');
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const parentPage = await getPageById(args.parent);
|
|
209
|
+
console.log(`父页面:${parentPage.title} (ID: ${parentPage.id}, Space: ${parentPage.space?.key})\n`);
|
|
210
|
+
const pages = await listChildPages(parentPage.id);
|
|
222
211
|
if (!pages.length) {
|
|
223
|
-
console.log('
|
|
212
|
+
console.log(' (无子页面)');
|
|
224
213
|
} else {
|
|
225
214
|
const maxId = Math.max(...pages.map(p => String(p.id).length));
|
|
226
215
|
for (const p of pages) {
|
|
227
216
|
const id = String(p.id).padEnd(maxId);
|
|
228
217
|
console.log(` ${id} v${p.version?.number ?? '?'} ${p.title}`);
|
|
229
218
|
}
|
|
230
|
-
console.log(`\n共 ${pages.length}
|
|
219
|
+
console.log(`\n共 ${pages.length} 个子页面`);
|
|
231
220
|
}
|
|
232
221
|
break;
|
|
233
222
|
}
|