gen-api-types 1.0.1 → 1.0.4
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 +3 -3
- package/bin/index.js +33 -0
- package/package.json +41 -34
- package/src/argv/index.ts +2 -2
- package/src/cli/index.ts +56 -35
- package/src/transformer/index.ts +10 -6
- package/src/utils/index.ts +7 -11
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ export class TestApi {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
@gen_type_m()
|
|
41
|
-
static getWeather(): Promise<
|
|
41
|
+
static getWeather(): Promise<Response__TestApi_getWeather> {
|
|
42
42
|
return fetch('http://t.weather.sojson.com/api/weather/city/101030100').then(r => r.json())
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -65,8 +65,8 @@ npx gen-api-types -o output_dir -O output_file_name ./api_dir1 ./api_dir2
|
|
|
65
65
|
Options:
|
|
66
66
|
-h, --help 输出帮助信息
|
|
67
67
|
-r, --project_root <path> 项目根目录
|
|
68
|
-
-
|
|
69
|
-
-
|
|
68
|
+
-O, --output_file <path> 输出文件名
|
|
69
|
+
-o, --output_dir <path> 输出目录
|
|
70
70
|
-t, --ts_config_path <path> tsconfig.json 文件路径
|
|
71
71
|
```
|
|
72
72
|
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 强制使用 tsx 执行当前目录下的 TypeScript 文件
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const { exec,execFile,spawn } = require('child_process');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { promisify } = require('util')
|
|
8
|
+
const p_exec = promisify(exec);
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
; (async function () {
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
await p_exec('tsx -v')
|
|
14
|
+
try {
|
|
15
|
+
const cli_path = path.resolve(__dirname,'../src/cli/index.ts')
|
|
16
|
+
// const cli_path = path.resolve(__dirname,'../dist/gen_api_types.cli.min.js')
|
|
17
|
+
|
|
18
|
+
const cp = spawn('tsx',[cli_path,...args],{ stdio: 'inherit',shell: true })
|
|
19
|
+
cp.on('error',err => console.error('执行 tsx 命令失败',err))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// console.error('执行 tsx 命令失败',error)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('请安装 tsx',error)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
})();
|
|
33
|
+
|
package/package.json
CHANGED
|
@@ -1,34 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "gen-api-types",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "./src/index.ts",
|
|
6
|
-
"bin": {
|
|
7
|
-
"gen-api-types": "src/cli/index.ts"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
}
|
|
34
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "gen-api-types",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gen-api-types": "src/cli/index.ts",
|
|
8
|
+
"gat": "bin/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "vitest",
|
|
12
|
+
"build": "webpack",
|
|
13
|
+
"publish:patch": "npm version patch && npm publish"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"generate types",
|
|
17
|
+
"TypeScript",
|
|
18
|
+
"Cli",
|
|
19
|
+
"API"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"src",
|
|
23
|
+
"tsconfig.json",
|
|
24
|
+
"package.json"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "ISC",
|
|
28
|
+
"packageManager": "pnpm@10.7.1",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"reflect-metadata": "^0.2.2",
|
|
31
|
+
"ts-morph": "^27.0.0",
|
|
32
|
+
"tsx": "^4.20.5"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^24.3.1",
|
|
36
|
+
"ts-loader": "^9.5.4",
|
|
37
|
+
"vitest": "^3.2.4",
|
|
38
|
+
"webpack": "^5.101.0",
|
|
39
|
+
"webpack-cli": "^6.0.1"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/argv/index.ts
CHANGED
|
@@ -51,8 +51,8 @@ if (help) {
|
|
|
51
51
|
Options:
|
|
52
52
|
-h, --help 输出帮助信息
|
|
53
53
|
-r, --project_root <path> 项目根目录
|
|
54
|
-
-
|
|
55
|
-
-
|
|
54
|
+
-O, --output_file <path> 输出文件名
|
|
55
|
+
-o, --output_dir <path> 输出目录
|
|
56
56
|
-t, --ts_config_path <path> tsconfig.json 文件路径
|
|
57
57
|
`
|
|
58
58
|
)
|
package/src/cli/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
// scripts/generate-api-types.ts
|
|
5
4
|
import * as path from 'path';
|
|
6
5
|
import { Decorator, Project } from 'ts-morph';
|
|
7
6
|
import { pathToFileURL } from 'url';
|
|
@@ -9,10 +8,12 @@ import { output_dir, output_file, positionals } from '../argv';
|
|
|
9
8
|
import { C_DECO_NAME, M_DECO_NAME } from '../constant';
|
|
10
9
|
import { GenTypeOptions } from '../decotators';
|
|
11
10
|
import { TypeTransformer } from '../transformer';
|
|
11
|
+
import { formatResultList } from '../utils';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// console.log('positionals', positionals)
|
|
14
14
|
|
|
15
15
|
const sourceFilesGlob = positionals.map(dir => path.normalize(`${dir}/**/*.ts`))
|
|
16
|
+
const out_put_target = path.resolve(output_dir, output_file)
|
|
16
17
|
|
|
17
18
|
// console.log('arg', _arg)
|
|
18
19
|
// console.log('sourceFilesGlob', sourceFilesGlob)
|
|
@@ -49,6 +50,7 @@ function getApiMethodsInfo() {
|
|
|
49
50
|
const project = new Project({});
|
|
50
51
|
|
|
51
52
|
project.addSourceFilesAtPaths(sourceFilesGlob);
|
|
53
|
+
// console.log('project.getSourceFiles().length', project.getSourceFiles().length)
|
|
52
54
|
// debugger
|
|
53
55
|
// 3. 遍历所有源文件
|
|
54
56
|
for (const sourceFile of project.getSourceFiles()) {
|
|
@@ -84,11 +86,10 @@ function getApiMethodsInfo() {
|
|
|
84
86
|
return apiMethodsInfo
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
type
|
|
88
|
-
|
|
89
|
-
errorList: { fullMethodName: string, error: any }[]
|
|
89
|
+
type ExecuteApiMethodResult = {
|
|
90
|
+
data?: any, typeName: string, fullMethodName: string, error?: any
|
|
90
91
|
}
|
|
91
|
-
async function
|
|
92
|
+
async function executeApiMethods(apiMethodsInfo: ApiMethodInfo[]): Promise<ExecuteApiMethodResult[]> {
|
|
92
93
|
const apiModuleMap = new Map<string, any>();
|
|
93
94
|
const taskList = apiMethodsInfo.map(async (apiMethodInfo) => {
|
|
94
95
|
const { className, methodName, fullMethodName, modulePath, args, typeName } = apiMethodInfo
|
|
@@ -103,69 +104,89 @@ async function excuteApiMethods(apiMethodsInfo: ApiMethodInfo[]): Promise<Excute
|
|
|
103
104
|
|
|
104
105
|
try {
|
|
105
106
|
apiModule = await import(pathToFileURL(modulePath).href)
|
|
106
|
-
console.log('apiMethod', apiModule)
|
|
107
|
+
// console.log('apiMethod', apiModule)
|
|
107
108
|
if (apiModule) apiModuleMap.set(modulePath, apiModule)
|
|
108
109
|
} catch (error) {
|
|
109
|
-
console.log(
|
|
110
|
+
console.log(`import ${modulePath} error \r\n`, error)
|
|
111
|
+
return { error: `module error`, fullMethodName, typeName }
|
|
110
112
|
}
|
|
111
|
-
|
|
112
113
|
}
|
|
114
|
+
|
|
115
|
+
|
|
113
116
|
const apiMethod = apiModule?.[className]?.[methodName]
|
|
114
117
|
if (apiMethod && typeof apiMethod === 'function') {
|
|
115
118
|
try {
|
|
116
|
-
console.log(`🔍 Calling ${fullMethodName} with args:`, args);
|
|
119
|
+
// console.log(`🔍 Calling ${fullMethodName} with args:`, args);
|
|
117
120
|
const result = apiMethod.apply(apiModule, args)
|
|
118
121
|
if (result instanceof Promise) {
|
|
119
122
|
const data = await result
|
|
120
123
|
// console.log(`${fullMethodName} result`)
|
|
121
124
|
return { data, typeName, fullMethodName }
|
|
122
125
|
}
|
|
123
|
-
return { error: 'not Promise method', fullMethodName }
|
|
126
|
+
return { error: 'not Promise method', fullMethodName, typeName }
|
|
124
127
|
} catch (error) {
|
|
125
128
|
console.error(`❌ ${fullMethodName} execute error:`, error)
|
|
126
|
-
return { error, fullMethodName }
|
|
129
|
+
return { error, fullMethodName, typeName }
|
|
127
130
|
}
|
|
128
131
|
} else {
|
|
129
|
-
console.error(`❌ 无法获取 ${fullMethodName}
|
|
132
|
+
console.error(`❌ 无法获取 ${fullMethodName} 方法, 或不是可调用方法 `)
|
|
130
133
|
return {
|
|
131
|
-
error: `method error`, fullMethodName
|
|
134
|
+
error: `method error`, fullMethodName, typeName
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
137
|
})
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
successList: resultList.filter(item => !item.error) as ExcuteApiMethodsResult['successList'],
|
|
139
|
-
errorList: resultList.filter(item => item.error) as ExcuteApiMethodsResult['errorList'],
|
|
140
|
-
}
|
|
139
|
+
return Promise.all(taskList)
|
|
140
|
+
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// console.log('out_put_target', out_put_target)
|
|
143
|
+
|
|
144
|
+
function createDeclarationFile(successList: ExecuteApiMethodResult[]) {
|
|
146
145
|
const ttf = new TypeTransformer({ filePath: out_put_target })
|
|
147
|
-
const tasks = successList.map(
|
|
146
|
+
const tasks = successList.map(async ({ data, typeName, fullMethodName }) => {
|
|
147
|
+
try {
|
|
148
|
+
await ttf.transform(data, typeName)
|
|
149
|
+
return { fullMethodName }
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('transform error', error)
|
|
152
|
+
return { fullMethodName, error }
|
|
153
|
+
}
|
|
154
|
+
})
|
|
148
155
|
return Promise.all(tasks)
|
|
149
156
|
}
|
|
150
157
|
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
151
161
|
async function main() {
|
|
152
162
|
try {
|
|
153
163
|
console.log('🚀 开始生成API类型...');
|
|
154
164
|
const apiMethodsInfo = getApiMethodsInfo();
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
if (apiMethodsInfo.length == 0) return console.warn('⚠️ 未找到需要转换的API,请检查api_dir 和 get_type装饰器标注是否正确!')
|
|
166
|
+
const executeList = await executeApiMethods(apiMethodsInfo);
|
|
167
|
+
|
|
168
|
+
const { successList: executeSuccessList, errorList: executeErrorList } = formatResultList(executeList)
|
|
169
|
+
if (executeErrorList.length) {
|
|
170
|
+
console.group('请求结果:')
|
|
171
|
+
console.table({
|
|
172
|
+
"✔️ executeSuccessList": executeSuccessList.map(item => item.fullMethodName).join(' '),
|
|
173
|
+
"❌ executeErrorList": executeErrorList.map(item => item.fullMethodName).join(' ')
|
|
174
|
+
})
|
|
175
|
+
console.groupEnd()
|
|
176
|
+
}
|
|
177
|
+
const transformList = await createDeclarationFile(executeSuccessList)
|
|
178
|
+
const { successList: transformSuccessList, errorList: transformErrorList } = formatResultList(transformList)
|
|
167
179
|
|
|
180
|
+
if (transformErrorList.length) {
|
|
181
|
+
console.group('转换结果:')
|
|
182
|
+
console.table({
|
|
183
|
+
"✔️ transformSuccessList": transformSuccessList.map(item => item.fullMethodName).join(' '),
|
|
184
|
+
"❌ transformErrorList": transformErrorList.map(item => item.fullMethodName).join(' ')
|
|
185
|
+
})
|
|
186
|
+
console.groupEnd()
|
|
168
187
|
|
|
188
|
+
}
|
|
189
|
+
if (transformSuccessList.length) console.log('✅ API 类型生成完成:', out_put_target);
|
|
169
190
|
} catch (error) {
|
|
170
191
|
console.error('❌ 出错了', error)
|
|
171
192
|
}
|
package/src/transformer/index.ts
CHANGED
|
@@ -14,8 +14,10 @@ function inferType(value: any): string {
|
|
|
14
14
|
return "number";
|
|
15
15
|
} else if (typeof value === "boolean") {
|
|
16
16
|
return "boolean";
|
|
17
|
-
} else if (
|
|
17
|
+
} else if (value === null) {
|
|
18
18
|
return "null";
|
|
19
|
+
} else if (!value) {
|
|
20
|
+
return "undefined";
|
|
19
21
|
} else {
|
|
20
22
|
return "any";
|
|
21
23
|
}
|
|
@@ -25,9 +27,11 @@ function inferType(value: any): string {
|
|
|
25
27
|
export class TypeTransformer {
|
|
26
28
|
project: Project;
|
|
27
29
|
sourceFile: SourceFile;
|
|
28
|
-
|
|
30
|
+
isExported: boolean
|
|
31
|
+
constructor({ projectOptions = {}, filePath = "types.d.ts", isExported = false } = {}) {
|
|
29
32
|
this.project = new Project(projectOptions);
|
|
30
33
|
this.sourceFile = this.createSourceFile(filePath);
|
|
34
|
+
this.isExported = isExported
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
createSourceFile(filePath: string) {
|
|
@@ -45,14 +49,14 @@ export class TypeTransformer {
|
|
|
45
49
|
// 4. 添加类型别名
|
|
46
50
|
this.sourceFile.addTypeAlias({
|
|
47
51
|
name: typeName,
|
|
48
|
-
|
|
52
|
+
isExported: this.isExported,
|
|
49
53
|
type: inferType(val)
|
|
50
54
|
});
|
|
55
|
+
// if (typeName == 'Res_Weather') throw Error('res_weather test error')
|
|
51
56
|
}
|
|
52
|
-
transform(res: any, typeName: string, isAsync = true) {
|
|
53
|
-
|
|
57
|
+
async transform(res: any, typeName: string, isAsync = true) {
|
|
54
58
|
this.generateTypeFromObject(res, typeName)
|
|
55
|
-
|
|
59
|
+
await this.sourceFile.save();
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
export function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
type ResultInfoBase = { error?: any }
|
|
2
|
+
export function formatResultList<T extends ResultInfoBase>(list: T[]) {
|
|
3
|
+
return list.reduce((acc, item) => {
|
|
4
|
+
if (item.error) acc.errorList.push(item)
|
|
5
|
+
else acc.successList.push(item)
|
|
6
|
+
return acc
|
|
7
|
+
}, { successList: [] as Omit<T, 'error'>[], errorList: [] as T[] })
|
|
6
8
|
}
|
|
7
|
-
|
|
8
|
-
let val = Array(10).fill(testHello())
|
|
9
|
-
console.log('val', val)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
console.log('999', 123)
|