wanzhuang-cli 1.0.1 → 1.0.3

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
@@ -40,21 +40,21 @@ wz create my-project -t uniapp
40
40
 
41
41
  ### 生成 API
42
42
 
43
- 基于 Swagger/OpenAPI 规范自动生成 TypeScript API 代码:
43
+ 基于 Swagger/OpenAPI 规范自动生成 TypeScript API 代码(使用 @hey-api/openapi-ts):
44
44
 
45
45
  ```bash
46
46
  # 默认生成到 src/api 目录
47
- wz api https://api.example.com/swagger.json
47
+ wz api http://221.195.14.250:8011/swagger/v1/swagger.json
48
48
 
49
49
  # 指定输出目录
50
- wz api https://api.example.com/swagger.json -o ./src/services
50
+ wz api http://221.195.14.250:8011/swagger/v1/swagger.json -o ./src/services
51
51
  ```
52
52
 
53
53
  生成的 API 包含:
54
54
 
55
55
  - TypeScript 类型定义
56
- - 请求函数
57
- - 参数验证
56
+ - SDK 函数
57
+ - JSON Schema 验证
58
58
 
59
59
  ### 更新依赖
60
60
 
@@ -7,4 +7,5 @@
7
7
  */
8
8
  export declare function createProject(projectName: string, options: {
9
9
  force: boolean;
10
+ template?: string;
10
11
  }): Promise<void>;
@@ -15,6 +15,36 @@ const fs_extra_1 = require("fs-extra");
15
15
  const fileOps_1 = require("../utils/fileOps");
16
16
  const configGen_1 = require("../utils/configGen");
17
17
  const deps_1 = require("../utils/deps");
18
+ /**
19
+ * 安全删除目录,带重试机制
20
+ */
21
+ async function safeRemoveDir(dir, maxRetries = 3) {
22
+ for (let i = 0; i < maxRetries; i++) {
23
+ try {
24
+ await (0, fs_extra_1.remove)(dir);
25
+ return;
26
+ }
27
+ catch (err) {
28
+ if (err.code === 'EBUSY' || err.code === 'EPERM') {
29
+ if (i < maxRetries - 1) {
30
+ console.log(chalk_1.default.yellow(`目录被占用,${1}秒后重试... (${i + 1}/${maxRetries})`));
31
+ await new Promise(resolve => setTimeout(resolve, 1000));
32
+ }
33
+ else {
34
+ throw new Error(`无法删除目录 ${dir}\n` +
35
+ `请关闭以下可能占用该目录的程序后重试:\n` +
36
+ ` - VSCode 或其他编辑器\n` +
37
+ ` - 资源管理器\n` +
38
+ ` - 终端(确保没有 cd 到该目录)\n` +
39
+ ` - 正在运行的开发服务器`);
40
+ }
41
+ }
42
+ else {
43
+ throw err;
44
+ }
45
+ }
46
+ }
47
+ }
18
48
  /**
19
49
  * 创建新项目的主函数
20
50
  * 1. 交互选择模板和功能
@@ -29,7 +59,7 @@ async function createProject(projectName, options) {
29
59
  if ((0, fs_1.existsSync)(targetDir)) {
30
60
  if (options.force) {
31
61
  console.log(chalk_1.default.yellow(`正在删除目录: ${targetDir}`));
32
- (0, fs_extra_1.removeSync)(targetDir);
62
+ await safeRemoveDir(targetDir);
33
63
  }
34
64
  else {
35
65
  const { action } = await inquirer_1.default.prompt([
@@ -47,25 +77,31 @@ async function createProject(projectName, options) {
47
77
  return;
48
78
  if (action === 'overwrite') {
49
79
  console.log(chalk_1.default.yellow(`正在删除目录: ${targetDir}`));
50
- (0, fs_extra_1.removeSync)(targetDir);
80
+ await safeRemoveDir(targetDir);
51
81
  }
52
82
  }
53
83
  }
54
84
  // 2. 创建新目录
55
85
  (0, fs_1.mkdirSync)(targetDir, { recursive: true });
56
- // 3. 交互选择模板
57
- const { template } = await inquirer_1.default.prompt([
58
- {
59
- name: 'template',
60
- type: 'list',
61
- message: '请选择项目模板:',
62
- choices: [
63
- { name: 'React', value: 'react' },
64
- { name: 'Vue', value: 'vue' },
65
- { name: 'UniApp', value: 'uniapp' },
66
- ],
67
- },
68
- ]);
86
+ // 3. 获取模板(优先使用命令行参数)
87
+ let template = options.template;
88
+ // 如果没有指定模板或模板无效,则交互选择
89
+ const validTemplates = ['react', 'vue', 'uniapp'];
90
+ if (!template || !validTemplates.includes(template)) {
91
+ const answer = await inquirer_1.default.prompt([
92
+ {
93
+ name: 'template',
94
+ type: 'list',
95
+ message: '请选择项目模板:',
96
+ choices: [
97
+ { name: 'React', value: 'react' },
98
+ { name: 'Vue', value: 'vue' },
99
+ { name: 'UniApp', value: 'uniapp' },
100
+ ],
101
+ },
102
+ ]);
103
+ template = answer.template;
104
+ }
69
105
  // 4. 交互多选功能
70
106
  const { features } = await inquirer_1.default.prompt([
71
107
  {
package/dist/utils/api.js CHANGED
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateApi = generateApi;
4
- const openapi_ts_request_1 = require("openapi-ts-request");
4
+ const openapi_ts_1 = require("@hey-api/openapi-ts");
5
5
  const fs_extra_1 = require("fs-extra");
6
- const prettier_1 = require("prettier");
7
6
  const path_1 = require("path");
8
7
  async function generateApi(schemaPath, outputDir) {
9
8
  try {
@@ -11,34 +10,18 @@ async function generateApi(schemaPath, outputDir) {
11
10
  const absoluteOutputDir = (0, path_1.resolve)(process.cwd(), outputDir);
12
11
  await (0, fs_extra_1.ensureDir)(absoluteOutputDir);
13
12
  // 生成 API 代码
14
- await (0, openapi_ts_request_1.generateService)({
15
- schemaPath,
16
- serversPath: absoluteOutputDir,
17
- requestLibPath: './request',
18
- isGenReactQuery: false,
19
- isDisplayTypeLabel: true,
20
- isGenJsonSchemas: false,
21
- isCamelCase: true,
22
- enableLogging: true,
13
+ await (0, openapi_ts_1.createClient)({
14
+ input: schemaPath,
15
+ output: absoluteOutputDir,
16
+ plugins: [
17
+ '@hey-api/typescript',
18
+ '@hey-api/schemas',
19
+ {
20
+ name: '@hey-api/sdk',
21
+ asClass: false,
22
+ },
23
+ ],
23
24
  });
24
- // 格式化生成的文件
25
- const files = ['api.ts', 'types.ts', 'request.ts'];
26
- for (const file of files) {
27
- const filePath = (0, path_1.join)(absoluteOutputDir, file);
28
- try {
29
- const content = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
30
- const formattedContent = await (0, prettier_1.format)(content, {
31
- parser: 'typescript',
32
- singleQuote: true,
33
- trailingComma: 'all',
34
- printWidth: 100,
35
- });
36
- await (0, fs_extra_1.writeFile)(filePath, formattedContent);
37
- }
38
- catch (error) {
39
- console.warn(`警告:无法格式化 ${file}:`, error);
40
- }
41
- }
42
25
  console.log('API 生成成功');
43
26
  }
44
27
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wanzhuang-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "万桩项目脚手架工具",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -21,22 +21,22 @@
21
21
  "author": "万桩",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
+ "@hey-api/openapi-ts": "^0.92.3",
24
25
  "chalk": "^4.1.2",
25
26
  "commander": "^11.1.0",
26
27
  "fs-extra": "^11.3.0",
27
28
  "inquirer": "^8.2.6",
28
- "openapi-ts-request": "^1.3.2",
29
29
  "ora": "^5.4.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/fs-extra": "^11.0.4",
33
33
  "@types/inquirer": "^9.0.7",
34
34
  "@types/node": "^20.17.32",
35
- "@vitest/coverage-v8": "^1.0.0",
35
+ "@vitest/coverage-v8": "^4.0.18",
36
36
  "prettier": "^3.5.3",
37
37
  "rimraf": "^5.0.5",
38
38
  "typescript": "^5.8.3",
39
- "vitest": "^1.0.0"
39
+ "vitest": "^4.0.18"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=14.0.0"
@@ -5,11 +5,41 @@ import inquirer from 'inquirer';
5
5
  import chalk from 'chalk';
6
6
  import ora from 'ora';
7
7
  import { copyTemplate, updatePackageJson } from '../utils/template';
8
- import { removeSync } from 'fs-extra';
8
+ import { remove } from 'fs-extra';
9
9
  import { convertExtRecursive, replaceImportExtRecursive, addLangTsToAllVue, convertViteConfigToTs } from '../utils/fileOps';
10
10
  import { genTsConfig, genEslintConfig, genPrettierConfig, genUnoConfig, genTailwindConfig } from '../utils/configGen';
11
11
  import { getDevDeps, installDevDeps } from '../utils/deps';
12
12
 
13
+ /**
14
+ * 安全删除目录,带重试机制
15
+ */
16
+ async function safeRemoveDir(dir: string, maxRetries = 3): Promise<void> {
17
+ for (let i = 0; i < maxRetries; i++) {
18
+ try {
19
+ await remove(dir);
20
+ return;
21
+ } catch (err: any) {
22
+ if (err.code === 'EBUSY' || err.code === 'EPERM') {
23
+ if (i < maxRetries - 1) {
24
+ console.log(chalk.yellow(`目录被占用,${1}秒后重试... (${i + 1}/${maxRetries})`));
25
+ await new Promise(resolve => setTimeout(resolve, 1000));
26
+ } else {
27
+ throw new Error(
28
+ `无法删除目录 ${dir}\n` +
29
+ `请关闭以下可能占用该目录的程序后重试:\n` +
30
+ ` - VSCode 或其他编辑器\n` +
31
+ ` - 资源管理器\n` +
32
+ ` - 终端(确保没有 cd 到该目录)\n` +
33
+ ` - 正在运行的开发服务器`
34
+ );
35
+ }
36
+ } else {
37
+ throw err;
38
+ }
39
+ }
40
+ }
41
+ }
42
+
13
43
  /**
14
44
  * 创建新项目的主函数
15
45
  * 1. 交互选择模板和功能
@@ -17,7 +47,7 @@ import { getDevDeps, installDevDeps } from '../utils/deps';
17
47
  * 3. 自动类型/配置/依赖集成
18
48
  * 4. 安装依赖并输出提示
19
49
  */
20
- export async function createProject(projectName: string, options: { force: boolean }) {
50
+ export async function createProject(projectName: string, options: { force: boolean; template?: string }) {
21
51
  // 目标目录
22
52
  const targetDir = join(process.cwd(), projectName);
23
53
 
@@ -25,7 +55,7 @@ export async function createProject(projectName: string, options: { force: boole
25
55
  if (existsSync(targetDir)) {
26
56
  if (options.force) {
27
57
  console.log(chalk.yellow(`正在删除目录: ${targetDir}`));
28
- removeSync(targetDir);
58
+ await safeRemoveDir(targetDir);
29
59
  } else {
30
60
  const { action } = await inquirer.prompt([
31
61
  {
@@ -41,7 +71,7 @@ export async function createProject(projectName: string, options: { force: boole
41
71
  if (!action) return;
42
72
  if (action === 'overwrite') {
43
73
  console.log(chalk.yellow(`正在删除目录: ${targetDir}`));
44
- removeSync(targetDir);
74
+ await safeRemoveDir(targetDir);
45
75
  }
46
76
  }
47
77
  }
@@ -49,19 +79,26 @@ export async function createProject(projectName: string, options: { force: boole
49
79
  // 2. 创建新目录
50
80
  mkdirSync(targetDir, { recursive: true });
51
81
 
52
- // 3. 交互选择模板
53
- const { template } = await inquirer.prompt([
54
- {
55
- name: 'template',
56
- type: 'list',
57
- message: '请选择项目模板:',
58
- choices: [
59
- { name: 'React', value: 'react' },
60
- { name: 'Vue', value: 'vue' },
61
- { name: 'UniApp', value: 'uniapp' },
62
- ],
63
- },
64
- ]);
82
+ // 3. 获取模板(优先使用命令行参数)
83
+ let template: 'react' | 'vue' | 'uniapp' = options.template as 'react' | 'vue' | 'uniapp';
84
+
85
+ // 如果没有指定模板或模板无效,则交互选择
86
+ const validTemplates = ['react', 'vue', 'uniapp'];
87
+ if (!template || !validTemplates.includes(template)) {
88
+ const answer = await inquirer.prompt([
89
+ {
90
+ name: 'template',
91
+ type: 'list',
92
+ message: '请选择项目模板:',
93
+ choices: [
94
+ { name: 'React', value: 'react' },
95
+ { name: 'Vue', value: 'vue' },
96
+ { name: 'UniApp', value: 'uniapp' },
97
+ ],
98
+ },
99
+ ]);
100
+ template = answer.template;
101
+ }
65
102
 
66
103
  // 4. 交互多选功能
67
104
  const { features } = await inquirer.prompt([
package/src/utils/api.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { generateService } from 'openapi-ts-request';
2
- import { ensureDir, writeFile, readFile } from 'fs-extra';
3
- import { format } from 'prettier';
4
- import { join, resolve } from 'path';
1
+ import { createClient } from '@hey-api/openapi-ts';
2
+ import { ensureDir } from 'fs-extra';
3
+ import { resolve } from 'path';
5
4
 
6
5
  export async function generateApi(schemaPath: string, outputDir: string): Promise<void> {
7
6
  try {
@@ -10,35 +9,19 @@ export async function generateApi(schemaPath: string, outputDir: string): Promis
10
9
  await ensureDir(absoluteOutputDir);
11
10
 
12
11
  // 生成 API 代码
13
- await generateService({
14
- schemaPath,
15
- serversPath: absoluteOutputDir,
16
- requestLibPath: './request',
17
- isGenReactQuery: false,
18
- isDisplayTypeLabel: true,
19
- isGenJsonSchemas: false,
20
- isCamelCase: true,
21
- enableLogging: true,
12
+ await createClient({
13
+ input: schemaPath,
14
+ output: absoluteOutputDir,
15
+ plugins: [
16
+ '@hey-api/typescript',
17
+ '@hey-api/schemas',
18
+ {
19
+ name: '@hey-api/sdk',
20
+ asClass: false,
21
+ },
22
+ ],
22
23
  });
23
24
 
24
- // 格式化生成的文件
25
- const files = ['api.ts', 'types.ts', 'request.ts'];
26
- for (const file of files) {
27
- const filePath = join(absoluteOutputDir, file);
28
- try {
29
- const content = await readFile(filePath, 'utf-8');
30
- const formattedContent = await format(content, {
31
- parser: 'typescript',
32
- singleQuote: true,
33
- trailingComma: 'all',
34
- printWidth: 100,
35
- });
36
- await writeFile(filePath, formattedContent);
37
- } catch (error) {
38
- console.warn(`警告:无法格式化 ${file}:`, error);
39
- }
40
- }
41
-
42
25
  console.log('API 生成成功');
43
26
  } catch (error) {
44
27
  console.error('生成 API 时出错:', error);