From d478844e2e6418849abe18be6a6b5a86ddf38c05 Mon Sep 17 00:00:00 2001 From: Eliamar Tani Date: Thu, 21 Mar 2024 13:46:47 +0000 Subject: [PATCH] feat: add support for Github Enterprise, fix #45 (#46) --- src/cli.ts | 2 +- src/config.ts | 4 +++- src/git.ts | 6 ++++-- src/github.ts | 10 +++++----- src/markdown.ts | 12 ++++++------ src/types.ts | 10 ++++++++++ test/git.test.ts | 46 ++++++++++++++++++++++++++++++++++++++++++---- 7 files changed, 71 insertions(+), 19 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index e1ad517..a2ea55b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -38,7 +38,7 @@ cli console.log(dim(`changelo${bold('github')} `) + dim(`v${version}`)) const { config, md, commits } = await generate(args as any) - webUrl = `https://github.com/${config.repo}/releases/new?title=${encodeURIComponent(String(config.name || config.to))}&body=${encodeURIComponent(String(md))}&tag=${encodeURIComponent(String(config.to))}&prerelease=${config.prerelease}` + webUrl = `https://${config.baseUrl}/${config.repo}/releases/new?title=${encodeURIComponent(String(config.name || config.to))}&body=${encodeURIComponent(String(md))}&tag=${encodeURIComponent(String(config.to))}&prerelease=${config.prerelease}` console.log(cyan(config.from) + dim(' -> ') + blue(config.to) + dim(` (${commits.length} commits)`)) console.log(dim('--------------')) diff --git a/src/config.ts b/src/config.ts index 7ee7e94..d5b9ad3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -29,10 +29,12 @@ export async function resolveConfig(options: ChangelogOptions) { packageJson: 'changelogithub', }).then(r => r.config || defaultConfig) + config.baseUrl = config.baseUrl ?? 'github.com' + config.baseUrlApi = config.baseUrlApi ?? 'api.github.com' config.to = config.to || await getCurrentGitBranch() config.from = config.from || await getLastMatchingTag(config.to) || await getFirstGitCommit() // @ts-expect-error backward compatibility - config.repo = config.repo || config.github || await getGitHubRepo() + config.repo = config.repo || config.github || await getGitHubRepo(config.baseUrl) config.prerelease = config.prerelease ?? isPrerelease(config.to) if (typeof config.repo !== 'string') diff --git a/src/git.ts b/src/git.ts index d98dc9e..b24b550 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,6 +1,8 @@ -export async function getGitHubRepo() { +export async function getGitHubRepo(baseUrl: string) { const url = await execCommand('git', ['config', '--get', 'remote.origin.url']) - const match = url.match(/github\.com[\/:]([\w\d._-]+?)\/([\w\d._-]+?)(\.git)?$/i) + const escapedBaseUrl = baseUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const regex = new RegExp(`${escapedBaseUrl}[\/:]([\\w\\d._-]+?)\\/([\\w\\d._-]+?)(\\.git)?$`, 'i') + const match = regex.exec(url) if (!match) throw new Error(`Can not parse GitHub repo from url ${url}`) return `${match[1]}/${match[2]}` diff --git a/src/github.ts b/src/github.ts index b64ba0c..a3422fd 100644 --- a/src/github.ts +++ b/src/github.ts @@ -9,11 +9,11 @@ export async function sendRelease( content: string, ) { const headers = getHeaders(options) - let url = `https://api.github.com/repos/${options.repo}/releases` + let url = `https://${options.baseUrlApi}/repos/${options.repo}/releases` let method = 'POST' try { - const exists = await $fetch(`https://api.github.com/repos/${options.repo}/releases/tags/${options.to}`, { + const exists = await $fetch(`https://${options.baseUrlApi}/repos/${options.repo}/releases/tags/${options.to}`, { headers, }) if (exists.url) { @@ -59,7 +59,7 @@ export async function resolveAuthorInfo(options: ChangelogOptions, info: AuthorI return info try { - const data = await $fetch(`https://api.github.com/search/users?q=${encodeURIComponent(info.email)}`, { + const data = await $fetch(`https://${options.baseUrlApi}/search/users?q=${encodeURIComponent(info.email)}`, { headers: getHeaders(options), }) info.login = data.items[0].login @@ -71,7 +71,7 @@ export async function resolveAuthorInfo(options: ChangelogOptions, info: AuthorI if (info.commits.length) { try { - const data = await $fetch(`https://api.github.com/repos/${options.repo}/commits/${info.commits[0]}`, { + const data = await $fetch(`https://${options.baseUrlApi}/repos/${options.repo}/commits/${info.commits[0]}`, { headers: getHeaders(options), }) info.login = data.author.login @@ -128,7 +128,7 @@ export async function resolveAuthors(commits: Commit[], options: ChangelogOption export async function hasTagOnGitHub(tag: string, options: ChangelogOptions) { try { - await $fetch(`https://api.github.com/repos/${options.repo}/git/ref/tags/${tag}`, { + await $fetch(`https://${options.baseUrlApi}/repos/${options.repo}/git/ref/tags/${tag}`, { headers: getHeaders(options), }) return true diff --git a/src/markdown.ts b/src/markdown.ts index 5eaf47e..27eca08 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -5,7 +5,7 @@ import type { Commit, ResolvedChangelogOptions } from './types' const emojisRE = /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g -function formatReferences(references: Reference[], github: string, type: 'issues' | 'hash'): string { +function formatReferences(references: Reference[], baseUrl: string, github: string, type: 'issues' | 'hash'): string { const refs = references .filter((i) => { if (type === 'issues') @@ -16,8 +16,8 @@ function formatReferences(references: Reference[], github: string, type: 'issues if (!github) return ref.value if (ref.type === 'pull-request' || ref.type === 'issue') - return `https://github.com/${github}/issues/${ref.value.slice(1)}` - return `[(${ref.value.slice(0, 5)})](https://github.com/${github}/commit/${ref.value})` + return `https://${baseUrl}/${github}/issues/${ref.value.slice(1)}` + return `[(${ref.value.slice(0, 5)})](https://${baseUrl}/${github}/commit/${ref.value})` }) const referencesString = join(refs).trim() @@ -28,8 +28,8 @@ function formatReferences(references: Reference[], github: string, type: 'issues } function formatLine(commit: Commit, options: ResolvedChangelogOptions) { - const prRefs = formatReferences(commit.references, options.repo as string, 'issues') - const hashRefs = formatReferences(commit.references, options.repo as string, 'hash') + const prRefs = formatReferences(commit.references, options.baseUrl, options.repo as string, 'issues') + const hashRefs = formatReferences(commit.references, options.baseUrl, options.repo as string, 'hash') let authors = join([...new Set(commit.resolvedAuthors?.map(i => i.login ? `@${i.login}` : `**${i.name}**`))])?.trim() if (authors) @@ -111,7 +111,7 @@ export function generateMarkdown(commits: Commit[], options: ResolvedChangelogOp if (!lines.length) lines.push('*No significant changes*') - const url = `https://github.com/${options.repo}/compare/${options.from}...${options.to}` + const url = `https://${options.baseUrl}/${options.repo}/compare/${options.from}...${options.to}` lines.push('', `#####     [View changes on GitHub](${url})`) diff --git a/src/types.ts b/src/types.ts index 8fb221a..d2c5e48 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,6 +64,16 @@ export interface ChangelogOptions extends Partial { * @default true */ emoji?: boolean + /** + * Github base url + * @default github.com + */ + baseUrl?: string + /** + * Github base API url + * @default api.github.com + */ + baseUrlApi?: string } export type ResolvedChangelogOptions = Required diff --git a/test/git.test.ts b/test/git.test.ts index 4b1475f..2f1eee3 100644 --- a/test/git.test.ts +++ b/test/git.test.ts @@ -1,10 +1,11 @@ import { expect, it } from 'vitest' -import { generate } from '../src' +import { generate, getGitHubRepo } from '../src' -it('parse', async () => { - const COMMIT_FROM = '19cf4f84f16f1a8e1e7032bbef550c382938649d' - const COMMIT_TO = '49b0222e8d60b7f299941def7511cee0460a8149' +const COMMIT_FROM = '19cf4f84f16f1a8e1e7032bbef550c382938649d' +const COMMIT_TO = '49b0222e8d60b7f299941def7511cee0460a8149' +const regexToFindAllUrls = /https:\/\/[^\s]*/g +it('parse', async () => { const { config, md } = await generate({ from: COMMIT_FROM, to: COMMIT_TO, @@ -12,6 +13,8 @@ it('parse', async () => { expect(config).toMatchInlineSnapshot(` { + "baseUrl": "github.com", + "baseUrlApi": "api.github.com", "capitalize": true, "contributors": true, "from": "19cf4f84f16f1a8e1e7032bbef550c382938649d", @@ -65,3 +68,38 @@ it('parse', async () => { ##### [View changes on GitHub](https://github.com/antfu/changelogithub/compare/19cf4f84f16f1a8e1e7032bbef550c382938649d...49b0222e8d60b7f299941def7511cee0460a8149)" `) }) + +it.each([ + { baseUrl: undefined, baseUrlApi: undefined, repo: undefined }, + { baseUrl: 'test.github.com', baseUrlApi: 'api.test.github.com', repo: 'user/changelogithub' }, +])('should generate config while baseUrl is set to $baseUrl', async (proposedConfig) => { + const { config, md } = await generate({ + ...proposedConfig, + from: COMMIT_FROM, + to: COMMIT_TO, + }) + + if (proposedConfig.baseUrl) { + expect(config).toEqual(expect.objectContaining(proposedConfig)) + } + else { + expect(config).toEqual(expect.objectContaining({ + baseUrl: 'github.com', + baseUrlApi: 'api.github.com', + })) + } + + const urlsToGithub = md.match(regexToFindAllUrls) + expect(urlsToGithub?.every(url => url.startsWith(`https://${config.baseUrl}`))).toBe(true) +}) + +it('should match with current github repo', async () => { + const repo = await getGitHubRepo('github.com') + expect(repo).toContain('/changelogithub') +}) + +it('should throw error when baseUrl is different from git repository', () => { + expect(async () => { + await getGitHubRepo('custom.git.com') + }).rejects.toThrow('Can not parse GitHub repo from url') +})