mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
Handle publication race during draft release notes patch
Between the draft status check and the PATCH request, a release can be published. The PATCH succeeds but now modifies published content. Check the PATCH response—if draft=false, publication won; restore the published body and leave generated notes unapplied.
This commit is contained in:
@@ -175,7 +175,10 @@ export async function createDraftRelease({
|
||||
log(`Release ${tag} was published while notes were generated; leaving it unchanged.`)
|
||||
return
|
||||
}
|
||||
await githubJson(
|
||||
// Why: the PATCH endpoint supports no conditional/versioned update, so the
|
||||
// GET above cannot close the window. The PATCH response reports the state we
|
||||
// actually wrote to; if publication won, put the published body back.
|
||||
const patchedRelease = await githubJson(
|
||||
fetchImpl,
|
||||
`https://api.github.com/repos/${repo}/releases/${existingRelease.id}`,
|
||||
token,
|
||||
@@ -184,6 +187,22 @@ export async function createDraftRelease({
|
||||
body: JSON.stringify({ body })
|
||||
}
|
||||
)
|
||||
if (patchedRelease?.draft !== true) {
|
||||
const publishedBody = typeof currentRelease.body === 'string' ? currentRelease.body : ''
|
||||
await githubJson(
|
||||
fetchImpl,
|
||||
`https://api.github.com/repos/${repo}/releases/${existingRelease.id}`,
|
||||
token,
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ body: publishedBody })
|
||||
}
|
||||
)
|
||||
log(
|
||||
`Release ${tag} was published while notes were patched; restored its published body and left the generated notes unapplied.`
|
||||
)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Why: GitHub's generated release notes can exceed the release body API
|
||||
// limit, so create with a bounded body. Omit target_commitish because the
|
||||
|
||||
@@ -207,8 +207,8 @@ describe('createDraftRelease', () => {
|
||||
jsonResponse([release('v1.4.35'), release('v1.4.36', { draft: true, id: 42 })])
|
||||
)
|
||||
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36', body: 'notes' }))
|
||||
.mockResolvedValueOnce(jsonResponse({ id: 42, draft: true }))
|
||||
.mockResolvedValueOnce(jsonResponse({ id: 42, body: 'notes' }))
|
||||
.mockResolvedValueOnce(jsonResponse({ id: 42, draft: true, body: 'stale' }))
|
||||
.mockResolvedValueOnce(jsonResponse({ id: 42, draft: true, body: 'notes' }))
|
||||
|
||||
await createDraftRelease({
|
||||
repo: 'stablyai/orca',
|
||||
@@ -248,6 +248,43 @@ describe('createDraftRelease', () => {
|
||||
})
|
||||
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(3)
|
||||
expect(fetchImpl).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'https://api.github.com/repos/stablyai/orca/releases/42',
|
||||
expect.not.objectContaining({ method: expect.anything() })
|
||||
)
|
||||
})
|
||||
|
||||
it('restores the published body when publication lands between the check and the patch', async () => {
|
||||
const log = vi.fn()
|
||||
const fetchImpl = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse([release('v1.4.35'), release('v1.4.36', { draft: true, id: 42 })])
|
||||
)
|
||||
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36', body: 'notes' }))
|
||||
.mockResolvedValueOnce(jsonResponse({ id: 42, draft: true, body: 'hand-written notes' }))
|
||||
.mockResolvedValueOnce(jsonResponse({ id: 42, draft: false, body: 'notes' }))
|
||||
.mockResolvedValueOnce(jsonResponse({ id: 42, draft: false, body: 'hand-written notes' }))
|
||||
|
||||
await createDraftRelease({
|
||||
repo: 'stablyai/orca',
|
||||
tag: 'v1.4.36',
|
||||
token: 'token',
|
||||
fetchImpl,
|
||||
log
|
||||
})
|
||||
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(5)
|
||||
expect(fetchImpl).toHaveBeenNthCalledWith(
|
||||
5,
|
||||
'https://api.github.com/repos/stablyai/orca/releases/42',
|
||||
expect.objectContaining({
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ body: 'hand-written notes' })
|
||||
})
|
||||
)
|
||||
expect(log).toHaveBeenCalledWith(expect.stringContaining('restored its published body'))
|
||||
})
|
||||
|
||||
it('preserves notes on an existing published release', async () => {
|
||||
|
||||
Reference in New Issue
Block a user