diff --git a/nuxt.config.ts b/nuxt.config.ts index e06326cbcc..a74644486f 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -22,6 +22,14 @@ export default defineNuxtConfig({ app: { head: { htmlAttrs: { lang: 'en' }, + link: [ + { + rel: 'search', + type: 'application/opensearchdescription+xml', + title: 'npm', + href: '/opensearch.xml', + }, + ], }, }, @@ -39,6 +47,7 @@ export default defineNuxtConfig({ routeRules: { '/': { prerender: true }, + '/opensearch.xml': { isr: true }, '/**': { isr: 60 }, '/package/**': { isr: 60 }, '/search': { isr: false, cache: false }, diff --git a/server/api/opensearch/suggestions.get.ts b/server/api/opensearch/suggestions.get.ts new file mode 100644 index 0000000000..335728405d --- /dev/null +++ b/server/api/opensearch/suggestions.get.ts @@ -0,0 +1,27 @@ +import type { NpmSearchResponse } from '#shared/types' +import { NPM_REGISTRY } from '#shared/utils/constants' + +export default defineCachedEventHandler( + async event => { + const query = getQuery(event) + const q = String(query.q || '').trim() + + if (!q) { + return [q, []] + } + + const params = new URLSearchParams({ text: q, size: '10' }) + const response = await $fetch(`${NPM_REGISTRY}/-/v1/search?${params}`) + + const suggestions = response.objects.map(obj => obj.package.name) + return [q, suggestions] + }, + { + maxAge: 60, + swr: true, + getKey: event => { + const query = getQuery(event) + return `opensearch-suggestions:${query.q || ''}` + }, + }, +) diff --git a/server/routes/opensearch.xml.get.ts b/server/routes/opensearch.xml.get.ts new file mode 100644 index 0000000000..b47cfd37de --- /dev/null +++ b/server/routes/opensearch.xml.get.ts @@ -0,0 +1,16 @@ +export default defineEventHandler(async event => { + const url = getRequestURL(event) + const origin = url.origin + return ` + + + npm + Search npm packages on npmx.dev + UTF-8 + ${origin}/favicon.svg + + + + + `.trim() +})