diff --git a/server/api/changelog/md/[provider]/[owner]/[repo]/[...path].get.ts b/server/api/changelog/md/[provider]/[owner]/[repo]/[...path].get.ts
index a1582b1414..07174f2518 100644
--- a/server/api/changelog/md/[provider]/[owner]/[repo]/[...path].get.ts
+++ b/server/api/changelog/md/[provider]/[owner]/[repo]/[...path].get.ts
@@ -2,9 +2,13 @@ import { resolveURL } from 'ufo'
import * as v from 'valibot'
import { getBaseFileUrl } from '~~/server/utils/changelog/baseFileUrl'
import {
+ createBitbucketRepoInfo,
createForgejoRepoInfo,
+ createGiteaRepoInfo,
+ createGiteeRepoInfo,
createGithubRepoInfo,
createGitLabRepoInfo,
+ createSourcehutRepoInfo,
createTangledInfo,
} from '~~/server/utils/changelog/mdRepoInfo'
import { validateHostWithValibot } from '~~/server/utils/changelog/validateHost'
@@ -56,6 +60,7 @@ export default defineCachedEventHandler(
headers: {
'User-Agent': 'npmx.dev',
},
+ timeout: 15_000,
})
const markdown = v.parse(v.string(), data)
if (raw != undefined) {
@@ -114,5 +119,13 @@ 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)
+ case 'sourcehut':
+ return createSourcehutRepoInfo(owner, repo, path)
+ case 'gitee':
+ return createGiteeRepoInfo(owner, repo, path)
}
}
diff --git a/server/api/changelog/releases/[provider]/[owner]/[repo].get.ts b/server/api/changelog/releases/[provider]/[owner]/[repo].get.ts
index 20e618072a..9737803d41 100644
--- a/server/api/changelog/releases/[provider]/[owner]/[repo].get.ts
+++ b/server/api/changelog/releases/[provider]/[owner]/[repo].get.ts
@@ -9,11 +9,19 @@ import {
GithubReleaseCollectionSchama,
ForgejoReleaseCollectionSchema,
GitlabReleaseCollectionSchema,
+ GiteaReleaseCollectionSchema,
+ GiteeReleaseCollectionSchema,
} 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
@@ -39,6 +47,10 @@ 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)
+ case 'gitee':
+ return await getReleasesFromGitee(owner, repo)
default:
throw createError({
@@ -79,6 +91,7 @@ async function getReleasesFromGithub(owner: string, repo: string) {
'Accept': '*/*',
'User-Agent': 'npmx.dev',
},
+ timeout: TIMEOUT_TIME,
})
const { releases } = v.parse(GithubReleaseCollectionSchama, data)
@@ -107,6 +120,7 @@ async function getReleasesFromForgejo(owner: string, repo: string, host: string)
headers: {
'User-Agent': 'npmx.dev',
},
+ timeout: TIMEOUT_TIME,
})
const releases = v.parse(ForgejoReleaseCollectionSchema, data)
@@ -137,6 +151,7 @@ async function getReleasesFromGitlab(owner: string, repo: string, host: string)
headers: {
'User-Agent': 'npmx.dev',
},
+ timeout: TIMEOUT_TIME,
})
const releases = v.parse(GitlabReleaseCollectionSchema, data)
@@ -158,3 +173,60 @@ 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`, {
+ 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, '
') ?? 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
+ })
+}
+
+async function getReleasesFromGitee(owner: string, repo: string) {
+ const data = await $fetch(
+ `https://gitee.com/api/v5/repos/${owner}/${repo}/releases?per_page=30`,
+ {
+ headers: {
+ 'User-Agent': 'npmx.dev',
+ },
+ timeout: TIMEOUT_TIME,
+ },
+ )
+
+ const releases = v.parse(GiteeReleaseCollectionSchema, data)
+
+ const render = await changelogRenderer(createGiteeRepoInfo(owner, repo))
+
+ return releases.map(r => {
+ const { html, toc } = render(r.body, r.id)
+ return {
+ id: r.id,
+ html: html?.replace(/(?)\n/g, '
') ?? null,
+ title: r.name || r.tag_name,
+ prerelease: r.prerelease,
+ toc,
+ link: `https://gitee.com/${owner}/${repo}/releases/tag/${r.tag_name}`,
+ publishedAt: r.created_at,
+ tag: r.tag_name,
+ } satisfies ReleaseData
+ })
+}
diff --git a/server/api/changelog/releases/[provider]/[owner]/[repo]/raw/[tag].get.ts b/server/api/changelog/releases/[provider]/[owner]/[repo]/raw/[tag].get.ts
index a09079a1a8..4b623d2124 100644
--- a/server/api/changelog/releases/[provider]/[owner]/[repo]/raw/[tag].get.ts
+++ b/server/api/changelog/releases/[provider]/[owner]/[repo]/raw/[tag].get.ts
@@ -3,11 +3,15 @@ import * as v from 'valibot'
import { ERROR_THROW_INCOMPLETE_PARAM } from '~~/shared/utils/constants'
import {
ForgejoReleaseSchama,
+ GiteaReleaseSchema,
+ GiteeReleaseSchema,
GithubReleaseCollectionSchama,
GitlabReleaseSchame,
} 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
@@ -40,6 +44,10 @@ export default defineCachedEventHandler(
return await getMarkdownFromForgejo(owner, repo, encodedTag, host)
case 'gitlab':
return await getMarkdownFromGitlab(owner, repo, encodedTag, host)
+ case 'gitea':
+ return await getMarkdownFromGitea(owner, repo, encodedTag, host)
+ case 'gitee':
+ return await getMarkdownFromGitee(owner, repo, encodedTag)
default:
throw createError({
status: 404,
@@ -84,8 +92,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)
@@ -101,6 +110,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)
@@ -129,6 +139,7 @@ async function getMarkdownFromForgejo(
headers: {
'User-Agent': 'npmx.dev',
},
+ timeout: TIMEOUT_TIME,
})
const release = v.parse(ForgejoReleaseSchama, data)
@@ -150,9 +161,50 @@ async function getMarkdownFromGitlab(
headers: {
'User-Agent': 'npmx.dev',
},
+ timeout: TIMEOUT_TIME,
})
const release = v.parse(GitlabReleaseSchame, data)
return release.description
}
+
+async function getMarkdownFromGitea(
+ owner: string,
+ repo: string,
+ /** tag should be encoded */
+ tag: string,
+ host: string = 'gitea.com',
+) {
+ const data = await $fetch(`https://${host}/api/v1/repos/${owner}/${repo}/releases/tags/${tag}`, {
+ headers: {
+ 'User-Agent': 'npmx.dev',
+ },
+ timeout: TIMEOUT_TIME,
+ })
+
+ const release = v.parse(GiteaReleaseSchema, data)
+
+ return release.body
+}
+
+async function getMarkdownFromGitee(
+ owner: string,
+ repo: string,
+ /** tag should be encoded */
+ tag: string,
+) {
+ const data = await $fetch(
+ `https://gitee.com/api/v5/repos/${owner}/${repo}/releases/tags/${tag}`,
+ {
+ headers: {
+ 'User-Agent': 'npmx.dev',
+ },
+ timeout: TIMEOUT_TIME,
+ },
+ )
+
+ const release = v.parse(GiteeReleaseSchema, data)
+
+ return release.body
+}
diff --git a/server/utils/changelog/baseFileUrl.ts b/server/utils/changelog/baseFileUrl.ts
index 7cda63169b..8643dc3e3e 100644
--- a/server/utils/changelog/baseFileUrl.ts
+++ b/server/utils/changelog/baseFileUrl.ts
@@ -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`,
@@ -35,6 +42,24 @@ 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`,
+ }
+ }
+ case 'sourcehut': {
+ return {
+ blob: `https://git.sr.ht/${ref.owner}/${ref.repo}/tree/HEAD/item`,
+ raw: `https://git.sr.ht/${ref.owner}/${ref.repo}/blob/HEAD`,
+ }
+ }
+ case 'gitee': {
+ return {
+ blob: `https://gitee.com/${ref.owner}/${ref.repo}/blob/main`,
+ raw: `https://gitee.com/${ref.owner}/${ref.repo}/raw/main`,
+ }
+ }
}
return null
}
diff --git a/server/utils/changelog/detectChangelog.ts b/server/utils/changelog/detectChangelog.ts
index 6289a7aebd..f7e3be71b1 100644
--- a/server/utils/changelog/detectChangelog.ts
+++ b/server/utils/changelog/detectChangelog.ts
@@ -8,6 +8,8 @@ import {
GithubReleaseSchama,
ForgejoReleaseSchama,
GitlabReleaseSchame,
+ GiteaReleaseSchema,
+ GiteeReleaseSchema,
} from '~~/shared/schemas/changelog/release'
import { resolveURL } from 'ufo'
import * as v from 'valibot'
@@ -66,6 +68,12 @@ async function checkReleases(
case 'gitlab': {
return checkLatestGitlabRelease(ref, directory)
}
+ case 'gitea': {
+ return checkLatestGiteaRelease(ref, directory)
+ }
+ case 'gitee': {
+ return checkLatestGiteeRelease(ref, directory)
+ }
}
return [false, null]
}
@@ -340,3 +348,130 @@ async function checkLatestGitlabRelease(
}
return [false, null]
}
+
+// gitea
+async function checkLatestGiteaRelease(
+ ref: RepoRef,
+ directory?: string,
+): Promise> {
+ 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]
+}
+
+async function checkLatestGiteeRelease(
+ ref: RepoRef,
+ directory?: string,
+): Promise> {
+ try {
+ const response = await $fetch(
+ `https://gitee.com/api/v5/repos/${ref.owner}/${ref.repo}/releases/latest`,
+ {
+ headers: {
+ 'User-Agent': 'npmx.dev',
+ 'accept': 'application/json',
+ },
+ },
+ )
+
+ const release = v.parse(GiteeReleaseSchema, response)
+
+ const matchedChangelog = release.body?.match(MD_REGEX)?.at(0)
+
+ // if no changelog.md or the url doesn't contain /blob/
+ if (!matchedChangelog || !matchedChangelog.includes('/blob/')) {
+ return [
+ {
+ provider: ref.provider,
+ type: 'release',
+ repo: `${ref.owner}/${ref.repo}`,
+ link: `https://gitee.com/${ref.owner}/${ref.repo}/releases`,
+ },
+ null,
+ ]
+ }
+
+ const path = matchedChangelog.replace(/^.*\/blob\/[^/]+\//i, '')
+
+ // makes sure that the correct directory is matched
+ if (
+ directory &&
+ !(
+ path.startsWith(directory.endsWith('/') ? directory : `${directory}/`) ||
+ ROOT_ONLY_REGEX.test(path)
+ )
+ ) {
+ return [false, null]
+ }
+ return [
+ {
+ provider: ref.provider,
+ type: 'md',
+ path,
+ repo: `${ref.owner}/${ref.repo}`,
+ link: matchedChangelog,
+ },
+ null,
+ ]
+ } catch (e) {
+ if (e instanceof Error) {
+ return [null, e]
+ }
+ }
+ return [false, null]
+}
diff --git a/server/utils/changelog/markdown.ts b/server/utils/changelog/markdown.ts
index e57d3f42e3..43ca44b870 100644
--- a/server/utils/changelog/markdown.ts
+++ b/server/utils/changelog/markdown.ts
@@ -118,19 +118,21 @@ 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
+ /** custom regex in case the git provider's issues uses a different format */
+ issueRegex?: RegExp
/** base url for a repository pull/merge request */
- prBaseUrl: string
+ prBaseUrl?: string
/**
* the text char that indicates a pull/merge request
*
* if it's the same as issueChar, than links will be parsed as issues and repo host is reponsible to redirect to pull/merge request
x*/
- prChar: keyof typeof issuePrRegexes
+ prChar?: keyof typeof issuePrRegexes
/** base url for a repository compare */
- compareBaseUrl: string
+ compareBaseUrl?: string
}
function resolveUrl(url: string, repoInfo: MarkdownRepoInfo, toUserContentId: ToUserContentIdFn) {
@@ -218,15 +220,17 @@ function resolveGitLinkText(href: string, label: string, repoInfo: MarkdownRepoI
switch (true) {
case href.startsWith(repoInfo.commitBaseUrl): {
- return lastSegment.slice(0, 7) // only show the first 6 letters/numbers of a commit
+ return lastSegment.slice(0, 7) // only show the first 7 letters/numbers of a commit
}
- case href.startsWith(repoInfo.issueBaseUrl): {
+ case !!repoInfo.issueChar &&
+ !!repoInfo.issueBaseUrl &&
+ href.startsWith(repoInfo.issueBaseUrl): {
return `${repoInfo.issueChar}${lastSegment}`
}
- case href.startsWith(repoInfo.prBaseUrl): {
+ case !!repoInfo.prChar && !!repoInfo.prBaseUrl && 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
@@ -256,21 +260,24 @@ function createResolveGitTextToLinks(mdInfo: MarkdownRepoInfo): IOptions['textFi
return `${match.slice(0, 7)}`
})
- .replace(issuePrRegexes[mdInfo.issueChar], match => {
- const id = match.replace(mdInfo.issueChar, '')
- return `${match}`
- })
// account
.replace(accountRegex, match => {
const acc = match.replace('@', '')
return `${match}`
})
+ if (mdInfo.issueChar && mdInfo.issueBaseUrl) {
+ text = text.replace(mdInfo.issueRegex ?? issuePrRegexes[mdInfo.issueChar], match => {
+ const id = match.replace(mdInfo.issueChar!, '')
+ return `${match}`
+ })
+ }
+
// pr/mr
- if (mdInfo.issueChar != mdInfo.prChar) {
+ if (mdInfo.issueChar != mdInfo.prChar && mdInfo.prChar && mdInfo.prBaseUrl) {
text = text.replace(issuePrRegexes[mdInfo.prChar], match => {
- const id = match.replace(mdInfo.prChar, '')
- return `${match}`
+ const id = match.replace(mdInfo.prChar!, '')
+ return `${match}`
})
}
diff --git a/server/utils/changelog/mdRepoInfo.ts b/server/utils/changelog/mdRepoInfo.ts
index fca0f1dd74..fec175da4f 100644
--- a/server/utils/changelog/mdRepoInfo.ts
+++ b/server/utils/changelog/mdRepoInfo.ts
@@ -78,3 +78,76 @@ 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
+ }
+}
+
+export function createSourcehutRepoInfo(
+ owner: string,
+ repo: string,
+ path?: string,
+): MarkdownRepoInfo {
+ const hostBaseUrl = 'https://git.sr.ht'
+ return {
+ hostBaseUrl,
+ blobBaseUrl: `${hostBaseUrl}/${owner}/${repo}/tree/HEAD/item`,
+ rawBaseUrl: `${hostBaseUrl}/${owner}/${repo}/blob/HEAD`,
+ path,
+ commitBaseUrl: `${hostBaseUrl}/${owner}/${repo}/commit`,
+ // source hut doesn't have/support issues,pr & compare
+ }
+}
+
+export function createGiteeRepoInfo(owner: string, repo: string, path?: string): MarkdownRepoInfo {
+ const hostBaseUrl = `https://gitee.com`
+ return {
+ hostBaseUrl,
+ blobBaseUrl: `${hostBaseUrl}/${owner}/${repo}/blob/HREAD`,
+ rawBaseUrl: `${hostBaseUrl}/${owner}/${repo}/raw/HEAD`,
+ path,
+ commitBaseUrl: `${hostBaseUrl}/${owner}/${repo}/commit`,
+ issueChar: '#',
+ issueBaseUrl: `${hostBaseUrl}/${owner}/${repo}/issues`,
+ issueRegex: /\B#[\dA-Z]+\b/g,
+ prChar: '!',
+ prBaseUrl: `${hostBaseUrl}/${owner}/${repo}/pulls`,
+ compareBaseUrl: `${hostBaseUrl}/${owner}/${repo}/compare`,
+ }
+}
diff --git a/shared/schemas/changelog/release.ts b/shared/schemas/changelog/release.ts
index 0ac03ddd5c..f6915855ca 100644
--- a/shared/schemas/changelog/release.ts
+++ b/shared/schemas/changelog/release.ts
@@ -46,3 +46,27 @@ 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)
+
+export const GiteeReleaseSchema = v.object({
+ id: v.number(),
+ tag_name: v.string(),
+ name: v.string(),
+ body: v.string(),
+ prerelease: v.boolean(),
+ created_at: v.pipe(v.string(), v.isoTimestamp()),
+})
+
+export const GiteeReleaseCollectionSchema = v.array(GiteeReleaseSchema)
diff --git a/test/unit/server/utils/changelog/markdown.spec.ts b/test/unit/server/utils/changelog/markdown.spec.ts
index 55f841b1b7..cc9e6ca1c4 100644
--- a/test/unit/server/utils/changelog/markdown.spec.ts
+++ b/test/unit/server/utils/changelog/markdown.spec.ts
@@ -1,6 +1,10 @@
import type { MarkdownRepoInfo } from '~~/server/utils/changelog/markdown'
import { describe, expect, it, vi, beforeAll } from 'vitest'
-import { createGithubRepoInfo, createGitLabRepoInfo } from '~~/server/utils/changelog/mdRepoInfo'
+import {
+ createGithubRepoInfo,
+ createGitLabRepoInfo,
+ createGiteeRepoInfo,
+} from '~~/server/utils/changelog/mdRepoInfo'
// testing changelog specific needs, others things are tested at ../readme.spec.ts
@@ -729,6 +733,43 @@ describe('Turn plaintext #isssue/#pr, !pr, @account & commmit into links', () =>
`,
)
})
+
+ // gitee
+ describe('gitee issue formatting', () => {
+ it('should turn issue into formatted links', async () => {
+ const info = createGiteeRepoInfo('test-owner', 'test-repo')
+ const renderer = await changelogRenderer(info)
+ // text from date-fns v4.3.0
+ const markdown = `- Fixed pt locale first day of week to be Sunday. See #IKF9K6
+- Fixed zh-CN, zh-HK, and zh-TW locale month parsing for October, November, and December. See #I9T5LW
+`
+ const result = renderer(markdown)
+
+ expect(result.html).toBe(`
+- Fixed pt locale first day of week to be Sunday. See #IKF9K6
+- Fixed zh-CN, zh-HK, and zh-TW locale month parsing for October, November, and December. See #I9T5LW
+
+`)
+ })
+
+ it('should turn issue into links between ()', async () => {
+ const info = createGiteeRepoInfo('test-owner', 'test-repo')
+ const renderer = await changelogRenderer(info)
+ // text comes from npmx release 0.15.0
+ const markdown = `- Minor ui improvements (#IKF9K6)
+- deps: Update module-replacements (#I9T5LW)
+- Release v0.15.0 (#IKEIK1)`
+
+ const result = renderer(markdown)
+
+ expect(result.html).toBe(`
+- Minor ui improvements (#IKF9K6)
+- deps: Update module-replacements (#I9T5LW)
+- Release v0.15.0 (#IKEIK1)
+
+`)
+ })
+ })
})
describe('format unformatted/auto links to git', () => {