diff --git a/app/components/OgImage/Package.takumi.vue b/app/components/OgImage/Package.takumi.vue index 8597c7bca3..adf9bb11a7 100644 --- a/app/components/OgImage/Package.takumi.vue +++ b/app/components/OgImage/Package.takumi.vue @@ -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 diff --git a/app/composables/useRepositoryUrl.ts b/app/composables/useRepositoryUrl.ts index 0e65aa0ee6..83ee640556 100644 --- a/app/composables/useRepositoryUrl.ts +++ b/app/composables/useRepositoryUrl.ts @@ -1,6 +1,9 @@ +import type { Repository } from '@npm/types' import { joinURL } from 'ufo' -type RequestedVersion = SlimPackument['requestedVersion'] | null +export type RequestedVersion = + | (Omit & { repository?: string | Repository }) + | null type UseRepositoryUrlReturn = { repositoryUrl: ComputedRef @@ -12,10 +15,14 @@ export function useRepositoryUrl( const repositoryUrl = computed(() => { 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 diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 3c23102dfd..0d44ded81a 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -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' @@ -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 } @@ -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) if (!baseRef || !createRef) return false if (baseRef.provider !== createRef.provider) return false diff --git a/server/utils/changelog/detectChangelog.ts b/server/utils/changelog/detectChangelog.ts index 926b71e143..6289a7aebd 100644 --- a/server/utils/changelog/detectChangelog.ts +++ b/server/utils/changelog/detectChangelog.ts @@ -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' @@ -20,21 +20,19 @@ type SafeResult = [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 } diff --git a/shared/types/npm-registry.ts b/shared/types/npm-registry.ts index 97c4d99903..46ec7caee1 100644 --- a/shared/types/npm-registry.ts +++ b/shared/types/npm-registry.ts @@ -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 'bugs'?: { url?: string; email?: string } 'storybook'?: { url: string } /** current version */ diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index d9c0a39f15..6807a02499 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -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[] diff --git a/test/nuxt/composables/use-repository-url.spec.ts b/test/nuxt/composables/use-repository-url.spec.ts index b66ff46ef6..260c6ab00e 100644 --- a/test/nuxt/composables/use-repository-url.spec.ts +++ b/test/nuxt/composables/use-repository-url.spec.ts @@ -1,8 +1,11 @@ +import type { Repository } from '@npm/types' import { describe, expect, it } from 'vitest' -type RequestedVersion = Exclude +type RequestedVersion = Omit, 'repository'> & { + repository?: string | Repository +} -function mockPackage(repository: RequestedVersion['repository']): RequestedVersion { +function mockPackage(repository: RequestedVersion['repository'] | string): RequestedVersion { return { _id: 'foo', name: 'foo',