From 38d68ac6d37040604f9d7206962f4e607b0edc41 Mon Sep 17 00:00:00 2001 From: Samuel Kopp <62482066+boywithkeyboard@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:04:13 +0100 Subject: [PATCH] BREAKING: rewrite action --- src/action.ts | 318 ++++++-------------------------------------------- src/config.ts | 25 ++++ src/index.ts | 12 ++ 3 files changed, 70 insertions(+), 285 deletions(-) create mode 100644 src/config.ts create mode 100644 src/index.ts diff --git a/src/action.ts b/src/action.ts index bdf23df..1c66122 100644 --- a/src/action.ts +++ b/src/action.ts @@ -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() - - 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 = - /(?[a-z\d](?:[a-z\d-]{0,37}[a-z\d])?)\/(?[\w.-]{1,100}))?(?[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) } diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..08d20a0 --- /dev/null +++ b/src/config.ts @@ -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') === '' ? null : getInput('tag') + } + + return CONFIG +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ba2be96 --- /dev/null +++ b/src/index.ts @@ -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.' + ) +}