/** * 当前工作区对应哪个代码托管仓库:读 git 的 origin 地址,按 host 认出是哪一家,带上凭据造出 [ForgeProvider]。 * * 所有要访问仓库的地方都从这里拿 [ForgeContext],不再各自去读 remote 和 token。 */ import type { ExtensionContext } from 'vscode' import type { Remote } from '../git/remote' import type { ForgeKind, ForgeProvider } from './types' import { workspace } from 'vscode' import { getToken } from '../auth/secrets' import { detectRepo } from '../git/remote' import { GiteaProvider } from './gitea/provider' import { GH_LOGIN_HINT, readGhToken } from './github/ghAuth' import { GitHubProvider } from './github/provider' import { forgeKindForHost } from './types' /** 一个工作区对应的代码托管仓库,和访问它用的 [ForgeProvider]。 */ export interface ForgeContext { workspaceRoot: string kind: ForgeKind host: string owner: string repo: string provider: ForgeProvider } /** * 解析结果;失败时 `message` 是可以直接给人看的原因,`reason` 给需要分情况处理的调用方: * `no-workspace` 没打开工作区,`no-remote` 没有能认出的 origin,`no-credentials` 没有凭据。 */ export type ForgeResolution = | { ok: true, forge: ForgeContext } | { ok: false, reason: 'no-workspace' | 'no-remote', message: string } | { ok: false, reason: 'no-credentials', message: string, kind: ForgeKind, host: string } /** * 给指定的仓库造 [ForgeContext];webhook 记下的仓库不一定是当前工作区的 origin,所以单独开放。 * * GitHub 的凭据现取 gh 的登录,Gitea 的凭据是用户在设置里填、存在 SecretStorage 里的 token。 */ export async function openForge(ctx: ExtensionContext, workspaceRoot: string, remote: Remote): Promise { const kind = forgeKindForHost(remote.host) const repo = { workspaceRoot, kind, host: remote.host, owner: remote.owner, repo: remote.repo } if (kind === 'github') { const token = await readGhToken() if (!token) return { ok: false, reason: 'no-credentials', kind, host: remote.host, message: GH_LOGIN_HINT } return { ok: true, forge: { ...repo, provider: new GitHubProvider({ owner: remote.owner, repo: remote.repo, token }) } } } const token = await getToken(ctx, remote.host) if (!token) return { ok: false, reason: 'no-credentials', kind, host: remote.host, message: `请先配置 ${remote.host} 的访问令牌` } return { ok: true, forge: { ...repo, provider: new GiteaProvider({ ...remote, token }) } } } /** 解析当前工作区(第一个工作区文件夹)的代码托管仓库。 */ export async function resolveForge(ctx: ExtensionContext): Promise { const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath if (!workspaceRoot) return { ok: false, reason: 'no-workspace', message: '请先打开一个工作区文件夹' } const remote = await detectRepo(workspaceRoot) if (!remote) return { ok: false, reason: 'no-remote', message: '当前工作区没有能识别的 origin 远程仓库' } return openForge(ctx, workspaceRoot, remote) } /** * 同 [resolveForge],解析不出来时抛错。 * * @throws [Error] 解析失败,`message` 是给人看的原因 */ export async function requireForge(ctx: ExtensionContext): Promise { const resolved = await resolveForge(ctx) if (!resolved.ok) throw new Error(resolved.message) return resolved.forge }