Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 74 additions & 11 deletions packages/shared/src/commands/add.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { log, select } from '@clack/prompts'
import { intro, log, outro, select, spinner } from '@clack/prompts'
import { defineCommand, runCommand } from 'citty'
import { addEslint } from '../functions'
import { REGISTRY_ORIGIN } from '../constants/registry'
import { addEslint, addFeature } from '../functions'
import { getIndex } from '../functions/registry'
import { i18n } from '../i18n'
import { mcp } from './mcp'

/**
* Integrations wire a tool into the project. Everything else typed after `add`
* is looked up in the registry, so `vuetify add dialog` writes a working,
* styled example instead of leaving the user to copy it out of the docs.
*/
const choices = ['eslint']

export const add = defineCommand({
Expand All @@ -14,8 +21,32 @@ export const add = defineCommand({
args: {
integration: {
type: 'positional',
required: false,
description: i18n.t('commands.add.integration.description', { choices: choices.join(', ') }),
},
example: {
type: 'string',
description: i18n.t('commands.add.args.example'),
},
dir: {
type: 'string',
description: i18n.t('commands.add.args.dir'),
},
registry: {
type: 'string',
default: REGISTRY_ORIGIN,
description: i18n.t('commands.add.args.registry'),
},
overwrite: {
type: 'boolean',
default: false,
description: i18n.t('commands.add.args.overwrite'),
},
yes: {
type: 'boolean',
default: false,
description: i18n.t('commands.add.args.yes'),
},
},
run: async ({ args }) => {
let integration = args.integration
Expand All @@ -26,26 +57,58 @@ export const add = defineCommand({
return
}

// With no argument, offer integrations and registry items in one list so
// neither surface is hidden behind knowing its name up front.
if (!integration) {
const loader = spinner()
loader.start(i18n.t('spinners.registry.fetching'))
const index = await getIndex(args.registry)
loader.stop(i18n.t('spinners.registry.fetched'))

const selected = await select({
message: i18n.t('prompts.add.integration'),
options: choices.map(c => ({ label: c, value: c })),
message: i18n.t('prompts.add.feature'),
options: [
...choices.map(choice => ({ label: choice, value: choice, hint: 'integration' })),
...index.items.map(item => ({
label: item.name,
value: item.name,
hint: `${item.type === 'components' ? 'component' : 'composable'} · ${item.category}`,
})),
],
})

if (typeof selected === 'symbol') {
log.warning(i18n.t('commands.add.integration.available', { choices: choices.join(', ') }))
return
}

integration = String(selected)
}
if (!choices.includes(integration)) {
log.error(i18n.t('commands.add.integration.invalid', { integration, choices: choices.join(', ') }))

if (choices.includes(integration)) {
switch (integration) {
case 'eslint': {
await addEslint()
break
}
}
return
}
switch (integration) {
case 'eslint': {
await addEslint()
break
}

intro(i18n.t('commands.add.intro', { name: integration }))

const written = await addFeature({
name: integration,
example: args.example,
dir: args.dir,
registry: args.registry,
overwrite: args.overwrite,
yes: args.yes,
})

// A failed resolution has already said why — don't follow it with "all done".
if (written.length > 0) {
outro(i18n.t('messages.all_done'))
}
},
})
21 changes: 21 additions & 0 deletions packages/shared/src/constants/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/** Static origin publishing the `vuetify add` registry. */
export const REGISTRY_ORIGIN = 'https://0.vuetifyjs.com'

/** Registry payload version this CLI understands. */
export const REGISTRY_VERSION = 1

export const V0 = '@vuetify/v0'

/** Emits the `--v0-*` custom properties the semantic utilities resolve against. */
export const THEME_PLUGIN = 'createThemePlugin'

export const REGISTRY_TIMEOUT = 15_000

export const UNOCSS_CONFIGS = [
'uno.config.ts',
'uno.config.js',
'uno.config.mjs',
'unocss.config.ts',
'unocss.config.js',
'unocss.config.mjs',
]
Loading