@genapi/pipeline 4.0.0 → 4.1.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/LICENSE.md +21 -21
- package/README.md +111 -60
- package/dist/index.d.mts +44 -9
- package/dist/index.mjs +179 -109
- package/package.json +4 -4
package/LICENSE.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025-PRESENT Hairyf <https://github.com/hairyf>
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-PRESENT Hairyf <https://github.com/hairyf>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,60 +1,111 @@
|
|
|
1
|
-
# @genapi/pipeline
|
|
2
|
-
|
|
3
|
-
<!-- automd:badges -->
|
|
4
|
-
|
|
5
|
-
[](https://npmjs.com/package/@genapi/pipeline)
|
|
6
|
-
[](https://npm.chart.dev/@genapi/pipeline)
|
|
7
|
-
|
|
8
|
-
<!-- /automd -->
|
|
9
|
-
|
|
10
|
-
GenAPI pipeline core: config reading, data source fetching, parsing, compilation, generation, and output. Composable for custom generation flows.
|
|
11
|
-
|
|
12
|
-
## Installation
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
pnpm add @genapi/pipeline
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## API
|
|
19
|
-
|
|
20
|
-
<!-- automd:jsdocs src=src/index.ts -->
|
|
21
|
-
|
|
22
|
-
## Pipeline
|
|
23
|
-
|
|
24
|
-
### `compiler(configRead)`
|
|
25
|
-
|
|
26
|
-
Compiles graphs to
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
# @genapi/pipeline
|
|
2
|
+
|
|
3
|
+
<!-- automd:badges -->
|
|
4
|
+
|
|
5
|
+
[](https://npmjs.com/package/@genapi/pipeline)
|
|
6
|
+
[](https://npm.chart.dev/@genapi/pipeline)
|
|
7
|
+
|
|
8
|
+
<!-- /automd -->
|
|
9
|
+
|
|
10
|
+
GenAPI pipeline core: config reading, data source fetching, parsing, compilation, generation, and output. Composable for custom generation flows.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @genapi/pipeline
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## API
|
|
19
|
+
|
|
20
|
+
<!-- automd:jsdocs src=src/index.ts -->
|
|
21
|
+
|
|
22
|
+
## Pipeline
|
|
23
|
+
|
|
24
|
+
### `compiler(configRead)`
|
|
25
|
+
|
|
26
|
+
Compiles graphs to code string: request and typings for each output.
|
|
27
|
+
|
|
28
|
+
**Example:**
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
compiler(configRead)
|
|
32
|
+
configRead.outputs.forEach(o => console.log(o.code))
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `config(userConfig)`
|
|
36
|
+
|
|
37
|
+
Normalizes pipeline config: output paths, responseType, baseURL, and builds ConfigRead with inputs/outputs.
|
|
38
|
+
|
|
39
|
+
**Example:**
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const configRead = config(defineConfig({ input: 'openapi.json', output: { main: 'src/api.ts' } }))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `default(config, original, parser, compiler, generate, dest)`
|
|
46
|
+
|
|
47
|
+
Builds a GenAPI pipeline from five steps: config → original → parser → compiler → generate → dest.
|
|
48
|
+
|
|
49
|
+
**Example:**
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const run = pipeline(config, original, parser, compiler, generate, dest)
|
|
53
|
+
await run(defineConfig({ input: 'openapi.json', output: { main: 'src/api.ts' } }))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `dest(configRead)`
|
|
57
|
+
|
|
58
|
+
Writes output files from configRead.outputs (code to path).
|
|
59
|
+
|
|
60
|
+
**Example:**
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
await dest(configRead)
|
|
64
|
+
// Writes configRead.outputs[].code to configRead.outputs[].path
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `generate(configRead, options?)`
|
|
68
|
+
|
|
69
|
+
Formats code for each output with Prettier.
|
|
70
|
+
|
|
71
|
+
**Example:**
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
await generate(configRead)
|
|
75
|
+
await generate(configRead, { printWidth: 100 })
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `original(configRead)`
|
|
79
|
+
|
|
80
|
+
Fetches source: resolves uri/http/json from configRead.inputs and sets configRead.source. Transforms the source based on parser configuration (wpapi -> swagger2, swagger -> unchanged). Supports YAML source URLs (e.g. .yaml / .yml); uses confbox for parsing (same as undocs).
|
|
81
|
+
|
|
82
|
+
**Example:**
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
await original(configRead)
|
|
86
|
+
// configRead.source is set from uri/http/json; wpapi transformed to swagger2 if parser is 'wpapi'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `formatTypescript(code, options?)`
|
|
90
|
+
|
|
91
|
+
Formats TypeScript/JavaScript code string with Prettier.
|
|
92
|
+
|
|
93
|
+
**Example:**
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const formatted = await formatTypescript('const x=1')
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
<!-- /automd -->
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
[MIT](https://github.com/hairyf/genapi/blob/main/LICENSE)
|
|
104
|
+
|
|
105
|
+
<!-- automd:with-automd -->
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
_🤖 auto updated with [automd](https://automd.unjs.io)_
|
|
110
|
+
|
|
111
|
+
<!-- /automd -->
|
package/dist/index.d.mts
CHANGED
|
@@ -3,14 +3,17 @@ import { ApiPipeline } from "@genapi/shared";
|
|
|
3
3
|
//#region src/pipeline/index.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Pipeline read (input) step: turns raw config into internal config + inputs.
|
|
6
|
+
* @description First step: normalizes user config and builds ConfigRead (inputs, outputs, graphs).
|
|
6
7
|
*/
|
|
7
8
|
type PipelineRead<Config, ConfigRead> = (config: Config) => ConfigRead | Promise<ConfigRead>;
|
|
8
9
|
/**
|
|
9
10
|
* Middle step that receives and returns config read; used for original, parser, compiler, generate.
|
|
11
|
+
* @description Each step may mutate configRead (e.g. set source, graphs, output.code).
|
|
10
12
|
*/
|
|
11
13
|
type PipelineFlow<ConfigRead> = (configRead: ConfigRead) => ConfigRead | Promise<ConfigRead>;
|
|
12
14
|
/**
|
|
13
15
|
* Pipeline dest (output) step: writes files from config read.
|
|
16
|
+
* @description Last step: writes configRead.outputs[].code to configRead.outputs[].path.
|
|
14
17
|
*/
|
|
15
18
|
type PipelineDest<ConfigRead> = (configRead: ApiPipeline.ConfigRead) => void | Promise<void>;
|
|
16
19
|
/**
|
|
@@ -24,6 +27,11 @@ type PipelineDest<ConfigRead> = (configRead: ApiPipeline.ConfigRead) => void | P
|
|
|
24
27
|
* @param dest - Writes output files
|
|
25
28
|
* @returns A function that runs the pipeline for a given config
|
|
26
29
|
* @group Pipeline
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const run = pipeline(config, original, parser, compiler, generate, dest)
|
|
33
|
+
* await run(defineConfig({ input: 'openapi.json', output: { main: 'src/api.ts' } }))
|
|
34
|
+
* ```
|
|
27
35
|
*/
|
|
28
36
|
declare function pipeline<Config = ApiPipeline.Config, ConfigRead = ApiPipeline.ConfigRead>(config: PipelineRead<Config, ConfigRead>, original: PipelineFlow<ConfigRead>, parser: PipelineFlow<ConfigRead>, compiler: PipelineFlow<ConfigRead>, generate: PipelineFlow<ConfigRead>, dest: PipelineDest<ConfigRead>): ApiPipeline.Pipeline;
|
|
29
37
|
declare namespace pipeline {
|
|
@@ -35,20 +43,17 @@ declare namespace pipeline {
|
|
|
35
43
|
var dest: typeof dest;
|
|
36
44
|
}
|
|
37
45
|
//#endregion
|
|
38
|
-
//#region src/compiler/
|
|
39
|
-
declare function compilerTsRequestDeclaration(configRead: ApiPipeline.ConfigRead): string;
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region src/compiler/typings.d.ts
|
|
46
|
+
//#region src/compiler/scope.d.ts
|
|
42
47
|
/**
|
|
43
|
-
*
|
|
48
|
+
* 统一编译:按 scope 从 graphs.scopes[scope] 生成一段完整 TS 代码(comments → imports → variables → functions → typings → interfaces)。
|
|
44
49
|
*/
|
|
45
|
-
declare function
|
|
50
|
+
declare function compile(configRead: ApiPipeline.ConfigRead, scope: string): string;
|
|
46
51
|
//#endregion
|
|
47
52
|
//#region src/compiler/index.d.ts
|
|
48
53
|
/**
|
|
49
|
-
* Compiles
|
|
54
|
+
* Compiles each output from graphs.scopes[output.type]; onlyDeclaration 时仅编译 type 为 "type" 的 output。
|
|
50
55
|
*
|
|
51
|
-
* @param configRead - ConfigRead with graphs and outputs
|
|
56
|
+
* @param configRead - ConfigRead with graphs.scopes and outputs
|
|
52
57
|
* @returns Same configRead with output.code set
|
|
53
58
|
* @group Pipeline
|
|
54
59
|
*/
|
|
@@ -61,6 +66,10 @@ declare function compiler(configRead: ApiPipeline.ConfigRead): ApiPipeline.Confi
|
|
|
61
66
|
* @param userConfig - Raw config from defineConfig
|
|
62
67
|
* @returns ConfigRead (config + inputs + outputs)
|
|
63
68
|
* @group Pipeline
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const configRead = config(defineConfig({ input: 'openapi.json', output: { main: 'src/api.ts' } }))
|
|
72
|
+
* ```
|
|
64
73
|
*/
|
|
65
74
|
declare function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead<Required<ApiPipeline.Config>>;
|
|
66
75
|
//#endregion
|
|
@@ -70,6 +79,11 @@ declare function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead<
|
|
|
70
79
|
*
|
|
71
80
|
* @param configRead - ConfigRead with outputs[].path and outputs[].code
|
|
72
81
|
* @group Pipeline
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* await dest(configRead)
|
|
85
|
+
* // Writes configRead.outputs[].code to configRead.outputs[].path
|
|
86
|
+
* ```
|
|
73
87
|
*/
|
|
74
88
|
declare function dest(configRead: ApiPipeline.ConfigRead): Promise<void>;
|
|
75
89
|
//#endregion
|
|
@@ -80,8 +94,24 @@ declare function dest(configRead: ApiPipeline.ConfigRead): Promise<void>;
|
|
|
80
94
|
* @param configRead - ConfigRead with outputs[].code
|
|
81
95
|
* @returns Same configRead with outputs[].code formatted
|
|
82
96
|
* @group Pipeline
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* await generate(configRead)
|
|
100
|
+
* await generate(configRead, { printWidth: 100 })
|
|
101
|
+
* ```
|
|
83
102
|
*/
|
|
84
103
|
declare function generate(configRead: ApiPipeline.ConfigRead, options?: any): Promise<ApiPipeline.ConfigRead<ApiPipeline.Config>>;
|
|
104
|
+
/**
|
|
105
|
+
* Formats TypeScript/JavaScript code string with Prettier.
|
|
106
|
+
*
|
|
107
|
+
* @param code - Source code to format
|
|
108
|
+
* @param options - Prettier options (merged with parser: 'typescript', printWidth: 800)
|
|
109
|
+
* @returns Formatted code string
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* const formatted = await formatTypescript('const x=1')
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
85
115
|
declare function formatTypescript(code: string, options?: any): Promise<string>;
|
|
86
116
|
//#endregion
|
|
87
117
|
//#region src/original/index.d.ts
|
|
@@ -93,7 +123,12 @@ declare function formatTypescript(code: string, options?: any): Promise<string>;
|
|
|
93
123
|
* @param configRead - ConfigRead with inputs (uri, http, or json)
|
|
94
124
|
* @returns Same configRead with source set and transformed if needed
|
|
95
125
|
* @group Pipeline
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* await original(configRead)
|
|
129
|
+
* // configRead.source is set from uri/http/json; wpapi transformed to swagger2 if parser is 'wpapi'
|
|
130
|
+
* ```
|
|
96
131
|
*/
|
|
97
132
|
declare function original(configRead: ApiPipeline.ConfigRead): Promise<ApiPipeline.ConfigRead<ApiPipeline.Config>>;
|
|
98
133
|
//#endregion
|
|
99
|
-
export { type PipelineDest, type PipelineFlow, type PipelineRead,
|
|
134
|
+
export { type PipelineDest, type PipelineFlow, type PipelineRead, compile, compiler, config, pipeline as default, dest, formatTypescript, generate, original };
|
package/dist/index.mjs
CHANGED
|
@@ -9,73 +9,9 @@ import { wpapiToSwagger2 } from "@genapi/transform";
|
|
|
9
9
|
import { parseYAML } from "confbox";
|
|
10
10
|
import { ofetch } from "ofetch";
|
|
11
11
|
|
|
12
|
-
//#region src/compiler/
|
|
13
|
-
/**
|
|
14
|
-
* Compiles configRead graphs to typings code string using knitwork-x.
|
|
15
|
-
*/
|
|
16
|
-
function compilerTsTypingsDeclaration(configRead, comment = true) {
|
|
17
|
-
configRead.graphs.comments = configRead.graphs.comments || [];
|
|
18
|
-
configRead.graphs.typings = configRead.graphs.typings || [];
|
|
19
|
-
configRead.graphs.interfaces = configRead.graphs.interfaces || [];
|
|
20
|
-
const sections = [];
|
|
21
|
-
if (comment && configRead.graphs.comments.length > 0) sections.push(genComment(configRead.graphs.comments.join("\n"), { block: true }));
|
|
22
|
-
const typings = configRead.graphs.typings.map((item) => {
|
|
23
|
-
return genTypeAlias(item.name, item.value, { export: !!item.export });
|
|
24
|
-
});
|
|
25
|
-
if (typings.length > 0) sections.push(typings.join("\n"));
|
|
26
|
-
const interfaces = configRead.graphs.interfaces.map((item) => {
|
|
27
|
-
const properties = (item.properties || []).map((p) => ({
|
|
28
|
-
name: p.name,
|
|
29
|
-
type: p.type ?? "any",
|
|
30
|
-
optional: !p.required,
|
|
31
|
-
jsdoc: p.description
|
|
32
|
-
}));
|
|
33
|
-
return genInterface(item.name, properties, { export: !!item.export });
|
|
34
|
-
});
|
|
35
|
-
if (interfaces.length > 0) sections.push(interfaces.join("\n"));
|
|
36
|
-
return sections.filter(Boolean).join("\n\n");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
//#endregion
|
|
40
|
-
//#region src/compiler/request.ts
|
|
41
|
-
function compilerTsRequestDeclaration(configRead) {
|
|
42
|
-
const { graphs, config, outputs } = configRead;
|
|
43
|
-
const sections = [];
|
|
44
|
-
const interfaceMap = new Map((graphs.interfaces || []).map((i) => [i.name, i]));
|
|
45
|
-
if (graphs.comments?.length) sections.push(genComment(graphs.comments.join("\n"), { block: true }));
|
|
46
|
-
const importLines = (graphs.imports || []).map((item) => {
|
|
47
|
-
const isType = !!item.type;
|
|
48
|
-
if (item.namespace) return genImport(item.value, {
|
|
49
|
-
name: "*",
|
|
50
|
-
as: item.name
|
|
51
|
-
}, { type: isType });
|
|
52
|
-
if (item.name && !item.names) return genImport(item.value, item.name, { type: isType });
|
|
53
|
-
const names = item.names || [];
|
|
54
|
-
const imports = item.name ? [{
|
|
55
|
-
name: "default",
|
|
56
|
-
as: item.name
|
|
57
|
-
}, ...names.map((n) => ({ name: n }))] : names;
|
|
58
|
-
return genImport(item.value, imports, { type: isType });
|
|
59
|
-
});
|
|
60
|
-
if (config.meta?.mockjs) importLines.push(genImport("better-mock", { name: "Mock" }));
|
|
61
|
-
if (importLines.length) sections.push(importLines.join("\n"));
|
|
62
|
-
const variables = (graphs.variables || []).map((item) => genVariable(item.name, item.value ?? "", {
|
|
63
|
-
export: !!item.export,
|
|
64
|
-
kind: item.flag
|
|
65
|
-
}));
|
|
66
|
-
if (variables.length) sections.push(variables.join("\n"));
|
|
67
|
-
const functions = genFunctionsWithMock(graphs.functions || [], interfaceMap, config);
|
|
68
|
-
if (functions.length) sections.push(functions.join("\n\n"));
|
|
69
|
-
const isGenerateType = outputs.some((v) => v.type === "typings");
|
|
70
|
-
const isTsRequest = outputs.some((v) => v.type === "request" && v.path.endsWith(".ts"));
|
|
71
|
-
if (!isGenerateType && isTsRequest) sections.push(compilerTsTypingsDeclaration(configRead, false));
|
|
72
|
-
return sections.filter(Boolean).join("\n\n");
|
|
73
|
-
}
|
|
12
|
+
//#region src/compiler/scope.ts
|
|
74
13
|
const RE_NAMESPACE = /^(Types\.|import\(['"][^'"]+['"]\)\.)/g;
|
|
75
14
|
const RE_ARRAY_TYPE = /^(.+)\[\]$/;
|
|
76
|
-
/**
|
|
77
|
-
* 优化后的 Mock 模板生成函数
|
|
78
|
-
*/
|
|
79
15
|
function generateMockTemplate(typeName, interfaceMap, visited = /* @__PURE__ */ new Set()) {
|
|
80
16
|
const cleanTypeName = typeName.replace(RE_NAMESPACE, "").trim();
|
|
81
17
|
const arrayMatch = cleanTypeName.match(RE_ARRAY_TYPE);
|
|
@@ -100,16 +36,15 @@ function generateMockTemplate(typeName, interfaceMap, visited = /* @__PURE__ */
|
|
|
100
36
|
const itemTemplate = generateMockTemplate(propType.slice(0, -2), interfaceMap, nextVisited);
|
|
101
37
|
return `'${prop.name}|1-5': ${itemTemplate}`;
|
|
102
38
|
}
|
|
103
|
-
|
|
104
|
-
return `'${prop.name}': ${innerTemplate}`;
|
|
39
|
+
return `'${prop.name}': ${generateMockTemplate(propType, interfaceMap, nextVisited)}`;
|
|
105
40
|
});
|
|
106
41
|
visited.delete(cleanTypeName);
|
|
107
42
|
return properties.length > 0 ? `{\n ${properties.join(",\n ")}\n }` : "{}";
|
|
108
43
|
}
|
|
109
44
|
function genFunctionsWithMock(functions, interfaceMap, config) {
|
|
110
45
|
const functionBlocks = [];
|
|
111
|
-
(functions || [])
|
|
112
|
-
|
|
46
|
+
for (const item of functions || []) {
|
|
47
|
+
functionBlocks.push(genFunction({
|
|
113
48
|
export: true,
|
|
114
49
|
name: item.name,
|
|
115
50
|
parameters: (item.parameters || []).map((p) => ({
|
|
@@ -122,29 +57,90 @@ function genFunctionsWithMock(functions, interfaceMap, config) {
|
|
|
122
57
|
returnType: item.returnType,
|
|
123
58
|
generics: item.generics,
|
|
124
59
|
jsdoc: item.description
|
|
125
|
-
});
|
|
126
|
-
functionBlocks.push(functionCode);
|
|
60
|
+
}));
|
|
127
61
|
const responseType = inject(item.name)?.responseType;
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
62
|
+
if (config.meta?.mockjs && responseType && responseType !== "void" && responseType !== "any") {
|
|
63
|
+
const mockTemplate = generateMockTemplate(responseType, interfaceMap);
|
|
64
|
+
functionBlocks.push(`${item.name}.mock = () => Mock.mock(${mockTemplate});`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
132
67
|
return functionBlocks;
|
|
133
68
|
}
|
|
69
|
+
function emptySlice() {
|
|
70
|
+
return {
|
|
71
|
+
comments: [],
|
|
72
|
+
functions: [],
|
|
73
|
+
imports: [],
|
|
74
|
+
variables: [],
|
|
75
|
+
typings: [],
|
|
76
|
+
interfaces: []
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function varFiledToRaw(name) {
|
|
80
|
+
if (name.length >= 2 && name.startsWith("'") && name.endsWith("'")) return name.slice(1, -1);
|
|
81
|
+
return name;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 统一编译:按 scope 从 graphs.scopes[scope] 生成一段完整 TS 代码(comments → imports → variables → functions → typings → interfaces)。
|
|
85
|
+
*/
|
|
86
|
+
function compile(configRead, scope) {
|
|
87
|
+
const slice = configRead.graphs.scopes[scope] ?? emptySlice();
|
|
88
|
+
const { config } = configRead;
|
|
89
|
+
const sections = [];
|
|
90
|
+
if (slice.comments?.length) sections.push(genComment(slice.comments.join("\n"), { block: true }));
|
|
91
|
+
const importLines = (slice.imports || []).map((item) => {
|
|
92
|
+
const isType = !!item.type;
|
|
93
|
+
if (item.namespace) return genImport(item.value, {
|
|
94
|
+
name: "*",
|
|
95
|
+
as: item.name
|
|
96
|
+
}, { type: isType });
|
|
97
|
+
if (item.name && !item.names) return genImport(item.value, item.name, { type: isType });
|
|
98
|
+
const names = item.names || [];
|
|
99
|
+
const imports = item.name ? [{
|
|
100
|
+
name: "default",
|
|
101
|
+
as: item.name
|
|
102
|
+
}, ...names.map((n) => ({ name: n }))] : names;
|
|
103
|
+
return genImport(item.value, imports, { type: isType });
|
|
104
|
+
});
|
|
105
|
+
if (config.meta?.mockjs) importLines.push(genImport("better-mock", { name: "Mock" }));
|
|
106
|
+
if (importLines.length) sections.push(importLines.join("\n"));
|
|
107
|
+
const variables = (slice.variables || []).map((item) => genVariable(item.name, item.value ?? "", {
|
|
108
|
+
export: !!item.export,
|
|
109
|
+
kind: item.flag
|
|
110
|
+
}));
|
|
111
|
+
if (variables.length) sections.push(variables.join("\n"));
|
|
112
|
+
const interfaceMap = new Map((slice.interfaces || []).map((i) => [i.name, i]));
|
|
113
|
+
const functions = genFunctionsWithMock(slice.functions || [], interfaceMap, config);
|
|
114
|
+
if (functions.length) sections.push(functions.join("\n\n"));
|
|
115
|
+
const typings = (slice.typings || []).map((item) => genTypeAlias(item.name, item.value, { export: !!item.export }));
|
|
116
|
+
if (typings.length) sections.push(typings.join("\n"));
|
|
117
|
+
const interfaces = (slice.interfaces || []).map((item) => {
|
|
118
|
+
const properties = (item.properties || []).map((p) => ({
|
|
119
|
+
name: varFiledToRaw(p.name),
|
|
120
|
+
type: p.type ?? "any",
|
|
121
|
+
optional: !p.required,
|
|
122
|
+
jsdoc: p.description
|
|
123
|
+
}));
|
|
124
|
+
return genInterface(item.name, properties, { export: !!item.export });
|
|
125
|
+
});
|
|
126
|
+
if (interfaces.length) sections.push(interfaces.join("\n"));
|
|
127
|
+
return sections.filter(Boolean).join("\n\n");
|
|
128
|
+
}
|
|
134
129
|
|
|
135
130
|
//#endregion
|
|
136
131
|
//#region src/compiler/index.ts
|
|
137
132
|
/**
|
|
138
|
-
* Compiles
|
|
133
|
+
* Compiles each output from graphs.scopes[output.type]; onlyDeclaration 时仅编译 type 为 "type" 的 output。
|
|
139
134
|
*
|
|
140
|
-
* @param configRead - ConfigRead with graphs and outputs
|
|
135
|
+
* @param configRead - ConfigRead with graphs.scopes and outputs
|
|
141
136
|
* @returns Same configRead with output.code set
|
|
142
137
|
* @group Pipeline
|
|
143
138
|
*/
|
|
144
139
|
function compiler(configRead) {
|
|
140
|
+
const onlyDeclaration = !!configRead.config.meta?.onlyDeclaration;
|
|
145
141
|
for (const output of configRead.outputs) {
|
|
146
|
-
if (output.type
|
|
147
|
-
|
|
142
|
+
if (onlyDeclaration && output.type !== "type") continue;
|
|
143
|
+
output.code = compile(configRead, output.type);
|
|
148
144
|
}
|
|
149
145
|
return configRead;
|
|
150
146
|
}
|
|
@@ -157,6 +153,10 @@ function compiler(configRead) {
|
|
|
157
153
|
* @param userConfig - Raw config from defineConfig
|
|
158
154
|
* @returns ConfigRead (config + inputs + outputs)
|
|
159
155
|
* @group Pipeline
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* const configRead = config(defineConfig({ input: 'openapi.json', output: { main: 'src/api.ts' } }))
|
|
159
|
+
* ```
|
|
160
160
|
*/
|
|
161
161
|
function config(userConfig) {
|
|
162
162
|
userConfig.meta = userConfig.meta || {};
|
|
@@ -172,28 +172,40 @@ function config(userConfig) {
|
|
|
172
172
|
const isTypescript = userConfig.output.main.endsWith(".ts");
|
|
173
173
|
const isGenerateType = userConfig.output?.type !== false;
|
|
174
174
|
const importTypePath = userConfig.meta.import.type || getImportTypePath(userConfig.output.main, userConfig.output.type || "");
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
175
|
+
const outputs = [];
|
|
176
|
+
const scopes = {};
|
|
177
|
+
const outputKeys = [
|
|
178
|
+
"main",
|
|
179
|
+
...userConfig.output.type !== false ? ["type"] : [],
|
|
180
|
+
...objectOutputKeys(userConfig.output)
|
|
181
|
+
];
|
|
182
|
+
for (const key of outputKeys) {
|
|
183
|
+
const outPath = key === "main" ? userConfig.output.main : key === "type" ? userConfig.output.type : userConfig.output[key];
|
|
184
|
+
if (typeof outPath !== "string") continue;
|
|
185
|
+
outputs.push({
|
|
186
|
+
type: key,
|
|
187
|
+
root: path.join(userRoot, path.dirname(outPath)),
|
|
188
|
+
path: path.join(userRoot, outPath),
|
|
189
|
+
...key === "type" ? { import: importTypePath } : {}
|
|
190
|
+
});
|
|
191
|
+
scopes[key] = {
|
|
192
|
+
comments: [],
|
|
193
|
+
functions: [],
|
|
194
|
+
imports: key === "main" && isTypescript && isGenerateType ? [{
|
|
195
|
+
name: "Types",
|
|
196
|
+
value: importTypePath,
|
|
197
|
+
type: true,
|
|
198
|
+
namespace: true
|
|
199
|
+
}] : [],
|
|
200
|
+
variables: [],
|
|
201
|
+
typings: key === "type" && userConfig.meta.responseType?.infer ? [{
|
|
202
|
+
export: true,
|
|
203
|
+
name: "Infer<T>",
|
|
204
|
+
value: userConfig.meta.responseType.infer
|
|
205
|
+
}] : [],
|
|
206
|
+
interfaces: []
|
|
207
|
+
};
|
|
208
|
+
}
|
|
197
209
|
const inputs = {};
|
|
198
210
|
if (typeof userConfig.input === "string") inputs.uri = userConfig.input;
|
|
199
211
|
if (typeof userConfig.input === "object") Object.assign(inputs, userConfig.input);
|
|
@@ -202,21 +214,43 @@ function config(userConfig) {
|
|
|
202
214
|
inputs,
|
|
203
215
|
outputs,
|
|
204
216
|
graphs: {
|
|
205
|
-
|
|
206
|
-
variables: [],
|
|
207
|
-
comments: [],
|
|
208
|
-
functions: [],
|
|
209
|
-
interfaces: [],
|
|
210
|
-
typings: typings.filter(Boolean),
|
|
217
|
+
scopes,
|
|
211
218
|
response: userConfig.meta.responseType
|
|
212
219
|
}
|
|
213
220
|
};
|
|
214
221
|
provide({
|
|
215
222
|
config: userConfig,
|
|
216
|
-
configRead
|
|
223
|
+
configRead,
|
|
224
|
+
imports: block(configRead, "imports")
|
|
217
225
|
});
|
|
218
226
|
return configRead;
|
|
219
227
|
}
|
|
228
|
+
function block(configRead, key) {
|
|
229
|
+
return {
|
|
230
|
+
add(scope, item) {
|
|
231
|
+
let slice = configRead.graphs.scopes[scope];
|
|
232
|
+
if (!slice) {
|
|
233
|
+
slice = {
|
|
234
|
+
comments: [],
|
|
235
|
+
functions: [],
|
|
236
|
+
imports: [],
|
|
237
|
+
variables: [],
|
|
238
|
+
typings: [],
|
|
239
|
+
interfaces: []
|
|
240
|
+
};
|
|
241
|
+
configRead.graphs.scopes[scope] = slice;
|
|
242
|
+
}
|
|
243
|
+
if (Array.isArray(slice[key])) slice[key].push(item);
|
|
244
|
+
},
|
|
245
|
+
values(scope) {
|
|
246
|
+
const slice = configRead.graphs.scopes[scope];
|
|
247
|
+
return slice && Array.isArray(slice[key]) ? slice[key] : [];
|
|
248
|
+
},
|
|
249
|
+
all() {
|
|
250
|
+
return Object.values(configRead.graphs.scopes).flatMap((s) => s[key] || []);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
220
254
|
function prefix(path) {
|
|
221
255
|
return path.startsWith(".") ? path : `./${path}`;
|
|
222
256
|
}
|
|
@@ -226,6 +260,11 @@ function getImportTypePath(main, type) {
|
|
|
226
260
|
importTypePath = prefix(importTypePath).replace(".ts", "");
|
|
227
261
|
return importTypePath;
|
|
228
262
|
}
|
|
263
|
+
/** 从 output 对象中取除 main/type 外的 key(如 api),且值为 string 的 */
|
|
264
|
+
function objectOutputKeys(output) {
|
|
265
|
+
if (typeof output !== "object" || output == null) return [];
|
|
266
|
+
return Object.keys(output).filter((k) => k !== "main" && k !== "type" && typeof output[k] === "string");
|
|
267
|
+
}
|
|
229
268
|
|
|
230
269
|
//#endregion
|
|
231
270
|
//#region src/dest/index.ts
|
|
@@ -234,6 +273,11 @@ function getImportTypePath(main, type) {
|
|
|
234
273
|
*
|
|
235
274
|
* @param configRead - ConfigRead with outputs[].path and outputs[].code
|
|
236
275
|
* @group Pipeline
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* await dest(configRead)
|
|
279
|
+
* // Writes configRead.outputs[].code to configRead.outputs[].path
|
|
280
|
+
* ```
|
|
237
281
|
*/
|
|
238
282
|
async function dest(configRead) {
|
|
239
283
|
await Promise.all(configRead.outputs.map(async (output) => {
|
|
@@ -250,11 +294,27 @@ async function dest(configRead) {
|
|
|
250
294
|
* @param configRead - ConfigRead with outputs[].code
|
|
251
295
|
* @returns Same configRead with outputs[].code formatted
|
|
252
296
|
* @group Pipeline
|
|
297
|
+
* @example
|
|
298
|
+
* ```ts
|
|
299
|
+
* await generate(configRead)
|
|
300
|
+
* await generate(configRead, { printWidth: 100 })
|
|
301
|
+
* ```
|
|
253
302
|
*/
|
|
254
303
|
async function generate(configRead, options) {
|
|
255
304
|
for (const output of configRead.outputs || []) if (output.code) output.code = await formatTypescript(output.code, options);
|
|
256
305
|
return configRead;
|
|
257
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Formats TypeScript/JavaScript code string with Prettier.
|
|
309
|
+
*
|
|
310
|
+
* @param code - Source code to format
|
|
311
|
+
* @param options - Prettier options (merged with parser: 'typescript', printWidth: 800)
|
|
312
|
+
* @returns Formatted code string
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* const formatted = await formatTypescript('const x=1')
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
258
318
|
async function formatTypescript(code, options) {
|
|
259
319
|
return await format(code, {
|
|
260
320
|
printWidth: 800,
|
|
@@ -289,6 +349,11 @@ async function fetchSource(url, options) {
|
|
|
289
349
|
* @param configRead - ConfigRead with inputs (uri, http, or json)
|
|
290
350
|
* @returns Same configRead with source set and transformed if needed
|
|
291
351
|
* @group Pipeline
|
|
352
|
+
* @example
|
|
353
|
+
* ```ts
|
|
354
|
+
* await original(configRead)
|
|
355
|
+
* // configRead.source is set from uri/http/json; wpapi transformed to swagger2 if parser is 'wpapi'
|
|
356
|
+
* ```
|
|
292
357
|
*/
|
|
293
358
|
async function original(configRead) {
|
|
294
359
|
if (configRead.inputs.uri) configRead.source = await fetchSource(configRead.inputs.uri);
|
|
@@ -329,6 +394,11 @@ function readJsonSource(json) {
|
|
|
329
394
|
* @param dest - Writes output files
|
|
330
395
|
* @returns A function that runs the pipeline for a given config
|
|
331
396
|
* @group Pipeline
|
|
397
|
+
* @example
|
|
398
|
+
* ```ts
|
|
399
|
+
* const run = pipeline(config, original, parser, compiler, generate, dest)
|
|
400
|
+
* await run(defineConfig({ input: 'openapi.json', output: { main: 'src/api.ts' } }))
|
|
401
|
+
* ```
|
|
332
402
|
*/
|
|
333
403
|
function pipeline(config, original, parser, compiler, generate, dest) {
|
|
334
404
|
return pPipe(config, original, parser, compiler, generate, dest);
|
|
@@ -345,4 +415,4 @@ pipeline.dest = dest;
|
|
|
345
415
|
var src_default = pipeline;
|
|
346
416
|
|
|
347
417
|
//#endregion
|
|
348
|
-
export {
|
|
418
|
+
export { compile, compiler, config, src_default as default, dest, formatTypescript, generate, original };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genapi/pipeline",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.1.0",
|
|
5
5
|
"author": "Hairyf <wwu710632@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/hairyf/genapi#readme",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"confbox": "^0.2.4",
|
|
28
28
|
"fs-extra": "^11.3.3",
|
|
29
|
-
"knitwork-x": "^0.
|
|
29
|
+
"knitwork-x": "^0.3.1",
|
|
30
30
|
"ofetch": "^1.5.1",
|
|
31
31
|
"p-pipe": "^4.0.0",
|
|
32
32
|
"prettier": "^3.8.1",
|
|
33
|
-
"@genapi/shared": "4.
|
|
34
|
-
"@genapi/transform": "4.
|
|
33
|
+
"@genapi/shared": "4.1.0",
|
|
34
|
+
"@genapi/transform": "4.1.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/fs-extra": "^11.0.4",
|