Skip to content

Commit 1a62c13

Browse files
authored
feat: modules db integration with nuxi module add (#197)
1 parent fe8ef7c commit 1a62c13

2 files changed

Lines changed: 116 additions & 10 deletions

File tree

src/commands/module/_utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export const categories = [
2727
export interface ModuleCompatibility {
2828
nuxt: string
2929
requires: { bridge?: boolean | 'optional' }
30+
versionMap: {
31+
[nuxtVersion: string]: string
32+
}
3033
}
3134

3235
export interface MaintainerInfo {

src/commands/module/add.ts

Lines changed: 113 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import { existsSync } from 'node:fs'
55
import { loadFile, writeFile, parseModule, ProxifiedModule } from 'magicast'
66
import consola from 'consola'
77
import { addDependency } from 'nypm'
8+
import {
9+
NuxtModule,
10+
checkNuxtCompatibility,
11+
fetchModules,
12+
getNuxtVersion,
13+
} from './_utils'
14+
import { satisfies } from 'semver'
815

916
export default defineCommand({
1017
meta: {
@@ -29,16 +36,18 @@ export default defineCommand({
2936
async setup(ctx) {
3037
const cwd = resolve(ctx.args.cwd || '.')
3138

32-
// TODO: Resolve and validate npm package name first
33-
const npmPackage = ctx.args.moduleName
39+
const r = await resolveModule(ctx.args.moduleName, cwd)
40+
if (r === false) {
41+
return
42+
}
3443

3544
// Add npm dependency
3645
if (!ctx.args.skipInstall) {
37-
consola.info(`Installing dev dependency \`${npmPackage}\``)
38-
await addDependency(npmPackage, { cwd, dev: true }).catch((err) => {
46+
consola.info(`Installing dev dependency \`${r.pkg}\``)
47+
await addDependency(r.pkg, { cwd, dev: true }).catch((err) => {
3948
consola.error(err)
4049
consola.error(
41-
`Please manually install \`${npmPackage}\` as a dev dependency`,
50+
`Please manually install \`${r.pkg}\` as a dev dependency`,
4251
)
4352
})
4453
}
@@ -50,17 +59,17 @@ export default defineCommand({
5059
config.modules = []
5160
}
5261
for (let i = 0; i < config.modules.length; i++) {
53-
if (config.modules[i] === npmPackage) {
54-
consola.info(`\`${npmPackage}\` is already in the \`modules\``)
62+
if (config.modules[i] === r.pkgName) {
63+
consola.info(`\`${r.pkgName}\` is already in the \`modules\``)
5564
return
5665
}
5766
}
58-
consola.info(`Adding \`${npmPackage}\` to the \`modules\``)
59-
config.modules.push(npmPackage)
67+
consola.info(`Adding \`${r.pkgName}\` to the \`modules\``)
68+
config.modules.push(r.pkgName)
6069
}).catch((err) => {
6170
consola.error(err)
6271
consola.error(
63-
`Please manually add \`${npmPackage}\` to the \`modules\` in \`nuxt.config.ts\``,
72+
`Please manually add \`${r.pkgName}\` to the \`modules\` in \`nuxt.config.ts\``,
6473
)
6574
})
6675
}
@@ -102,3 +111,97 @@ export default defineNuxtConfig({
102111
modules: []
103112
})`
104113
}
114+
115+
// Based on https://github.com/dword-design/package-name-regex
116+
const packageRegex =
117+
/^(@[a-z0-9-~][a-z0-9-._~]*\/)?([a-z0-9-~][a-z0-9-._~]*)(@[^@]+)?$/
118+
119+
async function resolveModule(
120+
moduleName: string,
121+
cwd: string,
122+
): Promise<
123+
| false
124+
| {
125+
nuxtModule?: NuxtModule
126+
pkg: string
127+
pkgName: string
128+
pkgVersion: string
129+
}
130+
> {
131+
let pkgName = moduleName
132+
let pkgVersion: string | undefined
133+
134+
const reMatch = moduleName.match(packageRegex)
135+
if (reMatch) {
136+
if (reMatch[3]) {
137+
pkgName = `${reMatch[1] || ''}${reMatch[2] || ''}`
138+
pkgVersion = reMatch[3].slice(1)
139+
}
140+
} else {
141+
consola.error(`Invalid package name \`${pkgName}\`.`)
142+
return false
143+
}
144+
145+
const modulesDB = await fetchModules().catch((err) => {
146+
consola.warn('Cannot search in the Nuxt Modules database: ' + err)
147+
return []
148+
})
149+
150+
const matchedModule = modulesDB.find(
151+
(module) => module.name === moduleName || module.npm === pkgName,
152+
)
153+
154+
if (matchedModule?.npm) {
155+
pkgName = matchedModule.npm
156+
}
157+
158+
if (matchedModule && matchedModule.compatibility.nuxt) {
159+
// Get local Nuxt version
160+
const nuxtVersion = await getNuxtVersion(cwd)
161+
162+
// Check for Module Compatibility
163+
if (!checkNuxtCompatibility(matchedModule, nuxtVersion)) {
164+
consola.warn(
165+
`The module \`${pkgName}\` is not compatible with Nuxt \`${nuxtVersion}\` (requires \`${matchedModule.compatibility.nuxt}\`)`,
166+
)
167+
const shouldContinue = await consola.prompt(
168+
'Do you want to continue installing incompatible version?',
169+
{
170+
type: 'confirm',
171+
initial: false,
172+
},
173+
)
174+
if (shouldContinue !== true) {
175+
return false
176+
}
177+
}
178+
179+
// Match corresponding version of module for local Nuxt version
180+
const versionMap = matchedModule.compatibility.versionMap
181+
if (versionMap) {
182+
for (const [_nuxtVersion, _moduleVersion] of Object.entries(versionMap)) {
183+
if (satisfies(nuxtVersion, _nuxtVersion)) {
184+
if (!pkgVersion) {
185+
pkgVersion = _moduleVersion
186+
} else {
187+
consola.warn(
188+
`Recommended version of \`${pkgName}\` for Nuxt \`${nuxtVersion}\` is \`${_moduleVersion}\` but you have requested \`${pkgVersion}\``,
189+
)
190+
pkgVersion = await consola.prompt('Choose a version:', {
191+
type: 'select',
192+
options: [_moduleVersion, pkgVersion],
193+
})
194+
}
195+
break
196+
}
197+
}
198+
}
199+
}
200+
201+
return {
202+
nuxtModule: matchedModule,
203+
pkg: `${pkgName}@${pkgVersion || 'latest'}`,
204+
pkgName,
205+
pkgVersion: pkgVersion || 'latest',
206+
}
207+
}

0 commit comments

Comments
 (0)