Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: modules db integration with nuxi module add #197

Merged
merged 13 commits into from
Sep 19, 2023
3 changes: 3 additions & 0 deletions src/commands/module/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export const categories = [
export interface ModuleCompatibility {
nuxt: string
requires: { bridge?: boolean | 'optional' }
versionMap: {
[nuxtVersion: string]: string
}
}

export interface MaintainerInfo {
Expand Down
123 changes: 113 additions & 10 deletions src/commands/module/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { existsSync } from 'node:fs'
import { loadFile, writeFile, parseModule, ProxifiedModule } from 'magicast'
import consola from 'consola'
import { addDependency } from 'nypm'
import {
NuxtModule,
checkNuxtCompatibility,
fetchModules,
getNuxtVersion,
} from './_utils'
import { satisfies } from 'semver'

export default defineCommand({
meta: {
Expand All @@ -29,16 +36,18 @@ export default defineCommand({
async setup(ctx) {
const cwd = resolve(ctx.args.cwd || '.')

// TODO: Resolve and validate npm package name first
const npmPackage = ctx.args.moduleName
const r = await resolveModule(ctx.args.moduleName, cwd)
if (r === false) {
return
}

// Add npm dependency
if (!ctx.args.skipInstall) {
consola.info(`Installing dev dependency \`${npmPackage}\``)
await addDependency(npmPackage, { cwd, dev: true }).catch((err) => {
consola.info(`Installing dev dependency \`${r.pkg}\``)
await addDependency(r.pkg, { cwd, dev: true }).catch((err) => {
consola.error(err)
consola.error(
`Please manually install \`${npmPackage}\` as a dev dependency`,
`Please manually install \`${r.pkg}\` as a dev dependency`,
)
})
}
Expand All @@ -50,17 +59,17 @@ export default defineCommand({
config.modules = []
}
for (let i = 0; i < config.modules.length; i++) {
if (config.modules[i] === npmPackage) {
consola.info(`\`${npmPackage}\` is already in the \`modules\``)
if (config.modules[i] === r.pkgName) {
consola.info(`\`${r.pkgName}\` is already in the \`modules\``)
return
}
}
consola.info(`Adding \`${npmPackage}\` to the \`modules\``)
config.modules.push(npmPackage)
consola.info(`Adding \`${r.pkgName}\` to the \`modules\``)
config.modules.push(r.pkgName)
}).catch((err) => {
consola.error(err)
consola.error(
`Please manually add \`${npmPackage}\` to the \`modules\` in \`nuxt.config.ts\``,
`Please manually add \`${r.pkgName}\` to the \`modules\` in \`nuxt.config.ts\``,
)
})
}
Expand Down Expand Up @@ -102,3 +111,97 @@ export default defineNuxtConfig({
modules: []
})`
}

// Based on https://github.com/dword-design/package-name-regex
const packageRegex =
/^(@[a-z0-9-~][a-z0-9-._~]*\/)?([a-z0-9-~][a-z0-9-._~]*)(@[^@]+)?$/

async function resolveModule(
moduleName: string,
cwd: string,
): Promise<
| false
| {
nuxtModule?: NuxtModule
pkg: string
pkgName: string
pkgVersion: string
}
> {
let pkgName = moduleName
let pkgVersion: string | undefined

const reMatch = moduleName.match(packageRegex)
if (reMatch) {
if (reMatch[3]) {
pkgName = `${reMatch[1] || ''}${reMatch[2] || ''}`
pkgVersion = reMatch[3].slice(1)
}
} else {
consola.error(`Invalid package name \`${pkgName}\`.`)
return false
}

const modulesDB = await fetchModules().catch((err) => {
consola.warn('Cannot search in the Nuxt Modules database: ' + err)
return []
})

const matchedModule = modulesDB.find(
(module) => module.name === moduleName || module.npm === pkgName,
)

if (matchedModule?.npm) {
pkgName = matchedModule.npm
}

if (matchedModule && matchedModule.compatibility.nuxt) {
// Get local Nuxt version
const nuxtVersion = await getNuxtVersion(cwd)

// Check for Module Compatibility
if (!checkNuxtCompatibility(matchedModule, nuxtVersion)) {
consola.warn(
`The module \`${pkgName}\` is not compatible with Nuxt \`${nuxtVersion}\` (requires \`${matchedModule.compatibility.nuxt}\`)`,
)
const shouldContinue = await consola.prompt(
'Do you want to continue installing incompatible version?',
{
type: 'confirm',
initial: false,
},
)
if (shouldContinue !== true) {
return false
}
}

// Match corresponding version of module for local Nuxt version
const versionMap = matchedModule.compatibility.versionMap
if (versionMap) {
for (const [_nuxtVersion, _moduleVersion] of Object.entries(versionMap)) {
if (satisfies(nuxtVersion, _nuxtVersion)) {
if (!pkgVersion) {
pkgVersion = _moduleVersion
} else {
consola.warn(
`Recommended version of \`${pkgName}\` for Nuxt \`${nuxtVersion}\` is \`${_moduleVersion}\` but you have requested \`${pkgVersion}\``,
)
pkgVersion = await consola.prompt('Choose a version:', {
type: 'select',
options: [_moduleVersion, pkgVersion],
})
}
break
}
}
}
}

return {
nuxtModule: matchedModule,
pkg: `${pkgName}@${pkgVersion || 'latest'}`,
pkgName,
pkgVersion: pkgVersion || 'latest',
}
}