create-lve 0.2.10 → 0.3.0

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/index.js CHANGED
@@ -4,44 +4,8 @@ import * as p from '@clack/prompts'
4
4
  import pc from 'picocolors'
5
5
  import fs from 'fs-extra'
6
6
  import path from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
- import { execSync, spawn } from 'node:child_process'
9
-
10
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
-
12
- const getReactAppTemplate = (isUno) => {
13
- const logoClass = isUno
14
- ? 'animate-spin animate-duration-20s animate-linear animate-infinite'
15
- : 'animate-[spin_20s_linear_infinite]'
16
-
17
- return `
18
- import { useState } from 'react'
19
- import reactLogo from './assets/react.svg'
20
-
21
- export function App() {
22
- const [count, setCount] = useState(0)
23
-
24
- return (
25
- <div className="max-w-7xl mx-auto p-8 text-center font-sans antialiased text-[#213547] dark:text-zinc-200 min-h-dvh flex flex-col justify-center items-center">
26
- <div className="flex justify-center gap-12 mb-12">
27
- <a href="https://viteplus.dev" target="_blank" rel="noreferrer" className="transition-all duration-300 hover:drop-shadow-[0_0_2em_#646cffaa]">
28
- <img src="/favicon.svg" className="h-24 p-6" alt="VitePlus logo" />
29
- </a>
30
- <a href="https://react.dev" target="_blank" rel="noreferrer" className="transition-all duration-300 hover:drop-shadow-[0_0_2em_#61dafbaa]">
31
- <img src={reactLogo} className="h-24 p-6 ${logoClass}" alt="React logo" />
32
- </a>
33
- </div>
34
- <h1 className="text-5xl font-bold leading-[1.1] mb-8">VitePlus + React</h1>
35
- <div className="p-8 space-y-4 flex flex-col items-center">
36
- <button onClick={() => setCount((count) => count + 1)} className="rounded-lg border border-transparent px-5 py-2.5 text-base font-medium bg-[#f9f9f9] dark:bg-zinc-800 cursor-pointer transition-colors hover:border-[#646cff] outline-none">
37
- count is {count}
38
- </button>
39
- </div>
40
- </div>
41
- )
42
- }
43
- `.trim()
44
- }
7
+ import { execSync } from 'node:child_process'
8
+ import { __dirname, applyProjectTransform, cleanupTemplate, installDependencies } from './config.js'
45
9
 
46
10
  async function main() {
47
11
  console.clear()
@@ -53,62 +17,77 @@ async function main() {
53
17
  console.log(logo)
54
18
  p.intro(`${pc.bgCyan(pc.black(' LVE-CLI '))}`)
55
19
 
56
- const project = await p.group({
57
- path: () => p.text({
58
- message: '项目名称',
59
- placeholder: 'my-app',
60
- defaultValue: 'my-app',
61
- validate: (value) => {
62
- if (!value || value.length === 0) return
63
- if (value.match(/[<>:"|?*]/)) return '路径包含非法字符'
20
+ const project = await p.group(
21
+ {
22
+ path: () =>
23
+ p.text({
24
+ message: '项目名称',
25
+ placeholder: 'my-app',
26
+ defaultValue: 'my-app',
27
+ validate: (value) => {
28
+ if (!value || value.length === 0) return
29
+ if (value.match(/[<>:"|?*]/)) return '路径包含非法字符'
30
+ },
31
+ }),
32
+ shouldOverwrite: ({ results }) => {
33
+ const targetDir = path.resolve(process.cwd(), results.path)
34
+ if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
35
+ return p.confirm({ message: `目录已存在,是否清空?`, initialValue: false })
36
+ }
64
37
  },
65
- }),
66
- shouldOverwrite: ({ results }) => {
67
- const targetDir = path.resolve(process.cwd(), results.path)
68
- if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
69
- return p.confirm({ message: `目录已存在,是否清空?`, initialValue: false })
70
- }
38
+ framework: () =>
39
+ p.select({
40
+ message: '选择框架',
41
+ options: [
42
+ { value: 'next', label: 'Next.js 16', hint: 'React 19 + Tailwind v4 + Shadcn UI' },
43
+ { value: 'react', label: 'React 19', hint: 'VitePlus + Compiler' },
44
+ { value: 'vue', label: 'Vue 3', hint: 'VitePlus + Optimized' },
45
+ ],
46
+ }),
47
+ cssEngine: ({ results }) => {
48
+ if (results.framework === 'next') return
49
+ return p.select({
50
+ message: '选择 CSS',
51
+ options: [
52
+ { value: 'unocss', label: 'UnoCSS', hint: '⚡️ 战机级性能' },
53
+ { value: 'tailwind', label: 'Tailwind v4', hint: '🛡️ 装甲级稳定' },
54
+ ],
55
+ })
56
+ },
57
+ install: () => p.confirm({ message: '是否现在自动安装依赖?', initialValue: true }),
71
58
  },
72
- framework: () => p.select({
73
- message: '选择框架',
74
- options: [
75
- { value: 'next', label: 'Next.js 16', hint: 'React 19 + Tailwind v4 + Base UI' },
76
- { value: 'react', label: 'React 19', hint: 'VitePlus + Compiler' },
77
- { value: 'vue', label: 'Vue 3', hint: 'VitePlus + Optimized' },
78
- ],
79
- }),
80
- cssEngine: ({ results }) => {
81
- if (results.framework === 'next') return;
82
- return p.select({
83
- message: '选择 CSS',
84
- options: [
85
- { value: 'unocss', label: 'UnoCSS', hint: '⚡️ 战机级性能' },
86
- { value: 'tailwind', label: 'Tailwind v4', hint: '🛡️ 装甲级稳定' },
87
- ],
88
- })
59
+ {
60
+ onCancel: () => {
61
+ p.cancel('操作取消')
62
+ process.exit(0)
63
+ },
89
64
  },
90
- install: () => p.confirm({ message: '是否现在自动安装依赖?', initialValue: true }),
91
- }, {
92
- onCancel: () => { p.cancel('操作取消'); process.exit(0) }
93
- })
94
- if (!project) process.exit(0)
65
+ )
66
+
67
+ const ctx = {
68
+ name: project.path,
69
+ framework: project.framework,
70
+ css: project.cssEngine,
71
+ shouldInstall: project.install,
72
+ targetDir: path.resolve(process.cwd(), project.path),
73
+ templateDir: path.resolve(__dirname, `template-${project.framework}`),
74
+ isNext: project.framework === 'next',
75
+ isUno: project.cssEngine === 'unocss',
76
+ pkgManager: project.framework === 'next' ? 'pnpm' : 'vp',
77
+ devCmd: project.framework === 'next' ? 'pnpm dev' : 'vp dev',
78
+ fmtCmd: project.framework === 'next' ? 'pnpm fmt' : 'vp fmt',
79
+ }
95
80
 
96
- const targetDir = path.resolve(process.cwd(), project.path)
97
- const templateDir = path.resolve(__dirname, `template-${project.framework}`)
98
- const isNext = project.framework === 'next'
99
- const isUno = project.cssEngine === 'unocss'
100
81
  const s = p.spinner()
101
82
 
102
- if (!isNext) {
83
+ if (!ctx.isNext) {
103
84
  try {
104
85
  execSync('vp --version', { stdio: 'ignore' })
105
86
  } catch {
106
87
  p.log.error(pc.red('未检测到 VitePlus (vp) 环境'))
107
88
  p.note(
108
- pc.white(
109
- `React/Vue 模板依赖 vp 工具链:\n${pc.cyan('https://viteplus.dev/guide')}`
110
- ),
111
- '环境缺失'
89
+ pc.white(`React/Vue 模板依赖 vp 工具链:\n${pc.cyan('https://viteplus.dev/guide')}`),
90
+ '环境缺失',
112
91
  )
113
92
  process.exit(1)
114
93
  }
@@ -117,140 +96,41 @@ async function main() {
117
96
  s.start('🛠️ 正在按需装配架构...')
118
97
 
119
98
  try {
120
- if (project.shouldOverwrite) await fs.emptyDir(targetDir)
121
- else await fs.ensureDir(targetDir)
122
- await fs.copy(templateDir, targetDir)
123
-
124
- await fs.remove(path.join(targetDir, 'pnpm-workspace.yaml'))
99
+ if (project.shouldOverwrite) await fs.emptyDir(ctx.targetDir)
100
+ await fs.ensureDir(ctx.targetDir)
101
+ await fs.copy(ctx.templateDir, ctx.targetDir)
125
102
 
126
- try { execSync('git init', { cwd: targetDir, stdio: 'ignore' }) } catch { }
103
+ await cleanupTemplate(ctx)
127
104
 
128
- const oldGit = path.join(targetDir, '_gitignore')
129
- if (fs.existsSync(oldGit)) await fs.move(oldGit, path.join(targetDir, '.gitignore'))
105
+ await applyProjectTransform(ctx)
130
106
 
131
- const pkgPath = path.join(targetDir, 'package.json')
132
- const pkg = await fs.readJson(pkgPath)
133
- pkg.name = path.basename(targetDir)
134
-
135
- if (isUno) {
136
- pkg.devDependencies['unocss'] = 'latest'
137
- } else {
138
- pkg.devDependencies['tailwindcss'] = 'latest'
139
- if (!isNext) pkg.devDependencies['@tailwindcss/vite'] = 'latest'
140
- }
141
-
142
- if (!isNext) {
143
- pkg.pnpm = {
144
- ...pkg.pnpm,
145
- overrides: {
146
- vite: 'npm:@voidzero-dev/vite-plus-core@latest',
147
- vitest: 'npm:@voidzero-dev/vite-plus-test@latest',
148
- },
149
- }
107
+ if (ctx.shouldInstall) {
108
+ await installDependencies(ctx, s)
150
109
  }
151
- await fs.writeJson(pkgPath, pkg, { spaces: 2 })
152
-
153
- if (isNext) {
154
- s.message(pc.green('检测到 Next.js 架构,正在同步核心配置...'))
155
- } else {
156
- const mainFile = project.framework === 'vue' ? 'src/main.ts' : 'src/main.tsx'
157
- const mainPath = path.join(targetDir, mainFile)
158
- const viteConfigPath = path.join(targetDir, 'vite.config.ts')
159
- const stylePath = path.join(targetDir, 'src/style.css')
160
- const appFile = project.framework === 'vue' ? 'src/App.vue' : 'src/App.tsx'
161
- const appPath = path.join(targetDir, appFile)
162
-
163
- let mainContent = await fs.readFile(mainPath, 'utf-8')
164
- let viteContent = await fs.readFile(viteConfigPath, 'utf-8')
165
110
 
166
- if (project.framework === 'react') {
167
- await fs.writeFile(appPath, getReactAppTemplate(isUno))
168
- }
169
-
170
- const pluginCode = isUno ? 'UnoCSS()' : 'tailwindcss()'
171
- const pluginImport = isUno ? "import UnoCSS from 'unocss/vite'\n" : "import tailwindcss from '@tailwindcss/vite'\n"
172
-
173
- viteContent = pluginImport + viteContent
174
- viteContent = viteContent.replace('/* VITE_PLUS_PLUGINS */', `${pluginCode}, `)
175
-
176
- if (isUno) {
177
- const unoConfig = `import { defineConfig, presetWind3, transformerCompileClass } from 'unocss'
178
-
179
- export default defineConfig({
180
- presets: [presetWind3()],
181
- transformers: [
182
- {
183
- name: 'auto-uno-injector',
184
- enforce: 'pre',
185
- idFilter(id) { return /\\.[tj]sx$|\\.vue$/.test(id) },
186
- async transform(code) {
187
- const classRegex = /(?:class|className)=["']([^"']+)["']/g
188
- let match
189
- while ((match = classRegex.exec(code.original))) {
190
- const content = match[1]
191
- if (content.trim() && !content.includes(':uno:')) {
192
- const insertPos = match.index + match[0].indexOf(content)
193
- code.appendLeft(insertPos, ':uno: ')
194
- }
195
- }
196
- },
197
- },
198
- transformerCompileClass({
199
- classPrefix: 'kfc-',
200
- }),
201
- ],
202
- })\n`
203
-
204
- await fs.writeFile(path.join(targetDir, 'uno.config.ts'), unoConfig)
205
-
206
- mainContent = `import 'virtual:uno.css'\n` + mainContent
207
- if (fs.existsSync(stylePath)) await fs.remove(stylePath)
208
- } else {
209
- await fs.writeFile(stylePath, `@import "tailwindcss";`)
210
- mainContent = `import './style.css'\n` + mainContent
211
- }
212
- await fs.writeFile(mainPath, mainContent)
213
- await fs.writeFile(viteConfigPath, viteContent)
214
- }
111
+ s.stop(pc.green('全套环境装配就绪'))
215
112
 
216
- const toRemove = ['pnpm-lock.yaml', 'node_modules', 'dist']
217
- await Promise.all(toRemove.map((file) => fs.remove(path.join(targetDir, file))))
113
+ const nextSteps = ctx.shouldInstall
114
+ ? `cd ${ctx.name}\n${ctx.devCmd}`
115
+ : `cd ${ctx.name}\n${ctx.pkgManager} install\n${ctx.devCmd}`
218
116
 
219
- const pkgManager = isNext ? 'pnpm' : 'vp'
220
- const devCmd = isNext ? 'pnpm dev' : 'vp dev'
221
- const fmtCmd = isNext ? 'pnpm fmt' : 'vp fmt'
117
+ p.note(pc.cyan(nextSteps), '下一步操作')
118
+ p.outro(pc.magenta(' 已经为你准备好了极致的开发环境!'))
119
+ } catch (err) {
120
+ s.stop(pc.red('手术失败'))
222
121
 
223
- if (project.install) {
224
- s.message(pc.green(`正在执行 ${pkgManager} install`))
122
+ if (ctx && ctx.targetDir && fs.existsSync(ctx.targetDir)) {
123
+ p.log.warn(pc.yellow(`正在清理残留文件: ${ctx.targetDir}...`))
225
124
  try {
226
- await new Promise((resolve, reject) => {
227
- const child = spawn(pkgManager, ['install'], {
228
- cwd: targetDir,
229
- stdio: 'ignore',
230
- shell: process.platform === 'win32',
231
- })
232
- child.on('close', (code) => code === 0 ? resolve() : reject())
233
- })
234
-
235
- s.message(pc.green(`正在执行 ${fmtCmd} 优化代码结构`))
236
- await new Promise((r) => setTimeout(r, 500))
237
- try { execSync(fmtCmd, { cwd: targetDir, stdio: 'ignore' }) } catch { }
238
- } catch (err) {
239
- p.log.warn('自动安装失败,请手动执行安装')
125
+ fs.removeSync(ctx.targetDir)
126
+ } catch (cleanErr) {
127
+ p.log.error(pc.red('清理残留文件失败,请手动删除', cleanErr))
240
128
  }
241
129
  }
242
130
 
243
- s.stop(pc.green('全套环境装配就绪'))
244
-
245
- const nextSteps = project.install ? `cd ${project.path}\n${devCmd}` : `cd ${project.path}\n${pkgManager} install\n${devCmd}`
246
- p.note(pc.cyan(nextSteps), '快速开始')
247
- p.outro(pc.magenta('✨ 已经为你准备好了极致的开发环境!'))
248
-
249
- } catch (err) {
250
- s.stop(pc.red('手术失败'))
251
131
  console.error(err)
252
132
  process.exit(1)
253
133
  }
254
134
  }
255
135
 
256
- main()
136
+ main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lve",
3
- "version": "0.2.10",
3
+ "version": "0.3.0",
4
4
  "bin": {
5
5
  "create-lve": "index.js"
6
6
  },
@@ -5,7 +5,6 @@
5
5
  "editor.formatOnSaveMode": "file",
6
6
  "oxc.enable.oxfmt": true,
7
7
 
8
- // 语言特定 Formatter
9
8
  "[javascript]": {
10
9
  "editor.defaultFormatter": "oxc.oxc-vscode"
11
10
  },
@@ -37,7 +36,7 @@
37
36
  "editor.defaultFormatter": "oxc.oxc-vscode"
38
37
  },
39
38
 
40
- // ==================== Next.js 专用 ====================
39
+ // ==================== Next.js ====================
41
40
  "workbench.editor.customLabels.patterns": {
42
41
  "**/app/**/layout.{js,jsx,ts,tsx}": "${dirname}/layout",
43
42
  "**/app/**/page.{js,jsx,ts,tsx}": "${dirname}/page",
@@ -50,8 +49,8 @@
50
49
  },
51
50
 
52
51
  // ==================== TypeScript(解决 Next.js TS 问题)===================
53
- "typescript.tsdk": "node_modules/typescript/lib",
54
- "typescript.enablePromptUseWorkspaceTsdk": true,
52
+ "js/ts.tsdk.path": "node_modules/typescript/lib",
53
+ "js/ts.tsdk.promptToUseWorkspaceVersion": true,
55
54
 
56
55
  // ==================== Tailwind + shadcn/ui(强烈推荐)===================
57
56
  "tailwindCSS.experimental.classRegex": [["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]],
@@ -68,6 +67,9 @@
68
67
  "js/ts.updateImportsOnFileMove.enabled": "always",
69
68
  "diffEditor.ignoreTrimWhitespace": true,
70
69
  "diffEditor.hideUnchangedRegions.enabled": true,
71
- "editor.inlayHints.enabled": "on",
72
- "js/ts.tsdk.path": "node_modules/typescript/lib"
70
+
71
+ "editor.codeActionsOnSave": {
72
+ "source.format.oxc": "always"
73
+ },
74
+ "editor.inlayHints.enabled": "on"
73
75
  }
@@ -1,5 +1,5 @@
1
- import { clsx, type ClassValue } from "clsx"
2
- import { twMerge } from "tailwind-merge"
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
3
 
4
4
  export function cn(...inputs: ClassValue[]) {
5
5
  return twMerge(clsx(inputs))
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/types/routes.d.ts";
3
+ import './.next/types/routes.d.ts'
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.