@ljjjjjjj/my-cli 1.1.0 → 1.1.1
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 +198 -3
- package/bin/index.js +95 -142
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,198 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# my-cli
|
|
2
|
+
|
|
3
|
+
一个基于 Node.js 的前端脚手架工具,帮助你快速生成 Vite 项目模板,并在已有项目中生成组件和页面骨架。
|
|
4
|
+
|
|
5
|
+
## 功能特点
|
|
6
|
+
|
|
7
|
+
- 支持创建 Vue3 + Vite 或 React + Vite 项目模板
|
|
8
|
+
- 支持在当前项目中生成组件代码
|
|
9
|
+
- 支持在当前项目中生成页面代码
|
|
10
|
+
- 会自动识别项目是 Vue 还是 React
|
|
11
|
+
- 支持通过配置文件自定义生成目录
|
|
12
|
+
- 支持使用 --force 覆盖已存在文件
|
|
13
|
+
|
|
14
|
+
## 支持的模板
|
|
15
|
+
|
|
16
|
+
- Vue3 + Vite
|
|
17
|
+
- React + Vite
|
|
18
|
+
|
|
19
|
+
## 安装方式
|
|
20
|
+
|
|
21
|
+
### 方式一:作为公开包使用(推荐给他人)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @ljjjjjjj/my-cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
安装后可以直接使用:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
my-cli --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 方式二:本地开发调试使用
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install
|
|
37
|
+
npm link
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
然后就可以在当前环境中直接执行:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
my-cli create your-project-name
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 命令说明
|
|
47
|
+
|
|
48
|
+
### 0. 查看帮助
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
my-cli help
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
或:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
my-cli --help
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
会看到当前支持的命令:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
my-cli create <项目名称>
|
|
64
|
+
my-cli add component <组件名称> [--force]
|
|
65
|
+
my-cli add page <页面名称> [--force]
|
|
66
|
+
my-cli help
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 1. 创建项目
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
my-cli create your-project-name
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
执行后会提示你:
|
|
76
|
+
|
|
77
|
+
- 选择项目模板
|
|
78
|
+
- 输入项目描述
|
|
79
|
+
|
|
80
|
+
创建成功后,会生成一个新的 Vite 项目目录。
|
|
81
|
+
|
|
82
|
+
### 2. 生成组件
|
|
83
|
+
|
|
84
|
+
在当前项目中执行:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
my-cli add component Button
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
脚手架会先从当前目录向上查找 package.json,定位项目根目录;随后根据项目类型自动选择对应模板。
|
|
91
|
+
|
|
92
|
+
默认生成位置:
|
|
93
|
+
|
|
94
|
+
- Vue 项目:src/components/Button/
|
|
95
|
+
- React 项目:src/components/Button/
|
|
96
|
+
|
|
97
|
+
默认生成内容:
|
|
98
|
+
|
|
99
|
+
Vue 项目:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
src/components/Button/
|
|
103
|
+
├── Button.vue
|
|
104
|
+
├── Button.module.scss
|
|
105
|
+
├── index.ts
|
|
106
|
+
├── README.md
|
|
107
|
+
└── Button.test.ts
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
React 项目:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
src/components/Button/
|
|
114
|
+
├── Button.jsx
|
|
115
|
+
├── Button.module.css
|
|
116
|
+
├── index.js
|
|
117
|
+
├── README.md
|
|
118
|
+
└── Button.test.jsx
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. 生成页面
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
my-cli add page User
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
默认生成位置:
|
|
128
|
+
|
|
129
|
+
- Vue 项目:src/pages/User/
|
|
130
|
+
- React 项目:src/pages/User/
|
|
131
|
+
|
|
132
|
+
默认生成内容:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
src/pages/User/
|
|
136
|
+
├── index.vue
|
|
137
|
+
├── style.scss
|
|
138
|
+
├── api.ts
|
|
139
|
+
├── hooks.ts
|
|
140
|
+
├── store.ts
|
|
141
|
+
└── types.ts
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
React 项目则会生成对应的 jsx 和 css 文件。
|
|
145
|
+
|
|
146
|
+
### 4. 覆盖已存在文件
|
|
147
|
+
|
|
148
|
+
如果目标文件已存在,默认会询问是否覆盖;如果你想直接覆盖,可以使用:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
my-cli add component Button --force
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
或简写:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
my-cli add page User -f
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## 配置说明
|
|
161
|
+
|
|
162
|
+
脚手架会先从当前目录向上查找 package.json,定位项目根目录。
|
|
163
|
+
|
|
164
|
+
你可以在项目根目录创建 .my-cli.json 来指定生成目录和框架:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"framework": "vue",
|
|
169
|
+
"componentsDir": "src/components",
|
|
170
|
+
"pagesDir": "src/pages"
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
也可以在 package.json 中配置:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"myCli": {
|
|
179
|
+
"framework": "react",
|
|
180
|
+
"componentsDir": "src/components",
|
|
181
|
+
"pagesDir": "src/pages"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
如果没有配置,脚手架会自动根据 package.json 或项目文件结构进行检测。
|
|
187
|
+
|
|
188
|
+
## 适用场景
|
|
189
|
+
|
|
190
|
+
这个脚手架适合用于:
|
|
191
|
+
|
|
192
|
+
- 快速初始化前端项目
|
|
193
|
+
- 在已有项目中快速生成组件和页面
|
|
194
|
+
- 作为学习和练习 CLI 工具的示例项目
|
|
195
|
+
|
|
196
|
+
## 说明
|
|
197
|
+
|
|
198
|
+
如果你希望把这个脚手架给他人使用,请直接使用发布到 npm 的包名进行安装。
|
package/bin/index.js
CHANGED
|
@@ -5,11 +5,15 @@ import fs from 'fs-extra';
|
|
|
5
5
|
import ora from 'ora';
|
|
6
6
|
import inquirer from 'inquirer';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
|
+
import { program } from 'commander'; // ← 关键新增
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
11
12
|
|
|
12
|
-
// ----------
|
|
13
|
+
// ---------- 读取 package.json ----------
|
|
14
|
+
const pkg = await fs.readJson(path.join(__dirname, '../package.json'));
|
|
15
|
+
|
|
16
|
+
// ---------- 模板映射 ----------
|
|
13
17
|
const TEMPLATE_MAP = {
|
|
14
18
|
'Vue3 + Vite': path.resolve(__dirname, '../templates/vue3-vite'),
|
|
15
19
|
'React + Vite': path.resolve(__dirname, '../templates/react-vite')
|
|
@@ -27,14 +31,6 @@ function renderTemplate(templatePath, replacements) {
|
|
|
27
31
|
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => replacements[key] ?? '');
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
function printHelp() {
|
|
31
|
-
console.log(`使用方法:
|
|
32
|
-
my-cli create <项目名称>
|
|
33
|
-
my-cli add component <组件名称> [--force]
|
|
34
|
-
my-cli add page <页面名称> [--force]
|
|
35
|
-
my-cli help`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
34
|
// ---------- 查找项目根目录 ----------
|
|
39
35
|
function findProjectRoot(startDir = process.cwd()) {
|
|
40
36
|
let current = startDir;
|
|
@@ -45,29 +41,27 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
45
41
|
}
|
|
46
42
|
current = path.dirname(current);
|
|
47
43
|
}
|
|
48
|
-
return startDir;
|
|
44
|
+
return startDir;
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
// ---------- 加载项目配置 ----------
|
|
52
48
|
function loadProjectConfig(projectRoot) {
|
|
53
49
|
const config = {
|
|
54
|
-
framework: null,
|
|
50
|
+
framework: null,
|
|
55
51
|
componentsDir: 'src/components',
|
|
56
52
|
pagesDir: 'src/pages'
|
|
57
53
|
};
|
|
58
54
|
|
|
59
|
-
// 1. 读取 .my-cli.json
|
|
60
55
|
const cliConfigPath = path.join(projectRoot, '.my-cli.json');
|
|
61
56
|
if (fs.existsSync(cliConfigPath)) {
|
|
62
57
|
try {
|
|
63
58
|
const userConfig = fs.readJsonSync(cliConfigPath);
|
|
64
59
|
Object.assign(config, userConfig);
|
|
65
|
-
} catch
|
|
60
|
+
} catch {
|
|
66
61
|
console.warn('⚠️ 解析 .my-cli.json 失败,使用默认配置');
|
|
67
62
|
}
|
|
68
63
|
}
|
|
69
64
|
|
|
70
|
-
// 2. 读取 package.json 中的 myCli 字段
|
|
71
65
|
const pkgPath = path.join(projectRoot, 'package.json');
|
|
72
66
|
if (fs.existsSync(pkgPath)) {
|
|
73
67
|
try {
|
|
@@ -75,21 +69,16 @@ function loadProjectConfig(projectRoot) {
|
|
|
75
69
|
if (pkg.myCli) {
|
|
76
70
|
Object.assign(config, pkg.myCli);
|
|
77
71
|
}
|
|
78
|
-
} catch
|
|
79
|
-
// ignore
|
|
80
|
-
}
|
|
72
|
+
} catch { }
|
|
81
73
|
}
|
|
82
74
|
|
|
83
|
-
// 确保路径使用相对路径(相对于项目根)
|
|
84
75
|
config.componentsDir = config.componentsDir.replace(/^\.?\//, '');
|
|
85
76
|
config.pagesDir = config.pagesDir.replace(/^\.?\//, '');
|
|
86
|
-
|
|
87
77
|
return config;
|
|
88
78
|
}
|
|
89
79
|
|
|
90
|
-
// ----------
|
|
80
|
+
// ---------- 检测项目类型 ----------
|
|
91
81
|
function detectProjectType(projectRoot, config) {
|
|
92
|
-
// 优先使用配置文件指定的框架
|
|
93
82
|
if (config.framework) {
|
|
94
83
|
if (config.framework === 'react' || config.framework === 'vue') {
|
|
95
84
|
return config.framework;
|
|
@@ -97,7 +86,6 @@ function detectProjectType(projectRoot, config) {
|
|
|
97
86
|
console.warn(`⚠️ 未知框架 "${config.framework}",将自动检测`);
|
|
98
87
|
}
|
|
99
88
|
|
|
100
|
-
// 自动检测
|
|
101
89
|
const pkgPath = path.join(projectRoot, 'package.json');
|
|
102
90
|
if (fs.existsSync(pkgPath)) {
|
|
103
91
|
try {
|
|
@@ -106,37 +94,23 @@ function detectProjectType(projectRoot, config) {
|
|
|
106
94
|
...(pkg.dependencies || {}),
|
|
107
95
|
...(pkg.devDependencies || {})
|
|
108
96
|
};
|
|
109
|
-
if (deps.react || deps['@vitejs/plugin-react'] || deps.preact)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (deps.vue || deps['@vitejs/plugin-vue']) {
|
|
113
|
-
return 'vue';
|
|
114
|
-
}
|
|
115
|
-
} catch (err) {
|
|
116
|
-
// ignore
|
|
117
|
-
}
|
|
97
|
+
if (deps.react || deps['@vitejs/plugin-react'] || deps.preact) return 'react';
|
|
98
|
+
if (deps.vue || deps['@vitejs/plugin-vue']) return 'vue';
|
|
99
|
+
} catch { }
|
|
118
100
|
}
|
|
119
101
|
|
|
120
|
-
// 通过文件存在性检测
|
|
121
102
|
const srcPath = path.join(projectRoot, 'src');
|
|
122
103
|
if (fs.existsSync(srcPath)) {
|
|
123
104
|
const files = fs.readdirSync(srcPath);
|
|
124
|
-
if (files.some(f => f.endsWith('.jsx') || f.endsWith('.tsx')))
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
if (files.some(f => f.endsWith('.vue'))) {
|
|
128
|
-
return 'vue';
|
|
129
|
-
}
|
|
105
|
+
if (files.some(f => f.endsWith('.jsx') || f.endsWith('.tsx'))) return 'react';
|
|
106
|
+
if (files.some(f => f.endsWith('.vue'))) return 'vue';
|
|
130
107
|
}
|
|
131
|
-
|
|
132
|
-
// 默认返回 vue(兼容旧逻辑)
|
|
133
108
|
return 'vue';
|
|
134
109
|
}
|
|
135
110
|
|
|
136
111
|
// ---------- 获取脚手架配置 ----------
|
|
137
112
|
function getScaffoldConfig(projectRoot, projectType) {
|
|
138
113
|
const templateRoot = path.resolve(__dirname, '../templates/generator');
|
|
139
|
-
|
|
140
114
|
if (projectType === 'react') {
|
|
141
115
|
return {
|
|
142
116
|
projectType,
|
|
@@ -154,10 +128,9 @@ function getScaffoldConfig(projectRoot, projectType) {
|
|
|
154
128
|
{ name: 'types.ts', templatePath: path.join(templateRoot, 'page/react/types.ts.tpl') }
|
|
155
129
|
],
|
|
156
130
|
testTemplate: (componentName) =>
|
|
157
|
-
`import { describe, it, expect } from 'vitest';\n\
|
|
131
|
+
`import { describe, it, expect } from 'vitest';\n\ndescribe('${componentName}', () => {\n it('renders correctly', () => {\n expect(true).toBe(true);\n });\n});\n`
|
|
158
132
|
};
|
|
159
133
|
}
|
|
160
|
-
|
|
161
134
|
// vue
|
|
162
135
|
return {
|
|
163
136
|
projectType: 'vue',
|
|
@@ -175,11 +148,11 @@ function getScaffoldConfig(projectRoot, projectType) {
|
|
|
175
148
|
{ name: 'types.ts', templatePath: path.join(templateRoot, 'page/vue/types.ts.tpl') }
|
|
176
149
|
],
|
|
177
150
|
testTemplate: (componentName) =>
|
|
178
|
-
`import { describe, it, expect } from 'vitest';\n\
|
|
151
|
+
`import { describe, it, expect } from 'vitest';\n\ndescribe('${componentName}', () => {\n it('renders correctly', () => {\n expect(true).toBe(true);\n });\n});\n`
|
|
179
152
|
};
|
|
180
153
|
}
|
|
181
154
|
|
|
182
|
-
// ----------
|
|
155
|
+
// ---------- 安全写入文件 ----------
|
|
183
156
|
async function safeWriteFile(filePath, content, force = false) {
|
|
184
157
|
if (fs.existsSync(filePath) && !force) {
|
|
185
158
|
const { overwrite } = await inquirer.prompt([
|
|
@@ -190,12 +163,10 @@ async function safeWriteFile(filePath, content, force = false) {
|
|
|
190
163
|
default: false
|
|
191
164
|
}
|
|
192
165
|
]);
|
|
193
|
-
if (!overwrite)
|
|
194
|
-
return false; // 跳过
|
|
195
|
-
}
|
|
166
|
+
if (!overwrite) return false;
|
|
196
167
|
}
|
|
197
168
|
await fs.outputFile(filePath, content);
|
|
198
|
-
return true;
|
|
169
|
+
return true;
|
|
199
170
|
}
|
|
200
171
|
|
|
201
172
|
// ---------- 生成组件 ----------
|
|
@@ -205,7 +176,6 @@ async function generateComponentFiles(name, projectRoot, config, force) {
|
|
|
205
176
|
await fs.ensureDir(baseDir);
|
|
206
177
|
|
|
207
178
|
const scaffoldConfig = getScaffoldConfig(projectRoot, config.framework || detectProjectType(projectRoot, config));
|
|
208
|
-
|
|
209
179
|
const replacements = {
|
|
210
180
|
componentName,
|
|
211
181
|
componentNameLower: componentName.toLowerCase()
|
|
@@ -234,18 +204,11 @@ async function generateComponentFiles(name, projectRoot, config, force) {
|
|
|
234
204
|
}
|
|
235
205
|
];
|
|
236
206
|
|
|
237
|
-
let overwrittenCount = 0;
|
|
238
|
-
let skippedCount = 0;
|
|
239
|
-
|
|
207
|
+
let overwrittenCount = 0, skippedCount = 0;
|
|
240
208
|
for (const file of filesToGenerate) {
|
|
241
209
|
const result = await safeWriteFile(file.path, file.content, force);
|
|
242
|
-
|
|
243
|
-
overwrittenCount++;
|
|
244
|
-
} else {
|
|
245
|
-
skippedCount++;
|
|
246
|
-
}
|
|
210
|
+
result ? overwrittenCount++ : skippedCount++;
|
|
247
211
|
}
|
|
248
|
-
|
|
249
212
|
return { overwrittenCount, skippedCount, baseDir };
|
|
250
213
|
}
|
|
251
214
|
|
|
@@ -256,7 +219,6 @@ async function generatePageFiles(name, projectRoot, config, force) {
|
|
|
256
219
|
await fs.ensureDir(baseDir);
|
|
257
220
|
|
|
258
221
|
const scaffoldConfig = getScaffoldConfig(projectRoot, config.framework || detectProjectType(projectRoot, config));
|
|
259
|
-
|
|
260
222
|
const replacements = {
|
|
261
223
|
pageName,
|
|
262
224
|
pageNameLower: pageName.toLowerCase()
|
|
@@ -272,8 +234,6 @@ async function generatePageFiles(name, projectRoot, config, force) {
|
|
|
272
234
|
content: renderTemplate(scaffoldConfig.pageStyleTemplatePath, replacements)
|
|
273
235
|
}
|
|
274
236
|
];
|
|
275
|
-
|
|
276
|
-
// 添加额外的页面文件(api.ts, hooks.ts 等)
|
|
277
237
|
for (const pageFile of scaffoldConfig.pageFiles) {
|
|
278
238
|
filesToGenerate.push({
|
|
279
239
|
path: path.join(baseDir, pageFile.name),
|
|
@@ -281,123 +241,110 @@ async function generatePageFiles(name, projectRoot, config, force) {
|
|
|
281
241
|
});
|
|
282
242
|
}
|
|
283
243
|
|
|
284
|
-
let overwrittenCount = 0;
|
|
285
|
-
let skippedCount = 0;
|
|
286
|
-
|
|
244
|
+
let overwrittenCount = 0, skippedCount = 0;
|
|
287
245
|
for (const file of filesToGenerate) {
|
|
288
246
|
const result = await safeWriteFile(file.path, file.content, force);
|
|
289
|
-
|
|
290
|
-
overwrittenCount++;
|
|
291
|
-
} else {
|
|
292
|
-
skippedCount++;
|
|
293
|
-
}
|
|
247
|
+
result ? overwrittenCount++ : skippedCount++;
|
|
294
248
|
}
|
|
295
|
-
|
|
296
249
|
return { overwrittenCount, skippedCount, baseDir };
|
|
297
250
|
}
|
|
298
251
|
|
|
299
|
-
// ----------
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
filteredArgs.push(arg);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
rest = filteredArgs;
|
|
316
|
-
|
|
317
|
-
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
318
|
-
printHelp();
|
|
319
|
-
process.exit(0);
|
|
320
|
-
}
|
|
252
|
+
// ---------- Commander 配置 ----------
|
|
253
|
+
program
|
|
254
|
+
.version(pkg.version, '-V, --version', '显示版本号')
|
|
255
|
+
.description('一个快速生成 Vue3/React + Vite 项目的脚手架工具');
|
|
256
|
+
|
|
257
|
+
// ---------- create 命令 ----------
|
|
258
|
+
program
|
|
259
|
+
.command('create <project-name>')
|
|
260
|
+
.description('创建一个新项目')
|
|
261
|
+
.option('-t, --template <name>', '指定模板名称(Vue3 + Vite / React + Vite)')
|
|
262
|
+
.option('-d, --description <desc>', '项目描述')
|
|
263
|
+
.action(async (projectName, options) => {
|
|
264
|
+
console.log(`✨ 正在创建项目: ${projectName}`);
|
|
321
265
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
266
|
+
let templateKey = options.template;
|
|
267
|
+
let description = options.description;
|
|
268
|
+
|
|
269
|
+
if (!templateKey) {
|
|
270
|
+
const answer = await inquirer.prompt([
|
|
271
|
+
{
|
|
272
|
+
type: 'select',
|
|
273
|
+
name: 'templateKey',
|
|
274
|
+
message: '请选择项目模板:',
|
|
275
|
+
choices: Object.keys(TEMPLATE_MAP)
|
|
276
|
+
}
|
|
277
|
+
]);
|
|
278
|
+
templateKey = answer.templateKey;
|
|
279
|
+
} else if (!Object.keys(TEMPLATE_MAP).includes(templateKey)) {
|
|
280
|
+
console.error(`❌ 未知模板: ${templateKey}`);
|
|
281
|
+
console.log(`可用模板: ${Object.keys(TEMPLATE_MAP).join(', ')}`);
|
|
282
|
+
process.exit(1);
|
|
327
283
|
}
|
|
328
284
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
name: 'description',
|
|
341
|
-
message: '项目描述:',
|
|
342
|
-
default: 'A new project'
|
|
343
|
-
}
|
|
344
|
-
]);
|
|
285
|
+
if (!description) {
|
|
286
|
+
const answer = await inquirer.prompt([
|
|
287
|
+
{
|
|
288
|
+
type: 'input',
|
|
289
|
+
name: 'description',
|
|
290
|
+
message: '项目描述:',
|
|
291
|
+
default: 'A new project'
|
|
292
|
+
}
|
|
293
|
+
]);
|
|
294
|
+
description = answer.description;
|
|
295
|
+
}
|
|
345
296
|
|
|
346
297
|
const targetPath = path.resolve(process.cwd(), projectName);
|
|
347
|
-
const sourceTemplatePath = TEMPLATE_MAP[
|
|
298
|
+
const sourceTemplatePath = TEMPLATE_MAP[templateKey];
|
|
348
299
|
|
|
349
300
|
const spinner = ora('正在复制模板文件...').start();
|
|
350
301
|
try {
|
|
351
302
|
await fs.copy(sourceTemplatePath, targetPath);
|
|
352
|
-
|
|
353
303
|
const pkgJsonPath = path.join(targetPath, 'package.json');
|
|
354
304
|
const pkg = await fs.readJson(pkgJsonPath);
|
|
355
|
-
pkg.description =
|
|
305
|
+
pkg.description = description;
|
|
356
306
|
await fs.writeJson(pkgJsonPath, pkg, { spaces: 2 });
|
|
357
|
-
|
|
358
307
|
spinner.succeed('✅ 项目创建成功!');
|
|
359
308
|
console.log(`
|
|
360
309
|
接下来执行命令:
|
|
361
|
-
cd ${projectName}
|
|
362
|
-
npm install
|
|
363
|
-
npm run dev
|
|
310
|
+
cd ${projectName}
|
|
311
|
+
npm install
|
|
312
|
+
npm run dev
|
|
364
313
|
`);
|
|
365
314
|
} catch (err) {
|
|
366
315
|
spinner.fail('❌ 创建项目失败');
|
|
367
316
|
console.error(err);
|
|
368
317
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ---------- add 命令 ----------
|
|
321
|
+
program
|
|
322
|
+
.command('add <type> <name>')
|
|
323
|
+
.description('添加组件或页面(type: component | page)')
|
|
324
|
+
.option('-f, --force', '强制覆盖已存在的文件')
|
|
325
|
+
.action(async (type, name, options) => {
|
|
326
|
+
if (!['component', 'page'].includes(type)) {
|
|
327
|
+
console.error('❌ 类型必须是 component 或 page');
|
|
328
|
+
process.exit(1);
|
|
379
329
|
}
|
|
380
330
|
|
|
381
|
-
// 定位项目根目录
|
|
382
331
|
const projectRoot = findProjectRoot(process.cwd());
|
|
383
332
|
const projectConfig = loadProjectConfig(projectRoot);
|
|
384
|
-
|
|
385
|
-
// 确定框架类型(用于模板选择)
|
|
386
333
|
const framework = projectConfig.framework || detectProjectType(projectRoot, projectConfig);
|
|
387
|
-
projectConfig.framework = framework;
|
|
334
|
+
projectConfig.framework = framework;
|
|
388
335
|
|
|
389
336
|
const spinner = ora('正在生成业务代码...').start();
|
|
390
337
|
try {
|
|
391
338
|
let result;
|
|
392
339
|
if (type === 'component') {
|
|
393
|
-
result = await generateComponentFiles(name, projectRoot, projectConfig, force);
|
|
340
|
+
result = await generateComponentFiles(name, projectRoot, projectConfig, options.force);
|
|
394
341
|
spinner.succeed(
|
|
395
342
|
`✅ 已生成组件文件: ${toPascalCase(name)}\n` +
|
|
396
343
|
` 位置: ${path.relative(projectRoot, result.baseDir)}\n` +
|
|
397
344
|
` 覆盖: ${result.overwrittenCount} 个文件,跳过: ${result.skippedCount} 个`
|
|
398
345
|
);
|
|
399
346
|
} else {
|
|
400
|
-
result = await generatePageFiles(name, projectRoot, projectConfig, force);
|
|
347
|
+
result = await generatePageFiles(name, projectRoot, projectConfig, options.force);
|
|
401
348
|
spinner.succeed(
|
|
402
349
|
`✅ 已生成页面文件: ${toPascalCase(name)}\n` +
|
|
403
350
|
` 位置: ${path.relative(projectRoot, result.baseDir)}\n` +
|
|
@@ -408,9 +355,15 @@ npm run dev
|
|
|
408
355
|
spinner.fail('❌ 生成业务代码失败');
|
|
409
356
|
console.error(err);
|
|
410
357
|
}
|
|
411
|
-
|
|
412
|
-
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
program.on('command:*', () => {
|
|
361
|
+
console.error('❌ 未知命令: %s', program.args.join(' '));
|
|
362
|
+
program.help();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
program.parse(process.argv);
|
|
413
366
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
367
|
+
if (!process.argv.slice(2).length) {
|
|
368
|
+
program.help();
|
|
369
|
+
}
|