zt-devops-cli 0.1.5__tar.gz → 0.1.7__tar.gz

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.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: zt-devops-cli
3
+ Version: 0.1.7
4
+ Summary: DevOps 平台迭代管理 CLI
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: playwright>=1.40.0
8
+ Requires-Dist: requests>=2.31.0
9
+ Requires-Dist: click>=8.1.0
10
+ Requires-Dist: pyyaml>=6.0
11
+
12
+ # DevOps CLI
13
+
14
+ 蓝鲸 DevOps 平台迭代管理命令行工具。
15
+
16
+ ## 打包
17
+ ```bash
18
+ python -m pip install --upgrade build twine
19
+ python -m build
20
+ twine upload dist/*
21
+ ```
22
+
23
+ ## 安装
24
+ * 源码安装
25
+ ```bash
26
+ pip install -e .
27
+ ```
28
+
29
+ * pip安装
30
+ ```bash
31
+ pipx install zt-devops-cli
32
+ ```
33
+
34
+ ## 使用
35
+
36
+ ### 登陆
37
+
38
+ 首次运行时会自动打开浏览器,请登录蓝鲸 DevOps。登录成功后 cookie 会缓存到本地。
39
+ ```bash
40
+ zt-devops-cli login
41
+ ```
42
+
43
+ ### 空间管理
44
+
45
+ ```bash
46
+ zt-devops-cli project list
47
+ ```
48
+
49
+ ### ZTeam 项目列表
50
+ ```bash
51
+ zt-devops-cli zteam-project list --project f39507
52
+ ```
53
+
54
+ ### 迭代管理
55
+
56
+ #### 迭代列表
57
+ ```bash
58
+ zt-devops-cli sprint list --project k64352
59
+ ```
60
+
61
+ #### 创建迭代
62
+
63
+ ```bash
64
+ zt-devops-cli sprint create --project k64352 \
65
+ --title "迭代名称" \
66
+ --start-date 2026-04-04 \
67
+ --end-date 2026-05-30 \
68
+ --purpose "迭代目标"
69
+ ```
70
+
71
+ #### 启用迭代
72
+ ```bash
73
+ zt-devops-cli sprint start --project k64352 --sprint-id <id>
74
+ ```
75
+
76
+ #### 删除迭代
77
+ ```bash
78
+ zt-devops-cli sprint delete --project k64352 --sprint-id <id>
79
+ ```
80
+
81
+ #### 完成迭代
82
+ ```bash
83
+ zt-devops-cli sprint done --project k64352 --sprint-id <id>
84
+ ```
85
+
86
+ ### 任务管理
87
+
88
+ #### 查询任务列表(TASK)
89
+ ```bash
90
+ zt-devops-cli task list --project f39507 \
91
+ --start-date 2026-04-01 \
92
+ --end-date 2026-04-30 \
93
+ --creator zt07905
94
+ ```
95
+
96
+ #### 新增任务(TASK)
97
+ ```bash
98
+ zt-devops-cli task create --project <project-id> \ # 通过 project list 获取
99
+ --title "<任务标题>" \
100
+ --owner <工号> \
101
+ --estimate-start <预计开始时间: yyyy-MM-dd> \
102
+ --estimate-end <预计完成时间: yyyy-MM-dd> \
103
+ --origin-hours 8.00 \ # 预估工时
104
+ --remain-hours 8.00 \ # 实际工时
105
+ --zteam-project-id <zteam-project-id> # 通过 zteam-project list 获取
106
+ ```
107
+
108
+ ## 配置
109
+ - cookie 缓存:`~/.zt-devops-cli/cookies.json`
110
+ - 配置文件:`~/.zt-devops-cli/config.yaml`
@@ -0,0 +1,99 @@
1
+ # DevOps CLI
2
+
3
+ 蓝鲸 DevOps 平台迭代管理命令行工具。
4
+
5
+ ## 打包
6
+ ```bash
7
+ python -m pip install --upgrade build twine
8
+ python -m build
9
+ twine upload dist/*
10
+ ```
11
+
12
+ ## 安装
13
+ * 源码安装
14
+ ```bash
15
+ pip install -e .
16
+ ```
17
+
18
+ * pip安装
19
+ ```bash
20
+ pipx install zt-devops-cli
21
+ ```
22
+
23
+ ## 使用
24
+
25
+ ### 登陆
26
+
27
+ 首次运行时会自动打开浏览器,请登录蓝鲸 DevOps。登录成功后 cookie 会缓存到本地。
28
+ ```bash
29
+ zt-devops-cli login
30
+ ```
31
+
32
+ ### 空间管理
33
+
34
+ ```bash
35
+ zt-devops-cli project list
36
+ ```
37
+
38
+ ### ZTeam 项目列表
39
+ ```bash
40
+ zt-devops-cli zteam-project list --project f39507
41
+ ```
42
+
43
+ ### 迭代管理
44
+
45
+ #### 迭代列表
46
+ ```bash
47
+ zt-devops-cli sprint list --project k64352
48
+ ```
49
+
50
+ #### 创建迭代
51
+
52
+ ```bash
53
+ zt-devops-cli sprint create --project k64352 \
54
+ --title "迭代名称" \
55
+ --start-date 2026-04-04 \
56
+ --end-date 2026-05-30 \
57
+ --purpose "迭代目标"
58
+ ```
59
+
60
+ #### 启用迭代
61
+ ```bash
62
+ zt-devops-cli sprint start --project k64352 --sprint-id <id>
63
+ ```
64
+
65
+ #### 删除迭代
66
+ ```bash
67
+ zt-devops-cli sprint delete --project k64352 --sprint-id <id>
68
+ ```
69
+
70
+ #### 完成迭代
71
+ ```bash
72
+ zt-devops-cli sprint done --project k64352 --sprint-id <id>
73
+ ```
74
+
75
+ ### 任务管理
76
+
77
+ #### 查询任务列表(TASK)
78
+ ```bash
79
+ zt-devops-cli task list --project f39507 \
80
+ --start-date 2026-04-01 \
81
+ --end-date 2026-04-30 \
82
+ --creator zt07905
83
+ ```
84
+
85
+ #### 新增任务(TASK)
86
+ ```bash
87
+ zt-devops-cli task create --project <project-id> \ # 通过 project list 获取
88
+ --title "<任务标题>" \
89
+ --owner <工号> \
90
+ --estimate-start <预计开始时间: yyyy-MM-dd> \
91
+ --estimate-end <预计完成时间: yyyy-MM-dd> \
92
+ --origin-hours 8.00 \ # 预估工时
93
+ --remain-hours 8.00 \ # 实际工时
94
+ --zteam-project-id <zteam-project-id> # 通过 zteam-project list 获取
95
+ ```
96
+
97
+ ## 配置
98
+ - cookie 缓存:`~/.zt-devops-cli/cookies.json`
99
+ - 配置文件:`~/.zt-devops-cli/config.yaml`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "zt-devops-cli"
7
- version = "0.1.5"
7
+ version = "0.1.7"
8
8
  description = "DevOps 平台迭代管理 CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,165 @@
1
+ """API 请求封装"""
2
+ import json as json_mod
3
+ import requests
4
+ from typing import Optional
5
+
6
+ from .auth import get_auth_cookies, get_auth_headers
7
+ from .config import config
8
+
9
+
10
+ class DevOpsAPI:
11
+ """DevOps API 客户端"""
12
+
13
+ def __init__(self, project_id: str):
14
+ self.project_id = project_id
15
+ self.base_url = config.BASE_URL
16
+
17
+ def _request(self, method: str, path: str, **kwargs) -> dict:
18
+ """发送 API 请求"""
19
+ cookies = get_auth_cookies()
20
+ headers = get_auth_headers(cookies, self.project_id)
21
+
22
+ url = f"{self.base_url}/{path}"
23
+
24
+ # 处理 data 参数
25
+ if "data" in kwargs:
26
+ kwargs["json"] = kwargs.pop("data")
27
+
28
+ # #############################################
29
+ # # 打印 curl 命令
30
+ # curl_parts = [f"curl -X {method}"]
31
+ # for k, v in headers.items():
32
+ # curl_parts.append(f"-H '{k}: {v}'")
33
+ # if "json" in kwargs:
34
+ # curl_parts.append(f"-d '{json_mod.dumps(kwargs['json'], ensure_ascii=False)}'")
35
+ # curl_parts.append(f"'{url}'")
36
+ # print(" ".join(curl_parts))
37
+ # #############################################
38
+
39
+ response = requests.request(
40
+ method=method,
41
+ url=url,
42
+ headers=headers,
43
+ cookies=cookies,
44
+ **kwargs
45
+ )
46
+
47
+ if response.status_code >= 400:
48
+ raise Exception(f"API 错误: {response.status_code} {response.text}")
49
+
50
+ return response.json()
51
+
52
+ # ========== 迭代操作 ==========
53
+
54
+ def create_sprint(
55
+ self,
56
+ title: str,
57
+ start_date: str,
58
+ end_date: str,
59
+ purpose: str = "",
60
+ test_start_date: Optional[str] = None,
61
+ test_end_date: Optional[str] = None,
62
+ development_pic: str = "",
63
+ test_pic: str = "",
64
+ acceptance_pic: str = "",
65
+ ) -> dict:
66
+ """创建迭代"""
67
+ data = {
68
+ "title": title,
69
+ "startDate": start_date,
70
+ "endDate": end_date,
71
+ "purpose": purpose,
72
+ "daterange": [f"{start_date}T16:00:00.000Z", f"{end_date}T16:00:00.000Z"],
73
+ "reviewTime": "",
74
+ "testStartDate": test_start_date or start_date,
75
+ "testEndDate": test_end_date or end_date,
76
+ "developmentPic": development_pic,
77
+ "testPic": test_pic,
78
+ "acceptancePic": acceptance_pic,
79
+ "excludeTime": "",
80
+ "state": "ACTIVE",
81
+ }
82
+ return self._request("POST", f"/ms/vteam/api/user/issue_sprint/{self.project_id}", data=data)
83
+
84
+ def start_sprint(self, sprint_data: dict) -> dict:
85
+ """启用迭代"""
86
+ return self._request("PUT", f"/ms/vteam/api/user/issue_sprint/{self.project_id}/start", data=sprint_data)
87
+
88
+ def delete_sprint(self, sprint_id: str) -> dict:
89
+ """删除迭代"""
90
+ return self._request("DELETE", f"/ms/vteam/api/user/issue_sprint/{self.project_id}/{sprint_id}")
91
+
92
+ def done_sprint(self, sprint_id: str) -> dict:
93
+ """完成迭代"""
94
+ return self._request("PUT", f"/ms/vteam/api/user/issue_sprint/{self.project_id}/complete/{sprint_id}")
95
+
96
+ def list_sprints(self) -> dict:
97
+ """查询迭代列表"""
98
+ return self._request("GET",
99
+ f"/ms/vteam/api/user/issue_sprint/{self.project_id}/flat?num=1&count=1&size=20&content=&start_time=&end_time=&state=ACTIVE%2CNOT_STARTED&sort_field=CREATED_TIME&sort_rule=DESC")
100
+
101
+ def list_projects(self) -> dict:
102
+ """查询项目列表"""
103
+ return self._request("GET",
104
+ f"/ms/projectmanager/api/user/project/cw/selectByType?showTree=false&haveUser=false")
105
+
106
+ def list_zteam_projects(self) -> dict:
107
+ """查询 ZTeam 项目列表(任务字段选项)"""
108
+ return self._request(
109
+ "GET",
110
+ f"/ms/vteam/api/user/issue_field_value/{self.project_id}/option/5abdcb4c783e11edbef4fa819a160800?all=false&classify=TASK",
111
+ )
112
+
113
+ # ========== 任务操作 ==========
114
+
115
+ def create_task(
116
+ self,
117
+ title: str,
118
+ model_type_id: str,
119
+ priority: str = "CENTRAL",
120
+ editor_type: str = "MARKDOWN",
121
+ desc: str = "",
122
+ parent_id: str = "",
123
+ demand_classify: str = "-1",
124
+ instance_values: Optional[list] = None,
125
+ ) -> dict:
126
+ """创建 TASK 任务"""
127
+ data = {
128
+ "title": title,
129
+ "modelTypeId": model_type_id,
130
+ "priority": priority,
131
+ "editorType": editor_type,
132
+ "desc": desc,
133
+ "parentId": parent_id,
134
+ "relationIssue": {},
135
+ "demandClassify": demand_classify,
136
+ "fileVO": [],
137
+ "labelId": [],
138
+ "instanceValue": instance_values or [],
139
+ }
140
+ return self._request("POST", f"/ms/vteam/api/user/issue/{self.project_id}", data=data)
141
+
142
+ def list_tasks(
143
+ self,
144
+ creator: Optional[str] = None,
145
+ start_date: Optional[str] = None,
146
+ end_date: Optional[str] = None,
147
+ num: int = 1,
148
+ size: int = 20,
149
+ remember: bool = False,
150
+ ) -> dict:
151
+ """按条件查询 TASK 列表"""
152
+
153
+ data = [ {"name": "exclude", "value": []}]
154
+
155
+ if creator:
156
+ data.insert(0, {"name": "createUser", "value": [creator]})
157
+ if start_date and end_date:
158
+ data.insert(0, {"name": "estimate_start_time", "value": [start_date, end_date]})
159
+ data.insert(0, {"name": "estimate_end_time", "value": [start_date, end_date]})
160
+ remember_value = str(remember).lower()
161
+ return self._request(
162
+ "POST",
163
+ f"/ms/vteam/api/user/issue/{self.project_id}/table/TASK?num={num}&size={size}&remember={remember_value}",
164
+ data=data,
165
+ )
@@ -91,10 +91,10 @@ def open_login_page(page) -> bool:
91
91
 
92
92
  def get_cookies_from_browser() -> dict:
93
93
  """通过 Playwright 从浏览器获取 cookie"""
94
- print("正在打开浏览器,请登录...")
94
+ print("正在启动浏览器(无头模式),请登录...")
95
95
 
96
96
  with sync_playwright() as p:
97
- browser = p.chromium.launch(headless=False)
97
+ browser = p.chromium.launch(headless=True)
98
98
  context = browser.new_context()
99
99
  page = context.new_page()
100
100
 
@@ -103,9 +103,16 @@ def get_cookies_from_browser() -> dict:
103
103
 
104
104
  # 尝试自动登录
105
105
  if not open_login_page(page):
106
- print("自动登陆失败. 请在浏览器中完成登录,登录成功后会自动继续...")
107
-
108
- print("登陆中.检测关键 cookie 出现")
106
+ print(
107
+ "自动登录失败。无头模式下无法手动操作页面,请设置环境变量 "
108
+ "ZT_SSO_USERNAME ZT_SSO_PASSWORD 后重试。"
109
+ )
110
+ browser.close()
111
+ raise RuntimeError(
112
+ "无头模式需要 ZT_SSO_USERNAME / ZT_SSO_PASSWORD 环境变量以完成自动登录。"
113
+ )
114
+
115
+ print("等待登录完成...")
109
116
  page.wait_for_function(
110
117
  f"""() => {{
111
118
  const cookies = document.cookie;
@@ -151,7 +158,7 @@ def get_cookies_from_browser() -> dict:
151
158
 
152
159
  def login() -> dict:
153
160
  """手动登录 - 强制打开浏览器重新获取 cookie"""
154
- print("正在打开浏览器,请重新登录...")
161
+ print("正在启动浏览器(无头模式),请重新登录...")
155
162
  cookies = get_cookies_from_browser()
156
163
  save_cookies(cookies)
157
164
  print("登录成功,cookie 已缓存!")