forked from Gitlink/gitlink-cli
Merge PR #57: feat(issue): support metadata fields
Resolved conflicts with i18n infrastructure from PR #95 and metadata lookup shortcuts from PR #43. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
commit
9843fc7ceb
11
README.md
11
README.md
|
|
@ -247,9 +247,15 @@ gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
|||
# Create an issue
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" -b "Steps to reproduce..."
|
||||
|
||||
# Create an issue with metadata
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" --priority-id 3 --tag-ids 4,5 --assigner-ids 7
|
||||
|
||||
# View an issue
|
||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus -i 123
|
||||
|
||||
# Update issue metadata
|
||||
gitlink-cli issue +update --owner Gitlink --repo forgeplus --number 123 --priority-id 4 --branch bugfix/login --due-date 2026-06-15
|
||||
|
||||
# Close an issue
|
||||
gitlink-cli issue +close --owner Gitlink --repo forgeplus -i 123
|
||||
|
||||
|
|
@ -278,6 +284,11 @@ gitlink-cli issue +tags --owner Gitlink --repo forgeplus --only-name
|
|||
gitlink-cli issue +statuses --owner Gitlink --repo forgeplus
|
||||
```
|
||||
|
||||
`issue +view`, `issue +update`, `issue +close`, and `issue +comment` prefer
|
||||
`--number` / `-n` for the issue number shown in the web URL. `--id` / `-i`
|
||||
is accepted as a compatibility alias for the same web issue number, not the
|
||||
global database ID.
|
||||
|
||||
### Label Management
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -258,9 +258,15 @@ gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
|||
# 创建 Issue
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: 登录失败" -b "复现步骤..."
|
||||
|
||||
# 创建带元数据的 Issue
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: 登录失败" --priority-id 3 --tag-ids 4,5 --assigner-ids 7
|
||||
|
||||
# 查看 Issue
|
||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus -i 123
|
||||
|
||||
# 更新 Issue 元数据
|
||||
gitlink-cli issue +update --owner Gitlink --repo forgeplus --number 123 --priority-id 4 --branch bugfix/login --due-date 2026-06-15
|
||||
|
||||
# 关闭 Issue
|
||||
gitlink-cli issue +close --owner Gitlink --repo forgeplus -i 123
|
||||
|
||||
|
|
@ -289,6 +295,10 @@ gitlink-cli issue +tags --owner Gitlink --repo forgeplus --only-name
|
|||
gitlink-cli issue +statuses --owner Gitlink --repo forgeplus
|
||||
```
|
||||
|
||||
`issue +view`、`issue +update`、`issue +close` 和 `issue +comment` 推荐使用
|
||||
`--number` / `-n` 传网页 URL 中的 Issue 编号。`--id` / `-i` 是同一网页 Issue
|
||||
编号的兼容别名,不是数据库内部 ID。
|
||||
|
||||
### 标签管理
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# Issue ID Alias
|
||||
|
||||
## Summary
|
||||
|
||||
`issue +view`, `issue +close`, `issue +update`, and `issue +comment` now accept
|
||||
`--id` / `-i` as a compatibility alias for `--number` / `-n`.
|
||||
|
||||
The alias uses the same project-level issue number shown in the web URL, for
|
||||
example `issues/123`. It is not the global database ID.
|
||||
|
||||
`--number` remains the preferred flag and takes precedence when both flags are
|
||||
provided.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus --id 123
|
||||
gitlink-cli issue +close --owner Gitlink --repo forgeplus -i 123
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 --body "Fixed"
|
||||
```
|
||||
|
||||
## Submitter
|
||||
|
||||
Wang Yue
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Issue Metadata Fields
|
||||
|
||||
## Summary
|
||||
|
||||
`issue +create` and `issue +update` now support common GitLink Issue metadata fields.
|
||||
When updating or closing an Issue, the shortcut also carries the current metadata
|
||||
back to the API so unrelated fields are not reset by partial updates.
|
||||
|
||||
## Added flags
|
||||
|
||||
| Flag | API field |
|
||||
|------|-----------|
|
||||
| `--priority-id` | `priority_id` |
|
||||
| `--tag-ids` | `issue_tag_ids` |
|
||||
| `--assigner-ids` | `assigner_ids` |
|
||||
| `--branch` | `branch_name` |
|
||||
| `--start-date` | `start_date` |
|
||||
| `--due-date` | `due_date` |
|
||||
|
||||
`issue +create --label` is also mapped as a single tag ID for backward compatibility.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus \
|
||||
--title "Bug: login failed" \
|
||||
--priority-id 3 \
|
||||
--tag-ids 4,5 \
|
||||
--assigner-ids 7
|
||||
|
||||
gitlink-cli issue +update --owner Gitlink --repo forgeplus \
|
||||
--number 123 \
|
||||
--priority-id 4 \
|
||||
--branch bugfix/login \
|
||||
--due-date 2026-06-15
|
||||
```
|
||||
|
|
@ -19,6 +19,13 @@ func v1RepoPath(ctx *common.RuntimeContext) string {
|
|||
type existingIssue struct {
|
||||
Subject string
|
||||
Description string
|
||||
StatusID interface{}
|
||||
PriorityID interface{}
|
||||
TagIDs []interface{}
|
||||
AssignerIDs []interface{}
|
||||
BranchName string
|
||||
StartDate string
|
||||
DueDate string
|
||||
}
|
||||
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
|
|
@ -60,6 +67,12 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
{Name: "assignee", Short: "a", Usage: tr.T("flag.issue.assignee")},
|
||||
{Name: "milestone", Short: "m", Usage: tr.T("flag.issue.milestone")},
|
||||
{Name: "label", Usage: tr.T("flag.issue.label")},
|
||||
{Name: "priority-id", Usage: "Priority ID", Default: "2"},
|
||||
{Name: "tag-ids", Usage: "Comma-separated issue tag IDs"},
|
||||
{Name: "assigner-ids", Usage: "Comma-separated issue assigner IDs"},
|
||||
{Name: "branch", Usage: "Linked branch name"},
|
||||
{Name: "start-date", Usage: "Start date (YYYY-MM-DD)"},
|
||||
{Name: "due-date", Usage: "Due date (YYYY-MM-DD)"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -84,6 +97,9 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
if m := ctx.Arg("milestone"); m != "" {
|
||||
body["fixed_version_id"] = m
|
||||
}
|
||||
if err := applyIssueMetadataArgs(ctx, body); err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("POST", v1RepoPath(ctx)+"/issues", body)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -94,10 +110,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
{
|
||||
Name: "view",
|
||||
Description: tr.T("cmd.issue.view.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "number", Short: "n", Usage: tr.T("flag.issue.number")},
|
||||
{Name: "id", Usage: "Alias for --number; uses the issue number from the web URL"},
|
||||
},
|
||||
Flags: issueNumberFlags(),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
|
|
@ -116,14 +129,12 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
{
|
||||
Name: "close",
|
||||
Description: tr.T("cmd.issue.close.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "number", Short: "n", Usage: tr.T("flag.issue.number"), Required: true},
|
||||
},
|
||||
Flags: issueNumberFlags(),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := ctx.RequireArg("number")
|
||||
number, err := issueNumberArg(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -135,8 +146,9 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
body := map[string]interface{}{
|
||||
"subject": current.Subject,
|
||||
"description": current.Description,
|
||||
"status_id": 5, // 5 = closed
|
||||
}
|
||||
preserveIssueMetadata(body, current)
|
||||
body["status_id"] = 5 // 5 = closed
|
||||
env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -147,25 +159,30 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
{
|
||||
Name: "update",
|
||||
Description: tr.T("cmd.issue.update.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "number", Short: "n", Usage: tr.T("flag.issue.number"), Required: true},
|
||||
{Name: "title", Short: "t", Usage: tr.T("flag.issue.new_title")},
|
||||
{Name: "body", Short: "b", Usage: tr.T("flag.issue.new_body")},
|
||||
{Name: "state", Short: "s", Usage: tr.T("flag.issue.new_state")},
|
||||
},
|
||||
Flags: appendIssueNumberFlags(
|
||||
common.Flag{Name: "title", Short: "t", Usage: tr.T("flag.issue.new_title")},
|
||||
common.Flag{Name: "body", Short: "b", Usage: tr.T("flag.issue.new_body")},
|
||||
common.Flag{Name: "state", Short: "s", Usage: tr.T("flag.issue.new_state")},
|
||||
common.Flag{Name: "priority-id", Usage: "New priority ID"},
|
||||
common.Flag{Name: "tag-ids", Usage: "Comma-separated issue tag IDs"},
|
||||
common.Flag{Name: "assigner-ids", Usage: "Comma-separated issue assigner IDs"},
|
||||
common.Flag{Name: "branch", Usage: "Linked branch name"},
|
||||
common.Flag{Name: "start-date", Usage: "Start date (YYYY-MM-DD)"},
|
||||
common.Flag{Name: "due-date", Usage: "Due date (YYYY-MM-DD)"},
|
||||
),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := ctx.RequireArg("number")
|
||||
number, err := issueNumberArg(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
title := ctx.Arg("title")
|
||||
description := ctx.Arg("body")
|
||||
state := ctx.Arg("state")
|
||||
if title == "" && description == "" && state == "" {
|
||||
return fmt.Errorf("at least one of --title, --body, or --state is required")
|
||||
if title == "" && description == "" && state == "" && !hasIssueMetadataArgs(ctx) {
|
||||
return fmt.Errorf("at least one update field is required")
|
||||
}
|
||||
|
||||
current, err := fetchExistingIssue(ctx, number)
|
||||
|
|
@ -177,6 +194,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
"subject": current.Subject,
|
||||
"description": current.Description,
|
||||
}
|
||||
preserveIssueMetadata(body, current)
|
||||
if t := ctx.Arg("title"); t != "" {
|
||||
body["subject"] = t
|
||||
}
|
||||
|
|
@ -190,6 +208,9 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
}
|
||||
body["status_id"] = statusID
|
||||
}
|
||||
if err := applyIssueMetadataArgs(ctx, body); err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -200,15 +221,14 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
{
|
||||
Name: "comment",
|
||||
Description: tr.T("cmd.issue.comment.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "number", Short: "n", Usage: tr.T("flag.issue.number"), Required: true},
|
||||
{Name: "body", Short: "b", Usage: tr.T("flag.comment.body"), Required: true},
|
||||
},
|
||||
Flags: appendIssueNumberFlags(
|
||||
common.Flag{Name: "body", Short: "b", Usage: tr.T("flag.comment.body"), Required: true},
|
||||
),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := ctx.RequireArg("number")
|
||||
number, err := issueNumberArg(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -353,6 +373,27 @@ func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
|||
return i18n.Default()
|
||||
}
|
||||
|
||||
func issueNumberFlags() []common.Flag {
|
||||
return []common.Flag{
|
||||
{Name: "number", Short: "n", Usage: "Issue number from the web URL (preferred)"},
|
||||
{Name: "id", Short: "i", Usage: "Compatibility alias for --number; this is not the database ID"},
|
||||
}
|
||||
}
|
||||
|
||||
func appendIssueNumberFlags(flags ...common.Flag) []common.Flag {
|
||||
return append(issueNumberFlags(), flags...)
|
||||
}
|
||||
|
||||
func issueNumberArg(ctx *common.RuntimeContext) (string, error) {
|
||||
if number := strings.TrimSpace(ctx.Arg("number")); number != "" {
|
||||
return number, nil
|
||||
}
|
||||
if id := strings.TrimSpace(ctx.Arg("id")); id != "" {
|
||||
return id, nil
|
||||
}
|
||||
return "", fmt.Errorf("required flag --number is missing (or use --id as a compatibility alias)")
|
||||
}
|
||||
|
||||
// normalizeIssueListIDs adds "number" (project_issues_index) and renames
|
||||
// "id" to "database_id" so the user-facing output uses the project-level
|
||||
// issue number, not the global database primary key.
|
||||
|
|
@ -400,9 +441,76 @@ func fetchExistingIssue(ctx *common.RuntimeContext, number string) (*existingIss
|
|||
return &existingIssue{
|
||||
Subject: subject,
|
||||
Description: description,
|
||||
StatusID: nestedIssueID(issueData, "status"),
|
||||
PriorityID: nestedIssueID(issueData, "priority"),
|
||||
TagIDs: issueObjectIDs(issueData, "tags", "issue_tags"),
|
||||
AssignerIDs: issueObjectIDs(issueData, "assigners"),
|
||||
BranchName: stringField(issueData, "branch_name"),
|
||||
StartDate: stringField(issueData, "start_date"),
|
||||
DueDate: stringField(issueData, "due_date"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func preserveIssueMetadata(body map[string]interface{}, issue *existingIssue) {
|
||||
if issue.StatusID != nil {
|
||||
body["status_id"] = issue.StatusID
|
||||
}
|
||||
if issue.PriorityID != nil {
|
||||
body["priority_id"] = issue.PriorityID
|
||||
}
|
||||
if len(issue.TagIDs) > 0 {
|
||||
body["issue_tag_ids"] = issue.TagIDs
|
||||
}
|
||||
if len(issue.AssignerIDs) > 0 {
|
||||
body["assigner_ids"] = issue.AssignerIDs
|
||||
}
|
||||
if issue.BranchName != "" {
|
||||
body["branch_name"] = issue.BranchName
|
||||
}
|
||||
if issue.StartDate != "" {
|
||||
body["start_date"] = issue.StartDate
|
||||
}
|
||||
if issue.DueDate != "" {
|
||||
body["due_date"] = issue.DueDate
|
||||
}
|
||||
}
|
||||
|
||||
func nestedIssueID(data map[string]interface{}, key string) interface{} {
|
||||
item, ok := data[key].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item["id"]
|
||||
}
|
||||
|
||||
func issueObjectIDs(data map[string]interface{}, keys ...string) []interface{} {
|
||||
for _, key := range keys {
|
||||
items, ok := data[key].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ids := make([]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
obj, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if id, ok := obj["id"]; ok {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
return ids
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringField(data map[string]interface{}, key string) string {
|
||||
value, _ := data[key].(string)
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizeIssueStatus(state string) (interface{}, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(state)) {
|
||||
case "open":
|
||||
|
|
@ -417,12 +525,77 @@ func normalizeIssueStatus(state string) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func issueNumberArg(ctx *common.RuntimeContext) (string, error) {
|
||||
if number := strings.TrimSpace(ctx.Arg("number")); number != "" {
|
||||
return number, nil
|
||||
func hasIssueMetadataArgs(ctx *common.RuntimeContext) bool {
|
||||
for _, name := range []string{"priority-id", "tag-ids", "label", "assigner-ids", "branch", "start-date", "due-date"} {
|
||||
if ctx.Arg(name) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if id := strings.TrimSpace(ctx.Arg("id")); id != "" {
|
||||
return id, nil
|
||||
}
|
||||
return "", fmt.Errorf("required flag --number (or --id alias) not set")
|
||||
return false
|
||||
}
|
||||
|
||||
func applyIssueMetadataArgs(ctx *common.RuntimeContext, body map[string]interface{}) error {
|
||||
if priority := ctx.Arg("priority-id"); priority != "" {
|
||||
priorityID, err := parseIssueID(priority, "priority-id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body["priority_id"] = priorityID
|
||||
}
|
||||
tagIDs := ctx.Arg("tag-ids")
|
||||
if label := ctx.Arg("label"); label != "" {
|
||||
if tagIDs != "" {
|
||||
return fmt.Errorf("--label cannot be used with --tag-ids")
|
||||
}
|
||||
tagIDs = label
|
||||
}
|
||||
if tagIDs != "" {
|
||||
ids, err := parseIssueIDList(tagIDs, "tag-ids")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body["issue_tag_ids"] = ids
|
||||
}
|
||||
if assignerIDs := ctx.Arg("assigner-ids"); assignerIDs != "" {
|
||||
ids, err := parseIssueIDList(assignerIDs, "assigner-ids")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body["assigner_ids"] = ids
|
||||
}
|
||||
if branch := ctx.Arg("branch"); branch != "" {
|
||||
body["branch_name"] = branch
|
||||
}
|
||||
if startDate := ctx.Arg("start-date"); startDate != "" {
|
||||
body["start_date"] = startDate
|
||||
}
|
||||
if dueDate := ctx.Arg("due-date"); dueDate != "" {
|
||||
body["due_date"] = dueDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseIssueIDList(value, flagName string) ([]int, error) {
|
||||
parts := strings.Split(value, ",")
|
||||
ids := make([]int, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
id, err := parseIssueID(part, flagName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func parseIssueID(value, flagName string) (int, error) {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return 0, fmt.Errorf("--%s contains an empty ID", flagName)
|
||||
}
|
||||
id, err := strconv.Atoi(trimmed)
|
||||
if err != nil || id <= 0 {
|
||||
return 0, fmt.Errorf("--%s must contain positive numeric IDs", flagName)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -82,15 +82,23 @@ gitlink-cli api POST /:owner/:repo/issues/series_update --body '{"ids":[1,2,3],"
|
|||
| gitlink-cli 参数 | GitLink API 字段 | 说明 |
|
||||
|------------------|-----------------|------|
|
||||
| `--number` / `-n` | `project_issues_index` | Issue 编号(网页 URL 中的序号) |
|
||||
| `--id` / `-i` | `project_issues_index` | `--number` 的兼容别名,不是数据库内部 ID |
|
||||
| `--title` | `subject` | Issue 标题 |
|
||||
| `--body` | `description` | Issue 描述 |
|
||||
| `--assignee` | `assigned_to_id` | 指派人 ID |
|
||||
| `--milestone` | `fixed_version_id` | 里程碑 ID |
|
||||
| `--state` | `status_id` | 状态(open=1,closed=5,也可直接传数字 ID) |
|
||||
| `--priority-id` | `priority_id` | 优先级 ID |
|
||||
| `--tag-ids` / `--label` | `issue_tag_ids` | Issue 标签 ID 数组 |
|
||||
| `--assigner-ids` | `assigner_ids` | 负责人 ID 数组 |
|
||||
| `--branch` | `branch_name` | 关联分支 |
|
||||
| `--start-date` | `start_date` | 开始日期 |
|
||||
| `--due-date` | `due_date` | 截止日期 |
|
||||
|
||||
## API 注意事项
|
||||
|
||||
- **Issue 编号(`--number`)是网页 URL 中看到的序号**(如 `issues/4` 中的 `4`),不是数据库内部 ID
|
||||
- `--id` / `-i` 仅作为 `--number` / `-n` 的兼容别名,传入的仍然是网页 URL 中的 Issue 编号
|
||||
- **批量关闭使用 `--numbers`,同样传网页 URL 中的 Issue 编号**,不是数据库内部 ID
|
||||
- Issue 操作使用 v1 API(`/api/v1/`),支持按 Issue 编号查询和操作
|
||||
- **创建 Issue 时 CLI 会自动设置 `status_id: 1`(新增)和 `priority_id: 2`(正常)**
|
||||
|
|
|
|||
Loading…
Reference in New Issue