Skip to content

Commit

Permalink
feat(nuxi): module search (#21)
Browse files Browse the repository at this point in the history
Co-authored-by: Pooya Parsa <[email protected]>
  • Loading branch information
pi0 committed Jun 19, 2023
1 parent ac184db commit 9a6fa0e
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 4 deletions.
78 changes: 74 additions & 4 deletions src/commands/module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { resolve } from 'pathe'
import { defineNuxtCommand } from './index'
import { existsSync } from 'node:fs'
import { loadFile, writeFile, parseCode } from 'magicast'
import type { ModuleNode } from 'magicast'
import { loadFile, writeFile, parseModule, ProxifiedModule } from 'magicast'
import consola from 'consola'
import type { Argv } from 'mri'
import { addDependency } from 'nypm'
import { fetchModules } from '../utils/modules'
import Fuse from 'fuse.js'
import { upperFirst, kebabCase } from 'scule'
import { bold, green, magenta, cyan, gray } from 'colorette'

export default defineNuxtCommand({
meta: {
Expand All @@ -17,6 +20,8 @@ export default defineNuxtCommand({
const command = args._.shift()
if (command === 'add') {
return addModule(args)
} else if (command === 'search') {
return findModuleByKeywords(args._.join(' '))
}
throw new Error(`Unknown sub-command: module ${command}`)
},
Expand Down Expand Up @@ -67,20 +72,85 @@ async function addModule(args: Argv) {
}
}

async function findModuleByKeywords(query: string) {
const modules = await fetchModules()
const fuse = new Fuse(modules, {
threshold: 0.1,
keys: [
{ name: 'name', weight: 1 },
{ name: 'npm', weight: 1 },
{ name: 'repo', weight: 1 },
{ name: 'tags', weight: 1 },
{ name: 'category', weight: 1 },
{ name: 'description', weight: 0.5 },
{ name: 'maintainers.name', weight: 0.5 },
{ name: 'maintainers.github', weight: 0.5 },
],
})

const results = fuse.search(query).map((result) => {
const res: Record<string, string> = {
name: bold(result.item.name),
homepage: cyan(result.item.website),
repository: gray(result.item.github),
description: gray(result.item.description),
package: gray(result.item.npm),
install: cyan(`nuxt module add ${result.item.npm}`),
}
if (result.item.github === result.item.website) {
delete res.homepage
}
if (result.item.name === result.item.npm) {
delete res.packageName
}
return res
})

if (!results.length) {
consola.info(`No nuxt modules found matching query ${magenta(query)}`)
return
}

consola.success(
`Found ${results.length} nuxt ${
results.length > 1 ? 'modules' : 'module'
} matching ${cyan(query)}:\n`
)
for (const foundModule of results) {
let maxLength = 0
const entries = Object.entries(foundModule).map(([key, val]) => {
const label = upperFirst(kebabCase(key)).replace(/-/g, ' ')
if (label.length > maxLength) {
maxLength = label.length
}
return [label, val || '-']
})
let infoStr = ''
for (const [label, value] of entries) {
infoStr +=
bold(label === 'Install' ? '→ ' : '- ') +
green(label.padEnd(maxLength + 2)) +
value +
'\n'
}
console.log(infoStr)
}
}

// -- Internal Utils --

async function updateNuxtConfig(
rootDir: string,
update: (config: any) => void
) {
let _module: ModuleNode
let _module: ProxifiedModule
const nuxtConfigFile = resolve(rootDir, 'nuxt.config.ts')
if (existsSync(nuxtConfigFile)) {
consola.info('Updating `nuxt.config.ts`')
_module = await loadFile(nuxtConfigFile)
} else {
consola.info('Creating `nuxt.config.ts`')
_module = parseCode(getDefaultNuxtConfig())
_module = parseModule(getDefaultNuxtConfig())
}
const defaultExport = _module.exports.default
if (!defaultExport) {
Expand Down
73 changes: 73 additions & 0 deletions src/utils/modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { $fetch } from 'ofetch'

export const categories = [
'Analytics',
'CMS',
'CSS',
'Database',
'Date',
'Deployment',
'Devtools',
'Extensions',
'Ecommerce',
'Fonts',
'Images',
'Libraries',
'Monitoring',
'Payment',
'Performance',
'Request',
'SEO',
'Security',
'UI',
]

export interface ModuleCompatibility {
nuxt: string
requires: { bridge?: boolean | 'optional' }
}

export interface MaintainerInfo {
name: string
github: string
twitter?: string
}

export interface GithubContributor {
username: string
name?: string
avatar_url?: string
}

export type CompatibilityStatus = 'working' | 'wip' | 'unknown' | 'not-working'
export type ModuleType = 'community' | 'official' | '3rd-party'

export interface NuxtModule {
name: string
description: string
repo: string
npm: string
icon?: string
github: string
website: string
learn_more: string
category: (typeof categories)[number]
type: ModuleType
maintainers: MaintainerInfo[]
contributors?: GithubContributor[]
compatibility: ModuleCompatibility

// Fetched in realtime API for modules.nuxt.org
downloads?: number
tags?: string[]
stars?: number
publishedAt?: number
createdAt?: number
}

export async function fetchModules(): Promise<NuxtModule[]> {
const data = await $fetch<NuxtModule[]>(
'https://cdn.jsdelivr.net/npm/@nuxt/modules@latest/modules.json'
)
return data
}

0 comments on commit 9a6fa0e

Please sign in to comment.