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/README.md +37 -0
- package/dist/index.mjs +29252 -0
- package/dist/multipart-parser-CbtVsPSq.mjs +175 -0
- package/dist/node-CcjDTqtN.mjs +4007 -0
- package/dist/prompt-CAXcFxNm.mjs +848 -0
- package/package.json +40 -0
- package/src/commands/upgrade.ts +5 -0
- package/src/features/dependencies/package.json +17 -0
- package/src/features/eslint.ts +75 -0
- package/src/features/i18n.ts +108 -0
- package/src/features/index.ts +31 -0
- package/src/features/pinia.ts +84 -0
- package/src/features/router.ts +147 -0
- package/src/features/types.ts +13 -0
- package/src/features/vuetify-nuxt-manual.ts +47 -0
- package/src/features/vuetify-nuxt-module.ts +58 -0
- package/src/index.ts +177 -0
- package/src/prompts.ts +201 -0
- package/src/utils/cli/postinstall/index.ts +1 -0
- package/src/utils/cli/postinstall/pnpm.ts +20 -0
- package/src/utils/cli/preinstall/index.ts +1 -0
- package/src/utils/cli/preinstall/yarn.ts +22 -0
- package/src/utils/convertProjectToJS.ts +98 -0
- package/src/utils/installDependencies.ts +25 -0
- package/src/utils/installFeature.ts +25 -0
- package/test/conflict.test.ts +75 -0
- package/test/index.test.ts +117 -0
- package/tsdown.config.ts +7 -0
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
|
+
})
|