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
3 changes: 2 additions & 1 deletion app/composables/useRepositoryUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export function useRepositoryUrl(

// append `repository.directory` for monorepo packages
if (repo.directory) {
url = joinURL(`${url}/tree/HEAD`, repo.directory)
const encodedDirectory = repo.directory.split('/').map(encodeURIComponent).join('/')
url = joinURL(`${url}/tree/HEAD`, encodedDirectory)
}

return url
Expand Down
2 changes: 1 addition & 1 deletion shared/utils/git-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export function normalizeGitUrl(input: string): string | null {
const raw = input.trim()
if (!raw) return null

const normalized = raw.replace(/^git\+/, '')
const normalized = raw.replace(/^git\+/, '').replace(/\.git$/i, '')

// Handle ssh:// and git:// URLs by converting to https://
if (/^(?:ssh|git):\/\//i.test(normalized)) {
Expand Down
54 changes: 54 additions & 0 deletions test/unit/app/composables/use-repository-url.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, toValue } from 'vue'
import type { SlimPackumentVersion } from '#shared/types/npm-registry'
import { useRepositoryUrl } from '../../../../app/composables/useRepositoryUrl'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are built in aliases for anything under app maybe can use that instead of deep path traversal

Image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also composables are auto-imported anyway

Suggested change
import { useRepositoryUrl } from '../../../../app/composables/useRepositoryUrl'


function createVersion(repository?: SlimPackumentVersion['repository']): SlimPackumentVersion {
return { repository } as SlimPackumentVersion
}

beforeEach(() => {
vi.stubGlobal('computed', computed)
vi.stubGlobal('toValue', toValue)
vi.stubGlobal('normalizeGitUrl', (url: string) => url)
})

afterEach(() => {
vi.unstubAllGlobals()
})

describe('useRepositoryUrl', () => {
it('returns null when repository url is missing', () => {
const { repositoryUrl } = useRepositoryUrl(null)
expect(repositoryUrl.value).toBeNull()
})

it('returns the url as-is when there is no directory', () => {
const { repositoryUrl } = useRepositoryUrl(
createVersion({ url: 'https://github.com/nuxt/nuxt' }),
)
expect(repositoryUrl.value).toBe('https://github.com/nuxt/nuxt')
})

it('encodes @ in scoped package directory', () => {
const { repositoryUrl } = useRepositoryUrl(
createVersion({
url: 'https://github.com/tailwindlabs/tailwindcss',
directory: 'packages/@tailwindcss-vite',
}),
)
expect(repositoryUrl.value).toBe(
'https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/%40tailwindcss-vite',
)
})

it('does not encode slashes between directory segments', () => {
const { repositoryUrl } = useRepositoryUrl(
createVersion({
url: 'https://github.com/withastro/astro',
directory: 'packages/astro',
}),
)
expect(repositoryUrl.value).toBe('https://github.com/withastro/astro/tree/HEAD/packages/astro')
})
})
30 changes: 29 additions & 1 deletion test/unit/shared/utils/git-providers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { describe, expect, it } from 'vitest'
import { parseRepositoryInfo, type RepositoryInfo } from '#shared/utils/git-providers'
import {
normalizeGitUrl,
parseRepositoryInfo,
type RepositoryInfo,
} from '#shared/utils/git-providers'

describe('parseRepositoryInfo', () => {
it('returns undefined for undefined input', () => {
Expand Down Expand Up @@ -386,6 +390,30 @@ describe('parseRepositoryInfo', () => {
})
})

describe('normalizeGitUrl', () => {
it('strips .git suffix from HTTPS URLs', () => {
expect(normalizeGitUrl('https://github.com/tailwindlabs/tailwindcss.git')).toBe(
'https://github.com/tailwindlabs/tailwindcss',
)
})

it('strips .git suffix from git+https URLs', () => {
expect(normalizeGitUrl('git+https://github.com/tailwindlabs/tailwindcss.git')).toBe(
'https://github.com/tailwindlabs/tailwindcss',
)
})

it('strips .git suffix from ssh URLs', () => {
expect(normalizeGitUrl('git@github.com:tailwindlabs/tailwindcss.git')).toBe(
'https://github.com/tailwindlabs/tailwindcss',
)
})

it('leaves URLs without .git suffix unchanged', () => {
expect(normalizeGitUrl('https://github.com/nuxt/nuxt')).toBe('https://github.com/nuxt/nuxt')
})
})

describe('RepositoryInfo type', () => {
it('includes blobBaseUrl in RepositoryInfo', () => {
const result = parseRepositoryInfo({
Expand Down
Loading