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,222 @@
|
|
|
1
|
+
# 构建与部署 (build / release) — `build`, `release`
|
|
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 build and deployment write-back surface and
|
|
6
|
+
> the ids it needs.
|
|
7
|
+
|
|
8
|
+
**What these two modules are for.** They are the other half of the *write-back* API
|
|
9
|
+
[`scm.md`](scm.md) describes: a CI/CD job tells PingCode "this build ran" and "this release
|
|
10
|
+
was deployed", and PingCode shows those facts on the work items involved. Nothing here runs
|
|
11
|
+
a pipeline, watches one, or reads anything from your CI server — every row you create is a
|
|
12
|
+
record inside PingCode that you wrote.
|
|
13
|
+
|
|
14
|
+
**Two separate groups, because they are two separate APIs** with different scopes:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
build 构建记录 /v1/build/builds pcp:*:devops:build
|
|
18
|
+
release 环境 /v1/release/environments ┐
|
|
19
|
+
部署 /v1/release/deploys ┘ pcp:*:devops:deploy
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**The write-back path.** Both groups are flat — there is no platform, project or product to
|
|
23
|
+
resolve first, unlike scm:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
build create ─────────────────────┐
|
|
27
|
+
├→ --work-item PLM-001 (that is the only link)
|
|
28
|
+
release env create → deploy create ┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
An environment is standing configuration (create it once); a build and a deploy are events
|
|
32
|
+
(one row per run).
|
|
33
|
+
|
|
34
|
+
**Three group-wide facts.**
|
|
35
|
+
|
|
36
|
+
- Both groups are **企业令牌 only** — which is exactly the token this CLI holds, so they work
|
|
37
|
+
out of the box. `build` needs `pcp:read:devops:build` / `pcp:write:devops:build`;
|
|
38
|
+
`release` needs `pcp:read:devops:deploy` / `pcp:write:devops:deploy` for **both** its
|
|
39
|
+
subgroups. These are *separate* from scm's `devops:code`: a token that can write commits
|
|
40
|
+
cannot write builds, and the only symptom is exit 4.
|
|
41
|
+
- **`--work-item` is the entire integration.** Neither group has any other reference to
|
|
42
|
+
PingCode data — no project, no product, no repository. A build or deploy with no
|
|
43
|
+
`--work-item` is invisible outside its own list.
|
|
44
|
+
- **Everything is organisation-level.** No `--platform`, no `--project`, no `--product` on
|
|
45
|
+
any leaf. Do not look for one.
|
|
46
|
+
|
|
47
|
+
### Build records — `build`
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pingcode build list --json
|
|
51
|
+
pingcode build list --page 1 --page-size 20 --json
|
|
52
|
+
pingcode build get 6a70c1eb919cce9794f01acb --json
|
|
53
|
+
pingcode build create --name unit-test --identifier 131 --provider jenkins --status success \
|
|
54
|
+
--start-at 1785700000 --end-at 1785700038 --duration 38 --dry-run --json
|
|
55
|
+
pingcode build create --name unit-test --identifier 131 --provider jenkins --status success \
|
|
56
|
+
--start-at 2026-08-04T09:00:00Z --end-at 2026-08-04T09:00:38Z --duration 38 \
|
|
57
|
+
--job-url https://ci.example.com/job/131 --result-overview "1000 test cases pass" \
|
|
58
|
+
--work-item PLM-001 --json
|
|
59
|
+
pingcode build update 6a70c1eb919cce9794f01acb --status failure --json
|
|
60
|
+
pingcode build delete 6a70c1eb919cce9794f01acb --yes --json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**`build list` has no filters. None.** The endpoint documents no query parameter, and
|
|
64
|
+
`identifier`, `name`, `status`, `provider` and `work_item_id` were each tried against the
|
|
65
|
+
live API and **silently ignored** — every row came back every time. So the CLI offers only
|
|
66
|
+
`--page` / `--page-size` / `--all`, and any list is a whole-organisation scan. **Keep the
|
|
67
|
+
`id` that `build create --json` returns**; it is the only cheap way back to a build.
|
|
68
|
+
|
|
69
|
+
**The build number (`--identifier`) is not a key.** Two builds may carry `131` and the API
|
|
70
|
+
accepts both. `build get` therefore takes an **id**, never a number, and there is no name
|
|
71
|
+
resolution to fall back on.
|
|
72
|
+
|
|
73
|
+
Seven flags are required on `create` because the API requires all seven: `--name`,
|
|
74
|
+
`--identifier`, `--provider`, `--status`, `--start-at`, `--end-at`, `--duration`. In
|
|
75
|
+
particular **`--duration` is not derived** from the two timestamps — the server never
|
|
76
|
+
computes it, so report whatever your pipeline measured.
|
|
77
|
+
|
|
78
|
+
- `--provider` is a closed enum: `bamboo | bitbucket | jenkins | other`. Anything else is
|
|
79
|
+
exit 7 from the server, not exit 2 from the CLI. There is no `github-actions` or
|
|
80
|
+
`gitlab-ci` value; use `other`.
|
|
81
|
+
- `--status` is `success | failure`. There is no "running", "cancelled" or "unstable" — a
|
|
82
|
+
build in flight is usually recorded once it finishes, or created and then moved with
|
|
83
|
+
`build update --status`.
|
|
84
|
+
- Timestamps take unix **seconds** or a date string. Milliseconds are rejected by the server
|
|
85
|
+
(exit 7, `数值不是有效的时间戳`), so pass seconds or let the CLI convert a date for you.
|
|
86
|
+
|
|
87
|
+
`build delete` exists — the only delete in the whole DevOps surface apart from
|
|
88
|
+
`scm branch delete` — because a build record can simply be written again. It requires
|
|
89
|
+
`--yes`, it reads the record first so the confirmation names it (`the build #131
|
|
90
|
+
"unit-test"`), and it takes no `--all`. It is a hard delete: a following `get` is exit 5.
|
|
91
|
+
Unlike deleting an scm branch, it takes nothing else with it.
|
|
92
|
+
|
|
93
|
+
There is **no `build replace`**. `PUT /v1/build/builds/{id}` exists upstream but would blank
|
|
94
|
+
every field you did not send, so it is reachable only as
|
|
95
|
+
`pingcode api PUT /v1/build/builds/<id>` — where you are explicitly asking for a full
|
|
96
|
+
replacement.
|
|
97
|
+
|
|
98
|
+
### Deploy environments — `release env`
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pingcode release env list --json
|
|
102
|
+
pingcode release env list --name production --json
|
|
103
|
+
pingcode release env get production --json
|
|
104
|
+
pingcode release env create --name production --html-url https://app.example.com --json
|
|
105
|
+
pingcode release env update production --html-url https://app.example.com/status --json
|
|
106
|
+
pingcode resolve release-env production --json | jq -r .id
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**An environment name is a complete address.** Names are unique per organisation and
|
|
110
|
+
`--name` is an exact (case-insensitive) filter that upstream really honours — so unlike
|
|
111
|
+
most ids in this API, you can pass the name everywhere an environment is expected, and
|
|
112
|
+
`pingcode resolve release-env <name>` turns it into the id `pingcode api` wants. A duplicate
|
|
113
|
+
create is exit 7 (`环境已经存在`).
|
|
114
|
+
|
|
115
|
+
Two things to know before scripting an update:
|
|
116
|
+
|
|
117
|
+
- **`--html-url` cannot be cleared.** The server validates it as a URL and rejects an empty
|
|
118
|
+
value, so a link can be replaced but never removed.
|
|
119
|
+
- The resource is only `{id, name, html_url}` — there is nothing else to patch.
|
|
120
|
+
|
|
121
|
+
There is **no `release env delete` leaf**, and this is *not* because the API lacks one: the
|
|
122
|
+
endpoint exists and works. It is simply not wrapped yet. Until it is:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pingcode api DELETE /v1/release/environments/<id> --yes
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The server **refuses** that while any deploy still references the environment (exit 7,
|
|
129
|
+
`'environment'正在使用,不能被删除`) — delete those deploys first. That refusal is good news:
|
|
130
|
+
unlike an scm branch, whose deletion orphans its commit refs permanently, nothing in
|
|
131
|
+
`release` can be left dangling.
|
|
132
|
+
|
|
133
|
+
### Deployment records — `release deploy`
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pingcode release deploy list --json
|
|
137
|
+
pingcode release deploy list --env production --json
|
|
138
|
+
pingcode release deploy get 6a70c153919cce9794f01aca --json
|
|
139
|
+
pingcode release deploy create --env production --status deployed --release-name 1.4.0 \
|
|
140
|
+
--start-at 1785700000 --end-at 1785700200 --duration 200 --dry-run --json
|
|
141
|
+
pingcode release deploy create --env production --status deployed --release-name 1.4.0 \
|
|
142
|
+
--start-at 2026-08-04T09:00:00Z --end-at 2026-08-04T09:03:20Z --duration 200 \
|
|
143
|
+
--release-url https://github.com/acme/app/releases/tag/1.4.0 --work-item PLM-001 --json
|
|
144
|
+
pingcode release deploy update 6a70c153919cce9794f01aca --status deployed --json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`--env <name|id>` and `--env-id <id>` are the usual pair: the name resolves (and a typo is
|
|
148
|
+
exit 2 that lists the real environments), the id is sent unchanged. One of them is required
|
|
149
|
+
on `create`; on `list` it is the **only** filter that works — `status`, `release_name` and
|
|
150
|
+
`work_item_id` were tried live and silently ignored.
|
|
151
|
+
|
|
152
|
+
⚠️ **On `list`, an unknown `--env-id` gives you an empty list, not an error.** The API answers
|
|
153
|
+
200 with zero rows, so silence does not distinguish "nothing deployed there" from "no such
|
|
154
|
+
environment". Use `--env <name>` instead and a bad name fails loudly. (`release deploy
|
|
155
|
+
create` does report a missing environment properly: exit 5.)
|
|
156
|
+
|
|
157
|
+
Six flags are required on `create`, for the same reason as `build`: `--status`,
|
|
158
|
+
`--release-name`, `--start-at`, `--end-at`, `--duration`, plus the environment.
|
|
159
|
+
|
|
160
|
+
- `--status` has exactly two values: `not_deployed | deployed`. There is **no failed or
|
|
161
|
+
rolled-back state** — record a rollback as another deploy of the previous release.
|
|
162
|
+
- `--release-name` is free text (`1.4.0`, a tag, a commit subject) and is **not unique**, so
|
|
163
|
+
it is not a lookup key. `release deploy get` takes an id.
|
|
164
|
+
|
|
165
|
+
⚠️ **A deploy cannot be moved between environments, and the API pretends otherwise.**
|
|
166
|
+
`release deploy update` has no `--env` on purpose: the endpoint documents `env_id` as
|
|
167
|
+
updatable and *accepts* it — it returns 200 **and echoes the new environment back in the
|
|
168
|
+
response** — but the change is never stored, and a following `get` shows the original
|
|
169
|
+
environment. So the usual "read it back to be sure" habit does not protect you here; the
|
|
170
|
+
response itself is wrong. Record a new deploy on the right environment instead, and remove
|
|
171
|
+
the stray one with `pingcode api DELETE /v1/release/deploys/<id> --yes`.
|
|
172
|
+
|
|
173
|
+
⚠️ **Moving a deploy's time window needs two calls, `--end-at` first.** A new `--start-at` is
|
|
174
|
+
validated against the **stored** `--end-at`, not against one sent in the same request, so
|
|
175
|
+
pushing a window forward in a single update is exit 7
|
|
176
|
+
(`开始时间必须小于等于已存在的结束时间`). Extend the end, then move the start. Reversing the two
|
|
177
|
+
on a `create` is exit 7 as well (`开始时间必须小于等于结束时间`).
|
|
178
|
+
|
|
179
|
+
No `release deploy replace` (the `PUT` would blank omitted fields — use
|
|
180
|
+
`pingcode api PUT /v1/release/deploys/<id>` deliberately) and no `release deploy delete`
|
|
181
|
+
leaf, though the endpoint exists and works:
|
|
182
|
+
`pingcode api DELETE /v1/release/deploys/<id> --yes`.
|
|
183
|
+
|
|
184
|
+
### Linking to work items — the trap both groups share
|
|
185
|
+
|
|
186
|
+
`--work-item` takes a work item **identifier** (`PLM-001`), repeatable, and it is the only
|
|
187
|
+
thing that connects a build or a deploy to anything in PingCode.
|
|
188
|
+
|
|
189
|
+
⚠️ **An identifier that does not exist is silently dropped and the call still returns 200.**
|
|
190
|
+
Mixed input links the ones that exist and ignores the rest, so **the exit code cannot tell
|
|
191
|
+
you whether the link happened**. The CLI compares what you asked for against the response
|
|
192
|
+
and prints a warning on stderr naming the identifiers that did not land — the exit code
|
|
193
|
+
stays 0, because the write itself succeeded. Under `--json` the authoritative `work_items`
|
|
194
|
+
array is on stdout: read it if you care.
|
|
195
|
+
|
|
196
|
+
On `update`, `--work-item` **replaces** the whole link set, it does not merge. Passing none
|
|
197
|
+
leaves the existing links alone.
|
|
198
|
+
|
|
199
|
+
Two contrasts with [`scm.md`](scm.md) worth stating, because they are the kind of thing
|
|
200
|
+
that gets copied across modules by mistake:
|
|
201
|
+
|
|
202
|
+
- **Nothing in these two groups upserts an identity.** scm's `--sender`, `--owner-name`,
|
|
203
|
+
`--creator`, `--merged-by` and `--reviewer` each create a permanent 托管平台用户 from a
|
|
204
|
+
typo. No field here is a name reference of that kind, so there is no ghost-identity
|
|
205
|
+
hazard to warn about.
|
|
206
|
+
- **A missing parent is reported honestly on a create** (`release deploy create --env-id
|
|
207
|
+
<unknown>` is exit 5), unlike `scm review list` under an unknown pull request. The one
|
|
208
|
+
place silence hides an absence here is `release deploy list --env-id`, called out above.
|
|
209
|
+
|
|
210
|
+
### What is not here
|
|
211
|
+
|
|
212
|
+
| Wanted | Reality |
|
|
213
|
+
|---|---|
|
|
214
|
+
| filter builds by status, work item, provider… | the endpoint honours no filter at all; page through `build list` or keep the id |
|
|
215
|
+
| look a build up by its build number | numbers are not unique; use the id |
|
|
216
|
+
| a "running"/"cancelled" build status | the enum is `success | failure` only |
|
|
217
|
+
| a failed or rolled-back deploy status | the enum is `not_deployed | deployed` only |
|
|
218
|
+
| `build replace` / `release … replace` | `PUT` is generic-layer only: `pingcode api PUT <path>` |
|
|
219
|
+
| `release env delete` / `release deploy delete` | the endpoints exist and work, but are not wrapped yet: `pingcode api DELETE <path> --yes` |
|
|
220
|
+
| move a deploy to another environment | not possible: `env_id` is accepted on PATCH, echoed back, and ignored — create a new deploy |
|
|
221
|
+
| attach a build to a project, sprint or release version | not in this API; `--work-item` is the only link |
|
|
222
|
+
| trigger, cancel or poll a pipeline | not in this API at all — these are records, not controls |
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# 跨对象资源 — relations / comments / attachments / activities
|
|
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 cross-object surface.
|
|
6
|
+
|
|
7
|
+
Four families are polymorphic over the object they hang on: **关联 relations**, **评论 comments**,
|
|
8
|
+
**附件 attachments** and **活动记录 activities**. They are the glue between the four modules — a
|
|
9
|
+
requirement linked to a work item linked to a test case linked to a defect — and they are the
|
|
10
|
+
only write-back channel an automated flow has for prose.
|
|
11
|
+
|
|
12
|
+
They live **under the entity they belong to**, so the entity's own command path supplies
|
|
13
|
+
`principal_type` and you never type it:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pingcode project work-item comment add <work-item> --text "CI #123 failed, filed BUG-45"
|
|
17
|
+
pingcode product idea relation list <idea> --target-type work_item
|
|
18
|
+
pingcode testhub cases relation add <case> --target-type work_item --target-id <id>
|
|
19
|
+
pingcode testhub runs activity list <run>
|
|
20
|
+
pingcode product ticket attachment list <ticket>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
There is deliberately **no top-level `comment` group**: it would have to ask you for
|
|
24
|
+
`--principal-type`, which is a value you can get wrong. If you need the generic form anyway, it
|
|
25
|
+
already exists: `pingcode api GET /v1/comments --query principal_type=work_item --query
|
|
26
|
+
principal_id=<id>`.
|
|
27
|
+
|
|
28
|
+
## 1. Where they are mounted, and where they are not
|
|
29
|
+
|
|
30
|
+
| mount | `principal_type` | relation | comment | attachment | activity |
|
|
31
|
+
|---|---|---|---|---|---|
|
|
32
|
+
| `pingcode project work-item …` | `work_item` | yes | yes | yes | yes |
|
|
33
|
+
| `pingcode product idea …` | `idea` | yes | yes | yes | yes |
|
|
34
|
+
| `pingcode product ticket …` | `ticket` | yes | yes | yes | yes |
|
|
35
|
+
| `pingcode testhub cases …` | `test_case` | yes | yes | yes | yes |
|
|
36
|
+
| `pingcode testhub runs …` | `test_run` | yes | yes | yes | yes |
|
|
37
|
+
|
|
38
|
+
**A test plan is not an object these families accept.** There is no
|
|
39
|
+
`pingcode testhub plans comment`, and there is no generic way to fake one: the API rejects
|
|
40
|
+
`principal_type=test_plan` outright, and `/v1/activities` answers it with an HTTP 500 rather than
|
|
41
|
+
a 4xx. Comment on the plan's runs instead.
|
|
42
|
+
|
|
43
|
+
A wiki page (`principal_type=page`) is accepted by comments and attachments but **not** by
|
|
44
|
+
activities, and 知识库 wiki has no command group of its own, so reach it through
|
|
45
|
+
`pingcode api … --query principal_type=page`.
|
|
46
|
+
|
|
47
|
+
## 2. Every command takes the parent object first
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pingcode project work-item comment get <work-item> <comment-id>
|
|
51
|
+
pingcode project work-item comment delete <work-item> <comment-id> --yes
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Two positional arguments, always in that order, for all four families. The reason is the API's,
|
|
55
|
+
not ours: `GET /v1/comments/{id}` and `GET /v1/attachments/{id}` **require** the principal in the
|
|
56
|
+
query — a comment id alone is not addressable. `relation get` / `relation delete` are the one
|
|
57
|
+
exception under the hood (a relation id is globally addressable), but they still take the parent
|
|
58
|
+
reference so the four families have one shape; it is accepted and not sent, and costs no request.
|
|
59
|
+
|
|
60
|
+
The parent reference accepts whatever that entity's own `get` accepts — an id, a `short_id`, an
|
|
61
|
+
identifier such as `SCR-5`, a pasted URL — and is resolved to a real id first, because **no
|
|
62
|
+
cross-object endpoint accepts anything but an id**.
|
|
63
|
+
|
|
64
|
+
## 3. relations: cross-*kind* only, and the pairs are asymmetric
|
|
65
|
+
|
|
66
|
+
`POST /v1/relations` takes no relation type of any kind. What it does take is a pair of kinds,
|
|
67
|
+
and it refuses some pairs — including every same-kind work-item pair:
|
|
68
|
+
|
|
69
|
+
| from ↓ | `work_item` | `idea` | `ticket` | `test_case` | `test_run` | `page` |
|
|
70
|
+
|---|---|---|---|---|---|---|
|
|
71
|
+
| `work_item` | **no** | yes | yes | yes | no | yes |
|
|
72
|
+
| `idea` | yes | yes | yes | yes | no | yes |
|
|
73
|
+
| `ticket` | yes | yes | yes | **no** | no | yes |
|
|
74
|
+
| `test_case` | yes | yes | **no** | no | no | yes |
|
|
75
|
+
| `test_run` | yes | no | no | no | no | no |
|
|
76
|
+
|
|
77
|
+
Read that table as *observed*, not *documented*: the API declares no vocabulary for either field,
|
|
78
|
+
so this is what a live tenant accepted. The CLI refuses nothing locally — it sends what you ask
|
|
79
|
+
and explains the rejection.
|
|
80
|
+
|
|
81
|
+
**There is a second filter the table cannot show: the work item's *type*.** Creating a link
|
|
82
|
+
*from* a test case, only a 需求 (story) or 缺陷 (bug) target is accepted; *from* a test run, only
|
|
83
|
+
a 缺陷. An epic, feature or task target is rejected with `不支持的工作项类型` (`100107`) — while the
|
|
84
|
+
same link created *from the work-item side* succeeds for any type. So if you are linking a case or
|
|
85
|
+
a run to a feature, create the link from the work item instead:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# refused (100107): testhub cases relation add <case> --target-type work_item --target-id <feature>
|
|
89
|
+
pingcode project work-item relation add <feature> --target-type test_case --target-id <case>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Three consequences worth internalising:
|
|
93
|
+
|
|
94
|
+
1. **work item ↔ work item is a different family.** Those links are typed (阻塞 / 重复 / 关联 …)
|
|
95
|
+
and live on `POST /v1/pjm/work_items/{id}/relations` with a `relation_type`. Reach them with
|
|
96
|
+
`pingcode api POST /v1/pjm/work_items/<id>/relations --set target_work_item_id=<id> --set
|
|
97
|
+
relation_type=<type>`, and list the types with
|
|
98
|
+
`pingcode api GET /v1/pjm/work_item/relation_types`.
|
|
99
|
+
2. **Direction matters even though the link does not.** `test_run → work_item` is accepted;
|
|
100
|
+
`work_item → test_run` is not. Once created, the link is stored as a mirrored pair and shows up
|
|
101
|
+
from both ends — with a *different* id on each side. Deleting either id removes both.
|
|
102
|
+
3. **`--target-type` is mandatory on `relation list`.** It reads like a filter and is not: omit it
|
|
103
|
+
and the API rejects the call.
|
|
104
|
+
|
|
105
|
+
When a pair is refused, the API answers `不支持的'principal_type'` (code `100049`) **whatever was
|
|
106
|
+
actually wrong** — a bad target kind, or no target kind at all. The CLI prints the pair it tried
|
|
107
|
+
and the kinds this principal does link to; trust that line over the server's message.
|
|
108
|
+
|
|
109
|
+
## 4. comments: delete is a soft delete
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pingcode project work-item comment add <work-item> --text "…" --reply-to <comment-id>
|
|
113
|
+
pingcode project work-item comment list <work-item> --all
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`comment delete` **does not remove the row.** It stays in `comment list` with `is_deleted` set, and
|
|
117
|
+
the table's `STATE` column shows `deleted`. Read that column and nothing else: whether the text
|
|
118
|
+
survives depends on the module — a work-item comment comes back empty, a ticket comment keeps its
|
|
119
|
+
body — so neither "has content" nor "has none" tells you whether a comment is live.
|
|
120
|
+
|
|
121
|
+
## 5. attachments: snippets only, and always under a comment
|
|
122
|
+
|
|
123
|
+
There are two upload shapes in the API and the CLI can only send one of them:
|
|
124
|
+
|
|
125
|
+
- **files** are `multipart/form-data` in a single request. This CLI cannot produce that body, so
|
|
126
|
+
`attachment add` does not exist. Read, list and delete work normally on files uploaded through
|
|
127
|
+
the web UI.
|
|
128
|
+
- **code snippets** are JSON, and `attachment add-snippet` sends them.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pingcode project work-item comment add <work-item> --text "build log"
|
|
132
|
+
pingcode project work-item attachment add-snippet <work-item> \
|
|
133
|
+
--comment-id <comment-id> --title main.go --format go --content-file ./main.go
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**`--comment-id` is required**, on the write and on every subsequent read. The docs call it
|
|
137
|
+
optional; live, a snippet posted without it is rejected (`100039`), and a snippet that exists is
|
|
138
|
+
reported as *not found* (`100045`, exit 5) if you read or delete it without the scope. Create the
|
|
139
|
+
comment first, keep its id.
|
|
140
|
+
|
|
141
|
+
`--format` is a closed vocabulary — `clike css dart django dockerfile go markdown nginx python php
|
|
142
|
+
shell sql swift html javascript jsx pascal sass stylus vue yaml haskell` — and anything else is
|
|
143
|
+
rejected as a malformed request. `--content` takes the text inline, `--content-file` reads a file
|
|
144
|
+
verbatim; exactly one of the two.
|
|
145
|
+
|
|
146
|
+
Unlike a comment, `attachment delete` is permanent.
|
|
147
|
+
|
|
148
|
+
## 6. activities: the only change feed there is
|
|
149
|
+
|
|
150
|
+
Read-only, and per-object. This API has **no webhook API and no global activity stream**, so
|
|
151
|
+
polling `activity list` on the objects you care about is the only way to notice a change.
|
|
152
|
+
|
|
153
|
+
`EVENT` is the machine-readable name (`unrelate-test-case`) and its verb (`unrelate`); `SUMMARY`
|
|
154
|
+
is Chinese prose written for humans and is **not** a contract — never match on it. The `content`
|
|
155
|
+
object differs per event type and is passed through untouched, so read it from `--json`.
|
|
156
|
+
|
|
157
|
+
## 7. Exit codes you will actually see here
|
|
158
|
+
|
|
159
|
+
| what happened | code | exit |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| parent reference could not be resolved | — | 2 |
|
|
162
|
+
| `--yes` missing on a delete | — | 2 |
|
|
163
|
+
| unsupported `principal_type`, or a refused pair, or a missing `--target-type` | `100049` | 7 |
|
|
164
|
+
| snippet without `--comment-id`, or an unknown `--format` | `100039` | 7 |
|
|
165
|
+
| work item on the other end has a type this direction refuses | `100107` | 7 |
|
|
166
|
+
| comment / attachment / activity / relation id does not exist | `100051` / `100045` / `100077` / `100801` | 5 |
|
|
167
|
+
| parent object does not exist (work item) | `100317` | 5 |
|
|
168
|
+
| `principal_type=test_plan` on `activities` | — | 8-ish: HTTP 500, do not do this |
|