@vdfor/confluence-sync 0.1.0 → 0.3.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/README.md +25 -1
- package/package.json +3 -2
- package/skills/confluence-sync.md +101 -0
- package/src/confluence.mjs +14 -13
- package/upload.mjs +10 -4
package/README.md
CHANGED
|
@@ -23,6 +23,9 @@ yarn global add @vdfor/confluence-sync
|
|
|
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
|
+
|
|
26
29
|
# 认证方式(二选一)
|
|
27
30
|
# 方式1(推荐):Personal Access Token
|
|
28
31
|
export CONFLUENCE_PAT="your-personal-access-token"
|
|
@@ -37,6 +40,7 @@ export CONFLUENCE_API_TOKEN="your-api-token"
|
|
|
37
40
|
```bash
|
|
38
41
|
confluence-sync upload --file test.md \
|
|
39
42
|
--base-url https://your-confluence-instance.com \
|
|
43
|
+
--space-key YOUR_SPACE_KEY \
|
|
40
44
|
--pat your-token
|
|
41
45
|
```
|
|
42
46
|
|
|
@@ -85,10 +89,11 @@ confluence-sync get-content --page-id <ID> [--markdown]
|
|
|
85
89
|
| 参数 | 说明 |
|
|
86
90
|
|------|------|
|
|
87
91
|
| `--base-url` | Confluence 服务地址 |
|
|
92
|
+
| `--space-key` | Space Key |
|
|
88
93
|
| `--pat` | Personal Access Token |
|
|
89
94
|
| `--username` | Confluence 登录名 |
|
|
90
95
|
| `--api-token` | API Token 或密码 |
|
|
91
|
-
| `--space` | Space Key |
|
|
96
|
+
| `--space` | Space Key(覆盖默认值) |
|
|
92
97
|
| `--file` | 本地文件路径 |
|
|
93
98
|
| `--title` | 页面标题 |
|
|
94
99
|
| `--page-id` | 页面 ID |
|
|
@@ -119,6 +124,25 @@ confluence-sync get-content --page-id 123456
|
|
|
119
124
|
confluence-sync get-content --page-id 123456 --markdown
|
|
120
125
|
```
|
|
121
126
|
|
|
127
|
+
## Skill 集成
|
|
128
|
+
|
|
129
|
+
本项目包含 Trae Skill,安装后可被智能体调用。
|
|
130
|
+
|
|
131
|
+
### 安装
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pnpm add -g @vdfor/confluence-sync
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 使用
|
|
138
|
+
|
|
139
|
+
安装后,智能体可直接调用 `confluence-sync` 命令:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
confluence-sync upload --file README.md --title "项目文档" --space MYSPACE
|
|
143
|
+
confluence-sync get-content --page-id 123456 --markdown
|
|
144
|
+
```
|
|
145
|
+
|
|
122
146
|
## License
|
|
123
147
|
|
|
124
148
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vdfor/confluence-sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"author": "vdfor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"upload.mjs",
|
|
23
|
-
"src/"
|
|
23
|
+
"src/",
|
|
24
|
+
"skills/"
|
|
24
25
|
],
|
|
25
26
|
"engines": {
|
|
26
27
|
"node": ">=18"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "confluence-sync"
|
|
3
|
+
description: "Confluence 文档同步 CLI 工具,支持将本地 Markdown 文件上传至 Confluence 并自动转换为 Confluence 页面格式。当用户需要上传、更新、获取 Confluence 页面内容或管理附件时调用。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Confluence Sync
|
|
7
|
+
|
|
8
|
+
Confluence 文档同步 CLI 工具,支持将本地 Markdown 文件上传至 Confluence 并自动转换为 Confluence 页面格式。
|
|
9
|
+
|
|
10
|
+
## 功能特性
|
|
11
|
+
|
|
12
|
+
- 上传 Markdown 文件为 Confluence 页面
|
|
13
|
+
- 更新已有页面内容
|
|
14
|
+
- 上传文件作为页面附件
|
|
15
|
+
- 列出空间页面和页面附件
|
|
16
|
+
- 获取页面内容(支持转换为 Markdown 格式)
|
|
17
|
+
|
|
18
|
+
## 配置要求
|
|
19
|
+
|
|
20
|
+
使用前需要配置以下环境变量或 CLI 参数:
|
|
21
|
+
|
|
22
|
+
### 必填配置
|
|
23
|
+
|
|
24
|
+
- `CONFLUENCE_BASE_URL`: Confluence 服务地址
|
|
25
|
+
- `CONFLUENCE_SPACE_KEY`: Space Key
|
|
26
|
+
- 认证方式(二选一):
|
|
27
|
+
- `CONFLUENCE_PAT`: Personal Access Token(推荐)
|
|
28
|
+
- `CONFLUENCE_USERNAME` + `CONFLUENCE_API_TOKEN`: Basic Auth 认证
|
|
29
|
+
|
|
30
|
+
### 可选配置
|
|
31
|
+
|
|
32
|
+
- `--space`: Space Key(覆盖默认值)
|
|
33
|
+
|
|
34
|
+
## 命令列表
|
|
35
|
+
|
|
36
|
+
### upload - 上传 Markdown 为新页面
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
confluence-sync upload --file <path> [--title <标题>] [--space <key>] [--parent <ID或标题>] [--dry-run]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### update - 更新已有页面
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
confluence-sync update --file <path> (--page-id <ID> | --title <标题>) [--space <key>] [--minor-edit]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### upload-file - 上传附件
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
confluence-sync upload-file --page-id <ID> --file <path> [--name <文件名>]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### list-pages - 列出空间页面
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
confluence-sync list-pages [--space <key>]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### list-files - 列出页面附件
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
confluence-sync list-files --page-id <ID>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### get-content - 获取页面内容
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
confluence-sync get-content --page-id <ID> [--markdown]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
使用 `--markdown` 参数可将 Confluence 内容转换为 Markdown 格式输出。
|
|
73
|
+
|
|
74
|
+
## 使用示例
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# 上传 Markdown 文件
|
|
78
|
+
confluence-sync upload --file README.md --title "项目文档" --space MYSPACE
|
|
79
|
+
|
|
80
|
+
# 更新已有页面
|
|
81
|
+
confluence-sync update --file README.md --page-id 123456
|
|
82
|
+
|
|
83
|
+
# 上传附件
|
|
84
|
+
confluence-sync upload-file --page-id 123456 --file image.png
|
|
85
|
+
|
|
86
|
+
# 列出空间所有页面
|
|
87
|
+
confluence-sync list-pages --space MYSPACE
|
|
88
|
+
|
|
89
|
+
# 获取页面内容
|
|
90
|
+
confluence-sync get-content --page-id 123456
|
|
91
|
+
|
|
92
|
+
# 获取页面内容并转换为 Markdown
|
|
93
|
+
confluence-sync get-content --page-id 123456 --markdown
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 注意事项
|
|
97
|
+
|
|
98
|
+
1. 使用前请确保已正确配置认证信息
|
|
99
|
+
2. Personal Access Token 需要在 Confluence 个人设置中生成
|
|
100
|
+
3. 上传附件时,如果附件已存在会自动更新
|
|
101
|
+
4. 使用 `--dry-run` 参数可以预览转换结果而不实际上传
|
package/src/confluence.mjs
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { marked } from 'marked';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
4
3
|
import { extname } from 'node:path';
|
|
5
4
|
|
|
6
5
|
// ── 配置 ──────────────────────────────────────────────────────────────────────
|
|
7
|
-
const config = JSON.parse(readFileSync(new URL('../config.json', import.meta.url), 'utf-8'));
|
|
8
|
-
const { spaceKey: defaultSpaceKey } = config;
|
|
9
|
-
|
|
10
6
|
let baseUrl = process.env.CONFLUENCE_BASE_URL;
|
|
7
|
+
let spaceKey = process.env.CONFLUENCE_SPACE_KEY;
|
|
11
8
|
let pat = process.env.CONFLUENCE_PAT;
|
|
12
9
|
let username = process.env.CONFLUENCE_USERNAME;
|
|
13
10
|
let apiToken = process.env.CONFLUENCE_API_TOKEN;
|
|
@@ -17,12 +14,14 @@ let api;
|
|
|
17
14
|
* 用 CLI 参数覆盖环境变量配置,需在其他函数调用前执行
|
|
18
15
|
* @param {object} [opts]
|
|
19
16
|
* @param {string} [opts.baseUrl]
|
|
17
|
+
* @param {string} [opts.spaceKey]
|
|
20
18
|
* @param {string} [opts.pat]
|
|
21
19
|
* @param {string} [opts.username]
|
|
22
20
|
* @param {string} [opts.apiToken]
|
|
23
21
|
*/
|
|
24
22
|
export function configure(opts = {}) {
|
|
25
23
|
if (opts.baseUrl) baseUrl = opts.baseUrl;
|
|
24
|
+
if (opts.spaceKey) spaceKey = opts.spaceKey;
|
|
26
25
|
if (opts.pat) pat = opts.pat;
|
|
27
26
|
if (opts.username) username = opts.username;
|
|
28
27
|
if (opts.apiToken) apiToken = opts.apiToken;
|
|
@@ -479,7 +478,8 @@ async function resolveParentId(parentIdentifier, spaceKey) {
|
|
|
479
478
|
* @param {boolean} [opts.dryRun] - 仅打印转换结果
|
|
480
479
|
*/
|
|
481
480
|
export async function uploadMarkdown(opts) {
|
|
482
|
-
const { filePath, title: optTitle, spaceKey
|
|
481
|
+
const { filePath, title: optTitle, spaceKey: spaceKeyOpt, parentId: parentRaw, dryRun = false } = opts;
|
|
482
|
+
const effectiveSpaceKey = spaceKeyOpt || spaceKey;
|
|
483
483
|
const fs = await import('node:fs');
|
|
484
484
|
const path = await import('node:path');
|
|
485
485
|
|
|
@@ -490,16 +490,16 @@ export async function uploadMarkdown(opts) {
|
|
|
490
490
|
if (dryRun) {
|
|
491
491
|
console.log('── 转换结果(dry-run)────────────────────────');
|
|
492
492
|
console.log(` 标题: ${title}`);
|
|
493
|
-
console.log(` Space: ${
|
|
493
|
+
console.log(` Space: ${effectiveSpaceKey}`);
|
|
494
494
|
console.log(' Body:\n' + bodyHtml);
|
|
495
495
|
return null;
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
// 查找父页面
|
|
499
|
-
const parentId = await resolveParentId(parentRaw,
|
|
499
|
+
const parentId = await resolveParentId(parentRaw, effectiveSpaceKey);
|
|
500
500
|
|
|
501
501
|
// 检查是否已存在同名页面
|
|
502
|
-
const existing = await getPageByTitle(
|
|
502
|
+
const existing = await getPageByTitle(effectiveSpaceKey, title);
|
|
503
503
|
if (existing) {
|
|
504
504
|
console.log(` 页面「${title}」已存在 (ID: ${existing.id}),将更新…`);
|
|
505
505
|
const updated = await updatePage(existing.id, title, bodyHtml, existing.version.number);
|
|
@@ -507,7 +507,7 @@ export async function uploadMarkdown(opts) {
|
|
|
507
507
|
return updated;
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
const page = await createPage(
|
|
510
|
+
const page = await createPage(effectiveSpaceKey, title, bodyHtml, parentId);
|
|
511
511
|
console.log(` ✅ 创建完成 → ${baseUrl}/pages/viewpage.action?pageId=${page.id}`);
|
|
512
512
|
return page;
|
|
513
513
|
}
|
|
@@ -516,7 +516,8 @@ export async function uploadMarkdown(opts) {
|
|
|
516
516
|
* 更新指定页面(通过 ID 或 space+title 查找)
|
|
517
517
|
*/
|
|
518
518
|
export async function updateMarkdown(opts) {
|
|
519
|
-
const { pageId, title: searchTitle, filePath, spaceKey
|
|
519
|
+
const { pageId, title: searchTitle, filePath, spaceKey: spaceKeyOpt, minorEdit = false } = opts;
|
|
520
|
+
const effectiveSpaceKey = spaceKeyOpt || spaceKey;
|
|
520
521
|
const fs = await import('node:fs');
|
|
521
522
|
|
|
522
523
|
const md = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -526,8 +527,8 @@ export async function updateMarkdown(opts) {
|
|
|
526
527
|
if (pageId) {
|
|
527
528
|
page = await getPageById(pageId);
|
|
528
529
|
} else if (searchTitle) {
|
|
529
|
-
page = await getPageByTitle(
|
|
530
|
-
if (!page) throw new Error(`未找到页面「${searchTitle}」(space: ${
|
|
530
|
+
page = await getPageByTitle(effectiveSpaceKey, searchTitle);
|
|
531
|
+
if (!page) throw new Error(`未找到页面「${searchTitle}」(space: ${effectiveSpaceKey})`);
|
|
531
532
|
} else {
|
|
532
533
|
throw new Error('必须指定 --page-id 或 --title 来定位目标页面');
|
|
533
534
|
}
|
|
@@ -539,5 +540,5 @@ export async function updateMarkdown(opts) {
|
|
|
539
540
|
}
|
|
540
541
|
|
|
541
542
|
// ── 导出配置 ──────────────────────────────────────────────────────────────────
|
|
542
|
-
export { defaultSpaceKey };
|
|
543
543
|
export function getBaseUrl() { return baseUrl; }
|
|
544
|
+
export function getSpaceKey() { return spaceKey; }
|
package/upload.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 环境变量(或通过 CLI 参数配置):
|
|
6
6
|
* 必填: CONFLUENCE_BASE_URL - Confluence 服务地址
|
|
7
|
+
* CONFLUENCE_SPACE_KEY - Space Key
|
|
7
8
|
* 认证方式(二选一):
|
|
8
9
|
* 方式1 PAT: CONFLUENCE_PAT - Personal Access Token
|
|
9
10
|
* 方式2 Basic Auth: CONFLUENCE_USERNAME - Confluence 登录名(工号)
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
*
|
|
20
21
|
* 可选配置参数(优先于环境变量):
|
|
21
22
|
* --base-url Confluence 服务地址
|
|
23
|
+
* --space-key Space Key
|
|
22
24
|
* --pat Personal Access Token
|
|
23
25
|
* --username Confluence 登录名
|
|
24
26
|
* --api-token API Token 或密码
|
|
@@ -32,8 +34,8 @@ import {
|
|
|
32
34
|
listAttachments,
|
|
33
35
|
getPageContent,
|
|
34
36
|
confluenceToMd,
|
|
35
|
-
defaultSpaceKey,
|
|
36
37
|
getBaseUrl,
|
|
38
|
+
getSpaceKey,
|
|
37
39
|
configure,
|
|
38
40
|
} from './src/confluence.mjs';
|
|
39
41
|
|
|
@@ -106,6 +108,7 @@ get-content 选项:
|
|
|
106
108
|
|
|
107
109
|
配置参数(优先于环境变量):
|
|
108
110
|
--base-url Confluence 服务地址
|
|
111
|
+
--space-key Space Key
|
|
109
112
|
--pat Personal Access Token
|
|
110
113
|
--username Confluence 登录名
|
|
111
114
|
--api-token API Token 或密码
|
|
@@ -113,6 +116,7 @@ get-content 选项:
|
|
|
113
116
|
环境变量:
|
|
114
117
|
必填:
|
|
115
118
|
CONFLUENCE_BASE_URL Confluence 服务地址
|
|
119
|
+
CONFLUENCE_SPACE_KEY Space Key
|
|
116
120
|
|
|
117
121
|
认证方式(二选一):
|
|
118
122
|
方式1 - Personal Access Token:
|
|
@@ -139,11 +143,13 @@ async function main() {
|
|
|
139
143
|
// 用 CLI 参数覆盖环境变量配置
|
|
140
144
|
configure({
|
|
141
145
|
baseUrl: args['base-url'],
|
|
146
|
+
spaceKey: args['space-key'],
|
|
142
147
|
pat: args['pat'],
|
|
143
148
|
username: args['username'],
|
|
144
149
|
apiToken: args['api-token'],
|
|
145
150
|
});
|
|
146
151
|
const baseUrl = getBaseUrl();
|
|
152
|
+
const spaceKey = getSpaceKey();
|
|
147
153
|
|
|
148
154
|
try {
|
|
149
155
|
switch (command) {
|
|
@@ -155,7 +161,7 @@ async function main() {
|
|
|
155
161
|
const result = await uploadMarkdown({
|
|
156
162
|
filePath: args.file,
|
|
157
163
|
title: args.title,
|
|
158
|
-
spaceKey: args.space ||
|
|
164
|
+
spaceKey: args.space || spaceKey,
|
|
159
165
|
parentId: args.parent,
|
|
160
166
|
dryRun: args['dry-run'] === true,
|
|
161
167
|
});
|
|
@@ -182,7 +188,7 @@ async function main() {
|
|
|
182
188
|
pageId: args['page-id'],
|
|
183
189
|
title: args.title,
|
|
184
190
|
filePath: args.file,
|
|
185
|
-
spaceKey: args.space ||
|
|
191
|
+
spaceKey: args.space || spaceKey,
|
|
186
192
|
minorEdit: args['minor-edit'] === true,
|
|
187
193
|
});
|
|
188
194
|
console.log('\n页面信息:');
|
|
@@ -210,7 +216,7 @@ async function main() {
|
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
case 'list-pages': {
|
|
213
|
-
const space = args.space ||
|
|
219
|
+
const space = args.space || spaceKey;
|
|
214
220
|
console.log(`Space: ${space}\n`);
|
|
215
221
|
const pages = await listPages(space);
|
|
216
222
|
if (!pages.length) {
|