pingcode-cli-unofficial 1.7.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/README.md +943 -0
- package/dist/bin/pingcode.js +17001 -0
- package/dist/bin/pingcode.js.map +1 -0
- package/package.json +51 -0
- package/skills/pingcode/SKILL.md +431 -0
- package/skills/pingcode/modules/api.md +116 -0
- package/skills/pingcode/modules/cicd.md +222 -0
- package/skills/pingcode/modules/crosscutting.md +168 -0
- package/skills/pingcode/modules/pjm.md +385 -0
- package/skills/pingcode/modules/scm.md +432 -0
- package/skills/pingcode/modules/ship.md +197 -0
- package/skills/pingcode/modules/testhub.md +386 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# 源码管理 (scm) — `scm`
|
|
2
|
+
|
|
3
|
+
> Part of the `pingcode` skill. Read [`../SKILL.md`](../SKILL.md) first — it carries the
|
|
4
|
+
> authentication gate, the stdout/stderr contract, the exit-code table and the rules that
|
|
5
|
+
> apply to every module. This file is only the scm command surface and the ids it needs.
|
|
6
|
+
|
|
7
|
+
**What this module is for.** It is the *write-back* API a CI system uses: you tell PingCode
|
|
8
|
+
which hosting platform, git identities, repositories, branches and commits exist, and
|
|
9
|
+
PingCode links them to work items. It does **not** read your git server — nothing here
|
|
10
|
+
clones, pushes or inspects a repository. Every row you create is a record inside PingCode.
|
|
11
|
+
|
|
12
|
+
**The CI write-back path, in order.** Each step needs the one before it:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
platform → repo → branch ─┬→ pr → review
|
|
16
|
+
├→ ref (commit ↔ branch)
|
|
17
|
+
commit ──────┘
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A commit is created *without* a repository (it is organisation-level), so `scm ref create`
|
|
21
|
+
is what attaches it to a branch. Do not look for a `--repo` on `scm commit`.
|
|
22
|
+
|
|
23
|
+
A pull request needs its **target branch id** first, and a code review needs its **pull
|
|
24
|
+
request id** — both are ids, not names or numbers, so create the branch and the PR before
|
|
25
|
+
you try to hang anything off them.
|
|
26
|
+
|
|
27
|
+
**Two group-wide facts.**
|
|
28
|
+
|
|
29
|
+
- Every scm endpoint is **企业令牌 only** — which is exactly the token this CLI holds, so the
|
|
30
|
+
whole group works out of the box. Required scopes: `pcp:read:devops:code` and
|
|
31
|
+
`pcp:write:devops:code`. Without them you get exit 4.
|
|
32
|
+
- An scm **platform** (托管平台) is *not* a ship product, even though both sit under a
|
|
33
|
+
`products` URL segment. `pingcode product …` is 产品管理; `pingcode scm platform …` is a
|
|
34
|
+
GitHub/GitLab/SVN server record.
|
|
35
|
+
|
|
36
|
+
### Hosting platforms — `scm platform`
|
|
37
|
+
|
|
38
|
+
Everything else in scm is addressed under a platform, so this is always the first call.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pingcode scm platform list --json
|
|
42
|
+
pingcode scm platform get Github --json
|
|
43
|
+
pingcode scm platform create --name "Gitea (internal)" --type other --dry-run --json
|
|
44
|
+
pingcode scm platform create --name "Gitea (internal)" --type other --description "self-hosted" --json
|
|
45
|
+
pingcode scm platform update Github --description "github.com" --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`--type` is a closed enum used to pick an icon:
|
|
49
|
+
`github | gitlab | bitbucket | coding.net | gogs | git | svn | gerrit | other`. A value outside
|
|
50
|
+
it is rejected by the server (exit 7), not by the CLI.
|
|
51
|
+
|
|
52
|
+
A platform **name is unique per organisation**; creating a duplicate is exit 7 with
|
|
53
|
+
`'product'已经存在`.
|
|
54
|
+
|
|
55
|
+
### Git identities — `scm platform-user`
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pingcode scm platform-user list --platform Github --json
|
|
59
|
+
pingcode scm platform-user list --platform Github --name octocat --json
|
|
60
|
+
pingcode scm platform-user get 685c6ca42974f854bb4979ac --platform Github --json
|
|
61
|
+
pingcode scm platform-user create --platform Github --name octocat --display-name "Octo Cat" \
|
|
62
|
+
--html-url https://github.com/octocat --json
|
|
63
|
+
pingcode scm platform-user update 685c6ca42974f854bb4979ac --platform Github --display-name "Octo" --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**A platform user is a git author identity, not a PingCode member, and there is no field that
|
|
67
|
+
links it to one.** The resource is exactly `{id, name, display_name, html_url, avatar_url}` —
|
|
68
|
+
no `user`, no `user_id`, no `email`, on read or on write. Attribution is by the **name
|
|
69
|
+
string**: a commit's `committer_name` and a branch's `sender_name` are matched against these
|
|
70
|
+
rows. So do not promise a user that the CLI can "assign a commit to a person"; what it can do
|
|
71
|
+
is make sure the git username exists as an identity with a readable display name and avatar.
|
|
72
|
+
|
|
73
|
+
⚠️ **Five write fields silently CREATE one of these rows when the name is unknown**, and
|
|
74
|
+
**there is no DELETE for a platform user anywhere in scm**, so every typo is permanent:
|
|
75
|
+
|
|
76
|
+
| field | flag | upserts? |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| repository `owner_name` | `scm repo create --owner-name` | **yes** |
|
|
79
|
+
| branch `sender_name` | `scm branch create --sender` | **yes** |
|
|
80
|
+
| pull request `creator_name` | `scm pr create/update --creator` | **yes** |
|
|
81
|
+
| pull request `merged_by_name` | `scm pr create/update --merged-by` | **yes** |
|
|
82
|
+
| code review `reviewer_name` | `scm review create/update --reviewer` | **yes** |
|
|
83
|
+
| commit `committer_name` | `scm commit create --committer` | **no** — that path has no platform to create one in |
|
|
84
|
+
|
|
85
|
+
All five upserts are verified live. Check the name before a write:
|
|
86
|
+
`scm platform-user list --platform <p> --name <username> --json` returns zero rows if it
|
|
87
|
+
does not exist yet.
|
|
88
|
+
|
|
89
|
+
`get` and `update` take an **id**, because ids in this API have three shapes and are never
|
|
90
|
+
guessed. Turn a git username into an id with the exact-match filter above
|
|
91
|
+
(`list --name octocat`), which is why no `resolve` kind exists for this resource.
|
|
92
|
+
|
|
93
|
+
### Repositories — `scm repo`
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pingcode scm repo list --platform Github --json
|
|
97
|
+
pingcode scm repo list --platform Github --full-name acme/pingcode-cli --json
|
|
98
|
+
pingcode scm repo list --platform Github --all --limit 200 --json
|
|
99
|
+
|
|
100
|
+
pingcode scm repo get pingcode-cli --platform Github --json
|
|
101
|
+
pingcode scm repo get acme/pingcode-cli --platform Github --json
|
|
102
|
+
|
|
103
|
+
pingcode scm repo create --platform Github --name pingcode-cli --full-name acme/pingcode-cli \
|
|
104
|
+
--owner-name octocat --private true \
|
|
105
|
+
--html-url https://github.com/acme/pingcode-cli \
|
|
106
|
+
--branches-url 'https://github.com/acme/pingcode-cli/tree/{branch}' \
|
|
107
|
+
--commits-url 'https://github.com/acme/pingcode-cli/commit/{sha}' --json
|
|
108
|
+
|
|
109
|
+
pingcode scm repo update acme/pingcode-cli --platform Github --private false --json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Rules that will bite otherwise:
|
|
113
|
+
|
|
114
|
+
- **`full_name` (`owner/name`) is the unique key, `name` is not.** Two repositories in one
|
|
115
|
+
platform may share a name (a fork and its upstream); `scm repo get <name>` then exits 2 and
|
|
116
|
+
lists both ids, and the `full_name` is what disambiguates it.
|
|
117
|
+
- **`--full-name` is the only list filter.** The API ignores a `name` query parameter and
|
|
118
|
+
returns every repository, so the CLI does not offer one.
|
|
119
|
+
- **`--owner-name` creates the identity if it does not exist.** An unknown git username is not
|
|
120
|
+
rejected: the server makes a new platform user for it and points `owner` at it. A typo
|
|
121
|
+
therefore silently produces a ghost identity, and nothing in this API can delete it.
|
|
122
|
+
- **`--private` and `--fork` take `true` / `false`**, not bare switches, so a repository can be
|
|
123
|
+
made public again. Omit the flag to leave the field untouched.
|
|
124
|
+
- **The `*_url` values are templates stored verbatim** (`{branch}`, `{sha}`,
|
|
125
|
+
`{base}...{head}`, `{number}`). PingCode substitutes them when it renders a link; the CLI
|
|
126
|
+
never does. **Quote them** in a shell, or the braces may be eaten.
|
|
127
|
+
|
|
128
|
+
### Branches — `scm branch`
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pingcode scm branch list --platform Github --repo acme/pingcode-cli --json
|
|
132
|
+
pingcode scm branch list --platform Github --repo acme/pingcode-cli --name feature/PLM-001 --json
|
|
133
|
+
pingcode scm branch list --platform Github --repo acme/pingcode-cli --work-item-id 5edca524cad2fa112b06105c --json
|
|
134
|
+
|
|
135
|
+
pingcode scm branch get feature/PLM-001 --platform Github --repo acme/pingcode-cli --json
|
|
136
|
+
|
|
137
|
+
pingcode scm branch create --platform Github --repo acme/pingcode-cli \
|
|
138
|
+
--name feature/PLM-001-login --sender octocat --work-item PLM-001 --json
|
|
139
|
+
|
|
140
|
+
pingcode scm branch update feature/PLM-001-login --platform Github --repo acme/pingcode-cli \
|
|
141
|
+
--work-item PLM-001 --work-item PLM-002 --json
|
|
142
|
+
pingcode scm branch update feature/PLM-001-login --platform Github --repo acme/pingcode-cli --default --json
|
|
143
|
+
|
|
144
|
+
pingcode scm branch delete feature/PLM-001-login --platform Github --repo acme/pingcode-cli --yes --json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Rules that will bite otherwise:
|
|
148
|
+
|
|
149
|
+
- **`--sender` creates the git identity if it does not exist**, exactly like `repo
|
|
150
|
+
--owner-name`. An unknown username is not rejected — a platform user is made for it, and
|
|
151
|
+
**nothing in this API can delete a platform user**. Create it deliberately first
|
|
152
|
+
(`scm platform-user create`) and check the spelling.
|
|
153
|
+
- **`--default` is a switch, and there is no `--default false`.** On a patch the server
|
|
154
|
+
accepts *only* `true` — the field is really the action "make this the default". It also
|
|
155
|
+
**clears the flag on whichever branch currently holds it**, so one call changes two
|
|
156
|
+
branches. (Contrast `scm repo --private true|false`, which is genuinely three-state.)
|
|
157
|
+
- **The first branch in an empty repository becomes the default automatically**, even
|
|
158
|
+
though you did not ask.
|
|
159
|
+
- **`--work-item` takes an identifier (`PLM-001`), not an id — and an unknown one is
|
|
160
|
+
silently ignored.** The API returns 200 either way. The CLI compares what came back
|
|
161
|
+
against what you asked for and prints a `warning:` on stderr naming the identifiers that
|
|
162
|
+
did not link; the exit code stays 0, and under `--json` the authoritative answer is the
|
|
163
|
+
`work_items` array on stdout. **Read it.**
|
|
164
|
+
- **`--work-item` REPLACES the link set on update**, it does not add to it. Pass every
|
|
165
|
+
identifier you want to keep. There is no "clear" flag — repeat the command with the
|
|
166
|
+
links you want.
|
|
167
|
+
- **`--name` is a real filter here** (exact, case-insensitive), unlike on `repo list`.
|
|
168
|
+
Branch names are unique per repository, which is also why `scm branch get <name>` works
|
|
169
|
+
in one request and needs no `resolve` kind.
|
|
170
|
+
|
|
171
|
+
### Deleting a branch — the one destructive command in scm
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# refuses without --yes, and names the branch it would delete
|
|
175
|
+
pingcode scm branch delete feature/old --platform Github --repo acme/pingcode-cli
|
|
176
|
+
pingcode scm branch delete feature/old --platform Github --repo acme/pingcode-cli --yes --dry-run --json
|
|
177
|
+
pingcode scm branch delete feature/old --platform Github --repo acme/pingcode-cli --yes --json
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**This is the only `DELETE` in the whole scm module** — 代码分支 is the one family shaped with
|
|
181
|
+
a `DELETE` and no `PUT`; the other five are the reverse. Three things to know before you
|
|
182
|
+
run it:
|
|
183
|
+
|
|
184
|
+
- **`--yes` is mandatory** and the refusal echoes the *resolved branch name*, not just what
|
|
185
|
+
you typed. There is deliberately **no `--all`**: bulk branch deletion is not offered.
|
|
186
|
+
- **The default branch cannot be deleted at all** (exit 7, `默认分支不能被删除`). Make another
|
|
187
|
+
branch the default first. A repository whose only branch is the default therefore has no
|
|
188
|
+
deletable branch.
|
|
189
|
+
- **Deleting a branch orphans its commit refs, permanently.** The refs keep resolving by
|
|
190
|
+
id, but `scm ref list --branch-id <the deleted branch>` then fails with a server error
|
|
191
|
+
(HTTP 500), and **refs have no delete**, so the broken state cannot be cleaned up. Delete
|
|
192
|
+
a branch only when you are sure nothing references it.
|
|
193
|
+
|
|
194
|
+
### Commits — `scm commit`
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# by SHA — this is the point of the family: a pipeline has a SHA, not a PingCode id
|
|
198
|
+
pingcode scm commit get 96a024347146ebdc5f481f45e6e6871e0c43af5f --json
|
|
199
|
+
pingcode scm commit get 5e3bb2128cfda459bbafa3fb --json
|
|
200
|
+
|
|
201
|
+
pingcode scm commit list --sha 96a024347146ebdc5f481f45e6e6871e0c43af5f --json
|
|
202
|
+
pingcode scm commit list --work-item-id 5edca524cad2fa112b06105c --json
|
|
203
|
+
|
|
204
|
+
pingcode scm commit create --sha 96a024347146ebdc5f481f45e6e6871e0c43af5f \
|
|
205
|
+
--message "feat(auth): #PLM-001 add login" --committer octocat \
|
|
206
|
+
--committed-at 2026-08-03T09:00:00Z \
|
|
207
|
+
--added src/login.ts --modified README.md --removed src/old.ts \
|
|
208
|
+
--work-item PLM-001 --json
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- **These leaves take no `--platform` and no `--repo`.** A commit is an
|
|
212
|
+
organisation-level record; that is the API's shape, not an omission. Consequently
|
|
213
|
+
`commit list` with no filter **scans every commit in the organisation** — always pass
|
|
214
|
+
`--sha` or `--work-item-id`.
|
|
215
|
+
- **`get` accepts a full 40-character SHA or a PingCode id.** An **abbreviated SHA does not
|
|
216
|
+
work** (the server answers "resource path error", exit 5), even though every git tool
|
|
217
|
+
accepts one. Pass the full hash.
|
|
218
|
+
- **`--sha` is the one value this API validates for you**: a malformed SHA is exit 7 on
|
|
219
|
+
create, not a silent acceptance.
|
|
220
|
+
- **`--committer` does NOT create an identity.** This is the opposite of branch `--sender`:
|
|
221
|
+
the commit stores the name as a plain string and no platform user is made, so a typo
|
|
222
|
+
leaves the commit attributed to nobody rather than creating a ghost row. Fix it by
|
|
223
|
+
recreating the commit — but note a duplicate SHA is rejected, so the wrong one persists.
|
|
224
|
+
- **`--added` / `--removed` / `--modified` are repeatable** and all three are sent even when
|
|
225
|
+
empty. `file_changed_count` is computed by the server.
|
|
226
|
+
- There is **no `update` and no `delete`** for a commit.
|
|
227
|
+
|
|
228
|
+
### Commit refs — `scm ref`
|
|
229
|
+
|
|
230
|
+
A ref is the record that says *this commit is on this branch*. Create the commit first.
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
pingcode scm ref create --platform Github --repo acme/pingcode-cli \
|
|
234
|
+
--sha 96a024347146ebdc5f481f45e6e6871e0c43af5f --branch-id 564587fe700d43b81b080767 --json
|
|
235
|
+
|
|
236
|
+
pingcode scm ref list --platform Github --repo acme/pingcode-cli --branch-id 564587fe700d43b81b080767 --json
|
|
237
|
+
pingcode scm ref get 5e451b7dd704c212f7de8b4f --platform Github --repo acme/pingcode-cli --json
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- **`--branch-id` is required on `list`, and it takes an id, not a name.** The API's list
|
|
241
|
+
requires the referenced entity, so **there is no way to list every ref in a repository**
|
|
242
|
+
— you enumerate one branch at a time. Get the id from `scm branch list --json`.
|
|
243
|
+
- **`--sha` must name a commit that already exists** in PingCode, or you get exit 5
|
|
244
|
+
(`'commit'资源不存在`). Order: `scm commit create` → `scm ref create`.
|
|
245
|
+
- Only branches can be referenced; the CLI sends `meta_type=branch` for you.
|
|
246
|
+
- There is **no `update` and no `delete`**, and a ref outlives the branch it points at (see
|
|
247
|
+
the branch delete warning above).
|
|
248
|
+
|
|
249
|
+
### Pull requests — `scm pr`
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
pingcode scm pr list --platform Github --repo acme/pingcode-cli --json
|
|
253
|
+
pingcode scm pr list --platform Github --repo acme/pingcode-cli --number 42 --json
|
|
254
|
+
pingcode scm pr list --platform Github --repo acme/pingcode-cli --work-item-id 5edca524cad2fa112b06105c --json
|
|
255
|
+
pingcode scm pr list --platform Github --repo acme/pingcode-cli --all --limit 200 --json
|
|
256
|
+
|
|
257
|
+
pingcode scm pr get 594587fe700d43b81b080789 --platform Github --repo acme/pingcode-cli --json
|
|
258
|
+
|
|
259
|
+
pingcode scm pr create --platform Github --repo acme/pingcode-cli \
|
|
260
|
+
--title "feat(auth): #PLM-001 add login" --number 42 --creator octocat \
|
|
261
|
+
--target-branch-id 564587fe700d43b81b080776 --source-branch-id 564587fe700d43b81b080767 \
|
|
262
|
+
--status open --work-item PLM-001 --json
|
|
263
|
+
|
|
264
|
+
pingcode scm pr update 594587fe700d43b81b080789 --platform Github --repo acme/pingcode-cli \
|
|
265
|
+
--status merged --merged-at 2026-08-03T10:00:00Z \
|
|
266
|
+
--merged-commit-sha 96a024347146ebdc5f481f45e6e6871e0c43af5f --merged-by octocat --json
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Rules that will bite otherwise:
|
|
270
|
+
|
|
271
|
+
- **A pull request is addressed by its id, and *found* by its number.** scm has no
|
|
272
|
+
`identifier` and no `short_id` anywhere, and the detail path takes the 24-hex id only.
|
|
273
|
+
So `--number` is a **list filter**, and `pr get|update` take the id: run
|
|
274
|
+
`pingcode scm pr list --number 42 --json` and read `values[0].id`. The CLI does not
|
|
275
|
+
guess whether what you typed "looks like a number". `--number` is an **exact filter and
|
|
276
|
+
it really works** (verified live) — unlike `repo list`'s ignored `?name=`, so this
|
|
277
|
+
discovery path is reliable; an unused number returns zero rows.
|
|
278
|
+
- **`--status` is required by the API on every patch**, not just when you want to change
|
|
279
|
+
it. Omit it and the CLI reads the pull request first and re-sends its *current* status,
|
|
280
|
+
which costs one extra GET; pass `--status` and it does not. There is no way to send a
|
|
281
|
+
patch without a status (verified live: `100008 'status'是必填字段`). The patch is
|
|
282
|
+
genuinely partial otherwise — counts and work-item links you do not mention survive it.
|
|
283
|
+
- **Both `--target-branch-id` and `--source-branch-id` are required on create**, and both
|
|
284
|
+
take **branch ids**, from `scm branch list --json`. They must also **differ** (the
|
|
285
|
+
server refuses `source == target`). The published API reference marks the source branch
|
|
286
|
+
optional; the live API rejects a create without it, so the CLI requires it and you get
|
|
287
|
+
exit 2 naming the flag instead of a server error.
|
|
288
|
+
- **`--merged-at`, `--merged-commit-sha` and `--merged-by` become required when
|
|
289
|
+
`--status merged`.** The server enforces that, not the CLI, so expect exit 7 rather than
|
|
290
|
+
exit 2 if you forget one (verified live: `100212`).
|
|
291
|
+
- ⚠️ **`--creator` and `--merged-by` CREATE a 托管平台用户 if the name is unknown.** They are
|
|
292
|
+
git usernames matched against 托管平台用户 rows by name, and an unrecognised one is
|
|
293
|
+
**upserted**, exactly as branch `--sender` is. scm has **no user DELETE**, so a typo
|
|
294
|
+
leaves a permanent ghost identity in the tenant. Create or confirm the identity first
|
|
295
|
+
with `scm platform-user list --name <username>`. (A commit's `--committer` is the one
|
|
296
|
+
exception — it creates nothing.)
|
|
297
|
+
- **The six `--*-count` flags are yours to report.** Nothing server-side recomputes them.
|
|
298
|
+
Note that the server stores `0` for any count you do not send, so unlike `merged_at`
|
|
299
|
+
(which stays absent) an omitted count is indistinguishable from `0` once written.
|
|
300
|
+
- **`--work-item` takes an identifier (`PLM-001`) and an unknown one is silently
|
|
301
|
+
ignored**, exactly as on a branch: the response's `work_items` array is the only
|
|
302
|
+
evidence, the CLI warns on stderr about identifiers that did not link, and the exit code
|
|
303
|
+
stays 0. On `update` it **REPLACES** the whole link set. `--work-item-id` on `list` is a
|
|
304
|
+
different thing — a work item **id**, and an unknown one is an error (exit 5), not an
|
|
305
|
+
empty result.
|
|
306
|
+
- **`number` must be unique in the repository**; a duplicate is a server-side conflict
|
|
307
|
+
(exit 7), not a silent overwrite.
|
|
308
|
+
- **A missing pull request exits 5**, on `get`, on `update`, and on `review create`.
|
|
309
|
+
|
|
310
|
+
### Code reviews — `scm review`
|
|
311
|
+
|
|
312
|
+
⚠️ **`scm review` is not `pingcode api … /v1/reviews`.** Two unrelated resources share
|
|
313
|
+
the word:
|
|
314
|
+
|
|
315
|
+
| | `scm review` (this section) | `/v1/reviews` (generic layer only) |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
| what | 代码评审: one review event on one pull request | 评审: a polymorphic review object with contents |
|
|
318
|
+
| addressed by | platform + repository + pull request | `principal_type` + `pilot_id` |
|
|
319
|
+
| used by | git / CI write-back | 需求 and 用例 review flows |
|
|
320
|
+
| reachable as | `pingcode scm review …` | `pingcode api GET /v1/reviews …` |
|
|
321
|
+
|
|
322
|
+
Their ids are not interchangeable. If you are recording "someone approved PR #42", you
|
|
323
|
+
want this section.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
pingcode scm review list --platform Github --repo acme/pingcode-cli \
|
|
327
|
+
--pr-id 594587fe700d43b81b080789 --json
|
|
328
|
+
|
|
329
|
+
pingcode scm review get 524587fe700d43b81b080988 --platform Github --repo acme/pingcode-cli \
|
|
330
|
+
--pr-id 594587fe700d43b81b080789 --json
|
|
331
|
+
|
|
332
|
+
pingcode scm review create --platform Github --repo acme/pingcode-cli \
|
|
333
|
+
--pr-id 594587fe700d43b81b080789 \
|
|
334
|
+
--status approved --reviewer octocat --submitted-at 2026-08-03T10:00:00Z \
|
|
335
|
+
--description "Review has approved" \
|
|
336
|
+
--html-url 'https://github.com/acme/pingcode-cli/pull/42#pullrequestreview-384383294' --json
|
|
337
|
+
|
|
338
|
+
pingcode scm review update 524587fe700d43b81b080988 --platform Github --repo acme/pingcode-cli \
|
|
339
|
+
--pr-id 594587fe700d43b81b080789 --status request_changes --json
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
- **`--pr-id` is required on every leaf, and it takes an id, not a number.** A review is
|
|
343
|
+
addressed three parents deep, and **there is no repository-wide or organisation-wide
|
|
344
|
+
review list** — you enumerate one pull request at a time, exactly as with `scm ref` and
|
|
345
|
+
branches. Get the id from `scm pr list --number <n> --json`.
|
|
346
|
+
- ⚠️ **A wrong `--pr-id` reads as "no reviews", not as an error.** `review list` against a
|
|
347
|
+
pull request that does not exist returns an **empty list with exit 0** — the pull request
|
|
348
|
+
is the one scm parent whose absence a child *list* does not report (a bad `--platform` or
|
|
349
|
+
`--repo` does fail). So an empty result means "either no reviews, or that pull request is
|
|
350
|
+
not there". Confirm with `scm pr get <id>` if it matters. `review get` and
|
|
351
|
+
`review create` do fail properly, with exit 5.
|
|
352
|
+
- ⚠️ **`--reviewer` CREATES a 托管平台用户 if the name is unknown**, exactly like
|
|
353
|
+
`pr create --creator` and `branch create --sender`, and platform users cannot be
|
|
354
|
+
deleted. Confirm the username first with `scm platform-user list --name <username>`.
|
|
355
|
+
- **A missing review exits 5** — including a real review id passed with the wrong
|
|
356
|
+
`--pr-id`, because the review genuinely is not at that address.
|
|
357
|
+
- **`--submitted-at` is required on create.** A review carries **no server-assigned
|
|
358
|
+
timestamp at all** — no `created_at`, no `updated_at` — so the time is yours to supply.
|
|
359
|
+
On `update` it is optional like everything else: this PATCH has no mandatory field
|
|
360
|
+
(unlike `scm pr update`).
|
|
361
|
+
- **`--status` is `comment` / `approved` / `request_changes`.** A value outside the enum
|
|
362
|
+
is rejected by the server (exit 7), not by the CLI.
|
|
363
|
+
- **`--reviewer` is a git username**, attribution by name again.
|
|
364
|
+
- **`--html-url` is optional**; without it PingCode shows no jump link back to the
|
|
365
|
+
hosting platform. **Quote it** in a shell — review URLs contain a `#`.
|
|
366
|
+
|
|
367
|
+
### Name → id
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
pingcode resolve scm-platform Github --json
|
|
371
|
+
pingcode resolve scm-repo pingcode-cli --parent 68393e8b47512a5d5d4e5b55 --json
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Both are cached for 24 h under `~/.pingcode/cache/`; pass `--no-cache` when a platform was
|
|
375
|
+
reconfigured. `--platform <name|id>` resolves by name; `--platform-id <id>` is sent verbatim
|
|
376
|
+
with no lookup, and the two are mutually exclusive (exit 2).
|
|
377
|
+
|
|
378
|
+
### What cannot be deleted, and why nothing is `replace`d
|
|
379
|
+
|
|
380
|
+
**`scm branch delete` is the only delete in this module.** For everything else — platforms,
|
|
381
|
+
git identities, repositories, commits, refs, **pull requests and code reviews** —
|
|
382
|
+
**no DELETE exists upstream**. Nothing you create there can ever be removed through the API, so
|
|
383
|
+
mark test data clearly and check spellings before you write. A mistyped pull request title
|
|
384
|
+
can be patched; a pull request created against the wrong repository or with the wrong
|
|
385
|
+
`--number` **cannot be withdrawn**. `--owner-name` and branch `--sender` are the two flags
|
|
386
|
+
that can create a row by accident.
|
|
387
|
+
|
|
388
|
+
**`PUT` is deliberately not offered anywhere.** Five scm families document a `PUT` that
|
|
389
|
+
replaces the whole record and blanks every field you did not send, and this API never
|
|
390
|
+
documents what clearing a field does. Use `update` (PATCH). **Full replacement of a pull
|
|
391
|
+
request or a code review therefore goes through `pingcode api PUT`** — and there, omitting
|
|
392
|
+
a field is not "leave it alone", it is a wipe. If you truly want a full replacement, ask
|
|
393
|
+
for it explicitly through the escape hatch:
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
pingcode api PUT /v1/scm/products/68393e8b47512a5d5d4e5b55 \
|
|
397
|
+
--set name=Github --set type=github
|
|
398
|
+
|
|
399
|
+
pingcode api PUT /v1/scm/products/<platform>/repositories/<repo>/pull_requests/<pr> \
|
|
400
|
+
--set title=… --set creator_name=… --set source_branch_id=… --set target_branch_id=… --set status=open
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
See [`api.md`](api.md).
|
|
404
|
+
|
|
405
|
+
⚠️ **代码分支 has no `PUT` upstream at all** — its fifth verb is `DELETE`, which is why
|
|
406
|
+
`scm branch delete` exists while its siblings have none. So there is nothing missing to
|
|
407
|
+
"complete": do not add a `scm branch replace`, and do not expect `scm platform delete` to
|
|
408
|
+
appear. All six documented scm families are now refined commands; the only part of
|
|
409
|
+
`/v1/scm/**` that is generic-layer-only is those five `PUT`s.
|
|
410
|
+
|
|
411
|
+
### Errors you should expect
|
|
412
|
+
|
|
413
|
+
| Situation | Exit |
|
|
414
|
+
|---|---|
|
|
415
|
+
| `--platform` missing, or given together with `--platform-id` | 2 |
|
|
416
|
+
| a name matches nothing, or matches two repositories | 2 |
|
|
417
|
+
| `update` with no field to change | 2 |
|
|
418
|
+
| `--private maybe` | 2 |
|
|
419
|
+
| the token lacks `pcp:*:devops:code` | 4 |
|
|
420
|
+
| a platform / repository / identity / branch / commit / ref id does not exist | 5 |
|
|
421
|
+
| `scm ref create` names a `--sha` or `--branch-id` that does not exist | 5 |
|
|
422
|
+
| an abbreviated SHA passed to `scm commit get` | 5 |
|
|
423
|
+
| `--branch-id` missing on `scm ref list` | 2 |
|
|
424
|
+
| `scm branch delete` without `--yes` | 2 |
|
|
425
|
+
| deleting the **default** branch | 7 |
|
|
426
|
+
| a `--type` outside the enum, or a duplicate name / SHA / ref | 7 |
|
|
427
|
+
| `scm ref list` for a branch that was deleted (server error) | 7 |
|
|
428
|
+
| `--pr-id` missing on any `scm review` leaf | 2 |
|
|
429
|
+
| a non-numeric `--number`, or a non-date `--merged-at` / `--submitted-at` | 2 |
|
|
430
|
+
| a pull request or code review id that does not exist | 5 |
|
|
431
|
+
| `--status merged` without `--merged-at` / `--merged-commit-sha` / `--merged-by` | 7 |
|
|
432
|
+
| a `--status` outside its enum, or a duplicate pull request `--number` | 7 |
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# 产品管理 (ship) — `product`
|
|
2
|
+
|
|
3
|
+
> Part of the `pingcode` skill. Read [`../SKILL.md`](../SKILL.md) first — it carries the
|
|
4
|
+
> authentication gate, the stdout/stderr contract, the exit-code table and the rules that
|
|
5
|
+
> apply to every module. This file is only the ship command surface plus the ship-specific traps.
|
|
6
|
+
|
|
7
|
+
### Products — 产品管理
|
|
8
|
+
|
|
9
|
+
A **product** (产品) is ship's parent scope, the way a project is pjm's. Resolve it first: every other
|
|
10
|
+
ship id is scoped to it.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pingcode product list --json
|
|
14
|
+
pingcode product list --keywords sales --json
|
|
15
|
+
pingcode product get SLC --json # name, identifier such as SLC, or id
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`--keywords` searches the **name only** — the identifier is not searchable server-side, so the CLI
|
|
19
|
+
matches it client-side over the full list. There is no `product create`/`update`/`delete`: ship has no
|
|
20
|
+
product DELETE at all, and `PATCH` only edits three cosmetic fields.
|
|
21
|
+
|
|
22
|
+
### `product meta` — mandatory before writing an idea or ticket
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pingcode product meta idea-states --product SLC --json
|
|
26
|
+
pingcode product meta idea-priorities --product SLC --json
|
|
27
|
+
pingcode product meta idea-suites --product SLC --json
|
|
28
|
+
pingcode product meta idea-properties --product SLC --json
|
|
29
|
+
pingcode product meta idea-plans --product SLC --json
|
|
30
|
+
pingcode product meta members --product SLC --json
|
|
31
|
+
pingcode product meta ticket-states --product SLC --json
|
|
32
|
+
pingcode product meta ticket-priorities --product SLC --json
|
|
33
|
+
pingcode product meta ticket-types --product SLC --json
|
|
34
|
+
pingcode product meta ticket-channels --product SLC --json
|
|
35
|
+
pingcode product meta ticket-properties --product SLC --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`product meta members` is the **only** valid source of `--assignee` values for ideas and tickets —
|
|
39
|
+
the organisation directory is not, because a user who is not a member of the product cannot be
|
|
40
|
+
assigned. `product meta idea-properties` / `product meta ticket-properties` are the only source of `--set` keys and,
|
|
41
|
+
for select-typed properties, of the option ids you must send instead of the display label.
|
|
42
|
+
`product meta idea-plans` is the only source of `plan_id` values — read the 需求排期 section below
|
|
43
|
+
before you use the word "plan" anywhere near this API.
|
|
44
|
+
|
|
45
|
+
### Requirements 需求 — `product idea`
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pingcode product idea list --product SLC --json
|
|
49
|
+
pingcode product idea list --product SLC --state 待评审 --assignee zhangsan --json
|
|
50
|
+
pingcode product idea list --product SLC --keywords sso --page-size 20 --page 0 --json
|
|
51
|
+
pingcode product idea list --product SLC --all --limit 200 --json
|
|
52
|
+
|
|
53
|
+
pingcode product idea get SLC-1 --json # identifier, id, or a pasted idea URL
|
|
54
|
+
|
|
55
|
+
pingcode product idea create --product SLC --title "Single sign-on" --dry-run --json
|
|
56
|
+
pingcode product idea create --product SLC --title "Single sign-on" \
|
|
57
|
+
--assignee zhangsan --priority P1 --suite "客户端 / 登录" --json
|
|
58
|
+
|
|
59
|
+
pingcode product idea update SLC-1 --title "Single sign-on (v2)" --json
|
|
60
|
+
pingcode product idea update SLC-1 --state 开发中 --json
|
|
61
|
+
pingcode product idea update SLC-1 --set 需求类型=5cb7e763fda1ce4ca0010002 --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`product idea list` is `POST /v1/ship/ideas/search` — the plain list endpoint cannot filter by assignee, date
|
|
65
|
+
or custom property, so the CLI never uses it. Note there is **no `--type`** anywhere on `idea`: ship
|
|
66
|
+
states are scoped to the product alone, which `--product` (or, on `update`, the idea itself) already
|
|
67
|
+
supplies.
|
|
68
|
+
|
|
69
|
+
### State history 流转记录 — `product idea history`
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pingcode product idea history list SLC-1 --json
|
|
73
|
+
pingcode product idea history list SLC-1 --all --json
|
|
74
|
+
pingcode product idea history get SLC-1 6a1cd3670faf359d7447bf37 --json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**State changes only.** A title, assignee, priority or 排期 change is *not* here — that is
|
|
78
|
+
`pingcode product idea activity`, the free-form audit feed. Every requirement has exactly one row
|
|
79
|
+
from creation, printed with `FROM` as `(new)`. Read-only upstream (a POST or DELETE on this path
|
|
80
|
+
answers HTTP 405), and the endpoint accepts `?name=`, `?state_id=` and `?keywords=` while **ignoring
|
|
81
|
+
all three** — which is why the CLI offers no filter flag here. Filter client-side after `--all`.
|
|
82
|
+
|
|
83
|
+
Unlike some sibling lists in this API, this one validates its parent: an empty result really means
|
|
84
|
+
"this requirement has no rows", and a bad reference exits 5. A history id that belongs to a
|
|
85
|
+
*different* requirement also exits 5 — the (requirement, record) pair is the address, and the CLI
|
|
86
|
+
resolves your `SLC-1` to an id first because the raw endpoint accepts nothing else.
|
|
87
|
+
|
|
88
|
+
### Requirement schedules 需求排期 — `product plan`
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pingcode product plan list --product SLC --json
|
|
92
|
+
pingcode product plan list --product SLC --all --limit 200 --json
|
|
93
|
+
pingcode product plan get 6a1c53580faf359d7447b68e --product SLC --json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Read-only, and permanently so**: `POST`, `PATCH` and `DELETE` on the schedule path all answer
|
|
97
|
+
HTTP 405, so there is nothing to reach through `pingcode api` either — schedules are created in the
|
|
98
|
+
web UI. There is **no filter flag**, for the same reason as above: the endpoint documents none and an
|
|
99
|
+
undeclared `?name=` changed nothing when tried.
|
|
100
|
+
|
|
101
|
+
`product plan list` returns the full record (name, assignee, start, end); `product meta idea-plans`
|
|
102
|
+
returns the same rows as `{id, name}` only, because ship answers **two structures for one resource**
|
|
103
|
+
depending on the endpoint. Use `plan list` to read a schedule, `meta idea-plans` to pick an id for
|
|
104
|
+
`product idea update --plan-id`.
|
|
105
|
+
|
|
106
|
+
A schedule id that does not exist exits **7**, not 5 — read the message rather than the code. The
|
|
107
|
+
vendor code is the same one an idea PATCH answers for an unknown `--plan-id`, and whether it can
|
|
108
|
+
additionally mean "exists, but in another product" has not been measurable on any tenant reached so
|
|
109
|
+
far (no tenant has had a single 排期 yet).
|
|
110
|
+
|
|
111
|
+
### Tickets 工单 — `product ticket`
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pingcode product ticket list --product SLC --json
|
|
115
|
+
pingcode product ticket list --product SLC --type 故障 --state 待处理 --json
|
|
116
|
+
pingcode product ticket list --product SLC --channel 邮件 --all --limit 200 --json
|
|
117
|
+
|
|
118
|
+
pingcode product ticket get SLC-7 --json
|
|
119
|
+
|
|
120
|
+
pingcode product ticket create --product SLC --type 故障 --title "Cannot log in" --dry-run --json
|
|
121
|
+
pingcode product ticket create --product SLC --type 故障 --title "Cannot log in" \
|
|
122
|
+
--assignee zhangsan --priority P1 --channel 邮件 --json
|
|
123
|
+
|
|
124
|
+
pingcode product ticket update SLC-7 --title "Cannot log in (iOS)" --json
|
|
125
|
+
pingcode product ticket transition SLC-7 --state 处理中 --json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`--type` is **required** on `product ticket create` — `type_id` is a required body field, which is the one
|
|
129
|
+
place ship demands a lookup (`pingcode product meta ticket-types --product SLC`) before a write can even be
|
|
130
|
+
attempted. `--channel` can only be set at create time; there is no way to change it afterwards.
|
|
131
|
+
|
|
132
|
+
## 4b. Ship rules that will bite you
|
|
133
|
+
|
|
134
|
+
These are on top of §4, which still applies. Ship is a different module with the same machinery, and
|
|
135
|
+
almost every difference is a trap.
|
|
136
|
+
|
|
137
|
+
1. **Resolve the product first, and scope everything to it.** A product is to ship what a project is
|
|
138
|
+
to pjm. `state_id`, `priority_id`, `suite_id`, `type_id`, `channel_id`, the `properties` keys and
|
|
139
|
+
the assignable people are **all product-scoped**. They frequently *look* org-global — the same
|
|
140
|
+
priority id `P0` appears under several products — but the API requires `product_id` on every
|
|
141
|
+
lookup, so never carry an id from one product to another.
|
|
142
|
+
2. **`--assignee` must be a product member.** `pingcode product meta members --product <p>` is the
|
|
143
|
+
only valid candidate set; the organisation directory (`settings users`) is not, and a non-member is
|
|
144
|
+
rejected.
|
|
145
|
+
3. **`--state <name>` needs no companion flag here.** Unlike pjm, ship states hang off the product
|
|
146
|
+
alone, so there is no `--type` on `idea` at all, and `--type` on `ticket` is a real field being
|
|
147
|
+
written, not a lookup aid. `--state` and `--state-id` remain mutually exclusive.
|
|
148
|
+
4. **Reads go through `search`.** `product idea list` and `product ticket list` are `POST …/search`. The plain list
|
|
149
|
+
endpoints exist but cannot filter by assignee, date or custom property, so the CLI never uses
|
|
150
|
+
them. Search takes **one operator per field and has no `$and`/`$or`**; multiple filters are
|
|
151
|
+
AND-ed. There is still no sorting anywhere.
|
|
152
|
+
5. **No state change is refused locally — the server decides, and a ticket refusal is explained.**
|
|
153
|
+
- `pingcode product ticket transition` and `product ticket update --state` send the PATCH. If the server refuses
|
|
154
|
+
it, the error `message` names the product's configured states, the current state and — when
|
|
155
|
+
the state plan can be read — **the states reachable from the current one**. Read it from
|
|
156
|
+
`message`: `--json` errors are `{kind,message,code,exit}` and carry no hint.
|
|
157
|
+
- Want to know before you write? `product ticket transition <t> --state <s> --dry-run` prints the
|
|
158
|
+
reachable set on stderr and sends nothing.
|
|
159
|
+
- `pingcode product idea update --state` gets the configured states on rejection but never a reachable
|
|
160
|
+
set: ship publishes no idea state-flow endpoint at all.
|
|
161
|
+
- The CLI does **not** refuse a transition on its own (the one exception: moving a ticket to the
|
|
162
|
+
state it is already in, which is exit 2). The server refuses atomically with no state change,
|
|
163
|
+
so a local check saves nothing — and a mis-identified state plan would otherwise block a legal
|
|
164
|
+
move with no escape hatch. Expect the server's exit code, not exit 2, for an illegal target.
|
|
165
|
+
6. **`--set key=value` sends the value verbatim, and select properties want option ids.** For a
|
|
166
|
+
`select`-typed property the API expects the option's `_id`, not the label you see in the UI —
|
|
167
|
+
the docs' own examples only show text properties, which is the trap. Run
|
|
168
|
+
`pingcode product meta idea-properties --product <p>` (or `ticket-properties`): it prints each key and
|
|
169
|
+
its `label=option_id` pairs. `properties` **replaces**, it never merges.
|
|
170
|
+
7. **Nothing in ship can be deleted.** There is no DELETE for products, ideas or tickets, and
|
|
171
|
+
`is_archived` / `is_deleted` are read-only. A test artifact you create is permanent — mark it in
|
|
172
|
+
the title (for example `[CLI smoke] …`) before you create it, not after.
|
|
173
|
+
8. **An identifier works on the resource, and nowhere below it.** `GET /v1/ship/ideas/<x>` accepts
|
|
174
|
+
the id, the 8-char `short_id` a pasted URL ends in **and** the human `SLC-1` — all three answered
|
|
175
|
+
200 live, correcting an earlier note here that said none of them did. But every *sub-collection* —
|
|
176
|
+
`history`, and the `relation` / `comment` / `attachment` / `activity` families — takes the 24-hex
|
|
177
|
+
id only and answers HTTP 404 `资源路径错误` for anything else. The CLI therefore resolves your
|
|
178
|
+
reference to an id first (one extra request when the identifier has a dashed product prefix, or a
|
|
179
|
+
`search` hop when it does not), so all four forms work at the command layer.
|
|
180
|
+
9. **`--suite` filtering on `product idea list` is undocumented.** The API lists `suite.id` as neither
|
|
181
|
+
filterable nor unfilterable, so an empty result proves nothing. The CLI warns when you use it.
|
|
182
|
+
10. **"Plan" is three unrelated things, and only one of them is a 排期.** Getting this wrong is the
|
|
183
|
+
fastest way to hand a valid id to the wrong endpoint and get a not-found you cannot explain:
|
|
184
|
+
| You mean | Command | What it is |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| 需求排期 requirement schedule | `pingcode product plan list --product <p>` | a named window a requirement is planned into; `idea update --plan-id` takes its id |
|
|
187
|
+
| 测试计划 test plan | `pingcode testhub plans list --library <l>` | a test cycle in a test library, with its own states and runs |
|
|
188
|
+
| 配置方案 configuration scheme | `pingcode api GET /v1/ship/ticket_state_plans` | a *scheme* — a reusable bundle of states/properties/transitions bound to products |
|
|
189
|
+
They share no ids and no vocabulary. The 排期 is the only one that is read-only for the whole
|
|
190
|
+
surface, and the configuration schemes are the only one with **no leaf you can type**. Note that
|
|
191
|
+
"no leaf" is not "not wired": `ticket_state_plans` and its `ticket_state_flows` child *are*
|
|
192
|
+
called, by the resolver cache, so that `ticket transition` can tell you which states are
|
|
193
|
+
reachable when the server refuses one. So they count towards README's 158 refined endpoints even
|
|
194
|
+
though no command bears their name — which is why that table is labelled *the refined layer*.
|
|
195
|
+
11. **Tags cannot be set through the API** on ideas or tickets, and `submitter_id` on a ticket is
|
|
196
|
+
silently ignored under a client-credentials token — the ticket is attributed to the token owner
|
|
197
|
+
with no error. The CLI exposes neither.
|