@deppon/create-deppon-app 2.2.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.
Files changed (57) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +63 -0
  3. package/deppon.js +640 -0
  4. package/package.json +51 -0
  5. package/template/.env +12 -0
  6. package/template/.env.dev-local.example +64 -0
  7. package/template/.env.development.example +64 -0
  8. package/template/.env.example +1 -0
  9. package/template/.env.production.example +64 -0
  10. package/template/.env.test.example +64 -0
  11. package/template/.eslintignore +2 -0
  12. package/template/.eslintrc.cjs +14 -0
  13. package/template/.prettierrc.js +3 -0
  14. package/template/.vscode/settings.json +8 -0
  15. package/template/Dockerfile +5 -0
  16. package/template/README.md +149 -0
  17. package/template/commitlint.config.js +11 -0
  18. package/template/gitignore +8 -0
  19. package/template/index.html +18 -0
  20. package/template/nginx.conf +70 -0
  21. package/template/npmrc +2 -0
  22. package/template/package.json +49 -0
  23. package/template/preview-server.js +117 -0
  24. package/template/public/favicon.ico +0 -0
  25. package/template/public/logo.png +0 -0
  26. package/template/src/App.vue +123 -0
  27. package/template/src/api/index.ts +13 -0
  28. package/template/src/api/prefercenter.ts +23 -0
  29. package/template/src/api/product.ts +16 -0
  30. package/template/src/api/user.ts +41 -0
  31. package/template/src/components/ExpandableMessage.vue +340 -0
  32. package/template/src/components/PageLayout.vue +43 -0
  33. package/template/src/config/dictionaryConfig.ts +24 -0
  34. package/template/src/directives/permission.ts +162 -0
  35. package/template/src/layouts/BaseLayout.vue +687 -0
  36. package/template/src/main.ts +27 -0
  37. package/template/src/router/index.ts +179 -0
  38. package/template/src/router/route.ts +61 -0
  39. package/template/src/stores/menu.ts +334 -0
  40. package/template/src/stores/product.ts +155 -0
  41. package/template/src/stores/route.ts +79 -0
  42. package/template/src/stores/user.ts +145 -0
  43. package/template/src/styles/index.ts +29 -0
  44. package/template/src/types/dictionary.d.ts +24 -0
  45. package/template/src/types/vite-env.d.ts +119 -0
  46. package/template/src/utils/dictionary.ts +188 -0
  47. package/template/src/utils/errorAnalyzer.ts +217 -0
  48. package/template/src/utils/messageVNode.ts +15 -0
  49. package/template/src/utils/request.ts +293 -0
  50. package/template/src/views/error/401.vue +30 -0
  51. package/template/src/views/error/403.vue +30 -0
  52. package/template/src/views/error/404.vue +30 -0
  53. package/template/src/views/home/index.vue +25 -0
  54. package/template/tsconfig.json +27 -0
  55. package/template/vite.config.ts +243 -0
  56. package/template/yarnrc +3 -0
  57. package/template/yarnrc.yml +7 -0
package/LICENSE ADDED
@@ -0,0 +1 @@
1
+
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @deppon/create-deppon-app
2
+
3
+ Vue 3 项目脚手架工具
4
+
5
+ ## 安装
6
+
7
+ `@deppon/create-deppon-app` 已发布到 npm 官方仓库,可直接使用:
8
+
9
+ ```bash
10
+ # 使用 npm create
11
+ npm create @deppon/deppon-app my-app
12
+
13
+ # 或使用 npx(确保使用最新版本)
14
+ npx @deppon/create-deppon-app@latest my-app
15
+
16
+ # 或全局安装后使用
17
+ npm install -g @deppon/create-deppon-app@latest
18
+ create-deppon-app my-app
19
+ ```
20
+
21
+ **关于缓存说明:**
22
+
23
+ - `npm create @deppon/deppon-app` 会使用 npm 的缓存机制,如果之前下载过旧版本,可能会使用缓存的版本
24
+ - `npx @deppon/create-deppon-app@latest` 明确指定 `@latest` 标签,会强制获取最新版本,避免缓存问题
25
+ - 如果使用 `npm create` 时遇到版本不是最新的情况,可以清除缓存后重试:
26
+
27
+ ```bash
28
+ # 清除 npm 缓存
29
+ npm cache clean --force
30
+
31
+ # 然后重新创建项目
32
+ npm create @deppon/deppon-app my-app
33
+ ```
34
+
35
+ 创建的项目包含:
36
+
37
+ - Vue 3 + TypeScript 基础配置
38
+ - 计数器示例组件
39
+ - 待办事项示例组件
40
+ - 完整的样式示例
41
+
42
+ ![](docs/images/project-template.jpg)
43
+
44
+ ## 安装依赖
45
+
46
+ 项目创建完成后,进入项目目录并安装依赖:
47
+
48
+ ```bash
49
+ cd my-app
50
+ npm install
51
+ # 或使用 yarn
52
+ yarn install
53
+ ```
54
+
55
+ ## FAQ
56
+
57
+ #### 既然是新项目为什么不选择更成熟的 vue-cli 或 vite 这类脚手架呢? @deppon/create-deppon-app 优势在哪?
58
+
59
+ `vue-cli` 是 Vue 官方团队出品的脚手架,其经过数年的版本迭代已经很成熟了。
60
+
61
+ `@deppon/create-deppon-app` 是在 `vue-cli` 的基础上,扩展了许多更友善的配置(通过配置文件设置),并且兼容老系统的项目 `webpack 4.0` 版本,统一配置,集中管理插件,依赖等信息,在技术规范统一方面也有很大的优势。
62
+
63
+ `vite` 是通过 `esbuild` `rollup` 等工具组成的一套全新的脚手架系统,因为是直接运行 `es module` 文件,在编译速度上有着极大的优势,但是对老项目的更新也是破坏性的,而且 `vite` 整体来说,还不如 `webpack` 成熟。为了线上运行的稳定性,我们可以牺牲部分开发构建效率(0.x 秒)。
package/deppon.js ADDED
@@ -0,0 +1,640 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+ // @ts-check
5
+ import path from 'node:path';
6
+ import child_process from 'node:child_process';
7
+ import { fileURLToPath } from 'node:url';
8
+ import fs from 'node:fs';
9
+ import https from 'node:https';
10
+ import { Command } from 'commander';
11
+
12
+ // 自定义错误类,兼容 commander v9
13
+ class InvalidOptionArgumentError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = 'InvalidOptionArgumentError';
17
+ }
18
+ }
19
+
20
+ const program = new Command();
21
+ import chalk from 'chalk';
22
+ import semver from 'semver';
23
+ import inquirer from 'inquirer';
24
+
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = path.dirname(__filename);
27
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, './package.json')));
28
+
29
+ let projectName = '';
30
+
31
+ /**
32
+ * 获取包的版本号
33
+ * 优先从本地 packages 目录读取,如果不存在则从 npm registry 获取
34
+ * @param {string} packageName - 包名,如 '@deppon/deppon-pinia'
35
+ * @param {string} defaultVersion - 默认版本号(如果获取失败时使用,应包含 ^ 前缀)
36
+ * @returns {Promise<string>} 版本号(带 ^ 前缀)
37
+ */
38
+ async function getPackageVersion(packageName, defaultVersion) {
39
+ // 尝试从本地 packages 目录读取
40
+ const packagesDir = path.resolve(__dirname, '../../packages');
41
+ const packageDirName = packageName.replace('@deppon/', 'deppon-');
42
+ const localPackagePath = path.join(packagesDir, packageDirName, 'package.json');
43
+
44
+ if (fs.existsSync(localPackagePath)) {
45
+ try {
46
+ const localPkg = JSON.parse(fs.readFileSync(localPackagePath, 'utf-8'));
47
+ if (localPkg.version) {
48
+ return `^${localPkg.version}`;
49
+ }
50
+ } catch (error) {
51
+ console.warn(chalk.yellow(`警告: 无法读取本地包 ${packageName} 的版本号: ${error.message}`));
52
+ }
53
+ }
54
+
55
+ // 如果本地不存在,尝试从 npm registry 获取
56
+ try {
57
+ const version = await fetchNpmVersion(packageName);
58
+ if (version) {
59
+ return `^${version}`;
60
+ }
61
+ } catch (error) {
62
+ // 静默失败,不显示警告(网络问题很常见)
63
+ }
64
+
65
+ // 如果都失败,使用默认版本号
66
+ return defaultVersion;
67
+ }
68
+
69
+ /**
70
+ * 检测 Yarn 版本
71
+ * @returns {Promise<string|null>} Yarn 版本号或 null(如果未安装)
72
+ */
73
+ function getYarnVersion() {
74
+ return new Promise(resolve => {
75
+ try {
76
+ child_process.exec('yarn --version', { timeout: 3000 }, (error, stdout) => {
77
+ if (error) {
78
+ resolve(null);
79
+ return;
80
+ }
81
+ const version = stdout.trim();
82
+ resolve(version || null);
83
+ });
84
+ } catch (error) {
85
+ resolve(null);
86
+ }
87
+ });
88
+ }
89
+
90
+ /**
91
+ * 判断是否为 Yarn 3+
92
+ * @param {string} version - Yarn 版本号
93
+ * @returns {boolean}
94
+ */
95
+ function isYarn3(version) {
96
+ if (!version) return false;
97
+ const majorVersion = parseInt(version.split('.')[0], 10);
98
+ return majorVersion >= 3;
99
+ }
100
+
101
+ /**
102
+ * 从 npm registry 获取包的最新版本号
103
+ * @param {string} packageName - 包名
104
+ * @returns {Promise<string|null>} 版本号或 null
105
+ */
106
+ function fetchNpmVersion(packageName) {
107
+ return new Promise((resolve, reject) => {
108
+ // 判断是否为 @deppon 作用域的包,使用华为云私有仓库
109
+ const isDepponPackage = packageName.startsWith('@deppon/');
110
+ const registryUrl = isDepponPackage
111
+ ? 'https://devrepo.devcloud.cn-east-3.huaweicloud.com/artgalaxy/api/npm/cn-east-3_8a2e1f0ee52d4adb9a0a6998d78d0dda_npm_1/'
112
+ : 'https://registry.npmjs.org/';
113
+
114
+ const url = `${registryUrl}${packageName}/latest`;
115
+
116
+ // 华为云私有仓库需要认证
117
+ const options = {
118
+ timeout: 5000,
119
+ rejectUnauthorized: false, // 忽略 SSL 证书验证
120
+ };
121
+
122
+ if (isDepponPackage) {
123
+ // 添加认证头
124
+ options.headers = {
125
+ Authorization:
126
+ 'Basic Y24tZWFzdC0zXzhhMmUxZjBlZTUyZDRhZGI5YTBhNjk5OGQ3OGQwZGRhXzBkMDg4Y2Y3NTUwMDBmOTIxZmZjYzAwNDU2YTUyM2JjOncyTC1kUTY9NGU=',
127
+ };
128
+ }
129
+
130
+ https
131
+ .get(url, options, res => {
132
+ let data = '';
133
+
134
+ res.on('data', chunk => {
135
+ data += chunk;
136
+ });
137
+
138
+ res.on('end', () => {
139
+ try {
140
+ // 检查响应状态码
141
+ if (res.statusCode !== 200) {
142
+ reject(new Error(`获取版本号失败: HTTP ${res.statusCode}`));
143
+ return;
144
+ }
145
+ const json = JSON.parse(data);
146
+ resolve(json.version || null);
147
+ } catch (error) {
148
+ reject(new Error(`解析 npm registry 响应失败: ${error.message}`));
149
+ }
150
+ });
151
+ })
152
+ .on('error', error => {
153
+ reject(error);
154
+ })
155
+ .on('timeout', () => {
156
+ reject(new Error('请求超时'));
157
+ });
158
+ });
159
+ }
160
+
161
+ /**
162
+ * 根据选择的包生成相应的初始化文件
163
+ * 注意:此函数不会覆盖模板中已存在的文件(main.ts、App.vue、request.ts)
164
+ */
165
+ async function generatePackageFiles(root, packages, _isVite) {
166
+ const srcDir = path.join(root, 'src');
167
+
168
+ // 生成 Pinia 相关文件
169
+ if (packages.includes('pinia')) {
170
+ console.log('生成 Pinia 配置');
171
+ const storesDir = path.join(srcDir, 'stores');
172
+ if (!fs.existsSync(storesDir)) {
173
+ fs.mkdirSync(storesDir, { recursive: true });
174
+ }
175
+
176
+ // 只在 stores 目录不存在 user.ts 时创建示例 store
177
+ const userStorePath = path.join(storesDir, 'user.ts');
178
+ if (!fs.existsSync(userStorePath)) {
179
+ const userStoreContent = `import { defineStore } from '@deppon/deppon-pinia';
180
+
181
+ export const useUserStore = defineStore('user', {
182
+ state: () => ({
183
+ name: '',
184
+ age: 0,
185
+ }),
186
+ getters: {
187
+ displayName: state => {
188
+ return state.name || '未命名';
189
+ },
190
+ },
191
+ actions: {
192
+ setName(name: string) {
193
+ this.name = name;
194
+ },
195
+ setAge(age: number) {
196
+ this.age = age;
197
+ },
198
+ },
199
+ });
200
+ `;
201
+ fs.writeFileSync(userStorePath, userStoreContent);
202
+ }
203
+ }
204
+
205
+ // 生成 Router 相关文件
206
+ if (packages.includes('router')) {
207
+ console.log('生成 Router 配置');
208
+ const routerDir = path.join(srcDir, 'router');
209
+ if (!fs.existsSync(routerDir)) {
210
+ fs.mkdirSync(routerDir, { recursive: true });
211
+ }
212
+
213
+ // 注意:不覆盖模板中的 router/index.ts,模板已包含完整的路由配置
214
+ // 模板中的 router/index.ts 已经包含了完整的路由配置和初始化代码
215
+
216
+ // 创建视图目录和示例页面(只在不存在时创建)
217
+ const viewsDir = path.join(srcDir, 'views');
218
+ if (!fs.existsSync(viewsDir)) {
219
+ fs.mkdirSync(viewsDir, { recursive: true });
220
+ }
221
+
222
+ // 只在 Home.vue 不存在时创建示例页面
223
+ const homeViewPath = path.join(viewsDir, 'Home.vue');
224
+ if (!fs.existsSync(homeViewPath)) {
225
+ const homeViewContent = `<template>
226
+ <div class="home">
227
+ <h1>首页</h1>
228
+ <p>欢迎使用 deppon-router</p>
229
+ <router-link to="/about">前往关于页</router-link>
230
+ </div>
231
+ </template>
232
+
233
+ <script setup lang="ts">
234
+ import { useRoute } from '@deppon/deppon-router';
235
+
236
+ const route = useRoute();
237
+ console.log('当前路由:', route.path);
238
+ </script>
239
+
240
+ <style scoped>
241
+ .home {
242
+ padding: 20px;
243
+ text-align: center;
244
+ }
245
+ </style>
246
+ `;
247
+ fs.writeFileSync(homeViewPath, homeViewContent);
248
+ }
249
+
250
+ // 只在 About.vue 不存在时创建示例页面
251
+ const aboutViewPath = path.join(viewsDir, 'About.vue');
252
+ if (!fs.existsSync(aboutViewPath)) {
253
+ const aboutViewContent = `<template>
254
+ <div class="about">
255
+ <h1>关于</h1>
256
+ <p>这是关于页面</p>
257
+ <router-link to="/">返回首页</router-link>
258
+ </div>
259
+ </template>
260
+
261
+ <script setup lang="ts">
262
+ import { useRouter } from '@deppon/deppon-router';
263
+
264
+ const router = useRouter();
265
+
266
+ const goHome = () => {
267
+ router.push('/');
268
+ };
269
+ </script>
270
+
271
+ <style scoped>
272
+ .about {
273
+ padding: 20px;
274
+ text-align: center;
275
+ }
276
+ </style>
277
+ `;
278
+ fs.writeFileSync(aboutViewPath, aboutViewContent);
279
+ }
280
+
281
+ // 注意:不覆盖模板中的 main.ts 和 App.vue,模板已包含完整配置
282
+ }
283
+
284
+ // 生成 Request 相关配置
285
+ if (packages.includes('request')) {
286
+ console.log('生成 Request 配置');
287
+ const utilsDir = path.join(srcDir, 'utils');
288
+ if (!fs.existsSync(utilsDir)) {
289
+ fs.mkdirSync(utilsDir, { recursive: true });
290
+ }
291
+
292
+ // 只在 request.ts 不存在时创建,模板中已包含完整的 request.ts
293
+ const requestPath = path.join(utilsDir, 'request.ts');
294
+ if (!fs.existsSync(requestPath)) {
295
+ const requestContent = `import request from '@deppon/deppon-request';
296
+
297
+ // 示例:封装 API 请求
298
+ export const getUserInfo = (id: number) => {
299
+ return request({
300
+ url: '/api/user/info',
301
+ method: 'get',
302
+ params: { id },
303
+ });
304
+ };
305
+
306
+ export default request;
307
+ `;
308
+ fs.writeFileSync(requestPath, requestContent);
309
+ }
310
+ // 注意:不覆盖模板中的 main.ts,模板已包含完整的配置
311
+ }
312
+
313
+ // 生成 UI 组件库相关配置
314
+ if (packages.includes('ui')) {
315
+ console.log('生成 UI 组件库配置');
316
+ // 注意:不覆盖模板中的 App.vue,模板已包含完整的 UI 配置和样式
317
+ }
318
+
319
+ // 注意:不覆盖模板中的 main.ts,模板已包含完整的配置
320
+ // 模板中的 main.ts 已经包含了所有必要的导入和初始化代码
321
+ }
322
+
323
+ program.name('create-deppon-app').description(pkg.description).version(pkg.version);
324
+
325
+ program
326
+ .argument('<project-directory>', '项目名称', name => {
327
+ if (name.includes(' ')) {
328
+ throw new InvalidOptionArgumentError('项目名称中不能包含空格');
329
+ }
330
+ return name;
331
+ })
332
+ .usage(`${chalk.green('<project-directory>')}`)
333
+ .action(name => {
334
+ projectName = name || undefined;
335
+ });
336
+
337
+ program.parse();
338
+
339
+ // 只支持 TypeScript 模板
340
+
341
+ async function createProject() {
342
+ const unsupportedNodeVersion = !semver.satisfies(semver.coerce(process.versions.node), '>=14');
343
+
344
+ if (unsupportedNodeVersion) {
345
+ console.log(
346
+ chalk.red(`Node 版本过低!当前 Node 版本为 ${process.versions.node}。\n\n` + `请升级至 14 或更高。\n`),
347
+ );
348
+ process.exit(1);
349
+ }
350
+ const root = path.resolve(projectName);
351
+ const appName = path.basename(root);
352
+
353
+ if (fs.existsSync(root)) {
354
+ console.log(chalk.red(`${projectName} 文件夹已存在,请先删除`));
355
+ process.exit(1);
356
+ }
357
+
358
+ // 询问用户选择构建工具
359
+ const { buildTool } = await inquirer.prompt([
360
+ {
361
+ type: 'list',
362
+ name: 'buildTool',
363
+ message: '请选择构建工具:',
364
+ choices: [
365
+ { name: 'Webpack (稳定,功能全面)', value: 'webpack' },
366
+ { name: 'Vite (快速,现代化)', value: 'vite' },
367
+ ],
368
+ default: 'webpack',
369
+ },
370
+ ]);
371
+
372
+ // 询问用户选择要添加的包
373
+ const { packages } = await inquirer.prompt([
374
+ {
375
+ type: 'checkbox',
376
+ name: 'packages',
377
+ message: '请选择要添加的包(使用空格键选择,回车确认):',
378
+ choices: [
379
+ { name: 'deppon-pinia (状态管理)', value: 'pinia', checked: false },
380
+ { name: 'deppon-router (路由管理)', value: 'router', checked: false },
381
+ { name: 'deppon-request (HTTP 请求)', value: 'request', checked: false },
382
+ { name: 'deppon-ui (UI 组件库)', value: 'ui', checked: false },
383
+ ],
384
+ },
385
+ ]);
386
+
387
+ fs.mkdirSync(projectName);
388
+
389
+ console.log();
390
+
391
+ console.log(`开始创建目录`);
392
+ console.log();
393
+
394
+ console.log(`生成 package.json`);
395
+
396
+ const ext = 'js,ts,vue';
397
+ const isVite = buildTool === 'vite';
398
+
399
+ // 动态获取所有需要的包版本号
400
+ console.log(chalk.blue('正在获取包版本号...'));
401
+ const versionPromises = [
402
+ getPackageVersion('@deppon/deppon-eslint-config', '^1.0.4'),
403
+ getPackageVersion(isVite ? '@deppon/deppon-vite-scripts' : '@deppon/deppon-webpack-scripts', '^1.0.4'),
404
+ ];
405
+
406
+ // 只获取用户选择的包的版本号
407
+ if (packages.includes('pinia')) {
408
+ versionPromises.push(getPackageVersion('@deppon/deppon-pinia', '^1.0.4'));
409
+ }
410
+ if (packages.includes('router')) {
411
+ versionPromises.push(getPackageVersion('@deppon/deppon-router', '^1.0.4'));
412
+ }
413
+ if (packages.includes('request')) {
414
+ versionPromises.push(getPackageVersion('@deppon/deppon-request', '^1.0.4'));
415
+ }
416
+ if (packages.includes('ui')) {
417
+ versionPromises.push(getPackageVersion('@deppon/deppon-ui', '^1.0.4'));
418
+ }
419
+
420
+ const versions = await Promise.all(versionPromises);
421
+ const eslintConfigVersion = versions[0];
422
+ const buildScriptVersion = versions[1];
423
+ let versionIndex = 2;
424
+ const piniaVersion = packages.includes('pinia') ? versions[versionIndex++] : null;
425
+ const routerVersion = packages.includes('router') ? versions[versionIndex++] : null;
426
+ const requestVersion = packages.includes('request') ? versions[versionIndex++] : null;
427
+ const uiVersion = packages.includes('ui') ? versions[versionIndex++] : null;
428
+
429
+ // 根据用户选择的包添加依赖
430
+ const dependencies = {
431
+ vue: '^3.4.0',
432
+ };
433
+ const devDependencies = {
434
+ '@deppon/deppon-eslint-config': eslintConfigVersion,
435
+ [isVite ? '@deppon/deppon-vite-scripts' : '@deppon/deppon-webpack-scripts']: buildScriptVersion,
436
+ '@types/node': '^20.0.0',
437
+ typescript: '^5.0.0',
438
+ };
439
+
440
+ // 添加选择的包依赖
441
+ if (packages.includes('pinia')) {
442
+ dependencies['@deppon/deppon-pinia'] = piniaVersion;
443
+ // pinia 会自动作为 @deppon/deppon-pinia 的依赖安装,无需手动添加
444
+ }
445
+ if (packages.includes('router')) {
446
+ dependencies['@deppon/deppon-router'] = routerVersion;
447
+ // vue-router 会自动作为 @deppon/deppon-router 的依赖安装,无需手动添加
448
+ }
449
+ if (packages.includes('request')) {
450
+ dependencies['@deppon/deppon-request'] = requestVersion;
451
+ }
452
+ if (packages.includes('ui')) {
453
+ dependencies['@deppon/deppon-ui'] = uiVersion;
454
+ }
455
+
456
+ const packageJson = {
457
+ name: appName,
458
+ version: '1.0.0',
459
+ license: 'MIT',
460
+ scripts: {
461
+ start: isVite ? 'deppon-vite start' : 'deppon start',
462
+ dev: isVite ? 'deppon-vite build' : 'export NODE_ENV=dev && deppon build',
463
+ pre: isVite ? 'deppon-vite build' : 'export NODE_ENV=pre && deppon build',
464
+ analyze: isVite ? 'deppon-vite build' : 'deppon build --analyze',
465
+ build: isVite ? 'deppon-vite build' : 'deppon build',
466
+ lint: `eslint . --ext ${ext}`,
467
+ format: `prettier --write **/*.{${ext}} && eslint . --ext ${ext} --fix`,
468
+ },
469
+ dependencies,
470
+ devDependencies,
471
+ };
472
+
473
+ fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson, null, 2));
474
+
475
+ const readmeContent = `
476
+ # ${appName.toUpperCase()}
477
+
478
+ ## 启动
479
+
480
+ \`\`\`bash
481
+ npm start
482
+ \`\`\`
483
+
484
+ ## 构建
485
+
486
+ \`\`\`bash
487
+ npm run build
488
+ `;
489
+ fs.writeFileSync(path.join(root, 'README.md'), readmeContent);
490
+ const templateDir = path.join(__dirname, `template`);
491
+
492
+ if (fs.existsSync(templateDir)) {
493
+ console.log(`初始化文件`);
494
+ fs.cpSync(templateDir, root, { recursive: true });
495
+
496
+ // 注意:不再生成 deppon.config.js,模板中已包含 vite.config.ts 配置文件
497
+ // 模板中的 vite.config.ts 已经包含了完整的 Vite 配置
498
+
499
+ // 重命名 .eslintrc.cjs 为 .eslintrc.js 并写入正确的配置
500
+ const eslintrcCjsPath = path.join(root, '.eslintrc.cjs');
501
+ const eslintrcJsPath = path.join(root, '.eslintrc.js');
502
+ if (fs.existsSync(eslintrcCjsPath)) {
503
+ // 写入正确的 ESLint 配置
504
+ const eslintConfig = `// This is a template file, ESLint config will be available after project initialization
505
+ module.exports = {
506
+ extends: ['@deppon/deppon-eslint-config'],
507
+ };
508
+ `;
509
+ fs.writeFileSync(eslintrcJsPath, eslintConfig);
510
+ fs.unlinkSync(eslintrcCjsPath);
511
+ }
512
+
513
+ // 重命名环境变量示例文件
514
+ const envExamplePath = path.join(root, '.example.env');
515
+ const envPath = path.join(root, '.env');
516
+ if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
517
+ fs.renameSync(envExamplePath, envPath, []);
518
+ }
519
+
520
+ // 重命名所有环境变量示例文件为实际配置文件
521
+ const envFileMappings = [
522
+ { example: 'env.development.example', target: '.env.development' }, // 处理不带点的文件
523
+ { example: '.env.dev-local.example', target: '.env.dev-local' },
524
+ { example: '.env.development.example', target: '.env.development' },
525
+ { example: '.env.production.example', target: '.env.production' },
526
+ { example: '.env.test.example', target: '.env.test' },
527
+ ];
528
+
529
+ envFileMappings.forEach(({ example, target }) => {
530
+ const examplePath = path.join(root, example);
531
+ const targetPath = path.join(root, target);
532
+ if (fs.existsSync(examplePath) && !fs.existsSync(targetPath)) {
533
+ fs.renameSync(examplePath, targetPath, []);
534
+ }
535
+ });
536
+
537
+ // 清理所有剩余的 example 文件(这些文件只是模板参考,不需要在生成的项目中保留)
538
+ const exampleFiles = [
539
+ '.env.dev-local.example',
540
+ '.env.development.example',
541
+ '.env.example',
542
+ '.env.production.example',
543
+ '.env.test.example',
544
+ ];
545
+ exampleFiles.forEach(exampleFile => {
546
+ const exampleFilePath = path.join(root, exampleFile);
547
+ if (fs.existsSync(exampleFilePath)) {
548
+ fs.unlinkSync(exampleFilePath);
549
+ }
550
+ });
551
+
552
+ // 替换 Dockerfile 中的项目名占位符
553
+ const dockerfilePath = path.join(root, 'Dockerfile');
554
+ if (fs.existsSync(dockerfilePath)) {
555
+ let dockerfileContent = fs.readFileSync(dockerfilePath, 'utf-8');
556
+ dockerfileContent = dockerfileContent.replace(/\{\{PROJECT_NAME\}\}/g, appName);
557
+ fs.writeFileSync(dockerfilePath, dockerfileContent);
558
+ }
559
+
560
+ // 根据选择的包生成相应的初始化代码
561
+ await generatePackageFiles(root, packages, isVite);
562
+ } else {
563
+ console.error(chalk.red(`找不到模板: ${templateDir}`));
564
+ process.exit(1);
565
+ }
566
+
567
+ process.chdir(root);
568
+
569
+ const npmrcExists = fs.existsSync(path.join(root, '.npmrc'));
570
+ if (npmrcExists) {
571
+ const data = fs.readFileSync(path.join(root, 'npmrc'));
572
+ fs.appendFileSync(path.join(root, '.npmrc'), data);
573
+ fs.unlinkSync(path.join(root, 'npmrc'));
574
+ } else {
575
+ fs.renameSync(path.join(root, 'npmrc'), path.join(root, '.npmrc'), []);
576
+ }
577
+
578
+ // 检测 Yarn 版本并选择合适的配置文件
579
+ const yarnVersion = await getYarnVersion();
580
+ const useYarn3 = isYarn3(yarnVersion);
581
+
582
+ if (useYarn3) {
583
+ // Yarn 3+ 使用 .yarnrc.yml
584
+ const yarnrcYmlExists = fs.existsSync(path.join(root, '.yarnrc.yml'));
585
+ if (yarnrcYmlExists) {
586
+ const data = fs.readFileSync(path.join(root, 'yarnrc.yml'));
587
+ fs.appendFileSync(path.join(root, '.yarnrc.yml'), '\n' + data);
588
+ fs.unlinkSync(path.join(root, 'yarnrc.yml'));
589
+ } else {
590
+ fs.renameSync(path.join(root, 'yarnrc.yml'), path.join(root, '.yarnrc.yml'), []);
591
+ }
592
+ // 删除旧的 .yarnrc 文件(如果存在)
593
+ const oldYarnrcPath = path.join(root, 'yarnrc');
594
+ if (fs.existsSync(oldYarnrcPath)) {
595
+ fs.unlinkSync(oldYarnrcPath);
596
+ }
597
+ } else {
598
+ // Yarn 1.x 使用 .yarnrc
599
+ const yarnrcExists = fs.existsSync(path.join(root, '.yarnrc'));
600
+ if (yarnrcExists) {
601
+ const data = fs.readFileSync(path.join(root, 'yarnrc'));
602
+ fs.appendFileSync(path.join(root, '.yarnrc'), data);
603
+ fs.unlinkSync(path.join(root, 'yarnrc'));
604
+ } else {
605
+ fs.renameSync(path.join(root, 'yarnrc'), path.join(root, '.yarnrc'), []);
606
+ }
607
+ // 删除 .yarnrc.yml 文件(如果存在)
608
+ const yarnrcYmlPath = path.join(root, 'yarnrc.yml');
609
+ if (fs.existsSync(yarnrcYmlPath)) {
610
+ fs.unlinkSync(yarnrcYmlPath);
611
+ }
612
+ }
613
+
614
+ const gitignoreExists = fs.existsSync(path.join(root, '.gitignore'));
615
+ if (gitignoreExists) {
616
+ const data = fs.readFileSync(path.join(root, 'gitignore'));
617
+ fs.appendFileSync(path.join(root, '.gitignore'), data);
618
+ fs.unlinkSync(path.join(root, 'gitignore'));
619
+ } else {
620
+ // 重命名 gitignore
621
+ // See: https://github.com/npm/npm/issues/1862
622
+ fs.renameSync(path.join(root, 'gitignore'), path.join(root, '.gitignore'), []);
623
+ }
624
+
625
+ console.log(`初始化 git \n\n`);
626
+ child_process.spawnSync('git init', { stdio: 'ignore' });
627
+
628
+ console.log(`${appName} 创建完成 \n\n 路径:${chalk.green(root)}\n\n`);
629
+ console.log(`构建工具: ${chalk.cyan(isVite ? 'Vite' : 'Webpack')}\n`);
630
+ if (packages.length > 0) {
631
+ console.log(`已添加的包: ${chalk.cyan(packages.join(', '))}\n`);
632
+ }
633
+ console.log(`进入目录: ${chalk.blue(`cd ${projectName}`)}`);
634
+ console.log(`安装依赖: ${chalk.blue(`npm install`)}\n\n`);
635
+ }
636
+
637
+ createProject().catch(err => {
638
+ console.error(chalk.red('创建项目失败:'), err);
639
+ process.exit(1);
640
+ });