- internal/gitea:用戶端支援寫入動詞(DoJSON/DoRaw,共用 do 核心) - internal/dailyops:七組日常操作的 API 層(issues/pulls/labels/milestones/releases/repos/api) - internal/cli:子命令接線;寫入類一律 --yes 閘門(未確認不發請求、exit 2) - teai pulls 相容 #6 工作流用法(無子命令);有子命令走日常操作 - 修正 dispatch:命令旗標(如 --has-work、--comments)不再被誤判為未知全域選項 - teai api 支援 path 內含查詢字串 - 單元測試(httptest 假伺服器)+真實站點交叉驗證(讀取命令輸出對照 Gitea API;寫入回路 create→comment→close 於 alex/.profile 驗證後清理)
244 lines
6.6 KiB
Go
244 lines
6.6 KiB
Go
// workflow_commands.go — gitea.py 工作流命令的 CLI 接線(#6):
|
||
// whoami、orgs、members [--has-work]、mine、pulls [--mine|--reviewer] [--repo]、next。
|
||
//
|
||
// 輸出走 internal/gitea 的 Printer(--output json|table);錯誤經 cli.exitCodeFor
|
||
// 映射結束碼(用法 2/API 3)。token 解析選序見 internal/gitea/auth.go。
|
||
package cli
|
||
|
||
import (
|
||
"errors"
|
||
"flag"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"gitea.alterminal.com/alterminal/teai/internal/gitea"
|
||
"gitea.alterminal.com/alterminal/teai/internal/workflow"
|
||
)
|
||
|
||
func init() {
|
||
register := func(c *command) {
|
||
commands[c.name] = c
|
||
}
|
||
register(&command{name: "whoami", usage: "目前帳號", run: runWhoami})
|
||
register(&command{name: "orgs", usage: "我所屬的組織", run: runOrgs})
|
||
register(&command{name: "members", usage: "Agents 團隊成員;--has-work 只列有未完成工作者", run: runMembers})
|
||
register(&command{name: "mine", usage: "分派給我、待跟進的 issues", run: runMine})
|
||
// pulls 由 dailyops_commands.go 註冊(#8):無子命令時走本檔的 runPulls
|
||
// 工作流清單,有子命令(list/view/create/merge/comment)走日常操作。
|
||
register(&command{name: "next", usage: "下一件該做的事", run: runNext})
|
||
}
|
||
|
||
// errNoRepoFlag 是 --repo 值格式錯誤。
|
||
var errNoRepoFlag = errors.New("--repo 需為 owner/repo 形式")
|
||
|
||
// newWorkflowClient 依全域選項建立 workflow 所需的 API client。
|
||
func newWorkflowClient(env *Env) (*workflow.Scanner, error) {
|
||
token, _, err := gitea.ResolveToken(gitea.AuthOptions{
|
||
FlagToken: env.Globals.Token,
|
||
SiteURL: env.Globals.URL,
|
||
ConfigPath: env.Globals.ConfigPath,
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
client, err := gitea.New(env.Globals.URL, token, env.Globals.Timeout)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &workflow.Scanner{Client: &workflow.APIClient{Client: client}}, nil
|
||
}
|
||
|
||
// printer 依全域選項建立輸出器。
|
||
func printer(env *Env) gitea.Printer {
|
||
return gitea.Printer{Format: env.Globals.Output, Out: env.Out}
|
||
}
|
||
|
||
// runWhoami:teai whoami。
|
||
func runWhoami(env *Env, args []string) error {
|
||
if len(args) != 0 {
|
||
return &ErrUsage{Msg: "whoami 不接受參數"}
|
||
}
|
||
s, err := newWorkflowClient(env)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
name, err := s.Whoami()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return printer(env).PrintOne(
|
||
[]gitea.Field{{Name: "username"}},
|
||
gitea.Values{name},
|
||
)
|
||
}
|
||
|
||
// runOrgs:teai orgs。
|
||
func runOrgs(env *Env, args []string) error {
|
||
if len(args) != 0 {
|
||
return &ErrUsage{Msg: "orgs 不接受參數"}
|
||
}
|
||
s, err := newWorkflowClient(env)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
orgs, err := s.Orgs()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
rows := make([]gitea.Values, 0, len(orgs))
|
||
for _, o := range orgs {
|
||
rows = append(rows, gitea.Values{o})
|
||
}
|
||
return printer(env).PrintList(
|
||
[]gitea.Field{{Name: "username"}},
|
||
rows,
|
||
)
|
||
}
|
||
|
||
// runMembers:teai members [--has-work]。輸出每行一個帳號(同 gitea.py)。
|
||
func runMembers(env *Env, args []string) error {
|
||
fs := flag.NewFlagSet("teai members", flag.ContinueOnError)
|
||
fs.SetOutput(env.Err)
|
||
hasWork := fs.Bool("has-work", false, "只列出有未完成工作的成員")
|
||
if err := fs.Parse(args); err != nil {
|
||
return &ErrUsage{Msg: err.Error()}
|
||
}
|
||
if fs.NArg() != 0 {
|
||
return &ErrUsage{Msg: "members 不接受位置參數"}
|
||
}
|
||
s, err := newWorkflowClient(env)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
names, err := s.Members(*hasWork)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// gitea.py members 每行輸出一個帳號(非 JSON);保持一致以便交叉驗證。
|
||
for _, name := range names {
|
||
fmt.Fprintln(env.Out, name)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// runMine:teai mine。
|
||
func runMine(env *Env, args []string) error {
|
||
if len(args) != 0 {
|
||
return &ErrUsage{Msg: "mine 不接受參數"}
|
||
}
|
||
s, err := newWorkflowClient(env)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
issues, err := s.Mine()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return printer(env).PrintList(
|
||
[]gitea.Field{
|
||
{Name: "repo"}, {Name: "number"}, {Name: "title"}, {Name: "url"},
|
||
},
|
||
issueRows(issues),
|
||
)
|
||
}
|
||
|
||
// runPulls:teai pulls [--mine|--reviewer] [--repo owner/repo]。
|
||
func runPulls(env *Env, args []string) error {
|
||
fs := flag.NewFlagSet("teai pulls", flag.ContinueOnError)
|
||
fs.SetOutput(env.Err)
|
||
mine := fs.Bool("mine", false, "限定作者是目前帳號")
|
||
reviewer := fs.Bool("reviewer", false, "限定自己是 requested reviewer")
|
||
repo := fs.String("repo", "", "限定單一倉庫(owner/repo)")
|
||
if err := fs.Parse(args); err != nil {
|
||
return &ErrUsage{Msg: err.Error()}
|
||
}
|
||
if fs.NArg() != 0 {
|
||
return &ErrUsage{Msg: "pulls 不接受位置參數"}
|
||
}
|
||
if *mine && *reviewer {
|
||
return &ErrUsage{Msg: "--mine 與 --reviewer 不能併用"}
|
||
}
|
||
var owner, name string
|
||
if *repo != "" {
|
||
parts := strings.SplitN(*repo, "/", 2)
|
||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||
return &ErrUsage{Msg: errNoRepoFlag.Error()}
|
||
}
|
||
owner, name = parts[0], parts[1]
|
||
}
|
||
s, err := newWorkflowClient(env)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
items, err := s.Pulls(*mine, *reviewer)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
rows := make([]gitea.Values, 0, len(items))
|
||
for _, item := range items {
|
||
if *repo != "" && (item.Owner != owner || item.Repo != name) {
|
||
continue
|
||
}
|
||
rows = append(rows, gitea.Values{
|
||
item.Owner + "/" + item.Repo,
|
||
item.Pull.Number,
|
||
item.Pull.Title,
|
||
item.Pull.WebURL(),
|
||
})
|
||
}
|
||
return printer(env).PrintList(
|
||
[]gitea.Field{
|
||
{Name: "repo"}, {Name: "number"}, {Name: "title"}, {Name: "url"},
|
||
},
|
||
rows,
|
||
)
|
||
}
|
||
|
||
// runNext:teai next。無工作輸出 null(exit 0)。
|
||
func runNext(env *Env, args []string) error {
|
||
if len(args) != 0 {
|
||
return &ErrUsage{Msg: "next 不接受參數"}
|
||
}
|
||
s, err := newWorkflowClient(env)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
work, err := s.Next()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
p := printer(env)
|
||
if work == nil {
|
||
return p.PrintNull()
|
||
}
|
||
// gitea.py 的 _work_dict:role 僅 pull 帶(issue 不輸出 role 欄位)。
|
||
fields := []gitea.Field{
|
||
{Name: "type"}, {Name: "role"}, {Name: "repo"},
|
||
{Name: "number"}, {Name: "title"}, {Name: "url"},
|
||
}
|
||
row := gitea.Values{work.Type, work.Role, work.Repo, work.Number, work.Title, work.URL}
|
||
if work.Role == "" {
|
||
fields = []gitea.Field{
|
||
{Name: "type"}, {Name: "repo"},
|
||
{Name: "number"}, {Name: "title"}, {Name: "url"},
|
||
}
|
||
row = gitea.Values{work.Type, work.Repo, work.Number, work.Title, work.URL}
|
||
}
|
||
return p.PrintOne(fields, row)
|
||
}
|
||
|
||
// issueRows 把 issues 轉成輸出列(repo 欄位由掃描脈絡補齊)。
|
||
func issueRows(issues []workflow.Issue) []gitea.Values {
|
||
rows := make([]gitea.Values, 0, len(issues))
|
||
for i := range issues {
|
||
issue := &issues[i]
|
||
rows = append(rows, gitea.Values{
|
||
issue.FullName(),
|
||
issue.Num(),
|
||
issue.Title,
|
||
issue.WebURL(),
|
||
})
|
||
}
|
||
return rows
|
||
}
|