fix: release script idempotent recovery when tag already exists

This commit is contained in:
Sucukdeluxe 2026-03-06 22:56:30 +01:00
parent 9bbeffb2df
commit 0c9bbb0153

View File

@ -260,11 +260,20 @@ async function createOrGetRelease(baseApi, tag, authHeader, notes) {
prerelease: false prerelease: false
}; };
const created = await apiRequest("POST", `${baseApi}/releases`, authHeader, JSON.stringify(payload)); const created = await apiRequest("POST", `${baseApi}/releases`, authHeader, JSON.stringify(payload));
if (!created.ok) { if (created.ok) {
throw new Error(`Failed to create release (${created.status}): ${JSON.stringify(created.body)}`);
}
return created.body; return created.body;
} }
// Gitea can return 409/422/500 UNIQUE when the release was already partially created.
// Retry the GET — it may now exist.
if (created.status === 409 || created.status === 422 || created.status === 500) {
const retry = await apiRequest("GET", `${baseApi}/releases/tags/${encodeURIComponent(tag)}`, authHeader);
if (retry.ok) {
process.stdout.write(`Release already exists, using existing release.\n`);
return retry.body;
}
}
throw new Error(`Failed to create release (${created.status}): ${JSON.stringify(created.body)}`);
}
async function uploadReleaseAssets(baseApi, releaseId, authHeader, releaseDir, files) { async function uploadReleaseAssets(baseApi, releaseId, authHeader, releaseDir, files) {
for (const fileName of files) { for (const fileName of files) {
@ -322,8 +331,12 @@ async function main() {
const releaseNotes = args.notes || `- Release ${tag}`; const releaseNotes = args.notes || `- Release ${tag}`;
const repo = getGiteaRepo(); const repo = getGiteaRepo();
const tagExists = spawnSync("git", ["rev-parse", "--verify", `refs/tags/${tag}`], { cwd: process.cwd(), stdio: "ignore" }).status === 0;
if (tagExists) {
process.stdout.write(`Tag ${tag} already exists locally — skipping version bump and git operations (recovery mode).\n`);
} else {
ensureNoTrackedChanges(); ensureNoTrackedChanges();
ensureTagMissing(tag);
if (args.dryRun) { if (args.dryRun) {
process.stdout.write(`Dry run: would release ${tag}. No changes made.\n`); process.stdout.write(`Dry run: would release ${tag}. No changes made.\n`);
@ -331,16 +344,19 @@ async function main() {
} }
updatePackageVersion(rootDir, version); updatePackageVersion(rootDir, version);
}
process.stdout.write(`Building release artifacts for ${tag}...\n`); process.stdout.write(`Building release artifacts for ${tag}...\n`);
run(NPM_RELEASE_WIN.command, NPM_RELEASE_WIN.args); run(NPM_RELEASE_WIN.command, NPM_RELEASE_WIN.args);
const assets = ensureAssetsExist(rootDir, version); const assets = ensureAssetsExist(rootDir, version);
if (!tagExists) {
run("git", ["add", "package.json"]); run("git", ["add", "package.json"]);
run("git", ["commit", "-m", `Release ${tag}`]); run("git", ["commit", "-m", `Release ${tag}`]);
run("git", ["push", repo.remote, "main"]); run("git", ["push", repo.remote, "main"]);
run("git", ["tag", tag]); run("git", ["tag", tag]);
run("git", ["push", repo.remote, tag]); run("git", ["push", repo.remote, tag]);
}
const authHeader = getAuthHeader(repo.host); const authHeader = getAuthHeader(repo.host);
const baseApi = `${repo.baseUrl}/api/v1/repos/${repo.owner}/${repo.repo}`; const baseApi = `${repo.baseUrl}/api/v1/repos/${repo.owner}/${repo.repo}`;