-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9c3dab2
commit 38d68ac
Showing
3 changed files
with
70 additions
and
285 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,306 +1,54 @@ | ||
import { getBooleanInput, getInput, setFailed, setOutput } from '@actions/core' | ||
import { context, getOctokit } from '@actions/github' | ||
import indentString from 'indent-string' | ||
import semver from 'semver' | ||
import { config } from './config' | ||
import { existsSync, readFileSync } from 'node:fs' | ||
import { setOutput } from '@actions/core' | ||
|
||
async function action() { | ||
const config = { | ||
token: getInput('token'), | ||
kind: getInput('kind', { required: true }), | ||
draft: getBooleanInput('draft'), | ||
includeAuthor: getBooleanInput('include_author'), | ||
includeDescription: getBooleanInput('include_description'), | ||
prereleasePrefix: getInput('prerelease_prefix'), | ||
mentionContributors: getBooleanInput('mention_contributors'), | ||
} | ||
|
||
const { rest } = getOctokit(config.token) | ||
|
||
// const fetchChangelog = async () => { | ||
// try { | ||
// const { data } = await rest.repos.getContent({ | ||
// ...context.repo, | ||
// path: 'changelog.md', | ||
// }) | ||
|
||
// // @ts-ignore: | ||
// return [data.sha, await readFile('changelog.md', { encoding: 'utf-8' })] | ||
// } catch (err) { | ||
// return [null, ''] | ||
// } | ||
// } | ||
|
||
const d = new Date() | ||
|
||
d.setDate(d.getDate() - 365) | ||
|
||
let latestRelease: { | ||
tag_name: string | ||
created_at: string | ||
[key: string]: unknown | ||
} = { | ||
tag_name: 'v0.0.0', | ||
created_at: d.toString(), | ||
} | ||
export async function action() { | ||
const { rest } = getOctokit(config().token) | ||
|
||
try { | ||
const result = await rest.repos.listReleases({ | ||
...context.repo, | ||
}) | ||
let tag = config().tag | ||
|
||
if ( | ||
result.status === 200 && result.data instanceof Array && | ||
result.data.length > 0 | ||
) { | ||
latestRelease = result.data[0] | ||
if (!tag) { | ||
if (/^refs\/tags\/[^\/]+$/.test(context.ref) === false) { | ||
throw new Error('Invalid context ref: ' + context.ref) | ||
} | ||
} catch (err) {} | ||
|
||
let currentVersion = latestRelease?.tag_name ?? 'v0.0.0' | ||
let nextVersion = 'v' | ||
|
||
if (config.kind === 'prepatch') { | ||
nextVersion += semver.prerelease(currentVersion) | ||
? semver.inc( | ||
currentVersion, | ||
'prerelease', | ||
undefined, | ||
config.prereleasePrefix, | ||
) | ||
: semver.inc( | ||
currentVersion, | ||
'prepatch', | ||
undefined, | ||
config.prereleasePrefix, | ||
) | ||
} else if (config.kind === 'patch') { | ||
nextVersion += semver.inc(currentVersion, 'patch') | ||
} else if (config.kind === 'preminor') { | ||
nextVersion += semver.prerelease(currentVersion) | ||
? semver.inc( | ||
currentVersion, | ||
'prerelease', | ||
undefined, | ||
config.prereleasePrefix, | ||
) | ||
: semver.inc( | ||
currentVersion, | ||
'preminor', | ||
undefined, | ||
config.prereleasePrefix, | ||
) | ||
} else if (config.kind === 'minor') { | ||
nextVersion += semver.inc(currentVersion, 'minor') | ||
} else if (config.kind === 'premajor') { | ||
nextVersion += semver.prerelease(currentVersion) | ||
? semver.inc( | ||
currentVersion, | ||
'prerelease', | ||
undefined, | ||
config.prereleasePrefix, | ||
) | ||
: semver.inc( | ||
currentVersion, | ||
'premajor', | ||
undefined, | ||
config.prereleasePrefix, | ||
) | ||
} else if (config.kind === 'major') { | ||
nextVersion += semver.inc(currentVersion, 'major') | ||
} else { | ||
throw new Error('Invalid kind.') | ||
tag = context.ref.replace('refs/tags/', '') | ||
} | ||
|
||
console.info( | ||
`previous release - ${latestRelease?.tag_name} (${latestRelease?.created_at})`, | ||
) | ||
|
||
let { data } = await rest.pulls.list({ | ||
...context.repo, | ||
per_page: 100, | ||
sort: 'updated', | ||
state: 'closed', | ||
direction: 'desc', | ||
}) | ||
|
||
data = [ | ||
...data, | ||
...(await rest.pulls.list({ | ||
...context.repo, | ||
per_page: 100, | ||
sort: 'updated', | ||
state: 'closed', | ||
direction: 'desc', | ||
page: 2, | ||
})).data, | ||
] | ||
|
||
const year = new Date().getUTCFullYear(), | ||
month = new Date().getUTCMonth() + 1, | ||
day = new Date().getUTCDate() | ||
|
||
// let changelogBody = | ||
// `## [${tag}](https://github.com/${context.repo.owner}/${context.repo.repo}/releases/tag/${tag})\n`, | ||
let releaseBody = `### ${nextVersion} / ${year}.${ | ||
month < 10 ? `0${month}` : month | ||
}.${day < 10 ? `0${day}` : day}\n` | ||
|
||
data.sort((a, b) => { | ||
const x = a.title.toLowerCase(), | ||
y = b.title.toLowerCase() | ||
|
||
if (x < y) { | ||
return -1 | ||
} | ||
|
||
if (x > y) { | ||
return 1 | ||
} | ||
|
||
return 0 | ||
}) | ||
|
||
const contributors = new Set<string>() | ||
|
||
for (const { user, merged_at, number, body, merge_commit_sha } of data) { | ||
if ( | ||
merged_at === null || user?.type === 'Bot' || merge_commit_sha === null | ||
) { | ||
continue | ||
} | ||
|
||
if ( | ||
latestRelease && new Date(latestRelease.created_at).getTime() >= | ||
new Date(merged_at).getTime() | ||
) { | ||
continue | ||
} | ||
|
||
const c = await rest.repos.getCommit({ | ||
...context.repo, | ||
ref: merge_commit_sha, | ||
}) | ||
let changelogPath: string | undefined | ||
|
||
if (c.status !== 200) { | ||
continue | ||
} | ||
|
||
if ( | ||
latestRelease && c.data.commit.committer?.date && | ||
new Date(c.data.commit.committer?.date).getTime() <= | ||
new Date(latestRelease.created_at).getTime() | ||
) { | ||
continue | ||
} | ||
|
||
const linkifyReferences = (commit: string) => { | ||
const issueRegex = | ||
/(?<!\w)(?:(?<organization>[a-z\d](?:[a-z\d-]{0,37}[a-z\d])?)\/(?<repository>[\w.-]{1,100}))?(?<!(?:\/\.{1,2}))#(?<issueNumber>[1-9]\d{0,9})\b/g | ||
|
||
const matches = commit.match(issueRegex) | ||
|
||
if (!matches) { | ||
return commit | ||
} | ||
|
||
for (const m of matches) { | ||
commit = commit.replace( | ||
`(${m})`, | ||
`([${m}](https://github.com/${context.repo.owner}/${context.repo.repo}/pull/${ | ||
m.slice(1) | ||
}))`, | ||
) | ||
} | ||
|
||
return commit | ||
} | ||
|
||
const i = c.data.commit.message.indexOf(')\n\n') | ||
let title = c.data.commit.message.substring(0, i > 0 ? i + 1 : undefined) | ||
|
||
const comments = (await rest.issues.listComments({ | ||
...context.repo, | ||
issue_number: number, | ||
})).data | ||
|
||
if ( | ||
comments.length > 0 && | ||
comments.some((c) => | ||
c.body !== undefined && c.body === '?log ignore' && | ||
(c.author_association === 'COLLABORATOR' || | ||
c.author_association === 'MEMBER' || c.author_association === 'OWNER') | ||
) | ||
) { | ||
continue | ||
} | ||
|
||
title = linkifyReferences(title) | ||
title = title.replace(': ', ': **').replace(' ([#', '** ([#') | ||
if (existsSync('./changelog.md')) { | ||
changelogPath = './changelog.md' | ||
} else if (existsSync('./CHANGELOG.md')) { | ||
changelogPath = './CHANGELOG.md' | ||
} | ||
|
||
// changelogBody += `\n* ${linkifyReferences(title)}` | ||
if (!changelogPath) { | ||
throw new Error('No changelog found.') | ||
} | ||
|
||
releaseBody += `\n* ${title}` | ||
let changelog = readFileSync(changelogPath, { encoding: 'utf-8' }) | ||
|
||
if (config.includeAuthor && user?.login) { | ||
// changelogBody += user?.login | ||
// ? ` by [@${user?.login}](https://github.com/${user?.login})` | ||
// : '' | ||
const startIndex = changelog.indexOf(`## [${tag}]`) + `## [${tag}](https://github.com/${context.repo.owner}/${context.repo.repo}/releases/tag/${tag})\n\n`.length | ||
|
||
contributors.add(`, @${user.login}`) | ||
releaseBody += ` by @${user.login}` | ||
} | ||
|
||
if (config.includeDescription && body !== null && body.length > 0) { | ||
// changelogBody += `\n\n${indentString(body, 2)}\n` | ||
releaseBody += `\n\n${indentString(body, 2)}\n` | ||
} | ||
} | ||
changelog = changelog.substring(startIndex) | ||
|
||
if (config.mentionContributors) { | ||
const arr = [...contributors.values()] | ||
const endIndex = changelog.indexOf('\n\n## [') | ||
|
||
arr[0] = arr[0].replace(', ', '') | ||
arr[arr.length - 1] = arr.length > 2 | ||
? arr[arr.length - 1].replace(', ', ', & ') | ||
: arr[arr.length - 1].replace(', ', ' & ') | ||
changelog = changelog.substring(0, endIndex < 0 ? undefined : endIndex) | ||
|
||
releaseBody += `\n\nContributed by ${arr.join('')}` | ||
} | ||
|
||
const { data: release } = await rest.repos.createRelease({ | ||
const { data } = await rest.repos.createRelease({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
tag_name: nextVersion, | ||
name: nextVersion, | ||
body: releaseBody, | ||
draft: config.draft, | ||
prerelease: config.kind.startsWith('pre'), | ||
target_commitish: context.sha, | ||
tag_name: tag, | ||
name: tag, | ||
body: changelog, | ||
draft: config().draft, | ||
prerelease: config().prerelease | ||
}) | ||
|
||
// const [sha, content] = await fetchChangelog() | ||
|
||
// await rest.repos.createOrUpdateFileContents({ | ||
// ...context.repo, | ||
// path: 'changelog.md', | ||
// content: Buffer.from( | ||
// `${changelogBody}${content === '' ? '\n' : `\n\n${content}`}`, | ||
// ).toString('base64'), | ||
// message: getInput('commit_message').replace('{tag}', tag), | ||
// ...(sha !== null && { sha }), | ||
// }) | ||
|
||
setOutput('release_id', release.id) | ||
setOutput('tag_name', nextVersion) | ||
setOutput('created_at', release.created_at) | ||
// setOutput('release_body', releaseBody) | ||
// setOutput('changelog_body', changelogBody) | ||
} | ||
|
||
try { | ||
action() | ||
} catch (err) { | ||
setFailed( | ||
err instanceof Error ? err.message : 'Something unexpected happened.', | ||
) | ||
setOutput('release_id', data.id) | ||
setOutput('tag_name', data.tag_name) | ||
setOutput('created_at', data.created_at) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { getBooleanInput, getInput } from '@actions/core' | ||
|
||
type Config = { | ||
token: string | ||
draft: boolean | ||
prerelease: boolean | ||
tag: string | null | ||
} | ||
|
||
let CONFIG: Config | undefined | ||
|
||
export function config() { | ||
if (CONFIG) { | ||
return CONFIG | ||
} | ||
|
||
CONFIG = { | ||
token: getInput('token'), | ||
draft: getBooleanInput('draft'), | ||
prerelease: getBooleanInput('prerelease'), | ||
tag: getInput('tag') === '<tag>' ? null : getInput('tag') | ||
} | ||
|
||
return CONFIG | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { setFailed } from '@actions/core' | ||
import { action } from './action' | ||
|
||
try { | ||
action() | ||
} catch (err) { | ||
setFailed( | ||
err instanceof Error | ||
? err.message | ||
: 'Something unexpected happened.' | ||
) | ||
} |