@hupan56/wlkj 3.1.32 → 3.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/bin/cli.js +117 -0
- package/package.json +1 -1
- package/templates/qoder/agents/insight-planning.md +67 -67
- package/templates/qoder/agents/prd-reference.md +47 -47
- package/templates/qoder/commands/optional/wl-insight.md +4 -4
- package/templates/qoder/commands/optional/wl-report.md +1 -1
- package/templates/qoder/commands/optional/wl-spec.md +23 -3
- package/templates/qoder/commands/optional/wl-status.md +12 -1
- package/templates/qoder/commands/wl-code.md +138 -7
- package/templates/qoder/commands/wl-commit.md +12 -1
- package/templates/qoder/commands/wl-design.md +70 -6
- package/templates/qoder/commands/wl-init.md +27 -0
- package/templates/qoder/commands/wl-prd.md +230 -15
- package/templates/qoder/commands/wl-req.md +10 -3
- package/templates/qoder/commands/wl-search.md +74 -20
- package/templates/qoder/commands/wl-task.md +3 -3
- package/templates/qoder/commands/wl-test.md +17 -2
- package/templates/qoder/contracts/CHANGELOG.md +418 -0
- package/templates/qoder/contracts/README.md +180 -0
- package/templates/qoder/contracts/code.md +82 -0
- package/templates/qoder/contracts/commit.md +86 -0
- package/templates/qoder/contracts/contract-header.md +76 -0
- package/templates/qoder/contracts/design.md +106 -0
- package/templates/qoder/contracts/fallback.md +126 -0
- package/templates/qoder/contracts/isolation.md +119 -0
- package/templates/qoder/contracts/prd.md +118 -0
- package/templates/qoder/contracts/schemas/design-spec.schema.json +46 -0
- package/templates/qoder/contracts/schemas/prd.schema.json +36 -0
- package/templates/qoder/contracts/schemas/test-cases.schema.json +40 -0
- package/templates/qoder/contracts/spec.md +116 -0
- package/templates/qoder/contracts/task.md +125 -0
- package/templates/qoder/contracts/test.md +112 -0
- package/templates/qoder/hooks/post-tool-use.py +61 -0
- package/templates/qoder/hooks/session-start.py +34 -66
- package/templates/qoder/hooks/stop-eval.py +47 -0
- package/templates/qoder/rules/wl-pipeline.md +37 -0
- package/templates/qoder/scripts/capability/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry_mcp.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +1 -1
- package/templates/qoder/scripts/capability/adapters/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/cli.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/mcp.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/qw.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +76 -100
- package/templates/qoder/scripts/capability/adapters/qw.py +295 -295
- package/templates/qoder/scripts/capability/caps/__init__.py +1 -1
- package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/context.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/cron.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/identity.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/memory.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/present.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/repo.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/memory.py +1 -1
- package/templates/qoder/scripts/capability/registry.py +21 -23
- package/templates/qoder/scripts/capability/registry_mcp.py +69 -5
- package/templates/qoder/scripts/capability/smoke_test_report.json +34 -20
- package/templates/qoder/scripts/deployment/setup/carriers.py +3 -1
- package/templates/qoder/scripts/deployment/setup/init_doctor.py +10 -3
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +11 -0
- package/templates/qoder/scripts/deployment/setup/wlkj_shim.py +104 -0
- package/templates/qoder/scripts/domain/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/deployment/deploy_to_test.py +298 -0
- package/templates/qoder/scripts/domain/integration/__init__.py +0 -0
- package/templates/qoder/scripts/domain/integration/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +392 -0
- package/templates/qoder/scripts/domain/integration/spec_upload.py +209 -0
- package/templates/qoder/scripts/domain/kg/build/kg_build.py +268 -25
- package/templates/qoder/scripts/domain/kg/build/kg_incremental.py +108 -3
- package/templates/qoder/scripts/domain/kg/build/kg_signatures.py +169 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
- package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
- package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
- package/templates/qoder/scripts/domain/kg/extract/extract.py +84 -0
- package/templates/qoder/scripts/domain/kg/extract/extract.py.bak +430 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
- package/templates/qoder/scripts/domain/kg/extract/java/__init__.py +15 -0
- package/templates/qoder/scripts/domain/kg/extract/java/_parser.py +271 -0
- package/templates/qoder/scripts/domain/kg/extract/java/all.py +145 -0
- package/templates/qoder/scripts/domain/kg/extract/java/build_java_to_pg.py +102 -0
- package/templates/qoder/scripts/domain/kg/extract/java/call_chain.py +49 -0
- package/templates/qoder/scripts/domain/kg/extract/java/class_extractor.py +141 -0
- package/templates/qoder/scripts/domain/kg/extract/java/domain_extractor.py +148 -0
- package/templates/qoder/scripts/domain/kg/extract/java/dubbo_extractor.py +33 -0
- package/templates/qoder/scripts/domain/kg/extract/java/endpoint_extractor.py +36 -0
- package/templates/qoder/scripts/domain/kg/extract/java/javadoc_extractor.py +110 -0
- package/templates/qoder/scripts/domain/kg/extract/java/llm_cn_filler.py +150 -0
- package/templates/qoder/scripts/domain/kg/extract/java/member_extractor.py +157 -0
- package/templates/qoder/scripts/domain/kg/extract/java/mybatisplus_extractor.py +34 -0
- package/templates/qoder/scripts/domain/kg/extract/java/pg_upsert.py +165 -0
- package/templates/qoder/scripts/domain/kg/extract/java/satoken_extractor.py +30 -0
- package/templates/qoder/scripts/domain/kg/extract/java/spring_extractor.py +39 -0
- package/templates/qoder/scripts/domain/kg/extract/java/validation_extractor.py +33 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
- package/templates/qoder/scripts/domain/kg/extract/ts_extract.py +111 -0
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +4 -2
- package/templates/qoder/scripts/domain/kg/kg.py +42 -5
- package/templates/qoder/scripts/domain/kg/search/_remote.py +187 -0
- package/templates/qoder/scripts/domain/kg/search/context_pack.py +32 -2
- package/templates/qoder/scripts/domain/kg/search/search_index.py +74 -20
- package/templates/qoder/scripts/domain/kg/storage/kg_duckdb.py +43 -0
- package/templates/qoder/scripts/domain/kg/switch_project.py +159 -0
- package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
- package/templates/qoder/scripts/domain/requirement/req.py +134 -28
- package/templates/qoder/scripts/domain/task/__pycache__/wlkj_panel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -0
- package/templates/qoder/scripts/engine/poller.py +219 -0
- package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/paths.py +102 -0
- package/templates/qoder/scripts/foundation/integrations/active_task.py +2 -1
- package/templates/qoder/scripts/orchestration/wlkj.py +4 -0
- package/templates/qoder/scripts/protocol/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +24 -11
- package/templates/qoder/scripts/protocol/transports/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/base.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/cli.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/http.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/stdio.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/http.py +7 -1
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +1 -1
- package/templates/qoder/scripts/validation/eval/report-commands.md +2 -2
- package/templates/qoder/settings.json +27 -9
- package/templates/qoder/skills/design-import/SKILL.md +3 -3
- package/templates/qoder/skills/design-review/SKILL.md +1 -1
- package/templates/qoder/skills/prd-generator/SKILL.md +4 -4
- package/templates/qoder/skills/prd-review/SKILL.md +1 -1
- package/templates/qoder/skills/prototype-generator/SKILL.md +3 -3
- package/templates/qoder/skills/spec-coder/SKILL.md +1 -1
- package/templates/qoder/skills/spec-generator/SKILL.md +80 -23
- package/templates/qoder/skills/test-generator/SKILL.md +1 -1
- package/templates/qoder/skills/wl-code/SKILL.md +13 -1
- package/templates/qoder/skills/wl-commit/SKILL.md +1 -1
- package/templates/qoder/skills/wl-design/SKILL.md +6 -6
- package/templates/qoder/skills/wl-init/SKILL.md +2 -2
- package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
- package/templates/qoder/skills/wl-prd/SKILL.md +60 -0
- package/templates/qoder/skills/wl-report/SKILL.md +2 -2
- package/templates/qoder/skills/wl-search/SKILL.md +1 -1
- package/templates/qoder/skills/wl-spec/SKILL.md +2 -2
- package/templates/qoder/skills/wl-status/SKILL.md +2 -2
- package/templates/qoder/skills/wl-task/SKILL.md +3 -3
- package/templates/qoder/skills/wl-test/SKILL.md +2 -2
- package/templates/qoder/templates/spec-template.md +124 -0
- package/templates/root/AGENTS.md +41 -14
- package/templates/qoder/scripts/domain/task/zentao_panel.py +0 -451
- package/templates/qoder/skills/wl-prd-full/SKILL.md +0 -121
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +0 -50
- package/templates/qoder/skills/wl-prd-review/SKILL.md +0 -47
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
deploy_to_test.py - 提交部署: 拉取 → cherry-pick到test分支 → 触发Jenkins构建
|
|
5
|
+
|
|
6
|
+
面板「提交」按钮调用。在 fywl-ics 代码仓库执行 git 操作。
|
|
7
|
+
|
|
8
|
+
三步:
|
|
9
|
+
1. 拉取当前分支最新代码
|
|
10
|
+
2. cherry-pick 当前提交到 test 分支 (冲突→abort→提示手动)
|
|
11
|
+
3. 从代码改动推断该构建哪个 Jenkins job → 触发构建
|
|
12
|
+
|
|
13
|
+
用法:
|
|
14
|
+
python deploy_to_test.py --json # 执行全部三步
|
|
15
|
+
python deploy_to_test.py --json --no-deploy # 只拉取+cherry-pick, 不触发Jenkins
|
|
16
|
+
python deploy_to_test.py --jobs # 列出可选的Jenkins job (供面板展示)
|
|
17
|
+
python deploy_to_test.py --json --job qas_ics_quality # 指定构建哪个job
|
|
18
|
+
|
|
19
|
+
配置 (从 .qoder/config.yaml 读, 没有就用默认):
|
|
20
|
+
deploy:
|
|
21
|
+
code_repo: data/code/fywl-ics # 代码仓库相对路径
|
|
22
|
+
test_branch: test # cherry-pick目标分支
|
|
23
|
+
jenkins_url: http://10.54.6.35:8866
|
|
24
|
+
jenkins_user: public_ics
|
|
25
|
+
jenkins_pass: a123456
|
|
26
|
+
jenkins_view: ICS-QAS # Jenkins视图名
|
|
27
|
+
|
|
28
|
+
退出码: 0成功; 3配置错误; 4冲突; 5网络/构建失败
|
|
29
|
+
"""
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
import json
|
|
33
|
+
import argparse
|
|
34
|
+
import subprocess
|
|
35
|
+
|
|
36
|
+
# 路径自举
|
|
37
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
38
|
+
_SCRIPTS = os.path.dirname(os.path.dirname(os.path.dirname(_HERE))) # .qoder/scripts
|
|
39
|
+
_COMMON = os.path.join(_SCRIPTS, 'foundation')
|
|
40
|
+
for _p in (_SCRIPTS, _COMMON):
|
|
41
|
+
if _p not in sys.path:
|
|
42
|
+
sys.path.insert(0, _p)
|
|
43
|
+
try:
|
|
44
|
+
from bootstrap import setup; setup()
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _load_config():
|
|
50
|
+
"""从 config.yaml 读 deploy 段, 没有用默认。"""
|
|
51
|
+
import yaml
|
|
52
|
+
cfg_path = os.path.join(os.path.dirname(_SCRIPTS), 'config.yaml')
|
|
53
|
+
defaults = {
|
|
54
|
+
'code_repo': 'data/code/fywl-ics',
|
|
55
|
+
'test_branch': 'test',
|
|
56
|
+
'jenkins_url': 'http://10.54.6.35:8866',
|
|
57
|
+
'jenkins_user': 'public_ics',
|
|
58
|
+
'jenkins_pass': 'a123456',
|
|
59
|
+
'jenkins_view': 'ICS-QAS',
|
|
60
|
+
}
|
|
61
|
+
try:
|
|
62
|
+
with open(cfg_path, encoding='utf-8') as f:
|
|
63
|
+
full = yaml.safe_load(f) or {}
|
|
64
|
+
deploy = full.get('deploy', {})
|
|
65
|
+
defaults.update({k: v for k, v in deploy.items() if v})
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
return defaults
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _git(args, repo_dir, check=True):
|
|
72
|
+
"""在指定仓库目录执行 git 命令。返回 (returncode, stdout, stderr)。"""
|
|
73
|
+
r = subprocess.run(['git'] + args, capture_output=True, text=True,
|
|
74
|
+
cwd=repo_dir, timeout=30)
|
|
75
|
+
if check and r.returncode != 0:
|
|
76
|
+
raise RuntimeError('git %s 失败: %s' % (' '.join(args), r.stderr.strip()))
|
|
77
|
+
return r.returncode, r.stdout.strip(), r.stderr.strip()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _repo_root(cfg):
|
|
81
|
+
"""定位代码仓库根目录。"""
|
|
82
|
+
from foundation.core.paths import get_repo_root
|
|
83
|
+
base = get_repo_root()
|
|
84
|
+
repo = os.path.join(str(base), cfg['code_repo'])
|
|
85
|
+
if not os.path.isdir(os.path.join(repo, '.git')):
|
|
86
|
+
# 兜底: 往上找
|
|
87
|
+
repo = os.path.join(str(base), 'data', 'code', 'fywl-ics')
|
|
88
|
+
return repo
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def step_pull(repo_dir):
|
|
92
|
+
"""第1步: 拉取当前分支最新代码。"""
|
|
93
|
+
_, branch, _ = _git(['branch', '--show-current'], repo_dir)
|
|
94
|
+
_git(['fetch', 'origin'], repo_dir, check=False)
|
|
95
|
+
rc, out, err = _git(['pull', 'origin', branch], repo_dir, check=False)
|
|
96
|
+
if rc != 0:
|
|
97
|
+
return {'ok': False, 'error': '拉取失败: %s' % err, 'branch': branch}
|
|
98
|
+
return {'ok': True, 'branch': branch, 'message': '已拉取 %s 分支' % branch}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def step_cherry_pick(repo_dir, test_branch):
|
|
102
|
+
"""第2步: cherry-pick 当前提交到 test 分支。冲突→abort→提示手动。"""
|
|
103
|
+
# 拿当前分支最新 commit
|
|
104
|
+
_, commit, _ = _git(['rev-parse', 'HEAD'], repo_dir)
|
|
105
|
+
_, cur_branch, _ = _git(['branch', '--show-current'], repo_dir)
|
|
106
|
+
|
|
107
|
+
# 切到 test 分支
|
|
108
|
+
_git(['fetch', 'origin', test_branch], repo_dir, check=False)
|
|
109
|
+
rc, out, err = _git(['checkout', test_branch], repo_dir, check=False)
|
|
110
|
+
if rc != 0:
|
|
111
|
+
# test 分支不存在, 从 origin 拉远程
|
|
112
|
+
rc2, out2, err2 = _git(['checkout', '-b', test_branch, 'origin/%s' % test_branch], repo_dir, check=False)
|
|
113
|
+
if rc2 != 0:
|
|
114
|
+
return {'ok': False, 'error': '切换test分支失败: %s' % err2}
|
|
115
|
+
|
|
116
|
+
_git(['pull', 'origin', test_branch], repo_dir, check=False)
|
|
117
|
+
|
|
118
|
+
# cherry-pick
|
|
119
|
+
rc, out, err = _git(['cherry-pick', commit], repo_dir, check=False)
|
|
120
|
+
if rc != 0:
|
|
121
|
+
# 冲突 → abort → 切回原分支 → 提示手动
|
|
122
|
+
_git(['cherry-pick', '--abort'], repo_dir, check=False)
|
|
123
|
+
_git(['checkout', cur_branch], repo_dir, check=False)
|
|
124
|
+
return {'ok': False, 'error': 'cherry-pick 到 %s 分支冲突, 已撤销。请手动解决:\n'
|
|
125
|
+
' git checkout %s && git cherry-pick %s' % (test_branch, test_branch, commit[:8]),
|
|
126
|
+
'conflict': True}
|
|
127
|
+
|
|
128
|
+
# 推送
|
|
129
|
+
rc, out, err = _git(['push', 'origin', test_branch], repo_dir, check=False)
|
|
130
|
+
if rc != 0:
|
|
131
|
+
return {'ok': False, 'error': '推送到 %s 失败: %s' % (test_branch, err)}
|
|
132
|
+
|
|
133
|
+
# 切回原分支
|
|
134
|
+
_git(['checkout', cur_branch], repo_dir, check=False)
|
|
135
|
+
|
|
136
|
+
return {'ok': True, 'commit': commit[:8], 'test_branch': test_branch,
|
|
137
|
+
'message': '已 cherry-pick %s 到 %s 并推送' % (commit[:8], test_branch)}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _get_changed_modules(repo_dir):
|
|
141
|
+
"""从 git diff 提取改动的模块名 (用于推断 Jenkins job)。"""
|
|
142
|
+
_, out, _ = _git(['diff', '--name-only', 'HEAD~1'], repo_dir, check=False)
|
|
143
|
+
if not out:
|
|
144
|
+
_, out, _ = _git(['diff', '--name-only', '--cached'], repo_dir, check=False)
|
|
145
|
+
if not out:
|
|
146
|
+
_, out, _ = _git(['status', '--porcelain'], repo_dir, check=False)
|
|
147
|
+
modules = set()
|
|
148
|
+
for line in out.split('\n'):
|
|
149
|
+
line = line.strip().split()[-1] if line.strip() else ''
|
|
150
|
+
if not line:
|
|
151
|
+
continue
|
|
152
|
+
# 从路径提取模块: ics-modules/ics-quality/xxx → ics-quality
|
|
153
|
+
parts = line.split('/')
|
|
154
|
+
for i, p in enumerate(parts):
|
|
155
|
+
if p.startswith('ics-') and i + 1 < len(parts):
|
|
156
|
+
modules.add(p)
|
|
157
|
+
break
|
|
158
|
+
# fywl-ui 前端
|
|
159
|
+
if 'fywl-ui' in line or line.startswith('apps/'):
|
|
160
|
+
modules.add('ui')
|
|
161
|
+
return modules
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _list_jenkins_jobs(cfg):
|
|
165
|
+
"""拉 Jenkins 视图下的 job 列表。"""
|
|
166
|
+
import urllib.request, base64
|
|
167
|
+
url = '%s/view/%s/api/json?tree=jobs[name]' % (cfg['jenkins_url'], cfg['jenkins_view'])
|
|
168
|
+
req = urllib.request.Request(url)
|
|
169
|
+
cred = base64.b64encode(('%s:%s' % (cfg['jenkins_user'], cfg['jenkins_pass'])).encode()).decode()
|
|
170
|
+
req.add_header('Authorization', 'Basic ' + cred)
|
|
171
|
+
r = urllib.request.urlopen(req, timeout=10)
|
|
172
|
+
data = json.loads(r.read())
|
|
173
|
+
return [j['name'] for j in data.get('jobs', [])]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _guess_jobs(modules, all_jobs):
|
|
177
|
+
"""从改动模块推断该构建哪些 Jenkins job。"""
|
|
178
|
+
matched = []
|
|
179
|
+
for mod in modules:
|
|
180
|
+
# 模块名 ics-quality → job名 qas_ics_quality 或 qas-ics-quality
|
|
181
|
+
mod_norm = mod.replace('-', '_').replace('_', '')
|
|
182
|
+
for job in all_jobs:
|
|
183
|
+
job_norm = job.replace('-', '_').replace('_', '').lower()
|
|
184
|
+
if mod_norm in job_norm:
|
|
185
|
+
matched.append(job)
|
|
186
|
+
return list(set(matched))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def step_jenkins_build(cfg, job_name):
|
|
190
|
+
"""第3步: 触发 Jenkins 构建。"""
|
|
191
|
+
import urllib.request, base64
|
|
192
|
+
# 先拿 crumb (Jenkins CSRF)
|
|
193
|
+
crumb_url = '%s/crumbIssuer/api/json' % cfg['jenkins_url']
|
|
194
|
+
cred = base64.b64encode(('%s:%s' % (cfg['jenkins_user'], cfg['jenkins_pass'])).encode()).decode()
|
|
195
|
+
headers = {'Authorization': 'Basic ' + cred}
|
|
196
|
+
try:
|
|
197
|
+
req = urllib.request.Request(crumb_url, headers=headers)
|
|
198
|
+
crumb_data = json.loads(urllib.request.urlopen(req, timeout=10).read())
|
|
199
|
+
crumb_field = crumb_data.get('crumbRequestField', '')
|
|
200
|
+
crumb_value = crumb_data.get('crumb', '')
|
|
201
|
+
headers[crumb_field] = crumb_value
|
|
202
|
+
except Exception:
|
|
203
|
+
pass # 没 crumb 也试
|
|
204
|
+
|
|
205
|
+
build_url = '%s/job/%s/build' % (cfg['jenkins_url'], job_name)
|
|
206
|
+
req = urllib.request.Request(build_url, data=b'', headers=headers, method='POST')
|
|
207
|
+
try:
|
|
208
|
+
r = urllib.request.urlopen(req, timeout=15)
|
|
209
|
+
return {'ok': True, 'job': job_name, 'message': '已触发 %s 构建 (HTTP %d)' % (job_name, r.status)}
|
|
210
|
+
except urllib.error.HTTPError as e:
|
|
211
|
+
if e.code == 201:
|
|
212
|
+
return {'ok': True, 'job': job_name, 'message': '已触发 %s 构建' % job_name}
|
|
213
|
+
return {'ok': False, 'error': '触发构建失败 (HTTP %d): %s' % (e.code, str(e)[:60])}
|
|
214
|
+
except Exception as e:
|
|
215
|
+
return {'ok': False, 'error': '触发构建异常: %s' % str(e)[:80]}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def deploy(job_name=None, skip_deploy=False):
|
|
219
|
+
"""执行部署全流程。返回 (result_dict, exit_code)。"""
|
|
220
|
+
cfg = _load_config()
|
|
221
|
+
repo_dir = _repo_root(cfg)
|
|
222
|
+
if not os.path.isdir(os.path.join(repo_dir, '.git')):
|
|
223
|
+
return {'ok': False, 'error': '代码仓库不存在: %s' % repo_dir}, 3
|
|
224
|
+
|
|
225
|
+
results = {'steps': []}
|
|
226
|
+
|
|
227
|
+
# ① 拉取
|
|
228
|
+
r1 = step_pull(repo_dir)
|
|
229
|
+
results['steps'].append({'step': 'pull', **r1})
|
|
230
|
+
if not r1['ok']:
|
|
231
|
+
return {'ok': False, 'error': r1['error'], 'results': results}, 5
|
|
232
|
+
|
|
233
|
+
# ② cherry-pick
|
|
234
|
+
r2 = step_cherry_pick(repo_dir, cfg['test_branch'])
|
|
235
|
+
results['steps'].append({'step': 'cherry-pick', **r2})
|
|
236
|
+
if not r2['ok']:
|
|
237
|
+
# 冲突不阻塞 Jenkins (开发可能只想先构建), 但标记冲突
|
|
238
|
+
results['conflict'] = r2.get('conflict', False)
|
|
239
|
+
|
|
240
|
+
# ③ Jenkins 构建
|
|
241
|
+
if not skip_deploy:
|
|
242
|
+
all_jobs = _list_jenkins_jobs(cfg)
|
|
243
|
+
if job_name:
|
|
244
|
+
# 指定了 job
|
|
245
|
+
r3 = step_jenkins_build(cfg, job_name)
|
|
246
|
+
else:
|
|
247
|
+
# 自动推断
|
|
248
|
+
modules = _get_changed_modules(repo_dir)
|
|
249
|
+
guessed = _guess_jobs(modules, all_jobs)
|
|
250
|
+
results['guessed_modules'] = list(modules)
|
|
251
|
+
results['guessed_jobs'] = guessed
|
|
252
|
+
if len(guessed) == 1:
|
|
253
|
+
r3 = step_jenkins_build(cfg, guessed[0])
|
|
254
|
+
elif len(guessed) > 1:
|
|
255
|
+
# 多个匹配 → 不自动构建, 让面板让用户选
|
|
256
|
+
r3 = {'ok': False, 'need_select': True, 'jobs': guessed,
|
|
257
|
+
'error': '改动涉及多个模块, 请选择要构建的job: %s' % ', '.join(guessed)}
|
|
258
|
+
else:
|
|
259
|
+
# 匹配不到 → 返回全部 job 让用户选
|
|
260
|
+
r3 = {'ok': False, 'need_select': True, 'jobs': all_jobs,
|
|
261
|
+
'error': '无法从改动推断构建模板, 请选择'}
|
|
262
|
+
results['steps'].append({'step': 'jenkins', **r3})
|
|
263
|
+
|
|
264
|
+
return {'ok': not results.get('conflict', False), 'results': results}, 0
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def main():
|
|
268
|
+
parser = argparse.ArgumentParser(description='提交部署: 拉取→cherry-pick→Jenkins构建')
|
|
269
|
+
parser.add_argument('--json', action='store_true', help='JSON输出')
|
|
270
|
+
parser.add_argument('--no-deploy', action='store_true', help='只拉取+cherry-pick, 不触发Jenkins')
|
|
271
|
+
parser.add_argument('--job', help='指定构建的Jenkins job名')
|
|
272
|
+
parser.add_argument('--jobs', action='store_true', help='列出可选的Jenkins job')
|
|
273
|
+
args = parser.parse_args()
|
|
274
|
+
|
|
275
|
+
cfg = _load_config()
|
|
276
|
+
|
|
277
|
+
if args.jobs:
|
|
278
|
+
try:
|
|
279
|
+
jobs = _list_jenkins_jobs(cfg)
|
|
280
|
+
result = {'ok': True, 'jobs': jobs}
|
|
281
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
282
|
+
except Exception as e:
|
|
283
|
+
print(json.dumps({'ok': False, 'error': str(e)}))
|
|
284
|
+
sys.exit(0)
|
|
285
|
+
|
|
286
|
+
result, code = deploy(job_name=args.job, skip_deploy=args.no_deploy)
|
|
287
|
+
|
|
288
|
+
if args.json:
|
|
289
|
+
print(json.dumps(result, ensure_ascii=False, default=str))
|
|
290
|
+
else:
|
|
291
|
+
for step in result.get('results', {}).get('steps', []):
|
|
292
|
+
status = 'OK' if step.get('ok') else 'FAIL'
|
|
293
|
+
print('[%s] %s: %s' % (status, step.get('step'), step.get('message', step.get('error', ''))))
|
|
294
|
+
sys.exit(code)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if __name__ == '__main__':
|
|
298
|
+
main()
|
|
File without changes
|
|
Binary file
|