Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: do not mark github release as latest if higher version exists #69

Merged
merged 2 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/commands/__test__/publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ it('publishes the next minor version', async () => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -116,6 +117,7 @@ it('releases a new version after an existing version', async () => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -216,6 +218,7 @@ it('comments on relevant github issues', async () => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -402,6 +405,7 @@ it('streams the release script stdout to the main process', async () => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -462,6 +466,7 @@ it('streams the release script stderr to the main process', async () => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -527,6 +532,7 @@ it('only pushes the newly created release tag to the remote', async () => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -574,6 +580,7 @@ it('treats breaking changes as minor versions when "prerelease" is set to true',
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -650,6 +657,7 @@ it('treats minor bumps as minor versions when "prerelease" is set to true', asyn
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down
8 changes: 7 additions & 1 deletion src/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ export class Publish extends Command<PublishArgv> {
),
)

// Get the latest release.
/**
* Get the latest release.
* @note This refers to the latest release tag at the current
* state of the branch. Since Release doesn't do branch analysis,
* this doesn't guarantee the latest release in general
* (consider backport releases where you checkout an old SHA).
*/
const tags = await getTags()
const latestRelease = await getLatestRelease(tags)

Expand Down
78 changes: 78 additions & 0 deletions src/utils/github/__test__/createGitHubRelease.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { rest } from 'msw'
import { DeferredPromise } from '@open-draft/deferred-promise'
import { testEnvironment } from '../../../../test/env'
import { mockRepo } from '../../../../test/fixtures'
import type { GitHubRelease } from '../getGitHubRelease'
import { createGitHubRelease } from '../createGitHubRelease'

const { setup, reset, cleanup, api } = testEnvironment({
fileSystemPath: 'create-github-release',
})

beforeAll(async () => {
await setup()
})

afterEach(async () => {
await reset()
})

afterAll(async () => {
await cleanup()
})

it('marks the release as non-latest if there is a higher version released on GitHub', async () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The confirming test suite.

const repo = mockRepo()
const requestBodyPromise = new DeferredPromise()
api.use(
rest.get<never, never, GitHubRelease>(
`https://api.github.com/repos/:owner/:name/releases/latest`,
(req, res, ctx) => {
return res(
// Set the latest GitHub release as v2.0.0.
ctx.json({
tag_name: 'v2.0.0',
html_url: '/v2.0.0',
}),
)
},
),
rest.post<never, never, GitHubRelease>(
`https://api.github.com/repos/:owner/:name/releases`,
(req, res, ctx) => {
requestBodyPromise.resolve(req.json())
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.1.1',
html_url: '/v1.1.1',
}),
)
},
),
)

// Try to release a backport version for v1.0.0.
const notes = '# Release notes'
const githubRelease = await createGitHubRelease(
{
repo,
nextRelease: {
version: '1.1.1',
tag: 'v1.1.1',
publishedAt: new Date(),
},
},
notes,
)
expect(githubRelease).toHaveProperty('html_url', '/v1.1.1')

const requestBody = await requestBodyPromise
expect(requestBody).toEqual({
tag_name: 'v1.1.1',
name: 'v1.1.1',
body: notes,
// Must set "false" as the value of the "make_latest" property.
make_latest: 'false',
})
})
2 changes: 1 addition & 1 deletion src/utils/github/__test__/getCommitAuthors.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { graphql } from 'msw'
import { getCommitAuthors } from '../getCommitAuthors'
import { log } from '../../../logger'
import { mockCommit } from '../../../../test/fixtures'
import { parseCommits } from '../../git/parseCommits'
import { testEnvironment } from '../../../../test/env'
import { graphql } from 'msw'

const { setup, reset, cleanup, api } = testEnvironment({
fileSystemPath: 'get-commit-authors',
Expand Down
20 changes: 19 additions & 1 deletion src/utils/github/createGitHubRelease.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import fetch from 'node-fetch'
import { format } from 'outvariant'
import { lt } from 'semver'
import type { ReleaseContext } from '../createContext'
import type { GitHubRelease } from './getGitHubRelease'
import { getGitHubRelease, type GitHubRelease } from './getGitHubRelease'
import { log } from '../../logger'

/**
* Create a new GitHub release with the given release notes.
* This is only called if there's no existing GitHub release
* for the next release tag.
* @return {string} The URL of the newly created release.
*/
export async function createGitHubRelease(
Expand All @@ -22,6 +25,20 @@ export async function createGitHubRelease(
),
)

// Determine if the next release should be marked as the
// latest release on GitHub. For that, fetch whichever latest
// release exists on GitHub and see if its version is larger
// than the version we are releasing right now.
const latestGitHubRelease = await getGitHubRelease('latest')
const shouldMarkAsLatest = latestGitHubRelease
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual fix.

? lt(latestGitHubRelease.tag_name || '0.0.0', context.nextRelease.tag)
: // Undefined is fine, it means GitHub will use its default
// value for the "make_latest" property in the API.
undefined

/**
* @see https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release
*/
const response = await fetch(
`https://api.github.com/repos/${repo.owner}/${repo.name}/releases`,
{
Expand All @@ -35,6 +52,7 @@ export async function createGitHubRelease(
tag_name: context.nextRelease.tag,
name: context.nextRelease.tag,
body: notes,
make_latest: shouldMarkAsLatest?.toString(),
}),
},
)
Expand Down
7 changes: 5 additions & 2 deletions src/utils/github/getGitHubRelease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import fetch from 'node-fetch'
import { getInfo } from '../git/getInfo'

export interface GitHubRelease {
tag_name: string
html_url: string
}

export async function getGitHubRelease(
tag: string,
tag: string | ('latest' & {}),
): Promise<GitHubRelease | undefined> {
const repo = await getInfo()

const response = await fetch(
`https://api.github.com/repos/${repo.owner}/${repo.name}/releases/tags/${tag}`,
tag === 'latest'
? `https://api.github.com/repos/${repo.owner}/${repo.name}/releases/latest`
: `https://api.github.com/repos/${repo.owner}/${repo.name}/releases/tags/${tag}`,
{
headers: {
Accept: 'application/json',
Expand Down
4 changes: 3 additions & 1 deletion test/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const api = setupServer(
)

beforeAll(() => {
api.listen()
api.listen({
onUnhandledRequest: 'error',
})
})

afterEach(() => {
Expand Down
Loading