feat(nuxt): migrate to new nuxt/cli (#22799)

This commit is contained in:
Daniel Roe 2023-08-25 16:20:32 +01:00 committed by GitHub
parent da117ec616
commit abd5d85770
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 27 additions and 1864 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ jspm_packages
package-lock.json
packages/*/README.md
!packages/nuxi/README.md
packages/*/LICENSE
*/**/yarn.lock
/.yarn

View File

@ -6,7 +6,6 @@
"@nuxt/test-utils": "./packages/test-utils",
"@nuxt/vite": "./packages/vite",
"@nuxt/webpack": "./packages/webpack",
"nuxi": "./packages/nuxi",
"nuxt": "./packages/nuxt"
}
}

View File

@ -33,7 +33,6 @@
"@nuxt/test-utils": "workspace:*",
"@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*",
"nuxi": "workspace:*",
"nuxt": "workspace:*",
"vite": "4.4.9",
"vue": "3.3.4",
@ -65,7 +64,7 @@
"jiti": "1.19.3",
"markdownlint-cli": "^0.33.0",
"nitropack": "2.6.0",
"nuxi": "workspace:*",
"nuxi": "npm:nuxi-ng@0.3.0-1692970235.c259efa",
"nuxt": "workspace:*",
"nuxt-vitest": "0.10.2",
"ofetch": "1.3.3",

5
packages/nuxi/README.md Normal file
View File

@ -0,0 +1,5 @@
# Nuxt CLI (nuxi)
⚡️ Next Generation CLI Experience for [Nuxt](https://nuxt.com/).
- 👉 View on GitHub at https://github.com/nuxt/cli

View File

@ -1,2 +0,0 @@
#!/usr/bin/env node
import('../dist/cli-wrapper.mjs')

View File

@ -1,31 +0,0 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
declaration: true,
rollup: {
inlineDependencies: true,
resolve: {
exportConditions: ['production', 'node'] as any
}
},
entries: [
'src/cli',
'src/cli-run',
'src/cli-wrapper',
'src/index'
],
externals: [
'@nuxt/kit',
'@nuxt/schema',
'@nuxt/test-utils',
'fsevents',
// TODO: Fix rollup/unbuild issue
'node:url',
'node:buffer',
'node:path',
'node:child_process',
'node:process',
'node:path',
'node:os'
]
})

View File

@ -1,60 +0,0 @@
{
"name": "nuxi",
"version": "3.6.5",
"repository": "nuxt/nuxt",
"license": "MIT",
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist/index.mjs",
"./cli": "./bin/nuxi.mjs"
},
"bin": "./bin/nuxi.mjs",
"files": [
"bin",
"dist"
],
"scripts": {
"prepack": "unbuild"
},
"devDependencies": {
"@nuxt/kit": "workspace:../kit",
"@nuxt/schema": "workspace:../schema",
"@types/clear": "0.1.2",
"@types/flat": "5.0.2",
"@types/mri": "1.1.1",
"@types/semver": "7.5.0",
"c12": "1.4.2",
"chokidar": "3.5.3",
"clear": "0.1.0",
"clipboardy": "3.0.0",
"colorette": "2.0.20",
"consola": "3.2.3",
"deep-object-diff": "1.1.9",
"defu": "6.1.2",
"destr": "2.0.1",
"execa": "7.2.0",
"flat": "5.0.2",
"giget": "1.1.2",
"h3": "1.8.0",
"jiti": "1.19.3",
"listhen": "1.4.0",
"mlly": "1.4.0",
"mri": "1.2.0",
"nitropack": "2.6.0",
"ohash": "1.1.3",
"pathe": "1.1.1",
"perfect-debounce": "1.0.0",
"pkg-types": "1.0.3",
"scule": "1.0.0",
"semver": "7.5.4",
"ufo": "1.3.0",
"unbuild": "latest"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
}

View File

@ -1,5 +0,0 @@
// @ts-expect-error internal property for tracking start time
process._startTime = Date.now()
// @ts-expect-error `default` property is not declared
import('./cli').then(r => (r.default || r).main())

View File

@ -1,58 +0,0 @@
/**
* This file is used to wrap the CLI entrypoint in a restartable process.
*/
import { fileURLToPath } from 'node:url'
import { fork } from 'node:child_process'
import type { ChildProcess } from 'node:child_process'
const cliEntry = new URL('../dist/cli-run.mjs', import.meta.url)
// Only enable wrapper for nuxt dev command
if (process.argv[2] === 'dev') {
process.env.__CLI_ARGV__ = JSON.stringify(process.argv)
startSubprocess()
} else {
import(cliEntry.href)
}
function startSubprocess () {
let childProc: ChildProcess | undefined
const onShutdown = () => {
if (childProc) {
childProc.kill()
childProc = undefined
}
}
process.on('exit', onShutdown)
process.on('SIGTERM', onShutdown) // Graceful shutdown
process.on('SIGINT', onShutdown) // Ctrl-C
process.on('SIGQUIT', onShutdown) // Ctrl-\
start()
function start () {
const _argv: string[] = (process.env.__CLI_ARGV__ ? JSON.parse(process.env.__CLI_ARGV__) : process.argv).slice(2)
const execArguments: string[] = getInspectArgs()
childProc = fork(fileURLToPath(cliEntry), [], { execArgv: execArguments })
childProc.on('close', (code) => { if (code) { process.exit(code) } })
childProc.on('message', (message) => {
if ((message as { type: string })?.type === 'nuxt:restart') {
childProc?.kill()
startSubprocess()
}
})
function getInspectArgs (): string[] {
const inspectArgv = _argv.find(argvItem => argvItem.includes('--inspect'))
if (!inspectArgv) {
return []
}
return [inspectArgv]
}
}
}

View File

@ -1,87 +0,0 @@
import mri from 'mri'
import { red } from 'colorette'
import type { ConsolaReporter } from 'consola'
import { consola } from 'consola'
import { checkEngines } from './utils/engines'
import type { Command, NuxtCommand } from './commands'
import { commands } from './commands'
import { showHelp } from './utils/help'
import { showBanner } from './utils/banner'
async function _main () {
const _argv = (process.env.__CLI_ARGV__ ? JSON.parse(process.env.__CLI_ARGV__) : process.argv).slice(2)
const args = mri(_argv, {
boolean: [
'no-clear'
]
})
const command = args._.shift() || 'usage'
showBanner(command === 'dev' && args.clear !== false && !args.help)
if (!(command in commands)) {
console.log('\n' + red('Invalid command ' + command))
await commands.usage().then(r => r.invoke())
process.exit(1)
}
// Check Node.js version in background
setTimeout(() => { checkEngines().catch(() => {}) }, 1000)
const cmd = await commands[command as Command]() as NuxtCommand
if (args.h || args.help) {
showHelp(cmd.meta)
} else {
const result = await cmd.invoke(args)
return result
}
}
// Wrap all console logs with consola for better DX
consola.wrapAll()
// Filter out unwanted logs
// TODO: Use better API from consola for intercepting logs
const wrapReporter = (reporter: ConsolaReporter) => ({
log (logObj, ctx) {
if (!logObj.args || !logObj.args.length) { return }
const msg = logObj.args[0]
if (typeof msg === 'string' && !process.env.DEBUG) {
// Hide vue-router 404 warnings
if (msg.startsWith('[Vue Router warn]: No match found for location with path')) {
return
}
// Suppress warning about native Node.js fetch
if (msg.includes('ExperimentalWarning: The Fetch API is an experimental feature')) {
return
}
// TODO: resolve upstream in Vite
// Hide sourcemap warnings related to node_modules
if (msg.startsWith('Sourcemap') && msg.includes('node_modules')) {
return
}
}
return reporter.log(logObj, ctx)
}
}) satisfies ConsolaReporter
consola.options.reporters = consola.options.reporters.map(wrapReporter)
process.on('unhandledRejection', err => consola.error('[unhandledRejection]', err))
process.on('uncaughtException', err => consola.error('[uncaughtException]', err))
export function main () {
_main()
.then((result) => {
if (result === 'error') {
process.exit(1)
} else if (result !== 'wait') {
process.exit()
}
})
.catch((error) => {
consola.error(error)
process.exit(1)
})
}

View File

@ -1,62 +0,0 @@
import { existsSync, promises as fsp } from 'node:fs'
import { dirname, resolve } from 'pathe'
import { consola } from 'consola'
import { loadKit } from '../utils/kit'
import { templates } from '../utils/templates'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'add',
usage: `npx nuxi add [--cwd] [--force] ${Object.keys(templates).join('|')} <name>`,
description: 'Create a new template file.'
},
async invoke (args) {
const cwd = resolve(args.cwd || '.')
const template = args._[0]
const name = args._[1]
// Validate template name
if (!templates[template]) {
consola.error(`Template ${template} is not supported. Possible values: ${Object.keys(templates).join(', ')}`)
process.exit(1)
}
// Validate options
if (!name) {
consola.error('name argument is missing!')
process.exit(1)
}
// Load config in order to respect srcDir
const kit = await loadKit(cwd)
const config = await kit.loadNuxtConfig({ cwd })
// Resolve template
const res = templates[template]({ name, args })
// Resolve full path to generated file
const path = resolve(config.srcDir, res.path)
// Ensure not overriding user code
if (!args.force && existsSync(path)) {
consola.error(`File exists: ${path} . Use --force to override or use a different name.`)
process.exit(1)
}
// Ensure parent directory exists
const parentDir = dirname(path)
if (!existsSync(parentDir)) {
consola.info('Creating directory', parentDir)
if (template === 'page') {
consola.info('This enables vue-router functionality!')
}
await fsp.mkdir(parentDir, { recursive: true })
}
// Write file
await fsp.writeFile(path, res.contents.trim() + '\n')
consola.info(`🪄 Generated a new ${template} in ${path}`)
}
})

View File

@ -1,110 +0,0 @@
import { promises as fsp } from 'node:fs'
import { join, resolve } from 'pathe'
import { createApp, eventHandler, lazyEventHandler, send, toNodeListener } from 'h3'
import { listen } from 'listhen'
import type { NuxtAnalyzeMeta } from '@nuxt/schema'
import { defu } from 'defu'
import { loadKit } from '../utils/kit'
import { clearDir } from '../utils/fs'
import { overrideEnv } from '../utils/env'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'analyze',
usage: 'npx nuxi analyze [--log-level] [--name] [--no-serve] [rootDir]',
description: 'Build nuxt and analyze production bundle (experimental)'
},
async invoke (args, options = {}) {
overrideEnv('production')
const name = args.name || 'default'
const slug = name.trim().replace(/[^a-z0-9_-]/gi, '_')
const rootDir = resolve(args._[0] || '.')
let analyzeDir = join(rootDir, '.nuxt/analyze', slug)
let buildDir = join(analyzeDir, '.nuxt')
let outDir = join(analyzeDir, '.output')
const startTime = Date.now()
const { loadNuxt, buildNuxt } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
overrides: defu(options.overrides, {
build: {
analyze: true
},
analyzeDir,
buildDir,
nitro: {
output: {
dir: outDir
}
},
logLevel: args['log-level']
})
})
analyzeDir = nuxt.options.analyzeDir
buildDir = nuxt.options.buildDir
outDir = nuxt.options.nitro.output?.dir || outDir
await clearDir(analyzeDir)
await buildNuxt(nuxt)
const endTime = Date.now()
const meta: NuxtAnalyzeMeta = {
name,
slug,
startTime,
endTime,
analyzeDir,
buildDir,
outDir
}
await nuxt.callHook('build:analyze:done', meta)
await fsp.writeFile(join(analyzeDir, 'meta.json'), JSON.stringify(meta, null, 2), 'utf-8')
console.info('Analyze results are available at: `' + analyzeDir + '`')
console.warn('Do not deploy analyze results! Use `nuxi build` before deploying.')
if (args.serve !== false && !process.env.CI) {
const app = createApp()
const serveFile = (filePath: string) => lazyEventHandler(async () => {
const contents = await fsp.readFile(filePath, 'utf-8')
return eventHandler(event => send(event, contents))
})
console.info('Starting stats server...')
app.use('/client', serveFile(join(analyzeDir, 'client.html')))
app.use('/nitro', serveFile(join(analyzeDir, 'nitro.html')))
app.use(eventHandler(() => `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Nuxt Bundle Stats (experimental)</title>
</head>
<h1>Nuxt Bundle Stats (experimental)</h1>
<ul>
<li>
<a href="/nitro">Nitro server bundle stats</a>
</li>
<li>
<a href="/client">Client bundle stats</a>
</li>
</ul>
</html>
`))
await listen(toNodeListener(app))
return 'wait' as const
}
}
})

View File

@ -1,34 +0,0 @@
import { execa } from 'execa'
import { consola } from 'consola'
import { resolve } from 'pathe'
import { tryResolveModule } from '../utils/esm'
import { defineNuxtCommand } from './index'
const MODULE_BUILDER_PKG = '@nuxt/module-builder'
export default defineNuxtCommand({
meta: {
name: 'build-module',
usage: 'npx nuxi build-module [--stub] [rootDir]',
description: `Helper command for using ${MODULE_BUILDER_PKG}`
},
async invoke (args) {
// Find local installed version
const rootDir = resolve(args._[0] || '.')
const hasLocal = await tryResolveModule(`${MODULE_BUILDER_PKG}/package.json`, rootDir)
const execArgs = Object.entries({
'--stub': args.stub,
'--prepare': args.prepare
}).filter(([, value]) => value).map(([key]) => key)
let cmd = 'nuxt-module-build'
if (!hasLocal) {
consola.warn(`Cannot find locally installed version of \`${MODULE_BUILDER_PKG}\` (>=0.2.0). Falling back to \`npx ${MODULE_BUILDER_PKG}\``)
cmd = 'npx'
execArgs.unshift(MODULE_BUILDER_PKG)
}
await execa(cmd, execArgs, { preferLocal: true, stdio: 'inherit', cwd: rootDir })
}
})

View File

@ -1,70 +0,0 @@
import { relative, resolve } from 'pathe'
import { consola } from 'consola'
import type { Nitro } from 'nitropack'
// we are deliberately inlining this code as a backup in case user has `@nuxt/schema<3.7`
import { writeTypes as writeTypesLegacy } from '../../../kit/src/template'
import { loadKit } from '../utils/kit'
import { clearBuildDir } from '../utils/fs'
import { overrideEnv } from '../utils/env'
import { showVersions } from '../utils/banner'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'build',
usage: 'npx nuxi build [--prerender] [--dotenv] [--log-level] [rootDir]',
description: 'Build nuxt for production deployment'
},
async invoke (args, options = {}) {
overrideEnv('production')
const rootDir = resolve(args._[0] || '.')
showVersions(rootDir)
const { loadNuxt, buildNuxt, useNitro, writeTypes = writeTypesLegacy } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
dotenv: {
cwd: rootDir,
fileName: args.dotenv
},
overrides: {
logLevel: args['log-level'],
// TODO: remove in 3.8
_generate: args.prerender,
...(args.prerender ? { nitro: { static: true } } : {}),
...(options?.overrides || {})
}
})
let nitro: Nitro | undefined
// In Bridge, if nitro is not enabled, useNitro will throw an error
try {
// Use ? for backward compatibility for Nuxt <= RC.10
nitro = useNitro?.()
} catch {}
await clearBuildDir(nuxt.options.buildDir)
await writeTypes(nuxt)
nuxt.hook('build:error', (err) => {
consola.error('Nuxt Build Error:', err)
process.exit(1)
})
await buildNuxt(nuxt)
if (args.prerender) {
if (!nuxt.options.ssr) {
consola.warn('HTML content not prerendered because `ssr: false` was set. You can read more in `https://nuxt.com/docs/getting-started/deployment#static-hosting`.')
}
// TODO: revisit later if/when nuxt build --prerender will output hybrid
const dir = nitro?.options.output.publicDir
const publicDir = dir ? relative(process.cwd(), dir) : '.output/public'
consola.success(`You can now deploy \`${publicDir}\` to any static hosting!`)
}
}
})

View File

@ -1,15 +0,0 @@
import { resolve } from 'pathe'
import { cleanupNuxtDirs } from '../utils/nuxt'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'cleanup',
usage: 'npx nuxi clean|cleanup',
description: 'Cleanup generated nuxt files and caches'
},
async invoke (args) {
const rootDir = resolve(args._[0] || '.')
await cleanupNuxtDirs(rootDir)
}
})

View File

@ -1,190 +0,0 @@
import type { AddressInfo } from 'node:net'
import type { RequestListener } from 'node:http'
import { relative, resolve } from 'pathe'
import chokidar from 'chokidar'
import { debounce } from 'perfect-debounce'
import type { Nuxt } from '@nuxt/schema'
import { consola } from 'consola'
import { withTrailingSlash } from 'ufo'
import { setupDotenv } from 'c12'
// we are deliberately inlining this code as a backup in case user has `@nuxt/schema<3.7`
import { writeTypes as writeTypesLegacy } from '../../../kit/src/template'
import { showBanner, showVersions } from '../utils/banner'
import { loadKit } from '../utils/kit'
import { importModule } from '../utils/esm'
import { overrideEnv } from '../utils/env'
import { loadNuxtManifest, writeNuxtManifest } from '../utils/nuxt'
import { clearBuildDir } from '../utils/fs'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'dev',
usage: 'npx nuxi dev [rootDir] [--dotenv] [--log-level] [--clipboard] [--open, -o] [--port, -p] [--host, -h] [--https] [--ssl-cert] [--ssl-key]',
description: 'Run nuxt development server'
},
async invoke (args, options = {}) {
overrideEnv('development')
const rootDir = resolve(args._[0] || '.')
showVersions(rootDir)
await setupDotenv({ cwd: rootDir, fileName: args.dotenv })
const { loadNuxt, loadNuxtConfig, buildNuxt, writeTypes = writeTypesLegacy } = await loadKit(rootDir)
const config = await loadNuxtConfig({
cwd: rootDir,
overrides: {
dev: true,
logLevel: args['log-level'],
...(options.overrides || {})
}
})
const { listen } = await import('listhen')
const { toNodeListener } = await import('h3')
let currentHandler: RequestListener | undefined
let loadingMessage = 'Nuxt is starting...'
const loadingHandler: RequestListener = async (_req, res) => {
const loadingTemplate = config.devServer.loadingTemplate ?? await importModule('@nuxt/ui-templates', config.modulesDir).then(r => r.loading)
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
res.statusCode = 503 // Service Unavailable
res.end(loadingTemplate({ loading: loadingMessage }))
}
const serverHandler: RequestListener = (req, res) => {
return currentHandler ? currentHandler(req, res) : loadingHandler(req, res)
}
const listener = await listen(serverHandler, {
showURL: false,
clipboard: args.clipboard,
open: args.open || args.o,
port: args.port || args.p || process.env.NUXT_PORT || process.env.NITRO_PORT || config.devServer.port,
hostname: args.host || args.h || process.env.NUXT_HOST || process.env.NITRO_HOST || config.devServer.host,
https: (args.https !== false && (args.https || config.devServer.https))
? {
cert: args['ssl-cert'] || process.env.NUXT_SSL_CERT || process.env.NITRO_SSL_CERT || (typeof config.devServer.https !== 'boolean' && config.devServer.https.cert) || undefined,
key: args['ssl-key'] || process.env.NUXT_SSL_KEY || process.env.NITRO_SSL_KEY || (typeof config.devServer.https !== 'boolean' && config.devServer.https.key) || undefined
}
: false
})
let currentNuxt: Nuxt
let distWatcher: chokidar.FSWatcher
const showURL = () => {
listener.showURL({
// TODO: Normalize URL with trailing slash within schema
baseURL: withTrailingSlash(currentNuxt?.options.app.baseURL) || '/'
})
}
async function hardRestart (reason?: string) {
if (process.send) {
await listener.close().catch(() => {})
await currentNuxt.close().catch(() => {})
await watcher.close().catch(() => {})
await distWatcher.close().catch(() => {})
consola.info(`${reason ? reason + '. ' : ''}Restarting nuxt...`)
process.send({ type: 'nuxt:restart' })
} else {
await load(true, reason)
}
}
const load = async (isRestart: boolean, reason?: string) => {
try {
loadingMessage = `${reason ? reason + '. ' : ''}${isRestart ? 'Restarting' : 'Starting'} nuxt...`
currentHandler = undefined
if (isRestart) {
consola.info(loadingMessage)
}
if (currentNuxt) {
await currentNuxt.close()
}
if (distWatcher) {
await distWatcher.close()
}
currentNuxt = await loadNuxt({
rootDir,
dev: true,
ready: false,
overrides: {
logLevel: args['log-level'],
vite: {
clearScreen: args.clear
},
...(options.overrides || {})
}
})
if (!isRestart) {
showURL()
}
// Write manifest and also check if we need cache invalidation
if (!isRestart) {
const previousManifest = await loadNuxtManifest(currentNuxt.options.buildDir)
const newManifest = await writeNuxtManifest(currentNuxt)
if (previousManifest && newManifest && previousManifest._hash !== newManifest._hash) {
await clearBuildDir(currentNuxt.options.buildDir)
}
}
await currentNuxt.ready()
const unsub = currentNuxt.hooks.hook('restart', async (options) => {
unsub() // we use this instead of `hookOnce` for Nuxt Bridge support
if (options?.hard) { return hardRestart() }
await load(true)
})
await currentNuxt.hooks.callHook('listen', listener.server, listener)
const address = (listener.server.address() || {}) as AddressInfo
currentNuxt.options.devServer.url = listener.url
currentNuxt.options.devServer.port = address.port
currentNuxt.options.devServer.host = address.address
currentNuxt.options.devServer.https = listener.https
await Promise.all([
writeTypes(currentNuxt).catch(console.error),
buildNuxt(currentNuxt)
])
distWatcher = chokidar.watch(resolve(currentNuxt.options.buildDir, 'dist'), { ignoreInitial: true, depth: 0 })
distWatcher.on('unlinkDir', () => {
dLoad(true, '.nuxt/dist directory has been removed')
})
currentHandler = toNodeListener(currentNuxt.server.app)
if (isRestart && args.clear !== false) {
showBanner()
showURL()
}
} catch (err) {
consola.error(`Cannot ${isRestart ? 'restart' : 'start'} nuxt: `, err)
currentHandler = undefined
loadingMessage = 'Error while loading nuxt. Please check console and fix errors.'
}
}
// Watch for config changes
// TODO: Watcher service, modules, and requireTree
const dLoad = debounce(load)
const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 0 })
watcher.on('all', (_event, _file) => {
const file = relative(rootDir, _file)
if (file === (args.dotenv || '.env')) { return hardRestart('.env updated') }
if (RESTART_RE.test(file)) {
dLoad(true, `${file} updated`)
}
})
await load(false)
return 'wait' as const
}
})
const RESTART_RE = /^(nuxt\.config\.(js|ts|mjs|cjs)|\.nuxtignore|\.nuxtrc)$/

View File

@ -1,24 +0,0 @@
import { resolve } from 'pathe'
import { execa } from 'execa'
import { showHelp } from '../utils/help'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'enable',
usage: 'npx nuxi devtools enable|disable [rootDir]',
description: 'Enable or disable features in a Nuxt project'
},
async invoke (args) {
const [command, _rootDir = '.'] = args._
const rootDir = resolve(_rootDir)
if (!['enable', 'disable'].includes(command)) {
console.error(`Unknown command \`${command}\`.`)
showHelp(this.meta)
process.exit(1)
}
await execa('npx', ['@nuxt/devtools-wizard@latest', command, rootDir], { stdio: 'inherit', cwd: rootDir })
}
})

View File

@ -1,14 +0,0 @@
import buildCommand from './build'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'generate',
usage: 'npx nuxi generate [rootDir] [--dotenv]',
description: 'Build Nuxt and prerender static routes'
},
async invoke (args, options = {}) {
args.prerender = true
await buildCommand.invoke(args, options)
}
})

View File

@ -1,46 +0,0 @@
import type { Argv } from 'mri'
const _rDefault = (r: any) => r.default || r
export const commands = {
dev: () => import('./dev').then(_rDefault),
build: () => import('./build').then(_rDefault),
'build-module': () => import('./build-module').then(_rDefault),
cleanup: () => import('./cleanup').then(_rDefault),
clean: () => import('./cleanup').then(_rDefault),
preview: () => import('./preview').then(_rDefault),
start: () => import('./preview').then(_rDefault),
analyze: () => import('./analyze').then(_rDefault),
generate: () => import('./generate').then(_rDefault),
prepare: () => import('./prepare').then(_rDefault),
typecheck: () => import('./typecheck').then(_rDefault),
usage: () => import('./usage').then(_rDefault),
info: () => import('./info').then(_rDefault),
init: () => import('./init').then(_rDefault),
create: () => import('./init').then(_rDefault),
devtools: () => import('./devtools').then(_rDefault),
upgrade: () => import('./upgrade').then(_rDefault),
test: () => import('./test').then(_rDefault),
add: () => import('./add').then(_rDefault),
new: () => import('./add').then(_rDefault)
}
export type Command = keyof typeof commands
export interface NuxtCommandMeta {
name: string;
usage: string;
description: string;
[key: string]: any;
}
export type CLIInvokeResult = void | 'error' | 'wait'
export interface NuxtCommand {
invoke(args: Argv, options?: Record<string, any>): Promise<CLIInvokeResult> | CLIInvokeResult
meta: NuxtCommandMeta
}
export function defineNuxtCommand (command: NuxtCommand): NuxtCommand {
return command
}

View File

@ -1,161 +0,0 @@
import os from 'node:os'
import { existsSync, readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { resolve } from 'pathe'
import jiti from 'jiti'
import destr from 'destr'
import type { PackageJson } from 'pkg-types'
import { splitByCase } from 'scule'
import clipboardy from 'clipboardy'
import type { NuxtModule } from '@nuxt/schema'
import type { packageManagerLocks } from '../utils/packageManagers'
import { getPackageManager, getPackageManagerVersion } from '../utils/packageManagers'
import { findup } from '../utils/fs'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'info',
usage: 'npx nuxi info [rootDir]',
description: 'Get information about nuxt project'
},
async invoke (args) {
// Resolve rootDir
const rootDir = resolve(args._[0] || '.')
// Load nuxt.config
const nuxtConfig = getNuxtConfig(rootDir)
// Find nearest package.json
const { dependencies = {}, devDependencies = {} } = findPackage(rootDir)
// Utils to query a dependency version
const getDepVersion = (name: string) => getPkg(name, rootDir)?.version || dependencies[name] || devDependencies[name]
const listModules = (arr = []) => arr
.map(m => normalizeConfigModule(m, rootDir))
.filter(Boolean)
.map((name) => {
const npmName = name!.split('/').splice(0, 2).join('/') // @foo/bar/baz => @foo/bar
const v = getDepVersion(npmName)
return '`' + (v ? `${name}@${v}` : name) + '`'
})
.join(', ')
// Check nuxt version
const nuxtVersion = getDepVersion('nuxt') || getDepVersion('nuxt-edge') || getDepVersion('nuxt3') || '0.0.0'
const isNuxt3 = nuxtVersion.startsWith('3')
const builder = isNuxt3
? nuxtConfig.builder /* latest schema */ || (nuxtConfig.vite !== false ? 'vite' : 'webpack') /* previous schema */
: nuxtConfig.bridge?.vite
? 'vite' /* bridge vite implementation */
: (nuxtConfig.buildModules?.includes('nuxt-vite')
? 'vite' /* nuxt-vite */
: 'webpack')
let packageManager: keyof typeof packageManagerLocks | 'unknown' | null = getPackageManager(rootDir)
if (packageManager) {
packageManager += '@' + getPackageManagerVersion(packageManager)
} else {
packageManager = 'unknown'
}
const infoObj = {
OperatingSystem: os.type(),
NodeVersion: process.version,
NuxtVersion: nuxtVersion,
NitroVersion: getDepVersion('nitropack'),
PackageManager: packageManager,
Builder: builder,
UserConfig: Object.keys(nuxtConfig).map(key => '`' + key + '`').join(', '),
RuntimeModules: listModules(nuxtConfig.modules),
BuildModules: listModules(nuxtConfig.buildModules || [])
}
console.log('RootDir:', rootDir)
let maxLength = 0
const entries = Object.entries(infoObj).map(([key, val]) => {
const label = splitByCase(key).join(' ')
if (label.length > maxLength) { maxLength = label.length }
return [label, val || '-']
})
let infoStr = ''
for (const [label, value] of entries) {
infoStr += '- ' + (label + ': ').padEnd(maxLength + 2) + (value.includes('`') ? value : '`' + value + '`') + '\n'
}
const copied = await clipboardy.write(infoStr).then(() => true).catch(() => false)
const splitter = '------------------------------'
console.log(`Nuxt project info: ${copied ? '(copied to clipboard)' : ''}\n\n${splitter}\n${infoStr}${splitter}\n`)
const isNuxt3OrBridge = infoObj.NuxtVersion.startsWith('3') || infoObj.BuildModules.includes('bridge')
console.log([
'👉 Report an issue: https://github.com/nuxt/nuxt/issues/new',
'👉 Suggest an improvement: https://github.com/nuxt/nuxt/discussions/new',
`👉 Read documentation: ${isNuxt3OrBridge ? 'https://nuxt.com' : 'https://v2.nuxt.com'}`
].join('\n\n') + '\n')
}
})
function normalizeConfigModule (module: NuxtModule | string | null | undefined, rootDir: string): string | null {
if (!module) {
return null
}
if (typeof module === 'string') {
return module
.split(rootDir).pop()! // Strip rootDir
.split('node_modules').pop()! // Strip node_modules
.replace(/^\//, '')
}
if (typeof module === 'function') {
return `${module.name}()`
}
if (Array.isArray(module)) {
return normalizeConfigModule(module[0], rootDir)
}
return null
}
function getNuxtConfig (rootDir: string) {
try {
(globalThis as any).defineNuxtConfig = (c: any) => c
const result = jiti(rootDir, { interopDefault: true, esmResolve: true })('./nuxt.config')
delete (globalThis as any).defineNuxtConfig
return result
} catch (err) {
// TODO: Show error as warning if it is not 404
return {}
}
}
function getPkg (name: string, rootDir: string) {
// Assume it is in {rootDir}/node_modules/${name}/package.json
let pkgPath = resolve(rootDir, 'node_modules', name, 'package.json')
// Try to resolve for more accuracy
const _require = createRequire(rootDir)
try { pkgPath = _require.resolve(name + '/package.json') } catch (_err) {
// console.log('not found:', name)
}
return readJSONSync(pkgPath) as PackageJson
}
function findPackage (rootDir: string) {
return findup(rootDir, (dir) => {
const p = resolve(dir, 'package.json')
if (existsSync(p)) {
return readJSONSync(p) as PackageJson
}
}) || {}
}
function readJSONSync (filePath: string) {
try {
return destr(readFileSync(filePath, 'utf-8'))
} catch (err) {
// TODO: Warn error
return null
}
}

View File

@ -1,68 +0,0 @@
import { writeFile } from 'node:fs/promises'
import { downloadTemplate, startShell } from 'giget'
import { relative } from 'pathe'
import { consola } from 'consola'
import { defineNuxtCommand } from './index'
const rpath = (p: string) => relative(process.cwd(), p)
const DEFAULT_REGISTRY = 'https://raw.githubusercontent.com/nuxt/starter/templates/templates'
export default defineNuxtCommand({
meta: {
name: 'init',
usage: 'npx nuxi init|create [--template,-t] [--force] [--offline] [--prefer-offline] [--shell] [dir]',
description: 'Initialize a fresh project'
},
async invoke (args) {
// Clone template
const template = args.template || args.t || 'v3'
if (typeof template === 'boolean') {
consola.error('Please specify a template!')
process.exit(1)
}
let t
try {
t = await downloadTemplate(template, {
dir: args._[0] as string,
force: args.force,
offline: args.offline,
preferOffline: args['prefer-offline'],
registry: process.env.NUXI_INIT_REGISTRY || DEFAULT_REGISTRY
})
} catch (err) {
if (process.env.DEBUG) {
throw err
}
consola.error((err as Error).toString())
process.exit(1)
}
// Show next steps
const relativeDist = rpath(t.dir)
// Write .nuxtrc with `shamefully-hoist=true` for pnpm
const usingPnpm = (process.env.npm_config_user_agent || '').includes('pnpm')
if (usingPnpm) {
await writeFile(`${relativeDist}/.npmrc`, 'shamefully-hoist=true')
}
const nextSteps = [
!args.shell && relativeDist.length > 1 && `\`cd ${relativeDist}\``,
'Install dependencies with `npm install` or `yarn install` or `pnpm install`',
'Start development server with `npm run dev` or `yarn dev` or `pnpm run dev`'
].filter(Boolean)
consola.log(`✨ Nuxt project is created with \`${t.name}\` template. Next steps:`)
for (const step of nextSteps) {
consola.log(` ${step}`)
}
if (args.shell) {
startShell(t.dir)
}
}
})

View File

@ -1,35 +0,0 @@
import { relative, resolve } from 'pathe'
import { consola } from 'consola'
// we are deliberately inlining this code as a backup in case user has `@nuxt/schema<3.7`
import { writeTypes as writeTypesLegacy } from '../../../kit/src/template'
import { clearBuildDir } from '../utils/fs'
import { loadKit } from '../utils/kit'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'prepare',
usage: 'npx nuxi prepare [--log-level] [rootDir]',
description: 'Prepare nuxt for development/build'
},
async invoke (args, options = {}) {
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const rootDir = resolve(args._[0] || '.')
const { loadNuxt, buildNuxt, writeTypes = writeTypesLegacy } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
overrides: {
_prepare: true,
logLevel: args['log-level'],
...(options.overrides || {})
}
})
await clearBuildDir(nuxt.options.buildDir)
await buildNuxt(nuxt)
await writeTypes(nuxt)
consola.success('Types generated in', relative(process.cwd(), nuxt.options.buildDir))
}
})

View File

@ -1,55 +0,0 @@
import { existsSync, promises as fsp } from 'node:fs'
import { dirname, relative } from 'node:path'
import { execa } from 'execa'
import { setupDotenv } from 'c12'
import { resolve } from 'pathe'
import { consola } from 'consola'
import { loadKit } from '../utils/kit'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'preview',
usage: 'npx nuxi preview|start [--dotenv] [rootDir]',
description: 'Launches nitro server for local testing after `nuxi build`.'
},
async invoke (args, options = {}) {
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const rootDir = resolve(args._[0] || '.')
const { loadNuxtConfig } = await loadKit(rootDir)
const config = await loadNuxtConfig({ cwd: rootDir, overrides: options?.overrides || {} })
const resolvedOutputDir = resolve(config.srcDir || rootDir, config.nitro.srcDir || 'server', config.nitro.output?.dir || '.output', 'nitro.json')
const defaultOutput = resolve(rootDir, '.output', 'nitro.json') // for backwards compatibility
const nitroJSONPaths = [resolvedOutputDir, defaultOutput]
const nitroJSONPath = nitroJSONPaths.find(p => existsSync(p))
if (!nitroJSONPath) {
consola.error('Cannot find `nitro.json`. Did you run `nuxi build` first? Search path:\n', nitroJSONPaths)
process.exit(1)
}
const outputPath = dirname(nitroJSONPath)
const nitroJSON = JSON.parse(await fsp.readFile(nitroJSONPath, 'utf-8'))
consola.info('Node.js version:', process.versions.node)
consola.info('Preset:', nitroJSON.preset)
consola.info('Working dir:', relative(process.cwd(), outputPath))
if (!nitroJSON.commands.preview) {
consola.error('Preview is not supported for this build.')
process.exit(1)
}
const envExists = args.dotenv ? existsSync(resolve(rootDir, args.dotenv)) : existsSync(rootDir)
if (envExists) {
consola.info('Loading `.env`. This will not be loaded when running the server in production.')
await setupDotenv({ cwd: rootDir, fileName: args.dotenv })
}
consola.info('Starting preview command:', nitroJSON.commands.preview)
const [command, ...commandArgs] = nitroJSON.commands.preview.split(' ')
consola.log('')
await execa(command, commandArgs, { stdio: 'inherit', cwd: outputPath })
}
})

View File

@ -1,43 +0,0 @@
import { resolve } from 'pathe'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'test',
usage: 'npx nuxi test [--dev] [--watch] [rootDir]',
description: 'Run tests'
},
async invoke (args, options = {}) {
process.env.NODE_ENV = process.env.NODE_ENV || 'test'
const rootDir = resolve(args._[0] || '.')
const { runTests } = await importTestUtils()
await runTests({
rootDir,
dev: !!args.dev,
watch: !!args.watch,
...(options || {})
})
if (args.watch) {
return 'wait' as const
}
}
})
async function importTestUtils (): Promise<typeof import('@nuxt/test-utils')> {
let err
for (const pkg of ['@nuxt/test-utils-edge', '@nuxt/test-utils']) {
try {
const exports = await import(pkg)
// Detect old @nuxt/test-utils
if (!exports.runTests) {
throw new Error('Invalid version of `@nuxt/test-utils` is installed!')
}
return exports
} catch (_err) {
err = _err
}
}
console.error(err)
throw new Error('`@nuxt/test-utils-edge` seems missing. Run `npm i -D @nuxt/test-utils-edge` or `yarn add -D @nuxt/test-utils-edge` to install.')
}

View File

@ -1,43 +0,0 @@
import { execa } from 'execa'
import { resolve } from 'pathe'
// we are deliberately inlining this code as a backup in case user has `@nuxt/schema<3.7`
import { writeTypes as writeTypesLegacy } from '../../../kit/src/template'
import { tryResolveModule } from '../utils/esm'
import { loadKit } from '../utils/kit'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'typecheck',
usage: 'npx nuxi typecheck [--log-level] [rootDir]',
description: 'Runs `vue-tsc` to check types throughout your app.'
},
async invoke (args, options = {}) {
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const rootDir = resolve(args._[0] || '.')
const { loadNuxt, buildNuxt, writeTypes = writeTypesLegacy } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
overrides: {
_prepare: true,
logLevel: args['log-level'],
...(options?.overrides || {})
}
})
// Generate types and build nuxt instance
await writeTypes(nuxt)
await buildNuxt(nuxt)
await nuxt.close()
// Prefer local install if possible
const hasLocalInstall = await tryResolveModule('typescript', rootDir) && await tryResolveModule('vue-tsc/package.json', rootDir)
if (hasLocalInstall) {
await execa('vue-tsc', ['--noEmit'], { preferLocal: true, stdio: 'inherit', cwd: rootDir })
} else {
await execa('npx', '-p vue-tsc -p typescript vue-tsc --noEmit'.split(' '), { stdio: 'inherit', cwd: rootDir })
}
}
})

View File

@ -1,74 +0,0 @@
import { execSync } from 'node:child_process'
import { consola } from 'consola'
import { resolve } from 'pathe'
import { readPackageJSON } from 'pkg-types'
import { getPackageManager, packageManagerLocks } from '../utils/packageManagers'
import { rmRecursive, touchFile } from '../utils/fs'
import { cleanupNuxtDirs, nuxtVersionToGitIdentifier } from '../utils/nuxt'
import { defineNuxtCommand } from './index'
async function getNuxtVersion (path: string): Promise<string | null> {
try {
const pkg = await readPackageJSON('nuxt', { url: path })
if (!pkg.version) {
consola.warn('Cannot find any installed nuxt versions in ', path)
}
return pkg.version || null
} catch {
return null
}
}
export default defineNuxtCommand({
meta: {
name: 'upgrade',
usage: 'npx nuxi upgrade [--force|-f]',
description: 'Upgrade nuxt'
},
async invoke (args) {
const rootDir = resolve(args._[0] || '.')
// Check package manager
const packageManager = getPackageManager(rootDir)
if (!packageManager) {
console.error('Cannot detect Package Manager in', rootDir)
process.exit(1)
}
const packageManagerVersion = execSync(`${packageManager} --version`).toString('utf8').trim()
consola.info('Package Manager:', packageManager, packageManagerVersion)
// Check currently installed nuxt version
const currentVersion = await getNuxtVersion(rootDir) || '[unknown]'
consola.info('Current nuxt version:', currentVersion)
// Force install
if (args.force || args.f) {
consola.info('Removing lock-file and node_modules...')
const pmLockFile = resolve(rootDir, packageManagerLocks[packageManager])
await rmRecursive([pmLockFile, resolve(rootDir, 'node_modules')])
await touchFile(pmLockFile)
}
// Install latest version
consola.info('Installing latest Nuxt 3 release...')
execSync(`${packageManager} ${packageManager === 'yarn' ? 'add' : 'install'} -D nuxt`, { stdio: 'inherit', cwd: rootDir })
// Cleanup after upgrade
await cleanupNuxtDirs(rootDir)
// Check installed nuxt version again
const upgradedVersion = await getNuxtVersion(rootDir) || '[unknown]'
consola.info('Upgraded nuxt version:', upgradedVersion)
if (upgradedVersion === currentVersion) {
consola.success('You\'re already using the latest version of nuxt.')
} else {
consola.success('Successfully upgraded nuxt from', currentVersion, 'to', upgradedVersion)
const commitA = nuxtVersionToGitIdentifier(currentVersion)
const commitB = nuxtVersionToGitIdentifier(upgradedVersion)
if (commitA && commitB) {
consola.info('Changelog:', `https://github.com/nuxt/nuxt/compare/${commitA}...${commitB}`)
}
}
}
})

View File

@ -1,21 +0,0 @@
import { cyan } from 'colorette'
import { showHelp } from '../utils/help'
import { commands, defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'help',
usage: 'nuxt help',
description: 'Show help'
},
invoke (_args) {
const sections: string[] = []
sections.push(`Usage: ${cyan(`npx nuxi ${Object.keys(commands).join('|')} [args]`)}`)
console.log(sections.join('\n\n') + '\n')
// Reuse the same wording as in `-h` commands
showHelp({})
}
})

View File

@ -1 +0,0 @@
export * from './run'

View File

@ -1,13 +0,0 @@
import mri from 'mri'
import type { Command, NuxtCommand } from './commands'
import { commands } from './commands'
export async function runCommand (command: string, argv = process.argv.slice(2), options: Record<string, any> = {}) {
const args = mri(argv)
args.clear = false // used by dev
const cmd = await commands[command as Command]() as NuxtCommand
if (!cmd) {
throw new Error(`Invalid command ${command}`)
}
await cmd.invoke(args, options)
}

View File

@ -1,21 +0,0 @@
import clear from 'clear'
import { bold, gray, green } from 'colorette'
import { version } from '../../package.json'
import { tryRequireModule } from './cjs'
export function showBanner (_clear?: boolean) {
if (_clear) { clear() }
console.log(gray(`Nuxi ${(bold(version))}`))
}
export function showVersions (cwd: string) {
const getPkgVersion = (pkg: string) => {
return tryRequireModule(`${pkg}/package.json`, cwd)?.version || ''
}
const nuxtVersion = getPkgVersion('nuxt') || getPkgVersion('nuxt-edge')
const nitroVersion = getPkgVersion('nitropack')
console.log(gray(
green(`Nuxt ${bold(nuxtVersion)}`) +
(nitroVersion ? ` with Nitro ${(bold(nitroVersion))}` : '')
))
}

View File

@ -1,27 +0,0 @@
import { createRequire } from 'node:module'
import { normalize } from 'pathe'
function getModulePaths (paths?: string | string[]): string[] {
return ([] as Array<string | undefined>)
.concat(
global.__NUXT_PREPATHS__,
paths,
process.cwd(),
global.__NUXT_PATHS__
)
.filter(Boolean) as string[]
}
const _require = createRequire(process.cwd())
function resolveModule (id: string, paths?: string | string[]) {
return normalize(_require.resolve(id, { paths: getModulePaths(paths) }))
}
function requireModule (id: string, paths?: string | string[]) {
return _require(resolveModule(id, paths))
}
export function tryRequireModule (id: string, paths?: string | string[]) {
try { return requireModule(id, paths) } catch { return null }
}

View File

@ -1,31 +0,0 @@
import flatten from 'flat'
import { detailedDiff } from 'deep-object-diff'
import { blue, cyan, green, red } from 'colorette'
function normalizeDiff (diffObj: any, type: 'added' | 'deleted' | 'updated', ignore: string[]) {
return Object.entries(flatten(diffObj) as Record<string, any>)
.map(([key, value]) => ({ key, value, type }))
.filter(item => !ignore.includes(item.key) && typeof item.value !== 'function')
}
export function diff (a: any, b: any, ignore: string[]) {
const _diff: any = detailedDiff(a, b)
return [
...normalizeDiff(_diff.added, 'added', ignore),
...normalizeDiff(_diff.deleted, 'deleted', ignore),
...normalizeDiff(_diff.updated, 'updated', ignore)
]
}
const typeMap = {
added: green('added'),
deleted: red('deleted'),
updated: blue('updated')
}
export function printDiff (diff: any) {
for (const item of diff) {
console.log(' ', typeMap[item.type as keyof typeof typeMap] || item.type, cyan(item.key), item.value ? `~> ${cyan(item.value)}` : '')
}
console.log()
}

View File

@ -1,12 +0,0 @@
import { engines } from '../../package.json'
export async function checkEngines () {
const satisfies = await import('semver/functions/satisfies.js')
.then(r => r.default || r as any as typeof import('semver/functions/satisfies.js')) // npm/node-semver#381
const currentNode = process.versions.node
const nodeRange = engines.node
if (!satisfies(currentNode, nodeRange)) {
console.warn(`Current version of Node.js (\`${currentNode}\`) is unsupported and might cause issues.\n Please upgrade to a compatible version (${nodeRange}).`)
}
}

View File

@ -1,8 +0,0 @@
export const overrideEnv = (targetEnv: string) => {
const currentEnv = process.env.NODE_ENV
if (currentEnv && currentEnv !== targetEnv) {
console.warn(`Changing \`NODE_ENV\` from \`${currentEnv}\` to \`${targetEnv}\`, to avoid unintended behavior.`)
}
process.env.NODE_ENV = targetEnv
}

View File

@ -1,13 +0,0 @@
import { pathToFileURL } from 'node:url'
import { interopDefault, resolvePath } from 'mlly'
export async function tryResolveModule (id: string, url = import.meta.url) {
try {
return await resolvePath(id, { url })
} catch { }
}
export async function importModule (id: string, url: string | string[] = import.meta.url) {
const resolvedPath = await resolvePath(id, { url })
return import(pathToFileURL(resolvedPath).href).then(interopDefault)
}

View File

@ -1,45 +0,0 @@
import { existsSync, promises as fsp } from 'node:fs'
import { dirname, join } from 'pathe'
import { consola } from 'consola'
export async function clearDir (path: string, exclude?: string[]) {
if (!exclude) {
await fsp.rm(path, { recursive: true, force: true })
} else if (existsSync(path)) {
const files = await fsp.readdir(path)
await Promise.all(files.map(async (name) => {
if (!exclude.includes(name)) {
await fsp.rm(join(path, name), { recursive: true, force: true })
}
}))
}
await fsp.mkdir(path, { recursive: true })
}
export function clearBuildDir (path: string) {
return clearDir(path, ['cache', 'analyze'])
}
export async function rmRecursive (paths: string[]) {
await Promise.all(paths.filter(p => typeof p === 'string').map(async (path) => {
consola.debug('Removing recursive path', path)
await fsp.rm(path, { recursive: true, force: true }).catch(() => {})
}))
}
export async function touchFile (path: string) {
const time = new Date()
await fsp.utimes(path, time, time).catch(() => {})
}
export function findup<T> (rootDir: string, fn: (dir: string) => T | undefined): T | null {
let dir = rootDir
while (dir !== dirname(dir)) {
const res = fn(dir)
if (res) {
return res
}
dir = dirname(dir)
}
return null
}

View File

@ -1,20 +0,0 @@
import { cyan, magenta } from 'colorette'
import type { NuxtCommandMeta } from '../commands'
export function showHelp (meta?: Partial<NuxtCommandMeta>) {
const sections: string[] = []
if (meta) {
if (meta.usage) {
sections.push(magenta('> ') + 'Usage: ' + cyan(meta.usage))
}
if (meta.description) {
sections.push(magenta('⋮ ') + meta.description)
}
}
sections.push(`Use ${cyan('npx nuxi [command] --help')} to see help for each command`)
console.log(sections.join('\n\n') + '\n')
}

View File

@ -1,24 +0,0 @@
import { importModule, tryResolveModule } from './esm'
export const loadKit = async (rootDir: string): Promise<typeof import('@nuxt/kit')> => {
try {
// Without PNP (or if users have a local install of kit, we bypass resolving from nuxt)
const localKit = await tryResolveModule('@nuxt/kit', rootDir)
// Otherwise, we resolve Nuxt _first_ as it is Nuxt's kit dependency that will be used
const rootURL = localKit ? rootDir : await tryResolveNuxt() || rootDir
return await importModule('@nuxt/kit', rootURL) as typeof import('@nuxt/kit')
} catch (e: any) {
if (e.toString().includes("Cannot find module '@nuxt/kit'")) {
throw new Error('nuxi requires `@nuxt/kit` to be installed in your project. Try installing `nuxt` v3 or `@nuxt/bridge` first.')
}
throw e
}
}
async function tryResolveNuxt () {
for (const pkg of ['nuxt3', 'nuxt', 'nuxt-edge']) {
const path = await tryResolveModule(pkg)
if (path) { return path }
}
return null
}

View File

@ -1,68 +0,0 @@
import { promises as fsp } from 'node:fs'
import { dirname, resolve } from 'pathe'
import { consola } from 'consola'
import { hash } from 'ohash'
import type { Nuxt } from '@nuxt/schema'
import { rmRecursive } from './fs'
interface NuxtProjectManifest {
_hash: string | null
project: {
rootDir: string
},
versions: {
nuxt: string
}
}
export async function cleanupNuxtDirs (rootDir: string) {
consola.info('Cleaning up generated nuxt files and caches...')
await rmRecursive([
'.nuxt',
'.output',
'dist',
'node_modules/.vite',
'node_modules/.cache'
].map(dir => resolve(rootDir, dir)))
}
export function nuxtVersionToGitIdentifier (version: string) {
// match the git identifier in the release, for example: 3.0.0-rc.8-27677607.a3a8706
const id = /\.([0-9a-f]{7,8})$/.exec(version)
if (id?.[1]) {
return id[1]
}
// match github tag, for example 3.0.0-rc.8
return `v${version}`
}
function resolveNuxtManifest (nuxt: Nuxt): NuxtProjectManifest {
const manifest: NuxtProjectManifest = {
_hash: null,
project: {
rootDir: nuxt.options.rootDir
},
versions: {
nuxt: nuxt._version
}
}
manifest._hash = hash(manifest)
return manifest
}
export async function writeNuxtManifest (nuxt: Nuxt): Promise<NuxtProjectManifest> {
const manifest = resolveNuxtManifest(nuxt)
const manifestPath = resolve(nuxt.options.buildDir, 'nuxt.json')
await fsp.mkdir(dirname(manifestPath), { recursive: true })
await fsp.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8')
return manifest
}
export async function loadNuxtManifest (buildDir: string): Promise<NuxtProjectManifest | null> {
const manifestPath = resolve(buildDir, 'nuxt.json')
const manifest: NuxtProjectManifest | null = await fsp.readFile(manifestPath, 'utf-8')
.then(data => JSON.parse(data) as NuxtProjectManifest)
.catch(() => null)
return manifest
}

View File

@ -1,28 +0,0 @@
import { execSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { resolve } from 'pathe'
import { findup } from './fs'
export const packageManagerLocks = {
yarn: 'yarn.lock',
npm: 'package-lock.json',
pnpm: 'pnpm-lock.yaml',
bun: 'bun.lockb'
}
type PackageManager = keyof typeof packageManagerLocks
export function getPackageManager (rootDir: string) {
return findup(rootDir, (dir) => {
for (const name in packageManagerLocks) {
const path = packageManagerLocks[name as PackageManager]
if (path && existsSync(resolve(dir, path))) {
return name
}
}
}) as PackageManager | null
}
export function getPackageManagerVersion (name: string) {
return execSync(`${name} --version`).toString('utf8').trim()
}

View File

@ -1,119 +0,0 @@
import { upperFirst } from 'scule'
interface TemplateOptions {
name: string,
args: Record<string, any>
}
interface Template {
(options: TemplateOptions): { path: string, contents: string }
}
const httpMethods = ['connect', 'delete', 'get', 'head', 'options', 'post', 'put', 'trace', 'patch']
const api: Template = ({ name, args }) => ({
path: `server/api/${name}${applySuffix(args, httpMethods, 'method')}.ts`,
contents: `
export default defineEventHandler((event) => {
return 'Hello ${name}'
})
`
})
const plugin: Template = ({ name, args }) => ({
path: `plugins/${name}${applySuffix(args, ['client', 'server'], 'mode')}.ts`,
contents: `
export default defineNuxtPlugin((nuxtApp) => {})
`
})
const component: Template = ({ name, args }) => ({
path: `components/${name}${applySuffix(args, ['client', 'server'], 'mode')}.vue`,
contents: `
<script lang="ts" setup></script>
<template>
<div>
Component: ${name}
</div>
</template>
<style scoped></style>
`
})
const composable: Template = ({ name }) => {
const nameWithUsePrefix = name.startsWith('use') ? name : `use${upperFirst(name)}`
return {
path: `composables/${name}.ts`,
contents: `
export const ${nameWithUsePrefix} = () => {
return ref()
}
`
}
}
const middleware: Template = ({ name, args }) => ({
path: `middleware/${name}${applySuffix(args, ['global'])}.ts`,
contents: `
export default defineNuxtRouteMiddleware((to, from) => {})
`
})
const layout: Template = ({ name }) => ({
path: `layouts/${name}.vue`,
contents: `
<script lang="ts" setup></script>
<template>
<div>
Layout: ${name}
<slot />
</div>
</template>
<style scoped></style>
`
})
const page: Template = ({ name }) => ({
path: `pages/${name}.vue`,
contents: `
<script lang="ts" setup></script>
<template>
<div>
Page: foo
</div>
</template>
<style scoped></style>
`
})
export const templates = {
api,
plugin,
component,
composable,
middleware,
layout,
page
} as Record<string, Template>
// -- internal utils --
function applySuffix (args: TemplateOptions['args'], suffixes: string[], unwrapFrom?: string): string {
let suffix = ''
// --client
for (const s of suffixes) {
if (args[s]) {
suffix += '.' + s
}
}
// --mode=server
if (unwrapFrom && args[unwrapFrom] && suffixes.includes(args[unwrapFrom])) {
suffix += '.' + args[unwrapFrom]
}
return suffix
}

View File

@ -82,7 +82,7 @@
"magic-string": "^0.30.3",
"mlly": "^1.4.0",
"nitropack": "^2.6.0",
"nuxi": "workspace:../nuxi",
"nuxi": "npm:nuxi-ng@0.3.0-1692970235.c259efa",
"nypm": "^0.3.1",
"ofetch": "^1.3.3",
"ohash": "^1.1.3",

View File

@ -17,11 +17,12 @@ export async function startServer () {
ctx.url = 'http://127.0.0.1:' + port
if (ctx.options.dev) {
const nuxiCLI = await kit.resolvePath('nuxi/cli')
ctx.serverProcess = execa(nuxiCLI, ['dev'], {
ctx.serverProcess = execa(nuxiCLI, ['_dev'], {
cwd: ctx.nuxt!.options.rootDir,
stdio: 'inherit',
env: {
...process.env,
_PORT: String(port),
PORT: String(port),
NITRO_PORT: String(port),
NODE_ENV: 'development'
@ -29,7 +30,7 @@ export async function startServer () {
})
await waitForPort(port, { retries: 32 })
let lastError
for (let i = 0; i < 50; i++) {
for (let i = 0; i < 150; i++) {
await new Promise(resolve => setTimeout(resolve, 100))
try {
const res = await $fetch(ctx.nuxt!.options.app.baseURL)

View File

@ -10,7 +10,6 @@ overrides:
'@nuxt/test-utils': workspace:*
'@nuxt/vite-builder': workspace:*
'@nuxt/webpack-builder': workspace:*
nuxi: workspace:*
nuxt: workspace:*
vite: 4.4.9
vue: 3.3.4
@ -96,8 +95,8 @@ importers:
specifier: 2.6.0
version: 2.6.0
nuxi:
specifier: workspace:*
version: link:packages/nuxi
specifier: npm:nuxi-ng@0.3.0-1692970235.c259efa
version: /nuxi-ng@0.3.0-1692970235.c259efa
nuxt:
specifier: workspace:*
version: link:packages/nuxt
@ -241,109 +240,6 @@ importers:
specifier: 5.88.2
version: 5.88.2
packages/nuxi:
optionalDependencies:
fsevents:
specifier: ~2.3.3
version: 2.3.3
devDependencies:
'@nuxt/kit':
specifier: workspace:*
version: link:../kit
'@nuxt/schema':
specifier: workspace:*
version: link:../schema
'@types/clear':
specifier: 0.1.2
version: 0.1.2
'@types/flat':
specifier: 5.0.2
version: 5.0.2
'@types/mri':
specifier: 1.1.1
version: 1.1.1
'@types/semver':
specifier: 7.5.0
version: 7.5.0
c12:
specifier: 1.4.2
version: 1.4.2
chokidar:
specifier: 3.5.3
version: 3.5.3
clear:
specifier: 0.1.0
version: 0.1.0
clipboardy:
specifier: 3.0.0
version: 3.0.0
colorette:
specifier: 2.0.20
version: 2.0.20
consola:
specifier: 3.2.3
version: 3.2.3
deep-object-diff:
specifier: 1.1.9
version: 1.1.9
defu:
specifier: 6.1.2
version: 6.1.2
destr:
specifier: 2.0.1
version: 2.0.1
execa:
specifier: 7.2.0
version: 7.2.0
flat:
specifier: 5.0.2
version: 5.0.2
giget:
specifier: 1.1.2
version: 1.1.2
h3:
specifier: 1.8.0
version: 1.8.0
jiti:
specifier: 1.19.3
version: 1.19.3
listhen:
specifier: 1.4.0
version: 1.4.0
mlly:
specifier: 1.4.0
version: 1.4.0
mri:
specifier: 1.2.0
version: 1.2.0
nitropack:
specifier: 2.6.0
version: 2.6.0
ohash:
specifier: 1.1.3
version: 1.1.3
pathe:
specifier: 1.1.1
version: 1.1.1
perfect-debounce:
specifier: 1.0.0
version: 1.0.0
pkg-types:
specifier: 1.0.3
version: 1.0.3
scule:
specifier: 1.0.0
version: 1.0.0
semver:
specifier: 7.5.4
version: 7.5.4
ufo:
specifier: 1.3.0
version: 1.3.0
unbuild:
specifier: latest
version: 2.0.0(typescript@5.2.2)
packages/nuxt:
dependencies:
'@nuxt/devalue':
@ -440,8 +336,8 @@ importers:
specifier: ^2.6.0
version: 2.6.0
nuxi:
specifier: workspace:*
version: link:../nuxi
specifier: npm:nuxi-ng@0.3.0-1692970235.c259efa
version: /nuxi-ng@0.3.0-1692970235.c259efa
nypm:
specifier: ^0.3.1
version: 0.3.1
@ -2630,6 +2526,7 @@ packages:
dependencies:
is-glob: 4.0.3
micromatch: 4.0.5
napi-wasm: 1.1.0
bundledDependencies:
- napi-wasm
@ -2921,10 +2818,6 @@ packages:
'@types/webpack': 4.41.33
dev: true
/@types/flat@5.0.2:
resolution: {integrity: sha512-3zsplnP2djeps5P9OyarTxwRpMLoe5Ash8aL9iprw0JxB+FAHjY+ifn4yZUuW4/9hqtnmor6uvjSRzJhiVbrEQ==}
dev: true
/@types/fs-extra@11.0.1:
resolution: {integrity: sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==}
dependencies:
@ -2995,10 +2888,6 @@ packages:
'@types/unist': 2.0.7
dev: true
/@types/mri@1.1.1:
resolution: {integrity: sha512-nJOuiTlsvmClSr3+a/trTSx4DTuY/VURsWGKSf/eeavh0LRMqdsK60ti0TlwM5iHiGOK3/Ibkxsbr7i9rzGreA==}
dev: true
/@types/ms@0.7.31:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: true
@ -4573,6 +4462,7 @@ packages:
/clear@0.1.0:
resolution: {integrity: sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==}
dev: false
/clipboardy@3.0.0:
resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==}
@ -4992,10 +4882,6 @@ packages:
/deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
/deep-object-diff@1.1.9:
resolution: {integrity: sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==}
dev: true
/deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
@ -8062,6 +7948,9 @@ packages:
engines: {node: ^14 || ^16 || >=18}
hasBin: true
/napi-wasm@1.1.0:
resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==}
/natural-compare-lite@1.4.0:
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
dev: true
@ -8274,6 +8163,13 @@ packages:
dependencies:
boolbase: 1.0.0
/nuxi-ng@0.3.0-1692970235.c259efa:
resolution: {integrity: sha512-/yp+V+Vpype1fau39yktrkFu2UZ2jo5bSdvDB8lI3LEdYT7sF9zg+6HqP+0OAV4Vnmst6ATxwY+pZiK+ds5mig==}
engines: {node: ^14.18.0 || >=16.10.0}
hasBin: true
optionalDependencies:
fsevents: 2.3.3
/nuxt-component-meta@0.5.3:
resolution: {integrity: sha512-+MHUrESdr+Si9PdbkxQrzQv+X6RdRd/ffmFWVVsZAHA7X9vGoNAYxwvoB1Pbs15TaPtFBWA1P5a4VGqtp+bhAg==}
dependencies: