Skip to content

Commit

Permalink
fix: do not mark github release as latest if higher version exists (#69)
Browse files Browse the repository at this point in the history
* fix: do not mark github release as latest if higher version exists

* chore: adjust tests for a new github request
  • Loading branch information
kettanaito authored Mar 15, 2024
1 parent 95377fd commit 3cfd8c6
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 8 deletions.
24 changes: 22 additions & 2 deletions src/commands/__test__/notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ let gitHubReleaseHandler: jest.Mock = jest.fn<
)
})

const githubLatestReleaseHandler = rest.get<never, never, GitHubRelease>(
`https://api.github.com/repos/:owner/:name/releases/latest`,
(req, res, ctx) => {
return res(ctx.status(404))
},
)

beforeAll(async () => {
await setup()
})
Expand All @@ -47,6 +54,7 @@ it('creates a GitHub release for a past release', async () => {
await createRepository('past-release')

api.use(
githubLatestReleaseHandler,
rest.get<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases/tags/:tag',
(req, res, ctx) => {
Expand Down Expand Up @@ -99,7 +107,12 @@ it('creates a GitHub release for a past release', async () => {

const notes = new Notes(
{
script: 'exit 0',
profiles: [
{
name: 'latest',
use: 'exit 0',
},
],
},
{
_: ['', '0.2.0'],
Expand Down Expand Up @@ -139,11 +152,13 @@ it('skips creating a GitHub release if the given release already exists', async
await createRepository('skip-if-exists')

api.use(
githubLatestReleaseHandler,
rest.get<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases/tags/:tag',
(req, res, ctx) => {
return res(
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand All @@ -153,7 +168,12 @@ it('skips creating a GitHub release if the given release already exists', async

const notes = new Notes(
{
script: 'exit 0',
profiles: [
{
name: 'latest',
use: 'exit 0',
},
],
},
{
_: ['', '1.0.0'],
Expand Down
24 changes: 24 additions & 0 deletions src/commands/__test__/publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,28 @@ afterAll(async () => {
await cleanup()
})

const githubLatestReleaseHandler = rest.get<never, never, GitHubRelease>(
`https://api.github.com/repos/:owner/:name/releases/latest`,
(req, res, ctx) => {
return res(ctx.status(404))
},
)

it('publishes the next minor version', async () => {
const repo = await createRepository('version-next-minor')

api.use(
graphql.query('GetCommitAuthors', (req, res, ctx) => {
return res(ctx.data({}))
}),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
(req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -110,12 +119,14 @@ it('releases a new version after an existing version', async () => {
graphql.query('GetCommitAuthors', (req, res, ctx) => {
return res(ctx.data({}))
}),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
(req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -210,12 +221,14 @@ it('comments on relevant github issues', async () => {
}),
)
}),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
(req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -288,6 +301,7 @@ it('supports dry-run mode', async () => {

api.use(
graphql.query('GetCommitAuthors', getReleaseContributorsResolver),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
createGitHubReleaseResolver,
Expand Down Expand Up @@ -396,12 +410,14 @@ it('streams the release script stdout to the main process', async () => {
graphql.query('GetCommitAuthors', (req, res, ctx) => {
return res(ctx.data({}))
}),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
(req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -456,12 +472,14 @@ it('streams the release script stderr to the main process', async () => {
graphql.query('GetCommitAuthors', (req, res, ctx) => {
return res(ctx.data({}))
}),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
(req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -521,12 +539,14 @@ it('only pushes the newly created release tag to the remote', async () => {
graphql.query('GetCommitAuthors', (req, res, ctx) => {
return res(ctx.data({}))
}),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
(req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -568,12 +588,14 @@ it('treats breaking changes as minor versions when "prerelease" is set to true',
graphql.query('GetCommitAuthors', (req, res, ctx) => {
return res(ctx.data({}))
}),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
(req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
tag_name: 'v1.0.0',
html_url: '/releases/1',
}),
)
Expand Down Expand Up @@ -644,12 +666,14 @@ it('treats minor bumps as minor versions when "prerelease" is set to true', asyn
graphql.query('GetCommitAuthors', (req, res, ctx) => {
return res(ctx.data({}))
}),
githubLatestReleaseHandler,
rest.post<never, never, GitHubRelease>(
'https://api.github.com/repos/:owner/:repo/releases',
(req, res, ctx) => {
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 () => {
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
26 changes: 25 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,26 @@ 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').catch(
(error) => {
log.error(`Failed to fetch the latest GitHub release:`, error)
// We aren't interested in the GET endpoint errors in this context.
return undefined
},
)
const shouldMarkAsLatest = latestGitHubRelease
? 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 +58,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

0 comments on commit 3cfd8c6

Please sign in to comment.