create-unibest 3.3.0 → 4.0.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/README.md CHANGED
@@ -1,63 +1,79 @@
1
- <h1 align="center">✨create unibest✨</h1>
1
+ # create-unibest
2
2
 
3
- <p align="center">
4
- <a href="https://www.npmjs.com/package/create-unibest"><img src="https://img.shields.io/npm/dm/create-unibest?colorA=363a4f&colorB=f5a97f&style=for-the-badge"></a>
5
- <a href="https://www.npmjs.com/package/create-unibest"><img src="https://img.shields.io/npm/v/create-unibest?colorA=363a4f&colorB=a6da95&style=for-the-badge"></a>
6
- </p>
3
+ 跨平台 uniapp 项目脚手架工具。
7
4
 
8
- <h2 align="center">
9
- <sub>>_ Easy to use create unibest</sub>
10
- </h2>
5
+ ## 功能特性
11
6
 
12
- ## 📖 介绍
7
+ - 🚀 **快速创建** - 通过命令行快速生成 unibest 项目
8
+ - 🎯 **灵活配置** - 支持选择平台、UI库、Feature
9
+ - 🔧 **Feature 注入** - 创建后可动态添加 i18n、login 等功能
10
+ - 📦 **多平台支持** - H5、小程序、App 一键生成
13
11
 
14
- `create-unibest` 是一个用于快速创建 `unibest` 项目的轻量脚手架工具,它可以帮助你快速创建一个基于 `vite` + `vue3` + `TS` 的 `uni-app` 项目,同时提供了一些模板供你选择。
12
+ ## 使用方式
15
13
 
16
- ## 🚤 快速使用
14
+ ### 安装
17
15
 
18
- ```shell
19
- pnpm create unibest <command> [options] # 基本命令格式
20
- pnpm create unibest my-project # 创建新的unibest项目
21
- pnpm create unibest -v # 查看版本信息
22
- pnpm create unibest -h # 查看帮助信息
23
- ```
16
+ ```bash
17
+ # 全局安装(可选)
18
+ npm i -g create-unibest
24
19
 
25
- ### 命令行参数
20
+ # 或直接使用
21
+ pnpm create unibest
22
+ ```
26
23
 
27
- 支持通过命令行参数跳过交互式询问,实现静默/快捷创建。
24
+ ### 创建项目
28
25
 
29
- | 参数 | 简写 | 说明 | 可选值 / 示例 |
30
- | :----------- | :--- | :----------- | :----------------------------------------------------------------------------------------------------- |
31
- | `--platform` | `-p` | 指定平台 | `h5`, `mp-weixin`, `app`, `mp-alipay`, `mp-toutiao`<br>示例: `-p h5,mp-weixin` 或 `-p h5 -p mp-weixin` |
32
- | `--ui` | `-u` | 指定 UI 库 | `wot-ui`, `uview-pro`, `sard-uniapp`, `uv-ui`, `uview-plus`, `none` |
33
- | `--login` | `-l` | 启用登录策略 | 无值,存在即开启 |
34
- | `--i18n` | `-i` | 启用多语言 | 无值,存在即开启 |
26
+ ```bash
27
+ # 交互式创建
28
+ pnpm create unibest my-project
29
+
30
+ # 命令行参数创建
31
+ pnpm create unibest my-project \
32
+ --platform h5,mp-weixin \
33
+ --ui none \
34
+ --i18n \
35
+ --login
36
+ ```
35
37
 
36
- #### 示例
38
+ ### 添加 Feature
37
39
 
38
40
  ```bash
39
- # 1. 基础用法:指定 UI 库和平台(H5 + 微信小程序)
40
- pnpm create unibest my-project -u wot-ui -p h5,mp-weixin
41
+ cd my-project
42
+
43
+ # 添加多语言
44
+ pnpm create unibest add i18n
41
45
 
42
- # 2. 进阶用法:指定 UI 库,并开启登录策略和多语言
43
- pnpm create unibest my-project -u uview-plus -l -i
46
+ # 添加登录策略
47
+ pnpm create unibest add login
44
48
 
45
- # 3. 极简用法:不使用 UI 库,但支持多端(H5 + App + 微信小程序)
46
- pnpm create unibest my-project -u none -p h5,app,mp-weixin
49
+ # 强制重新注入(覆盖已有配置)
50
+ pnpm create unibest add i18n --force
47
51
  ```
48
52
 
49
- ### 全局安装(可选)
53
+ ## 参数说明
50
54
 
51
- ```shell
52
- npm i -g create-unibest # 全局安装,得到 best 命令
53
- npm update -g create-unibest # 更新 create-unibest 包
54
- ```
55
+ ### 创建项目参数
55
56
 
56
- 安装后可使用的命令:
57
+ | 参数 | 说明 |
58
+ |------|------|
59
+ | `-p, --platform` | 平台类型,支持 `h5`, `mp-weixin`, `app`, `mp-alipay`, `mp-toutiao` |
60
+ | `-u, --ui` | UI 库,支持 `none`, `wot-ui`, `uview-pro`, `sard-uniapp`, `uv-ui`, `uview-plus` |
61
+ | `-l, --login` | 启用登录策略 |
62
+ | `-i, --i18n` | 启用多语言 |
57
63
 
58
- ```shell
59
- best <command> [options] # 基本命令格式
60
- best my-project # 创建新的unibest项目
61
- best -v # 查看版本信息
62
- best -h # 查看帮助信息
63
- ```
64
+ ### 添加 Feature 参数
65
+
66
+ | 参数 | 说明 |
67
+ |------|------|
68
+ | `--path` | 项目路径,默认当前目录 |
69
+ | `--force` | 强制重新注入 Feature |
70
+
71
+ ## 与 unibest 模板版本对应关系
72
+
73
+ | create-unibest 版本 | unibest 模板版本 |
74
+ |---------------------|------------------|
75
+ | 4.0.0 | 4.3.0+ |
76
+
77
+ ## License
78
+
79
+ MIT
package/bin/index.js CHANGED
@@ -6,7 +6,6 @@ import { fileURLToPath } from 'node:url'
6
6
  const __filename = fileURLToPath(import.meta.url)
7
7
  const __dirname = dirname(__filename)
8
8
 
9
- // 导入主程序
10
9
  import('../dist/index.js').catch((err) => {
11
10
  console.error('Failed to load CLI:', err)
12
11
  process.exit(1)
package/dist/index.js CHANGED
@@ -1,11 +1,254 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import process5 from "process";
4
+ import process6 from "process";
5
+ import { green as green6, yellow as yellow5 } from "kolorist";
5
6
  import minimist from "minimist";
6
7
 
7
- // src/commands/create/prompts.ts
8
- import { text, multiselect, select, confirm, cancel, isCancel } from "@clack/prompts";
8
+ // package.json
9
+ var version = "4.0.0";
10
+ var package_default = {
11
+ name: "create-unibest",
12
+ type: "module",
13
+ version,
14
+ packageManager: "pnpm@10.10.0",
15
+ description: "\u5FEB\u901F\u521B\u5EFA unibest \u9879\u76EE\u7684\u811A\u624B\u67B6\u5DE5\u5177",
16
+ author: "feige996",
17
+ license: "MIT",
18
+ homepage: "https://unibest.tech",
19
+ bin: {
20
+ best: "bin/index.js",
21
+ "create-unibest": "bin/index.js",
22
+ unibest: "bin/index.js"
23
+ },
24
+ files: [
25
+ "bin",
26
+ "dist"
27
+ ],
28
+ scripts: {
29
+ dev: "cross-env NODE_ENV=development tsup --watch",
30
+ build: "cross-env NODE_ENV=production tsup",
31
+ prepare: "pnpm build",
32
+ start: "cross-env NODE_ENV=development node bin/index.js"
33
+ },
34
+ dependencies: {
35
+ "@clack/prompts": "^0.11.0",
36
+ dayjs: "^1.11.18",
37
+ ejs: "^3.1.10",
38
+ "fs-extra": "^11.3.0",
39
+ kolorist: "^1.8.0",
40
+ minimist: "^1.2.8",
41
+ "node-fetch": "^3.3.2"
42
+ },
43
+ devDependencies: {
44
+ "@types/ejs": "^3.1.5",
45
+ "@types/fs-extra": "^11.0.4",
46
+ "@types/minimist": "^1.2.5",
47
+ "@types/node": "^24.5.0",
48
+ "cross-env": "^7.0.3",
49
+ tsup: "^8.5.0",
50
+ typescript: "^5.9.0"
51
+ }
52
+ };
53
+
54
+ // src/commands/add.ts
55
+ import fs4 from "fs";
56
+ import path3 from "path";
57
+ import process2 from "process";
58
+ import { cancel, intro, isCancel, log, multiselect } from "@clack/prompts";
59
+ import { bold as bold2, green as green2 } from "kolorist";
60
+
61
+ // src/features/interface.ts
62
+ var AVAILABLE_FEATURES = [
63
+ {
64
+ name: "i18n",
65
+ description: "\u591A\u8BED\u8A00\u652F\u6301",
66
+ dependencies: {
67
+ "vue-i18n": "^9.0.0",
68
+ "dayjs": "^1.11.0"
69
+ }
70
+ },
71
+ {
72
+ name: "login",
73
+ description: "\u767B\u5F55\u7B56\u7565\uFF08\u9ED1\u767D\u540D\u5355\u3001\u767B\u5F55\u62E6\u622A\u7B49\uFF09",
74
+ dependencies: {}
75
+ }
76
+ ];
77
+ function getFeatureByName(name) {
78
+ return AVAILABLE_FEATURES.find((f) => f.name === name);
79
+ }
80
+ function getSelectedFeatures(options) {
81
+ const features = [];
82
+ if (options.i18n) {
83
+ const feature = getFeatureByName("i18n");
84
+ if (feature) features.push(feature);
85
+ }
86
+ if (options.loginStrategy) {
87
+ const feature = getFeatureByName("login");
88
+ if (feature) features.push(feature);
89
+ }
90
+ return features;
91
+ }
92
+
93
+ // src/features/loader.ts
94
+ import fs from "fs";
95
+ import path from "path";
96
+ var FEATURES_DIR = path.resolve(process.cwd(), "../../features");
97
+ async function loadFeatureHooks(featureName) {
98
+ const hooksPath = path.join(FEATURES_DIR, featureName, "hooks.ts");
99
+ if (!fs.existsSync(hooksPath)) {
100
+ return null;
101
+ }
102
+ const module = await import(hooksPath);
103
+ return module;
104
+ }
105
+ function getAvailableFeatureNames() {
106
+ if (!fs.existsSync(FEATURES_DIR)) {
107
+ return [];
108
+ }
109
+ const entries = fs.readdirSync(FEATURES_DIR, { withFileTypes: true });
110
+ return entries.filter((e) => e.isDirectory() && fs.existsSync(path.join(FEATURES_DIR, e.name, "files"))).map((e) => e.name);
111
+ }
112
+
113
+ // src/utils/injector.ts
114
+ import fs2 from "fs";
115
+ import path2 from "path";
116
+ import { dirname } from "path";
117
+ import { fileURLToPath } from "url";
118
+ var __filename = fileURLToPath(import.meta.url);
119
+ var __dirname = dirname(__filename);
120
+ var FEATURES_PATH = path2.join(__dirname, "..", "..", "..", "features");
121
+ var FeatureInjector = class {
122
+ constructor(projectPath) {
123
+ this.projectPath = projectPath;
124
+ }
125
+ injectFile(targetPath, featureCode, placeholder) {
126
+ const fullPath = path2.join(this.projectPath, targetPath);
127
+ if (!fs2.existsSync(fullPath)) {
128
+ return { success: false, message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${targetPath}` };
129
+ }
130
+ const content = fs2.readFileSync(fullPath, "utf-8");
131
+ if (!content.includes(placeholder)) {
132
+ return { success: false, message: `\u5360\u4F4D\u7B26\u4E0D\u5B58\u5728: ${placeholder}` };
133
+ }
134
+ const newContent = content.replace(placeholder, featureCode);
135
+ fs2.writeFileSync(fullPath, newContent);
136
+ return { success: true, message: `\u6CE8\u5165\u6210\u529F: ${targetPath}` };
137
+ }
138
+ replaceFile(targetPath, featureFilePath) {
139
+ const targetFullPath = path2.join(this.projectPath, targetPath);
140
+ const featureFullPath = path2.join(FEATURES_PATH, featureFilePath);
141
+ if (!fs2.existsSync(featureFullPath)) {
142
+ return { success: false, message: `Feature \u6587\u4EF6\u4E0D\u5B58\u5728: ${featureFilePath}` };
143
+ }
144
+ const featureContent = fs2.readFileSync(featureFullPath, "utf-8");
145
+ fs2.writeFileSync(targetFullPath, featureContent);
146
+ return { success: true, message: `\u66FF\u6362\u6210\u529F: ${targetPath}` };
147
+ }
148
+ createFile(relativePath, featureFilePath) {
149
+ const targetFullPath = path2.join(this.projectPath, relativePath);
150
+ const featureFullPath = path2.join(FEATURES_PATH, featureFilePath);
151
+ if (!fs2.existsSync(featureFullPath)) {
152
+ return { success: false, message: `Feature \u6587\u4EF6\u4E0D\u5B58\u5728: ${featureFilePath}` };
153
+ }
154
+ const dir = path2.dirname(targetFullPath);
155
+ if (!fs2.existsSync(dir)) {
156
+ fs2.mkdirSync(dir, { recursive: true });
157
+ }
158
+ const featureContent = fs2.readFileSync(featureFullPath, "utf-8");
159
+ fs2.writeFileSync(targetFullPath, featureContent);
160
+ return { success: true, message: `\u521B\u5EFA\u6210\u529F: ${relativePath}` };
161
+ }
162
+ appendAfter(targetPath, marker, code) {
163
+ const fullPath = path2.join(this.projectPath, targetPath);
164
+ if (!fs2.existsSync(fullPath)) {
165
+ return { success: false, message: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${targetPath}` };
166
+ }
167
+ const content = fs2.readFileSync(fullPath, "utf-8");
168
+ if (!content.includes(marker)) {
169
+ return { success: false, message: `\u6807\u8BB0\u4E0D\u5B58\u5728: ${marker}` };
170
+ }
171
+ const newContent = content.replace(marker, `${marker}
172
+ ${code}`);
173
+ fs2.writeFileSync(fullPath, newContent);
174
+ return { success: true, message: `\u8FFD\u52A0\u6210\u529F: ${targetPath}` };
175
+ }
176
+ };
177
+ async function injectI18n(projectPath) {
178
+ const results = [];
179
+ const injector = new FeatureInjector(projectPath);
180
+ results.push(injector.appendAfter(
181
+ "src/main.ts",
182
+ `import 'virtual:uno.css'`,
183
+ `import i18n from './locale/index'`
184
+ ));
185
+ results.push(injector.appendAfter(
186
+ "src/main.ts",
187
+ ` app.use(requestInterceptor)`,
188
+ ` app.use(i18n)`
189
+ ));
190
+ results.push(injector.replaceFile(
191
+ "src/tabbar/config.ts",
192
+ "i18n/files/src/tabbar/config.ts"
193
+ ));
194
+ results.push(injector.replaceFile(
195
+ "src/tabbar/index.vue",
196
+ "i18n/files/src/tabbar/index.vue"
197
+ ));
198
+ results.push(injector.replaceFile(
199
+ "src/tabbar/TabbarItem.vue",
200
+ "i18n/files/src/tabbar/TabbarItem.vue"
201
+ ));
202
+ results.push(injector.replaceFile(
203
+ "src/utils/index.ts",
204
+ "i18n/files/src/utils/index.ts"
205
+ ));
206
+ results.push(injector.replaceFile(
207
+ "src/store/token.ts",
208
+ "i18n/files/src/store/token.ts"
209
+ ));
210
+ const i18nFiles = [
211
+ "src/locale/index.ts",
212
+ "src/locale/en.json",
213
+ "src/locale/zh-Hans.json",
214
+ "src/locale/README.md",
215
+ "src/utils/i18n.ts",
216
+ "src/types/i18n.d.ts",
217
+ "src/tabbar/i18n.ts",
218
+ "src/pages/i18n/index.vue"
219
+ ];
220
+ for (const file of i18nFiles) {
221
+ const featurePath = `i18n/files/${file}`;
222
+ results.push(injector.createFile(file, featurePath));
223
+ }
224
+ return results;
225
+ }
226
+ async function injectLogin(projectPath) {
227
+ const results = [];
228
+ const injector = new FeatureInjector(projectPath);
229
+ results.push(injector.replaceFile(
230
+ "src/router/interceptor.ts",
231
+ "login/files/src/router/interceptor.ts"
232
+ ));
233
+ results.push(injector.replaceFile(
234
+ "src/router/config.ts",
235
+ "login/files/src/router/config.ts"
236
+ ));
237
+ results.push(injector.replaceFile(
238
+ "src/pages/me/me.vue",
239
+ "login/files/src/pages/me.vue"
240
+ ));
241
+ const loginFiles = [
242
+ "src/pages/auth/login.vue",
243
+ "src/pages/auth/register.vue",
244
+ "src/pages/auth/README.md"
245
+ ];
246
+ for (const file of loginFiles) {
247
+ const featurePath = `login/files/${file}`;
248
+ results.push(injector.createFile(file, featurePath));
249
+ }
250
+ return results;
251
+ }
9
252
 
10
253
  // src/utils/logger.ts
11
254
  import { bold, green, red, yellow, cyan } from "kolorist";
@@ -32,6 +275,178 @@ var logger = {
32
275
  }
33
276
  };
34
277
 
278
+ // src/utils/readPackageJson.ts
279
+ import fs3 from "fs";
280
+ function readPackageJson(pkgPath) {
281
+ if (!fs3.existsSync(pkgPath)) {
282
+ throw new Error(`package.json not found: ${pkgPath}`);
283
+ }
284
+ const content = fs3.readFileSync(pkgPath, "utf-8");
285
+ return JSON.parse(content);
286
+ }
287
+ function writePackageJson(pkgPath, pkg) {
288
+ fs3.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
289
+ `);
290
+ }
291
+
292
+ // src/commands/add.ts
293
+ function getFeatureStatusFromPackageJson(pkgPath) {
294
+ try {
295
+ const pkg = readPackageJson(pkgPath);
296
+ return {
297
+ i18n: pkg.unibest?.i18n === true,
298
+ login: pkg.unibest?.loginStrategy === true
299
+ };
300
+ } catch {
301
+ return { i18n: false, login: false };
302
+ }
303
+ }
304
+ function updatePackageJsonForFeature(pkgPath, featureName) {
305
+ const pkg = readPackageJson(pkgPath);
306
+ if (!pkg.unibest) {
307
+ pkg.unibest = {};
308
+ }
309
+ switch (featureName) {
310
+ case "i18n":
311
+ pkg.unibest.i18n = true;
312
+ break;
313
+ case "login":
314
+ pkg.unibest.loginStrategy = true;
315
+ break;
316
+ }
317
+ writePackageJson(pkgPath, pkg);
318
+ }
319
+ async function checkFeatureStatus(projectPath) {
320
+ const pkgPath = path3.join(projectPath, "package.json");
321
+ const statusFromPkg = getFeatureStatusFromPackageJson(pkgPath);
322
+ return [
323
+ { name: "i18n", enabled: statusFromPkg.i18n },
324
+ { name: "login", enabled: statusFromPkg.login }
325
+ ];
326
+ }
327
+ async function addFeature(featureName, projectPath, options = {}) {
328
+ const feature = getFeatureByName(featureName);
329
+ if (!feature) {
330
+ logger.error(`\u672A\u77E5\u7684 Feature: ${featureName}`);
331
+ return false;
332
+ }
333
+ const pkgPath = path3.join(projectPath, "package.json");
334
+ const pkg = readPackageJson(pkgPath);
335
+ let alreadyAdded = false;
336
+ if (featureName === "i18n" && pkg.unibest?.i18n === true) {
337
+ alreadyAdded = true;
338
+ } else if (featureName === "login" && pkg.unibest?.loginStrategy === true) {
339
+ alreadyAdded = true;
340
+ }
341
+ if (alreadyAdded && !options.force) {
342
+ logger.warn(`Feature ${featureName} \u5DF2\u6DFB\u52A0\u8FC7\uFF0C\u5982\u9700\u91CD\u65B0\u6CE8\u5165\u8BF7\u4F7F\u7528 --force \u53C2\u6570`);
343
+ return true;
344
+ }
345
+ log.info(`\u6B63\u5728\u6DFB\u52A0 Feature: ${green2(featureName)} - ${feature.description}`);
346
+ try {
347
+ let results;
348
+ switch (featureName) {
349
+ case "i18n":
350
+ results = await injectI18n(projectPath);
351
+ break;
352
+ case "login":
353
+ results = await injectLogin(projectPath);
354
+ break;
355
+ default:
356
+ logger.error(`\u4E0D\u652F\u6301\u7684 Feature: ${featureName}`);
357
+ return false;
358
+ }
359
+ for (const result of results) {
360
+ if (result.success) {
361
+ logger.success(result.message);
362
+ } else {
363
+ logger.warn(result.message);
364
+ }
365
+ }
366
+ const hooks = await loadFeatureHooks(featureName);
367
+ if (hooks?.postApply) {
368
+ await hooks.postApply({
369
+ options: { projectName: "", platforms: [], uiLibrary: "none", i18n: true, loginStrategy: true },
370
+ projectPath,
371
+ featureName
372
+ });
373
+ }
374
+ updatePackageJsonForFeature(pkgPath, featureName);
375
+ logger.success(`\u5DF2\u66F4\u65B0 package.json \u7684 unibest \u914D\u7F6E`);
376
+ if (feature.dependencies && Object.keys(feature.dependencies).length > 0) {
377
+ logger.info(`\u5B89\u88C5\u4F9D\u8D56: ${Object.keys(feature.dependencies).join(", ")}`);
378
+ }
379
+ logger.success(`Feature ${featureName} \u6DFB\u52A0\u6210\u529F\uFF01`);
380
+ return true;
381
+ } catch (error) {
382
+ logger.error(`\u6DFB\u52A0 Feature \u5931\u8D25: ${error.message}`);
383
+ return false;
384
+ }
385
+ }
386
+ async function addCommand(args) {
387
+ const options = {
388
+ path: args.path || args.p || ".",
389
+ feature: args._[1] || args.feature || args.f,
390
+ force: args.force || args.f
391
+ };
392
+ intro(bold2(green2(`create-unibest@v${version} \u6DFB\u52A0 Feature`)));
393
+ const projectPath = path3.isAbsolute(options.path) ? options.path : path3.join(process2.cwd(), options.path);
394
+ const pkgPath = path3.join(projectPath, "package.json");
395
+ if (!fs4.existsSync(pkgPath)) {
396
+ logger.error(`\u9879\u76EE\u4E0D\u5B58\u5728: ${projectPath}`);
397
+ process2.exit(1);
398
+ }
399
+ const pkg = readPackageJson(pkgPath);
400
+ if (pkg.name !== "unibest") {
401
+ logger.warn(`\u5F53\u524D\u9879\u76EE\u53EF\u80FD\u4E0D\u662F unibest \u9879\u76EE: ${pkg.name}`);
402
+ }
403
+ const availableFeatures = getAvailableFeatureNames();
404
+ try {
405
+ if (!options.feature) {
406
+ const detected = await checkFeatureStatus(projectPath);
407
+ const available = availableFeatures.filter((f) => !detected.find((d) => d.name === f && d.enabled));
408
+ if (available.length === 0) {
409
+ logger.info("\u6240\u6709\u53EF\u7528 Feature \u5DF2\u542F\u7528");
410
+ return;
411
+ }
412
+ const selectedFeatures = await multiselect({
413
+ message: `\u8BF7\u9009\u62E9\u8981\u6DFB\u52A0\u7684 Feature`,
414
+ options: available.map((name) => {
415
+ const feature = getFeatureByName(name);
416
+ return {
417
+ value: name,
418
+ label: feature?.name || name,
419
+ hint: feature?.description || ""
420
+ };
421
+ }),
422
+ required: false
423
+ });
424
+ if (isCancel(selectedFeatures)) {
425
+ cancel("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
426
+ process2.exit(0);
427
+ }
428
+ if (!Array.isArray(selectedFeatures) || selectedFeatures.length === 0) {
429
+ logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Feature");
430
+ return;
431
+ }
432
+ for (const featureName of selectedFeatures) {
433
+ await addFeature(featureName, projectPath, options);
434
+ }
435
+ } else {
436
+ const features = Array.isArray(options.feature) ? options.feature : [options.feature];
437
+ for (const featureName of features) {
438
+ await addFeature(featureName, projectPath, options);
439
+ }
440
+ }
441
+ } catch (error) {
442
+ logger.error(`\u6DFB\u52A0 Feature \u5931\u8D25: ${error.message}`);
443
+ process2.exit(1);
444
+ }
445
+ }
446
+
447
+ // src/commands/create/prompts.ts
448
+ import { text, multiselect as multiselect2, select, confirm, cancel as cancel2, isCancel as isCancel2 } from "@clack/prompts";
449
+
35
450
  // src/utils/validate.ts
36
451
  import { existsSync } from "fs";
37
452
  import { yellow as yellow2 } from "kolorist";
@@ -58,8 +473,8 @@ function checkProjectNameExistAndValidate(_projectName) {
58
473
  }
59
474
 
60
475
  // src/commands/create/prompts.ts
61
- import { green as green2, red as red2 } from "kolorist";
62
- import process2 from "process";
476
+ import { green as green3, red as red2 } from "kolorist";
477
+ import process3 from "process";
63
478
  var VALID_PLATFORMS = ["h5", "mp-weixin", "app", "mp-alipay", "mp-toutiao"];
64
479
  var VALID_UI_LIBRARIES = ["none", "wot-ui", "uview-pro", "sard-uniapp", "uv-ui", "uview-plus"];
65
480
  async function promptUser(projectName, argv = {}) {
@@ -79,7 +494,7 @@ async function promptUser(projectName, argv = {}) {
79
494
  if (invalidPlatforms.length > 0) {
80
495
  console.error(red2(`\u65E0\u6548\u7684\u5E73\u53F0\u53C2\u6570: ${invalidPlatforms.join(", ")}`));
81
496
  console.error(red2(`\u53EF\u9009\u503C: ${VALID_PLATFORMS.join(", ")}`));
82
- process2.exit(1);
497
+ process3.exit(1);
83
498
  }
84
499
  platforms = parsedPlatforms;
85
500
  }
@@ -88,20 +503,26 @@ async function promptUser(projectName, argv = {}) {
88
503
  if (!VALID_UI_LIBRARIES.includes(uiArg)) {
89
504
  console.error(red2(`\u65E0\u6548\u7684UI\u5E93\u53C2\u6570: ${uiArg}`));
90
505
  console.error(red2(`\u53EF\u9009\u503C: ${VALID_UI_LIBRARIES.join(", ")}`));
91
- process2.exit(1);
506
+ process3.exit(1);
92
507
  }
93
508
  uiLibrary = uiArg;
94
509
  }
95
- if (argv.l || argv.login) {
510
+ const loginArg = argv.l ?? argv.login;
511
+ if (loginArg === true || loginArg === "true") {
96
512
  loginStrategy = true;
513
+ } else if (loginArg === false || loginArg === "false") {
514
+ loginStrategy = false;
97
515
  }
98
- if (argv.i || argv.i18n) {
516
+ const i18nArg = argv.i ?? argv.i18n;
517
+ if (i18nArg === true || i18nArg === "true") {
99
518
  i18n = true;
519
+ } else if (i18nArg === false || i18nArg === "false") {
520
+ i18n = false;
100
521
  }
101
522
  try {
102
523
  if (!projectName) {
103
524
  const inputProjectName = await text({
104
- message: `\u8BF7\u8F93\u5165\u9879\u76EE\u540D\u79F0${green2("[\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF\uFF0C\u5343\u4E07\u522B\u5199\u4E2D\u6587]")}`,
525
+ message: `\u8BF7\u8F93\u5165\u9879\u76EE\u540D\u79F0${green3("[\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF\uFF0C\u5343\u4E07\u522B\u5199\u4E2D\u6587]")}`,
105
526
  initialValue: "",
106
527
  validate: (value) => {
107
528
  if (!value.trim()) return "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
@@ -110,15 +531,15 @@ async function promptUser(projectName, argv = {}) {
110
531
  return;
111
532
  }
112
533
  });
113
- if (isCancel(inputProjectName)) {
114
- cancel("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
115
- process2.exit(0);
534
+ if (isCancel2(inputProjectName)) {
535
+ cancel2("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
536
+ process3.exit(0);
116
537
  }
117
538
  projectName = inputProjectName;
118
539
  }
119
540
  if (!platforms) {
120
- const selectedPlatforms = await multiselect({
121
- message: `\u8BF7\u9009\u62E9\u9700\u8981\u652F\u6301\u7684\u5E73\u53F0\uFF08\u591A\u9009\uFF09${green2("[\u811A\u624B\u67B6\u5C06\u6839\u636E\u6240\u9009\u5E73\u53F0\u751F\u6210\u5BF9\u5E94\u7684\u5E73\u53F0\u4EE3\u7801\uFF0C\u8BF7\u6839\u636E\u5B9E\u9645\u60C5\u51B5\u9009\u62E9]")}`,
541
+ const selectedPlatforms = await multiselect2({
542
+ message: `\u8BF7\u9009\u62E9\u9700\u8981\u652F\u6301\u7684\u5E73\u53F0\uFF08\u591A\u9009\uFF09${green3("[\u811A\u624B\u67B6\u5C06\u6839\u636E\u6240\u9009\u5E73\u53F0\u751F\u6210\u5BF9\u5E94\u7684\u5E73\u53F0\u4EE3\u7801\uFF0C\u8BF7\u6839\u636E\u5B9E\u9645\u60C5\u51B5\u9009\u62E9]")}`,
122
543
  options: [
123
544
  { value: "h5", label: "H5" },
124
545
  { value: "mp-weixin", label: "\u5FAE\u4FE1\u5C0F\u7A0B\u5E8F" },
@@ -130,9 +551,9 @@ async function promptUser(projectName, argv = {}) {
130
551
  // 默认选择 H5
131
552
  required: true
132
553
  });
133
- if (isCancel(selectedPlatforms)) {
134
- cancel("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
135
- process2.exit(0);
554
+ if (isCancel2(selectedPlatforms)) {
555
+ cancel2("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
556
+ process3.exit(0);
136
557
  }
137
558
  platforms = selectedPlatforms;
138
559
  }
@@ -149,20 +570,20 @@ async function promptUser(projectName, argv = {}) {
149
570
  ],
150
571
  initialValue: "none"
151
572
  });
152
- if (isCancel(selectedUiLibrary)) {
153
- cancel("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
154
- process2.exit(0);
573
+ if (isCancel2(selectedUiLibrary)) {
574
+ cancel2("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
575
+ process3.exit(0);
155
576
  }
156
577
  uiLibrary = selectedUiLibrary;
157
578
  }
158
579
  if (loginStrategy === void 0) {
159
580
  const selectedLoginStrategy = await confirm({
160
- message: `\u662F\u5426\u9700\u8981\u767B\u5F55\u7B56\u7565\uFF08\u9ED1\u767D\u540D\u5355\u3001\u767B\u5F55\u62E6\u622A\u7B49\uFF09\uFF1F${green2("[\u6682\u4E0D\u77E5\u9053\u7684\uFF0C\u9009No\u5373\u53EF\uFF0C\u9879\u76EE\u751F\u6210\u540E\u4E5F\u53EF\u4EE5\u52A0\u8BE5\u7B56\u7565]")}`,
581
+ message: `\u662F\u5426\u9700\u8981\u767B\u5F55\u7B56\u7565\uFF08\u9ED1\u767D\u540D\u5355\u3001\u767B\u5F55\u62E6\u622A\u7B49\uFF09\uFF1F${green3("[\u6682\u4E0D\u77E5\u9053\u7684\uFF0C\u9009No\u5373\u53EF\uFF0C\u9879\u76EE\u751F\u6210\u540E\u4E5F\u53EF\u4EE5\u52A0\u8BE5\u7B56\u7565]")}`,
161
582
  initialValue: false
162
583
  });
163
- if (isCancel(selectedLoginStrategy)) {
164
- cancel("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
165
- process2.exit(0);
584
+ if (isCancel2(selectedLoginStrategy)) {
585
+ cancel2("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
586
+ process3.exit(0);
166
587
  }
167
588
  loginStrategy = selectedLoginStrategy;
168
589
  }
@@ -171,9 +592,9 @@ async function promptUser(projectName, argv = {}) {
171
592
  message: "\u662F\u5426\u9700\u8981\u591A\u8BED\u8A00i18n\uFF1F",
172
593
  initialValue: false
173
594
  });
174
- if (isCancel(selectedI18n)) {
175
- cancel("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
176
- process2.exit(0);
595
+ if (isCancel2(selectedI18n)) {
596
+ cancel2("\u64CD\u4F5C\u5DF2\u53D6\u6D88");
597
+ process3.exit(0);
177
598
  }
178
599
  i18n = selectedI18n;
179
600
  }
@@ -189,73 +610,25 @@ async function promptUser(projectName, argv = {}) {
189
610
  };
190
611
  } catch (error) {
191
612
  logger.error(`\u8BE2\u95EE\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
192
- process2.exit(1);
613
+ process3.exit(1);
193
614
  }
194
615
  }
195
616
 
196
617
  // src/commands/create/generate.ts
197
- import process4 from "process";
198
- import { log } from "@clack/prompts";
618
+ import process5 from "process";
619
+ import { log as log2 } from "@clack/prompts";
199
620
 
200
621
  // src/utils/cloneRepo.ts
201
622
  import { exec } from "child_process";
202
- import { promises as fs } from "fs";
203
- import { join as join3 } from "path";
204
- import process3 from "process";
623
+ import { promises as fsPromises } from "fs";
624
+ import { join as join3, dirname as dirname2 } from "path";
625
+ import { fileURLToPath as fileURLToPath2 } from "url";
626
+ import process4 from "process";
205
627
  import { red as red3 } from "kolorist";
206
628
 
207
629
  // src/utils/replacePackageJson.ts
208
630
  import { readFileSync, writeFileSync } from "fs";
209
631
  import { join as join2 } from "path";
210
-
211
- // package.json
212
- var version = "3.3.0";
213
- var package_default = {
214
- name: "create-unibest",
215
- type: "module",
216
- version,
217
- updateTime: "2026-01-19",
218
- packageManager: "pnpm@9.0.0",
219
- description: "\u5FEB\u901F\u521B\u5EFAunibest\u9879\u76EE\u7684\u811A\u624B\u67B6\u5DE5\u5177",
220
- author: "",
221
- license: "ISC",
222
- keywords: [],
223
- main: "dist/index.js",
224
- bin: {
225
- best: "bin/index.js",
226
- "create-unibest": "bin/index.js"
227
- },
228
- files: [
229
- "bin",
230
- "dist"
231
- ],
232
- scripts: {
233
- dev: "cross-env NODE_ENV=development tsup --watch",
234
- build: "cross-env NODE_ENV=production tsup",
235
- prepare: "cross-env NODE_ENV=production npm run build",
236
- start: "cross-env NODE_ENV=development node bin/index.js"
237
- },
238
- dependencies: {
239
- "@clack/prompts": "^0.11.0",
240
- dayjs: "^1.11.18",
241
- ejs: "^3.1.10",
242
- "fs-extra": "^11.3.0",
243
- kolorist: "^1.8.0",
244
- minimist: "^1.2.8",
245
- "node-fetch": "^3.3.2"
246
- },
247
- devDependencies: {
248
- "@types/ejs": "^3.1.5",
249
- "@types/fs-extra": "^11.0.4",
250
- "@types/minimist": "^1.2.5",
251
- "@types/node": "^24.5.0",
252
- "cross-env": "^7.0.3",
253
- tsup: "^8.5.0",
254
- typescript: "^5.9.0"
255
- }
256
- };
257
-
258
- // src/utils/replacePackageJson.ts
259
632
  import dayjs from "dayjs";
260
633
  function replaceContent(filePath, projectName, version2, options) {
261
634
  const fileContent = JSON.parse(readFileSync(filePath, "utf8"));
@@ -267,6 +640,21 @@ function replaceContent(filePath, projectName, version2, options) {
267
640
  delete fileContent.name;
268
641
  delete fileContent.version;
269
642
  const { projectName: _, ...restOptions } = options;
643
+ const selectedFeatures = getSelectedFeatures(options);
644
+ const featureDeps = {};
645
+ for (const feature of selectedFeatures) {
646
+ if (feature.dependencies) {
647
+ Object.assign(featureDeps, feature.dependencies);
648
+ }
649
+ }
650
+ if (!fileContent.dependencies) {
651
+ fileContent.dependencies = {};
652
+ }
653
+ for (const [pkg, ver] of Object.entries(featureDeps)) {
654
+ if (!fileContent.dependencies[pkg]) {
655
+ fileContent.dependencies[pkg] = ver;
656
+ }
657
+ }
270
658
  const newContent = {
271
659
  name: projectName,
272
660
  type: fileContent.type,
@@ -291,9 +679,28 @@ function replacePackageJson(root2, name, version2, options) {
291
679
  }
292
680
 
293
681
  // src/utils/cloneRepo.ts
682
+ var __filename2 = fileURLToPath2(import.meta.url);
683
+ var __dirname2 = dirname2(__filename2);
684
+ var USE_LOCAL_TEMPLATE = process4.env.LOCAL_TEMPLATE === "true";
685
+ var TEMPLATE_BASE_PATH = null;
686
+ async function getTemplateBasePath() {
687
+ if (TEMPLATE_BASE_PATH) return TEMPLATE_BASE_PATH;
688
+ const candidates = [
689
+ join3(__dirname2, "..", "..", "..", "packages", "template-base"),
690
+ join3(__dirname2, "..", "..", "packages", "template-base")
691
+ ];
692
+ const { existsSync: existsSync3 } = await import("fs");
693
+ for (const candidate of candidates) {
694
+ if (existsSync3(candidate)) {
695
+ TEMPLATE_BASE_PATH = candidate;
696
+ return candidate;
697
+ }
698
+ }
699
+ throw new Error("\u65E0\u6CD5\u627E\u5230 template-base \u76EE\u5F55");
700
+ }
294
701
  async function removeGitFolder(localPath) {
295
702
  const gitFolderPath = join3(localPath, ".git");
296
- await fs.rm(gitFolderPath, { recursive: true, force: true });
703
+ await fsPromises.rm(gitFolderPath, { recursive: true, force: true });
297
704
  }
298
705
  var REPO_URL = "https://gitee.com/feige996/unibest.git";
299
706
  async function cloneRepo(projectName, branch) {
@@ -320,12 +727,33 @@ async function cloneRepo(projectName, branch) {
320
727
  throw new Error("cloneRepo error");
321
728
  }
322
729
  }
730
+ async function copyLocalTemplate(projectName) {
731
+ const projectPath = join3(process4.cwd(), projectName);
732
+ const sourcePath = await getTemplateBasePath();
733
+ await new Promise((resolve, reject) => {
734
+ const execStr = `cp -r "${sourcePath}/." "${projectPath}/"`;
735
+ exec(execStr, (error) => {
736
+ if (error) {
737
+ reject(error);
738
+ } else {
739
+ resolve();
740
+ }
741
+ });
742
+ });
743
+ return projectPath;
744
+ }
323
745
  async function cloneRepoByBranch(root2, name, branch, options) {
324
746
  try {
325
- await cloneRepo(name, branch);
747
+ if (USE_LOCAL_TEMPLATE) {
748
+ console.log("\u4F7F\u7528\u672C\u5730\u6A21\u677F\u6D4B\u8BD5...");
749
+ await copyLocalTemplate(name);
750
+ } else {
751
+ console.log("\u4ECE Git \u514B\u9686\u57FA\u7840\u6A21\u677F...");
752
+ await cloneRepo(name, "base");
753
+ }
326
754
  } catch (error) {
327
- console.error(`${red3(`\u6A21\u677F\u7C7B\u578B${branch}\u4E0B\u8F7D\u5931\u8D25\uFF01`)} ${error}`);
328
- process3.exit(1);
755
+ console.error(`${red3(`\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25\uFF01`)} ${error}`);
756
+ process4.exit(1);
329
757
  }
330
758
  const projectPath = join3(root2, name);
331
759
  replacePackageJson(projectPath, name, "1.0.0", options);
@@ -687,25 +1115,36 @@ function debug(...args) {
687
1115
  }
688
1116
 
689
1117
  // src/commands/create/generate.ts
690
- import path from "path";
691
- var root = process4.cwd();
1118
+ import path4 from "path";
1119
+ var root = process5.cwd();
692
1120
  async function generateProject(options) {
693
1121
  debug("generateProject options", options);
694
1122
  const { projectName, platforms, uiLibrary, loginStrategy, i18n } = options;
695
- if (!loginStrategy && !i18n) {
696
- debug("\u62C9\u53D6 base \u5206\u652F");
697
- await cloneRepoByBranch(root, projectName, "base", options);
698
- } else if (!loginStrategy && i18n) {
699
- debug("\u62C9\u53D6 base-i18n \u5206\u652F");
700
- await cloneRepoByBranch(root, projectName, "base-i18n", options);
701
- } else if (loginStrategy && !i18n) {
702
- debug("\u62C9\u53D6 base-login \u5206\u652F");
703
- await cloneRepoByBranch(root, projectName, "base-login", options);
704
- } else if (loginStrategy && i18n) {
705
- debug("\u62C9\u53D6 base-login-i18n \u5206\u652F");
706
- await cloneRepoByBranch(root, projectName, "base-login-i18n", options);
707
- }
708
- const projectPath = path.join(root, projectName);
1123
+ const projectPath = path4.join(root, projectName);
1124
+ debug("\u62C9\u53D6 base \u5206\u652F");
1125
+ await cloneRepoByBranch(root, projectName, "base", options);
1126
+ if (i18n) {
1127
+ debug("\u6CE8\u5165 i18n feature");
1128
+ const results = await injectI18n(projectPath);
1129
+ for (const result of results) {
1130
+ if (result.success) {
1131
+ debug(result.message);
1132
+ } else {
1133
+ logger.warn(result.message);
1134
+ }
1135
+ }
1136
+ }
1137
+ if (loginStrategy) {
1138
+ debug("\u6CE8\u5165 login feature");
1139
+ const results = await injectLogin(projectPath);
1140
+ for (const result of results) {
1141
+ if (result.success) {
1142
+ debug(result.message);
1143
+ } else {
1144
+ logger.warn(result.message);
1145
+ }
1146
+ }
1147
+ }
709
1148
  if (uiLibrary === "none") {
710
1149
  debug("\u4E0D\u5F15\u5165\u4EFB\u4F55UI\u5E93");
711
1150
  } else {
@@ -718,8 +1157,18 @@ async function generateProject(options) {
718
1157
  logger.info("\u60A8\u53EF\u4EE5\u5728\u9879\u76EE\u521B\u5EFA\u540E\u624B\u52A8\u914D\u7F6E UI \u5E93");
719
1158
  }
720
1159
  }
1160
+ const selectedFeatures = getSelectedFeatures(options);
1161
+ const allDeps = {};
1162
+ for (const feature of selectedFeatures) {
1163
+ if (feature.dependencies) {
1164
+ Object.assign(allDeps, feature.dependencies);
1165
+ }
1166
+ }
1167
+ if (Object.keys(allDeps).length > 0) {
1168
+ logger.info(`Feature \u4F9D\u8D56: ${Object.keys(allDeps).join(", ")}`);
1169
+ }
721
1170
  try {
722
- log.success(`\u9879\u76EE${projectName}\u521B\u5EFA\u6210\u529F\uFF01`);
1171
+ log2.success(`\u9879\u76EE${projectName}\u521B\u5EFA\u6210\u529F\uFF01`);
723
1172
  logger.info("\u4E0B\u4E00\u6B65:");
724
1173
  logger.info(` cd ${projectName}`);
725
1174
  logger.info(" pnpm i");
@@ -733,8 +1182,8 @@ async function generateProject(options) {
733
1182
  }
734
1183
 
735
1184
  // src/commands/create.ts
736
- import { intro, log as log2 } from "@clack/prompts";
737
- import { bold as bold2, yellow as yellow3, green as green3 } from "kolorist";
1185
+ import { intro as intro2, log as log3 } from "@clack/prompts";
1186
+ import { bold as bold3, yellow as yellow3, green as green4 } from "kolorist";
738
1187
 
739
1188
  // src/utils/unibestVersion.ts
740
1189
  import fetch from "node-fetch";
@@ -811,11 +1260,11 @@ function generateDeviceIdentifier() {
811
1260
  async function createCommand(args) {
812
1261
  const projectName = args._[1] || args._[0];
813
1262
  const versionUnibest = await getUnibestVersionFromGitee() || "4.0.0";
814
- intro(bold2(green3(`create-unibest@v${version} \u5FEB\u901F\u521B\u5EFA ${yellow3(`unibest@v${versionUnibest}`)} \u9879\u76EE`)));
1263
+ intro2(bold3(green4(`create-unibest@v${version} \u5FEB\u901F\u521B\u5EFA ${yellow3(`unibest@v${versionUnibest}`)} \u9879\u76EE`)));
815
1264
  if (projectName) {
816
1265
  const errorMessage = checkProjectNameExistAndValidate(projectName);
817
1266
  if (errorMessage) {
818
- log2.error(errorMessage);
1267
+ log3.error(errorMessage);
819
1268
  process.exit(1);
820
1269
  }
821
1270
  }
@@ -824,16 +1273,16 @@ async function createCommand(args) {
824
1273
  await generateProject(projectOptions);
825
1274
  beacon(projectOptions);
826
1275
  } catch (error) {
827
- log2.error(`\u521B\u5EFA\u9879\u76EE\u5931\u8D25: ${error.message}`);
1276
+ log3.error(`\u521B\u5EFA\u9879\u76EE\u5931\u8D25: ${error.message}`);
828
1277
  process.exit(1);
829
1278
  }
830
1279
  }
831
1280
 
832
1281
  // src/utils/color.ts
833
- import { blue, green as green4, magenta as magenta2, red as red4, yellow as yellow4 } from "kolorist";
1282
+ import { blue, green as green5, magenta as magenta2, red as red4, yellow as yellow4 } from "kolorist";
834
1283
  var color = {
835
1284
  blue,
836
- green: green4,
1285
+ green: green5,
837
1286
  magenta: magenta2,
838
1287
  red: red4,
839
1288
  yellow: yellow4
@@ -846,33 +1295,54 @@ function printHelp() {
846
1295
  console.log(color.blue("\u5FEB\u901F\u4F7F\u7528:"));
847
1296
  console.log(color.green(" pnpm create unibest <command> [options]"));
848
1297
  console.log(color.green(" pnpm create unibest my-project \u521B\u5EFA\u65B0\u7684unibest\u9879\u76EE"));
1298
+ console.log(color.green(" pnpm create unibest add i18n \u4E3A\u5DF2\u6709\u9879\u76EE\u6DFB\u52A0i18n\u7279\u6027"));
1299
+ console.log(color.green(" pnpm create unibest add login \u4E3A\u5DF2\u6709\u9879\u76EE\u6DFB\u52A0login\u7279\u6027"));
849
1300
  console.log(color.green(" pnpm create unibest -v \u67E5\u770B\u7248\u672C\u4FE1\u606F"));
850
1301
  console.log(color.green(" pnpm create unibest -h \u67E5\u770B\u5E2E\u52A9\u4FE1\u606F"));
851
1302
  console.log("");
852
- console.log(color.blue("\u9009\u9879:"));
1303
+ console.log(color.blue("\u53EF\u7528\u547D\u4EE4:"));
1304
+ console.log(" <projectName> \u521B\u5EFA\u9879\u76EE\uFF08\u4E0D\u7528 new \u5173\u952E\u5B57\uFF09");
1305
+ console.log(" create <projectName> \u521B\u5EFA\u9879\u76EE");
1306
+ console.log(" add <feature> \u4E3A\u5DF2\u6709\u9879\u76EE\u6DFB\u52A0 Feature");
1307
+ console.log("");
1308
+ console.log(color.blue("\u521B\u5EFA\u9879\u76EE\u9009\u9879:"));
853
1309
  console.log(" -p, --platform <type> \u6307\u5B9A\u5E73\u53F0 (h5, mp-weixin, app, mp-alipay, mp-toutiao)");
854
1310
  console.log(" \u652F\u6301\u591A\u9009: -p h5,mp-weixin \u6216 -p h5 -p mp-weixin");
855
1311
  console.log(" -u, --ui <library> \u6307\u5B9AUI\u5E93 (wot-ui, uview-pro, sard-uniapp, uv-ui, uview-plus, none)");
856
1312
  console.log(" -l, --login \u662F\u5426\u9700\u8981\u767B\u5F55\u7B56\u7565");
857
1313
  console.log(" -i, --i18n \u662F\u5426\u9700\u8981\u591A\u8BED\u8A00");
858
1314
  console.log("");
1315
+ console.log(color.blue("\u6DFB\u52A0Feature\u9009\u9879:"));
1316
+ console.log(" <feature> \u8981\u6DFB\u52A0\u7684 Feature (i18n, login)");
1317
+ console.log(" --path <path> \u9879\u76EE\u8DEF\u5F84 (\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55)");
1318
+ console.log(" --force \u5F3A\u5236\u91CD\u65B0\u6CE8\u5165\uFF08\u8986\u76D6\u5DF2\u6709\u914D\u7F6E\uFF09");
1319
+ console.log("");
1320
+ console.log(color.blue("\u53EF\u7528Feature:"));
1321
+ console.log(" i18n \u591A\u8BED\u8A00\u652F\u6301");
1322
+ console.log(" login \u767B\u5F55\u7B56\u7565\uFF08\u9ED1\u767D\u540D\u5355\u3001\u767B\u5F55\u62E6\u622A\u7B49\uFF09");
1323
+ console.log("");
859
1324
  console.log(color.blue("\u793A\u4F8B:"));
1325
+ console.log(" # \u521B\u5EFA\u9879\u76EE");
860
1326
  console.log(" pnpm create unibest my-project -u wot-ui -p h5,mp-weixin");
861
1327
  console.log(" pnpm create unibest my-project -u uview-plus -l -i");
862
1328
  console.log(" pnpm create unibest my-project -u none -p h5,app,mp-weixin");
863
1329
  console.log("");
864
- console.log(color.blue("\u5168\u5C40\u5B89\u88C5 (\u53EF\u9009):"));
865
- console.log(color.green(" npm i -g create-unibest \u5168\u5C40\u5B89\u88C5\uFF0C\u5F97\u5230 best \u547D\u4EE4"));
866
- console.log(color.green(" best <command> [options]"));
867
- console.log(color.green(" best my-project \u521B\u5EFA\u65B0\u7684unibest\u9879\u76EE"));
1330
+ console.log(" # \u6DFB\u52A0 Feature");
1331
+ console.log(" cd my-project && pnpm create unibest add i18n");
1332
+ console.log(" pnpm create unibest add login --path /path/to/project");
1333
+ console.log(" pnpm create unibest add i18n login");
1334
+ console.log(" pnpm create unibest add i18n --force # \u91CD\u65B0\u6CE8\u5165");
1335
+ console.log("");
1336
+ console.log(" # \u5168\u5C40\u5B89\u88C5");
1337
+ console.log(" npm i -g create-unibest");
1338
+ console.log(" best my-project");
1339
+ console.log(" best add i18n");
868
1340
  console.log("");
869
1341
  }
870
1342
 
871
1343
  // src/index.ts
872
- import { green as green5 } from "kolorist";
873
- import { yellow as yellow5 } from "kolorist";
874
1344
  function main() {
875
- const args = minimist(process5.argv.slice(2));
1345
+ const args = minimist(process6.argv.slice(2));
876
1346
  const command = args._[0];
877
1347
  debug("command:", command);
878
1348
  debug("args:", args);
@@ -889,6 +1359,9 @@ function main() {
889
1359
  case "new":
890
1360
  createCommand(args);
891
1361
  break;
1362
+ case "add":
1363
+ addCommand(args);
1364
+ break;
892
1365
  case "-h":
893
1366
  case "--help":
894
1367
  printHelp();
@@ -906,8 +1379,8 @@ async function printVersion() {
906
1379
  const cliVersion = version;
907
1380
  const latestVersion = await getUnibestVersionFromGitee();
908
1381
  if (latestVersion && latestVersion !== cliVersion) {
909
- console.log(`unibest-cli ${cliVersion} ${yellow5(`->`)} ${green5(`\u6700\u65B0\u7248\u672C: ${latestVersion}`)}`);
910
- console.log(`\u4F7F\u7528 ${green5(`npm update -g create-unibest`)} \u6216 ${green5(`pnpm add -g create-unibest`)} \u66F4\u65B0`);
1382
+ console.log(`unibest-cli ${cliVersion} ${yellow5(`->`)} ${green6(`\u6700\u65B0\u7248\u672C: ${latestVersion}`)}`);
1383
+ console.log(`\u4F7F\u7528 ${green6(`npm update -g create-unibest`)} \u6216 ${green6(`pnpm add -g create-unibest`)} \u66F4\u65B0`);
911
1384
  console.log();
912
1385
  } else {
913
1386
  console.log(`unibest-cli ${cliVersion}`);
package/package.json CHANGED
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "create-unibest",
3
3
  "type": "module",
4
- "version": "3.3.0",
5
- "updateTime": "2026-01-19",
6
- "packageManager": "pnpm@9.0.0",
7
- "description": "快速创建unibest项目的脚手架工具",
8
- "author": "",
9
- "license": "ISC",
10
- "keywords": [],
11
- "main": "dist/index.js",
4
+ "version": "4.0.0",
5
+ "packageManager": "pnpm@10.10.0",
6
+ "description": "快速创建 unibest 项目的脚手架工具",
7
+ "author": "feige996",
8
+ "license": "MIT",
9
+ "homepage": "https://unibest.tech",
12
10
  "bin": {
13
11
  "best": "bin/index.js",
14
- "create-unibest": "bin/index.js"
12
+ "create-unibest": "bin/index.js",
13
+ "unibest": "bin/index.js"
15
14
  },
16
15
  "files": [
17
16
  "bin",
@@ -20,7 +19,7 @@
20
19
  "scripts": {
21
20
  "dev": "cross-env NODE_ENV=development tsup --watch",
22
21
  "build": "cross-env NODE_ENV=production tsup",
23
- "prepare": "cross-env NODE_ENV=production npm run build",
22
+ "prepare": "pnpm build",
24
23
  "start": "cross-env NODE_ENV=development node bin/index.js"
25
24
  },
26
25
  "dependencies": {
package/dist/index.d.ts DELETED
@@ -1 +0,0 @@
1
- #!/usr/bin/env node