Improve onboard template: per-issue content-aware welcome comments

- fetchIssueDetail now gets both subject and description
- renderComment substitutes {subject}, {description}, {number}, {login}
- defaultTemplate includes issue summary for targeted guidance
- dry-run preview shows the full rendered comment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Donkey_kevin 2026-06-15 19:21:42 +08:00
parent 0ed9dcabfc
commit be837d4f8e
1 changed files with 54 additions and 22 deletions

View File

@ -25,7 +25,7 @@ func Shortcuts() []*common.Shortcut {
Flags: []common.Flag{
{Name: "issues", Short: "i", Usage: "Comma-separated issue numbers (e.g. 1,3,7)"},
{Name: "tag", Short: "t", Usage: "Tag name to match (comma-separated)", Default: "good first issue,help wanted"},
{Name: "template", Usage: "Custom welcome message template (use {login} for @mention)"},
{Name: "template", Usage: "Custom welcome message template ({login}, {number}, {subject}, {description})"},
},
DryRun: true,
DryRunHint: dryRunHint,
@ -96,11 +96,11 @@ func runWelcomeByIssueNumbers(ctx *common.RuntimeContext, issueArg string) error
var issues []issueInfo
for _, num := range issueNums {
subject, err := fetchIssueSubject(ctx, num)
info, err := fetchIssueDetail(ctx, num)
if err != nil {
return fmt.Errorf("获取 issue #%d 失败: %w", num, err)
}
issues = append(issues, issueInfo{number: num, subject: subject})
issues = append(issues, info)
}
return processIssues(ctx, issues)
@ -127,18 +127,20 @@ func parseIssueNumbers(s string) ([]int, error) {
return nums, nil
}
// fetchIssueSubject fetches a single issue's subject by number.
func fetchIssueSubject(ctx *common.RuntimeContext, issueNumber int) (string, error) {
// fetchIssueDetail fetches a single issue's subject and description by number.
func fetchIssueDetail(ctx *common.RuntimeContext, issueNumber int) (issueInfo, error) {
path := fmt.Sprintf("/v1/%s/%s/issues/%d", ctx.Owner, ctx.Repo, issueNumber)
env, err := ctx.CallAPIWithQuery("GET", path, nil)
if err != nil {
return "", err
return issueInfo{}, err
}
data, _ := env.Data.(map[string]interface{})
if subj := getString(data, "subject"); subj != "" {
return subj, nil
subj := getString(data, "subject")
if subj == "" {
subj = fmt.Sprintf("issue #%d", issueNumber)
}
return fmt.Sprintf("issue #%d", issueNumber), nil
desc := getString(data, "description")
return issueInfo{number: issueNumber, subject: subj, description: desc}, nil
}
// processIssues handles the common issue processing loop used by both
@ -146,7 +148,7 @@ func fetchIssueSubject(ctx *common.RuntimeContext, issueNumber int) (string, err
func processIssues(ctx *common.RuntimeContext, issues []issueInfo) error {
tmpl := ctx.Arg("template")
if tmpl == "" {
tmpl = defaultTemplate(ctx.Owner, ctx.Repo)
tmpl = ""
}
type result struct {
@ -162,7 +164,11 @@ func processIssues(ctx *common.RuntimeContext, issues []issueInfo) error {
continue
}
// Render per-issue message with issue-specific variables.
body := renderComment(ctx, issue, tmpl)
if ctx.IsDryRun() {
fmt.Printf("\n--- 预览 #%d \"%s\" ---\n%s\n---\n", issue.number, issue.subject, body)
proceed, err := common.ConfirmAction(ctx)
if err != nil {
return err
@ -173,7 +179,7 @@ func processIssues(ctx *common.RuntimeContext, issues []issueInfo) error {
}
}
if err := addComment(ctx, issue.number, tmpl); err != nil {
if err := addComment(ctx, issue.number, body); err != nil {
results = append(results, result{issue.number, "error", err.Error()})
} else {
results = append(results, result{issue.number, "added", fmt.Sprintf("#%d \"%s\" — 已添加引导评论", issue.number, issue.subject)})
@ -247,8 +253,9 @@ func tagNamesList(tags map[string]int) []string {
}
type issueInfo struct {
number int
subject string
number int
subject string
description string
}
func fetchTaggedIssues(ctx *common.RuntimeContext, tagIDs []string) ([]issueInfo, error) {
@ -313,30 +320,55 @@ func hasWelcomeComment(ctx *common.RuntimeContext, issueNumber int) bool {
}
func addComment(ctx *common.RuntimeContext, issueNumber int, body string) error {
body = strings.ReplaceAll(body, "{login}", ctx.Owner)
path := fmt.Sprintf("/v1/%s/%s/issues/%d/journals", ctx.Owner, ctx.Repo, issueNumber)
payload := map[string]interface{}{"notes": marker + "\n\n" + body}
_, err := ctx.CallAPI("POST", path, payload)
return err
}
// defaultTemplate returns the default onboarding message.
func defaultTemplate(owner, repo string) string {
// renderComment renders the comment body for a specific issue.
// It uses the --template if provided, otherwise generates an issue-aware default.
func renderComment(ctx *common.RuntimeContext, issue issueInfo, customTmpl string) string {
tmpl := customTmpl
if tmpl == "" {
tmpl = defaultTemplate(ctx.Owner, ctx.Repo, issue)
}
body := strings.NewReplacer(
"{login}", ctx.Owner,
"{number}", strconv.Itoa(issue.number),
"{subject}", issue.subject,
"{description}", issue.description,
).Replace(tmpl)
return body
}
// defaultTemplate returns an issue-aware onboarding message.
func defaultTemplate(owner, repo string, issue issueInfo) string {
summary := issue.subject
if len(issue.description) > 200 {
summary = issue.description[:200] + "..."
} else if issue.description != "" {
summary = issue.description
}
return fmt.Sprintf(`## 欢迎贡献:wave:
感谢你对 [%s/%s](https://www.gitlink.org.cn/%s/%s) 的关注。这个 issue 适合新手参与,以下是快速上手指南:
感谢你对 [%s/%s](https://www.gitlink.org.cn/%s/%s) 的关注。
### 参与步骤
### :bulb: 关于本 Issue{subject}
%s
### :rocket: 参与步骤
1. **Fork 仓库** 并克隆到本地
2. 创建新分支` + "`git checkout -b fix/issue-{number}`" + `
3. 修改代码并提交
3. 参照上方 issue 描述修改代码
4. 推送到你的 Fork 后创建 Pull Request
### 注意事项
### :memo: 注意事项
- 请先阅读 [CONTRIBUTING.md](https://www.gitlink.org.cn/%s/%s/src/master/CONTRIBUTING.md)(如有)
- 如遇到问题欢迎在 issue 评论区留言
- 有疑问欢迎在评论区留言讨论
期待你的 PR`, owner, repo, owner, repo, owner, repo)
期待你的 PR`, owner, repo, owner, repo, summary, owner, repo)
}
func getString(m map[string]interface{}, key string) string {