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,385 @@
|
|
|
1
|
+
# 项目管理 (pjm) — `project`
|
|
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 pjm command surface and the ids it needs.
|
|
6
|
+
|
|
7
|
+
### Projects — 项目管理
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pingcode project list --json
|
|
11
|
+
pingcode project list --keywords mobile --type scrum
|
|
12
|
+
pingcode project get "Mobile App" --json
|
|
13
|
+
pingcode project progress "Mobile App" --json
|
|
14
|
+
|
|
15
|
+
pingcode project create --name "Payments" --identifier PAY --type scrum --dry-run --json
|
|
16
|
+
pingcode project create --name "Payments" --identifier PAY --type scrum \
|
|
17
|
+
--assignee wangxiao --member wangxiao --member lina --json
|
|
18
|
+
pingcode project update "Payments" --description "Q4 rewrite" --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**A project cannot be deleted, and cannot even be archived.** There is no `DELETE
|
|
22
|
+
/v1/pjm/projects/{id}`, and `is_archived` is read-only — a patch containing it returns 200 and
|
|
23
|
+
changes nothing (verified live). So `project create` is **irreversible**: a project created by
|
|
24
|
+
mistake stays in every listing forever. Always `--dry-run` first.
|
|
25
|
+
|
|
26
|
+
Two more create-time facts an `--help` line cannot convey:
|
|
27
|
+
|
|
28
|
+
- **`--visibility` can only be set at create time.** Patching it later is accepted and silently
|
|
29
|
+
dropped, exactly like `is_archived`, `type` and `process_id`.
|
|
30
|
+
- **`--identifier` prefixes every work-item id** in the project (`PAY-1`, `PAY-2`). It must be
|
|
31
|
+
uppercase and short; anything else is rejected as a bad format rather than truncated. It is the
|
|
32
|
+
one required create field that *can* still be changed afterwards — and changing it renames every
|
|
33
|
+
work-item identifier in the project.
|
|
34
|
+
- **`--start-at` / `--end-at` are instants, stored verbatim** — `12:34:56` comes back as
|
|
35
|
+
`12:34:56`. This is the opposite of `project sprint` and `project version`, which snap to
|
|
36
|
+
`00:00:00` / `23:59:59` of the date. That is why this pair is spelled `--start-at` / `--end-at`
|
|
37
|
+
and theirs is `--start` / `--end`. The inconsistency is upstream's, not the CLI's.
|
|
38
|
+
- **`--assignee` (负责人) is unrelated to membership.** It may be someone who is not a member, and
|
|
39
|
+
setting it does not add them. `PATCH` is partial: fields you do not pass are untouched.
|
|
40
|
+
|
|
41
|
+
`project progress` returns **work-item counts only** — no sprint, release or workload figure — and
|
|
42
|
+
it is a single count block, not a list, despite the API docs implying otherwise. The three counts
|
|
43
|
+
group every state by its `type`, so a custom state counts towards whichever of
|
|
44
|
+
pending / in_progress / completed it is configured as.
|
|
45
|
+
|
|
46
|
+
### 项目成员 Members — `project member`
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pingcode project member list --project "Mobile App" --json
|
|
50
|
+
pingcode project member get wangxiao --project "Mobile App" --json
|
|
51
|
+
pingcode project member add --project "Mobile App" --user lina --json
|
|
52
|
+
pingcode project member add --project "Mobile App" --group-id 68389e7f33ee52bc5c2584c1 --json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- **A membership is addressed by the USER id.** The row's `id` *is* the user (or group) id, so
|
|
56
|
+
`member get` takes the same reference `member add --user` took. There is no separate membership
|
|
57
|
+
id.
|
|
58
|
+
- **There is no `member remove` leaf.** The endpoint exists; it is reachable through the generic
|
|
59
|
+
layer, and that call is verified working:
|
|
60
|
+
`pingcode api DELETE /v1/pjm/projects/<project_id>/members/<user_id> --yes`. It is not a refined
|
|
61
|
+
leaf because a membership is the cheapest thing in this API to recreate — one `member add`.
|
|
62
|
+
- `--role-id` is optional and defaults to 普通成员. The three roles are organisation-level:
|
|
63
|
+
`pingcode api GET /v1/directory/roles`.
|
|
64
|
+
- A user who is not in the project, **and** an id that does not exist, both answer
|
|
65
|
+
`成员不在项目中` → **exit 5**. The two are indistinguishable, so exit 5 here means "not a member
|
|
66
|
+
of this project", not necessarily "no such user".
|
|
67
|
+
|
|
68
|
+
### `project meta` — mandatory before creating or updating a work item
|
|
69
|
+
|
|
70
|
+
`type_id`, `state_id` and `priority_id` are **project-scoped**: the same state name has a different id
|
|
71
|
+
in another project, and system work-item types use slugs (`task`, `story`, `bug`) while custom types
|
|
72
|
+
use hex ids. Never reuse an id across projects, and never guess one.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pingcode project meta types --project "Mobile App" --json
|
|
76
|
+
pingcode project meta states --project "Mobile App" --type task --json
|
|
77
|
+
pingcode project meta priorities --project "Mobile App" --json
|
|
78
|
+
pingcode project meta sprints --project "Mobile App" --json
|
|
79
|
+
pingcode project meta relation-types --json
|
|
80
|
+
pingcode project meta tags --project "Mobile App" --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`pingcode project meta states` requires **both** a project and a type — that is an API constraint, not a CLI
|
|
84
|
+
choice.
|
|
85
|
+
|
|
86
|
+
**`--state <name>` therefore always requires `--type`, everywhere in this module.** States live in a
|
|
87
|
+
`(project, work item type)` pair and the API does **not** report a work item's type, so the CLI cannot
|
|
88
|
+
infer it — not on `list`, not on `create`, and not on `update` / `transition` either. Either pass
|
|
89
|
+
`--type <name|id>` alongside `--state <name>`, or skip the lookup entirely with `--state-id <id>`.
|
|
90
|
+
`--state` and `--state-id` are mutually exclusive.
|
|
91
|
+
|
|
92
|
+
**State changes are workflow-validated by the server**: the target state must belong to the type's
|
|
93
|
+
state scheme *and* a legal transition must exist from the current state. There is no local
|
|
94
|
+
pre-check — on rejection the CLI prints the server's message plus the states configured for that
|
|
95
|
+
type on stderr, but only if you passed `--type`, since listing the candidates needs it too.
|
|
96
|
+
|
|
97
|
+
Lookups are cached under `~/.pingcode/cache/` for 24 hours. Use `--no-cache` if a project was
|
|
98
|
+
reconfigured and an id looks stale.
|
|
99
|
+
|
|
100
|
+
`project meta relation-types` is **organisation-wide** and takes no project: nine system rows, and
|
|
101
|
+
the `CATEGORY` column (`relate`, `block`, `blocked_by`, `cause`, `caused_by`, `clone`, `cloned_by`,
|
|
102
|
+
`duplicate`, `mention`) is the stable key to script against — the ids are 24-hex and differ per
|
|
103
|
+
tenant. It serves `project work-item link add` and nothing else.
|
|
104
|
+
|
|
105
|
+
**`project meta tags` needs reading before you use an id from it.** `--project` is *required* by the
|
|
106
|
+
endpoint and then *ignored* by it — three different projects return byte-identical lists (verified
|
|
107
|
+
live) — so what you get is every tag in the **organisation**. Tags are nevertheless really
|
|
108
|
+
project-scoped where it counts: `work-item tag add` refuses a tag belonging to another project with
|
|
109
|
+
`'tag'资源不存在`, which reads as though the tag did not exist. It does; it is simply not this
|
|
110
|
+
project's. Concretely: of 23 org-wide tags, **all 23 were refused** for a work item in one project
|
|
111
|
+
and **8 were accepted** for a work item in another. There is no endpoint listing just one project's
|
|
112
|
+
tags, so the reliable way to find a usable id is to read the `tags[]` of a work item already in that
|
|
113
|
+
project. Names are not unique either (four `后端`, three `前端`), which is also why `pingcode resolve`
|
|
114
|
+
has **no** work-item-tag kind: a cached resolver would hand back ids the write refuses.
|
|
115
|
+
|
|
116
|
+
### Work items — `project work-item`
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pingcode project work-item list --project "Mobile App" --json
|
|
120
|
+
pingcode project work-item list --project "Mobile App" --type task --state "In Progress" --json
|
|
121
|
+
pingcode project work-item list --project "Mobile App" --assignee wangxiao --page-size 20 --page 0
|
|
122
|
+
pingcode project work-item list --project "Mobile App" --all --limit 200 --json
|
|
123
|
+
|
|
124
|
+
pingcode project work-item get SCR-5 --json
|
|
125
|
+
pingcode project work-item get 1bAqLmTG --json
|
|
126
|
+
pingcode project work-item get https://example.pingcode.com/pjm/work_items/1bAqLmTG --json
|
|
127
|
+
|
|
128
|
+
pingcode project work-item create --project "Mobile App" --type task --title "Fix login retry" --dry-run --json
|
|
129
|
+
pingcode project work-item create --project "Mobile App" --type task --title "Fix login retry" \
|
|
130
|
+
--assignee wangxiao --priority High --end-at 2026-02-15 --json
|
|
131
|
+
|
|
132
|
+
pingcode project work-item update SCR-5 --title "Fix login retry (v2)" --json
|
|
133
|
+
pingcode project work-item update SCR-5 --type task --state "In Progress" --json
|
|
134
|
+
pingcode project work-item transition SCR-5 --type task --state Done --json
|
|
135
|
+
pingcode project work-item transition SCR-5 --state-id 5eb623f6a70571487ea47000 --json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`project work-item get` accepts an id, a `short_id`, an identifier such as `SCR-5`, or a pasted work-item URL.
|
|
139
|
+
`update` and `transition` accept the same forms and resolve them to a real id first.
|
|
140
|
+
|
|
141
|
+
On `update` and `transition`, `--type` is **only** a lookup aid: it resolves `--state <name>` and lets
|
|
142
|
+
the CLI list the candidate states if the server rejects the change. It is never written to the work
|
|
143
|
+
item — there is no patchable type field.
|
|
144
|
+
|
|
145
|
+
**The assignee cannot be cleared via the API.** `PATCH`'s `assignee_id` is a plain string with no
|
|
146
|
+
`nullable`, and the server treats `null` as "field absent" (HTTP 200, no-op) and `""` as invalid
|
|
147
|
+
(HTTP 400). So `work-item update <id> --assignee ""` does **not** send a request — it fails fast with
|
|
148
|
+
exit 2 and a message naming the limitation. To unassign, use the PingCode web UI.
|
|
149
|
+
|
|
150
|
+
#### Filtering: two transports, one command
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pingcode project work-item list --project "Mobile App" --unassigned --json
|
|
154
|
+
pingcode project work-item list --project "Mobile App" --title-contains login --json
|
|
155
|
+
pingcode project work-item list --project "Mobile App" --created-after 2026-08-01 --all --json
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Six flags — `--unassigned`, `--title-contains`, `--created-after/-before`,
|
|
159
|
+
`--updated-after/-before`, marked `(search)` in `--help` — can only be expressed by
|
|
160
|
+
`POST /v1/pjm/work_items/search`, so passing any of them switches the transport. **Paging, `--all`
|
|
161
|
+
and the reported total behave identically on both**, so the switch is invisible except for which
|
|
162
|
+
filters exist. What genuinely differs is the filter vocabulary: search cannot filter by
|
|
163
|
+
`identifier`, `short_id` or bug type at all, and the simple list cannot filter by date, title text
|
|
164
|
+
or "unassigned".
|
|
165
|
+
|
|
166
|
+
#### `bulk-update` — one property, many work items
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pingcode project work-item bulk-update --id SCR-5 --id SCR-6 --assignee wangxiao --json
|
|
170
|
+
pingcode project work-item bulk-update --id SCR-5 --id SCR-6 --project "Mobile App" --type task --state Done --json
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**The endpoint carries ONE `property_name` per call**, so exactly one of `--assignee` / `--state` /
|
|
174
|
+
`--priority` / `--title` / `--description` / `--property` may be given; two properties need two
|
|
175
|
+
invocations. Four things about it that will bite an agent that assumes otherwise:
|
|
176
|
+
|
|
177
|
+
- **`--sprint` does not exist here, deliberately.** `property_name: sprint_id` is accepted with
|
|
178
|
+
HTTP 200 and `updated: 0`, and changes nothing (verified live). The same is true of `type_id`,
|
|
179
|
+
`tag_ids`, `version_ids`, `participant_ids`, `properties` and `bug_type_id`. Moving items into a
|
|
180
|
+
sprint or onto a release has to go one at a time through `work-item update --sprint` /
|
|
181
|
+
`--release` — see the next section. Only `assignee_id`, `state_id`, `priority_id`, `title` and
|
|
182
|
+
`description` actually apply; `--property` is the unvalidated escape hatch for anything else.
|
|
183
|
+
- **Always read the `updated` count.** It is the *only* signal the endpoint gives: a nonexistent id,
|
|
184
|
+
a silently-rejected value and an unsupported property all answer 200. The CLI warns when `updated`
|
|
185
|
+
is less than the number of ids sent — treat that warning as "verify by reading the items back".
|
|
186
|
+
- **It is best-effort, not atomic.** An unknown id is skipped silently and the rest still land. (The
|
|
187
|
+
`sprint bulk-create` and `version bulk-create` *are* atomic — do not generalise from one to the other.)
|
|
188
|
+
- **It leaves no audit trail.** The change appears in neither `work-item activity list` nor
|
|
189
|
+
`work-item history list`, while the equivalent single `update` does. It is invisible to an audit.
|
|
190
|
+
|
|
191
|
+
#### Moving an existing item into a sprint, or onto a release
|
|
192
|
+
|
|
193
|
+
`work-item update` carries both, and it is the only refined path: `bulk-update` answers
|
|
194
|
+
200 / `updated: 0` for `sprint_id` and `version_ids` alike, however valid the id (verified live
|
|
195
|
+
2026-08-05, and again after these flags landed).
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pingcode project work-item update SCR-5 --sprint "Sprint 12" --json
|
|
199
|
+
pingcode project work-item update SCR-5 --release "1.4.0" --json
|
|
200
|
+
pingcode project work-item update SCR-5 --release "1.4.0" --release "1.4.1-hotfix" --json
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- **The release flag is `--release`, NOT `--version`.** `--version` belongs to the CLI itself, and
|
|
204
|
+
because commander's root parses options across the whole argv it wins even after the subcommand:
|
|
205
|
+
`work-item update <id> --version 1.4.0` prints `0.1.0` and **exits 0 having sent nothing**. Do not
|
|
206
|
+
reach for it. These are the same 发布 that `project version list` prints.
|
|
207
|
+
- **The two fields have different shapes upstream, so the flags differ.** `sprint_id` is a
|
|
208
|
+
**scalar**: one `--sprint`, and moving between sprints is a single call. `version_ids` is an
|
|
209
|
+
**array that replaces**: `--release` is repeatable, and the releases you pass become the complete
|
|
210
|
+
list. Pass every release the item should end up on in one invocation; read the current ones off
|
|
211
|
+
`work-item get` first if you mean to add rather than set.
|
|
212
|
+
- **No `--project` is needed.** Both names resolve per project, and the project comes off the work
|
|
213
|
+
item itself. Repeating `--release` costs no extra round-trip: the second name hits the list the
|
|
214
|
+
first one cached.
|
|
215
|
+
- **Neither field can be EMPTIED — not by these flags and not by any other route.** Omitting
|
|
216
|
+
`--release` leaves the list alone, and the generic layer cannot clear it either (all live
|
|
217
|
+
2026-08-05): `--body '{"version_ids":[]}'` is refused with
|
|
218
|
+
`100006 'version_ids'不是有效的数组(数组不能为空)`, `[""]` with `不是有效的字符串`, and
|
|
219
|
+
`{"version_ids":null}` answers **200 and changes nothing** — the accepted-and-ignored pattern this
|
|
220
|
+
API keeps repeating. `sprint_id` behaves identically (`null` → 200 no-op, `""` → `100003`). You can
|
|
221
|
+
move an item to a *different* sprint or release; you cannot take it off all of them. `version_ids`
|
|
222
|
+
is also one of the few fields the server really type-checks, so a stringified array via `--set` is
|
|
223
|
+
`100006`, exit 7, nothing written — use `--body` if you go around the flags.
|
|
224
|
+
- Both changes *are* audited, unlike `bulk-update`: they show up in `work-item activity list` as
|
|
225
|
+
`property_key: iteration` and `property_key: version`, with `origin`/`target` — which is also how
|
|
226
|
+
you verify them without trusting the 200.
|
|
227
|
+
|
|
228
|
+
#### `delete`
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pingcode project work-item delete SCR-5 --yes --dry-run --json
|
|
232
|
+
pingcode project work-item delete SCR-5 --yes
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`--yes` is mandatory and the refusal echoes the identifier and title, so a wrong reference is
|
|
236
|
+
visible before the write. Unlike a sprint (which can never be deleted) a work item **can** be, and
|
|
237
|
+
it leaves every list, sprint, board and link immediately. The web UI recycle bin can restore it;
|
|
238
|
+
this API cannot.
|
|
239
|
+
|
|
240
|
+
#### `link` vs `relation` — two unrelated families, and they must not be confused
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
pingcode project meta relation-types --json
|
|
244
|
+
pingcode project work-item link add SCR-5 --target SCR-6 --relation block --json
|
|
245
|
+
pingcode project work-item link list SCR-5 --json
|
|
246
|
+
pingcode project work-item link get SCR-5 <link_id> --json
|
|
247
|
+
pingcode project work-item link delete SCR-5 <link_id> --yes
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- **`link` is work item ↔ work item, with a required type** (`/v1/pjm/work_items/{id}/relations`).
|
|
251
|
+
`--relation` accepts the category slug (`block`), the localized name (`阻塞`) or the id — all three
|
|
252
|
+
verified working.
|
|
253
|
+
- **`relation` is work item ↔ anything else, with no type at all** (`/v1/relations`) and it
|
|
254
|
+
**refuses** two work items outright (`不支持的'principal_type'`). Use it for a work item to an
|
|
255
|
+
idea, ticket, test case or wiki page.
|
|
256
|
+
- The server maintains the **inverse** edge: adding `block` on one item adds `blocked_by` on the
|
|
257
|
+
other, and deleting either side removes both. **The two sides have different link ids**, so delete
|
|
258
|
+
the id `link list` printed for the item you are on — passing the other end's id answers
|
|
259
|
+
`工作项或工作项关联不存在` → exit 5.
|
|
260
|
+
- Links may cross projects, and a work item may be linked to itself.
|
|
261
|
+
|
|
262
|
+
#### `tag` — and the list that does not exist
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
pingcode project work-item tag add SCR-5 --tag-id 6a28fbe209dbd0bc097457ee --json
|
|
266
|
+
pingcode project work-item tag get SCR-5 <tag_id> --json
|
|
267
|
+
pingcode project work-item tag delete SCR-5 <tag_id> --yes
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**There is no `work-item tag list`, and there cannot be**: upstream has the add, the get-one and the
|
|
271
|
+
delete but no collection GET. Read the `tags[]` field of `work-item get` instead — that is the
|
|
272
|
+
complete answer for one work item. `project meta tags` is the only way to enumerate the vocabulary,
|
|
273
|
+
with the organisation-wide caveat documented above; `--tag <name>` is resolved live against the work
|
|
274
|
+
item's own project and errors with the candidates when a name is ambiguous, which the common names
|
|
275
|
+
are.
|
|
276
|
+
|
|
277
|
+
#### `history` — state changes only
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
pingcode project work-item history list SCR-5 --json
|
|
281
|
+
pingcode project work-item history get SCR-5 <history_id> --json
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
流转记录 is **state changes only**. A title, assignee or sprint change is not here — that is
|
|
285
|
+
`work-item activity list`, the free-form audit feed. Every work item has one row from creation, with
|
|
286
|
+
`FROM` shown as `(new)`. A `bulk-update` appears in **neither** feed.
|
|
287
|
+
|
|
288
|
+
### 迭代 Sprints — `project sprint`
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
pingcode project meta sprints --project "Mobile App" --json # the LIST lives here
|
|
292
|
+
pingcode project sprint get "Sprint 5" --project "Mobile App" --json
|
|
293
|
+
pingcode project sprint create --project "Mobile App" --name "Sprint 5" \
|
|
294
|
+
--start 2026-09-01 --end 2026-09-14 --assignee wangxiao --json
|
|
295
|
+
pingcode project sprint update "Sprint 5" --project "Mobile App" --status in_progress --json
|
|
296
|
+
pingcode project sprint bulk-create --project "Mobile App" --assignee wangxiao --file sprints.json --json
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Four things about sprints that `--help` cannot make obvious enough:
|
|
300
|
+
|
|
301
|
+
1. **There is no `sprint list` leaf.** The list is `project meta sprints`, because it doubles as
|
|
302
|
+
the lookup `--sprint <name>` resolves against. Do not look for `project sprint list`.
|
|
303
|
+
2. **A sprint cannot be deleted. Ever.** The API exposes only `GET` and `PATCH` on a sprint, so
|
|
304
|
+
`pingcode api DELETE /v1/pjm/projects/<p>/sprints/<id>` is refused before any request too.
|
|
305
|
+
Treat `sprint create` and `sprint bulk-create` as irreversible.
|
|
306
|
+
3. **Sprints exist only in scrum and hybrid projects.** In a kanban or waterfall project the list
|
|
307
|
+
is empty and a create fails with `'project'资源不存在` — *the project is fine*, sprints are not
|
|
308
|
+
available in it. That exits **7**, not 5, precisely because the code cannot be told apart from a
|
|
309
|
+
genuinely missing project; the CLI appends the explanation to the message. Plan releases with
|
|
310
|
+
`project version` instead, which works in every project type.
|
|
311
|
+
4. **`--status` writes a field, it does not run the lifecycle.** Setting `in_progress` or
|
|
312
|
+
`completed` through the API leaves `started_at` and `completed_at` `null`, and neither is
|
|
313
|
+
writable. A sprint "completed" via the API is not the same as one completed in the web UI.
|
|
314
|
+
|
|
315
|
+
### 发布 Releases — `project version`
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
pingcode project version list --project "Mobile App" --json
|
|
319
|
+
pingcode project version list --project "Mobile App" --name 1.4 --status in_progress --json
|
|
320
|
+
pingcode project version get "1.4.0" --project "Mobile App" --json
|
|
321
|
+
pingcode project version create --project "Mobile App" --name 1.4.0 \
|
|
322
|
+
--start 2026-09-01 --end 2026-09-30 --assignee wangxiao --json
|
|
323
|
+
pingcode project version update "1.4.0" --project "Mobile App" --stage-id <id> --operate-at 2026-09-20 --json
|
|
324
|
+
pingcode project version delete "1.4.0" --project "Mobile App" --yes --json
|
|
325
|
+
pingcode project version bulk-create --project "Mobile App" --assignee wangxiao --file releases.json --json
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**"version" is the most overloaded word in this API. This one is a project release 发布.** It is
|
|
329
|
+
not a wiki page revision (`/v1/wiki/pages/{id}/versions`), not a work-item state or property
|
|
330
|
+
*scheme* (`work_item_state_plans`, `*_property_plans`), and not a test plan or a requirement
|
|
331
|
+
schedule. Four different resources, one English word. `project version` only ever means the first.
|
|
332
|
+
|
|
333
|
+
- **`--name` on the list is a SUBSTRING search**, case-insensitive — `--name 1.4` matches `1.4.0`
|
|
334
|
+
and `1.4.1`. This is unlike `scm platform list --name` and `release env list --name`, which are
|
|
335
|
+
exact matches. Do not use it as an existence check.
|
|
336
|
+
- **`--status` filters on the stage's *kind*** — `pending | in_progress | published` — not on a
|
|
337
|
+
field of the release, which has no `status` at all. Stage *names* (`未开始`) are rejected with
|
|
338
|
+
exit 2.
|
|
339
|
+
- **The API ignores `--project` on `get` and `update`.** A version id is effectively
|
|
340
|
+
organisation-wide: naming the wrong project still reads, and still *writes* — the change lands on
|
|
341
|
+
the release in its real project. Only `delete` refuses a mismatched pair. Never treat a
|
|
342
|
+
successful update as proof that the project was right.
|
|
343
|
+
- **`progress` and `changelog` are read-only.** No body field writes either; sending one is
|
|
344
|
+
accepted and silently dropped.
|
|
345
|
+
- **`--operate-at` requires `--stage-id`.** Sent alone the API answers 200, echoes the *previous*
|
|
346
|
+
value and stores nothing, so the CLI refuses it with exit 2. Moving to a stage the release has
|
|
347
|
+
never been in requires it; moving to one it has been in does not. Moving to the stage it is
|
|
348
|
+
already in is refused by the server (exit 7).
|
|
349
|
+
- **`version delete` detaches the release from every work item that references it.** The work items
|
|
350
|
+
survive; their version link disappears. It is gated behind `--yes` and the confirmation names the
|
|
351
|
+
release.
|
|
352
|
+
- **Stages and categories are ids, not names.** `--stage-id` and `--category-id` take ids because
|
|
353
|
+
the stage and category endpoints are outside this command group:
|
|
354
|
+
`pingcode api GET /v1/pjm/stages` and
|
|
355
|
+
`pingcode api GET /v1/pjm/projects/<project_id>/version_categories`. A release name *is*
|
|
356
|
+
resolvable: `pingcode resolve pjm-version "1.4.0" --parent <project_id>`.
|
|
357
|
+
|
|
358
|
+
### Both families: dates, and the two `bulk-create` leaves
|
|
359
|
+
|
|
360
|
+
**`--start` and `--end` are dates, not instants.** The server stores `--start` at `00:00:00` and
|
|
361
|
+
`--end` at `23:59:59` of the date given, in both families, on create and on update. Pass
|
|
362
|
+
`2026-09-01`, not a timestamp, and do not try to express a partial day — it will be widened.
|
|
363
|
+
Unlike `release deploy`, both ends may travel in one update and are validated against each other.
|
|
364
|
+
|
|
365
|
+
Both `bulk-create` leaves take a JSON array (or the wire's own `{"sprints": […]}` / `{"versions": […]}`
|
|
366
|
+
wrapper) through `--file <path>`:
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
[
|
|
370
|
+
{"name": "Sprint 5", "start": "2026-09-01", "end": "2026-09-14"},
|
|
371
|
+
{"name": "Sprint 6", "start": "2026-09-15", "end": "2026-09-28", "assignee": "lina"}
|
|
372
|
+
]
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
- Every entry needs `name`, `start`, `end`, a project and an assignee. The last two may come from
|
|
376
|
+
the entry (`project` / `project_id`, `assignee` / `assignee_id`) or from a shared `--project` /
|
|
377
|
+
`--assignee` on the command line.
|
|
378
|
+
- **Unknown keys are refused before anything is sent.** This matters: the API accepts unknown body
|
|
379
|
+
fields with a 200 and silently drops them, so a typo would otherwise create every row with the
|
|
380
|
+
field missing and no error anywhere.
|
|
381
|
+
- **The call is atomic.** If any entry is rejected, none is created — verified live. There is no
|
|
382
|
+
entry limit (60 in one call was accepted), but two entries sharing a name inside one batch is an
|
|
383
|
+
HTTP 500.
|
|
384
|
+
- **Both `bulk-create` endpoints are 企业令牌 only and the docs declare no scope for them.** They work with
|
|
385
|
+
the CLI's client-credentials token. `pingcode api describe pjm.sprints.bulk` reports the same.
|