Files
memind/release-gate/ci-status.mjs
john a95681ea3a
Memind CI / Test, build, and release guards (push) Successful in 1m39s
fix: treat pending Gitea CI as acceptable on synced main
Normal Portal releases should not block when commit status stays pending
after the candidate is already pushed to origin/main.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 22:36:40 +08:00

74 lines
2.5 KiB
JavaScript

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;
}