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 +5 -5
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +51 -15
- package/dist/utils/api.js +12 -29
- package/package.json +4 -4
- package/src/commands/create.ts +54 -17
- package/src/utils/api.ts +14 -31
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
|
|
47
|
+
wz api http://221.195.14.250:8011/swagger/v1/swagger.json
|
|
48
48
|
|
|
49
49
|
# 指定输出目录
|
|
50
|
-
wz api
|
|
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
|
|
package/dist/commands/create.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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,
|
|
15
|
-
schemaPath,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
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": "^
|
|
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": "^
|
|
39
|
+
"vitest": "^4.0.18"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=14.0.0"
|
package/src/commands/create.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 {
|
|
2
|
-
import { ensureDir
|
|
3
|
-
import {
|
|
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
|
|
14
|
-
schemaPath,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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);
|