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,10 +260,19 @@ 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) {
@ -322,25 +331,32 @@ async function main() {
const releaseNotes = args.notes || `- Release ${tag}`; const releaseNotes = args.notes || `- Release ${tag}`;
const repo = getGiteaRepo(); const repo = getGiteaRepo();
ensureNoTrackedChanges(); const tagExists = spawnSync("git", ["rev-parse", "--verify", `refs/tags/${tag}`], { cwd: process.cwd(), stdio: "ignore" }).status === 0;
ensureTagMissing(tag);
if (args.dryRun) { if (tagExists) {
process.stdout.write(`Dry run: would release ${tag}. No changes made.\n`); process.stdout.write(`Tag ${tag} already exists locally — skipping version bump and git operations (recovery mode).\n`);
return; } else {
ensureNoTrackedChanges();
if (args.dryRun) {
process.stdout.write(`Dry run: would release ${tag}. No changes made.\n`);
return;
}
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);
run("git", ["add", "package.json"]); if (!tagExists) {
run("git", ["commit", "-m", `Release ${tag}`]); run("git", ["add", "package.json"]);
run("git", ["push", repo.remote, "main"]); run("git", ["commit", "-m", `Release ${tag}`]);
run("git", ["tag", tag]); run("git", ["push", repo.remote, "main"]);
run("git", ["push", repo.remote, tag]); run("git", ["tag", 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}`;