Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { resolveURL } from 'ufo'
import * as v from 'valibot'
import { getBaseFileUrl } from '~~/server/utils/changelog/baseFileUrl'
import {
createBitbucketRepoInfo,
createForgejoRepoInfo,
createGiteaRepoInfo,
createGithubRepoInfo,
createGitLabRepoInfo,
createTangledInfo,
Expand Down Expand Up @@ -56,6 +58,7 @@ export default defineCachedEventHandler(
headers: {
'User-Agent': 'npmx.dev',
},
timeout: 15_000,
})
const markdown = v.parse(v.string(), data)
if (raw != undefined) {
Expand Down Expand Up @@ -114,5 +117,9 @@ function getRepoInfo(
return createGitLabRepoInfo(host ?? 'gitlab.com', owner, repo, path)
case 'tangled':
return createTangledInfo(owner, repo, path)
case 'gitea':
return createGiteaRepoInfo(host ?? 'gitea.com', owner, repo, path)
case 'bitbucket':
return createBitbucketRepoInfo(owner, repo, path)
}
}
41 changes: 40 additions & 1 deletion server/api/changelog/releases/[provider]/[owner]/[repo].get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@ import {
GithubReleaseCollectionSchama,
ForgejoReleaseCollectionSchema,
GitlabReleaseCollectionSchema,
GiteaReleaseCollectionSchema,
} from '~~/shared/schemas/changelog/release'
import { changelogRenderer } from '~~/server/utils/changelog/markdown'
import { createForgejoRepoInfo, createGithubRepoInfo } from '~~/server/utils/changelog/mdRepoInfo'
import {
createForgejoRepoInfo,
createGiteaRepoInfo,
createGithubRepoInfo,
} from '~~/server/utils/changelog/mdRepoInfo'
import { validateHostWithValibot } from '~~/server/utils/changelog/validateHost'

const TIMEOUT_TIME = 15_000

export default defineCachedEventHandler(
async event => {
const provider = getRouterParam(event, 'provider') as ProviderId
Expand All @@ -39,6 +46,8 @@ export default defineCachedEventHandler(
return await getReleasesFromForgejo(owner, repo, host)
case 'gitlab':
return await getReleasesFromGitlab(owner, repo, host)
case 'gitea':
return await getReleasesFromGitea(owner, repo, host)

default:
throw createError({
Expand Down Expand Up @@ -79,6 +88,7 @@ async function getReleasesFromGithub(owner: string, repo: string) {
'Accept': '*/*',
'User-Agent': 'npmx.dev',
},
timeout: TIMEOUT_TIME,
})

const { releases } = v.parse(GithubReleaseCollectionSchama, data)
Expand Down Expand Up @@ -107,6 +117,7 @@ async function getReleasesFromForgejo(owner: string, repo: string, host: string)
headers: {
'User-Agent': 'npmx.dev',
},
timeout: TIMEOUT_TIME,
})
const releases = v.parse(ForgejoReleaseCollectionSchema, data)

Expand Down Expand Up @@ -137,6 +148,7 @@ async function getReleasesFromGitlab(owner: string, repo: string, host: string)
headers: {
'User-Agent': 'npmx.dev',
},
timeout: TIMEOUT_TIME,
})

const releases = v.parse(GitlabReleaseCollectionSchema, data)
Expand All @@ -158,3 +170,30 @@ async function getReleasesFromGitlab(owner: string, repo: string, host: string)
} satisfies ReleaseData
})
}

async function getReleasesFromGitea(owner: string, repo: string, host: string) {
const data = await $fetch(`https://${host}/api/v1/repos/${owner}/${repo}/releases?draft=false`, {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
headers: {
'User-Agent': 'npmx.dev',
},
timeout: TIMEOUT_TIME,
})
const releases = v.parse(GiteaReleaseCollectionSchema, data)

const render = await changelogRenderer(createGiteaRepoInfo(host, owner, repo))

return releases.map(r => {
const { html, toc } = render(r.body, r.id)
return {
id: r.id,
html: html?.replace(/(?<!>)\n/g, '<br>') ?? null,
title: r.name || r.tag_name,
prerelease: r.prerelease,
toc,
link: r.html_url,
publishedAt: r.published_at,
draft: r.draft,
tag: r.tag_name,
} satisfies ReleaseData
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from '~~/shared/schemas/changelog/release'
import { validateHostWithValibot } from '~~/server/utils/changelog/validateHost'

const TIMEOUT_TIME = 15_000

export default defineCachedEventHandler(
async event => {
const provider = getRouterParam(event, 'provider') as ProviderId
Expand Down Expand Up @@ -84,8 +86,9 @@ async function getMarkdownFromGithub(owner: string, repo: string, tag: string, e
'User-Agent': 'npmx.dev',
},
ignoreResponseError: true,
timeout: TIMEOUT_TIME,
},
)
).catch(() => null)

const parsed = v.safeParse(v.object({ body: v.string() }), responseGithub)

Expand All @@ -101,6 +104,7 @@ async function getMarkdownFromGithub(owner: string, repo: string, tag: string, e
'Accept': '*/*',
'User-Agent': 'npmx.dev',
},
timeout: TIMEOUT_TIME,
})

const { releases } = v.parse(GithubReleaseCollectionSchama, responseUngh)
Expand Down Expand Up @@ -129,6 +133,7 @@ async function getMarkdownFromForgejo(
headers: {
'User-Agent': 'npmx.dev',
},
timeout: TIMEOUT_TIME,
})

const release = v.parse(ForgejoReleaseSchama, data)
Expand All @@ -150,6 +155,7 @@ async function getMarkdownFromGitlab(
headers: {
'User-Agent': 'npmx.dev',
},
timeout: TIMEOUT_TIME,
})

const release = v.parse(GitlabReleaseSchame, data)
Expand Down
15 changes: 14 additions & 1 deletion server/utils/changelog/baseFileUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ export function getBaseFileUrl(ref: RepoRef): RepoFileUrl | null {
raw: `https://codeberg.org/${ref.owner}/${ref.repo}/raw/branch/HEAD`,
}
case 'forgejo': {
const host = ref.host
return {
blob: `https://${ref.host}/${ref.owner}/${ref.repo}/src/branch/HEAD`,
raw: `https://${ref.host}/${ref.owner}/${ref.repo}/raw/branch/HEAD`,
}
}
case 'gitea': {
// although similar to forgejo, we keep these seperate because they're seperately maintained projects
const host = ref.host ?? 'gitea.com'
return {
blob: `https://${host}/${ref.owner}/${ref.repo}/src/branch/HEAD`,
raw: `https://${host}/${ref.owner}/${ref.repo}/raw/branch/HEAD`,
Expand All @@ -35,6 +42,12 @@ export function getBaseFileUrl(ref: RepoRef): RepoFileUrl | null {
raw: `https://tangled.org/${ref.owner}/${ref.repo}/raw/HEAD`,
}
}
case 'bitbucket': {
return {
blob: `https://bitbucket.org/${ref.owner}/${ref.repo}/src/HEAD`,
raw: `https://bitbucket.org/${ref.owner}/${ref.repo}/raw/HEAD`,
}
}
}
return null
}
69 changes: 69 additions & 0 deletions server/utils/changelog/detectChangelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
GithubReleaseSchama,
ForgejoReleaseSchama,
GitlabReleaseSchame,
GiteaReleaseSchema,
} from '~~/shared/schemas/changelog/release'
import { resolveURL } from 'ufo'
import * as v from 'valibot'
Expand Down Expand Up @@ -66,6 +67,9 @@ async function checkReleases(
case 'gitlab': {
return checkLatestGitlabRelease(ref, directory)
}
case 'gitea': {
return checkLatestGiteaRelease(ref, directory)
}
}
return [false, null]
}
Expand Down Expand Up @@ -340,3 +344,68 @@ async function checkLatestGitlabRelease(
}
return [false, null]
}

// gitea
async function checkLatestGiteaRelease(
ref: RepoRef,
directory?: string,
): Promise<SafeResult<ChangelogInfo | false>> {
try {
const host = ref.host ?? 'gitea.com'

const response = await $fetch(
`https://${host}/api/v1/repos/${ref.owner}/${ref.repo}/releases/latest`,
{
headers: {
'User-Agent': 'npmx.dev',
'accept': 'application/json',
},
},
)

const release = v.parse(GiteaReleaseSchema, response)

const matchedChangelog = release.body?.match(MD_REGEX)?.at(0)

// /src/branch/ can be similar to /blob/
if (!matchedChangelog || !matchedChangelog.includes('/src/branch/')) {
return [
{
type: 'release',
link: `https://${host}/${ref.owner}/${ref.repo}/releases`,
provider: ref.provider,
repo: `${ref.owner}/${ref.repo}`,
host: ref.host,
},
null,
]
}

const path = matchedChangelog.replace(/^.*\/src\/branch\/[^/]+\//i, '')
if (
directory &&
!(
path.startsWith(directory.endsWith('/') ? directory : `${directory}/`) ||
ROOT_ONLY_REGEX.test(path)
)
) {
return [false, null] as const
}
return [
{
provider: ref.provider,
type: 'md',
path,
repo: `${ref.owner}/${ref.repo}`,
link: matchedChangelog,
host: ref.host,
},
null,
]
} catch (e) {
if (e instanceof Error) {
return [null, e]
}
}
return [false, null]
}
21 changes: 12 additions & 9 deletions server/utils/changelog/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ export interface MarkdownRepoInfo {
/** the base url of repository commit */
commitBaseUrl: string
/** base url for a repository issue */
issueBaseUrl: string
issueBaseUrl?: string
/** the text char that indicates an issue */
issueChar: keyof typeof issuePrRegexes
issueChar?: keyof typeof issuePrRegexes
/** base url for a repository pull/merge request */
prBaseUrl: string
/**
Expand All @@ -130,7 +130,7 @@ export interface MarkdownRepoInfo {
x*/
prChar: keyof typeof issuePrRegexes
/** base url for a repository compare */
compareBaseUrl: string
compareBaseUrl?: string
}

function resolveUrl(url: string, repoInfo: MarkdownRepoInfo, toUserContentId: ToUserContentIdFn) {
Expand Down Expand Up @@ -220,13 +220,13 @@ function resolveGitLinkText(href: string, label: string, repoInfo: MarkdownRepoI
case href.startsWith(repoInfo.commitBaseUrl): {
return lastSegment.slice(0, 7) // only show the first 6 letters/numbers of a commit
}
case href.startsWith(repoInfo.issueBaseUrl): {
case !!repoInfo.issueBaseUrl && href.startsWith(repoInfo.issueBaseUrl): {
return `${repoInfo.issueChar}${lastSegment}`
}
case href.startsWith(repoInfo.prBaseUrl): {
return `${repoInfo.prChar}${lastSegment}`
}
case href.startsWith(repoInfo.compareBaseUrl): {
case !!repoInfo.compareBaseUrl && href.startsWith(repoInfo.compareBaseUrl): {
return lastSegment
}
// for account we don't resolve, this is something the git providers also don't do
Expand Down Expand Up @@ -256,16 +256,19 @@ function createResolveGitTextToLinks(mdInfo: MarkdownRepoInfo): IOptions['textFi

return `<a href="${joinURL(mdInfo.commitBaseUrl, match)}" rel="nofollow noreferrer noopener" target="_blank">${match.slice(0, 7)}</a>`
})
.replace(issuePrRegexes[mdInfo.issueChar], match => {
const id = match.replace(mdInfo.issueChar, '')
return `<a href="${joinURL(mdInfo.issueBaseUrl, id)}" rel="nofollow noreferrer noopener" target="_blank">${match}</a>`
})
// account
.replace(accountRegex, match => {
const acc = match.replace('@', '')
return `<a href="${joinURL(mdInfo.hostBaseUrl, acc)}" rel="nofollow noreferrer noopener" target="_blank">${match}</a>`
})

if (mdInfo.issueChar && mdInfo.issueBaseUrl) {
text = text.replace(issuePrRegexes[mdInfo.issueChar], match => {
const id = match.replace(mdInfo.issueChar!, '')
return `<a href="${joinURL(mdInfo.issueBaseUrl!, id)}" rel="nofollow noreferrer noopener" target="_blank">${match}</a>`
})
}

// pr/mr
if (mdInfo.issueChar != mdInfo.prChar) {
text = text.replace(issuePrRegexes[mdInfo.prChar], match => {
Expand Down
40 changes: 40 additions & 0 deletions server/utils/changelog/mdRepoInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,43 @@ export function createTangledInfo(owner: string, repo: string, path?: string): M
compareBaseUrl: `${hostBaseUrl}/${owner}/${repo}/compare`,
}
}

// similar to forgejo, but they're seperate projects so keeping this also seperate
export function createGiteaRepoInfo(
host: string,
owner: string,
repo: string,
path?: string,
): MarkdownRepoInfo {
const hostBaseUrl = `https://${host}`
return {
hostBaseUrl,
blobBaseUrl: `${hostBaseUrl}/${owner}/${repo}/src/branch/HEAD`,
rawBaseUrl: `${hostBaseUrl}/${owner}/${repo}/raw/branch/HEAD`,
path,
commitBaseUrl: `${hostBaseUrl}/${owner}/${repo}/commit`,
issueChar: '#',
issueBaseUrl: `${hostBaseUrl}/${owner}/${repo}/issues`,
prChar: '#',
prBaseUrl: `${hostBaseUrl}/${owner}/${repo}/pulls`,
compareBaseUrl: `${hostBaseUrl}/${owner}/${repo}/compare`,
}
}

export function createBitbucketRepoInfo(
owner: string,
repo: string,
path?: string,
): MarkdownRepoInfo {
const hostBaseUrl = 'https://bitbucket.org'
return {
hostBaseUrl,
blobBaseUrl: `${hostBaseUrl}/${owner}/${repo}/src/HEAD`,
rawBaseUrl: `${hostBaseUrl}/${owner}/${repo}/raw/HEAD`,
path,
commitBaseUrl: `${hostBaseUrl}/${owner}/${repo}/commits`,
prChar: '#',
prBaseUrl: `${hostBaseUrl}/${owner}/${repo}/pull-requests`,
// bitbucket uses jira instead of normal issues which can't be resolved, further the compare page also seems to be different
}
}
13 changes: 13 additions & 0 deletions shared/schemas/changelog/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,16 @@ export const GitlabReleaseSchame = v.object({
})

export const GitlabReleaseCollectionSchema = v.array(GitlabReleaseSchame)

export const GiteaReleaseSchema = v.object({
id: v.number(),
tag_name: v.string(),
name: v.string(),
body: v.string(),
html_url: v.pipe(v.string(), v.url()),
draft: v.boolean(),
prerelease: v.boolean(),
published_at: v.pipe(v.string(), v.isoTimestamp()),
})

export const GiteaReleaseCollectionSchema = v.array(GiteaReleaseSchema)
Loading