mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 23:22:02 +00:00
feat(kit): module compatibility utils (#21246)
This commit is contained in:
parent
a31899af65
commit
c0b3d26b00
@ -2,6 +2,10 @@ import satisfies from 'semver/functions/satisfies.js' // npm/node-semver#381
|
|||||||
import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/schema'
|
import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/schema'
|
||||||
import { useNuxt } from './context'
|
import { useNuxt } from './context'
|
||||||
|
|
||||||
|
export function normalizeSemanticVersion (version: string) {
|
||||||
|
return version.replace(/-[0-9]+\.[0-9a-f]+/, '') // Remove edge prefix
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check version constraints and return incompatibility issues as an array
|
* Check version constraints and return incompatibility issues as an array
|
||||||
*/
|
*/
|
||||||
@ -11,9 +15,7 @@ export async function checkNuxtCompatibility (constraints: NuxtCompatibility, nu
|
|||||||
// Nuxt version check
|
// Nuxt version check
|
||||||
if (constraints.nuxt) {
|
if (constraints.nuxt) {
|
||||||
const nuxtVersion = getNuxtVersion(nuxt)
|
const nuxtVersion = getNuxtVersion(nuxt)
|
||||||
const nuxtSemanticVersion = nuxtVersion
|
if (!satisfies(normalizeSemanticVersion(nuxtVersion), constraints.nuxt, { includePrerelease: true })) {
|
||||||
.replace(/-[0-9]+\.[0-9a-f]+/, '') // Remove edge prefix
|
|
||||||
if (!satisfies(nuxtSemanticVersion, constraints.nuxt, { includePrerelease: true })) {
|
|
||||||
issues.push({
|
issues.push({
|
||||||
name: 'nuxt',
|
name: 'nuxt',
|
||||||
message: `Nuxt version \`${constraints.nuxt}\` is required but currently using \`${nuxtVersion}\``
|
message: `Nuxt version \`${constraints.nuxt}\` is required but currently using \`${nuxtVersion}\``
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Module
|
// Module
|
||||||
export * from './module/define'
|
export * from './module/define'
|
||||||
export * from './module/install'
|
export * from './module/install'
|
||||||
|
export * from './module/compatibility'
|
||||||
|
|
||||||
// Loader
|
// Loader
|
||||||
export * from './loader/config'
|
export * from './loader/config'
|
||||||
|
45
packages/kit/src/module/compatibility.test.ts
Normal file
45
packages/kit/src/module/compatibility.test.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { loadNuxt } from '../loader/nuxt'
|
||||||
|
import { getNuxtModuleVersion, hasNuxtModule, hasNuxtModuleCompatibility } from './compatibility'
|
||||||
|
import { defineNuxtModule } from './define'
|
||||||
|
|
||||||
|
describe('nuxt module compatibility', () => {
|
||||||
|
it('check module installed', async () => {
|
||||||
|
const nuxt = await loadNuxt({
|
||||||
|
overrides: {
|
||||||
|
modules: [
|
||||||
|
defineNuxtModule({
|
||||||
|
meta: {
|
||||||
|
name: 'nuxt-module-foo'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(hasNuxtModule('nuxt-module-foo', nuxt)).toStrictEqual(true)
|
||||||
|
await nuxt.close()
|
||||||
|
})
|
||||||
|
it('can retrieve module version from module instance', async () => {
|
||||||
|
const nuxt = await loadNuxt({})
|
||||||
|
const module = defineNuxtModule({
|
||||||
|
meta: {
|
||||||
|
name: 'nuxt-module-foo',
|
||||||
|
version: '1.0.0'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(await getNuxtModuleVersion(module, nuxt)).toEqual('1.0.0')
|
||||||
|
await nuxt.close()
|
||||||
|
})
|
||||||
|
it('check module instance version compatibility', async () => {
|
||||||
|
const nuxt = await loadNuxt({})
|
||||||
|
const module = defineNuxtModule({
|
||||||
|
meta: {
|
||||||
|
name: 'nuxt-module-foo',
|
||||||
|
version: '1.0.0'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(await hasNuxtModuleCompatibility(module, '^1.0.0', nuxt)).toStrictEqual(true)
|
||||||
|
expect(await hasNuxtModuleCompatibility(module, '^2.0.0', nuxt)).toStrictEqual(false)
|
||||||
|
await nuxt.close()
|
||||||
|
})
|
||||||
|
})
|
54
packages/kit/src/module/compatibility.ts
Normal file
54
packages/kit/src/module/compatibility.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import satisfies from 'semver/functions/satisfies.js' // npm/node-semver#381
|
||||||
|
import type { Nuxt, NuxtModule } from '@nuxt/schema'
|
||||||
|
import { useNuxt } from '../context'
|
||||||
|
import { normalizeSemanticVersion } from '../compatibility'
|
||||||
|
import { loadNuxtModuleInstance } from './install'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a Nuxt module is installed by name.
|
||||||
|
*
|
||||||
|
* This will check both the installed modules and the modules to be installed. Note
|
||||||
|
* that it cannot detect if a module is _going to be_ installed programmatically by another module.
|
||||||
|
*/
|
||||||
|
export function hasNuxtModule (moduleName: string, nuxt: Nuxt = useNuxt()) : boolean {
|
||||||
|
return nuxt.options._installedModules.some(({ meta }) => meta.name === moduleName) ||
|
||||||
|
nuxt.options.modules.includes(moduleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a Nuxt Module is compatible with a given semver version.
|
||||||
|
*/
|
||||||
|
export async function hasNuxtModuleCompatibility (module: string | NuxtModule, semverVersion: string, nuxt: Nuxt = useNuxt()): Promise<boolean> {
|
||||||
|
const version = await getNuxtModuleVersion(module, nuxt)
|
||||||
|
if (!version) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return satisfies(normalizeSemanticVersion(version), semverVersion, {
|
||||||
|
includePrerelease: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version of a Nuxt module.
|
||||||
|
*
|
||||||
|
* Scans installed modules for the version, if it's not found it will attempt to load the module instance and get the version from there.
|
||||||
|
*/
|
||||||
|
export async function getNuxtModuleVersion (module: string | NuxtModule, nuxt: Nuxt | any = useNuxt()): Promise<string | false> {
|
||||||
|
const moduleMeta = (typeof module === 'string' ? { name: module } : await module.getMeta?.()) || {}
|
||||||
|
if (moduleMeta.version) { return moduleMeta.version }
|
||||||
|
// need a name from here
|
||||||
|
if (!moduleMeta.name) { return false }
|
||||||
|
// maybe the version got attached within the installed module instance?
|
||||||
|
const version = nuxt.options._installedModules
|
||||||
|
// @ts-expect-error _installedModules is not typed
|
||||||
|
.filter(m => m.meta.name === moduleMeta.name).map(m => m.meta.version)?.[0]
|
||||||
|
if (version) {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
// it's possible that the module will be installed, it just hasn't been done yet, preemptively load the instance
|
||||||
|
if (typeof module !== 'string' && nuxt.options.modules.includes(moduleMeta.name)) {
|
||||||
|
const { buildTimeModuleMeta } = await loadNuxtModuleInstance(moduleMeta.name, nuxt)
|
||||||
|
return buildTimeModuleMeta.version || false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { lstatSync } from 'node:fs'
|
import { existsSync, promises as fsp, lstatSync } from 'node:fs'
|
||||||
import type { Nuxt, NuxtModule } from '@nuxt/schema'
|
import type { ModuleMeta, Nuxt, NuxtModule } from '@nuxt/schema'
|
||||||
import { dirname, isAbsolute } from 'pathe'
|
import { dirname, isAbsolute, join } from 'pathe'
|
||||||
|
import { defu } from 'defu'
|
||||||
import { isNuxt2 } from '../compatibility'
|
import { isNuxt2 } from '../compatibility'
|
||||||
import { useNuxt } from '../context'
|
import { useNuxt } from '../context'
|
||||||
import { requireModule } from '../internal/cjs'
|
import { requireModule } from '../internal/cjs'
|
||||||
@ -8,9 +9,8 @@ import { importModule } from '../internal/esm'
|
|||||||
import { resolveAlias, resolvePath } from '../resolve'
|
import { resolveAlias, resolvePath } from '../resolve'
|
||||||
|
|
||||||
/** Installs a module on a Nuxt instance. */
|
/** Installs a module on a Nuxt instance. */
|
||||||
export async function installModule (moduleToInstall: string | NuxtModule, _inlineOptions?: any, _nuxt?: Nuxt) {
|
export async function installModule (moduleToInstall: string | NuxtModule, inlineOptions?: any, nuxt: Nuxt = useNuxt()) {
|
||||||
const nuxt = useNuxt()
|
const { nuxtModule, buildTimeModuleMeta } = await loadNuxtModuleInstance(moduleToInstall, nuxt)
|
||||||
const { nuxtModule, inlineOptions } = await normalizeModule(moduleToInstall, _inlineOptions)
|
|
||||||
|
|
||||||
// Call module
|
// Call module
|
||||||
const res = (
|
const res = (
|
||||||
@ -29,7 +29,7 @@ export async function installModule (moduleToInstall: string | NuxtModule, _inli
|
|||||||
|
|
||||||
nuxt.options._installedModules = nuxt.options._installedModules || []
|
nuxt.options._installedModules = nuxt.options._installedModules || []
|
||||||
nuxt.options._installedModules.push({
|
nuxt.options._installedModules.push({
|
||||||
meta: await nuxtModule.getMeta?.(),
|
meta: defu(await nuxtModule.getMeta?.(), buildTimeModuleMeta),
|
||||||
timings: res.timings,
|
timings: res.timings,
|
||||||
entryPath: typeof moduleToInstall === 'string' ? resolveAlias(moduleToInstall) : undefined
|
entryPath: typeof moduleToInstall === 'string' ? resolveAlias(moduleToInstall) : undefined
|
||||||
})
|
})
|
||||||
@ -48,9 +48,8 @@ export const normalizeModuleTranspilePath = (p: string) => {
|
|||||||
return p.split('node_modules/').pop() as string
|
return p.split('node_modules/').pop() as string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function normalizeModule (nuxtModule: string | NuxtModule, inlineOptions?: any) {
|
export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, nuxt: Nuxt = useNuxt()) {
|
||||||
const nuxt = useNuxt()
|
let buildTimeModuleMeta: ModuleMeta = {}
|
||||||
|
|
||||||
// Import if input is string
|
// Import if input is string
|
||||||
if (typeof nuxtModule === 'string') {
|
if (typeof nuxtModule === 'string') {
|
||||||
const src = await resolvePath(nuxtModule)
|
const src = await resolvePath(nuxtModule)
|
||||||
@ -61,6 +60,10 @@ async function normalizeModule (nuxtModule: string | NuxtModule, inlineOptions?:
|
|||||||
console.error(`Error while requiring module \`${nuxtModule}\`: ${error}`)
|
console.error(`Error while requiring module \`${nuxtModule}\`: ${error}`)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
// nuxt-module-builder generates a module.json with metadata including the version
|
||||||
|
if (existsSync(join(dirname(src), 'module.json'))) {
|
||||||
|
buildTimeModuleMeta = JSON.parse(await fsp.readFile(join(dirname(src), 'module.json'), 'utf-8'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw error if input is not a function
|
// Throw error if input is not a function
|
||||||
@ -68,5 +71,5 @@ async function normalizeModule (nuxtModule: string | NuxtModule, inlineOptions?:
|
|||||||
throw new TypeError('Nuxt module should be a function: ' + nuxtModule)
|
throw new TypeError('Nuxt module should be a function: ' + nuxtModule)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { nuxtModule, inlineOptions } as { nuxtModule: NuxtModule<any>, inlineOptions: undefined | Record<string, any> }
|
return { nuxtModule, buildTimeModuleMeta } as { nuxtModule: NuxtModule<any>, buildTimeModuleMeta: ModuleMeta }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user