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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ test-results/

# generated files
shared/types/lexicons

**/__screenshots__/**
17 changes: 8 additions & 9 deletions app/composables/npm/useNpmSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type {
NpmDownloadCount,
MinimalPackument,
} from '#shared/types'
import { NPM_REGISTRY, NPM_API } from '~/utils/npm/common'

/**
* Convert packument to search result format for display
Expand Down Expand Up @@ -55,7 +54,6 @@ export function useNpmSearch(
query: MaybeRefOrGetter<string>,
options: MaybeRefOrGetter<NpmSearchOptions> = {},
) {
const cachedFetch = useCachedFetch()
// Client-side cache
const cache = shallowRef<{
query: string
Expand All @@ -70,7 +68,7 @@ export function useNpmSearch(

const asyncData = useLazyAsyncData(
() => `search:incremental:${toValue(query)}`,
async (_nuxtApp, { signal }) => {
async ({ $npmRegistry, $npmApi }, { signal }) => {
const q = toValue(query)

if (!q.trim()) {
Expand All @@ -91,8 +89,8 @@ export function useNpmSearch(
if (q.length === 1) {
const encodedName = encodePackageName(q)
const [{ data: pkg, isStale }, { data: downloads }] = await Promise.all([
cachedFetch<Packument>(`${NPM_REGISTRY}/${encodedName}`, { signal }),
cachedFetch<NpmDownloadCount>(`${NPM_API}/downloads/point/last-week/${encodedName}`, {
$npmRegistry<Packument>(`/${encodedName}`, { signal }),
$npmApi<NpmDownloadCount>(`/downloads/point/last-week/${encodedName}`, {
signal,
}),
])
Expand Down Expand Up @@ -122,8 +120,8 @@ export function useNpmSearch(
}
}

const { data: response, isStale } = await cachedFetch<NpmSearchResponse>(
`${NPM_REGISTRY}/-/v1/search?${params.toString()}`,
const { data: response, isStale } = await $npmRegistry<NpmSearchResponse>(
`/-/v1/search?${params.toString()}`,
{ signal },
60,
)
Expand All @@ -146,6 +144,7 @@ export function useNpmSearch(

// Fetch more results incrementally (only used in incremental mode)
async function fetchMore(targetSize: number): Promise<void> {
const { $npmRegistry } = useNuxtApp()
const q = toValue(query).trim()
if (!q) {
cache.value = null
Expand Down Expand Up @@ -179,8 +178,8 @@ export function useNpmSearch(
params.set('size', String(size))
params.set('from', String(from))

const { data: response } = await cachedFetch<NpmSearchResponse>(
`${NPM_REGISTRY}/-/v1/search?${params.toString()}`,
const { data: response } = await $npmRegistry<NpmSearchResponse>(
`/-/v1/search?${params.toString()}`,
{},
60,
)
Expand Down
27 changes: 12 additions & 15 deletions app/composables/npm/useOrgPackages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { NpmSearchResponse, NpmSearchResult, MinimalPackument } from '#shared/types'
import { emptySearchResponse, packumentToSearchResult } from './useNpmSearch'
import { NPM_REGISTRY, NPM_API } from '~/utils/npm/common'
import { mapWithConcurrency } from '#shared/utils/async'

/**
Expand All @@ -13,6 +12,7 @@ async function fetchBulkDownloads(
packageNames: string[],
options: Parameters<typeof $fetch>[1] = {},
): Promise<Map<string, number>> {
const { $npmApi } = useNuxtApp()
const downloads = new Map<string, number>()
if (packageNames.length === 0) return downloads

Expand All @@ -28,11 +28,11 @@ async function fetchBulkDownloads(
bulkPromises.push(
(async () => {
try {
const response = await $fetch<Record<string, { downloads: number } | null>>(
`${NPM_API}/downloads/point/last-week/${chunk.join(',')}`,
const response = await $npmApi<Record<string, { downloads: number } | null>>(
`/downloads/point/last-week/${chunk.join(',')}`,
options,
)
for (const [name, data] of Object.entries(response)) {
for (const [name, data] of Object.entries(response.data)) {
if (data?.downloads !== undefined) {
downloads.set(name, data.downloads)
}
Expand All @@ -54,8 +54,8 @@ async function fetchBulkDownloads(
const results = await Promise.allSettled(
batch.map(async name => {
const encoded = encodePackageName(name)
const data = await $fetch<{ downloads: number }>(
`${NPM_API}/downloads/point/last-week/${encoded}`,
const { data } = await $npmApi<{ downloads: number }>(
`/downloads/point/last-week/${encoded}`,
)
return { name, downloads: data.downloads }
}),
Expand All @@ -80,11 +80,9 @@ async function fetchBulkDownloads(
* Returns search-result-like objects for compatibility with PackageList
*/
export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
const cachedFetch = useCachedFetch()

const asyncData = useLazyAsyncData(
() => `org-packages:${toValue(orgName)}`,
async (_nuxtApp, { signal }) => {
async ({ $npmRegistry }, { signal }) => {
const org = toValue(orgName)
if (!org) {
return emptySearchResponse
Expand All @@ -93,8 +91,8 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
// Get all package names in the org
let packageNames: string[]
try {
const { data } = await cachedFetch<Record<string, string>>(
`${NPM_REGISTRY}/-/org/${encodeURIComponent(org)}/package`,
const { data } = await $npmRegistry<Record<string, string>>(
`/-/org/${encodeURIComponent(org)}/package`,
{ signal },
)
packageNames = Object.keys(data)
Expand Down Expand Up @@ -124,10 +122,9 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
async name => {
try {
const encoded = encodePackageName(name)
const { data: pkg } = await cachedFetch<MinimalPackument>(
`${NPM_REGISTRY}/${encoded}`,
{ signal },
)
const { data: pkg } = await $npmRegistry<MinimalPackument>(`/${encoded}`, {
signal,
})
return pkg
} catch {
return null
Expand Down
5 changes: 3 additions & 2 deletions app/composables/npm/useOutdatedDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
isNonSemverConstraint,
constraintIncludesPrerelease,
} from '~/utils/npm/outdated-dependencies'
import { NPM_REGISTRY } from '~/utils/npm/common'

// Cache for packument fetches to avoid duplicate requests across components
const packumentCache = new Map<string, Promise<Packument | null>>()
Expand All @@ -21,6 +20,8 @@ async function checkDependencyOutdated(
packageName: string,
constraint: string,
): Promise<OutdatedDependencyInfo | null> {
const { $npmRegistry } = useNuxtApp()

if (isNonSemverConstraint(constraint)) {
return null
}
Expand All @@ -31,7 +32,7 @@ async function checkDependencyOutdated(
if (cached) {
packument = await cached
} else {
const promise = cachedFetch<Packument>(`${NPM_REGISTRY}/${encodePackageName(packageName)}`)
const promise = $npmRegistry<Packument>(`/${encodePackageName(packageName)}`)
.then(({ data }) => data)
.catch(() => null)
packumentCache.set(packageName, promise)
Expand Down
7 changes: 2 additions & 5 deletions app/composables/npm/usePackage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Packument, SlimPackument, SlimVersion, SlimPackumentVersion } from '#shared/types'
import { NPM_REGISTRY } from '~/utils/npm/common'
import { extractInstallScriptsInfo } from '~/utils/install-scripts'

/** Number of recent versions to include in initial payload */
Expand Down Expand Up @@ -92,13 +91,11 @@ export function usePackage(
name: MaybeRefOrGetter<string>,
requestedVersion?: MaybeRefOrGetter<string | null>,
) {
const cachedFetch = useCachedFetch()

const asyncData = useLazyAsyncData(
() => `package:${toValue(name)}:${toValue(requestedVersion) ?? ''}`,
async (_nuxtApp, { signal }) => {
async ({ $npmRegistry }, { signal }) => {
const encodedName = encodePackageName(toValue(name))
const { data: r, isStale } = await cachedFetch<Packument>(`${NPM_REGISTRY}/${encodedName}`, {
const { data: r, isStale } = await $npmRegistry<Packument>(`/${encodedName}`, {
signal,
})
const reqVer = toValue(requestedVersion)
Expand Down
9 changes: 3 additions & 6 deletions app/composables/npm/usePackageDownloads.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import type { NpmDownloadCount } from '#shared/types'
import { NPM_API } from '~/utils/npm/common'

export function usePackageDownloads(
name: MaybeRefOrGetter<string>,
period: MaybeRefOrGetter<'last-day' | 'last-week' | 'last-month' | 'last-year'> = 'last-week',
) {
const cachedFetch = useCachedFetch()

const asyncData = useLazyAsyncData(
() => `downloads:${toValue(name)}:${toValue(period)}`,
async (_nuxtApp, { signal }) => {
async ({ $npmApi }, { signal }) => {
const encodedName = encodePackageName(toValue(name))
const { data, isStale } = await cachedFetch<NpmDownloadCount>(
`${NPM_API}/downloads/point/${toValue(period)}/${encodedName}`,
const { data, isStale } = await $npmApi<NpmDownloadCount>(
`/downloads/point/${toValue(period)}/${encodedName}`,
{ signal },
)
return { ...data, isStale }
Expand Down
15 changes: 11 additions & 4 deletions app/composables/useCachedFetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CachedFetchResult } from '#shared/utils/fetch-cache-config'
import defu from 'defu'

/**
* Get the cachedFetch function from the current request context.
Expand Down Expand Up @@ -34,9 +35,12 @@ export function useCachedFetch(): CachedFetchFunction {
return async <T = unknown>(
url: string,
options: Parameters<typeof $fetch>[1] = {},
_ttl?: number,
_ttl: number = FETCH_CACHE_DEFAULT_TTL,
): Promise<CachedFetchResult<T>> => {
const data = (await $fetch<T>(url, options)) as T
const defaultFetchOptions: Parameters<typeof $fetch>[1] = {
cache: 'force-cache',
}
const data = (await $fetch<T>(url, defu(options, defaultFetchOptions))) as T
return { data, isStale: false, cachedAt: null }
}
}
Expand All @@ -55,9 +59,12 @@ export function useCachedFetch(): CachedFetchFunction {
return async <T = unknown>(
url: string,
options: Parameters<typeof $fetch>[1] = {},
_ttl?: number,
_ttl: number = FETCH_CACHE_DEFAULT_TTL,
): Promise<CachedFetchResult<T>> => {
const data = (await $fetch<T>(url, options)) as T
const defaultFetchOptions: Parameters<typeof $fetch>[1] = {
cache: 'force-cache',
}
const data = (await $fetch<T>(url, defu(options, defaultFetchOptions))) as T
return { data, isStale: false, cachedAt: null }
}
}
2 changes: 0 additions & 2 deletions app/pages/search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,6 @@ interface ValidatedSuggestion {
/** Cache for existence checks to avoid repeated API calls */
const existenceCache = ref<Record<string, boolean | 'pending'>>({})

const NPM_REGISTRY = 'https://registry.npmjs.org'

interface NpmSearchResponse {
total: number
objects: Array<{ package: { name: string } }>
Expand Down
22 changes: 22 additions & 0 deletions app/plugins/npm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export default defineNuxtPlugin(() => {
const cachedFetch = useCachedFetch()

return {
provide: {
npmRegistry: <T>(
url: Parameters<CachedFetchFunction>[0],
options?: Parameters<CachedFetchFunction>[1],
ttl?: Parameters<CachedFetchFunction>[2],
) => {
return cachedFetch<T>(url, { baseURL: NPM_REGISTRY, ...options }, ttl)
},
npmApi: <T>(
url: Parameters<CachedFetchFunction>[0],
options?: Parameters<CachedFetchFunction>[1],
ttl?: Parameters<CachedFetchFunction>[2],
) => {
return cachedFetch<T>(url, { baseURL: NPM_API, ...options }, ttl)
},
},
}
})
8 changes: 4 additions & 4 deletions app/utils/npm/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { PackageVersionInfo } from '#shared/types'
import { getVersions } from 'fast-npm-meta'
import { compare } from 'semver'
import { NPM_API } from './common'

type NpmDownloadsRangeResponse = {
start: string
Expand All @@ -19,10 +18,11 @@ export async function fetchNpmDownloadsRange(
start: string,
end: string,
): Promise<NpmDownloadsRangeResponse> {
const { $npmApi } = useNuxtApp()
const encodedName = encodePackageName(packageName)
return await $fetch<NpmDownloadsRangeResponse>(
`${NPM_API}/downloads/range/${start}:${end}/${encodedName}`,
)
return (
await $npmApi<NpmDownloadsRangeResponse>(`/downloads/range/${start}:${end}/${encodedName}`)
).data
}

// ============================================================================
Expand Down
3 changes: 0 additions & 3 deletions app/utils/npm/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
export const NPM_REGISTRY = 'https://registry.npmjs.org'
export const NPM_API = 'https://api.npmjs.org'

/**
* Constructs a scope:team string in the format expected by npm.
* npm operations require the format @scope:team (with @ prefix).
Expand Down
3 changes: 1 addition & 2 deletions app/utils/package-name.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import validatePackageName from 'validate-npm-package-name'
import { NPM_REGISTRY } from '#shared/utils/constants'

/**
* Normalize a package name for comparison by removing common variations.
Expand Down Expand Up @@ -70,8 +71,6 @@ export interface CheckNameResult {
similarPackages?: SimilarPackage[]
}

const NPM_REGISTRY = 'https://registry.npmjs.org'

export async function checkPackageExists(
name: string,
options: Parameters<typeof $fetch>[1] = {},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"@vitest/coverage-v8": "4.0.18",
"@vue/test-utils": "2.4.6",
"axe-core": "4.11.1",
"defu": "6.1.4",
"fast-check": "4.5.3",
"fast-npm-meta": "1.0.0",
"knip": "5.83.0",
Expand Down
1 change: 1 addition & 0 deletions shared/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const CACHE_MAX_AGE_ONE_YEAR = 60 * 60 * 24 * 365

// API Strings
export const NPM_REGISTRY = 'https://registry.npmjs.org'
export const NPM_API = 'https://api.npmjs.org'
export const ERROR_PACKAGE_ANALYSIS_FAILED = 'Failed to analyze package.'
export const ERROR_PACKAGE_VERSION_AND_FILE_FAILED = 'Version and file path are required.'
export const ERROR_PACKAGE_REQUIREMENTS_FAILED =
Expand Down
8 changes: 3 additions & 5 deletions test/nuxt/composables/use-npm-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('usePackageDownloads', () => {

// Check that fetch was called with the correct URL (first argument)
expect(fetchSpy).toHaveBeenCalled()
expect(fetchSpy.mock.calls[0]?.[0]).toBe('https://api.npmjs.org/downloads/point/last-week/vue')
expect(fetchSpy.mock.calls[0]?.[0]).toBe('/downloads/point/last-week/vue')
expect(data.value?.downloads).toBe(1234567)
})

Expand All @@ -40,7 +40,7 @@ describe('usePackageDownloads', () => {

// Check that fetch was called with the correct URL (first argument)
expect(fetchSpy).toHaveBeenCalled()
expect(fetchSpy.mock.calls[0]?.[0]).toBe('https://api.npmjs.org/downloads/point/last-month/vue')
expect(fetchSpy.mock.calls[0]?.[0]).toBe('/downloads/point/last-month/vue')
})

it('should encode scoped package names', async () => {
Expand All @@ -54,8 +54,6 @@ describe('usePackageDownloads', () => {

// Check that fetch was called with the correct URL (first argument)
expect(fetchSpy).toHaveBeenCalled()
expect(fetchSpy.mock.calls[0]?.[0]).toBe(
'https://api.npmjs.org/downloads/point/last-week/@vue%2Fcore',
)
expect(fetchSpy.mock.calls[0]?.[0]).toBe('/downloads/point/last-week/@vue%2Fcore')
})
})
1 change: 1 addition & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default defineConfig({
},
browser: {
enabled: true,
headless: true,
provider: playwright(),
instances: [{ browser: 'chromium', headless: true }],
},
Expand Down
Loading