diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index 27fcef31d71..8f28d14e2e4 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -3,6 +3,7 @@ import type { Argv } from 'mri'
export const commands = {
dev: () => import('./dev'),
build: () => import('./build'),
+ prepare: () => import('./prepare'),
usage: () => import('./usage')
}
diff --git a/packages/cli/src/commands/prepare.ts b/packages/cli/src/commands/prepare.ts
new file mode 100644
index 00000000000..def691ed274
--- /dev/null
+++ b/packages/cli/src/commands/prepare.ts
@@ -0,0 +1,57 @@
+import { promises as fsp } from 'fs'
+import { relative, resolve } from 'upath'
+import { cyan } from 'colorette'
+
+import { requireModule, getModulePaths, getNearestPackage } from '../utils/cjs'
+import { exists } from '../utils/fs'
+import { success } from '../utils/log'
+import { defineNuxtCommand } from './index'
+
+export default defineNuxtCommand({
+ meta: {
+ name: 'prepare',
+ usage: 'nu prepare',
+ description: 'Prepare nuxt for development/build'
+ },
+ async invoke (args) {
+ process.env.NODE_ENV = process.env.NODE_ENV || 'production'
+ const rootDir = resolve(args._[0] || '.')
+
+ const { loadNuxt } = requireModule('@nuxt/kit', rootDir) as typeof import('@nuxt/kit')
+ const nuxt = await loadNuxt({ rootDir })
+
+ const adHocModules = nuxt.options._majorVersion === 3
+ ? ['@nuxt/kit', '@nuxt/app', '@nuxt/nitro']
+ : ['@nuxt/kit']
+
+ const types = [
+ ...adHocModules,
+ // Modules
+ ...nuxt.options.buildModules,
+ ...nuxt.options.modules,
+ ...nuxt.options._modules
+ ].filter(f => typeof f === 'string')
+
+ const modulePaths = getModulePaths(nuxt.options.modulesDir)
+ const _references = await Promise.all(types.map(async (id) => {
+ const pkg = getNearestPackage(id, modulePaths)
+ return pkg ? `/// ` : await exists(id) && `/// `
+ })).then(arr => arr.filter(Boolean))
+
+ const references = Array.from(new Set(_references)) as string[]
+ await nuxt.callHook('prepare:types', { references })
+
+ const declarationPath = resolve(`${rootDir}/nuxt.d.ts`)
+
+ const declaration = [
+ '// Declarations auto generated by `nuxt prepare`. Please do not manually modify this file.',
+ '',
+ ...references,
+ ''
+ ].join('\n')
+
+ await fsp.writeFile(declarationPath, declaration)
+
+ success('Generated', cyan(relative(process.cwd(), declarationPath)))
+ }
+})
diff --git a/packages/cli/src/utils/cjs.ts b/packages/cli/src/utils/cjs.ts
index 3936bdb8d07..08ac1af4d9a 100644
--- a/packages/cli/src/utils/cjs.ts
+++ b/packages/cli/src/utils/cjs.ts
@@ -1,18 +1,34 @@
-import { normalize } from 'upath'
+import { normalize, dirname } from 'upath'
-export function resolveModule (id: string, paths?: string) {
- return normalize(require.resolve(id, {
- paths: [].concat(
- // @ts-ignore
- global.__NUXT_PREPATHS__,
- paths,
- process.cwd(),
- // @ts-ignore
- global.__NUXT_PATHS__
- ).filter(Boolean)
- }))
+export function getModulePaths (paths?: string | string[]): string[] {
+ return [].concat(
+ // @ts-ignore
+ global.__NUXT_PREPATHS__,
+ ...(Array.isArray(paths) ? paths : [paths]),
+ process.cwd(),
+ // @ts-ignore
+ global.__NUXT_PATHS__
+ ).filter(Boolean)
}
-export function requireModule (id: string, paths?: string) {
+export function resolveModule (id: string, paths?: string | string[]) {
+ return normalize(require.resolve(id, { paths: getModulePaths(paths) }))
+}
+
+export function tryResolveModule (id: string, paths?: string | string[]) {
+ try {
+ return resolveModule(id, paths)
+ } catch { return null }
+}
+
+export function requireModule (id: string, paths?: string | string[]) {
return require(resolveModule(id, paths))
}
+
+export function getNearestPackage (id: string, paths?: string | string[]) {
+ while (dirname(id) !== id) {
+ try { return requireModule(id + '/package.json', paths) } catch { }
+ id = dirname(id)
+ }
+ return null
+}
diff --git a/packages/cli/src/utils/fs.ts b/packages/cli/src/utils/fs.ts
new file mode 100644
index 00000000000..e646b352113
--- /dev/null
+++ b/packages/cli/src/utils/fs.ts
@@ -0,0 +1,11 @@
+import { promises as fsp } from 'fs'
+
+// Check if a file exists
+export async function exists (path: string) {
+ try {
+ await fsp.access(path)
+ return true
+ } catch {
+ return false
+ }
+}
diff --git a/packages/cli/src/utils/log.ts b/packages/cli/src/utils/log.ts
index ab3c895b07b..92001b96afd 100644
--- a/packages/cli/src/utils/log.ts
+++ b/packages/cli/src/utils/log.ts
@@ -1,5 +1,6 @@
-import { red, yellow, cyan } from 'colorette'
+import { green, red, yellow, cyan } from 'colorette'
-export const error = (...args) => console.error(red('[error]'), ...args)
-export const warn = (...args) => console.warn(yellow('[warn]'), ...args)
-export const info = (...args) => console.info(cyan('[info]'), ...args)
+export const error = (...args: any[]) => console.error(red('[error]'), ...args)
+export const warn = (...args: any[]) => console.warn(yellow('[warn]'), ...args)
+export const info = (...args: any[]) => console.info(cyan('[info]'), ...args)
+export const success = (...args: any[]) => console.log(green('[success]'), ...args)
diff --git a/packages/kit/src/types/hooks.ts b/packages/kit/src/types/hooks.ts
index 42bb644075b..fbabf01c253 100644
--- a/packages/kit/src/types/hooks.ts
+++ b/packages/kit/src/types/hooks.ts
@@ -66,6 +66,9 @@ export interface NuxtHooks {
'config': (options: NuxtConfig) => HookResult
'run:before': (options: { argv: string[], cmd: { name: string, usage: string, description: string, options: Record }, rootDir: string }) => HookResult
+ // nuxt-cli
+ 'prepare:types': (options: { references: string[] }) => HookResult
+
// @nuxt/core
'ready': (nuxt: Nuxt) => HookResult
'close': (nuxt: Nuxt) => HookResult
diff --git a/playground/nuxt.d.ts b/playground/nuxt.d.ts
new file mode 100644
index 00000000000..b1cb579f47a
--- /dev/null
+++ b/playground/nuxt.d.ts
@@ -0,0 +1,7 @@
+// Declarations auto generated by `nuxt prepare`. Please do not manually modify this file.
+
+///
+///
+///
+///
+///