Skip to content
Merged
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
7 changes: 4 additions & 3 deletions app/components/OgImage/Package.takumi.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ const versionLabel = computed(() => (version ? `v${version}` : ''))

const repositoryUrl = computed(() => {
const repo = displayVersion.value?.repository
if (!repo?.url) return null
let url = normalizeGitUrl(repo.url)
const repoUrl = typeof repo === 'object' ? repo.url : repo
if (!repoUrl) return null
let url = normalizeGitUrl(repoUrl)
// append `repository.directory` for monorepo packages
if (repo.directory) {
if (typeof repo === 'object' && repo.directory) {
url = joinURL(`${url}/tree/HEAD`, repo.directory)
}
return url
Expand Down
11 changes: 9 additions & 2 deletions app/composables/useRepositoryUrl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { Repository } from '@npm/types'
import { joinURL } from 'ufo'

type RequestedVersion = SlimPackument['requestedVersion'] | null
export type RequestedVersion =
| SlimPackument['requestedVersion']
| { repository?: string | Repository }

type UseRepositoryUrlReturn = {
repositoryUrl: ComputedRef<string | null>
Expand All @@ -12,10 +15,14 @@ export function useRepositoryUrl(
const repositoryUrl = computed<string | null>(() => {
const repo = toValue(requestedVersion)?.repository

if (typeof repo === 'string') {
// sometimes repo can be a string due to not being normalized during publishing
return normalizeGitUrl(repo)
}

if (!repo?.url) {
return null
}

let url = normalizeGitUrl(repo.url)
if (!url) {
return null
Expand Down
23 changes: 10 additions & 13 deletions server/api/registry/analysis/[...pkg].get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
CACHE_MAX_AGE_ONE_DAY,
ERROR_PACKAGE_ANALYSIS_FAILED,
} from '#shared/utils/constants'
import { parseRepoUrl } from '#shared/utils/git-providers'
import { parseRepositoryInfo } from '#shared/utils/git-providers'
import { encodePackageName } from '#shared/utils/npm'
import { fetchPackageWithTypesAndFiles } from '#server/utils/file-tree'
import { getLatestVersionBatch } from 'fast-npm-meta'
Expand Down Expand Up @@ -68,7 +68,7 @@ export default defineCachedEventHandler(
/** Package metadata needed for association validation */
interface PackageWithMeta {
maintainers?: Array<{ name: string }>
repository?: { url?: string }
repository?: { url?: string } | string
deprecated?: string
}

Expand Down Expand Up @@ -147,30 +147,27 @@ async function fetchCreatePackageForValidation(
* Check if two packages are associated (share maintainers or same repo owner).
*/
function isAssociatedPackage(
basePkg: { maintainers?: Array<{ name: string }>; repository?: { url?: string } },
createPkg: { maintainers?: Array<{ name: string }>; repository?: { url?: string } },
basePkg: { maintainers?: Array<{ name: string }>; repository?: { url?: string } | string },
createPkg: { maintainers?: Array<{ name: string }>; repository?: { url?: string } | string },
): boolean {
const baseMaintainers = new Set(basePkg.maintainers?.map(m => m.name.toLowerCase()) ?? [])
const createMaintainers = createPkg.maintainers?.map(m => m.name.toLowerCase()) ?? []
const hasSharedMaintainer = createMaintainers.some(name => baseMaintainers.has(name))

return (
hasSharedMaintainer ||
hasSameRepositoryOwner(basePkg.repository?.url, createPkg.repository?.url)
)
return hasSharedMaintainer || hasSameRepositoryOwner(basePkg.repository, createPkg.repository)
}

/**
* Check if two repository URLs have the same owner (works with any git provider).
*/
function hasSameRepositoryOwner(
baseRepoUrl: string | undefined,
createRepoUrl: string | undefined,
baseRepo: string | { url?: string } | undefined,
createRepo: string | { url?: string } | undefined,
): boolean {
if (!baseRepoUrl || !createRepoUrl) return false
if (!baseRepo || !createRepo) return false

const baseRef = parseRepoUrl(baseRepoUrl)
const createRef = parseRepoUrl(createRepoUrl)
const baseRef = parseRepositoryInfo(baseRepo)
const createRef = parseRepositoryInfo(createRepo)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

if (!baseRef || !createRef) return false
if (baseRef.provider !== createRef.provider) return false
Expand Down
14 changes: 6 additions & 8 deletions server/utils/changelog/detectChangelog.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ChangelogMarkdownInfo, ChangelogInfo } from '~~/shared/types/changelog'
import type { ExtendedPackageJson } from '~~/shared/utils/package-analysis'
import { type RepoRef, parseRepoUrl } from '~~/shared/utils/git-providers'
import { type RepoRef, parseRepositoryInfo } from '~~/shared/utils/git-providers'
import { type RepoFileUrl, getBaseFileUrl } from './baseFileUrl'
import { FetchError } from 'ofetch'
import { ERROR_CHANGELOG_NOT_FOUND, ERROR_UNGH_API_KEY_EXHAUSTED } from '~~/shared/utils/constants'
Expand All @@ -20,21 +20,19 @@ type SafeResult<R, E = Error> = [R, null] | [null, E]
* first checks if releases are available and then changelog.md
*/
export async function detectChangelog(pkg: ExtendedPackageJson) {
if (!pkg.repository?.url) {
return false
}

const repoRef = parseRepoUrl(pkg.repository.url)
const repoRef = parseRepositoryInfo(pkg.repository)
if (!repoRef) {
return false
}

const [releases, releasesError] = await checkReleases(repoRef, pkg.repository.directory)
const directory = typeof pkg.repository === 'object' ? pkg.repository.directory : undefined

const [releases, releasesError] = await checkReleases(repoRef, directory)
if (releases) {
return releases
}

const changelog = await checkChangelogFile(repoRef, pkg.repository.directory)
const changelog = await checkChangelogFile(repoRef, directory)
if (changelog) {
return changelog
}
Expand Down
2 changes: 1 addition & 1 deletion shared/types/npm-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export interface SlimPackument {
'license'?: string
'homepage'?: string
'keywords'?: string[]
'repository'?: { type?: string; url?: string; directory?: string }
'repository'?: { type?: string; url?: string; directory?: string } | string
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
'bugs'?: { url?: string; email?: string }
'storybook'?: { url: string }
/** current version */
Expand Down
2 changes: 1 addition & 1 deletion shared/utils/package-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export interface ExtendedPackageJson {
/** npm maintainers (returned by registry API) */
maintainers?: Array<{ name: string; email?: string }>
/** Repository info (returned by registry API) */
repository?: { url?: string; type?: string; directory?: string }
repository?: { url?: string; type?: string; directory?: string } | string
}

export type PackageExports = string | null | { [key: string]: PackageExports } | PackageExports[]
Expand Down
15 changes: 14 additions & 1 deletion test/nuxt/composables/use-repository-url.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { RequestedVersion as RequestedVersionNullable } from '~/composables/useRepositoryUrl'
import { describe, expect, it } from 'vitest'

type RequestedVersion = Exclude<SlimPackument['requestedVersion'], null>
type RequestedVersion = NonNullable<RequestedVersionNullable>

function mockPackage(repository: RequestedVersion['repository']): RequestedVersion {
return {
Expand Down Expand Up @@ -66,4 +67,16 @@ describe('useRepositoryUrl', () => {

expect(repositoryUrl.value).toBe('https://github.com/org/repo/tree/HEAD/packages/core/')
})

it('should handle shorthand url', () => {
const { repositoryUrl } = useRepositoryUrl(mockPackage('https://github.com/nuxt/ui'))
expect(repositoryUrl.value).toBe('https://github.com/nuxt/ui')
})

it('should strip .git from shorthand repo url', () => {
const { repositoryUrl } = useRepositoryUrl(
mockPackage('git+https://github.com/agentmarkup/agentmarkup.git'),
)
expect(repositoryUrl.value).toBe('https://github.com/agentmarkup/agentmarkup')
})
})
Loading