Skip to content

Commit 8613bdc

Browse files
committed
added suppot for tangled & optimized how the markdown file is gotten at the /md route
1 parent 67e2144 commit 8613bdc

4 files changed

Lines changed: 147 additions & 126 deletions

File tree

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { resolveURL } from 'ufo'
12
import * as v from 'valibot'
3+
import { getBaseFileUrl } from '~~/server/utils/changelog/baseFileUrl'
24
import { createForgejoRepoInfo, createGithubRepoInfo } from '~~/server/utils/changelog/mdRepoInfo'
35
import {
46
ERROR_CHANGELOG_FILE_FAILED,
@@ -7,7 +9,7 @@ import {
79

810
export default defineCachedEventHandler(
911
async event => {
10-
const provider = getRouterParam(event, 'provider')
12+
const provider = getRouterParam(event, 'provider') as ProviderId
1113
const repo = getRouterParam(event, 'repo')
1214
const owner = getRouterParam(event, 'owner')
1315
const path = getRouterParam(event, 'path')
@@ -23,21 +25,23 @@ export default defineCachedEventHandler(
2325
}
2426

2527
try {
26-
switch (provider as ProviderId) {
27-
case 'github':
28-
return await getGithubMarkDown(owner, repo, path)
29-
case 'codeberg':
30-
case 'forgejo':
31-
return await getForgejoMarkdown(owner, repo, path, host ?? 'codeberg.org')
32-
case 'gitlab':
33-
return await getGitlabMarkdown(owner, repo, path, host ?? 'gitlab.com')
28+
const baseUrl = getBaseFileUrl({
29+
owner,
30+
provider: provider as ProviderId,
31+
repo,
32+
host,
33+
})
34+
const mdRepoInfo = getRepoInfo(provider, owner, repo, host, path)
3435

35-
default:
36-
throw createError({
37-
status: 404,
38-
statusMessage: ERROR_CHANGELOG_NOT_FOUND,
39-
})
36+
if (!baseUrl || !mdRepoInfo) {
37+
throw createError({
38+
status: 404,
39+
statusMessage: ERROR_CHANGELOG_NOT_FOUND,
40+
})
4041
}
42+
const data = await $fetch(resolveURL(baseUrl.raw, path))
43+
const markdown = v.parse(v.string(), data)
44+
return (await changelogRenderer(mdRepoInfo))(markdown)
4145
} catch (error) {
4246
handleApiError(error, {
4347
statusCode: 500,
@@ -59,30 +63,20 @@ export default defineCachedEventHandler(
5963
},
6064
)
6165

62-
async function getGithubMarkDown(owner: string, repo: string, path: string) {
63-
const data = await $fetch(`https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${path}`)
64-
65-
const markdown = v.parse(v.string(), data)
66-
67-
return (await changelogRenderer(createGithubRepoInfo(owner, repo, path)))(markdown)
68-
}
69-
70-
async function getForgejoMarkdown(owner: string, repo: string, path: string, host: string) {
71-
const data = await $fetch(`https://${host}/${owner}/${repo}/raw/branch/HEAD/${path}`)
72-
73-
const markdown = v.parse(v.string(), data)
74-
75-
return (await changelogRenderer(createForgejoRepoInfo(host, owner, repo, path)))(markdown)
76-
}
77-
78-
async function getGitlabMarkdown(owner: string, repo: string, path: string, host: string) {
79-
const data = await $fetch(
80-
`https://${host}/${decodeURIComponent(owner)}/${repo}/-/raw/HEAD/${path}`,
81-
)
82-
83-
const markdown = v.parse(v.string(), data)
84-
85-
return (
86-
await changelogRenderer(createGitLabRepoInfo(host, decodeURIComponent(owner), repo, path))
87-
)(markdown)
66+
function getRepoInfo(
67+
provider: ProviderId,
68+
owner: string,
69+
repo: string,
70+
host: string | undefined,
71+
path?: string,
72+
) {
73+
switch (provider) {
74+
case 'github':
75+
return createGithubRepoInfo(owner, repo, path)
76+
case 'codeberg':
77+
case 'forgejo':
78+
return createForgejoRepoInfo(host ?? 'codeberg.org', owner, repo, path)
79+
case 'gitlab':
80+
return createGitLabRepoInfo(host ?? 'gitlab.com', owner, repo, path)
81+
}
8882
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export interface RepoFileUrl {
2+
raw: string
3+
blob: string
4+
}
5+
6+
export function getBaseFileUrl(ref: RepoRef): RepoFileUrl | null {
7+
switch (ref.provider) {
8+
case 'github':
9+
return {
10+
raw: `https://raw.githubusercontent.com/${ref.owner}/${ref.repo}/HEAD`,
11+
blob: `https://github.com/${ref.owner}/${ref.repo}/blob/HEAD`,
12+
}
13+
case 'codeberg':
14+
case 'forgejo': {
15+
const host = ref.host ?? 'codeberg.org'
16+
return {
17+
blob: `https://${host}/${ref.owner}/${ref.repo}/src/branch/HEAD`,
18+
raw: `https://${host}/${ref.owner}/${ref.repo}/raw/branch/HEAD`,
19+
}
20+
}
21+
case 'gitlab': {
22+
const host = ref.host ?? 'gitlab.com'
23+
return {
24+
blob: `https://${host}/${ref.owner}/${ref.repo}/-/blob/HEAD`,
25+
raw: `https://${host}/${ref.owner}/${ref.repo}/-/raw/HEAD`,
26+
}
27+
}
28+
case 'tangled': {
29+
return {
30+
blob: `https://tangled.org/${ref.owner}/${ref.repo}/blob/HEAD`,
31+
raw: `https://tangled.org/${ref.owner}/${ref.repo}/raw/HEAD`,
32+
}
33+
}
34+
}
35+
return null
36+
}

server/utils/changelog/detectChangelog.ts

Lines changed: 60 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { ChangelogMarkdownInfo, ChangelogInfo } from '~~/shared/types/changelog'
2-
import { FetchError } from 'ofetch'
32
import type { ExtendedPackageJson } from '~~/shared/utils/package-analysis'
43
import { type RepoRef, parseRepoUrl } from '~~/shared/utils/git-providers'
4+
import { type RepoFileUrl, getBaseFileUrl } from './baseFileUrl'
5+
import { FetchError } from 'ofetch'
56
import { ERROR_CHANGELOG_NOT_FOUND, ERROR_UNGH_API_KEY_EXHAUSTED } from '~~/shared/utils/constants'
67
import {
78
GithubReleaseSchama,
@@ -24,6 +25,7 @@ export async function detectChangelog(pkg: ExtendedPackageJson) {
2425
}
2526

2627
const repoRef = parseRepoUrl(pkg.repository.url)
28+
console.log({ repoRef })
2729
if (!repoRef) {
2830
return false
2931
}
@@ -71,6 +73,63 @@ async function checkReleases(
7173
return [false, null]
7274
}
7375

76+
/// changelog markdown
77+
78+
const EXTENSIONS = ['.md', ''] as const
79+
80+
const CHANGELOG_FILENAMES = ['changelog', 'releases', 'changes', 'history', 'news']
81+
.map(fileName => {
82+
const fileNameUpperCase = fileName.toUpperCase()
83+
return EXTENSIONS.map(ext => [`${fileNameUpperCase}${ext}`, `${fileName}${ext}`])
84+
})
85+
.flat(3)
86+
87+
async function checkChangelogFile(
88+
ref: RepoRef,
89+
directory?: string,
90+
): Promise<ChangelogMarkdownInfo | false> {
91+
const baseUrl = getBaseFileUrl(ref)
92+
if (!baseUrl) {
93+
return false
94+
}
95+
96+
if (directory) {
97+
const inDir = await checkFiles(ref, baseUrl, directory)
98+
if (inDir) {
99+
return inDir
100+
}
101+
}
102+
return checkFiles(ref, baseUrl)
103+
}
104+
105+
async function checkFiles(ref: RepoRef, baseUrl: RepoFileUrl, dir?: string) {
106+
for (const fileName of CHANGELOG_FILENAMES) {
107+
const exists = await fetch(resolveURL(baseUrl.raw, dir ?? '', fileName), {
108+
headers: {
109+
// GitHub API requires User-Agent
110+
'User-Agent': 'npmx.dev',
111+
},
112+
method: ref.provider != 'tangled' ? 'HEAD' : 'GET', // we just need to know if it exists or not, tangled doesn't support HEAD
113+
})
114+
.then(r => r.ok)
115+
.catch(() => false)
116+
const owner = ref.provider == 'gitlab' ? encodeURIComponent(ref.owner) : ref.owner
117+
if (exists) {
118+
return {
119+
type: 'md',
120+
provider: ref.provider,
121+
path: resolveURL(dir ?? '', fileName),
122+
repo: `${owner}/${ref.repo}`,
123+
link: resolveURL(baseUrl.blob, dir ?? '', fileName),
124+
host: ref.host,
125+
} satisfies ChangelogMarkdownInfo
126+
}
127+
}
128+
return false
129+
}
130+
131+
// releases
132+
74133
const MD_REGEX = /(?<=\[.*?(changelog|releases|changes|history|news)\.md.*?\]\()(.*?)(?=\))/i
75134
const ROOT_ONLY_REGEX = /^\/[^/]+$/
76135

@@ -144,91 +203,6 @@ async function checkLatestGithubRelease(
144203
}
145204
}
146205

147-
/// changelog markdown
148-
149-
const EXTENSIONS = ['.md', ''] as const
150-
151-
const CHANGELOG_FILENAMES = ['changelog', 'releases', 'changes', 'history', 'news']
152-
.map(fileName => {
153-
const fileNameUpperCase = fileName.toUpperCase()
154-
return EXTENSIONS.map(ext => [`${fileNameUpperCase}${ext}`, `${fileName}${ext}`])
155-
})
156-
.flat(3)
157-
158-
async function checkChangelogFile(
159-
ref: RepoRef,
160-
directory?: string,
161-
): Promise<ChangelogMarkdownInfo | false> {
162-
const baseUrl = getBaseFileUrl(ref)
163-
if (!baseUrl) {
164-
return false
165-
}
166-
167-
if (directory) {
168-
const inDir = await checkFiles(ref, baseUrl, directory)
169-
if (inDir) {
170-
return inDir
171-
}
172-
}
173-
return checkFiles(ref, baseUrl)
174-
}
175-
176-
async function checkFiles(ref: RepoRef, baseUrl: RepoFileUrl, dir?: string) {
177-
for (const fileName of CHANGELOG_FILENAMES) {
178-
const exists = await fetch(resolveURL(baseUrl.raw, dir ?? '', fileName), {
179-
headers: {
180-
// GitHub API requires User-Agent
181-
'User-Agent': 'npmx.dev',
182-
},
183-
method: 'HEAD', // we just need to know if it exists or not
184-
})
185-
.then(r => r.ok)
186-
.catch(() => false)
187-
if (exists) {
188-
return {
189-
type: 'md',
190-
provider: ref.provider,
191-
path: resolveURL(dir ?? '', fileName),
192-
repo: `${encodeURIComponent(ref.owner)}/${ref.repo}`,
193-
link: resolveURL(baseUrl.blob, dir ?? '', fileName),
194-
host: ref.host,
195-
} satisfies ChangelogMarkdownInfo
196-
}
197-
}
198-
return false
199-
}
200-
201-
interface RepoFileUrl {
202-
raw: string
203-
blob: string
204-
}
205-
206-
function getBaseFileUrl(ref: RepoRef): RepoFileUrl | null {
207-
switch (ref.provider) {
208-
case 'github':
209-
return {
210-
raw: `https://raw.githubusercontent.com/${ref.owner}/${ref.repo}/HEAD`,
211-
blob: `https://github.com/${ref.owner}/${ref.repo}/blob/HEAD`,
212-
}
213-
case 'codeberg':
214-
case 'forgejo': {
215-
const host = ref.host ?? 'codeberg.org'
216-
return {
217-
blob: `https://${host}/${ref.owner}/${ref.repo}/src/branch/HEAD`,
218-
raw: `https://${host}/${ref.owner}/${ref.repo}/raw/branch/HEAD`,
219-
}
220-
}
221-
case 'gitlab': {
222-
const host = ref.host ?? 'gitlab.com'
223-
return {
224-
blob: `https://${host}/${ref.owner}/${ref.repo}/-/blob/HEAD`,
225-
raw: `https://${host}/${ref.owner}/${ref.repo}/-/raw/HEAD`,
226-
}
227-
}
228-
}
229-
return null
230-
}
231-
232206
// codeberg / forgejo
233207

234208
async function checkLatestForgejoRelease(

server/utils/changelog/mdRepoInfo.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,20 @@ export function createGitLabRepoInfo(
6161
compareBaseUrl: `${hostBaseUrl}/${owner}/${repo}/-/compare`,
6262
}
6363
}
64+
65+
export function createTangledInfo(owner: string, repo: string, path?: string): MarkdownRepoInfo {
66+
const hostBaseUrl = 'https://tangled.org'
67+
68+
return {
69+
hostBaseUrl,
70+
blobBaseUrl: `${hostBaseUrl}/${owner}/${repo}/blob/HEAD`,
71+
rawBaseUrl: `${hostBaseUrl}/${owner}/${repo}/raw/HEAD/`,
72+
path,
73+
commitBaseUrl: `${hostBaseUrl}/${owner}/${repo}/commit`,
74+
issueChar: '#',
75+
issueBaseUrl: `${hostBaseUrl}/${owner}/${repo}/issues`,
76+
prChar: '#',
77+
prBaseUrl: `${hostBaseUrl}/${owner}/${repo}/pulls`,
78+
compareBaseUrl: `${hostBaseUrl}/${owner}/${repo}/compare`,
79+
}
80+
}

0 commit comments

Comments
 (0)