@cloudglab/confluence-cli 0.0.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/AGENTS.md +34 -0
- package/CHANGELOG.md +26 -0
- package/README.md +147 -0
- package/assets/readme/confluence-cli-hero.png +0 -0
- package/assets/readme/confluence-cli-hero.svg +7 -0
- package/assets/readme/prompts/01-cover-confluence-cli.md +61 -0
- package/dist/api/endpoints.d.ts +404 -0
- package/dist/api/endpoints.js +85 -0
- package/dist/api/index.d.ts +148 -0
- package/dist/api/index.js +143 -0
- package/dist/bin/confluence-reader.d.ts +2 -0
- package/dist/bin/confluence-reader.js +8 -0
- package/dist/bin/confluence-writer.d.ts +2 -0
- package/dist/bin/confluence-writer.js +8 -0
- package/dist/bin/confluence.d.ts +2 -0
- package/dist/bin/confluence.js +11 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +154 -0
- package/dist/core/api-provider.d.ts +3 -0
- package/dist/core/api-provider.js +13 -0
- package/dist/core/changelog.d.ts +7 -0
- package/dist/core/changelog.js +42 -0
- package/dist/core/cli-output.d.ts +16 -0
- package/dist/core/cli-output.js +318 -0
- package/dist/core/cli-registry.d.ts +20 -0
- package/dist/core/cli-registry.js +148 -0
- package/dist/core/command-groups.generated.d.ts +2 -0
- package/dist/core/command-groups.generated.js +88 -0
- package/dist/core/config.d.ts +5 -0
- package/dist/core/config.js +108 -0
- package/dist/core/http-error.d.ts +2 -0
- package/dist/core/http-error.js +4 -0
- package/dist/core/http.d.ts +28 -0
- package/dist/core/http.js +124 -0
- package/dist/core/inline-comment.d.ts +23 -0
- package/dist/core/inline-comment.js +27 -0
- package/dist/core/list-result.d.ts +14 -0
- package/dist/core/list-result.js +81 -0
- package/dist/core/manifest.d.ts +11 -0
- package/dist/core/manifest.js +42 -0
- package/dist/core/pagination.d.ts +26 -0
- package/dist/core/pagination.js +45 -0
- package/dist/core/roles.d.ts +4 -0
- package/dist/core/roles.js +12 -0
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +60 -0
- package/dist/core/validation.d.ts +2 -0
- package/dist/core/validation.js +10 -0
- package/dist/core/value.d.ts +2 -0
- package/dist/core/value.js +19 -0
- package/dist/core/write-guard.d.ts +25 -0
- package/dist/core/write-guard.js +49 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/install.d.ts +3 -0
- package/dist/install.js +407 -0
- package/dist/manifest.json +122 -0
- package/dist/tools/attachments.d.ts +2 -0
- package/dist/tools/attachments.js +46 -0
- package/dist/tools/content.d.ts +2 -0
- package/dist/tools/content.js +45 -0
- package/dist/tools/convert.d.ts +2 -0
- package/dist/tools/convert.js +63 -0
- package/dist/tools/init.d.ts +2 -0
- package/dist/tools/init.js +24 -0
- package/dist/tools/install.d.ts +2 -0
- package/dist/tools/install.js +52 -0
- package/dist/tools/labels.d.ts +2 -0
- package/dist/tools/labels.js +22 -0
- package/dist/tools/metadata.d.ts +2 -0
- package/dist/tools/metadata.js +26 -0
- package/dist/tools/rest.d.ts +2 -0
- package/dist/tools/rest.js +52 -0
- package/dist/tools/spaces.d.ts +2 -0
- package/dist/tools/spaces.js +18 -0
- package/dist/tools/transfer.d.ts +2 -0
- package/dist/tools/transfer.js +407 -0
- package/dist/types/common.d.ts +17 -0
- package/dist/types/common.js +2 -0
- package/dist/update-probe.d.ts +2 -0
- package/dist/update-probe.js +142 -0
- package/dist/utils/mark-metadata.d.ts +9 -0
- package/dist/utils/mark-metadata.js +16 -0
- package/dist/utils/markdown.d.ts +9 -0
- package/dist/utils/markdown.js +220 -0
- package/dist/utils/result.d.ts +3 -0
- package/dist/utils/result.js +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/docs/confluence-7.13.7-api.md +183 -0
- package/docs/index.html +608 -0
- package/docs/release.md +41 -0
- package/package.json +63 -0
- package/skills/confluence-cli/SKILL.md +63 -0
- package/skills/confluence-cli/reference/cli.md +36 -0
- package/skills/confluence-cli/reference/commands.md +41 -0
- package/skills/confluence-cli/reference/content.md +23 -0
- package/skills/confluence-cli/reference/overview.md +23 -0
- package/skills/confluence-cli/reference/rest.md +19 -0
- package/skills/confluence-cli/reference/transfer.md +27 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function firstString(...values) {
|
|
2
|
+
for (const value of values) {
|
|
3
|
+
if (typeof value !== "string")
|
|
4
|
+
continue;
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
if (trimmed)
|
|
7
|
+
return trimmed;
|
|
8
|
+
}
|
|
9
|
+
for (const value of values) {
|
|
10
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
11
|
+
continue;
|
|
12
|
+
return String(value);
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
export function isRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=value.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface WriteGuardInput {
|
|
2
|
+
action: string;
|
|
3
|
+
confirm?: boolean;
|
|
4
|
+
payload: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface WritePreview {
|
|
7
|
+
ok: false;
|
|
8
|
+
preview: true;
|
|
9
|
+
reason: string;
|
|
10
|
+
action: string;
|
|
11
|
+
payload: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface UnsupportedWriteDiagnostic {
|
|
14
|
+
ok: false;
|
|
15
|
+
supported: false;
|
|
16
|
+
error: string;
|
|
17
|
+
action: string;
|
|
18
|
+
diagnostic: string;
|
|
19
|
+
payload: unknown;
|
|
20
|
+
}
|
|
21
|
+
export declare function isWriteEnabled(): boolean;
|
|
22
|
+
export declare function getWritePreview(input: WriteGuardInput, reason: string): WritePreview;
|
|
23
|
+
export declare function getUnsupportedWriteDiagnostic(input: WriteGuardInput, diagnostic: string): UnsupportedWriteDiagnostic;
|
|
24
|
+
export declare function assertWriteAllowed(input: WriteGuardInput): void;
|
|
25
|
+
export declare function previewOrAssertWriteAllowed(input: WriteGuardInput): WritePreview | UnsupportedWriteDiagnostic | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const UNSUPPORTED_WRITE_ACTIONS = {};
|
|
2
|
+
export function isWriteEnabled() {
|
|
3
|
+
return process.env.CONFLUENCE_DISABLE_WRITE !== "true";
|
|
4
|
+
}
|
|
5
|
+
export function getWritePreview(input, reason) {
|
|
6
|
+
return {
|
|
7
|
+
ok: false,
|
|
8
|
+
preview: true,
|
|
9
|
+
reason,
|
|
10
|
+
action: input.action,
|
|
11
|
+
payload: input.payload,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function getUnsupportedWriteDiagnostic(input, diagnostic) {
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
supported: false,
|
|
18
|
+
error: `写操作 ${input.action} 当前不能真实执行`,
|
|
19
|
+
action: input.action,
|
|
20
|
+
diagnostic,
|
|
21
|
+
payload: input.payload,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function assertWriteAllowed(input) {
|
|
25
|
+
const unsupportedReason = UNSUPPORTED_WRITE_ACTIONS[input.action];
|
|
26
|
+
if (unsupportedReason) {
|
|
27
|
+
throw new Error(`写操作 ${input.action} 当前不支持真实执行:${unsupportedReason}`);
|
|
28
|
+
}
|
|
29
|
+
if (!isWriteEnabled()) {
|
|
30
|
+
throw new Error(`写操作已禁用。若要执行 ${input.action},需要移除 CONFLUENCE_DISABLE_WRITE=true。`);
|
|
31
|
+
}
|
|
32
|
+
if (input.confirm !== true) {
|
|
33
|
+
throw new Error(`写操作缺少确认。若要执行 ${input.action},需要传入 confirm: true。`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function previewOrAssertWriteAllowed(input) {
|
|
37
|
+
const unsupportedReason = UNSUPPORTED_WRITE_ACTIONS[input.action];
|
|
38
|
+
if (unsupportedReason) {
|
|
39
|
+
return getUnsupportedWriteDiagnostic(input, unsupportedReason);
|
|
40
|
+
}
|
|
41
|
+
if (!isWriteEnabled()) {
|
|
42
|
+
return getWritePreview(input, `写操作已禁用。若要执行 ${input.action},需要移除 CONFLUENCE_DISABLE_WRITE=true。`);
|
|
43
|
+
}
|
|
44
|
+
if (input.confirm !== true) {
|
|
45
|
+
return getWritePreview(input, `写操作缺少确认。若要执行 ${input.action},需要传入 confirm: true。`);
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=write-guard.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/install.js
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { access, mkdtemp, readdir, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { ConfluenceApi } from "./api/index.js";
|
|
6
|
+
import { loadConfluenceConfig, normalizeConfig, saveConfig } from "./core/config.js";
|
|
7
|
+
import { writeUpdateCacheAfterInstall } from "./update-probe.js";
|
|
8
|
+
const PACKAGE_NAME = "@cloudglab/confluence-cli";
|
|
9
|
+
const GIT_SKILL_SOURCE = "cloudglab/confluence-cli";
|
|
10
|
+
export async function runInstallCommand(args = []) {
|
|
11
|
+
const options = parseInstallOptions(args);
|
|
12
|
+
await installPackageAndSkill("安装", options);
|
|
13
|
+
if (options.skipConfigCheck) {
|
|
14
|
+
printSuccessGuide("安装", "已跳过 Confluence 配置校验。");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await ensureValidConfluenceConfig();
|
|
18
|
+
printSuccessGuide("安装", "Confluence 配置校验通过。");
|
|
19
|
+
}
|
|
20
|
+
export async function runUpdateCommand(args = []) {
|
|
21
|
+
const options = parseInstallOptions(args);
|
|
22
|
+
await installPackageAndSkill("更新", options);
|
|
23
|
+
if (options.skipConfigCheck) {
|
|
24
|
+
printSuccessGuide("更新", "已跳过 Confluence 配置校验。");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await ensureValidConfluenceConfig();
|
|
28
|
+
printSuccessGuide("更新", "Confluence 配置校验通过。");
|
|
29
|
+
}
|
|
30
|
+
export async function runUninstallCommand(args = []) {
|
|
31
|
+
const options = parseUninstallOptions(args);
|
|
32
|
+
if (!options.confirm) {
|
|
33
|
+
printUninstallPreview(options);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!options.cliOnly) {
|
|
37
|
+
await uninstallSkill();
|
|
38
|
+
}
|
|
39
|
+
if (!options.skillOnly) {
|
|
40
|
+
await uninstallPackage();
|
|
41
|
+
}
|
|
42
|
+
if (shouldRemoveConfig(options)) {
|
|
43
|
+
await removeConfigFile();
|
|
44
|
+
}
|
|
45
|
+
process.stdout.write("\n卸载完成。\n");
|
|
46
|
+
}
|
|
47
|
+
function printSuccessGuide(action, status) {
|
|
48
|
+
process.stdout.write(`\n${action}完成,${status}\n\n${renderBanner()}\n\n`);
|
|
49
|
+
process.stdout.write(`快速开始:
|
|
50
|
+
confluence help 查看帮助
|
|
51
|
+
confluence list 查看可用命令
|
|
52
|
+
confluence searchContent --limit 10 查询页面
|
|
53
|
+
confluence listRestApis --limit 20 查看 REST API 模板
|
|
54
|
+
|
|
55
|
+
常用配置:
|
|
56
|
+
confluence update 更新 CLI 和 Skill
|
|
57
|
+
confluence install --skip-config-check 仅安装,跳过配置校验
|
|
58
|
+
CONFLUENCE_DISABLE_WRITE=true 禁用真实写操作
|
|
59
|
+
|
|
60
|
+
写操作提示:真实写入仍需显式传 confirm=true。
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
function parseInstallOptions(args) {
|
|
64
|
+
let skillSource = "local";
|
|
65
|
+
let skillLocalPath;
|
|
66
|
+
let skipConfigCheck = false;
|
|
67
|
+
let cliOnly = false;
|
|
68
|
+
let skillOnly = false;
|
|
69
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
70
|
+
const arg = args[index];
|
|
71
|
+
if (arg === "--skill-source" || arg.startsWith("--skill-source=")) {
|
|
72
|
+
const value = readRequiredOptionValue(args, index, "--skill-source");
|
|
73
|
+
if (value !== "local" && value !== "git" && value !== "npm") {
|
|
74
|
+
throw new Error("--skill-source 只支持 local、git 或 npm");
|
|
75
|
+
}
|
|
76
|
+
skillSource = value;
|
|
77
|
+
if (arg === "--skill-source")
|
|
78
|
+
index += 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (arg === "--skill-local-path" || arg.startsWith("--skill-local-path=")) {
|
|
82
|
+
skillLocalPath = readRequiredOptionValue(args, index, "--skill-local-path");
|
|
83
|
+
if (arg === "--skill-local-path")
|
|
84
|
+
index += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (arg === "--skip-config-check" || arg.startsWith("--skip-config-check=")) {
|
|
88
|
+
const parsed = readBooleanFlag(args, index, "--skip-config-check");
|
|
89
|
+
skipConfigCheck = parsed.value;
|
|
90
|
+
index += parsed.consumedArgs;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (arg === "--cli-only" || arg.startsWith("--cli-only=")) {
|
|
94
|
+
const parsed = readBooleanFlag(args, index, "--cli-only");
|
|
95
|
+
cliOnly = parsed.value;
|
|
96
|
+
index += parsed.consumedArgs;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (arg === "--skill-only" || arg.startsWith("--skill-only=")) {
|
|
100
|
+
const parsed = readBooleanFlag(args, index, "--skill-only");
|
|
101
|
+
skillOnly = parsed.value;
|
|
102
|
+
index += parsed.consumedArgs;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`未知安装参数: ${arg}`);
|
|
106
|
+
}
|
|
107
|
+
if (cliOnly && skillOnly) {
|
|
108
|
+
throw new Error("--cli-only 和 --skill-only 不能同时使用");
|
|
109
|
+
}
|
|
110
|
+
return { skillSource, skillLocalPath, skipConfigCheck, cliOnly, skillOnly };
|
|
111
|
+
}
|
|
112
|
+
function parseUninstallOptions(args) {
|
|
113
|
+
let confirm = false;
|
|
114
|
+
let keepConfig = false;
|
|
115
|
+
let cliOnly = false;
|
|
116
|
+
let skillOnly = false;
|
|
117
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
118
|
+
const arg = args[index];
|
|
119
|
+
if (arg === "--confirm" || arg.startsWith("--confirm=")) {
|
|
120
|
+
const parsed = readBooleanFlag(args, index, "--confirm");
|
|
121
|
+
confirm = parsed.value;
|
|
122
|
+
index += parsed.consumedArgs;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (arg === "--keep-config" || arg.startsWith("--keep-config=")) {
|
|
126
|
+
const parsed = readBooleanFlag(args, index, "--keep-config");
|
|
127
|
+
keepConfig = parsed.value;
|
|
128
|
+
index += parsed.consumedArgs;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (arg === "--cli-only" || arg.startsWith("--cli-only=")) {
|
|
132
|
+
const parsed = readBooleanFlag(args, index, "--cli-only");
|
|
133
|
+
cliOnly = parsed.value;
|
|
134
|
+
index += parsed.consumedArgs;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (arg === "--skill-only" || arg.startsWith("--skill-only=")) {
|
|
138
|
+
const parsed = readBooleanFlag(args, index, "--skill-only");
|
|
139
|
+
skillOnly = parsed.value;
|
|
140
|
+
index += parsed.consumedArgs;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`未知卸载参数: ${arg}`);
|
|
144
|
+
}
|
|
145
|
+
if (cliOnly && skillOnly) {
|
|
146
|
+
throw new Error("--cli-only 和 --skill-only 不能同时使用");
|
|
147
|
+
}
|
|
148
|
+
return { confirm, keepConfig, cliOnly, skillOnly };
|
|
149
|
+
}
|
|
150
|
+
function printUninstallPreview(options) {
|
|
151
|
+
const steps = [
|
|
152
|
+
...(!options.cliOnly ? ["卸载 confluence skill(项目级和全局级)"] : []),
|
|
153
|
+
...(!options.skillOnly ? ["卸载全局 CLI 包并清理 npm 残留目录"] : []),
|
|
154
|
+
...(shouldRemoveConfig(options) ? ["删除 ~/.confluence/config.json"] : ["保留 ~/.confluence/config.json"]),
|
|
155
|
+
];
|
|
156
|
+
process.stdout.write(`卸载预览:\n${steps.map((step) => ` - ${step}`).join("\n")}\n\n真实执行请运行:\n confluence uninstall --confirm true\n npx -y ${PACKAGE_NAME}@latest uninstall --confirm true\n\n可选参数:\n --keep-config true 保留 Confluence 配置\n --cli-only true 只卸载 CLI\n --skill-only true 只卸载 skill\n`);
|
|
157
|
+
}
|
|
158
|
+
function shouldRemoveConfig(options) {
|
|
159
|
+
return !options.keepConfig && !options.cliOnly && !options.skillOnly;
|
|
160
|
+
}
|
|
161
|
+
function createSkillAddArgs(source) {
|
|
162
|
+
return ["-y", "skills", "add", source, "--yes"];
|
|
163
|
+
}
|
|
164
|
+
function createSkillRemoveArgs(global = false) {
|
|
165
|
+
return ["-y", "skills", "remove", "confluence-cli", "--yes", ...(global ? ["--global"] : [])];
|
|
166
|
+
}
|
|
167
|
+
function readOptionValue(arg, optionName) {
|
|
168
|
+
const prefix = `${optionName}=`;
|
|
169
|
+
return arg.startsWith(prefix) ? arg.slice(prefix.length) : undefined;
|
|
170
|
+
}
|
|
171
|
+
function readRequiredOptionValue(args, index, optionName) {
|
|
172
|
+
const arg = args[index];
|
|
173
|
+
const inlineValue = readOptionValue(arg, optionName);
|
|
174
|
+
if (inlineValue !== undefined) {
|
|
175
|
+
if (inlineValue.trim() === "") {
|
|
176
|
+
throw createMissingOptionValueError(optionName);
|
|
177
|
+
}
|
|
178
|
+
return inlineValue;
|
|
179
|
+
}
|
|
180
|
+
const next = args[index + 1];
|
|
181
|
+
if (typeof next !== "string" || next.startsWith("--")) {
|
|
182
|
+
throw createMissingOptionValueError(optionName);
|
|
183
|
+
}
|
|
184
|
+
return next;
|
|
185
|
+
}
|
|
186
|
+
function createMissingOptionValueError(optionName) {
|
|
187
|
+
if (optionName === "--skill-local-path") {
|
|
188
|
+
return new Error("--skill-local-path 需要传入本地目录路径");
|
|
189
|
+
}
|
|
190
|
+
return new Error(`${optionName} 需要传入参数值`);
|
|
191
|
+
}
|
|
192
|
+
function readBooleanFlag(args, index, optionName) {
|
|
193
|
+
const inlineValue = readOptionValue(args[index], optionName);
|
|
194
|
+
if (inlineValue !== undefined)
|
|
195
|
+
return { value: parseBooleanValue(inlineValue, optionName), consumedArgs: 0 };
|
|
196
|
+
const next = args[index + 1];
|
|
197
|
+
if (typeof next === "string" && !next.startsWith("--")) {
|
|
198
|
+
return { value: parseBooleanValue(next, optionName), consumedArgs: 1 };
|
|
199
|
+
}
|
|
200
|
+
return { value: true, consumedArgs: 0 };
|
|
201
|
+
}
|
|
202
|
+
function parseBooleanValue(value, optionName) {
|
|
203
|
+
const normalized = value.trim().toLowerCase();
|
|
204
|
+
if (["true", "1", "yes", "y", "on"].includes(normalized))
|
|
205
|
+
return true;
|
|
206
|
+
if (["false", "0", "no", "n", "off"].includes(normalized))
|
|
207
|
+
return false;
|
|
208
|
+
throw new Error(`${optionName} 只支持 true 或 false`);
|
|
209
|
+
}
|
|
210
|
+
async function installPackageAndSkill(action, options) {
|
|
211
|
+
if (!options.skillOnly) {
|
|
212
|
+
await cleanupGlobalPackageResidues();
|
|
213
|
+
await installGlobalCli(action);
|
|
214
|
+
}
|
|
215
|
+
if (!options.cliOnly) {
|
|
216
|
+
await installSkill(action, options);
|
|
217
|
+
}
|
|
218
|
+
await writeUpdateCacheAfterInstall();
|
|
219
|
+
}
|
|
220
|
+
async function installGlobalCli(action) {
|
|
221
|
+
const args = ["install", "-g", `${PACKAGE_NAME}@latest`];
|
|
222
|
+
try {
|
|
223
|
+
await runStep(`${action} Confluence CLI`, "npm", args);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (!isNpmDirectoryNotEmptyError(error)) {
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
process.stdout.write("\n检测到 npm 全局安装目录残留,正在清理后重试...\n");
|
|
230
|
+
await cleanupGlobalPackageResidues();
|
|
231
|
+
await runStep(`${action} Confluence CLI`, "npm", args);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function isNpmDirectoryNotEmptyError(error) {
|
|
235
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
236
|
+
return message.includes("ENOTEMPTY") || message.toLowerCase().includes("directory not empty");
|
|
237
|
+
}
|
|
238
|
+
async function runNpxStepWithRetry(title, args) {
|
|
239
|
+
try {
|
|
240
|
+
await runStep(title, "npx", args);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (!isNpmDirectoryNotEmptyError(error)) {
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(`\n检测到 npx 缓存目录残留,正在清理后重试 ${title}...\n`);
|
|
247
|
+
await cleanupNpxResidues();
|
|
248
|
+
await runStep(title, "npx", args);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function installSkill(action, options) {
|
|
252
|
+
if (options.skillLocalPath) {
|
|
253
|
+
await runNpxStepWithRetry(`${action} Confluence skill`, createSkillAddArgs(path.resolve(options.skillLocalPath)));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (options.skillSource === "local") {
|
|
257
|
+
await installSkillFromInstalledPackage(action);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (options.skillSource === "git") {
|
|
261
|
+
await runNpxStepWithRetry(`${action} Confluence skill`, createSkillAddArgs(GIT_SKILL_SOURCE));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
await installSkillFromNpmPackage(action);
|
|
265
|
+
}
|
|
266
|
+
async function installSkillFromInstalledPackage(action) {
|
|
267
|
+
const skillPath = await getInstalledPackageSkillPath();
|
|
268
|
+
try {
|
|
269
|
+
await access(skillPath);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
throw new Error(`未找到已安装包内的 Confluence skill:${skillPath}。可重试 --skill-source npm 或 --skill-source git。`);
|
|
273
|
+
}
|
|
274
|
+
await runNpxStepWithRetry(`${action} Confluence skill`, createSkillAddArgs(skillPath));
|
|
275
|
+
}
|
|
276
|
+
async function getInstalledPackageSkillPath() {
|
|
277
|
+
const globalNodeModules = (await runCommandOutput("npm", ["root", "-g"])).trim();
|
|
278
|
+
if (!globalNodeModules)
|
|
279
|
+
throw new Error("npm root -g 没有返回全局 node_modules 路径");
|
|
280
|
+
return path.join(globalNodeModules, PACKAGE_NAME, "skills", "confluence-cli");
|
|
281
|
+
}
|
|
282
|
+
async function installSkillFromNpmPackage(action) {
|
|
283
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), "confluence-cli-skill-"));
|
|
284
|
+
try {
|
|
285
|
+
const stdout = await runCommandOutput("npm", ["pack", `${PACKAGE_NAME}@latest`, "--pack-destination", tempDir, "--silent"]);
|
|
286
|
+
const tarballName = stdout.trim().split("\n").filter(Boolean).at(-1);
|
|
287
|
+
if (!tarballName)
|
|
288
|
+
throw new Error("npm pack 没有返回包文件名");
|
|
289
|
+
await runStep("解压 Confluence npm 包", "tar", ["-xzf", path.join(tempDir, tarballName), "-C", tempDir]);
|
|
290
|
+
await runNpxStepWithRetry(`${action} Confluence skill`, createSkillAddArgs(path.join(tempDir, "package")));
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function uninstallSkill() {
|
|
297
|
+
await runNpxStepWithRetry("卸载项目级 Confluence skill", createSkillRemoveArgs(false));
|
|
298
|
+
await runNpxStepWithRetry("卸载全局级 Confluence skill", createSkillRemoveArgs(true));
|
|
299
|
+
}
|
|
300
|
+
async function uninstallPackage() {
|
|
301
|
+
await runStep("卸载 Confluence CLI", "npm", ["uninstall", "-g", PACKAGE_NAME]);
|
|
302
|
+
await cleanupGlobalPackageResidues();
|
|
303
|
+
}
|
|
304
|
+
async function cleanupGlobalPackageResidues() {
|
|
305
|
+
const globalNodeModules = (await runCommandOutput("npm", ["root", "-g"])).trim();
|
|
306
|
+
if (globalNodeModules) {
|
|
307
|
+
await rm(path.join(globalNodeModules, PACKAGE_NAME), { recursive: true, force: true });
|
|
308
|
+
const scopeDir = path.join(globalNodeModules, "@cloudglab");
|
|
309
|
+
let entries = [];
|
|
310
|
+
try {
|
|
311
|
+
entries = await readdir(scopeDir);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
entries = [];
|
|
315
|
+
}
|
|
316
|
+
await Promise.all(entries
|
|
317
|
+
.filter((entry) => entry.startsWith(".confluence-cli-"))
|
|
318
|
+
.map((entry) => rm(path.join(scopeDir, entry), { recursive: true, force: true })));
|
|
319
|
+
}
|
|
320
|
+
await cleanupNpxResidues();
|
|
321
|
+
}
|
|
322
|
+
async function cleanupNpxResidues() {
|
|
323
|
+
const npxCacheDir = path.join(os.homedir(), ".npm", "_npx");
|
|
324
|
+
let entries = [];
|
|
325
|
+
try {
|
|
326
|
+
entries = await readdir(npxCacheDir);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
await Promise.all(entries.map(async (entry) => {
|
|
332
|
+
const hashDir = path.join(npxCacheDir, entry);
|
|
333
|
+
const cloudglabDir = path.join(hashDir, "node_modules", "@cloudglab");
|
|
334
|
+
let cloudglabEntries = [];
|
|
335
|
+
try {
|
|
336
|
+
cloudglabEntries = await readdir(cloudglabDir);
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const hasConfluenceCli = cloudglabEntries.some((item) => item === "confluence-cli" || item.startsWith(".confluence-cli-"));
|
|
342
|
+
if (hasConfluenceCli) {
|
|
343
|
+
await rm(hashDir, { recursive: true, force: true });
|
|
344
|
+
}
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
async function removeConfigFile() {
|
|
348
|
+
await rm(path.join(os.homedir(), ".confluence", "config.json"), { force: true });
|
|
349
|
+
}
|
|
350
|
+
async function ensureValidConfluenceConfig() {
|
|
351
|
+
const config = loadConfluenceConfig();
|
|
352
|
+
const api = new ConfluenceApi(config);
|
|
353
|
+
await api.getCurrentUser();
|
|
354
|
+
saveConfig(normalizeConfig(config));
|
|
355
|
+
}
|
|
356
|
+
async function runStep(title, command, args) {
|
|
357
|
+
process.stdout.write(`\n${title}...\n`);
|
|
358
|
+
await runCommand(command, args);
|
|
359
|
+
}
|
|
360
|
+
function runCommand(command, args) {
|
|
361
|
+
return new Promise((resolve, reject) => {
|
|
362
|
+
const child = spawn(command, args, { shell: process.platform === "win32" });
|
|
363
|
+
let stderr = "";
|
|
364
|
+
child.stdout?.pipe(process.stdout);
|
|
365
|
+
child.stderr?.on("data", (chunk) => {
|
|
366
|
+
stderr += chunk.toString("utf8");
|
|
367
|
+
process.stderr.write(chunk);
|
|
368
|
+
});
|
|
369
|
+
child.on("error", reject);
|
|
370
|
+
child.on("close", (code) => {
|
|
371
|
+
if (code === 0) {
|
|
372
|
+
resolve();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
reject(new Error(`${command} ${args.join(" ")} 执行失败,退出码 ${String(code)}${stderr ? `:${stderr.trim()}` : ""}`));
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function runCommandOutput(command, args) {
|
|
380
|
+
return new Promise((resolve, reject) => {
|
|
381
|
+
const child = spawn(command, args, { shell: process.platform === "win32" });
|
|
382
|
+
let stdout = "";
|
|
383
|
+
let stderr = "";
|
|
384
|
+
child.stdout?.on("data", (chunk) => { stdout += chunk.toString("utf8"); });
|
|
385
|
+
child.stderr?.on("data", (chunk) => { stderr += chunk.toString("utf8"); });
|
|
386
|
+
child.on("error", reject);
|
|
387
|
+
child.on("close", (code) => {
|
|
388
|
+
if (code === 0) {
|
|
389
|
+
resolve(stdout);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
reject(new Error(`${command} ${args.join(" ")} 执行失败,退出码 ${String(code)}${stderr ? `:${stderr.trim()}` : ""}`));
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function renderBanner() {
|
|
397
|
+
return [
|
|
398
|
+
" ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ",
|
|
399
|
+
" /\\ \\ /\\ \\ /\\__\\ /\\ \\ /\\__\\ /\\__\\ /\\ \\ /\\__\\ /\\ \\ /\\ \\ ",
|
|
400
|
+
" /::\\ \\ /::\\ \\ /:| _|_ /::\\ \\ /:/ / /:/ _/_ /::\\ \\ /:| _|_ /::\\ \\ /::\\ \\ ",
|
|
401
|
+
"/:/\\:\\__\\ /:/\\:\\__\\ /::|/\\__\\ /::\\:\\__\\ /:/__/ /:/_/\\__\\ /::\\:\\__\\ /::|/\\__\\ /:/\\:\\__\\ /::\\:\\__\\",
|
|
402
|
+
"\\:\\ \/__/ \\:\\/:/ / \\/|::/ / \\/\\:\\/__/ \\:\\ \\ \\:\\/:/ / \\:\\:\\/ / \\/|::/ / \\:\\ \/__/ \\:\\:\\/ /",
|
|
403
|
+
" \\:\\__\\ \\::/ / |:/ / \/__/ \\:\\__\\ \\::/ / \\:\\/ / |:/ / \\:\\__\\ \\:\\/ / ",
|
|
404
|
+
" \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ ",
|
|
405
|
+
].join("\n");
|
|
406
|
+
}
|
|
407
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.0.1",
|
|
3
|
+
"commands": [
|
|
4
|
+
"addComment",
|
|
5
|
+
"addLabels",
|
|
6
|
+
"callRestApi",
|
|
7
|
+
"convertContentBody",
|
|
8
|
+
"convertMarkdownToWiki",
|
|
9
|
+
"convertMermaidToDrawio",
|
|
10
|
+
"deleteContent",
|
|
11
|
+
"deleteLabel",
|
|
12
|
+
"downloadAttachment",
|
|
13
|
+
"downloadPage",
|
|
14
|
+
"findContent",
|
|
15
|
+
"generateMarkMetadata",
|
|
16
|
+
"getComments",
|
|
17
|
+
"getContent",
|
|
18
|
+
"getCurrentUser",
|
|
19
|
+
"getLabels",
|
|
20
|
+
"getPageChildren",
|
|
21
|
+
"getSpace",
|
|
22
|
+
"initConfluence",
|
|
23
|
+
"install",
|
|
24
|
+
"listAttachments",
|
|
25
|
+
"listRestApis",
|
|
26
|
+
"listSpaces",
|
|
27
|
+
"remove",
|
|
28
|
+
"searchContent",
|
|
29
|
+
"uninstall",
|
|
30
|
+
"update",
|
|
31
|
+
"updateAttachment",
|
|
32
|
+
"uploadAttachment",
|
|
33
|
+
"uploadHtml",
|
|
34
|
+
"uploadMarkdown"
|
|
35
|
+
],
|
|
36
|
+
"groups": {
|
|
37
|
+
"init": [
|
|
38
|
+
"initConfluence"
|
|
39
|
+
],
|
|
40
|
+
"install": [
|
|
41
|
+
"install",
|
|
42
|
+
"remove",
|
|
43
|
+
"uninstall",
|
|
44
|
+
"update"
|
|
45
|
+
],
|
|
46
|
+
"convert": [
|
|
47
|
+
"convertMarkdownToWiki",
|
|
48
|
+
"convertMermaidToDrawio"
|
|
49
|
+
],
|
|
50
|
+
"metadata": [
|
|
51
|
+
"generateMarkMetadata"
|
|
52
|
+
],
|
|
53
|
+
"rest": [
|
|
54
|
+
"callRestApi",
|
|
55
|
+
"listRestApis"
|
|
56
|
+
],
|
|
57
|
+
"space": [
|
|
58
|
+
"convertContentBody",
|
|
59
|
+
"getCurrentUser",
|
|
60
|
+
"getSpace",
|
|
61
|
+
"listSpaces"
|
|
62
|
+
],
|
|
63
|
+
"content": [
|
|
64
|
+
"addComment",
|
|
65
|
+
"deleteContent",
|
|
66
|
+
"findContent",
|
|
67
|
+
"getComments",
|
|
68
|
+
"getContent",
|
|
69
|
+
"getPageChildren",
|
|
70
|
+
"searchContent"
|
|
71
|
+
],
|
|
72
|
+
"labels": [
|
|
73
|
+
"addLabels",
|
|
74
|
+
"deleteLabel",
|
|
75
|
+
"getLabels"
|
|
76
|
+
],
|
|
77
|
+
"attachments": [
|
|
78
|
+
"downloadAttachment",
|
|
79
|
+
"listAttachments",
|
|
80
|
+
"updateAttachment",
|
|
81
|
+
"uploadAttachment"
|
|
82
|
+
],
|
|
83
|
+
"transfer": [
|
|
84
|
+
"downloadPage",
|
|
85
|
+
"uploadHtml",
|
|
86
|
+
"uploadMarkdown"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
"commandToGroup": {
|
|
90
|
+
"initConfluence": "init",
|
|
91
|
+
"install": "install",
|
|
92
|
+
"remove": "install",
|
|
93
|
+
"uninstall": "install",
|
|
94
|
+
"update": "install",
|
|
95
|
+
"convertMarkdownToWiki": "convert",
|
|
96
|
+
"convertMermaidToDrawio": "convert",
|
|
97
|
+
"generateMarkMetadata": "metadata",
|
|
98
|
+
"callRestApi": "rest",
|
|
99
|
+
"listRestApis": "rest",
|
|
100
|
+
"convertContentBody": "space",
|
|
101
|
+
"getCurrentUser": "space",
|
|
102
|
+
"getSpace": "space",
|
|
103
|
+
"listSpaces": "space",
|
|
104
|
+
"addComment": "content",
|
|
105
|
+
"deleteContent": "content",
|
|
106
|
+
"findContent": "content",
|
|
107
|
+
"getComments": "content",
|
|
108
|
+
"getContent": "content",
|
|
109
|
+
"getPageChildren": "content",
|
|
110
|
+
"searchContent": "content",
|
|
111
|
+
"addLabels": "labels",
|
|
112
|
+
"deleteLabel": "labels",
|
|
113
|
+
"getLabels": "labels",
|
|
114
|
+
"downloadAttachment": "attachments",
|
|
115
|
+
"listAttachments": "attachments",
|
|
116
|
+
"updateAttachment": "attachments",
|
|
117
|
+
"uploadAttachment": "attachments",
|
|
118
|
+
"downloadPage": "transfer",
|
|
119
|
+
"uploadHtml": "transfer",
|
|
120
|
+
"uploadMarkdown": "transfer"
|
|
121
|
+
}
|
|
122
|
+
}
|