fix: clean up duplicate drafts after canonicalization (#753)

* fix: clean up duplicate drafts after canonicalization

Signed-off-by: Rui Chen <rui@chenrui.dev>

* refactor: collapse duplicate draft cleanup path

Signed-off-by: Rui Chen <rui@chenrui.dev>

---------

Signed-off-by: Rui Chen <rui@chenrui.dev>
This commit is contained in:
Rui Chen
2026-03-14 22:48:32 -04:00
committed by GitHub
parent bafaa2d7ac
commit 0a28836784
3 changed files with 71 additions and 11 deletions

View File

@@ -610,6 +610,66 @@ describe('github', () => {
release_id: duplicateRelease.id, release_id: duplicateRelease.id,
}); });
}); });
it('deletes the just-created duplicate draft even if recent release listing misses it', async () => {
const canonicalRelease: Release = {
id: 1,
upload_url: 'canonical-upload',
html_url: 'canonical-html',
tag_name: 'v1.0.0',
name: 'canonical',
body: 'test',
target_commitish: 'main',
draft: true,
prerelease: false,
assets: [],
};
const duplicateRelease: Release = {
id: 2,
upload_url: 'duplicate-upload',
html_url: 'duplicate-html',
tag_name: 'v1.0.0',
name: 'duplicate',
body: 'test',
target_commitish: 'main',
draft: true,
prerelease: false,
assets: [],
};
let lookupCount = 0;
const deleteReleaseSpy = vi.fn(async () => undefined);
const mockReleaser: Releaser = {
getReleaseByTag: () => {
lookupCount += 1;
if (lookupCount === 1) {
return Promise.reject({ status: 404 });
}
return Promise.resolve({ data: canonicalRelease });
},
createRelease: () => Promise.resolve({ data: duplicateRelease }),
updateRelease: () => Promise.reject('Not implemented'),
finalizeRelease: () => Promise.reject('Not implemented'),
allReleases: async function* () {
yield { data: [canonicalRelease] };
},
listReleaseAssets: () => Promise.reject('Not implemented'),
deleteReleaseAsset: () => Promise.reject('Not implemented'),
deleteRelease: deleteReleaseSpy,
updateReleaseAsset: () => Promise.reject('Not implemented'),
uploadReleaseAsset: () => Promise.reject('Not implemented'),
};
const result = await release(config, mockReleaser, 2);
assert.equal(result.release.id, canonicalRelease.id);
assert.equal(result.created, false);
expect(deleteReleaseSpy).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
release_id: duplicateRelease.id,
});
});
}); });
describe('upload', () => { describe('upload', () => {

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -707,9 +707,13 @@ async function cleanupDuplicateDraftReleases(
repo: string, repo: string,
tag: string, tag: string,
canonicalReleaseId: number, canonicalReleaseId: number,
recentReleases: Release[], releases: Release[],
): Promise<void> { ): Promise<void> {
for (const duplicate of recentReleases) { const uniqueReleases = Array.from(
new Map(releases.map((release) => [release.id, release])).values(),
);
for (const duplicate of uniqueReleases) {
if (duplicate.id === canonicalReleaseId || !duplicate.draft || duplicate.assets.length > 0) { if (duplicate.id === canonicalReleaseId || !duplicate.draft || duplicate.assets.length > 0) {
continue; continue;
} }
@@ -760,14 +764,10 @@ async function canonicalizeCreatedRelease(
); );
} }
await cleanupDuplicateDraftReleases( await cleanupDuplicateDraftReleases(releaser, owner, repo, tag, canonicalRelease.id, [
releaser, createdRelease,
owner, ...recentReleases,
repo, ]);
tag,
canonicalRelease.id,
recentReleases,
);
return canonicalRelease; return canonicalRelease;
} }