sdd-flow-kit 1.0.5 → 1.0.7
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 +7 -7
- package/dist/cli.js +1 -1
- package/dist/steps/setupProject.js +39 -0
- package/dist/steps/startFlow.js +7 -1
- package/package.json +1 -1
- package/src/templates/artifacts/02-/351/234/200/346/261/202/345/210/206/346/236/220/346/217/220/347/244/272/350/257/215.template.md +9 -0
- package/src/templates/artifacts/05-openspec-/346/217/220/346/241/210/350/215/211/347/250/277.template.md +5 -0
- package/src/templates/prompts/02-sdd-loop-prompt.md +6 -0
- package/src/templates/skills/confluence-doc.py.template +38 -35
- package/src/templates/skills/doc-skill.SKILL.md.template +2 -2
package/README.md
CHANGED
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
#### 安装教程
|
|
15
15
|
|
|
16
16
|
1. 进入你要开发的项目目录
|
|
17
|
-
2. 安装:`pnpm add -D sdd-flow-kit
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
3.
|
|
22
|
-
-
|
|
23
|
-
|
|
17
|
+
2. 安装:`pnpm add -D sdd-flow-kit `
|
|
18
|
+
检查是否安装 - `ls .cursor/skills/sdd-flow-kit/SKILL.md`
|
|
19
|
+
- `ls .claude/skills/sdd-flow-kit/SKILL.md`
|
|
20
|
+
- `ls docs/*-doc-skill/SKILL.md`
|
|
21
|
+
3. 若仍未生成,执行手动兜底(100%触发初始化):
|
|
22
|
+
- `node ./node_modules/sdd-flow-kit/dist/postinstall.js`
|
|
23
|
+
|
|
24
24
|
4. (可选)预演安装:`npx sdd-flow-kit install --dry-run --plan-json`
|
|
25
25
|
5. (可选)环境健检:`npx sdd-flow-kit doctor --agent claude-code`
|
|
26
26
|
6. 完成后,直接在 Cursor / Claude Code / Codex / OpenClaw 中用自然语言发起需求,例如:`开发 ADI 2.3.2 需求`
|
package/dist/cli.js
CHANGED
|
@@ -122,7 +122,7 @@ async function main() {
|
|
|
122
122
|
projectKind: projectKind,
|
|
123
123
|
});
|
|
124
124
|
// eslint-disable-next-line no-console
|
|
125
|
-
console.log(`setup 完成:agent=${result.agent}, project=${result.projectKind}, branch=${result.branch}, setupExecuted=${result.setupExecuted}, skillInstalled=${result.skillInstalled}, docsSkillInstalled=${result.docsSkillInstalled}, dryRun=${dryRun}`);
|
|
125
|
+
console.log(`setup 完成:agent=${result.agent}, project=${result.projectKind}, branch=${result.branch}, setupExecuted=${result.setupExecuted}, skillInstalled=${result.skillInstalled}, docsSkillInstalled=${result.docsSkillInstalled}, opsxConfigInstalled=${result.opsxConfigInstalled}, dryRun=${dryRun}`);
|
|
126
126
|
if (result.plannedActions.length > 0) {
|
|
127
127
|
// eslint-disable-next-line no-console
|
|
128
128
|
console.log(`planned: ${result.plannedActions.join(" | ")}`);
|
|
@@ -220,6 +220,39 @@ async function installDocsSkill(projectRoot, kind) {
|
|
|
220
220
|
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(docsDir, "confluence-doc.py"), py);
|
|
221
221
|
return { dirPath: docsDir };
|
|
222
222
|
}
|
|
223
|
+
function getOpsxPathsByAgent(agent) {
|
|
224
|
+
switch (agent) {
|
|
225
|
+
case "cursor":
|
|
226
|
+
return { editor: "cursor", skillsDir: ".cursor/skills", commandsDir: ".cursor/commands" };
|
|
227
|
+
case "claude-code":
|
|
228
|
+
return { editor: "claude-code", skillsDir: ".claude/skills", commandsDir: ".claude/commands" };
|
|
229
|
+
case "codex":
|
|
230
|
+
return { editor: "codex", skillsDir: ".codex/skills", commandsDir: ".codex/commands" };
|
|
231
|
+
case "openclaw":
|
|
232
|
+
return { editor: "openclaw", skillsDir: ".openclaw/skills", commandsDir: ".openclaw/commands" };
|
|
233
|
+
default:
|
|
234
|
+
return { editor: "custom", skillsDir: ".sdd-flow-kit/skills", commandsDir: ".sdd-flow-kit/commands" };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function installOpsxConfig(projectRoot, agent) {
|
|
238
|
+
const target = node_path_1.default.join(projectRoot, ".opsx", "config.json");
|
|
239
|
+
const paths = getOpsxPathsByAgent(agent);
|
|
240
|
+
const config = {
|
|
241
|
+
version: 1,
|
|
242
|
+
editor: paths.editor,
|
|
243
|
+
skillsDir: paths.skillsDir,
|
|
244
|
+
commandsDir: paths.commandsDir,
|
|
245
|
+
deliveryPipeline: {
|
|
246
|
+
skillName: "opsx-dev-delivery-pipeline",
|
|
247
|
+
packageManager: "pnpm",
|
|
248
|
+
unitScript: "unit",
|
|
249
|
+
playwrightCommand: "exec playwright test",
|
|
250
|
+
devServerUrl: "http://localhost:5173",
|
|
251
|
+
playwrightPicDir: "playwright-pic",
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
await (0, fs_1.writeTextFileEnsuredDir)(target, JSON.stringify(config, null, 2));
|
|
255
|
+
}
|
|
223
256
|
async function setupProject(options) {
|
|
224
257
|
const defaultAgent = options.agent ?? "claude-code";
|
|
225
258
|
const selectedAgent = options.yes ? defaultAgent : await askAgent(defaultAgent);
|
|
@@ -242,6 +275,7 @@ async function setupProject(options) {
|
|
|
242
275
|
let setupExecuted = false;
|
|
243
276
|
let skillInstalled = false;
|
|
244
277
|
let docsSkillInstalled = false;
|
|
278
|
+
let opsxConfigInstalled = false;
|
|
245
279
|
if (!environmentReady) {
|
|
246
280
|
const { cmd, args } = buildSetupCommand(options.projectRoot, selectedAgent);
|
|
247
281
|
if (options.dryRun) {
|
|
@@ -269,12 +303,15 @@ async function setupProject(options) {
|
|
|
269
303
|
}
|
|
270
304
|
const { dir } = docSkillDirName(selectedKind);
|
|
271
305
|
plannedActions.push(`would install docs skill: ${node_path_1.default.join(options.projectRoot, "docs", dir)}`);
|
|
306
|
+
plannedActions.push(`would install opsx config: ${node_path_1.default.join(options.projectRoot, ".opsx", "config.json")}`);
|
|
272
307
|
}
|
|
273
308
|
else {
|
|
274
309
|
await installAgentSkill(options.projectRoot);
|
|
275
310
|
skillInstalled = true;
|
|
276
311
|
await installDocsSkill(options.projectRoot, selectedKind);
|
|
277
312
|
docsSkillInstalled = true;
|
|
313
|
+
await installOpsxConfig(options.projectRoot, selectedAgent);
|
|
314
|
+
opsxConfigInstalled = true;
|
|
278
315
|
}
|
|
279
316
|
const summary = [
|
|
280
317
|
"# SETUP-SUMMARY",
|
|
@@ -287,6 +324,7 @@ async function setupProject(options) {
|
|
|
287
324
|
`setupExecuted: ${setupExecuted}`,
|
|
288
325
|
`skillInstalled: ${skillInstalled}`,
|
|
289
326
|
`docsSkillInstalled: ${docsSkillInstalled}`,
|
|
327
|
+
`opsxConfigInstalled: ${opsxConfigInstalled}`,
|
|
290
328
|
`dryRun: ${Boolean(options.dryRun)}`,
|
|
291
329
|
"",
|
|
292
330
|
"plannedActions:",
|
|
@@ -304,5 +342,6 @@ async function setupProject(options) {
|
|
|
304
342
|
plannedActions,
|
|
305
343
|
skillInstalled,
|
|
306
344
|
docsSkillInstalled,
|
|
345
|
+
opsxConfigInstalled,
|
|
307
346
|
};
|
|
308
347
|
}
|
package/dist/steps/startFlow.js
CHANGED
|
@@ -81,9 +81,11 @@ async function startFlowScaffold(options) {
|
|
|
81
81
|
"",
|
|
82
82
|
"## Step 1:获取需求文档(docs/<project>-doc-skill)",
|
|
83
83
|
"请打开 `01-获取需求文档指引.md`,并使用你的 AI 工具执行相应动作,把 PRD/接口文档回填到 `source/`。",
|
|
84
|
+
"门禁:`source/PRD.md` 若仍是占位文本,禁止进入 Step2。",
|
|
84
85
|
"",
|
|
85
86
|
"## Step 2:执行 SDD 循环并生成 01/02/03 文件",
|
|
86
|
-
"请打开 `02-需求分析提示词.md`,在 Cursor/Claude Code/Codex/OpenClaw
|
|
87
|
+
"请打开 `02-需求分析提示词.md`,在 Cursor/Claude Code/Codex/OpenClaw 中执行。",
|
|
88
|
+
"门禁:提问模式仅允许更新 `03-待确认问题清单.md`;闭环后才允许输出最终版 `01/02`。",
|
|
87
89
|
"",
|
|
88
90
|
"## Step 3:人工核验待确认问题",
|
|
89
91
|
"将 `03-待确认问题清单.md` 收敛为闭环(必要时调整 01/02)。",
|
|
@@ -93,6 +95,10 @@ async function startFlowScaffold(options) {
|
|
|
93
95
|
"",
|
|
94
96
|
"## Step 5:触发 openspec + superpowers 提案",
|
|
95
97
|
"使用 `05-openspec-提案草稿.md` 作为 openspec 的提案输入入口。",
|
|
98
|
+
"",
|
|
99
|
+
"## Step 6:执行 /opsx-apply",
|
|
100
|
+
"必须在当前仓库的本地 OPSX skills/config 下执行,不允许跨仓 fallback 到其它目录。",
|
|
101
|
+
"门禁:delivery-pipeline 未成功(exit 0)前,禁止宣称 apply 完成。",
|
|
96
102
|
].join("\n"));
|
|
97
103
|
return {
|
|
98
104
|
runId,
|
package/package.json
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
--- 你将要运行的提示词结束 ---
|
|
10
10
|
|
|
11
|
+
## 执行门禁(必须遵守,和手动流程一致)
|
|
12
|
+
- `source/PRD.md` 若仍是占位文本(没有真实 PRD 内容),**禁止**继续 Step2,先返回“Step1 未完成”并停止。
|
|
13
|
+
- 当处于“提问模式”时,**只允许更新** `03-待确认问题清单.md`;**禁止**产出最终版 `01-需求分析报告.md` / `02-改动点清单.md`。
|
|
14
|
+
- 只有当用户明确确认“矛盾点池已闭合”后,才可进入“交付模式”并输出最终版 `01` / `02`。
|
|
15
|
+
|
|
11
16
|
## 输入注入(建议做法)
|
|
12
17
|
- 将 `source/PRD.md` 的内容作为 `{{PRD_MARKDOWN}}` 注入
|
|
13
18
|
- 将 `source/接口文档.md` 的内容作为 `{{INTERFACE_DOC_MARKDOWN}}` 注入(没有则忽略)
|
|
@@ -18,3 +23,7 @@
|
|
|
18
23
|
- `02-改动点清单.md`
|
|
19
24
|
- `03-待确认问题清单.md`
|
|
20
25
|
|
|
26
|
+
## 输出话术(建议固定)
|
|
27
|
+
- 提问模式:`SDD 模式:提问模式;矛盾点池状态:未闭合;仅更新 03,等待人工确认。`
|
|
28
|
+
- 交付模式:`SDD 模式:交付模式;矛盾点池状态:已闭合;已输出最终版 01/02/03。`
|
|
29
|
+
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
3. 将本文件中的内容复制给 openspec + superpowers 的“提案输入”
|
|
9
9
|
4. 进入生成 UI 图 + 任务拆解 + 开发阶段
|
|
10
10
|
|
|
11
|
+
## 门禁(与手动流程一致)
|
|
12
|
+
- 仅当 `03-待确认问题清单.md` 已明确“矛盾点池状态:已闭合”时,才可进入提案阶段。
|
|
13
|
+
- 若 `03` 未闭合,禁止进入提案,先回到 Step3 完成确认。
|
|
14
|
+
- 提案完成后进入 `/opsx-apply`,必须在当前仓库本地 OPSX 配置中执行。
|
|
15
|
+
|
|
11
16
|
## 输入摘要
|
|
12
17
|
- `01-需求分析报告.md`:已完成
|
|
13
18
|
- `02-改动点清单.md`:已完成(仅代码文件改动)
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
|
|
58
58
|
请根据当前状态选择执行路径:
|
|
59
59
|
|
|
60
|
+
### 0) 前置门禁(必须先检查)
|
|
61
|
+
- 若 `source/PRD.md` 未回填真实 PRD(仍为占位),立即停止并返回:`Step1 未完成:请先完成 PRD 回填`。
|
|
62
|
+
- 不得在 PRD 缺失时产出 `01/02/03` 的“最终版”内容。
|
|
63
|
+
|
|
60
64
|
#### 路径 A:提问模式 (当前存在矛盾点)
|
|
61
65
|
> **触发条件**:当你发现 PRD 与现有代码库不一致,或代码逻辑存在盲区时。
|
|
62
66
|
|
|
@@ -64,6 +68,7 @@
|
|
|
64
68
|
1. **不要强行编写解决方案。**
|
|
65
69
|
2. 列出所有 **断点**、**起点缺失** 和 **推测缺口**。
|
|
66
70
|
3. 输出 `03-待确认问题清单.md`,并暂停生成最终报告,等待人工输入。
|
|
71
|
+
4. 明确标注:`矛盾点池状态:未闭合`。
|
|
67
72
|
|
|
68
73
|
---
|
|
69
74
|
|
|
@@ -74,6 +79,7 @@
|
|
|
74
79
|
1. 生成最终版 `01-需求分析报告.md` 和 `02-改动点清单.md`。
|
|
75
80
|
2. 在报告中明确标注“**矛盾点池状态:已闭合**”。
|
|
76
81
|
3. 确认 `02-改动点清单.md` 中仅包含代码文件改动,不涉及 `openspec/spec/` 内容。
|
|
82
|
+
4. 输出时附带一句:`可进入 Step4 技术文档与 Step5 提案阶段`。
|
|
77
83
|
|
|
78
84
|
---
|
|
79
85
|
|
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Confluence 需求文档获取脚本(Playwright)
|
|
4
|
-
|
|
5
|
-
用法:
|
|
6
|
-
python3 confluence-doc.py <版本号>
|
|
7
|
-
|
|
8
|
-
环境变量:
|
|
9
|
-
CONFLUENCE_BASE_URL 例如 https://confluence.huan.tv
|
|
10
|
-
CONFLUENCE_SEARCH_URL 例如 https://confluence.huan.tv/dosearchsite.action?includeArchivedSpaces=false
|
|
11
|
-
CONFLUENCE_USERNAME
|
|
12
|
-
CONFLUENCE_PASSWORD
|
|
13
|
-
DOC_PRODUCT_PREFIX 例如 ADI / OMS / HUAN / ADTOOLS
|
|
14
|
-
DOC_DOWNLOAD_DIR 输出目录(默认当前脚本所在目录的上级 docs/)
|
|
15
|
-
|
|
16
|
-
注意:
|
|
17
|
-
- 为安全起见,本模板不包含任何默认账号密码
|
|
18
|
-
- 你需要先安装 playwright:
|
|
19
|
-
pip install playwright && playwright install chromium
|
|
20
|
-
"""
|
|
2
|
+
"""Confluence 需求文档获取脚本(Playwright - 通用版)"""
|
|
21
3
|
|
|
22
4
|
import sys
|
|
23
5
|
import os
|
|
@@ -34,9 +16,9 @@ except ImportError:
|
|
|
34
16
|
|
|
35
17
|
def env(name: str, default: str | None = None) -> str:
|
|
36
18
|
v = os.environ.get(name, default)
|
|
37
|
-
if v is None
|
|
38
|
-
|
|
39
|
-
return str(v)
|
|
19
|
+
if v is None:
|
|
20
|
+
return ""
|
|
21
|
+
return str(v).strip()
|
|
40
22
|
|
|
41
23
|
|
|
42
24
|
def load_env_file(file_path: Path) -> None:
|
|
@@ -89,6 +71,11 @@ def parse_html_to_markdown(html_content: str) -> str:
|
|
|
89
71
|
md = re.sub(r"<li[^>]*>(.*?)</li>", r"- \1\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
90
72
|
md = re.sub(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', r"[\2](\1)", md, flags=re.DOTALL | re.IGNORECASE)
|
|
91
73
|
md = re.sub(r'<img[^>]*src="([^"]+)"[^>]*>', r"", md, flags=re.DOTALL | re.IGNORECASE)
|
|
74
|
+
md = re.sub(r"<table[^>]*>", r"\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
75
|
+
md = re.sub(r"</table>", r"\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
76
|
+
md = re.sub(r"<tr[^>]*>", r"|", md, flags=re.DOTALL | re.IGNORECASE)
|
|
77
|
+
md = re.sub(r"</tr>", r"\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
78
|
+
md = re.sub(r"<t[dh][^>]*>(.*?)</t[dh]>", r"|\1", md, flags=re.DOTALL | re.IGNORECASE)
|
|
92
79
|
md = re.sub(r"<p[^>]*>(.*?)</p>", r"\1\n\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
93
80
|
md = re.sub(r"<br[^>]*>", r"\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
94
81
|
md = re.sub(r"<strong[^>]*>(.*?)</strong>", r"**\1**", md, flags=re.DOTALL | re.IGNORECASE)
|
|
@@ -167,20 +154,20 @@ def find_search_result(page, base_url: str, keyword: str):
|
|
|
167
154
|
return best
|
|
168
155
|
|
|
169
156
|
|
|
170
|
-
def run_search(page, base_url: str, keyword: str
|
|
171
|
-
page.goto(search_url, wait_until="networkidle", timeout=60000)
|
|
172
|
-
page.wait_for_timeout(800)
|
|
157
|
+
def run_search(page, base_url: str, keyword: str) -> str | None:
|
|
173
158
|
search_selector = None
|
|
174
159
|
for selector in ["#search-query-input", "input[name='search-query']", "input.search-query", "input[id*='search']"]:
|
|
175
160
|
if page.locator(selector).count() > 0:
|
|
176
161
|
search_selector = selector
|
|
177
162
|
break
|
|
178
163
|
if not search_selector:
|
|
164
|
+
if page.locator("input[type='text']").count() == 0:
|
|
165
|
+
return None
|
|
179
166
|
search_selector = "input[type='text']"
|
|
180
|
-
page.fill(search_selector, keyword)
|
|
167
|
+
page.fill(search_selector, keyword, timeout=8000)
|
|
181
168
|
page.keyboard.press("Enter")
|
|
182
169
|
page.wait_for_load_state("networkidle", timeout=30000)
|
|
183
|
-
page.wait_for_timeout(
|
|
170
|
+
page.wait_for_timeout(2000)
|
|
184
171
|
return find_search_result(page, base_url, keyword)
|
|
185
172
|
|
|
186
173
|
|
|
@@ -195,10 +182,10 @@ def main():
|
|
|
195
182
|
for env_name in [".env.pre", ".env", ".env.local", ".env.development", ".env.test"]:
|
|
196
183
|
load_env_file(repo_root / env_name)
|
|
197
184
|
|
|
198
|
-
base_url = env("CONFLUENCE_BASE_URL")
|
|
199
|
-
search_url = env("CONFLUENCE_SEARCH_URL"
|
|
200
|
-
username = env("CONFLUENCE_USERNAME")
|
|
201
|
-
password = env("CONFLUENCE_PASSWORD")
|
|
185
|
+
base_url = env("CONFLUENCE_BASE_URL", "https://confluence.huan.tv") or "https://confluence.huan.tv"
|
|
186
|
+
search_url = env("CONFLUENCE_SEARCH_URL") or f"{base_url.rstrip('/')}/dosearchsite.action?includeArchivedSpaces=false"
|
|
187
|
+
username = env("CONFLUENCE_USERNAME") or env("CONFLUENCE_USER")
|
|
188
|
+
password = env("CONFLUENCE_PASSWORD") or env("CONFLUENCE_PASS")
|
|
202
189
|
prefix = os.environ.get("DOC_PRODUCT_PREFIX", "{{DOC_PRODUCT_PREFIX}}").strip() or "{{DOC_PRODUCT_PREFIX}}"
|
|
203
190
|
version = normalize_version(sys.argv[1])
|
|
204
191
|
keywords = search_keywords(prefix, version)
|
|
@@ -221,23 +208,34 @@ def main():
|
|
|
221
208
|
|
|
222
209
|
with sync_playwright() as p:
|
|
223
210
|
browser = p.chromium.launch(headless=True)
|
|
224
|
-
context = browser.new_context(
|
|
211
|
+
context = browser.new_context(
|
|
212
|
+
user_agent=(
|
|
213
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
|
214
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
215
|
+
)
|
|
216
|
+
)
|
|
225
217
|
page = context.new_page()
|
|
226
218
|
try:
|
|
227
|
-
|
|
228
|
-
page.goto(search_url, wait_until="networkidle", timeout=60000)
|
|
219
|
+
print("\n📄 访问搜索页面...")
|
|
220
|
+
response = page.goto(search_url, wait_until="networkidle", timeout=60000)
|
|
221
|
+
if response:
|
|
222
|
+
print(f" 状态码: {response.status}")
|
|
229
223
|
if "login" in page.url.lower():
|
|
224
|
+
if not username or not password:
|
|
225
|
+
raise RuntimeError("检测到登录页但未提供 CONFLUENCE_USERNAME / CONFLUENCE_PASSWORD")
|
|
226
|
+
print("🔐 检测到需要登录,开始自动登录...")
|
|
230
227
|
page.wait_for_selector("#username-field", timeout=10000)
|
|
231
228
|
page.fill("#username-field", username)
|
|
232
229
|
page.fill("#password-field", password)
|
|
233
230
|
page.click("#login-button")
|
|
234
231
|
page.wait_for_load_state("networkidle", timeout=30000)
|
|
232
|
+
print("✅ 登录完成")
|
|
235
233
|
|
|
236
234
|
target_url = None
|
|
237
235
|
matched_keyword = None
|
|
238
236
|
for kw in keywords:
|
|
239
237
|
print(f"\n🔍 搜索关键词: {kw}")
|
|
240
|
-
found = run_search(page, base_url, kw
|
|
238
|
+
found = run_search(page, base_url, kw)
|
|
241
239
|
if found:
|
|
242
240
|
target_url = found
|
|
243
241
|
matched_keyword = kw
|
|
@@ -260,6 +258,8 @@ def main():
|
|
|
260
258
|
content_elem = page.locator("#main-content")
|
|
261
259
|
if not content_elem.count():
|
|
262
260
|
content_elem = page.locator(".wiki-content")
|
|
261
|
+
if not content_elem.count():
|
|
262
|
+
content_elem = page.locator(".content-body")
|
|
263
263
|
content_html = content_elem.inner_html() if content_elem.count() else ""
|
|
264
264
|
|
|
265
265
|
img_urls = re.findall(r'<img[^>]*src="([^"]+)"', content_html)
|
|
@@ -304,6 +304,9 @@ def main():
|
|
|
304
304
|
print("\n✅ 获取完成!")
|
|
305
305
|
print(f"📄 主文档: {md_path}")
|
|
306
306
|
print(f"🖼️ 图片目录: {version_dir}")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
print(f"\n❌ 获取失败: {e}")
|
|
309
|
+
raise
|
|
307
310
|
finally:
|
|
308
311
|
browser.close()
|
|
309
312
|
|
|
@@ -21,7 +21,6 @@ description: |
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
cd docs/{{SKILL_DIR}}
|
|
24
|
-
export CONFLUENCE_BASE_URL="https://confluence.huan.tv"
|
|
25
24
|
export CONFLUENCE_USERNAME="<你的账号>"
|
|
26
25
|
export CONFLUENCE_PASSWORD="<你的密码>"
|
|
27
26
|
export DOC_PRODUCT_PREFIX="{{PRODUCT_PREFIX}}"
|
|
@@ -52,5 +51,6 @@ python3 confluence-doc.py {{PRODUCT_PREFIX}}_V1.2.3
|
|
|
52
51
|
- 依赖 Playwright:`pip install playwright && playwright install chromium`
|
|
53
52
|
- 脚本:`docs/{{SKILL_DIR}}/confluence-doc.py`
|
|
54
53
|
- 为安全起见,脚本不包含默认账号密码,请用环境变量注入
|
|
55
|
-
-
|
|
54
|
+
- 默认 Confluence 地址为 `https://confluence.huan.tv`(可用 `CONFLUENCE_BASE_URL` 覆盖)
|
|
55
|
+
- 脚本会自动尝试读取仓库根目录的 `.env.pre` / `.env` / `.env.local` / `.env.development` / `.env.test`(若其中存在 `CONFLUENCE_*` 配置可直接复用)
|
|
56
56
|
|