Files
superwork/vscode/cli/internal/forge/open.go
T

84 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package forge
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/cruldra/superpowers-vscode/cli/internal/repo"
"github.com/cruldra/superpowers-vscode/cli/internal/tea"
)
// Env is where [Open] gets credentials from; tests swap in canned answers, real runs use [SystemEnv].
type Env struct {
// GitHubToken returns the token the GitHub CLI holds for `host`.
GitHubToken func(host string) (string, error)
// TeaLogin returns the tea login whose URL points at `host`, or `nil` when there is none.
TeaLogin func(host string) (*tea.TeaLogin, error)
// Getenv reads an environment variable.
Getenv func(key string) string
}
// SystemEnv reads credentials from this machine: `gh auth token`, the tea config file and the process environment.
func SystemEnv() Env {
return Env{
GitHubToken: ghAuthToken,
TeaLogin: func(host string) (*tea.TeaLogin, error) {
path, err := tea.DefaultConfigPath()
if err != nil {
return nil, err
}
return tea.LoginForHost(path, host)
},
Getenv: os.Getenv,
}
}
// ghAuthToken asks the GitHub CLI for the token it stores for `host`.
func ghAuthToken(host string) (string, error) {
out, err := exec.Command("gh", "auth", "token", "--hostname", host).Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && len(exitErr.Stderr) > 0 {
return "", fmt.Errorf("gh auth token --hostname %s 失败: %w: %s", host, err, strings.TrimSpace(string(exitErr.Stderr)))
}
return "", fmt.Errorf("gh auth token --hostname %s 失败: %w", host, err)
}
return string(out), nil
}
// Open connects to the repo at `o` on the forge its host names, picking credentials the way that forge expects.
//
// GitHub takes the token from `gh auth token`. Gitea takes the token of the tea login for the same host, and its
// URL as the API root; with no such login, or one without a token, it falls back to `GITEA_TOKEN` and the origin's web root.
func Open(o repo.Origin, env Env) (Forge, error) {
if KindForHost(o.Host) == KindGitHub {
token, err := env.GitHubToken(o.Host)
if err != nil {
return nil, fmt.Errorf("取 GitHub token 失败,请先运行 `gh auth login`: %w", err)
}
token = strings.TrimSpace(token)
if token == "" {
return nil, fmt.Errorf("gh 没有 %s 的 token,请先运行 `gh auth login`", o.Host)
}
return NewGitHub(GitHubAPIBaseURL, token, o.Owner, o.Repo), nil
}
login, err := env.TeaLogin(o.Host)
if err != nil {
return nil, err
}
baseURL, token := o.WebURL, ""
if login != nil {
baseURL, token = login.URL, login.Token
}
if token == "" {
token = env.Getenv("GITEA_TOKEN")
}
if token == "" {
return nil, fmt.Errorf("没有 %s 的 Gitea token:tea config 里没有该 host 的 login 或其 token 为空,GITEA_TOKEN 也没设置;请运行 `tea login add` 或设置 GITEA_TOKEN", o.Host)
}
return NewGitea(baseURL, token, o.Owner, o.Repo), nil
}