diff --git a/packages/nuxi/src/commands/_utils.ts b/packages/nuxi/src/commands/_utils.ts new file mode 100644 index 00000000..024ce837 --- /dev/null +++ b/packages/nuxi/src/commands/_utils.ts @@ -0,0 +1,26 @@ +import type { commands } from '.' + +// Inlined list of nuxi commands to avoid including `commands` in bundle if possible +export const nuxiCommands = [ + 'add', + 'analyze', + 'build', + 'cleanup', + '_dev', + 'dev', + 'devtools', + 'generate', + 'info', + 'init', + 'module', + 'prepare', + 'preview', + 'start', + 'test', + 'typecheck', + 'upgrade', +] as const satisfies (keyof typeof commands)[] + +export function isNuxiCommand(command: string) { + return (nuxiCommands as string[]).includes(command) +} diff --git a/packages/nuxi/src/commands/init.ts b/packages/nuxi/src/commands/init.ts index 57008fbe..1a46d3f5 100644 --- a/packages/nuxi/src/commands/init.ts +++ b/packages/nuxi/src/commands/init.ts @@ -19,6 +19,7 @@ import { runCommand } from '../run' import { nuxtIcon, themeColor } from '../utils/ascii' import { logger } from '../utils/logger' import { cwdArgs, logLevelArgs } from './_shared' +import addModuleCommand from './module/add' const DEFAULT_REGISTRY = 'https://raw.githubusercontent.com/nuxt/starter/templates/templates' const DEFAULT_TEMPLATE_NAME = 'v4' @@ -435,14 +436,13 @@ export default defineCommand({ // Add modules if (modulesToAdd.length > 0) { const args: string[] = [ - 'add', ...modulesToAdd, `--cwd=${templateDownloadPath}`, ctx.args.install ? '' : '--skipInstall', ctx.args.logLevel ? `--logLevel=${ctx.args.logLevel}` : '', ].filter(Boolean) - await runCommand('module', args) + await runCommand(addModuleCommand, args) } // Display next steps diff --git a/packages/nuxi/src/commands/module/add.ts b/packages/nuxi/src/commands/module/add.ts index b40723a3..8a5da677 100644 --- a/packages/nuxi/src/commands/module/add.ts +++ b/packages/nuxi/src/commands/module/add.ts @@ -21,6 +21,7 @@ import { runCommand } from '../../run' import { logger } from '../../utils/logger' import { getNuxtVersion } from '../../utils/versions' import { cwdArgs, logLevelArgs } from '../_shared' +import prepareCommand from '../prepare' import { checkNuxtCompatibility, fetchModules, getRegistryFromContent } from './_utils' interface RegistryMeta { @@ -95,7 +96,7 @@ export default defineCommand({ if (!ctx.args.skipInstall) { const args = Object.entries(ctx.args).filter(([k]) => k in cwdArgs || k in logLevelArgs).map(([k, v]) => `--${k}=${v}`) - await runCommand('prepare', args) + await runCommand(prepareCommand, args) } }, }) diff --git a/packages/nuxi/src/index.ts b/packages/nuxi/src/index.ts index 580bb27c..70f311c8 100644 --- a/packages/nuxi/src/index.ts +++ b/packages/nuxi/src/index.ts @@ -1,2 +1,2 @@ -export { main } from './main' -export { runCommand, runMain } from './run' +export { main, runMain } from './main' +export { runCommand } from './run' diff --git a/packages/nuxi/src/main.ts b/packages/nuxi/src/main.ts index 7b26ae95..8f0457c4 100644 --- a/packages/nuxi/src/main.ts +++ b/packages/nuxi/src/main.ts @@ -2,7 +2,7 @@ import nodeCrypto from 'node:crypto' import { resolve } from 'node:path' import process from 'node:process' -import { defineCommand } from 'citty' +import { runMain as _runMain, defineCommand } from 'citty' import { provider } from 'std-env' import { description, name, version } from '../package.json' @@ -71,3 +71,5 @@ export const main = defineCommand({ } }, }) + +export const runMain = () => _runMain(main) diff --git a/packages/nuxi/src/run.ts b/packages/nuxi/src/run.ts index 6d3d2cc6..3975cd6b 100644 --- a/packages/nuxi/src/run.ts +++ b/packages/nuxi/src/run.ts @@ -1,10 +1,10 @@ +import type { ArgsDef, CommandDef } from 'citty' import process from 'node:process' -import { fileURLToPath } from 'node:url' -import { runCommand as _runCommand, runMain as _runMain } from 'citty' +import { fileURLToPath } from 'node:url' +import { runCommand as _runCommand } from 'citty' -import { commands } from './commands' -import { main } from './main' +import { isNuxiCommand } from './commands/_utils' globalThis.__nuxt_cli__ = globalThis.__nuxt_cli__ || { // Programmatic usage fallback @@ -17,21 +17,24 @@ globalThis.__nuxt_cli__ = globalThis.__nuxt_cli__ || { ), } -export const runMain = () => _runMain(main) - // To provide subcommands call it as `runCommand(, [, ...])` -export async function runCommand( - name: string, +export async function runCommand( + command: CommandDef, argv: string[] = process.argv.slice(2), data: { overrides?: Record } = {}, ) { argv.push('--no-clear') // Dev - - if (!(name in commands)) { - throw new Error(`Invalid command ${name}`) + if (command.meta && 'name' in command.meta && typeof command.meta.name === 'string') { + const name = command.meta.name + if (!(isNuxiCommand(name))) { + throw new Error(`Invalid command ${name}`) + } + } + else { + throw new Error(`Invalid command, must be named`) } - return await _runCommand(await commands[name as keyof typeof commands](), { + return await _runCommand(command, { rawArgs: argv, data: { overrides: data.overrides || {}, diff --git a/packages/nuxi/test/unit/commands/_utils.test.ts b/packages/nuxi/test/unit/commands/_utils.test.ts new file mode 100644 index 00000000..e61ea96d --- /dev/null +++ b/packages/nuxi/test/unit/commands/_utils.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' +import { isNuxiCommand, nuxiCommands } from '../../../src/commands/_utils' + +describe('isNuxiCommand', () => { + it('should return true for valid nuxi commands', () => { + nuxiCommands.forEach((command) => { + expect(isNuxiCommand(command)).toBe(true) + }) + }) + + it('should return false for invalid nuxi commands', () => { + const invalidCommands = [ + '', + ' ', + 'devv', + 'Dev', + 'BuilD', + 'random', + 'nuxi', + 'install', + undefined, + null, + ] + + invalidCommands.forEach((command) => { + expect(isNuxiCommand(command as string)).toBe(false) + }) + }) +})