85 lines
3.0 KiB
Go
85 lines
3.0 KiB
Go
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 needs a [tea.TeaLogin] for the same host and uses its URL as the API
|
||
// root and its token; `GITEA_TOKEN` only fills in when that login has no token, so the variable never reaches a host the
|
||
// user has not logged in to with tea.
|
||
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
|
||
}
|
||
if login == nil {
|
||
return nil, fmt.Errorf("tea config 里没有 %s 的 login,spx 不会把 Gitea token 发给 tea 没登录过的 host;请先运行 `tea login add` 登录这个 host", o.Host)
|
||
}
|
||
token := login.Token
|
||
if token == "" {
|
||
token = env.Getenv("GITEA_TOKEN")
|
||
}
|
||
if token == "" {
|
||
return nil, fmt.Errorf("tea config 里 %s 的 login %q 没有 token,GITEA_TOKEN 也没设置;请重新运行 `tea login add` 或设置 GITEA_TOKEN", o.Host, login.Name)
|
||
}
|
||
return NewGitea(login.URL, token, o.Owner, o.Repo), nil
|
||
}
|