create-vuetify 0.0.6-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,177 @@
1
+ import fs, { existsSync, rmSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { intro, outro, spinner } from '@clack/prompts'
4
+ import { createBanner, initVuetify0, projectArgs } from '@vuetify/cli-shared'
5
+ import { i18n } from '@vuetify/cli-shared/i18n'
6
+ import { defineCommand, runMain } from 'citty'
7
+ import { downloadTemplate } from 'giget'
8
+ import { readPackageJSON, writePackageJSON } from 'pkg-types'
9
+
10
+ import { version } from '../package.json'
11
+ import { upgrade } from './commands/upgrade'
12
+ import { applyFeatures } from './features'
13
+ import { vuetifyNuxtManual } from './features/vuetify-nuxt-manual'
14
+ import { prompt } from './prompts'
15
+ import { convertProjectToJS } from './utils/convertProjectToJS'
16
+ import { installDependencies } from './utils/installDependencies'
17
+
18
+ const VUETIFY_0_APP_DEFAULT_NAME = 'vuetify0-app'
19
+
20
+ export const main = defineCommand({
21
+ meta: {
22
+ name: 'create-vuetify',
23
+ version,
24
+ description: i18n.t('cli.create.description'),
25
+ },
26
+ args: {
27
+ ...projectArgs(),
28
+ cwd: {
29
+ type: 'string',
30
+ description: 'The current working directory',
31
+ },
32
+ features: {
33
+ type: 'string', // This might need to be array? citty args are usually string/boolean
34
+ // If user passes --features router,pinia
35
+ description: 'The features to install (router, pinia, eslint)',
36
+ },
37
+ typescript: {
38
+ type: 'boolean',
39
+ description: 'Use TypeScript',
40
+ },
41
+ packageManager: {
42
+ type: 'string',
43
+ description: 'The package manager to use (npm, pnpm, yarn, bun)',
44
+ },
45
+ debug: {
46
+ type: 'boolean',
47
+ description: 'Show debug logs',
48
+ default: false,
49
+ },
50
+ v0: {
51
+ type: 'boolean',
52
+ description: i18n.t('cli.create_v0.description'),
53
+ },
54
+ },
55
+ run: async ({ args }) => {
56
+ const cwd = args.cwd || process.cwd()
57
+ const debug = (...msg: any[]) => args.debug && console.log('DEBUG:', ...msg)
58
+ debug('run args=', JSON.stringify(args, null, 2))
59
+ debug('VUETIFY_CLI_TEMPLATES_PATH=', process.env.VUETIFY_CLI_TEMPLATES_PATH)
60
+
61
+ console.log(createBanner())
62
+
63
+ if (args.v0) {
64
+ await initVuetify0({
65
+ ...args,
66
+ defaultName: VUETIFY_0_APP_DEFAULT_NAME,
67
+ cwd,
68
+ })
69
+ return
70
+ }
71
+
72
+ intro(i18n.t('messages.create.intro', { version }))
73
+
74
+ const features = typeof args.features === 'string'
75
+ ? args.features.split(',').filter(Boolean)
76
+ : args.features
77
+
78
+ const rawArgs = args as Record<string, any>
79
+ const packageManager = rawArgs.packageManager || rawArgs['package-manager']
80
+
81
+ const context = await prompt({ ...args, features, packageManager }, cwd)
82
+ debug('context=', JSON.stringify(context, null, 2))
83
+ const projectRoot = join(cwd, context.name)
84
+ debug('projectRoot=', projectRoot)
85
+
86
+ if (context.force && existsSync(projectRoot)) {
87
+ rmSync(projectRoot, { recursive: true, force: true })
88
+ }
89
+
90
+ const templateName = context.type === 'vue'
91
+ ? `vue/base`
92
+ : `nuxt/base`
93
+
94
+ const s = spinner()
95
+ s.start(i18n.t('spinners.template.downloading', { template: templateName }))
96
+
97
+ if (process.env.VUETIFY_CLI_TEMPLATES_PATH) {
98
+ const templatePath = join(process.env.VUETIFY_CLI_TEMPLATES_PATH, templateName)
99
+ debug(`Copying template from ${templatePath}...`)
100
+ if (existsSync(templatePath)) {
101
+ debug(`templatePath exists. Copying to ${projectRoot}`)
102
+ fs.cpSync(templatePath, projectRoot, {
103
+ recursive: true,
104
+ filter: src => {
105
+ return !src.includes('node_modules') && !src.includes('.git') && !src.includes('.DS_Store')
106
+ },
107
+ })
108
+ debug(`Copy complete.`)
109
+ try {
110
+ const files = fs.readdirSync(projectRoot)
111
+ debug('files in projectRoot:', files)
112
+ } catch (error) {
113
+ debug('Failed to list files in projectRoot:', error)
114
+ }
115
+ } else {
116
+ debug(`templatePath does not exist: ${templatePath}`)
117
+ }
118
+ s.stop(i18n.t('spinners.template.copied'))
119
+ } else {
120
+ const templateSource = `gh:vuetifyjs/cli/templates/${templateName}`
121
+
122
+ try {
123
+ await downloadTemplate(templateSource, {
124
+ dir: projectRoot,
125
+ force: context.force,
126
+ })
127
+ s.stop(i18n.t('spinners.template.downloaded'))
128
+ } catch (error) {
129
+ s.stop(i18n.t('spinners.template.failed'))
130
+ console.error(`Failed to download template: ${error}`)
131
+ throw error
132
+ }
133
+ }
134
+
135
+ let pkg
136
+ pkg = await readPackageJSON(join(projectRoot, 'package.json'))
137
+
138
+ s.start(i18n.t('spinners.config.applying'))
139
+ if (context.features && context.features.length > 0) {
140
+ await applyFeatures(projectRoot, context.features, pkg, !!context.typescript, context.clientHints)
141
+ }
142
+
143
+ if (context.type === 'nuxt' && (!context.features || !context.features.includes('vuetify-nuxt-module'))) {
144
+ await vuetifyNuxtManual.apply({ cwd: projectRoot, pkg, isTypescript: !!context.typescript })
145
+ }
146
+ s.stop(i18n.t('spinners.config.applied'))
147
+
148
+ // Update package.json name
149
+ const pkgPath = join(projectRoot, 'package.json')
150
+ if (existsSync(pkgPath)) {
151
+ if (!pkg) {
152
+ pkg = await readPackageJSON(pkgPath)
153
+ }
154
+ pkg.name = context.name
155
+ await writePackageJSON(pkgPath, pkg)
156
+ }
157
+
158
+ if (context.type === 'vue' && !context.typescript) {
159
+ s.start(i18n.t('spinners.convert.js'))
160
+ await convertProjectToJS(projectRoot)
161
+ s.stop(i18n.t('spinners.convert.done'))
162
+ }
163
+
164
+ if (context.install && context.packageManager) {
165
+ s.start(i18n.t('spinners.dependencies.installing_with', { pm: context.packageManager }))
166
+ await installDependencies(projectRoot, context.packageManager as any)
167
+ s.stop(i18n.t('spinners.dependencies.installed'))
168
+ }
169
+
170
+ outro(i18n.t('messages.create.generated', { name: context.name, path: projectRoot }))
171
+ },
172
+ subCommands: {
173
+ upgrade,
174
+ },
175
+ })
176
+
177
+ runMain(main)
package/src/prompts.ts ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readdirSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+ import { cancel, confirm, group, multiselect, select, text } from '@clack/prompts'
6
+ import { i18n } from '@vuetify/cli-shared/i18n'
7
+ import { dim } from 'kolorist'
8
+ import { getUserAgent } from 'package-manager-detector'
9
+ import validate from 'validate-npm-package-name'
10
+
11
+ export interface ProjectOptions {
12
+ name: string
13
+ type: 'vue' | 'nuxt'
14
+ features: string[]
15
+ typescript?: boolean
16
+ packageManager?: string
17
+ install?: boolean
18
+ force?: boolean
19
+ clientHints?: boolean
20
+ }
21
+
22
+ export async function prompt (args: Partial<ProjectOptions>, cwd = process.cwd()): Promise<ProjectOptions> {
23
+ const options = await group({
24
+ name: () => {
25
+ if (args.name) {
26
+ return Promise.resolve(args.name)
27
+ }
28
+ return text({
29
+ message: i18n.t('prompts.project.name'),
30
+ initialValue: 'vuetify-project',
31
+ validate: value => {
32
+ const { validForNewPackages, errors, warnings } = validate(value ? value.trim() : '')
33
+ if (!validForNewPackages) {
34
+ return i18n.t('prompts.project.invalid', { error: (errors || warnings)?.[0] })
35
+ }
36
+ },
37
+ })
38
+ },
39
+ force: ({ results }) => {
40
+ const name = (results.name as string) || args.name
41
+ const projectRoot = join(cwd, name!)
42
+
43
+ if (existsSync(projectRoot) && readdirSync(projectRoot).length > 0) {
44
+ if (args.force) {
45
+ return Promise.resolve(true)
46
+ }
47
+
48
+ return confirm({
49
+ message: i18n.t('prompts.project.overwrite', { path: projectRoot }),
50
+ initialValue: false,
51
+ })
52
+ }
53
+ return Promise.resolve(args.force || false)
54
+ },
55
+ type: () => {
56
+ if (args.type) {
57
+ return Promise.resolve(args.type)
58
+ }
59
+ return select({
60
+ message: i18n.t('prompts.framework.select'),
61
+ initialValue: 'vue',
62
+ options: [
63
+ { label: i18n.t('prompts.framework.vue'), value: 'vue' },
64
+ { label: i18n.t('prompts.framework.nuxt'), value: 'nuxt' },
65
+ ],
66
+ })
67
+ },
68
+ typescript: ({ results }) => {
69
+ const type = (results.type as string) || args.type
70
+
71
+ if (type === 'vue' && args.typescript === undefined) {
72
+ return confirm({
73
+ message: i18n.t('prompts.typescript.use'),
74
+ initialValue: true,
75
+ })
76
+ }
77
+ return Promise.resolve(args.typescript ?? true)
78
+ },
79
+ router: ({ results }) => {
80
+ if (args.features) {
81
+ if (args.features.includes('router') && args.features.includes('file-router')) {
82
+ console.error(i18n.t('prompts.router.conflict'))
83
+ process.exit(1)
84
+ }
85
+ if (args.features.includes('router')) {
86
+ return Promise.resolve('router')
87
+ }
88
+ if (args.features.includes('file-router')) {
89
+ return Promise.resolve('file-router')
90
+ }
91
+ return Promise.resolve('none')
92
+ }
93
+
94
+ const type = (results.type as string) || args.type
95
+ if (type !== 'vue') {
96
+ return Promise.resolve('none')
97
+ }
98
+
99
+ return select({
100
+ message: i18n.t('prompts.router.select'),
101
+ initialValue: 'router',
102
+ options: [
103
+ { label: i18n.t('prompts.router.none'), value: 'none' },
104
+ { label: i18n.t('prompts.router.standard.label'), value: 'router', hint: i18n.t('prompts.router.standard.hint') },
105
+ { label: i18n.t('prompts.router.file.label'), value: 'file-router', hint: i18n.t('prompts.router.file.hint') },
106
+ ],
107
+ })
108
+ },
109
+ features: ({ results }) => {
110
+ if (args.features) {
111
+ return Promise.resolve(args.features.filter(f => f !== 'router' && f !== 'file-router'))
112
+ }
113
+ const type = (results.type as string) || args.type
114
+
115
+ return type === 'vue'
116
+ ? multiselect({
117
+ message: i18n.t('prompts.features.select', { hint: dim('↑/↓ to navigate, space to select, a to toggle all, enter to confirm') }),
118
+ options: [
119
+ { label: i18n.t('prompts.features.pinia.label'), value: 'pinia', hint: i18n.t('prompts.features.pinia.hint') },
120
+ { label: i18n.t('prompts.features.eslint.label'), value: 'eslint', hint: i18n.t('prompts.features.eslint.hint') },
121
+ ],
122
+ initialValues: ['eslint'],
123
+ required: false,
124
+ })
125
+ : multiselect({
126
+ message: i18n.t('prompts.features.select', { hint: dim('↑/↓ to navigate, space to select, a to toggle all, enter to confirm') }),
127
+ options: [
128
+ { label: i18n.t('prompts.features.pinia.label'), value: 'pinia', hint: i18n.t('prompts.features.pinia.hint') },
129
+ { label: i18n.t('prompts.features.eslint.label'), value: 'eslint', hint: i18n.t('prompts.features.eslint.hint') },
130
+ { label: i18n.t('prompts.features.vuetify_nuxt_module.label'), value: 'vuetify-nuxt-module', hint: i18n.t('prompts.features.vuetify_nuxt_module.hint') },
131
+ { label: i18n.t('prompts.features.i18n.label'), value: 'i18n', hint: i18n.t('prompts.features.i18n.hint') },
132
+ ],
133
+ initialValues: ['eslint', 'vuetify-nuxt-module'],
134
+ required: false,
135
+ })
136
+ },
137
+ clientHints: ({ results }) => {
138
+ if (args.clientHints !== undefined) {
139
+ return Promise.resolve(args.clientHints)
140
+ }
141
+ const type = (results.type as string) || args.type
142
+ const features = (results.features as string[]) || args.features || []
143
+
144
+ if (type === 'nuxt' && features.includes('vuetify-nuxt-module')) {
145
+ return confirm({
146
+ message: i18n.t('prompts.client_hints.enable'),
147
+ initialValue: false,
148
+ })
149
+ }
150
+ return Promise.resolve(false)
151
+ },
152
+ packageManager: () => {
153
+ if (args.packageManager) {
154
+ return Promise.resolve(args.packageManager)
155
+ }
156
+ if (args.install === false) {
157
+ return Promise.resolve('none')
158
+ }
159
+ return select({
160
+ message: i18n.t('prompts.package_manager.select'),
161
+ initialValue: getUserAgent() ?? 'npm',
162
+ options: [
163
+ { label: 'npm', value: 'npm' },
164
+ { label: 'pnpm', value: 'pnpm' },
165
+ { label: 'yarn', value: 'yarn' },
166
+ { label: 'deno', value: 'deno' },
167
+ { label: 'bun', value: 'bun' },
168
+ { label: 'none', value: 'none' },
169
+ ],
170
+ })
171
+ },
172
+ install: ({ results }) => {
173
+ if (args.install !== undefined) {
174
+ return Promise.resolve(args.install)
175
+ }
176
+ const pm = (results.packageManager as string) || args.packageManager
177
+ if (pm === 'none') {
178
+ return Promise.resolve(false)
179
+ }
180
+ return confirm({
181
+ message: i18n.t('prompts.install'),
182
+ initialValue: true,
183
+ })
184
+ },
185
+ }, {
186
+ onCancel: () => {
187
+ cancel(i18n.t('prompts.cancel'))
188
+ process.exit(0)
189
+ },
190
+ })
191
+
192
+ const features = [
193
+ ...(options.features as string[]),
194
+ options.router,
195
+ ].filter(f => f && f !== 'none')
196
+
197
+ return {
198
+ ...options,
199
+ features,
200
+ } as ProjectOptions
201
+ }
@@ -0,0 +1 @@
1
+ export { default as pnpm } from './pnpm'
@@ -0,0 +1,20 @@
1
+ import { x } from 'tinyexec'
2
+
3
+ export async function pnpmIgnored (root: string) {
4
+ const pnpmVersion = (await x(`pnpm`, ['-v'], { nodeOptions: { cwd: root } })).stdout.trim()
5
+ const [major] = pnpmVersion.split('.').map(Number)
6
+ if (major && major >= 10) {
7
+ const detect = (await x('pnpm', ['ignored-builds'], { nodeOptions: { cwd: root } })).stdout
8
+ if (detect.startsWith('Automatically ignored builds during installation:\n None')) {
9
+ return
10
+ }
11
+ return detect
12
+ }
13
+ }
14
+
15
+ export default async function pnpm (root: string) {
16
+ const detect = await pnpmIgnored(root)
17
+ if (detect) {
18
+ console.warn(detect)
19
+ }
20
+ }
@@ -0,0 +1 @@
1
+ export { default as yarn } from './yarn'
@@ -0,0 +1,22 @@
1
+ import { appendFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import { x } from 'tinyexec'
4
+
5
+ const templateToAppend = `
6
+ packageExtensions:
7
+ unplugin-vue-router@*:
8
+ dependencies:
9
+ "@vue/compiler-sfc": "*"
10
+ `
11
+
12
+ export async function yarnFile (root: string) {
13
+ const pnpmVersion = (await (x('yarn', ['-v'], { nodeOptions: { cwd: root } }))).stdout.trim()
14
+ const [major] = pnpmVersion.split('.').map(Number)
15
+ if (major && major >= 2) {
16
+ appendFileSync(resolve(root, '.yarnrc.yml'), templateToAppend)
17
+ }
18
+ }
19
+
20
+ export default async function yarn (root: string) {
21
+ await yarnFile(root)
22
+ }
@@ -0,0 +1,98 @@
1
+ import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { readPackageJSON, writePackageJSON } from 'pkg-types'
4
+
5
+ export async function convertProjectToJS (projectRoot: string) {
6
+ // 1. Remove TS specific config files
7
+ const filesToRemove = [
8
+ 'tsconfig.json',
9
+ 'tsconfig.app.json',
10
+ 'tsconfig.node.json',
11
+ 'env.d.ts',
12
+ ]
13
+ for (const file of filesToRemove) {
14
+ const path = join(projectRoot, file)
15
+ if (existsSync(path)) {
16
+ rmSync(path)
17
+ }
18
+ }
19
+
20
+ // 2. Update package.json
21
+ const pkgPath = join(projectRoot, 'package.json')
22
+ if (existsSync(pkgPath)) {
23
+ const pkg = await readPackageJSON(pkgPath)
24
+
25
+ // Remove devDependencies
26
+ const devDepsToRemove = [
27
+ '@tsconfig/node22',
28
+ '@types/node',
29
+ '@vue/tsconfig',
30
+ 'typescript',
31
+ 'vue-tsc',
32
+ ]
33
+ if (pkg.devDependencies) {
34
+ for (const dep of devDepsToRemove) {
35
+ delete pkg.devDependencies[dep]
36
+ }
37
+ // Remove @types/*
38
+ for (const dep of Object.keys(pkg.devDependencies)) {
39
+ if (dep.startsWith('@types/')) {
40
+ delete pkg.devDependencies[dep]
41
+ }
42
+ }
43
+ }
44
+
45
+ // Update scripts
46
+ if (pkg.scripts) {
47
+ delete pkg.scripts['type-check']
48
+ delete pkg.scripts['build-only']
49
+ if (pkg.scripts.build && pkg.scripts.build.includes('type-check')) {
50
+ pkg.scripts.build = 'vite build'
51
+ }
52
+ }
53
+
54
+ await writePackageJSON(pkgPath, pkg)
55
+ }
56
+
57
+ // 3. Rename and transform files
58
+ function walk (dir: string) {
59
+ const files = readdirSync(dir)
60
+ for (const file of files) {
61
+ const path = join(dir, file)
62
+ const stat = statSync(path)
63
+ if (stat.isDirectory()) {
64
+ walk(path)
65
+ } else {
66
+ handleFile(path)
67
+ }
68
+ }
69
+ }
70
+
71
+ function handleFile (filePath: string) {
72
+ if (filePath.endsWith('.vue')) {
73
+ let content = readFileSync(filePath, 'utf8')
74
+ // Remove lang="ts"
75
+ content = content.replace(/\s?lang="ts"/g, '')
76
+ writeFileSync(filePath, content)
77
+ } else if (filePath.endsWith('.ts') || filePath.endsWith('.mts')) {
78
+ let content = readFileSync(filePath, 'utf8')
79
+
80
+ // Special handling for plugins/index.ts
81
+ if (filePath.endsWith('plugins/index.ts')) {
82
+ content = content.replace(/import type { App } from 'vue'.*\n/, '')
83
+ content = content.replace(/app: App/, 'app')
84
+ }
85
+
86
+ // Rename file
87
+ const newPath = filePath.replace(/\.m?ts$/, match => match === '.mts' ? '.mjs' : '.js')
88
+ writeFileSync(newPath, content)
89
+ rmSync(filePath)
90
+ } else if (filePath.endsWith('index.html')) {
91
+ let content = readFileSync(filePath, 'utf8')
92
+ content = content.replace('src/main.ts', 'src/main.js')
93
+ writeFileSync(filePath, content)
94
+ }
95
+ }
96
+
97
+ walk(projectRoot)
98
+ }
@@ -0,0 +1,25 @@
1
+ import { installDependencies as installDependencies$1 } from 'nypm'
2
+ import { getUserAgent } from 'package-manager-detector'
3
+ import { pnpm } from './cli/postinstall'
4
+ import { yarn } from './cli/preinstall'
5
+
6
+ export const packageManager = getUserAgent() ?? 'npm'
7
+
8
+ export async function installDependencies (root: string = process.cwd(), manager = packageManager) {
9
+ if (manager === 'yarn') {
10
+ await yarn(root)
11
+ }
12
+ await installDependencies$1({
13
+ packageManager: manager,
14
+ cwd: root,
15
+ silent: true,
16
+ })
17
+ .catch(() => {
18
+ console.error(
19
+ `Failed to install dependencies using ${manager}.`,
20
+ )
21
+ })
22
+ if (manager === 'pnpm') {
23
+ await pnpm(root)
24
+ }
25
+ }
@@ -0,0 +1,25 @@
1
+ import { cpSync, existsSync, mkdtempSync, rmSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { downloadTemplate } from 'giget'
5
+
6
+ export async function installFeature (feature: string, cwd: string) {
7
+ const templateName = `vue/${feature}`
8
+
9
+ if (process.env.VUETIFY_CLI_TEMPLATES_PATH) {
10
+ const templatePath = join(process.env.VUETIFY_CLI_TEMPLATES_PATH, templateName)
11
+ if (existsSync(templatePath)) {
12
+ cpSync(templatePath, cwd, { recursive: true })
13
+ }
14
+ } else {
15
+ const tmp = mkdtempSync(join(tmpdir(), 'vuetify-feature-'))
16
+ try {
17
+ await downloadTemplate(`gh:vuetifyjs/cli/templates/${templateName}`, {
18
+ dir: tmp,
19
+ })
20
+ cpSync(tmp, cwd, { recursive: true })
21
+ } finally {
22
+ rmSync(tmp, { recursive: true, force: true })
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,75 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { x } from 'tinyexec'
4
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
5
+
6
+ const CLI_PATH = path.resolve(__dirname, '../dist/index.mjs')
7
+ const TEMPLATES_PATH = path.resolve(__dirname, '../../../templates')
8
+ const TEMP_DIR = path.resolve(__dirname, '../.test-tmp-conflict')
9
+
10
+ describe('create-vuetify conflict', () => {
11
+ beforeAll(() => {
12
+ if (!fs.existsSync(TEMP_DIR)) {
13
+ fs.mkdirSync(TEMP_DIR)
14
+ }
15
+ })
16
+
17
+ afterAll(() => {
18
+ fs.rmSync(TEMP_DIR, { recursive: true, force: true })
19
+ })
20
+
21
+ const runCli = async (args: string[], cwd: string) => {
22
+ const proc = x('node', [CLI_PATH, ...args], {
23
+ nodeOptions: {
24
+ cwd,
25
+ env: {
26
+ ...process.env,
27
+ VUETIFY_CLI_TEMPLATES_PATH: TEMPLATES_PATH,
28
+ },
29
+ },
30
+ throwOnError: false,
31
+ })
32
+
33
+ for await (const line of proc) {
34
+ console.log(line)
35
+ }
36
+
37
+ const result = await proc
38
+ if (result.exitCode !== 0) {
39
+ console.error('Command failed with exit code:', result.exitCode)
40
+ console.error('STDERR:', result.stderr)
41
+ throw new Error(`Process exited with non-zero status (${result.exitCode})`)
42
+ }
43
+ return result
44
+ }
45
+
46
+ it('should error when both router and file-router selected via flags', async () => {
47
+ const projectName = 'test-conflict'
48
+ const projectPath = path.join(TEMP_DIR, projectName)
49
+
50
+ if (fs.existsSync(projectPath)) {
51
+ fs.rmSync(projectPath, { recursive: true, force: true })
52
+ }
53
+
54
+ // Select both router and file-router
55
+ const args = [
56
+ `--name=${projectName}`,
57
+ '--type=vue',
58
+ '--typescript',
59
+ '--features=router,file-router',
60
+ '--package-manager=pnpm',
61
+ '--force',
62
+ '--no-install',
63
+ '--no-interactive',
64
+ ]
65
+
66
+ console.log(`Running: create-vuetify ${args.join(' ')}`)
67
+
68
+ try {
69
+ await runCli(args, TEMP_DIR)
70
+ expect.fail('Should have failed')
71
+ } catch (error: any) {
72
+ expect(error.message).toContain('Process exited with non-zero status (1)')
73
+ }
74
+ })
75
+ })