Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3ce4f79
feat(cli): add tab completions
AmirSa12 Oct 11, 2025
447e1a3
readme
AmirSa12 Oct 13, 2025
0461d5c
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 13, 2025
394e3bc
update
AmirSa12 Oct 13, 2025
2bf2b6c
bombshell v0.0.5
AmirSa12 Oct 13, 2025
22fc13c
types
AmirSa12 Oct 13, 2025
8877baa
update
AmirSa12 Oct 13, 2025
81e36cf
updates
AmirSa12 Oct 17, 2025
d1babb4
use node module resolution
AmirSa12 Oct 17, 2025
632781d
Merge remote-tracking branch 'origin/main' into feat/tab-completions
danielroe Oct 21, 2025
2252472
refactor: use native node to run completions data
danielroe Oct 23, 2025
87e58f9
chore: update tab to version 0.0.7
AmirSa12 Oct 23, 2025
1860f10
chore: readme
AmirSa12 Oct 23, 2025
63b2c8b
Merge remote-tracking branch 'origin/main' into feat/tab-completions
danielroe Oct 30, 2025
d4213cd
chore: move into build script
danielroe Oct 30, 2025
d64d259
chore: suppress lint
danielroe Oct 30, 2025
c7352b5
chore: lint
danielroe Oct 30, 2025
4e7d9bc
build: also for create nuxt
danielroe Oct 30, 2025
6325654
build: move back to postinstall
danielroe Oct 30, 2025
f15c787
docs: remove global installation instructions
danielroe Oct 30, 2025
1ede8e8
chore: knip update
danielroe Oct 30, 2025
c1c28d0
chore: use file url for output path
danielroe Oct 30, 2025
243bf65
chore: update tab to version 0.0.8
AmirSa12 Oct 30, 2025
c016105
update tab to version 0.0.9
AmirSa12 Nov 4, 2025
590290c
Merge remote-tracking branch 'upstream/main' into feat/tab-completions
AmirSa12 Nov 4, 2025
d37a720
Merge remote-tracking branch 'upstream/main' into feat/tab-completions
AmirSa12 Nov 4, 2025
166246a
Merge remote-tracking branch 'origin/main' into feat/tab-completions
danielroe Nov 4, 2025
c9c8bf7
build: use module url for import
danielroe Nov 4, 2025
44a49d1
chore: dance, dance little man
danielroe Nov 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,32 @@

All commands are listed on https://nuxt.com/docs/api/commands.

## Shell Autocompletions

Nuxi provides shell autocompletions for commands, options, and option values powered by [`@bomb.sh/tab`](https://github.com/bombshell-dev/tab).

### Setup

For permanent setup in zsh, add this to your `~/.zshrc`:

```bash
# Add to ~/.zshrc for permanent autocompletions (same can be done for other shells)
source <(nuxt complete zsh)
```

### Package Manager Integration

`@bomb.sh/tab` integrates with [package managers](https://github.com/bombshell-dev/tab?tab=readme-ov-file#package-manager-completions). Autocompletions work when running `nuxt` directly within a Nuxt project:

```bash
pnpm nuxt <Tab>
npm run nuxt <Tab>
yarn nuxt <Tab>
bun nuxt <Tab>
```

For package manager autocompletions, you should install [tab's package manager completions](https://github.com/bombshell-dev/tab?tab=readme-ov-file#package-manager-completions) separately.

## Contributing

```bash
Expand Down
1 change: 1 addition & 0 deletions packages/create-nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"citty": "^0.1.6"
},
"devDependencies": {
"@bomb.sh/tab": "^0.0.6",
"@types/node": "^22.18.10",
"rollup": "^4.52.4",
"rollup-plugin-visualizer": "^6.0.4",
Expand Down
8 changes: 8 additions & 0 deletions packages/create-nuxt/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { provider } from 'std-env'

import init from '../../nuxi/src/commands/init'
import { setupInitCompletions } from '../../nuxi/src/completions-init'
import { setupGlobalConsole } from '../../nuxi/src/utils/console'
import { checkEngines } from '../../nuxi/src/utils/engines'
import { logger } from '../../nuxi/src/utils/logger'
Expand All @@ -15,6 +16,11 @@
},
args: init.args,
async setup(ctx) {
const isCompletionRequest = ctx.args._?.[0] === 'complete'
if (isCompletionRequest) {
return
}

setupGlobalConsole({ dev: false })

// Check Node.js version and CLI updates in background
Expand All @@ -25,3 +31,5 @@
await init.run?.(ctx)
},
})

await setupInitCompletions(main)

Check failure on line 35 in packages/create-nuxt/src/main.ts

View workflow job for this annotation

GitHub Actions / autofix

Do not use top-level await

Check failure on line 35 in packages/create-nuxt/src/main.ts

View workflow job for this annotation

GitHub Actions / lint

Do not use top-level await
4 changes: 3 additions & 1 deletion packages/nuxi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@
},
"scripts": {
"dev:prepare": "unbuild --stub",
"build": "unbuild",
"build": "pnpm generate:completions-data && unbuild",
"build:stub": "unbuild --stub",
"dev": "node ./bin/nuxi.mjs dev ./playground",
"dev:bun": "bun --bun ./bin/nuxi.mjs dev ./playground",
"generate:completions-data": "tsx ./scripts/generate-completions-data.ts",
"nuxi": "node ./bin/nuxi.mjs",
"nuxi-bun": "bun --bun ./bin/nuxi.mjs",
"prepack": "unbuild",
"test:dist": "node ./bin/nuxi.mjs info ./playground"
},
"devDependencies": {
"@bomb.sh/tab": "^0.0.6",
"@nuxt/kit": "^4.1.3",
"@nuxt/schema": "^4.1.3",
"@nuxt/test-utils": "^3.19.2",
Expand Down
74 changes: 74 additions & 0 deletions packages/nuxi/scripts/generate-completions-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/** generate completion data from nitropack and Nuxt starter repo */

import { writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import { dirname, join, resolve } from 'node:path'

Check failure on line 6 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected "node:path" to come before "node:url"

const require = createRequire(import.meta.url)
const __dirname = dirname(fileURLToPath(import.meta.url))

interface PresetMeta {
_meta?: { name: string }

Check failure on line 12 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 2 spaces but found 4

Check failure on line 12 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 2 spaces but found 4
}

async function generateCompletionData() {
const data: {

Check failure on line 16 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 2 spaces but found 4

Check failure on line 16 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 2 spaces but found 4
nitroPresets: string[]

Check failure on line 17 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 8

Check failure on line 17 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 8
templates: string[]

Check failure on line 18 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 8

Check failure on line 18 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 8
} = {
nitroPresets: [],
templates: [],
}

const nitropackPath = dirname(require.resolve('nitropack/package.json'))
const presetsPath = join(nitropackPath, 'dist/presets/_all.gen.mjs')
const { default: allPresets } = await import(presetsPath) as { default: PresetMeta[] }

data.nitroPresets = allPresets
.map(preset => preset._meta?.name)
.filter((name): name is string => Boolean(name))
.filter(name => !['base-worker', 'nitro-dev', 'nitro-prerender'].includes(name))
.filter((name, index, array) => array.indexOf(name) === index)
.sort()

const response = await fetch(
'https://api.github.com/repos/nuxt/starter/contents/templates?ref=templates'
)

if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`)
}

const files = await response.json() as Array<{ name: string; type: string }>

const templateEntries = files
.filter(file => {
if (file.type === 'dir') return true
if (file.type === 'file' && file.name.endsWith('.json') && file.name !== 'content.json') {
return true
}
return false
})
.map(file => file.name.replace('.json', ''))

data.templates = Array.from(new Set(templateEntries))
.filter(name => name !== 'community')
.sort()

const outputPath = resolve(__dirname, '../src/utils/completions-data.ts')
const content = `/** Auto-generated file */

export const nitroPresets = ${JSON.stringify(data.nitroPresets, null, 2)} as const

export const templates = ${JSON.stringify(data.templates, null, 2)} as const
`

await writeFile(outputPath, content, 'utf-8')
}

generateCompletionData().catch((error) => {
console.error('Failed to generate completion data:', error)
process.exit(1)
})

Check failure on line 73 in packages/nuxi/scripts/generate-completions-data.ts

View workflow job for this annotation

GitHub Actions / autofix

Unexpected use of the global variable 'process'. Use 'require("process")' instead

28 changes: 28 additions & 0 deletions packages/nuxi/src/completions-init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ArgsDef, CommandDef } from 'citty'
import tab from '@bomb.sh/tab/citty'
import { templates } from './utils/completions-data'

export async function setupInitCompletions<T extends ArgsDef = ArgsDef>(command: CommandDef<T>) {
const completion = await tab(command)

const templateOption = completion.options?.get('template')
if (templateOption) {
templateOption.handler = (complete) => {
for (const template of templates) {
complete(template, '')
}
}
}

const logLevelOption = completion.options?.get('logLevel')
if (logLevelOption) {
logLevelOption.handler = (complete) => {
complete('silent', 'No logs')
complete('info', 'Standard logging')
complete('verbose', 'Detailed logging')
}
}

return completion
}

79 changes: 79 additions & 0 deletions packages/nuxi/src/completions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ArgsDef, CommandDef } from 'citty'
import tab from '@bomb.sh/tab/citty'
import { nitroPresets, templates } from './utils/completions-data'

export async function initCompletions<T extends ArgsDef = ArgsDef>(command: CommandDef<T>) {
const completion = await tab(command)

const devCommand = completion.commands.get('dev')
if (devCommand) {
const portOption = devCommand.options.get('port')
if (portOption) {
portOption.handler = (complete) => {
complete('3000', 'Default development port')
complete('3001', 'Alternative port')
complete('8080', 'Common alternative port')
}
}

const hostOption = devCommand.options.get('host')
if (hostOption) {
hostOption.handler = (complete) => {
complete('localhost', 'Local development')
complete('0.0.0.0', 'Listen on all interfaces')
complete('127.0.0.1', 'Loopback address')
}
}
}

const buildCommand = completion.commands.get('build')
if (buildCommand) {
const presetOption = buildCommand.options.get('preset')
if (presetOption) {
presetOption.handler = (complete) => {
for (const preset of nitroPresets) {
complete(preset, '')
}
}
}
}

const initCommand = completion.commands.get('init')
if (initCommand) {
const templateOption = initCommand.options.get('template')
if (templateOption) {
templateOption.handler = (complete) => {
for (const template of templates) {
complete(template, '')
}
}
}
}

const addCommand = completion.commands.get('add')
if (addCommand) {
const cwdOption = addCommand.options.get('cwd')
if (cwdOption) {
cwdOption.handler = (complete) => {
complete('.', 'Current directory')
}
}
}

const logLevelCommands = ['dev', 'build', 'generate', 'preview', 'prepare', 'init']
for (const cmdName of logLevelCommands) {
const cmd = completion.commands.get(cmdName)
if (cmd) {
const logLevelOption = cmd.options.get('logLevel')
if (logLevelOption) {
logLevelOption.handler = (complete) => {
complete('silent', 'No logs')
complete('info', 'Standard logging')
complete('verbose', 'Detailed logging')
}
}
}
}

return completion
}
7 changes: 6 additions & 1 deletion packages/nuxi/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { provider } from 'std-env'
import { description, name, version } from '../package.json'
import { commands } from './commands'
import { cwdArgs } from './commands/_shared'
import { initCompletions } from './completions'
import { setupGlobalConsole } from './utils/console'
import { checkEngines } from './utils/engines'
import { logger } from './utils/logger'
Expand Down Expand Up @@ -84,4 +85,8 @@ export const main = defineCommand({
},
})

export const runMain = () => _runMain(main)
export async function runMain() {
await initCompletions(main)

return _runMain(main)
}
69 changes: 69 additions & 0 deletions packages/nuxi/src/utils/completions-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/** Auto-generated file */

export const nitroPresets = [
"alwaysdata",
"aws-amplify",
"aws-lambda",
"azure-functions",
"azure-swa",
"bun",
"cleavr",
"cli",
"cloudflare-dev",
"cloudflare-durable",
"cloudflare-module",
"cloudflare-module-legacy",
"cloudflare-pages",
"cloudflare-pages-static",
"cloudflare-worker",
"deno-deploy",
"deno-server",
"deno-server-legacy",
"digital-ocean",
"edgio",
"firebase",
"firebase-app-hosting",
"flight-control",
"genezio",
"github-pages",
"gitlab-pages",
"heroku",
"iis-handler",
"iis-node",
"koyeb",
"netlify",
"netlify-builder",
"netlify-edge",
"netlify-legacy",
"netlify-static",
"node-cluster",
"node-listener",
"node-server",
"platform-sh",
"render-com",
"service-worker",
"static",
"stormkit",
"vercel",
"vercel-edge",
"vercel-static",
"winterjs",
"zeabur",
"zeabur-static",
"zerops",
"zerops-static"
] as const

export const templates = [
"doc-driven",
"hub",
"layer",
"module",
"module-devtools",
"ui",
"ui-vue",
"v2-bridge",
"v3",
"v4",
"v4-compat"
] as const
1 change: 1 addition & 0 deletions packages/nuxt-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"prepack": "unbuild"
},
"dependencies": {
"@bomb.sh/tab": "^0.0.6",
"c12": "^3.3.0",
"citty": "^0.1.6",
"clipboardy": "^5.0.0",
Expand Down
6 changes: 5 additions & 1 deletion packages/nuxt-cli/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'
import { runCommand as _runCommand, runMain as _runMain } from 'citty'

import { commands } from '../../nuxi/src/commands'
import { initCompletions } from '../../nuxi/src/completions'
import { main } from './main'

globalThis.__nuxt_cli__ = globalThis.__nuxt_cli__ || {
Expand All @@ -17,7 +18,10 @@ globalThis.__nuxt_cli__ = globalThis.__nuxt_cli__ || {
),
}

export const runMain = () => _runMain(main)
export const runMain = async () => {
await initCompletions(main)
return _runMain(main)
}

export async function runCommand(
name: string,
Expand Down
Loading
Loading