Files
superwork/vscode/src/panel/handlers/release.ts
T

174 lines
6.6 KiB
TypeScript
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.
/**
* 工具栏「发布」按钮的扩展侧 handler:切新发布分支、摘修复到当期发布分支、触发正式服部署。
*
* git 逻辑在 `src/git/release.ts`,Jenkins 触发在 `src/jenkins/trigger.ts`;这里只负责
* 读设置、弹 QuickPick、发 toast、维护 `panel.releaseRunning` 互斥。
*/
import type { KanbanWebviewPanel } from '../KanbanPanel'
import { window, workspace } from 'vscode'
import { gitFetch } from '../../git/branches'
import {
attachPulls,
cherryPickToRelease,
cutRelease,
getReleaseStatus,
groupCommitsByPr,
indexUnreleasedByMergePr,
listReleaseBranches,
listUnreleasedCommits,
todayReleaseBranch,
} from '../../git/release'
import { triggerJenkinsBuild } from '../../jenkins/trigger'
import { getSettings } from '../../settings/store'
import { dismiss, toast } from './handoffFlow'
export async function handleReleaseStatus(panel: KanbanWebviewPanel): Promise<void> {
const root = workspace.workspaceFolders?.[0]?.uri.fsPath
const s = getSettings(panel.context)
const base = {
type: 'release/status' as const,
devBranch: s.devBranch,
nextBranch: todayReleaseBranch(s.releaseBranchPrefix),
running: panel.releaseRunning,
// webview 只在设置弹窗打开时才有设置快照,「部署」菜单项是否显示由这里告诉它
jenkinsConfigured: s.jenkinsJob.length > 0,
}
if (!root) {
panel.postMessage({ ...base, unreleased: 0, unavailable: '请先打开一个工作区文件夹' })
return
}
const st = await getReleaseStatus({ cwd: root, devBranch: s.devBranch, prefix: s.releaseBranchPrefix })
panel.postMessage({ ...base, current: st.current, unreleased: st.unreleased, unavailable: st.unavailable })
}
function requireRoot(): string {
const root = workspace.workspaceFolders?.[0]?.uri.fsPath
if (!root)
throw new Error('请先打开一个工作区文件夹')
return root
}
/** 三个写动作共用:互斥 + 前后刷新状态 + 统一错误 toast */
async function runReleaseAction(panel: KanbanWebviewPanel, fn: () => Promise<void>): Promise<void> {
if (panel.releaseRunning)
return
panel.releaseRunning = true
void handleReleaseStatus(panel)
try {
await fn()
}
catch (err) {
toast(panel, 'error', err instanceof Error ? err.message : String(err), { dismissOnTimer: 10_000 })
}
finally {
panel.releaseRunning = false
void handleReleaseStatus(panel)
}
}
async function deploy(panel: KanbanWebviewPanel, branch: string): Promise<void> {
const s = getSettings(panel.context)
await triggerJenkinsBuild({ job: s.jenkinsJob, params: { REF: branch, REMOTE_HOST: s.jenkinsProdHost } })
toast(panel, 'success', `已触发 Jenkins ${s.jenkinsJob}:REF=${branch} → ${s.jenkinsProdHost}`)
}
export function handleReleaseCut(panel: KanbanWebviewPanel): Promise<void> {
return runReleaseAction(panel, async () => {
const root = requireRoot()
const s = getSettings(panel.context)
const branch = todayReleaseBranch(s.releaseBranchPrefix)
const count = await cutRelease({ cwd: root, devBranch: s.devBranch, branch })
toast(panel, 'success', `已切发布分支 ${branch}(${count} 个提交)`)
if (!s.jenkinsJob)
return
const pick = await window.showInformationMessage(`现在部署 ${branch} 到${s.jenkinsProdHost}?`, '部署')
if (pick === '部署')
await deploy(panel, branch)
})
}
export async function handleReleaseCherryPick(panel: KanbanWebviewPanel): Promise<void> {
try {
const root = requireRoot()
const s = getSettings(panel.context)
const status = await getReleaseStatus({ cwd: root, devBranch: s.devBranch, prefix: s.releaseBranchPrefix })
if (status.unavailable)
throw new Error(status.unavailable)
const current = status.current
if (!current)
throw new Error('尚无发布分支,先切新发布')
const commits = await listUnreleasedCommits({ cwd: root, devBranch: s.devBranch, current })
if (commits.length === 0) {
toast(panel, 'info', '没有待摘的提交')
return
}
const index = await indexUnreleasedByMergePr({
cwd: root,
current,
devBranch: s.devBranch,
shas: commits.map(c => c.sha),
})
panel.postMessage({
type: 'release/pick-show',
branch: current,
groups: groupCommitsByPr(attachPulls(commits, index)),
firstAfterReleaseSha: commits.at(-1)?.sha,
})
}
catch (err) {
toast(panel, 'error', err instanceof Error ? err.message : String(err), { dismissOnTimer: 10_000 })
}
}
export function handleReleaseCherryPickConfirm(panel: KanbanWebviewPanel, shas: string[]): Promise<void> {
return runReleaseAction(panel, async () => {
if (shas.length === 0)
return
const root = requireRoot()
const s = getSettings(panel.context)
const status = await getReleaseStatus({ cwd: root, devBranch: s.devBranch, prefix: s.releaseBranchPrefix })
if (status.unavailable)
throw new Error(status.unavailable)
const current = status.current
if (!current)
throw new Error('尚无发布分支,先切新发布')
const commits = await listUnreleasedCommits({ cwd: root, devBranch: s.devBranch, current })
const order = new Map(commits.map((c, i) => [c.sha, i]))
const ordered = [...shas].sort((a, b) => (order.get(b) ?? 0) - (order.get(a) ?? 0))
const spinner = toast(panel, 'info', `正在摘 ${ordered.length} 个提交到 ${current}…`, { spinner: true, dismissOnTimer: 0 })
try {
await cherryPickToRelease({ cwd: root, branch: current, shas: ordered })
}
finally {
dismiss(panel, spinner)
}
toast(panel, 'success', `已摘 ${ordered.length} 个提交到 ${current}`)
})
}
export function handleReleaseDeploy(panel: KanbanWebviewPanel): Promise<void> {
return runReleaseAction(panel, async () => {
const root = requireRoot()
const s = getSettings(panel.context)
if (!s.jenkinsJob)
throw new Error('未配置 Jenkins 任务名,请先在设置里填写')
await gitFetch(root)
const branches = await listReleaseBranches({ cwd: root, prefix: s.releaseBranchPrefix })
if (branches.length === 0)
throw new Error('尚无发布分支')
const [current, ...older] = branches
const items = [
{ label: current, description: '当期' },
...older.map(label => ({ label, description: '回滚到此发布点' })),
]
const pick = await window.showQuickPick(items, {
title: `部署到${s.jenkinsProdHost}`,
placeHolder: '选择要部署的发布分支(默认当期,选旧的即回滚)',
})
if (!pick)
return
await deploy(panel, pick.label)
})
}