import { spawnSync } from 'node:child_process'; import path from 'node:path'; const ROOT = path.resolve(new URL('..', import.meta.url).pathname); function git(args) { const result = spawnSync('git', args, { cwd: ROOT, encoding: 'utf8' }); if (result.status !== 0) { throw new Error(`git ${args.join(' ')} failed`); } return result.stdout.trim(); } async function resolveRemote() { let remote = ''; try { remote = git(['remote', 'get-url', 'origin']); } catch { return null; } const slugMatch = remote.match(/(?:https?:\/\/[^/]+\/|git@[^:]+:)(.+?)(?:\.git)?$/); if (!slugMatch) return null; const slug = slugMatch[1].replace(/\.git$/, ''); const [owner, repo] = slug.split('/'); if (!owner || !repo) return null; const hostMatch = remote.match(/^git@([^:]+):/) || remote.match(/^https?:\/\/([^/]+)\//); const host = hostMatch?.[1] ?? 'git.tkmind.cn'; const protocol = remote.startsWith('git@') ? 'https' : remote.split('://')[0]; return `${protocol}://${host}/api/v1/repos/${owner}/${repo}`; } async function queryGiteaCommitStatus(apiBase, commitSha) { const response = await fetch(`${apiBase}/commits/${commitSha}/status`, { signal: AbortSignal.timeout(15_000), }); if (!response.ok) { throw new Error(`Gitea commit status lookup failed: HTTP ${response.status}`); } const payload = await response.json(); const state = String(payload?.state ?? '').trim().toLowerCase(); if (state === 'success') return 'success'; if (state === 'pending') return 'pending'; return state || 'missing'; } export async function resolveReleaseCiStatus(commitSha) { const explicit = String(process.env.MEMIND_RELEASE_CI_STATUS ?? '').trim().toLowerCase(); if (explicit) return explicit; const apiBase = await resolveRemote(); if (!apiBase) return 'missing'; try { return await queryGiteaCommitStatus(apiBase, commitSha); } catch { return 'missing'; } } export function isReleaseCiAcceptable(ciStatus, { headSha, remoteSha } = {}) { const normalized = String(ciStatus ?? 'missing').trim().toLowerCase(); if (normalized === 'success') return true; if (normalized === 'failure' || normalized === 'error') return false; // Self-hosted releases often have no meaningful Gitea commit status. Once the // candidate is fully pushed to origin/main, treat missing/pending CI as acceptable. if ( (normalized === 'missing' || normalized === 'pending') && remoteSha && headSha === remoteSha ) { return true; } return false; }