create-vite-vue 1.6.1 → 1.8.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/bin/index.js +44 -426
- package/lib/features.js +13 -0
- package/lib/mainFile.js +48 -0
- package/lib/package.js +45 -0
- package/lib/plugins/autoRoute.js +79 -0
- package/lib/plugins/https.js +38 -0
- package/lib/plugins/index.js +23 -0
- package/lib/plugins/tailwind.js +14 -0
- package/lib/prompts.js +80 -0
- package/lib/template.js +29 -0
- package/lib/utils.js +38 -0
- package/package.json +1 -1
- package/template/axios-ts/src/utils/requestCache.ts +7 -0
- package/template/base-js/jsconfig.json +1 -0
- package/template/base-ts/tsconfig.json +1 -0
package/bin/index.js
CHANGED
|
@@ -1,454 +1,72 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process'
|
|
1
|
+
#!/usr/bin/env node
|
|
3
2
|
import fs from 'fs'
|
|
4
3
|
import path from 'path'
|
|
5
|
-
import prompts from 'prompts'
|
|
6
4
|
import { fileURLToPath } from 'url'
|
|
7
5
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const n1 = a[i] || 0
|
|
17
|
-
const n2 = b[i] || 0
|
|
18
|
-
if(n1 > n2) return 1
|
|
19
|
-
if(n1 < n2) return -1
|
|
20
|
-
}
|
|
21
|
-
return 0
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const currentVersion = process.version.replace('v', '')
|
|
25
|
-
|
|
26
|
-
if(compareVersion(currentVersion, requiredVersion) < 0) {
|
|
27
|
-
console.error(`❌ Node.js 版本过低`)
|
|
28
|
-
console.error(`当前版本: ${currentVersion}`)
|
|
29
|
-
console.error(`最低要求: ${requiredVersion}`)
|
|
30
|
-
console.error(`请升级 Node.js 后再运行`)
|
|
31
|
-
process.exit(1)
|
|
32
|
-
}
|
|
6
|
+
// === 导入模块 ===
|
|
7
|
+
import { parseExtraPlugins, parseFeatures } from '../lib/features.js'
|
|
8
|
+
import { generateMainFile } from '../lib/mainFile.js'
|
|
9
|
+
import { generatePackageJson } from '../lib/package.js'
|
|
10
|
+
import { setupPlugins } from '../lib/plugins/index.js'
|
|
11
|
+
import { askAutoRoute, askRunDev, chooseFeatures, chooseLanguage, getProjectName } from '../lib/prompts.js'
|
|
12
|
+
import { copyBaseTemplate, copyOptionalTemplates, updateIndexHtml } from '../lib/template.js'
|
|
13
|
+
import { checkNodeVersion, detectPackageManager, runCmd } from '../lib/utils.js'
|
|
33
14
|
|
|
15
|
+
// ===================== 常量 =====================
|
|
16
|
+
const requiredVersion = '22.19.0'
|
|
34
17
|
const __filename = fileURLToPath(import.meta.url)
|
|
35
18
|
const __dirname = path.dirname(__filename)
|
|
36
|
-
|
|
37
19
|
const pkgManager = detectPackageManager()
|
|
38
20
|
|
|
39
21
|
const pkgCommands = {
|
|
40
|
-
npm: {
|
|
41
|
-
|
|
42
|
-
dev: 'npm run dev'
|
|
43
|
-
},
|
|
44
|
-
pnpm: {
|
|
45
|
-
install: 'pnpm install',
|
|
46
|
-
dev: 'pnpm dev'
|
|
47
|
-
},
|
|
48
|
-
bun: {
|
|
49
|
-
install: 'bun install',
|
|
50
|
-
dev: 'bun run dev'
|
|
51
|
-
}
|
|
22
|
+
npm: { install: 'npm install', dev: 'npm run dev' },
|
|
23
|
+
pnpm: { install: 'pnpm install', dev: 'pnpm dev' }
|
|
52
24
|
}
|
|
53
25
|
|
|
26
|
+
// ===================== 主流程 =====================
|
|
54
27
|
; (async () => {
|
|
55
|
-
|
|
56
|
-
let projectName
|
|
57
|
-
while(true) {
|
|
58
|
-
const res = await prompts({
|
|
59
|
-
type: 'text',
|
|
60
|
-
name: 'projectName',
|
|
61
|
-
message: '📦 项目名称',
|
|
62
|
-
validate: v => v ? true : '项目名不能为空'
|
|
63
|
-
})
|
|
64
|
-
projectName = res.projectName
|
|
65
|
-
if(!projectName) process.exit(1)
|
|
66
|
-
|
|
67
|
-
const targetDir = path.resolve(process.cwd(), projectName)
|
|
68
|
-
if(fs.existsSync(targetDir)) {
|
|
69
|
-
console.log('❌ 目录已存在,请重新输入')
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
break
|
|
73
|
-
}
|
|
28
|
+
checkNodeVersion(requiredVersion)
|
|
74
29
|
|
|
30
|
+
const projectName = await getProjectName(fs, path)
|
|
75
31
|
const targetDir = path.resolve(process.cwd(), projectName)
|
|
76
32
|
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
message: '请选择项目语言',
|
|
82
|
-
choices: [
|
|
83
|
-
{ title: 'JavaScript', value: 'js' },
|
|
84
|
-
{ title: 'TypeScript', value: 'ts' }
|
|
85
|
-
]
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// 2️⃣ 功能选择(多选)
|
|
89
|
-
const { featureList } = await prompts({
|
|
90
|
-
type: 'multiselect',
|
|
91
|
-
name: 'featureList',
|
|
92
|
-
message: '请选择基础功能(↑↓选择,空格确认,回车完成)',
|
|
93
|
-
instructions: false,
|
|
94
|
-
choices: [
|
|
95
|
-
{ title: 'Vue Router', value: 'router' },
|
|
96
|
-
{ title: 'Pinia(含持久化)', value: 'pinia' },
|
|
97
|
-
{ title: 'Axios', value: 'axios' },
|
|
98
|
-
{ title: 'Element Plus(PC UI)', value: 'element' },
|
|
99
|
-
{ title: 'Vant(Mobile UI)', value: 'vant' },
|
|
100
|
-
{ title: 'VueUse(实用 Composition API)', value: 'vueuse' },
|
|
101
|
-
{ title: 'Lodash(工具库)', value: 'lodash' },
|
|
102
|
-
{ title: 'Day.js(日期处理)', value: 'dayjs' },
|
|
103
|
-
{ title: 'Tailwind CSS(原子化 CSS)', value: 'tailwind' },
|
|
104
|
-
{ title: 'mitt(事件总线)', value: 'mitt' },
|
|
105
|
-
{ title: 'HTTPS(mkcert)', value: 'https' }
|
|
106
|
-
]
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// 转换成原来的结构(保证后面代码基本不用动)
|
|
110
|
-
const features = {
|
|
111
|
-
router: featureList?.includes('router') || false,
|
|
112
|
-
pinia: featureList?.includes('pinia') || false,
|
|
113
|
-
axios: featureList?.includes('axios') || false,
|
|
114
|
-
ui: featureList?.filter(v => ['element', 'vant'].includes(v)) || []
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const extraPlugins = featureList?.filter(v =>
|
|
118
|
-
['vueuse', 'lodash', 'dayjs', 'tailwind', 'mitt', 'https'].includes(v)
|
|
119
|
-
) || []
|
|
120
|
-
|
|
121
|
-
// 询问是否开启自动路由
|
|
122
|
-
let autoRoute = false
|
|
123
|
-
if(features.router) {
|
|
124
|
-
const { enableAutoRoute } = await prompts({
|
|
125
|
-
type: 'toggle',
|
|
126
|
-
name: 'enableAutoRoute',
|
|
127
|
-
message: '是否开启自动配置路由(vite-plugin-pages)?',
|
|
128
|
-
initial: false,
|
|
129
|
-
active: '是',
|
|
130
|
-
inactive: '否'
|
|
131
|
-
})
|
|
132
|
-
autoRoute = enableAutoRoute
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const enableHttps = featureList?.includes('https') || false
|
|
136
|
-
|
|
137
|
-
// 3️⃣ 是否立即运行 dev
|
|
138
|
-
const { runDev } = await prompts({
|
|
139
|
-
type: 'select',
|
|
140
|
-
name: 'runDev',
|
|
141
|
-
message: `是否立即运行 ${pkgCommands[pkgManager].dev}?`,
|
|
142
|
-
choices: [{ title: 'Yes', value: true }, { title: 'No', value: false }]
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
// 4️⃣ 拷贝 base 模板
|
|
146
|
-
const baseTemplate = language === 'ts' ? 'base-ts' : 'base-js'
|
|
147
|
-
fs.cpSync(
|
|
148
|
-
path.resolve(__dirname, `../template/${baseTemplate}`),
|
|
149
|
-
targetDir,
|
|
150
|
-
{ recursive: true }
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
// 替换 index.html 的 title
|
|
154
|
-
const indexPath = path.join(targetDir, 'index.html')
|
|
155
|
-
if(fs.existsSync(indexPath)) {
|
|
156
|
-
const indexContent = fs.readFileSync(indexPath, 'utf-8')
|
|
157
|
-
fs.writeFileSync(
|
|
158
|
-
indexPath,
|
|
159
|
-
indexContent.replace(/<title>.*<\/title>/, `<title>${projectName}</title>`)
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// 追加 Tailwind CSS 导入
|
|
164
|
-
if(extraPlugins.includes('tailwind')) {
|
|
165
|
-
const stylePath = path.join(targetDir, 'src/style.css')
|
|
166
|
-
const original = fs.readFileSync(stylePath, 'utf-8')
|
|
167
|
-
if(!original.startsWith('@import "tailwindcss";')) {
|
|
168
|
-
fs.writeFileSync(stylePath, `@import "tailwindcss";\n${original}`)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 5️⃣ 拷贝可选模板(基础功能)
|
|
173
|
-
const copy = name => {
|
|
174
|
-
fs.cpSync(path.resolve(__dirname, `../template/${name}`), targetDir, { recursive: true })
|
|
175
|
-
}
|
|
176
|
-
features.router && copy(language === 'ts' ? 'router-ts' : 'router-js')
|
|
177
|
-
features.pinia && copy(language === 'ts' ? 'pinia-ts' : 'pinia-js')
|
|
178
|
-
features.axios && copy(language === 'ts' ? 'axios-ts' : 'axios-js')
|
|
179
|
-
|
|
180
|
-
// 拷贝增强插件模板
|
|
181
|
-
for(const plugin of extraPlugins) {
|
|
182
|
-
const templateName = `${plugin}-${language === 'ts' ? 'ts' : 'js'}`
|
|
183
|
-
const templatePath = path.resolve(__dirname, `../template/${templateName}`)
|
|
184
|
-
if(fs.existsSync(templatePath)) {
|
|
185
|
-
fs.cpSync(templatePath, targetDir, { recursive: true })
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// 6️⃣ 生成 main.js / main.ts
|
|
190
|
-
const mainFile = language === 'ts' ? 'main.ts' : 'main.js'
|
|
191
|
-
const mainTplPath = path.join(targetDir, `src/${mainFile}.tpl`)
|
|
192
|
-
let main = fs.readFileSync(mainTplPath, 'utf-8')
|
|
33
|
+
const language = await chooseLanguage()
|
|
34
|
+
const featureList = await chooseFeatures()
|
|
35
|
+
const features = parseFeatures(featureList)
|
|
36
|
+
const extraPlugins = parseExtraPlugins(featureList)
|
|
193
37
|
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
? "import { createPinia } from 'pinia'\nimport persistedstate from 'pinia-plugin-persistedstate'"
|
|
198
|
-
: '',
|
|
199
|
-
'/* __ELEMENT_IMPORT__ */': features.ui.includes('element')
|
|
200
|
-
? `import ElementPlus from 'element-plus'
|
|
201
|
-
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|
202
|
-
import 'element-plus/dist/index.css'
|
|
203
|
-
import * as ElementPlusIconsVue from '@element-plus/icons-vue'`
|
|
204
|
-
: '',
|
|
205
|
-
'/* __VANT_IMPORT__ */': features.ui.includes('vant')
|
|
206
|
-
? `import Vant from 'vant'
|
|
207
|
-
import 'vant/lib/index.css'`
|
|
208
|
-
: '',
|
|
209
|
-
'/* __ROUTER_USE__ */': features.router ? 'app.use(router)' : '',
|
|
210
|
-
'/* __PINIA_USE__ */': features.pinia
|
|
211
|
-
? 'app.use(createPinia().use(persistedstate))'
|
|
212
|
-
: '',
|
|
213
|
-
'/* __ELEMENT_USE__ */': features.ui.includes('element')
|
|
214
|
-
? `app.use(ElementPlus, { locale: zhCn })
|
|
215
|
-
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|
216
|
-
app.component(key, component)
|
|
217
|
-
}`
|
|
218
|
-
: '',
|
|
219
|
-
'/* __VANT_USE__ */': features.ui.includes('vant')
|
|
220
|
-
? 'app.use(Vant)'
|
|
221
|
-
: ''
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function escapeRegExp (str) {
|
|
225
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
for(const [placeholder, content] of Object.entries(replacements)) {
|
|
229
|
-
if(content) {
|
|
230
|
-
main = main.replace(placeholder, content)
|
|
231
|
-
} else {
|
|
232
|
-
const re = new RegExp(`^\\s*${escapeRegExp(placeholder)}\\s*$\\n?`, 'gm')
|
|
233
|
-
main = main.replace(re, '')
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
main = main.replace(/(\s*)const app = createApp\(App\)/, '\n\n$1const app = createApp(App)')
|
|
238
|
-
main = main.replace(/\n{3,}/g, '\n\n')
|
|
239
|
-
|
|
240
|
-
fs.writeFileSync(path.join(targetDir, `src/${mainFile}`), main)
|
|
241
|
-
fs.unlinkSync(mainTplPath)
|
|
242
|
-
|
|
243
|
-
// 7️⃣ 生成 package.json
|
|
244
|
-
const pkgTpl = path.join(targetDir, 'package.json.tpl')
|
|
245
|
-
if(fs.existsSync(pkgTpl)) {
|
|
246
|
-
let pkg = fs.readFileSync(pkgTpl, 'utf-8')
|
|
247
|
-
|
|
248
|
-
const optionalDeps = {}
|
|
249
|
-
if(features.router) optionalDeps['vue-router'] = '^5.0.3'
|
|
250
|
-
if(features.pinia) {
|
|
251
|
-
optionalDeps['pinia'] = '^3.0.4'
|
|
252
|
-
optionalDeps['pinia-plugin-persistedstate'] = '^4.7.1'
|
|
253
|
-
}
|
|
254
|
-
if(features.axios) optionalDeps['axios'] = '^1.13.6'
|
|
255
|
-
if(features.ui.includes('element')) {
|
|
256
|
-
optionalDeps['element-plus'] = '^2.13.5'
|
|
257
|
-
optionalDeps['@element-plus/icons-vue'] = '^2.3.2'
|
|
258
|
-
}
|
|
259
|
-
if(features.ui.includes('vant')) {
|
|
260
|
-
optionalDeps['vant'] = '^4.9.22'
|
|
261
|
-
}
|
|
262
|
-
// 增强插件依赖
|
|
263
|
-
if(extraPlugins.includes('vueuse')) optionalDeps['@vueuse/core'] = '^14.2.1'
|
|
264
|
-
if(extraPlugins.includes('dayjs')) optionalDeps['dayjs'] = '^1.11.20'
|
|
265
|
-
if(extraPlugins.includes('lodash')) optionalDeps['lodash'] = '^4.17.23'
|
|
266
|
-
if(extraPlugins.includes('tailwind')) {
|
|
267
|
-
optionalDeps['tailwindcss'] = '^4.2.2'
|
|
268
|
-
optionalDeps['@tailwindcss/postcss'] = '^4.2.2'
|
|
269
|
-
optionalDeps['postcss'] = '^8.5.8'
|
|
270
|
-
}
|
|
271
|
-
if(extraPlugins.includes('mitt')) optionalDeps['mitt'] = '^3.0.1'
|
|
272
|
-
if(enableHttps) optionalDeps['vite-plugin-mkcert'] = '^1.17.10'
|
|
273
|
-
if(autoRoute) optionalDeps['vite-plugin-pages'] = '^0.33.3'
|
|
38
|
+
const autoRoute = await askAutoRoute(features.router)
|
|
39
|
+
const enableHttps = featureList.includes('https') || false
|
|
40
|
+
const runDev = await askRunDev(pkgCommands[pkgManager].dev)
|
|
274
41
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
42
|
+
// 模板文件处理
|
|
43
|
+
copyBaseTemplate(language, targetDir, __dirname)
|
|
44
|
+
updateIndexHtml(projectName, targetDir)
|
|
45
|
+
copyOptionalTemplates(features, extraPlugins, language, targetDir, __dirname)
|
|
280
46
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// ⭐ 新增逻辑
|
|
286
|
-
let pkgObj = JSON.parse(pkg)
|
|
287
|
-
|
|
288
|
-
if(pkgManager === 'pnpm' && features.ui.includes('vant')) {
|
|
289
|
-
pkgObj.pnpm = {
|
|
290
|
-
overrides: {
|
|
291
|
-
"@vant/use": "^1.0.0",
|
|
292
|
-
"@vant/popperjs": "^1.0.0"
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
pkg = JSON.stringify(pkgObj, null, 2)
|
|
298
|
-
|
|
299
|
-
fs.writeFileSync(path.join(targetDir, 'package.json'), pkg)
|
|
300
|
-
fs.unlinkSync(pkgTpl)
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// 8️⃣ 配置 vite.config.js / vite.config.ts(自动路由 + HTTPS)
|
|
304
|
-
const viteConfigPath = path.join(
|
|
47
|
+
// 配置插件
|
|
48
|
+
setupPlugins(features, extraPlugins, {
|
|
49
|
+
language,
|
|
305
50
|
targetDir,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if(fs.existsSync(viteConfigPath)) {
|
|
310
|
-
let viteConfig = fs.readFileSync(viteConfigPath, 'utf-8')
|
|
311
|
-
|
|
312
|
-
// ===== mkcert 插件 =====
|
|
313
|
-
if(enableHttps) {
|
|
314
|
-
if(!viteConfig.includes("vite-plugin-mkcert")) {
|
|
315
|
-
viteConfig = viteConfig.replace(
|
|
316
|
-
/(import .*?from .*?\n)/,
|
|
317
|
-
`$1import mkcert from 'vite-plugin-mkcert'\n`
|
|
318
|
-
)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if(!viteConfig.includes("mkcert(")) {
|
|
322
|
-
viteConfig = viteConfig.replace(
|
|
323
|
-
/plugins:\s*\[/,
|
|
324
|
-
`plugins: [
|
|
325
|
-
mkcert(),`
|
|
326
|
-
)
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ===== 自动路由 =====
|
|
331
|
-
if(autoRoute) {
|
|
332
|
-
if(!viteConfig.includes("import fs from 'fs'")) {
|
|
333
|
-
viteConfig = `import fs from 'fs'\n${viteConfig}`
|
|
334
|
-
}
|
|
335
|
-
if(!viteConfig.includes("import Pages from 'vite-plugin-pages'")) {
|
|
336
|
-
viteConfig = viteConfig.replace(
|
|
337
|
-
/(import .*?from .*?\n)/,
|
|
338
|
-
`$1import Pages from 'vite-plugin-pages'\n`
|
|
339
|
-
)
|
|
340
|
-
}
|
|
341
|
-
if(!viteConfig.includes("Pages({")) {
|
|
342
|
-
viteConfig = viteConfig.replace(
|
|
343
|
-
/plugins:\s*\[/,
|
|
344
|
-
`plugins: [
|
|
345
|
-
Pages({
|
|
346
|
-
dirs: 'src/views',
|
|
347
|
-
extensions: ['vue'],
|
|
348
|
-
exclude: ['**/_*.vue'],
|
|
349
|
-
async extendRoute(route) {
|
|
350
|
-
const componentPath = path.resolve(process.cwd(), route.component.slice(1))
|
|
351
|
-
const dirPath = path.dirname(componentPath)
|
|
352
|
-
const metaFile = path.resolve(dirPath, 'meta.json')
|
|
353
|
-
if(fs.existsSync(metaFile)) {
|
|
354
|
-
try {
|
|
355
|
-
const metaContent = fs.readFileSync(metaFile, 'utf-8')
|
|
356
|
-
const meta = JSON.parse(metaContent)
|
|
357
|
-
route.meta = { ...(route.meta || {}), ...meta }
|
|
358
|
-
} catch(err) {
|
|
359
|
-
console.warn(\`加载 \${metaFile} 失败:\`, err)
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
return { ...route }
|
|
363
|
-
}
|
|
364
|
-
}),`
|
|
365
|
-
)
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
fs.writeFileSync(viteConfigPath, viteConfig)
|
|
370
|
-
}
|
|
371
|
-
// 9️⃣ 替换 router/index.js
|
|
372
|
-
if(features.router) {
|
|
373
|
-
const routerIndexPath = path.join(targetDir, `src/router/index.${language === 'ts' ? 'ts' : 'js'}`)
|
|
374
|
-
if(autoRoute) {
|
|
375
|
-
fs.writeFileSync(
|
|
376
|
-
routerIndexPath,
|
|
377
|
-
`import { createRouter, createWebHistory } from 'vue-router'
|
|
378
|
-
import routes from '~pages'
|
|
379
|
-
|
|
380
|
-
routes.unshift({
|
|
381
|
-
path: '/',
|
|
382
|
-
redirect: '/home'
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
export default createRouter({
|
|
386
|
-
history: createWebHistory(),
|
|
387
|
-
routes
|
|
388
|
-
})
|
|
389
|
-
`
|
|
390
|
-
)
|
|
391
|
-
} else {
|
|
392
|
-
fs.writeFileSync(
|
|
393
|
-
routerIndexPath,
|
|
394
|
-
`import { createRouter, createWebHistory } from 'vue-router'
|
|
395
|
-
|
|
396
|
-
const routes = [
|
|
397
|
-
{
|
|
398
|
-
path: '/',
|
|
399
|
-
component: () => import('@/views/home/index.vue')
|
|
400
|
-
}
|
|
401
|
-
]
|
|
402
|
-
|
|
403
|
-
export default createRouter({
|
|
404
|
-
history: createWebHistory(),
|
|
405
|
-
routes
|
|
406
|
-
})
|
|
407
|
-
`
|
|
408
|
-
)
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// 1️⃣0️⃣ 安装依赖
|
|
413
|
-
console.log('📦 安装依赖中...')
|
|
51
|
+
autoRoute,
|
|
52
|
+
enableHttps
|
|
53
|
+
})
|
|
414
54
|
|
|
415
|
-
|
|
55
|
+
// 生成 main 文件
|
|
56
|
+
await generateMainFile(features, extraPlugins, language, targetDir)
|
|
416
57
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
} else {
|
|
420
|
-
installCmd = 'npm install'
|
|
421
|
-
}
|
|
58
|
+
// package.json
|
|
59
|
+
generatePackageJson(projectName, features, extraPlugins, autoRoute, enableHttps, language, targetDir, pkgManager)
|
|
422
60
|
|
|
423
|
-
|
|
61
|
+
// 安装依赖
|
|
62
|
+
runCmd(pkgCommands[pkgManager].install, targetDir)
|
|
424
63
|
|
|
425
|
-
//
|
|
426
|
-
if(runDev)
|
|
427
|
-
|
|
428
|
-
if(enableHttps) {
|
|
429
|
-
console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...')
|
|
430
|
-
}
|
|
431
|
-
execSync(pkgCommands[pkgManager].dev, {
|
|
432
|
-
cwd: targetDir,
|
|
433
|
-
stdio: 'inherit'
|
|
434
|
-
})
|
|
435
|
-
} else {
|
|
64
|
+
// 启动 dev 或提示完成
|
|
65
|
+
if(runDev) runCmd(pkgCommands[pkgManager].dev, targetDir)
|
|
66
|
+
else {
|
|
436
67
|
console.log(`\n✅ 项目创建完成`)
|
|
437
68
|
console.log(`👉 cd ${projectName}`)
|
|
438
69
|
console.log(`👉 ${pkgCommands[pkgManager].dev}`)
|
|
439
|
-
if(enableHttps)
|
|
440
|
-
console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...\n')
|
|
441
|
-
}
|
|
70
|
+
if(enableHttps) console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...')
|
|
442
71
|
}
|
|
443
|
-
})()
|
|
444
|
-
|
|
445
|
-
function detectPackageManager () {
|
|
446
|
-
const userAgent = process.env.npm_config_user_agent || ''
|
|
447
|
-
|
|
448
|
-
if(userAgent.startsWith('pnpm')) return 'pnpm'
|
|
449
|
-
if(userAgent.startsWith('npm')) return 'npm'
|
|
450
|
-
|
|
451
|
-
if(fs.existsSync('pnpm-lock.yaml')) return 'pnpm'
|
|
452
|
-
|
|
453
|
-
return 'npm'
|
|
454
|
-
}
|
|
72
|
+
})()
|
package/lib/features.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// lib/features.js
|
|
2
|
+
export function parseFeatures (featureList) {
|
|
3
|
+
return {
|
|
4
|
+
router: featureList.includes('router'),
|
|
5
|
+
pinia: featureList.includes('pinia'),
|
|
6
|
+
axios: featureList.includes('axios'),
|
|
7
|
+
ui: featureList.filter(v => ['element', 'vant'].includes(v))
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parseExtraPlugins (featureList) {
|
|
12
|
+
return featureList.filter(v => ['vueuse', 'lodash', 'dayjs', 'tailwind', 'mitt', 'https'].includes(v))
|
|
13
|
+
}
|
package/lib/mainFile.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// lib/mainFile.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export async function generateMainFile (features, extraPlugins, language, targetDir) {
|
|
6
|
+
const mainFile = language === 'ts' ? 'main.ts' : 'main.js'
|
|
7
|
+
const mainTplPath = path.join(targetDir, `src/${mainFile}.tpl`)
|
|
8
|
+
if(!fs.existsSync(mainTplPath)) return
|
|
9
|
+
let main = fs.readFileSync(mainTplPath, 'utf-8')
|
|
10
|
+
|
|
11
|
+
const replacements = {
|
|
12
|
+
'/* __ROUTER_IMPORT__ */': features.router ? "import router from './router'" : '',
|
|
13
|
+
'/* __PINIA_IMPORT__ */': features.pinia ? "import { createPinia } from 'pinia'\nimport persistedstate from 'pinia-plugin-persistedstate'" : '',
|
|
14
|
+
'/* __ELEMENT_IMPORT__ */': features.ui.includes('element')
|
|
15
|
+
? `import ElementPlus from 'element-plus'
|
|
16
|
+
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|
17
|
+
import 'element-plus/dist/index.css'
|
|
18
|
+
import * as ElementPlusIconsVue from '@element-plus/icons-vue'`
|
|
19
|
+
: '',
|
|
20
|
+
'/* __VANT_IMPORT__ */': features.ui.includes('vant')
|
|
21
|
+
? `import Vant from 'vant'
|
|
22
|
+
import 'vant/lib/index.css'`
|
|
23
|
+
: '',
|
|
24
|
+
'/* __ROUTER_USE__ */': features.router ? 'app.use(router)' : '',
|
|
25
|
+
'/* __PINIA_USE__ */': features.pinia ? 'app.use(createPinia().use(persistedstate))' : '',
|
|
26
|
+
'/* __ELEMENT_USE__ */': features.ui.includes('element')
|
|
27
|
+
? `app.use(ElementPlus, { locale: zhCn })
|
|
28
|
+
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|
29
|
+
app.component(key, component)
|
|
30
|
+
}` : '',
|
|
31
|
+
'/* __VANT_USE__ */': features.ui.includes('vant') ? 'app.use(Vant)' : ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function escapeRegExp (str) {
|
|
35
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for(const [placeholder, content] of Object.entries(replacements)) {
|
|
39
|
+
if(content) main = main.replace(placeholder, content)
|
|
40
|
+
else main = main.replace(new RegExp(`^\\s*${escapeRegExp(placeholder)}\\s*$\\n?`, 'gm'), '')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
main = main.replace(/(\s*)const app = createApp\(App\)/, '\n\n$1const app = createApp(App)')
|
|
44
|
+
main = main.replace(/\n{3,}/g, '\n\n')
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(path.join(targetDir, `src/${mainFile}`), main)
|
|
47
|
+
fs.unlinkSync(mainTplPath)
|
|
48
|
+
}
|
package/lib/package.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// lib/package.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function generatePackageJson (projectName, features, extraPlugins, autoRoute, enableHttps, language, targetDir, pkgManager) {
|
|
6
|
+
const pkgTpl = path.join(targetDir, 'package.json.tpl')
|
|
7
|
+
if(!fs.existsSync(pkgTpl)) return
|
|
8
|
+
let pkg = fs.readFileSync(pkgTpl, 'utf-8')
|
|
9
|
+
|
|
10
|
+
const optionalDeps = {}
|
|
11
|
+
if(features.router) optionalDeps['vue-router'] = '^5.0.3'
|
|
12
|
+
if(features.pinia) {
|
|
13
|
+
optionalDeps['pinia'] = '^3.0.4'
|
|
14
|
+
optionalDeps['pinia-plugin-persistedstate'] = '^4.7.1'
|
|
15
|
+
}
|
|
16
|
+
if(features.axios) optionalDeps['axios'] = '^1.13.6'
|
|
17
|
+
if(features.ui.includes('element')) {
|
|
18
|
+
optionalDeps['element-plus'] = '^2.13.5'
|
|
19
|
+
optionalDeps['@element-plus/icons-vue'] = '^2.3.2'
|
|
20
|
+
}
|
|
21
|
+
if(features.ui.includes('vant')) optionalDeps['vant'] = '^4.9.22'
|
|
22
|
+
if(extraPlugins.includes('vueuse')) optionalDeps['@vueuse/core'] = '^14.2.1'
|
|
23
|
+
if(extraPlugins.includes('dayjs')) optionalDeps['dayjs'] = '^1.11.20'
|
|
24
|
+
if(extraPlugins.includes('lodash')) optionalDeps['lodash'] = '^4.17.23'
|
|
25
|
+
if(extraPlugins.includes('tailwind')) {
|
|
26
|
+
optionalDeps['tailwindcss'] = '^4.2.2'
|
|
27
|
+
optionalDeps['@tailwindcss/postcss'] = '^4.2.2'
|
|
28
|
+
optionalDeps['postcss'] = '^8.5.8'
|
|
29
|
+
}
|
|
30
|
+
if(extraPlugins.includes('mitt')) optionalDeps['mitt'] = '^3.0.1'
|
|
31
|
+
if(enableHttps) optionalDeps['vite-plugin-mkcert'] = '^1.17.10'
|
|
32
|
+
if(autoRoute) optionalDeps['vite-plugin-pages'] = '^0.33.3'
|
|
33
|
+
|
|
34
|
+
const keys = Object.keys(optionalDeps)
|
|
35
|
+
const depsStr = keys.length ? ',\n' + keys.map(k => ` "${k}": "${optionalDeps[k]}"`).join(',\n') : ''
|
|
36
|
+
pkg = pkg.replace('__PROJECT_NAME__', projectName).replace('__OPTIONAL_DEP__', depsStr)
|
|
37
|
+
|
|
38
|
+
const pkgObj = JSON.parse(pkg)
|
|
39
|
+
if(pkgManager === 'pnpm' && features.ui.includes('vant')) {
|
|
40
|
+
pkgObj.pnpm = { overrides: { "@vant/use": "^1.0.0", "@vant/popperjs": "^1.0.0" } }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fs.writeFileSync(path.join(targetDir, 'package.json'), JSON.stringify(pkgObj, null, 2))
|
|
44
|
+
fs.unlinkSync(pkgTpl)
|
|
45
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// lib/plugins/autoRoute.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function setupAutoRoute (language, targetDir) {
|
|
6
|
+
setupRouter(language, targetDir)
|
|
7
|
+
setupVite(language, targetDir)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// ================= router =================
|
|
11
|
+
function setupRouter (language, targetDir) {
|
|
12
|
+
const routerIndexPath = path.join(
|
|
13
|
+
targetDir,
|
|
14
|
+
`src/router/index.${language === 'ts' ? 'ts' : 'js'}`
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const content = `import { createRouter, createWebHistory } from 'vue-router'
|
|
18
|
+
import routes from '~pages'
|
|
19
|
+
|
|
20
|
+
routes.unshift({ path: '/', redirect: '/home' })
|
|
21
|
+
|
|
22
|
+
export default createRouter({ history: createWebHistory(), routes })`
|
|
23
|
+
|
|
24
|
+
fs.writeFileSync(routerIndexPath, content)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ================= vite =================
|
|
28
|
+
function setupVite (language, targetDir) {
|
|
29
|
+
const viteConfigPath = path.join(
|
|
30
|
+
targetDir,
|
|
31
|
+
`vite.config.${language === 'ts' ? 'ts' : 'js'}`
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if(!fs.existsSync(viteConfigPath)) return
|
|
35
|
+
|
|
36
|
+
let viteConfig = fs.readFileSync(viteConfigPath, 'utf-8')
|
|
37
|
+
|
|
38
|
+
// import fs
|
|
39
|
+
if(!viteConfig.includes("import fs from 'fs'")) {
|
|
40
|
+
viteConfig = `import fs from 'fs'\n${viteConfig}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// import Pages
|
|
44
|
+
if(!viteConfig.includes("vite-plugin-pages")) {
|
|
45
|
+
viteConfig = viteConfig.replace(
|
|
46
|
+
/(import .*?from .*?\n)/,
|
|
47
|
+
`$1import Pages from 'vite-plugin-pages'\n`
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 插件注入
|
|
52
|
+
if(!viteConfig.includes("Pages({")) {
|
|
53
|
+
viteConfig = viteConfig.replace(
|
|
54
|
+
/plugins:\s*\[/,
|
|
55
|
+
`plugins: [\n Pages({
|
|
56
|
+
dirs: 'src/views',
|
|
57
|
+
extensions: ['vue'],
|
|
58
|
+
exclude: ['**/_*.vue'],
|
|
59
|
+
async extendRoute(route) {
|
|
60
|
+
const componentPath = path.resolve(process.cwd(), route.component.slice(1))
|
|
61
|
+
const dirPath = path.dirname(componentPath)
|
|
62
|
+
const metaFile = path.resolve(dirPath, 'meta.json')
|
|
63
|
+
if(fs.existsSync(metaFile)) {
|
|
64
|
+
try {
|
|
65
|
+
const metaContent = fs.readFileSync(metaFile, 'utf-8')
|
|
66
|
+
const meta = JSON.parse(metaContent)
|
|
67
|
+
route.meta = { ...(route.meta || {}), ...meta }
|
|
68
|
+
} catch(err) {
|
|
69
|
+
console.warn(\`加载 \${metaFile} 失败:\`, err)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { ...route }
|
|
73
|
+
}
|
|
74
|
+
}),`
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fs.writeFileSync(viteConfigPath, viteConfig)
|
|
79
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// lib/plugins/https.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function setupHttps (targetDir) {
|
|
6
|
+
const viteConfigPathTs = path.join(targetDir, 'vite.config.ts')
|
|
7
|
+
const viteConfigPathJs = path.join(targetDir, 'vite.config.js')
|
|
8
|
+
|
|
9
|
+
const viteConfigPath = fs.existsSync(viteConfigPathTs)
|
|
10
|
+
? viteConfigPathTs
|
|
11
|
+
: viteConfigPathJs
|
|
12
|
+
|
|
13
|
+
if(!viteConfigPath || !fs.existsSync(viteConfigPath)) return
|
|
14
|
+
|
|
15
|
+
let viteConfig = fs.readFileSync(viteConfigPath, 'utf-8')
|
|
16
|
+
|
|
17
|
+
// ================== 1. 注入 import ==================
|
|
18
|
+
if(!viteConfig.includes("vite-plugin-mkcert")) {
|
|
19
|
+
viteConfig = viteConfig.replace(
|
|
20
|
+
/(import .*?from .*?\n)/,
|
|
21
|
+
`$1import mkcert from 'vite-plugin-mkcert'\n`
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ================== 2. 注入插件 ==================
|
|
26
|
+
if(!viteConfig.includes('mkcert()')) {
|
|
27
|
+
viteConfig = viteConfig.replace(
|
|
28
|
+
/plugins:\s*\[/,
|
|
29
|
+
`plugins: [\n mkcert(),`
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(viteConfigPath, viteConfig)
|
|
34
|
+
|
|
35
|
+
// ================== 3. 提示 ==================
|
|
36
|
+
console.log('🔐 已启用 HTTPS(mkcert)')
|
|
37
|
+
console.log('👉 首次运行会自动生成本地证书,请稍等...')
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// lib/plugins/index.js
|
|
2
|
+
import { setupAutoRoute } from './autoRoute.js'
|
|
3
|
+
import { setupHttps } from './https.js'
|
|
4
|
+
import { setupTailwind } from './tailwind.js'
|
|
5
|
+
|
|
6
|
+
export function setupPlugins (features, extraPlugins, context) {
|
|
7
|
+
const { language, targetDir, autoRoute, enableHttps } = context
|
|
8
|
+
|
|
9
|
+
// Tailwind
|
|
10
|
+
if(extraPlugins.includes('tailwind')) {
|
|
11
|
+
setupTailwind(targetDir)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// HTTPS
|
|
15
|
+
if(enableHttps) {
|
|
16
|
+
setupHttps(targetDir)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 自动路由
|
|
20
|
+
if(features.router && autoRoute) {
|
|
21
|
+
setupAutoRoute(language, targetDir)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// lib/plugins/tailwind.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function setupTailwind (targetDir) {
|
|
6
|
+
const stylePath = path.join(targetDir, 'src/style.css')
|
|
7
|
+
if(!fs.existsSync(stylePath)) return
|
|
8
|
+
|
|
9
|
+
const original = fs.readFileSync(stylePath, 'utf-8')
|
|
10
|
+
|
|
11
|
+
if(!original.includes('@import "tailwindcss";')) {
|
|
12
|
+
fs.writeFileSync(stylePath, `@import "tailwindcss";\n${original}`)
|
|
13
|
+
}
|
|
14
|
+
}
|
package/lib/prompts.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// lib/prompts.js
|
|
2
|
+
import prompts from 'prompts'
|
|
3
|
+
|
|
4
|
+
export async function getProjectName (fs, path) {
|
|
5
|
+
while(true) {
|
|
6
|
+
const res = await prompts({
|
|
7
|
+
type: 'text',
|
|
8
|
+
name: 'projectName',
|
|
9
|
+
message: '📦 项目名称',
|
|
10
|
+
validate: v => v ? true : '项目名不能为空'
|
|
11
|
+
})
|
|
12
|
+
const name = res.projectName
|
|
13
|
+
if(!name) process.exit(1)
|
|
14
|
+
const targetDir = path.resolve(process.cwd(), name)
|
|
15
|
+
if(fs.existsSync(targetDir)) {
|
|
16
|
+
console.log('❌ 目录已存在,请重新输入')
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
return name
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function chooseLanguage () {
|
|
24
|
+
const { language } = await prompts({
|
|
25
|
+
type: 'select',
|
|
26
|
+
name: 'language',
|
|
27
|
+
message: '请选择项目语言',
|
|
28
|
+
choices: [
|
|
29
|
+
{ title: 'JavaScript', value: 'js' },
|
|
30
|
+
{ title: 'TypeScript', value: 'ts' }
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
return language
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function chooseFeatures () {
|
|
37
|
+
const { featureList } = await prompts({
|
|
38
|
+
type: 'multiselect',
|
|
39
|
+
name: 'featureList',
|
|
40
|
+
message: '请选择基础功能(↑↓选择,空格确认,回车完成)',
|
|
41
|
+
instructions: false,
|
|
42
|
+
choices: [
|
|
43
|
+
{ title: 'Vue Router', value: 'router' },
|
|
44
|
+
{ title: 'Pinia(含持久化)', value: 'pinia' },
|
|
45
|
+
{ title: 'Axios', value: 'axios' },
|
|
46
|
+
{ title: 'Element Plus(PC UI)', value: 'element' },
|
|
47
|
+
{ title: 'Vant(Mobile UI)', value: 'vant' },
|
|
48
|
+
{ title: 'VueUse(实用 Composition API)', value: 'vueuse' },
|
|
49
|
+
{ title: 'Lodash(工具库)', value: 'lodash' },
|
|
50
|
+
{ title: 'Day.js(日期处理)', value: 'dayjs' },
|
|
51
|
+
{ title: 'Tailwind CSS(原子化 CSS)', value: 'tailwind' },
|
|
52
|
+
{ title: 'mitt(事件总线)', value: 'mitt' },
|
|
53
|
+
{ title: 'HTTPS(mkcert)', value: 'https' }
|
|
54
|
+
]
|
|
55
|
+
})
|
|
56
|
+
return featureList || []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function askAutoRoute (routerEnabled) {
|
|
60
|
+
if(!routerEnabled) return false
|
|
61
|
+
const { enableAutoRoute } = await prompts({
|
|
62
|
+
type: 'toggle',
|
|
63
|
+
name: 'enableAutoRoute',
|
|
64
|
+
message: '是否开启自动配置路由(vite-plugin-pages)?',
|
|
65
|
+
initial: false,
|
|
66
|
+
active: '是',
|
|
67
|
+
inactive: '否'
|
|
68
|
+
})
|
|
69
|
+
return enableAutoRoute
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function askRunDev (devCommand) {
|
|
73
|
+
const { runDev } = await prompts({
|
|
74
|
+
type: 'select',
|
|
75
|
+
name: 'runDev',
|
|
76
|
+
message: `是否立即运行 ${devCommand}?`,
|
|
77
|
+
choices: [{ title: 'Yes', value: true }, { title: 'No', value: false }]
|
|
78
|
+
})
|
|
79
|
+
return runDev
|
|
80
|
+
}
|
package/lib/template.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// lib/template.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function copyBaseTemplate (language, targetDir, __dirname) {
|
|
6
|
+
const baseTemplate = language === 'ts' ? 'base-ts' : 'base-js'
|
|
7
|
+
fs.cpSync(path.resolve(__dirname, `../template/${baseTemplate}`), targetDir, { recursive: true })
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function updateIndexHtml (projectName, targetDir) {
|
|
11
|
+
const indexPath = path.join(targetDir, 'index.html')
|
|
12
|
+
if(!fs.existsSync(indexPath)) return
|
|
13
|
+
const indexContent = fs.readFileSync(indexPath, 'utf-8')
|
|
14
|
+
fs.writeFileSync(indexPath, indexContent.replace(/<title>.*<\/title>/, `<title>${projectName}</title>`))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function copyOptionalTemplates (features, extraPlugins, language, targetDir, __dirname) {
|
|
18
|
+
const copy = name => {
|
|
19
|
+
fs.cpSync(path.resolve(__dirname, `../template/${name}`), targetDir, { recursive: true })
|
|
20
|
+
}
|
|
21
|
+
features.router && copy(language === 'ts' ? 'router-ts' : 'router-js')
|
|
22
|
+
features.pinia && copy(language === 'ts' ? 'pinia-ts' : 'pinia-js')
|
|
23
|
+
features.axios && copy(language === 'ts' ? 'axios-ts' : 'axios-js')
|
|
24
|
+
for(const plugin of extraPlugins) {
|
|
25
|
+
const templateName = `${plugin}-${language === 'ts' ? 'ts' : 'js'}`
|
|
26
|
+
const templatePath = path.resolve(__dirname, `../template/${templateName}`)
|
|
27
|
+
if(fs.existsSync(templatePath)) copy(templateName)
|
|
28
|
+
}
|
|
29
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// lib/utils.js
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
|
|
5
|
+
export function checkNodeVersion (requiredVersion) {
|
|
6
|
+
const currentVersion = process.version.replace('v', '')
|
|
7
|
+
if(compareVersion(currentVersion, requiredVersion) < 0) {
|
|
8
|
+
console.error(`❌ Node.js 版本过低`)
|
|
9
|
+
console.error(`当前版本: ${currentVersion}`)
|
|
10
|
+
console.error(`最低要求: ${requiredVersion}`)
|
|
11
|
+
console.error(`请升级 Node.js 后再运行`)
|
|
12
|
+
process.exit(1)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function compareVersion (v1, v2) {
|
|
17
|
+
const a = v1.split('.').map(Number)
|
|
18
|
+
const b = v2.split('.').map(Number)
|
|
19
|
+
for(let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
20
|
+
const n1 = a[i] || 0
|
|
21
|
+
const n2 = b[i] || 0
|
|
22
|
+
if(n1 > n2) return 1
|
|
23
|
+
if(n1 < n2) return -1
|
|
24
|
+
}
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function detectPackageManager () {
|
|
29
|
+
const userAgent = process.env.npm_config_user_agent || ''
|
|
30
|
+
if(userAgent.startsWith('pnpm')) return 'pnpm'
|
|
31
|
+
if(userAgent.startsWith('npm')) return 'npm'
|
|
32
|
+
if(fs.existsSync('pnpm-lock.yaml')) return 'pnpm'
|
|
33
|
+
return 'npm'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function runCmd (cmd, cwd = process.cwd()) {
|
|
37
|
+
execSync(cmd, { cwd, stdio: 'inherit' })
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { InternalAxiosRequestConfig } from "axios"
|
|
2
2
|
|
|
3
|
+
// 扩展 InternalAxiosRequestConfig 类型,添加自定义属性 __requestKey
|
|
4
|
+
declare module "axios" {
|
|
5
|
+
interface InternalAxiosRequestConfig {
|
|
6
|
+
__requestKey?: string
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
const pendingRequests = new Map()
|
|
4
11
|
|
|
5
12
|
/**
|