gen-api-types 1.0.14 → 1.0.17

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.en.md CHANGED
@@ -10,8 +10,9 @@ With this tool, we can mark request interface classes and methods through TypeSc
10
10
 
11
11
  > Note:
12
12
  >
13
- > 1. Because this tool uses TypeScript decorators, and decorators currently (TypeScript 5.0) do not support decorating plain functions directly, APIs must be written as **API classes + static API methods**.
14
- > 2. This tool needs to dynamically execute TypeScript code (importing API classes and calling the marked static API methods), so it runs through the bundled `tsx` dependency. No global `tsx` installation is required.
13
+ > 1. Because this tool uses TypeScript method decorators, APIs must be written as **API classes + API methods**.
14
+ > 2. The CLI dynamically imports and executes marked API modules through the project's bundled `tsx` runtime. No global `tsx` installation is required.
15
+ > 3. API methods are executed for real in the CLI process. Make sure required environment variables, network access, and authentication are available.
15
16
 
16
17
  #### Installation
17
18
 
@@ -23,14 +24,17 @@ npm install gen-api-types -D
23
24
 
24
25
  #### Usage
25
26
 
26
- ##### 1. Mark interface class names and methods
27
+ ##### 1. Mark API classes and methods
27
28
 
28
29
  ```ts
29
30
  import { gen_type_c, gen_type_m } from 'gen-api-types'
30
31
 
31
32
  @gen_type_c()
32
33
  export class TestApi {
33
- @gen_type_m({ args: [100], typeName: 'XXX' })
34
+ @gen_type_m({
35
+ args: [100],
36
+ typeName: 'XXX',
37
+ })
34
38
  static async getList(id: number): Promise<XXX> {
35
39
  return asleep(1000).then(() => {
36
40
  return { name: 'zs', id }
@@ -38,7 +42,7 @@ export class TestApi {
38
42
  }
39
43
 
40
44
  @gen_type_m()
41
- static getWeather(): Promise<Response_TestApi_getWeather> {
45
+ getWeather(): Promise<Response_TestApi_getWeather> {
42
46
  return fetch('http://t.weather.sojson.com/api/weather/city/101030100').then(r => r.json())
43
47
  }
44
48
  }
@@ -46,14 +50,26 @@ export class TestApi {
46
50
 
47
51
  As shown in the code above:
48
52
 
49
- - `@gen_type_c` decorator function is used to mark interface classes. Since the tool dynamically analyzes all ts files in the specified directory, marking interface classes helps quickly locate them.
50
- - `@gen_type_m` decorator function marks the request methods that need to be converted. It can accept a configuration object with two fields:
51
- 1. `typeName: string` Interface return type name. If not specified, the default name will be: `Response_${ClassName}_${MethodName}`
52
- 2. `args: any[]` Method parameter list. The tool will pass this list when calling the request method.
53
+ - `@gen_type_c()` marks an API class.
54
+ - `@gen_type_m()` marks a method to execute and convert.
55
+ - `args: any[]` contains the arguments passed to the method. Both static and non-static methods are supported.
56
+ - `typeName: string` is the generated type name. If omitted, it defaults to `Response_${ClassName}_${MethodName}`.
57
+ - Prefer the exported `gen_type_c` and `gen_type_m` aliases. `GatDecorator` is the internal container used for constant-based decorator names and is not required in business code.
58
+
59
+ Decorator options may span multiple lines:
60
+
61
+ ```ts
62
+ @gen_type_m({
63
+ args: [100],
64
+ typeName: 'XXX'
65
+ })
66
+ ```
53
67
 
54
68
  > Note:
55
69
 
56
- If TypeScript reports the decorator error: "The runtime will invoke the decorator with 2 arguments, but the decorator expects 3", set `compilerOptions.experimentalDecorators` to `true` in tsconfig.
70
+ If TypeScript reports the decorator error "The runtime will invoke the decorator with 2 arguments, but the decorator expects 3", set `compilerOptions.experimentalDecorators` to `true` in `tsconfig.json`.
71
+
72
+ The CLI currently applies a 3-second execution timeout to each API method. Timeouts, synchronous exceptions, and rejected promises are reported as execution failures. A timeout stops waiting for the result but cannot cancel an underlying request that has already started.
57
73
 
58
74
  ##### 2. Execute command
59
75
 
@@ -85,18 +101,18 @@ You can also use it by configuring scripts in package.json:
85
101
  }
86
102
  ```
87
103
 
104
+ The CLI scans `.ts` files in the input directories, finds marked classes and methods, and dynamically imports the modules containing them. The decorators execute the API methods during module import; the declaration file is generated after all marked methods finish.
105
+
88
106
  Command output:
89
107
 
90
108
  ```shell
91
109
  🚀 Start generating API types...
92
110
  sourceFilesGlob [ 'src\\**\\*.ts' ]
93
- 📋 Processing UserApi.getList ...
94
- 📋 Processing UserApi.getWeather ...
95
111
  Request results:
96
112
  ┌────────────────┬──────────────────────────────────────┐
97
113
  │ (index) │ Values │
98
114
  ├────────────────┼──────────────────────────────────────┤
99
- │ ✔️ successList │ 'UserApi.getList UserApi.getWeather' │
115
+ │ ✔️ successList │ 'TestApi.getList TestApi.getWeather' │
100
116
  │ ❌ errorList │ '' │
101
117
  └────────────────┴──────────────────────────────────────┘
102
118
  ✅ API type generation completed
@@ -147,3 +163,48 @@ Generated output example:
147
163
  export type XXX = { name: string };
148
164
  export type Response_TestApi_getWeather = {...}
149
165
  ```
166
+
167
+ ##### 4. Vite plugin
168
+
169
+ Decorators are needed for type generation, but they normally should not execute when the business application runs or builds. In a Vite project, use the plugin to remove `gen_type_c` and `gen_type_m` from the transformed business code:
170
+
171
+ ```ts
172
+ // vite.config.ts
173
+ import { defineConfig } from 'vite'
174
+ import { removeGatDecorators } from 'gen-api-types'
175
+
176
+ export default defineConfig({
177
+ plugins: [removeGatDecorators()],
178
+ })
179
+ ```
180
+
181
+ The plugin processes `.ts` and `.tsx` files and supports single-line and multi-line decorator options. Use the `gen_type_c` and `gen_type_m` aliases in business code; direct `GatDecorator.gen_type_m()` calls do not match the plugin's current decorator names.
182
+
183
+ The Vite plugin only affects Vite's transform pipeline. It is not involved when the CLI imports and executes API modules.
184
+
185
+ #### VS Code Extension
186
+
187
+ If you use `gen-api-types` in VS Code, you can install the companion extension [gen-api-types-vsce](https://github.com/xuejiangping/gen-api-types-vsce) to generate API return types from the context menu.
188
+
189
+ ![alt text](docs/images/image.png)
190
+
191
+ The extension does not bundle the CLI. It invokes the version of `gen-api-types` installed locally in the current project.
192
+
193
+ After installing the extension, right-click in a `.ts` or `.tsx` file containing the decorated APIs and select `Generate API Return Types (gen-api-types)`. By default, the extension will:
194
+
195
+ - Use the directory containing the current TypeScript file as the `api_dirs` argument
196
+ - Generate the type file in the same directory as the current TypeScript file
197
+ - Use `api-types.d.ts` as the default output file name
198
+ - Ask for confirmation before overwriting an existing output file
199
+
200
+ You can configure the CLI arguments in the VS Code settings:
201
+
202
+ | Extension setting | CLI argument | Default behavior |
203
+ | ---------------------------- | ---------------------- | ----------------------------------------------------- |
204
+ | `gen-api-types.projectRoot` | `-r, --project_root` | Workspace root containing the current TypeScript file |
205
+ | `gen-api-types.outputFile` | `-O, --output_file` | `api-types.d.ts` |
206
+ | `gen-api-types.outputDir` | `-o, --output_dir` | Directory containing the current TypeScript file |
207
+ | `gen-api-types.tsConfigPath` | `-t, --ts_config_path` | Not passed; the CLI uses its default value |
208
+ | `gen-api-types.isExported` | `--isExported` | `false` |
209
+
210
+ The extension is essentially a VS Code entry point for the CLI. Type analysis, API execution, and type-file generation are still handled by `gen-api-types`.
package/README.md CHANGED
@@ -10,8 +10,9 @@
10
10
 
11
11
  > 注意:
12
12
  >
13
- > 1. 由于需要使用到ts装饰器特性,而装饰器目前(ts 5.0)不支持直接标记普通函数,所以我们的接口必须以 **接口类+静态api方法** 的形式书写
14
- > 2. 该工具需要动态执行 ts 代码(import接口类,然后调用标记的静态api方法),因此会通过内置依赖的 `tsx` 执行工具运行,无需额外全局安装 `tsx`。
13
+ > 1. 由于需要使用 TypeScript 方法装饰器,接口需要以 **API 类 + API 方法** 的形式书写。
14
+ > 2. CLI 会动态导入并执行标记的 API 模块,因此通过项目内置的 `tsx` 运行,不需要全局安装 `tsx`。
15
+ > 3. API 方法会在 CLI 进程中真实执行,请确保运行所需的环境变量、网络权限和鉴权配置已经准备好。
15
16
 
16
17
  #### 安装教程
17
18
 
@@ -19,19 +20,21 @@
19
20
 
20
21
  ```shell
21
22
  npm install gen-api-types -D
22
-
23
23
  ```
24
24
 
25
25
  #### 使用说明
26
26
 
27
- ##### 1. 标记接口类名和方法
27
+ ##### 1. 标记 API 类和方法
28
28
 
29
29
  ```ts
30
30
  import { gen_type_c, gen_type_m } from 'gen-api-types'
31
31
 
32
32
  @gen_type_c()
33
33
  export class TestApi {
34
- @gen_type_m({ args: [100], typeName: 'XXX' })
34
+ @gen_type_m({
35
+ args: [100],
36
+ typeName: 'XXX',
37
+ })
35
38
  static async getList(id: number): Promise<XXX> {
36
39
  return asleep(1000).then(() => {
37
40
  return { name: 'zs', id }
@@ -39,7 +42,7 @@ export class TestApi {
39
42
  }
40
43
 
41
44
  @gen_type_m()
42
- static getWeather(): Promise<Response_TestApi_getWeather> {
45
+ getWeather(): Promise<Response_TestApi_getWeather> {
43
46
  return fetch('http://t.weather.sojson.com/api/weather/city/101030100').then(r => r.json())
44
47
  }
45
48
  }
@@ -47,14 +50,26 @@ export class TestApi {
47
50
 
48
51
  如上面代码所示:
49
52
 
50
- - `@gen_type_c`装饰器函数,用来标记接口类。因为工具会动态分析指定目录下的所有 ts 文件,标记接口类,可以帮助我们快速定位接口类
51
- - `@gen_type_m`装饰器函数标记需要转换的请求方法。它可以接收一个配置对象,包含两个字段。
52
- 1. `typeName: string` 接口返回类型名称,若不指定该字段,默认生成名称为: `Response_${类名}_${方法名}`
53
- 2. `args:any[] ` 方法参数列表,工具调用请求方法时,会将参数列表传入
53
+ - `@gen_type_c()` 标记 API 类。
54
+ - `@gen_type_m()` 标记需要执行并生成类型的方法。
55
+ - `args: any[]` 是调用方法时传入的参数,支持静态方法和非静态方法。
56
+ - `typeName: string` 是生成的类型名称;不指定时默认为 `Response_${类名}_${方法名}`。
57
+ - 推荐使用导出的 `gen_type_c`、`gen_type_m` 别名。`GatDecorator` 是内部用于按常量名称注册装饰器的容器,不是业务代码必须使用的入口。
58
+
59
+ 装饰器参数可以跨多行书写:
60
+
61
+ ```ts
62
+ @gen_type_m({
63
+ args: [100],
64
+ typeName: 'XXX'
65
+ })
66
+ ```
54
67
 
55
68
  > 注意:
56
69
 
57
- 若使用装饰器时ts报错: "运行时将使用 2 个自变量调用修饰器,但修饰器需要 3 个",请将tsconfig中`compilerOptions.experimentalDecorators`设置为`true`
70
+ 若使用装饰器时 TypeScript 报错“运行时将使用 2 个自变量调用修饰器,但修饰器需要 3 个”,请将 `tsconfig.json` 中的 `compilerOptions.experimentalDecorators` 设置为 `true`。
71
+
72
+ CLI 默认会为单个 API 方法设置 5 秒执行超时。超时、同步异常或 Promise rejection 都会被记录为该方法的执行失败;超时只能停止等待,不能取消已经发出的底层请求。
58
73
 
59
74
  ##### 2. 执行命令
60
75
 
@@ -86,13 +101,13 @@ Options:
86
101
  }
87
102
  ```
88
103
 
104
+ CLI 会扫描输入目录中的 `.ts` 文件,找到标记的类和方法后,动态导入包含这些方法的模块。模块导入时装饰器会执行 API 方法,所有方法完成后再生成声明文件。
105
+
89
106
  命令输出:
90
107
 
91
108
  ```shell
92
109
  🚀 开始生成API类型...
93
110
  sourceFilesGlob [ 'src\\**\\*.ts' ]
94
- 📋 处理 TestApi.getList ...
95
- 📋 处理 TestApi.getWeather ...
96
111
  请求结果:
97
112
  ┌────────────────┬──────────────────────────────────────┐
98
113
  │ (index) │ Values │
@@ -149,6 +164,24 @@ export type XXX = { name: string };
149
164
  export type Response_TestApi_getWeather = {...}
150
165
  ```
151
166
 
167
+ ##### 4. Vite 插件
168
+
169
+ 装饰器只用于生成类型,业务项目正常运行或构建时通常不需要执行这些装饰器。Vite 项目可以使用插件移除 `gen_type_c` 和 `gen_type_m`,避免装饰器在业务运行时产生副作用:
170
+
171
+ ```ts
172
+ // vite.config.ts
173
+ import { defineConfig } from 'vite'
174
+ import { removeGatDecorators } from 'gen-api-types'
175
+
176
+ export default defineConfig({
177
+ plugins: [removeGatDecorators()],
178
+ })
179
+ ```
180
+
181
+ 插件只处理 `.ts` 和 `.tsx` 文件,并支持单行或多行装饰器参数。推荐在业务代码中使用 `gen_type_c`、`gen_type_m` 别名;如果直接使用 `GatDecorator.gen_type_m()`,不会匹配插件当前的装饰器名称。
182
+
183
+ Vite 插件只影响 Vite 的转换流程,不参与 CLI 的 API 执行流程。
184
+
152
185
  #### VS Code 插件
153
186
 
154
187
  如果你在 VS Code 中使用 `gen-api-types` ,可以安装配套插件 [gen-api-types-vsce](https://github.com/xuejiangping/gen-api-types-vsce),通过右键菜单生成 API 返回类型。
package/bin/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gen-api-types",
3
- "version": "1.0.14",
3
+ "version": "1.0.17",
4
4
  "description": "一个自动生成请求接口返回类型的 cli 小工具",
5
5
  "main": "./src/index.ts",
6
6
  "exports": {
@@ -19,7 +19,8 @@
19
19
  "test": "vitest",
20
20
  "publish:patch": "npm version patch && git push --tags && npm publish",
21
21
  "preinstall": "echo preinstall 。。。",
22
- "postinstall": "echo postinstall 。。。"
22
+ "postinstall": "echo postinstall 。。。",
23
+ "gat": "gat -o test/output test/api "
23
24
  },
24
25
  "keywords": [
25
26
  "generate types",
@@ -43,6 +44,7 @@
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/node": "^24.3.1",
47
+ "vite": "^8.2.2",
46
48
  "vitest": "^3.2.4"
47
49
  }
48
50
  }
package/src/argv/index.ts CHANGED
@@ -61,7 +61,7 @@ export const { positionals, values: { project_root, output_file, output_dir, ts_
61
61
 
62
62
  if (help) {
63
63
  console.log(`
64
- gen-api-types [options] [positionals
64
+ gen-api-types [options] [positionals]
65
65
 
66
66
  Options:
67
67
  -h, --help 输出帮助信息
package/src/cli/index.ts CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
 
4
4
  import * as path from 'path';
5
- import { Decorator, Project } from 'ts-morph';
5
+ import { Project } from 'ts-morph';
6
6
  import { pathToFileURL } from 'url';
7
- import { isExported, output_dir, output_file, positionals } from '../argv';
8
- import { C_DECO_NAME, M_DECO_NAME } from '../constant';
9
- import { GenTypeOptions } from '../decotators';
7
+ import { isExported, output_dir, output_file, positionals, ts_config_path } from '../argv';
8
+ import { DECO_NAME_C, DECO_NAME_M } from '../constant';
9
+ import { ExecuteApiMethodResult, executeState } from '../state';
10
10
  import { TypeTransformer } from '../transformer';
11
11
  import { formatResultList } from '../utils';
12
12
 
@@ -19,36 +19,15 @@ const out_put_target = path.resolve(output_dir, output_file)
19
19
  // console.log('sourceFilesGlob', sourceFilesGlob)
20
20
  // debugger
21
21
 
22
- type ApiMethodInfo = {
23
- className: string,
24
- methodName: string,
25
- fullMethodName: string,
26
- modulePath: string,
27
- args: any[],
28
- typeName: string,
29
- }
30
-
31
- function parserDecoArgs(deco: Decorator): GenTypeOptions {
32
- const errMsg = `装饰器参数错误`
33
- try {
34
- const optionStr = deco.getArguments()[0]?.getText()
35
- if (!optionStr) return {}
36
- const option = eval(`(()=>(${optionStr}))()`)
37
- if (typeof option === 'object') return option
38
- else throw new Error(errMsg)
39
- } catch (error) {
40
- console.warn('parserDecoArgs error', error)
41
- return {}
42
- }
43
22
 
44
- }
45
- function getApiMethodsInfo() {
23
+ function getModulePathSet() {
46
24
  console.log('sourceFilesGlob', sourceFilesGlob)
47
- const apiMethodsInfo: ApiMethodInfo[] = []
25
+ const modulePathSet: Set<string> = new Set()
48
26
  // 2. 使用ts-morph创建项目,便于解析源码
49
27
  // const project = new Project({ tsConfigFilePath: ts_config_path });
50
- const project = new Project({});
51
-
28
+ const project = new Project({
29
+ tsConfigFilePath: ts_config_path
30
+ });
52
31
  project.addSourceFilesAtPaths(sourceFilesGlob);
53
32
  // console.log('project.getSourceFiles().length', project.getSourceFiles().length)
54
33
  // debugger
@@ -57,82 +36,38 @@ function getApiMethodsInfo() {
57
36
 
58
37
  const classes = sourceFile.getClasses();
59
38
  for (const classDeclaration of classes) {
60
- const c_deco = classDeclaration.getDecorator(C_DECO_NAME)
39
+ const c_deco = classDeclaration.getDecorator(DECO_NAME_C)
61
40
  if (!c_deco) continue
62
41
  const methods = classDeclaration.getMethods();
63
42
  for (const method of methods) {
64
-
65
43
  const className = classDeclaration.getName()!;
66
44
  const methodName = method.getName();
67
45
  const fullMethodName = `${className}.${methodName}`;
68
46
 
69
- if (!method.isStatic()) {
70
- console.warn(`⚠️ ${fullMethodName} is not static method,only static method can be transformed`)
71
- continue
72
- }
73
47
  // 4. 检查方法是否被我们的装饰器标记
74
- const m_deco = method.getDecorator(M_DECO_NAME)
48
+ const m_deco = method.getDecorator(DECO_NAME_M)
75
49
  if (!m_deco) continue
50
+ executeState.emit(executeState.ADD_TASK, fullMethodName)
51
+ const modulePath = sourceFile.getFilePath()
52
+ if (!modulePathSet.has(modulePath)) modulePathSet.add(modulePath)
76
53
 
77
- const { args = [], typeName = `Response_${className}_${methodName}` } = parserDecoArgs(m_deco)
78
- apiMethodsInfo.push({
79
- className, methodName, fullMethodName, modulePath: sourceFile.getFilePath(),
80
- typeName, args
81
- })
82
54
 
83
55
  }
84
56
  }
85
57
  }
86
- return apiMethodsInfo
58
+ return modulePathSet
87
59
  }
88
60
 
89
- type ExecuteApiMethodResult = {
90
- data?: any, typeName: string, fullMethodName: string, error?: any
91
- }
92
- async function executeApiMethods(apiMethodsInfo: ApiMethodInfo[]): Promise<ExecuteApiMethodResult[]> {
93
- const apiModuleMap = new Map<string, any>();
94
- const taskList = apiMethodsInfo.map(async (apiMethodInfo) => {
95
- const { className, methodName, fullMethodName, modulePath, args, typeName } = apiMethodInfo
96
- console.log(`📋 处理 ${fullMethodName} ...`);
97
- let apiModule = null
98
- if (apiModuleMap.has(modulePath)) apiModule = apiModuleMap.get(modulePath)
99
- else {
100
- // apiModule = await import(modulePath)
101
- // debugger
102
- // console.log('modulePath', modulePath)
103
- // console.log('pathToFileURL(modulePath).href', pathToFileURL(modulePath).href)
104
-
105
- try {
106
- apiModule = await import(pathToFileURL(modulePath).href)
107
- // console.log('apiMethod', apiModule)
108
- if (apiModule) apiModuleMap.set(modulePath, apiModule)
109
- } catch (error) {
110
- console.log(`import ${modulePath} error \r\n`, error)
111
- return { error: `module error`, fullMethodName, typeName }
112
- }
113
- }
114
-
115
-
116
- const apiMethod = apiModule?.[className]?.[methodName]
117
- if (apiMethod && typeof apiMethod === 'function') {
118
- try {
119
- // console.log(`🔍 Calling ${fullMethodName} with args:`, args);
120
- const result = apiMethod.apply(apiModule, args)
121
- const data = await Promise.resolve(result)
122
- return { data, typeName, fullMethodName }
123
- } catch (error) {
124
- console.error(`❌ ${fullMethodName} execute error:`, error)
125
- return { error, fullMethodName, typeName }
126
- }
127
- } else {
128
- console.error(`❌ 无法获取 ${fullMethodName} 方法, 或不是可调用方法 `)
129
- return {
130
- error: `method error`, fullMethodName, typeName
131
- }
132
- }
133
- })
134
61
 
135
- return Promise.all(taskList)
62
+ /**
63
+ * 引入包含标记的方法的模块,触发装饰器执行,记录执行结果
64
+ * @param modulePathSet
65
+ * @returns
66
+ */
67
+ async function importApiModule<T extends string>(modulePathSet: Set<T>) {
68
+ return Promise.all(
69
+ Array.from(modulePathSet).map(modulePath => import(pathToFileURL(modulePath).href))
70
+ )
136
71
 
137
72
  }
138
73
 
@@ -155,13 +90,20 @@ function createDeclarationFile(successList: ExecuteApiMethodResult[]) {
155
90
 
156
91
 
157
92
  async function main() {
93
+ console.log('🚀 开始生成API类型...');
94
+ const modulePathSet = getModulePathSet();
95
+ if (modulePathSet.size == 0) {
96
+
97
+ console.error('⚠️ 未找到需要转换的API,请检查api_dir 和 gen_type装饰器标注是否正确!')
98
+ process.exit(1)
99
+ }
158
100
  try {
159
- console.log('🚀 开始生成API类型...');
160
- const apiMethodsInfo = getApiMethodsInfo();
161
- if (apiMethodsInfo.length == 0) return console.warn('⚠️ 未找到需要转换的API,请检查api_dir 和 get_type装饰器标注是否正确!')
162
- const executeList = await executeApiMethods(apiMethodsInfo);
101
+ await importApiModule(modulePathSet);
102
+ // executeState.addListener(executeState.TASKLIST_CLEAR, executeResultList=>{
103
+ // })
163
104
 
164
- const { successList: executeSuccessList, errorList: executeErrorList } = formatResultList(executeList)
105
+ const executeResultList = await executeState.promise
106
+ const { successList: executeSuccessList, errorList: executeErrorList } = formatResultList(executeResultList)
165
107
  if (executeErrorList.length) {
166
108
  console.group('请求结果:')
167
109
  console.table({
@@ -185,8 +127,12 @@ async function main() {
185
127
  if (transformSuccessList.length) console.log('✅ API 类型生成完成:', out_put_target);
186
128
  } catch (error) {
187
129
  console.error('❌ 出错了', error)
130
+ process.exitCode = 1
188
131
  }
189
132
 
133
+
134
+
135
+
190
136
  }
191
137
 
192
138
  main()
@@ -1,5 +1,5 @@
1
1
  export const GEN_TYPE_METADATA_KEY = Symbol('gen:type:metadata');
2
2
  /** 标记方法的装饰器名称 */
3
- export const M_DECO_NAME = 'gen_type_m';
3
+ export const DECO_NAME_M = 'gen_type_m';
4
4
  /** 标记类的装饰器名称 */
5
- export const C_DECO_NAME = 'gen_type_c';
5
+ export const DECO_NAME_C = 'gen_type_c';
@@ -1,5 +1,9 @@
1
1
  // import 'reflect-metadata';
2
2
 
3
+ import { DECO_NAME_C, DECO_NAME_M } from "../constant/index.ts";
4
+ import { executeState } from "../state/index.ts";
5
+ import { executeApiMethod } from "../utils/index.ts";
6
+
3
7
  // 定义一个唯一的metadata key
4
8
 
5
9
 
@@ -7,26 +11,45 @@ export interface GenTypeOptions {
7
11
  args?: any[];
8
12
  typeName?: string;
9
13
  }
10
- /**
11
- * 标记方法
12
- */
13
- export function gen_type_m({ args = [], typeName }: GenTypeOptions = {}) {
14
- return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
15
- // 只存储元数据,不执行任何逻辑
16
- // Reflect.defineMetadata(
17
- // GEN_TYPE_METADATA_KEY,
18
- // { args, typeName },
19
- // target,
20
- // propertyKey
21
- // );
22
- };
23
- }
24
14
 
25
15
 
26
- /**
27
- * 标记类
28
- */
29
- export function gen_type_c() {
30
- return function <T>(target: T) {
31
- };
32
- }
16
+ const EXEC_TIMEOUT = 5_000
17
+ export class GatDecorator {
18
+ static [DECO_NAME_C]() {
19
+ return function <T>(target: T) {
20
+ };
21
+ }
22
+ static [DECO_NAME_M]({ args = [], typeName }: GenTypeOptions = {}) {
23
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
24
+
25
+
26
+ (async () => {
27
+ const className = typeof target == 'function' ? target.name : target.constructor.name
28
+ const fullMethodName = `${className}.${propertyKey}`
29
+ console.log('fullMethodName', fullMethodName)
30
+ typeName ??= `Response_${className}_${propertyKey}`
31
+ let resultInfo = null
32
+ try {
33
+ const apiMethod = descriptor.value as Function
34
+ // console.log(`🔍 Calling ${fullMethodName} with args:`, args);
35
+
36
+ const data = await executeApiMethod({
37
+ timeout: EXEC_TIMEOUT,
38
+ method: () => apiMethod.apply(target, args)
39
+ })
40
+
41
+ resultInfo = { data, typeName, fullMethodName }
42
+ } catch (error) {
43
+ console.error(`❌ ${fullMethodName} execute error:`, error)
44
+ resultInfo = { error, fullMethodName, typeName }
45
+ } finally {
46
+ executeState.emit(executeState.EXECUTE_END, resultInfo)
47
+ }
48
+ })();
49
+
50
+ };
51
+ }
52
+ }
53
+
54
+ export const gen_type_c = GatDecorator[DECO_NAME_C]
55
+ export const gen_type_m = GatDecorator[DECO_NAME_M]
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
- export { C_DECO_NAME, M_DECO_NAME } from './constant';
2
- export * from './decotators';
1
+ export * from './constant/index.ts';
2
+ export * from './decotators/index.ts';
3
+ export * from './plugins/index.ts';
3
4
 
@@ -0,0 +1 @@
1
+ export * from './vite/index.ts';
@@ -0,0 +1,46 @@
1
+ import type { Plugin } from 'vite';
2
+ import { DECO_NAME_C, DECO_NAME_M } from '../../constant/index.ts';
3
+
4
+ function escapeRegExp(value: string) {
5
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
6
+ }
7
+ /**
8
+ * 去掉装饰器,用于在项目运行/构建时,gen-api-types的装饰器不会有任何副作用,影响到原来项目代码
9
+ * @param decorators
10
+ * @returns
11
+ */
12
+ export function removeDecorators(decorators: string[]): Plugin {
13
+ const names = decorators.map(escapeRegExp).join('|');
14
+
15
+ const decoratorRegex = new RegExp(
16
+ `^[ \\t]*@(?:${names})(?:\\s*\\([\\s\\S]*?\\))?[ \\t]*\\r?\\n?`,
17
+ 'gm'
18
+ );
19
+
20
+ return {
21
+ name: 'removeDecorators',
22
+ enforce: 'pre',
23
+
24
+ transform(code, id) {
25
+ if (!/\.(ts|tsx)(\?.*)?$/.test(id)) {
26
+ return;
27
+ }
28
+
29
+ const transformedCode = code.replace(decoratorRegex, '');
30
+
31
+ if (transformedCode === code) {
32
+ return;
33
+ }
34
+
35
+ return {
36
+ code: transformedCode,
37
+ map: null
38
+ };
39
+ }
40
+ };
41
+ }
42
+
43
+ export function removeGatDecorators() {
44
+ const decorators = [DECO_NAME_M, DECO_NAME_C]
45
+ return removeDecorators(decorators)
46
+ }
@@ -0,0 +1,45 @@
1
+ import EventEmitter from "events"
2
+ import { promiseWithResolvers } from "../utils/index.ts"
3
+
4
+ export type ExecuteApiMethodResult = {
5
+ data?: any, typeName: string, fullMethodName: string, error?: any
6
+ }
7
+
8
+
9
+
10
+
11
+
12
+
13
+ export class ExecuteState extends EventEmitter {
14
+
15
+ EXECUTE_END = 'executeEnd'
16
+ TASKLIST_CLEAR = 'taskClear'
17
+ ADD_TASK = 'addTask'
18
+ private resultList: ExecuteApiMethodResult[] = []
19
+ private taskList: string[] = []
20
+ promiseWithResolvers = promiseWithResolvers<ExecuteApiMethodResult[]>()
21
+ constructor() {
22
+ super()
23
+ this.init()
24
+ }
25
+ init() {
26
+ this.addListener(this.EXECUTE_END, (result: ExecuteApiMethodResult) => {
27
+ this.resultList.push(result)
28
+ if (this.resultList.length === this.taskList.length) {
29
+ this.emit(this.TASKLIST_CLEAR, this.resultList)
30
+ this.promiseWithResolvers?.resolve(this.resultList)
31
+ }
32
+ })
33
+
34
+ this.addListener(this.ADD_TASK, (taskName) => {
35
+ this.taskList.push(taskName)
36
+ })
37
+ }
38
+
39
+ get promise() {
40
+ return this.promiseWithResolvers?.promise
41
+ }
42
+
43
+ }
44
+
45
+ export const executeState = new ExecuteState()
@@ -6,3 +6,42 @@ export function formatResultList<T extends ResultInfoBase>(list: T[]) {
6
6
  return acc
7
7
  }, { successList: [] as Omit<T, 'error'>[], errorList: [] as T[] })
8
8
  }
9
+
10
+ export function promiseWithResolvers<T>() {
11
+ let promise!: Promise<T>
12
+ let resolve!: (value: T | PromiseLike<T>) => void
13
+ let reject!: (reason?: any) => void
14
+ promise = new Promise((res, rej) => {
15
+ resolve = res
16
+ reject = rej
17
+ })
18
+ return {
19
+ promise, resolve, reject
20
+ }
21
+ }
22
+
23
+ export async function executeApiMethod<T>({
24
+ method,
25
+ timeout = 10 * 1000
26
+ }: {
27
+ method: () => T | PromiseLike<T>
28
+ timeout?: number
29
+ }): Promise<T> {
30
+ let timer: ReturnType<typeof setTimeout> | undefined
31
+
32
+ const timeoutPromise = new Promise<never>((_, reject) => {
33
+ timer = setTimeout(() => {
34
+ reject(new Error(`API execution timed out after ${timeout}ms`))
35
+ }, timeout)
36
+ })
37
+
38
+ try {
39
+ return await Promise.race([
40
+ Promise.resolve().then(method),
41
+ timeoutPromise
42
+ ])
43
+ } finally {
44
+ if (timer) clearTimeout(timer)
45
+ }
46
+ }
47
+
package/tsconfig.json CHANGED
@@ -30,7 +30,7 @@
30
30
  /* Modules */
31
31
  "module": "esnext" /* Specify what module code is generated. */,
32
32
  // "rootDir": "./", /* Specify the root folder within your source files. */
33
- "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
33
+ "moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */,
34
34
  // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
35
35
  // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
36
36
  // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
@@ -38,7 +38,7 @@
38
38
  // "types": [], /* Specify type package names to be included without being referenced in a source file. */
39
39
  // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
40
40
  // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
41
- // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
41
+ "allowImportingTsExtensions": true /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */,
42
42
  // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
43
43
  // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
44
44
  // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
@@ -59,7 +59,7 @@
59
59
  // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
60
60
  // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
61
61
  // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
62
- // "noEmit": true, /* Disable emitting files from a compilation. */
62
+ "noEmit": true /* Disable emitting files from a compilation. */,
63
63
  // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
64
64
  // "outDir": "./", /* Specify an output folder for all emitted files. */
65
65
  // "removeComments": true, /* Disable emitting comments. */