✨ feat(vscode): webhook 副作用按 assignee 门禁,适配多人协作
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 会话身份:当前 Gitea token 对应的用户,按 host 缓存。
|
||||
*
|
||||
* 多人协作的三个消费方:
|
||||
* - webhook 副作用门禁(coordinator)用 login 判断"这是不是我的工单"
|
||||
* - 实施认领(handleImplement)把 assignee 设为当前用户
|
||||
* - 状态栏常驻展示身份,token 失效/用错同事 token 时第一眼就能看出来
|
||||
*/
|
||||
|
||||
import type { ExtensionContext, StatusBarItem } from 'vscode'
|
||||
import type { GiteaUser } from '../gitea/api'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { StatusBarAlignment, ThemeColor, window, workspace } from 'vscode'
|
||||
import { detectRepo } from '../git/remote'
|
||||
import { getCurrentUser } from '../gitea/api'
|
||||
import { logger } from '../logging/logger'
|
||||
import { getToken } from './secrets'
|
||||
|
||||
const userByHost = new Map<string, GiteaUser>()
|
||||
let statusBar: StatusBarItem | undefined
|
||||
|
||||
/**
|
||||
* 解析并缓存 token 对应的 login。失败(网络/token 失效)返回 undefined,
|
||||
* 由调用方决定降级行为——门禁侧的语义是"身份未知 → observe"。
|
||||
*/
|
||||
export async function loginForToken(host: string, token: string): Promise<string | undefined> {
|
||||
const hit = userByHost.get(host)
|
||||
if (hit)
|
||||
return hit.login
|
||||
try {
|
||||
const user = await getCurrentUser({ host, token })
|
||||
userByHost.set(host, user)
|
||||
return user.login
|
||||
}
|
||||
catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
logger.add({
|
||||
level: 'error',
|
||||
source: 'identity',
|
||||
message: `解析 Gitea 身份失败 host=${host}`,
|
||||
details: message,
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** token 变更后必须调用,否则状态栏和门禁还认旧身份。 */
|
||||
export function clearIdentityCache(host?: string): void {
|
||||
if (host)
|
||||
userByHost.delete(host)
|
||||
else
|
||||
userByHost.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活时(以及 token 保存后)调用:解析当前工作区的 Gitea 身份,
|
||||
* 展示到状态栏,并与 git 提交邮箱比对——token 被同步/复制到同事机器
|
||||
* 是已发生过的真实事故(chw 机器),这里是最后一道人眼防线。
|
||||
*/
|
||||
export async function initIdentity(ctx: ExtensionContext): Promise<void> {
|
||||
if (!statusBar) {
|
||||
statusBar = window.createStatusBarItem(StatusBarAlignment.Left, 0)
|
||||
ctx.subscriptions.push(statusBar)
|
||||
}
|
||||
|
||||
const ws = workspace.workspaceFolders?.[0]?.uri.fsPath
|
||||
if (!ws) {
|
||||
statusBar.hide()
|
||||
return
|
||||
}
|
||||
const remote = await detectRepo(ws)
|
||||
if (!remote) {
|
||||
statusBar.hide()
|
||||
return
|
||||
}
|
||||
|
||||
const token = await getToken(ctx, remote.host)
|
||||
if (!token) {
|
||||
statusBar.text = '$(account) spx: 未配置 token'
|
||||
statusBar.tooltip = `尚未为 ${remote.host} 配置 Gitea token,点击配置`
|
||||
statusBar.command = 'superpowers.setGiteaToken'
|
||||
statusBar.backgroundColor = new ThemeColor('statusBarItem.warningBackground')
|
||||
statusBar.show()
|
||||
return
|
||||
}
|
||||
|
||||
const login = await loginForToken(remote.host, token)
|
||||
if (!login) {
|
||||
statusBar.text = '$(account) spx: token 失效'
|
||||
statusBar.tooltip = `${remote.host} 的 token 无法解析身份,点击重新配置`
|
||||
statusBar.command = 'superpowers.setGiteaToken'
|
||||
statusBar.backgroundColor = new ThemeColor('statusBarItem.errorBackground')
|
||||
statusBar.show()
|
||||
return
|
||||
}
|
||||
|
||||
statusBar.text = `$(account) spx: ${login}`
|
||||
statusBar.tooltip = `Gitea 身份 ${login} @ ${remote.host}`
|
||||
statusBar.command = 'superpowers.setGiteaToken'
|
||||
statusBar.backgroundColor = undefined
|
||||
statusBar.show()
|
||||
logger.add({
|
||||
level: 'info',
|
||||
source: 'identity',
|
||||
message: `当前 Gitea 身份 ${login} @ ${remote.host}`,
|
||||
})
|
||||
|
||||
checkGitIdentityMatch(ws, remote.host, login)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gitea 账号邮箱 vs `git config user.email`:不一致基本等于"用了别人的
|
||||
* token"或"git 身份没配"。只警告不拦截——同名多邮箱的合法场景存在。
|
||||
*/
|
||||
function checkGitIdentityMatch(workspaceRoot: string, host: string, login: string): void {
|
||||
const giteaEmail = userByHost.get(host)?.email?.trim().toLowerCase()
|
||||
if (!giteaEmail)
|
||||
return
|
||||
const res = spawnSync('git', ['-C', workspaceRoot, 'config', 'user.email'], { encoding: 'utf-8' })
|
||||
const gitEmail = (res.stdout ?? '').trim().toLowerCase()
|
||||
if (!gitEmail || gitEmail === giteaEmail)
|
||||
return
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source: 'identity',
|
||||
message: `Gitea 身份与 git 提交邮箱不一致: ${login}(${giteaEmail}) vs git(${gitEmail})`,
|
||||
})
|
||||
void window.showWarningMessage(
|
||||
`Gitea token 身份是 ${login}(${giteaEmail}),但 git 提交邮箱是 ${gitEmail}。确认没有用错同事的 token。`,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user