Skip to content
Merged
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: 3 additions & 0 deletions src/commands/module/_utils.ts
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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',
}
}