fix(milestone): make +create --description/--due-date optional to match platform API

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
farmyobutu5233 2026-07-05 12:06:21 +00:00
parent 9749a4c832
commit f43afa75a6
3 changed files with 51 additions and 8 deletions

View File

@ -0,0 +1,20 @@
# milestone +create 放宽必填参数
## 背景
`milestone +create` 之前把 `--description``--due-date` 标记为必填,
但平台 v1 API 创建里程碑仅要求 `name`(已在生产环境
`POST /api/v1/:owner/:repo/milestones` 用仅含 `{"name": ...}` 的请求验证成功)。
CLI 侧的额外限制迫使用户为可选字段编造内容。
## 变更
- `--description``--due-date` 改为可选;未提供时请求体中省略对应字段。
- `milestonePayload``requireAll` 参数收敛为 `requireName`,仅校验 `--name`
## 验证
- `go test ./shortcuts/milestone/`(新增 `TestMilestoneCreateNameOnly`
断言未提供的可选字段不出现在请求体中)
- `go vet ./...`
- 生产 gitlink.org.cn 实测:`milestone +create -n <name>` 仅带名称创建成功。

View File

@ -46,8 +46,8 @@ func Shortcuts() []*common.Shortcut {
Description: "Create a milestone",
Flags: []common.Flag{
{Name: "name", Short: "n", Usage: "Milestone name", Required: true},
{Name: "description", Short: "d", Usage: "Milestone description", Required: true},
{Name: "due-date", Usage: "Due date in YYYY-MM-DD format", Required: true},
{Name: "description", Short: "d", Usage: "Milestone description"},
{Name: "due-date", Usage: "Due date in YYYY-MM-DD format"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
@ -168,7 +168,7 @@ func milestoneStatusPath(ctx *common.RuntimeContext, id string) string {
return fmt.Sprintf("%s/milestones/%s/update_status", ctx.RepoPath(), url.PathEscape(id))
}
func milestonePayload(ctx *common.RuntimeContext, requireAll bool) (map[string]interface{}, error) {
func milestonePayload(ctx *common.RuntimeContext, requireName bool) (map[string]interface{}, error) {
payload := map[string]interface{}{}
if name := ctx.Arg("name"); name != "" {
payload["name"] = name
@ -180,11 +180,9 @@ func milestonePayload(ctx *common.RuntimeContext, requireAll bool) (map[string]i
payload["effective_date"] = dueDate
}
if requireAll {
for _, name := range []string{"name", "description", "due-date"} {
if _, err := ctx.RequireArg(name); err != nil {
return nil, err
}
if requireName {
if _, err := ctx.RequireArg("name"); err != nil {
return nil, err
}
return payload, nil
}

View File

@ -55,6 +55,31 @@ func TestMilestoneCreatePayload(t *testing.T) {
assertEqual(t, payload["effective_date"], "2026-07-01")
}
func TestMilestoneCreateNameOnly(t *testing.T) {
var payload map[string]interface{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assertRequest(t, r, "POST", "/v1/owner/repo/milestones.json")
payload = decodeJSON(t, r)
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
}))
defer server.Close()
err := runMilestoneShortcut(t, server, "create", map[string]string{
"name": "v1.1",
})
if err != nil {
t.Fatalf("create shortcut failed: %v", err)
}
assertEqual(t, payload["name"], "v1.1")
if _, ok := payload["description"]; ok {
t.Fatalf("description should be omitted when not provided")
}
if _, ok := payload["effective_date"]; ok {
t.Fatalf("effective_date should be omitted when not provided")
}
}
func TestMilestoneViewWithIssueFilters(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assertRequest(t, r, "GET", "/v1/owner/repo/milestones/7.json")