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 +83 -203
- package/package.json +1 -1
- package/template-next/.vscode/settings.json +8 -6
- package/template-next/lib/utils.ts +2 -2
- package/template-next/next-env.d.ts +1 -1
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 {
|
|
8
|
-
import {
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
+
await cleanupTemplate(ctx)
|
|
127
104
|
|
|
128
|
-
|
|
129
|
-
if (fs.existsSync(oldGit)) await fs.move(oldGit, path.join(targetDir, '.gitignore'))
|
|
105
|
+
await applyProjectTransform(ctx)
|
|
130
106
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
117
|
+
p.note(pc.cyan(nextSteps), '下一步操作')
|
|
118
|
+
p.outro(pc.magenta('✨ 已经为你准备好了极致的开发环境!'))
|
|
119
|
+
} catch (err) {
|
|
120
|
+
s.stop(pc.red('手术失败'))
|
|
222
121
|
|
|
223
|
-
if (
|
|
224
|
-
|
|
122
|
+
if (ctx && ctx.targetDir && fs.existsSync(ctx.targetDir)) {
|
|
123
|
+
p.log.warn(pc.yellow(`正在清理残留文件: ${ctx.targetDir}...`))
|
|
225
124
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
@@ -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
|
-
"
|
|
54
|
-
"
|
|
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
|
-
|
|
72
|
-
"
|
|
70
|
+
|
|
71
|
+
"editor.codeActionsOnSave": {
|
|
72
|
+
"source.format.oxc": "always"
|
|
73
|
+
},
|
|
74
|
+
"editor.inlayHints.enabled": "on"
|
|
73
75
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import
|
|
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.
|