79 lines
3.4 KiB
TypeScript
79 lines
3.4 KiB
TypeScript
/**
|
||
* 当前工作区对应哪个代码托管仓库:读 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<ForgeResolution> {
|
||
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<ForgeResolution> {
|
||
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<ForgeContext> {
|
||
const resolved = await resolveForge(ctx)
|
||
if (!resolved.ok)
|
||
throw new Error(resolved.message)
|
||
return resolved.forge
|
||
}
|