@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 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
- [![npm version](https://img.shields.io/npm/v/@genapi/pipeline)](https://npmjs.com/package/@genapi/pipeline)
6
- [![npm downloads](https://img.shields.io/npm/dm/@genapi/pipeline)](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 AST: request code and typings for each output.
27
-
28
- ### `config(userConfig)`
29
-
30
- Normalizes pipeline config: output paths, responseType, baseURL (from meta), and builds ConfigRead with inputs/outputs.
31
-
32
- ### `default(config, original, parser, compiler, generate, dest)`
33
-
34
- Builds a GenAPI pipeline from five steps: config → original → parser → compiler → generate → dest.
35
-
36
- ### `dest(configRead)`
37
-
38
- Writes output files from configRead.outputs (code to path).
39
-
40
- ### `generate(configRead)`
41
-
42
- Generates code string from AST for each output and formats with Prettier.
43
-
44
- ### `original(configRead)`
45
-
46
- Fetches source: resolves uri/http/json from configRead.inputs and sets configRead.source.
47
-
48
- <!-- /automd -->
49
-
50
- ## License
51
-
52
- [MIT](https://github.com/hairyf/genapi/blob/main/LICENSE)
53
-
54
- <!-- automd:with-automd -->
55
-
56
- ---
57
-
58
- _🤖 auto updated with [automd](https://automd.unjs.io)_
59
-
60
- <!-- /automd -->
1
+ # @genapi/pipeline
2
+
3
+ <!-- automd:badges -->
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@genapi/pipeline)](https://npmjs.com/package/@genapi/pipeline)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@genapi/pipeline)](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/request.d.ts
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
- * Compiles configRead graphs to typings code string using knitwork-x.
48
+ * 统一编译:按 scope graphs.scopes[scope] 生成一段完整 TS 代码(comments imports → variables → functions → typings → interfaces)。
44
49
  */
45
- declare function compilerTsTypingsDeclaration(configRead: ApiPipeline.ConfigRead, comment?: boolean): string;
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 graphs to code string: request and typings for each output.
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, compiler, compilerTsRequestDeclaration, compilerTsTypingsDeclaration, config, pipeline as default, dest, formatTypescript, generate, original };
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/typings.ts
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
- const innerTemplate = generateMockTemplate(propType, interfaceMap, nextVisited);
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 || []).forEach((item) => {
112
- const functionCode = genFunction({
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 (!config.meta?.mockjs || !responseType || responseType === "void" || responseType === "any") return;
129
- const mockTemplate = generateMockTemplate(responseType, interfaceMap);
130
- functionBlocks.push(`${item.name}.mock = () => Mock.mock(${mockTemplate});`);
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 graphs to code string: request and typings for each output.
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 === "request" && !configRead.config.meta?.onlyDeclaration) output.code = compilerTsRequestDeclaration(configRead);
147
- if (output.type === "typings") output.code = compilerTsTypingsDeclaration(configRead);
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 imports = [isTypescript && isGenerateType && {
176
- name: "Types",
177
- value: importTypePath,
178
- type: true,
179
- namespace: true
180
- }];
181
- const outputs = [{
182
- type: "request",
183
- root: path.join(userRoot, path.dirname(userConfig.output.main)),
184
- path: path.join(userRoot, userConfig.output.main)
185
- }];
186
- const typings = [!!userConfig.meta.responseType?.infer && {
187
- export: true,
188
- name: "Infer<T>",
189
- value: userConfig.meta.responseType.infer
190
- }];
191
- if (userConfig.output.type !== false) outputs.push({
192
- type: "typings",
193
- root: path.join(userRoot, path.dirname(userConfig.output.type)),
194
- import: importTypePath,
195
- path: path.join(userRoot, userConfig.output.type)
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
- imports: imports.filter(Boolean),
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 { compiler, compilerTsRequestDeclaration, compilerTsTypingsDeclaration, config, src_default as default, dest, formatTypescript, generate, original };
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.0.0",
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.2.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.0.0",
34
- "@genapi/transform": "4.0.0"
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",