mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Support updating existing draft releases when regenerating notes (#19014)
Move release existence check into create-draft-release.mjs. Draft releases are updated via PATCH, published releases are skipped, making the release-cut workflow idempotent.
This commit is contained in:
@@ -809,13 +809,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ needs.cut.outputs.tag }}
|
||||
run: |
|
||||
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
echo "Release $TAG already exists."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
node config/scripts/create-draft-release.mjs "$TAG"
|
||||
run: node config/scripts/create-draft-release.mjs "$TAG"
|
||||
|
||||
terminal-rendering-golden:
|
||||
needs: cut
|
||||
|
||||
@@ -128,10 +128,14 @@ export async function createDraftRelease({
|
||||
throw new Error('token is required')
|
||||
}
|
||||
|
||||
const previousTag = latestPreviousPublishedDesktopReleaseTag(
|
||||
await fetchRepoReleases(repo, token, fetchImpl),
|
||||
tag
|
||||
)
|
||||
const releases = await fetchRepoReleases(repo, token, fetchImpl)
|
||||
const existingRelease = releases.find((release) => release?.tag_name === tag)
|
||||
if (existingRelease && existingRelease.draft !== true) {
|
||||
log(`Release ${tag} already exists and is published.`)
|
||||
return
|
||||
}
|
||||
|
||||
const previousTag = latestPreviousPublishedDesktopReleaseTag(releases, tag)
|
||||
const generateNotesBody = {
|
||||
tag_name: tag,
|
||||
target_commitish: tag,
|
||||
@@ -156,24 +160,43 @@ export async function createDraftRelease({
|
||||
typeof releaseNotes.name === 'string' && releaseNotes.name.length > 0 ? releaseNotes.name : tag
|
||||
const prerelease = tag.includes('-rc.')
|
||||
|
||||
// Why: GitHub's generated release notes can exceed the release body API
|
||||
// limit, so create with a bounded body. Omit target_commitish because the
|
||||
// release-cut tag already exists and GitHub rejects the tag name there.
|
||||
await githubJson(fetchImpl, `https://api.github.com/repos/${repo}/releases`, token, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
tag_name: tag,
|
||||
name,
|
||||
body,
|
||||
draft: true,
|
||||
prerelease
|
||||
if (existingRelease) {
|
||||
if (!Number.isInteger(existingRelease.id)) {
|
||||
throw new Error(`Draft release ${tag} is missing a GitHub release id`)
|
||||
}
|
||||
await githubJson(
|
||||
fetchImpl,
|
||||
`https://api.github.com/repos/${repo}/releases/${existingRelease.id}`,
|
||||
token,
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ body })
|
||||
}
|
||||
)
|
||||
} 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
|
||||
// release-cut tag already exists and GitHub rejects the tag name there.
|
||||
await githubJson(fetchImpl, `https://api.github.com/repos/${repo}/releases`, token, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
tag_name: tag,
|
||||
name,
|
||||
body,
|
||||
draft: true,
|
||||
prerelease
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (generatedBody.length !== body.length) {
|
||||
log(`Created draft release ${tag} with truncated generated notes (${body.length} chars).`)
|
||||
log(
|
||||
`${existingRelease ? 'Updated' : 'Created'} draft release ${tag} with truncated generated notes (${body.length} chars).`
|
||||
)
|
||||
} else {
|
||||
log(`Created draft release ${tag} with generated notes (${body.length} chars).`)
|
||||
log(
|
||||
`${existingRelease ? 'Updated' : 'Created'} draft release ${tag} with generated notes (${body.length} chars).`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ describe('createDraftRelease', () => {
|
||||
it('creates a draft release with bounded generated notes', async () => {
|
||||
const fetchImpl = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse([release('v1.4.35'), release('v1.4.36')]))
|
||||
.mockResolvedValueOnce(jsonResponse([release('v1.4.35')]))
|
||||
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36', body: 'a'.repeat(130_000) }))
|
||||
.mockResolvedValueOnce(jsonResponse({ tag_name: 'v1.4.36', draft: true }))
|
||||
|
||||
@@ -184,7 +184,7 @@ describe('createDraftRelease', () => {
|
||||
it('marks rc tags as prereleases', async () => {
|
||||
const fetchImpl = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse([release('v1.4.36'), release('v1.4.36-rc.1')]))
|
||||
.mockResolvedValueOnce(jsonResponse([release('v1.4.36')]))
|
||||
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36-rc.1', body: 'notes' }))
|
||||
.mockResolvedValueOnce(jsonResponse({ tag_name: 'v1.4.36-rc.1', draft: true }))
|
||||
|
||||
@@ -200,10 +200,48 @@ describe('createDraftRelease', () => {
|
||||
expect(createBody.prerelease).toBe(true)
|
||||
})
|
||||
|
||||
it('regenerates notes for an existing draft release', async () => {
|
||||
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, body: 'notes' }))
|
||||
|
||||
await createDraftRelease({
|
||||
repo: 'stablyai/orca',
|
||||
tag: 'v1.4.36',
|
||||
token: 'token',
|
||||
fetchImpl,
|
||||
log: vi.fn()
|
||||
})
|
||||
|
||||
expect(fetchImpl).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'https://api.github.com/repos/stablyai/orca/releases/42',
|
||||
expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ body: 'notes' }) })
|
||||
)
|
||||
})
|
||||
|
||||
it('preserves notes on an existing published release', async () => {
|
||||
const fetchImpl = vi.fn().mockResolvedValueOnce(jsonResponse([release('v1.4.36', { id: 42 })]))
|
||||
|
||||
await createDraftRelease({
|
||||
repo: 'stablyai/orca',
|
||||
tag: 'v1.4.36',
|
||||
token: 'token',
|
||||
fetchImpl,
|
||||
log: vi.fn()
|
||||
})
|
||||
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('omits previous_tag_name for the first desktop release so notes fall back to the GitHub default', async () => {
|
||||
const fetchImpl = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse([release('v1.4.36'), release('mobile-v0.0.12')]))
|
||||
.mockResolvedValueOnce(jsonResponse([release('mobile-v0.0.12')]))
|
||||
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36', body: 'notes' }))
|
||||
.mockResolvedValueOnce(jsonResponse({ tag_name: 'v1.4.36', draft: true }))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user