@ossjs/release
Version:
Minimalistic, opinionated, and predictable release automation tool.
148 lines (123 loc) • 4.04 kB
text/typescript
import type { BuilderCallback } from 'yargs'
import { format, invariant } from 'outvariant'
import { Command } from '#/src/Command.js'
import { getTag, type TagPointer } from '#/src/utils/git/get-tag.js'
import { getLatestRelease } from '#/src/utils/git/get-latest-release.js'
import { getTags } from '#/src/utils/git/get-tags.js'
import { getCommit } from '#/src/utils/git/get-commit.js'
import { getInfo } from '#/src/utils/git/get-info.js'
import { execAsync } from '#/src/utils/exec-async.js'
import { demandGitHubToken } from '#/src/utils/env.js'
interface Argv {
_: [path: string, tag?: string]
}
export enum ReleaseStatus {
/**
* Release is public and available for everybody to see
* on the GitHub releases page.
*/
Public = 'public',
/**
* Release is pushed to GitHub but is marked as draft.
*/
Draft = 'draft',
/**
* Release is local, not present on GitHub.
*/
Unpublished = 'unpublished',
}
export class Show extends Command<Argv> {
static command = 'show'
static description = 'Show release info'
static builder: BuilderCallback<{}, Argv> = (yargs) => {
return yargs
.usage('$0 show [tag]')
.example([
['$0 show', 'Show the latest release info'],
['$0 show 1.2.3', 'Show specific release tag info'],
])
.positional('tag', {
type: 'string',
description: 'Release tag',
demandOption: false,
})
}
public run = async () => {
await demandGitHubToken().catch((error) => {
this.log.error(error.message)
process.exit(1)
})
const [, tag] = this.argv._
const pointer = await this.getTagPointer(tag?.toString())
this.log.info(format('found tag "%s"!', pointer.tag))
const commit = await getCommit(pointer.hash)
invariant(
commit,
'Failed to retrieve release info for tag "%s": cannot find commit associated with the tag.',
tag,
)
// Print local Git info about the release commit.
const commitOut = await execAsync(`git log -1 ${commit.commit.long}`).then(
({ stdout }) => stdout,
)
this.log.info(commitOut)
// Print the remote GitHub info about the release.
const repo = await getInfo()
const releaseResponse = await fetch(
`https://api.github.com/repos/${repo.owner}/${repo.name}/releases/tags/${pointer.tag}`,
{
headers: {
Authorization: `token ${process.env.GITHUB_TOKEN}`,
},
},
)
const isPublishedRelease = releaseResponse.status === 200
const release = await releaseResponse.json()
const releaseStatus: ReleaseStatus = isPublishedRelease
? release.draft
? ReleaseStatus.Draft
: ReleaseStatus.Public
: ReleaseStatus.Unpublished
this.log.info(format('release status: %s', releaseStatus))
if (
releaseStatus === ReleaseStatus.Public ||
releaseStatus === ReleaseStatus.Draft
) {
this.log.info(format('release url: %s', release?.html_url))
}
if (!isPublishedRelease) {
this.log.warn(
format('release "%s" is not published to GitHub!', pointer.tag),
)
}
}
/**
* Returns tag pointer by the given tag name.
* If no tag name was given, looks up the latest release tag
* and returns its pointer.
*/
private async getTagPointer(tag?: string): Promise<TagPointer> {
if (tag) {
this.log.info(format('looking up explicit "%s" tag...', tag))
const pointer = await getTag(tag)
invariant(
pointer,
'Failed to retrieve release tag: tag "%s" does not exist.',
tag,
)
return pointer
}
this.log.info('looking up the latest release tag...')
const tags = await getTags()
invariant(
tags.length > 0,
'Failed to retrieve release tag: repository has no releases.',
)
const latestPointer = await getLatestRelease(tags)
invariant(
latestPointer,
'Failed to retrieve release tag: cannot retrieve releases.',
)
return latestPointer
}
}