92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
import type { Issue, IssueColumn } from '../gitea/types'
|
|
|
|
const COLUMNS: readonly IssueColumn[] = ['todo', 'in-progress', 'review', 'done']
|
|
|
|
/** 非空字符串才算有值:状态里写空串表示把字段清掉。 */
|
|
const STRING_FIELDS = [
|
|
'sessionId',
|
|
'implementSessionId',
|
|
'reviewSessionId',
|
|
'testSessionId',
|
|
'prototypeSessionId',
|
|
'profilePath',
|
|
'brainstormProfilePath',
|
|
'testProfilePath',
|
|
'prototypeProfilePath',
|
|
'pr',
|
|
'prMergedAt',
|
|
'branch',
|
|
'worktreePath',
|
|
'color',
|
|
'handoffBundleKey',
|
|
'handoffFrom',
|
|
'handoffRequestFrom',
|
|
'handoffRequestError',
|
|
] as const
|
|
|
|
const BOOLEAN_FIELDS = ['prMerged', 'autoReview', 'reviewPassed'] as const
|
|
|
|
/** 状态里能直接放到卡片上的字段;读不出合法值的字段不出现。 */
|
|
export type IssueStateFields = Partial<Pick<Issue, CardStateField>>
|
|
|
|
type CardStateField = 'column' | 'specFile' | 'planFile' | 'prototypeFile' | 'prDiffFile' | 'implementStatus' | typeof STRING_FIELDS[number] | typeof BOOLEAN_FIELDS[number]
|
|
|
|
/**
|
|
* 过滤 specFile / planFile 里的垃圾值:真实路径必须含 `/` 且以 `.md` 结尾。
|
|
*
|
|
* `...`、`…` 是 cc 在头脑风暴阶段往工单正文里写的占位说明,旧版 webhook 曾把它们当路径存进状态,读出时在这里清掉。
|
|
*/
|
|
export function isValidSpxFilePath(v: unknown): v is string {
|
|
return typeof v === 'string'
|
|
&& v.length > 0
|
|
&& v.includes('/')
|
|
&& v.endsWith('.md')
|
|
&& v !== '...'
|
|
&& v !== '…'
|
|
}
|
|
|
|
/** prototypeFile 指向 Figma 插件的 manifest.json,规则同 [isValidSpxFilePath],只是后缀换成 `.json`。 */
|
|
function isValidPrototypeFilePath(v: unknown): v is string {
|
|
return typeof v === 'string'
|
|
&& v.length > 0
|
|
&& v.includes('/')
|
|
&& v.endsWith('.json')
|
|
&& v !== '...'
|
|
&& v !== '…'
|
|
}
|
|
|
|
function isValidPrDiffFilePath(v: unknown): v is string {
|
|
return typeof v === 'string' && /^docs\/pr-diff\/\S+\.md$/.test(v)
|
|
}
|
|
|
|
function isIssueColumn(v: unknown): v is IssueColumn {
|
|
return typeof v === 'string' && (COLUMNS as readonly string[]).includes(v)
|
|
}
|
|
|
|
/** 把一份工单状态翻成卡片字段,只留合法的值,代码托管仓库和 YouTrack 的卡片都按它读。 */
|
|
export function issueStateFields(state: Record<string, unknown>): IssueStateFields {
|
|
const out: Record<string, unknown> = {}
|
|
if (isIssueColumn(state.column))
|
|
out.column = state.column
|
|
for (const field of STRING_FIELDS) {
|
|
const v = state[field]
|
|
if (typeof v === 'string' && v.length > 0)
|
|
out[field] = v
|
|
}
|
|
for (const field of BOOLEAN_FIELDS) {
|
|
if (typeof state[field] === 'boolean')
|
|
out[field] = state[field]
|
|
}
|
|
if (isValidSpxFilePath(state.specFile))
|
|
out.specFile = state.specFile
|
|
if (isValidSpxFilePath(state.planFile))
|
|
out.planFile = state.planFile
|
|
if (isValidPrototypeFilePath(state.prototypeFile))
|
|
out.prototypeFile = state.prototypeFile
|
|
if (isValidPrDiffFilePath(state.prDiffFile))
|
|
out.prDiffFile = state.prDiffFile
|
|
if (state.implementStatus === 'running' || state.implementStatus === 'done' || state.implementStatus === 'failed')
|
|
out.implementStatus = state.implementStatus
|
|
return out as IssueStateFields
|
|
}
|