wormajs 0.1.0 → 0.2.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/dist/constant.js +1 -9
- package/dist/core/parser/openApiParser/helper.js +91 -69
- package/dist/helper/config/GeneratorHelper.js +20 -18
- package/dist/helper/config/zType.js +2 -2
- package/dist/helper/progress.js +2 -2
- package/dist/plugins/presets/payloadModifier/hepler.js +29 -33
- package/dist/plugins/presets/payloadModifier/index.js +8 -8
- package/dist/plugins/presets/rename.js +5 -0
- package/dist/plugins/presets/tagModifier.js +6 -2
- package/package.json +1 -1
- package/typings/index.d.ts +9 -5
- package/typings/plugins.d.ts +47 -34
package/dist/constant.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// 枚举常量定义 — 统一管理项目中所有硬编码的字符串/数字常量
|
|
4
4
|
// ============================================================
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.FRAMEWORK_NAMES = exports.MODULE_TYPE_TO_KIND = exports.MODULE_TYPE_DIRS = exports.ModuleTypeDir = exports.PresetTemplateName = exports.TemplateSkipDir = exports.TemplatePlaceholder = exports.PluginName = exports.
|
|
6
|
+
exports.FRAMEWORK_NAMES = exports.MODULE_TYPE_TO_KIND = exports.MODULE_TYPE_DIRS = exports.ModuleTypeDir = exports.PresetTemplateName = exports.TemplateSkipDir = exports.TemplatePlaceholder = exports.PluginName = exports.FilterScope = exports.RenameScope = exports.ParameterIn = exports.TEMPLATE_EXTENSIONS = exports.FileExtension = exports.FrameworkName = exports.ModuleKind = exports.PlatformTypeEnum = exports.ConfigTypeEnum = exports.TemplateTypeEnum = void 0;
|
|
7
7
|
exports.getTypeFileExtension = getTypeFileExtension;
|
|
8
8
|
/** 模板代码生成类型 */
|
|
9
9
|
var TemplateTypeEnum;
|
|
@@ -86,14 +86,6 @@ var FilterScope;
|
|
|
86
86
|
FilterScope["URL"] = "url";
|
|
87
87
|
FilterScope["TAG"] = "tag";
|
|
88
88
|
})(FilterScope || (exports.FilterScope = FilterScope = {}));
|
|
89
|
-
/** 参数修改 scope 类型 */
|
|
90
|
-
var ModifierScope;
|
|
91
|
-
(function (ModifierScope) {
|
|
92
|
-
ModifierScope["PARAMS"] = "params";
|
|
93
|
-
ModifierScope["PATH_PARAMS"] = "pathParams";
|
|
94
|
-
ModifierScope["DATA"] = "data";
|
|
95
|
-
ModifierScope["RESPONSE"] = "response";
|
|
96
|
-
})(ModifierScope || (exports.ModifierScope = ModifierScope = {}));
|
|
97
89
|
/** 内建插件名称 */
|
|
98
90
|
var PluginName;
|
|
99
91
|
(function (PluginName) {
|
|
@@ -13,7 +13,6 @@ const swagger2openapi_1 = __importDefault(require("swagger2openapi"));
|
|
|
13
13
|
const poolManager_1 = require("../../../core/workerPool/poolManager");
|
|
14
14
|
const helper_1 = require("../../../helper");
|
|
15
15
|
const utils_1 = require("../../../utils");
|
|
16
|
-
const supportedExtname = ['json', 'yaml'];
|
|
17
16
|
function isSwagger2(data) {
|
|
18
17
|
return !!data?.swagger;
|
|
19
18
|
}
|
|
@@ -61,56 +60,72 @@ function convertSwagger2Async(data) {
|
|
|
61
60
|
});
|
|
62
61
|
});
|
|
63
62
|
}
|
|
64
|
-
//
|
|
65
|
-
async function
|
|
66
|
-
const [, extname] = /\.([^.]+)$/.exec(url) ?? [];
|
|
67
|
-
if (!supportedExtname.includes(extname)) {
|
|
68
|
-
throw helper_1.logger.throwError(`Unsupported file type: ${extname}`, {
|
|
69
|
-
url,
|
|
70
|
-
projectPath,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
63
|
+
// Read local openapi spec file as raw text
|
|
64
|
+
async function fetchRawLocalFile(url, projectPath = process.cwd()) {
|
|
73
65
|
const filePath = node_path_1.default.resolve(projectPath, url);
|
|
74
|
-
if (extname === 'yaml') {
|
|
75
|
-
return js_yaml_1.default.load(await promises_1.default.readFile(filePath, 'utf-8'));
|
|
76
|
-
}
|
|
77
|
-
// M6-C3: prefer async read, fallback to require() for environments where
|
|
78
|
-
// fs is mocked (e.g. memfs in tests) but the real file exists on disk
|
|
79
66
|
try {
|
|
80
|
-
return
|
|
67
|
+
return await promises_1.default.readFile(filePath, 'utf-8');
|
|
81
68
|
}
|
|
82
69
|
catch {
|
|
83
|
-
//
|
|
70
|
+
// M6-C3: fallback to require() for environments where fs is mocked
|
|
71
|
+
// (e.g. memfs in tests) but the real file exists on disk. Limited to JSON.
|
|
84
72
|
// eslint-disable-next-line ts/no-require-imports
|
|
85
|
-
return require(filePath);
|
|
73
|
+
return JSON.stringify(require(filePath), null, 2);
|
|
86
74
|
}
|
|
87
75
|
}
|
|
88
|
-
//
|
|
89
|
-
async function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
76
|
+
// Fetch remote openapi spec as raw text
|
|
77
|
+
async function fetchRawRemoteFile(url, fetchOptions) {
|
|
78
|
+
return (await (0, utils_1.fetchData)(url, fetchOptions)) ?? '';
|
|
79
|
+
}
|
|
80
|
+
const isRemoteUrl = (u) => /^https?:\/\//.test(u);
|
|
81
|
+
/**
|
|
82
|
+
* Fetch the raw spec text (JSON/YAML) from the first URL that succeeds.
|
|
83
|
+
* Returns the raw text together with the resolved URL; throws if all URLs fail.
|
|
84
|
+
*/
|
|
85
|
+
async function fetchRawText(urls, options) {
|
|
86
|
+
if (urls.length === 0) {
|
|
87
|
+
throw helper_1.logger.throwError('No URLs provided to fetch OpenAPI document');
|
|
95
88
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
89
|
+
const { projectPath, fetchOptions } = options;
|
|
90
|
+
// All URLs race in parallel: local files are fast, remote ones use network.
|
|
91
|
+
// Each task fetches the raw text AND validates that it is actually an OpenAPI/Swagger
|
|
92
|
+
// document before resolving. Invalid candidates (e.g. an HTML error page returned by
|
|
93
|
+
// a fallback URL) reject, so that Promise.any falls through to the next URL
|
|
94
|
+
// instead of resolving with garbage text.
|
|
95
|
+
const tasks = urls.map((u) => {
|
|
96
|
+
return (async () => {
|
|
97
|
+
const text = isRemoteUrl(u)
|
|
98
|
+
? await fetchRawRemoteFile(u, fetchOptions)
|
|
99
|
+
: await fetchRawLocalFile(u, projectPath);
|
|
100
|
+
// Quick parse + validity check (full parse + Swagger2→OpenAPI3 conversion
|
|
101
|
+
// happens later in parseSpec, after beforeSpecParse may rewrite the text).
|
|
102
|
+
let probe;
|
|
103
|
+
try {
|
|
104
|
+
probe = JSON.parse(text);
|
|
105
|
+
}
|
|
106
|
+
catch (jsonError) {
|
|
107
|
+
try {
|
|
108
|
+
probe = js_yaml_1.default.load(text);
|
|
109
|
+
}
|
|
110
|
+
catch (yamlError) {
|
|
111
|
+
throw new Error(`${u}: ${(jsonError instanceof Error ? jsonError.message : String(jsonError))} (YAML: ${yamlError instanceof Error ? yamlError.message : String(yamlError)})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!isValidOpenApiData(probe)) {
|
|
115
|
+
throw new Error(`${u} did not yield a valid OpenAPI/Swagger document`);
|
|
116
|
+
}
|
|
117
|
+
return { text, url: u };
|
|
118
|
+
})();
|
|
119
|
+
});
|
|
120
|
+
try {
|
|
121
|
+
return await Promise.any(tasks);
|
|
108
122
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
catch (err) {
|
|
124
|
+
const errors = (err instanceof AggregateError)
|
|
125
|
+
? err.errors.map((e) => e.message)
|
|
126
|
+
: [err.message];
|
|
127
|
+
throw helper_1.logger.throwError(`Unable to retrieve valid OpenAPI document from any URL:\n${errors.join('\n')}`);
|
|
112
128
|
}
|
|
113
|
-
return data;
|
|
114
129
|
}
|
|
115
130
|
// Validate OpenAPI data
|
|
116
131
|
function isValidOpenApiData(data) {
|
|
@@ -124,49 +139,56 @@ function isValidOpenApiData(data) {
|
|
|
124
139
|
// Check if it contains required OpenAPI/Swagger structure
|
|
125
140
|
return !!(data.openapi || data.swagger || data.info || data.paths);
|
|
126
141
|
}
|
|
127
|
-
const isRemoteUrl = (u) => /^https?:\/\//.test(u);
|
|
128
142
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
143
|
+
* Parse a raw spec string (JSON or YAML) into an OpenAPIDocument.
|
|
144
|
+
* Performs format auto-detection, validity checks, and Swagger2→OpenAPI3 conversion.
|
|
131
145
|
*/
|
|
132
|
-
async function
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
async function parseSpec(text, url) {
|
|
147
|
+
let data;
|
|
148
|
+
try {
|
|
149
|
+
// Try to parse as JSON first
|
|
150
|
+
data = JSON.parse(text);
|
|
135
151
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
152
|
+
catch (jsonError) {
|
|
153
|
+
try {
|
|
154
|
+
// Fall back to YAML (also covers JSON, since JSON is a subset of YAML)
|
|
155
|
+
data = js_yaml_1.default.load(text);
|
|
156
|
+
}
|
|
157
|
+
catch (yamlError) {
|
|
158
|
+
throw helper_1.logger.throwError(`Only JSON and YAML formats are supported. Parsing failed:
|
|
159
|
+
${jsonError instanceof Error ? jsonError.message : String(jsonError)}
|
|
160
|
+
${yamlError instanceof Error ? yamlError.message : String(yamlError)}`, {
|
|
161
|
+
url,
|
|
142
162
|
});
|
|
143
163
|
}
|
|
144
|
-
return parseLocalFile(u, projectPath).then(data => ({ data, url: u }));
|
|
145
|
-
});
|
|
146
|
-
try {
|
|
147
|
-
return await Promise.any(tasks);
|
|
148
164
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
// Validate if the data is valid (prevent server from returning error responses)
|
|
166
|
+
if (!isValidOpenApiData(data)) {
|
|
167
|
+
throw new Error(`Data retrieved from URL ${url} is not a valid OpenAPI document`);
|
|
168
|
+
}
|
|
169
|
+
// If it is a swagger2 file — convert via worker to avoid main-thread blocking
|
|
170
|
+
if (isSwagger2(data)) {
|
|
171
|
+
data = await convertSwagger2Async(data);
|
|
154
172
|
}
|
|
173
|
+
return data;
|
|
155
174
|
}
|
|
156
175
|
/**
|
|
157
176
|
* Parse OpenAPI document and return the resolved URL alongside the data.
|
|
158
177
|
* Use this when you need to know which URL actually provided the document.
|
|
178
|
+
*
|
|
179
|
+
* If `beforeSpecParse` is provided, it is invoked with the raw spec text once
|
|
180
|
+
* it has been fetched (but before parsing), and its returned string replaces
|
|
181
|
+
* the text that will be parsed.
|
|
159
182
|
*/
|
|
160
183
|
async function getOpenApiDataWithUrl(url, options) {
|
|
161
|
-
const { projectPath, fetchOptions } = options ?? {};
|
|
184
|
+
const { projectPath, fetchOptions, beforeSpecParse } = options ?? {};
|
|
162
185
|
// Normalize to array — single string or array both handled uniformly
|
|
163
186
|
const urls = Array.isArray(url) ? url : [url];
|
|
164
|
-
const {
|
|
165
|
-
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
187
|
+
const { text, url: resolvedUrl } = await fetchRawText(urls, { projectPath, fetchOptions });
|
|
188
|
+
// Allow the caller (e.g. a `beforeSpecParse` plugin hook) to transform the
|
|
189
|
+
// raw spec text before it is parsed into an OpenAPIDocument.
|
|
190
|
+
const finalText = (beforeSpecParse ? await beforeSpecParse(text) : text) ?? text;
|
|
191
|
+
const result = await parseSpec(finalText, resolvedUrl);
|
|
170
192
|
if (!result) {
|
|
171
193
|
throw helper_1.logger.throwError(`Cannot read file from ${urls.join(', ')}`, {
|
|
172
194
|
projectPath,
|
|
@@ -128,10 +128,11 @@ class GeneratorHelper {
|
|
|
128
128
|
fetchOptions: config.fetchOptions,
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
|
-
static async openApiDataWithUrl(config, projectPath) {
|
|
131
|
+
static async openApiDataWithUrl(config, projectPath, opts) {
|
|
132
132
|
return (0, helper_1.getOpenApiDataWithUrl)(config.input, {
|
|
133
133
|
projectPath,
|
|
134
134
|
fetchOptions: config.fetchOptions,
|
|
135
|
+
beforeSpecParse: opts?.beforeSpecParse,
|
|
135
136
|
});
|
|
136
137
|
}
|
|
137
138
|
static async generate(config, { projectPath, force, tracker }) {
|
|
@@ -152,16 +153,17 @@ class GeneratorHelper {
|
|
|
152
153
|
});
|
|
153
154
|
reportCore(5, 'starting');
|
|
154
155
|
const frozenConfig = Object.freeze(config);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
helper_2.logger.debug('Running beforeOpenapiParse hook');
|
|
158
|
-
await pluginDriver.hookParallelEach('beforeOpenapiParse', () => ({
|
|
159
|
-
config: frozenConfig,
|
|
160
|
-
projectPath,
|
|
161
|
-
}));
|
|
156
|
+
reportCore(10, 'beforeSpecParse');
|
|
157
|
+
helper_2.logger.debug('Fetching and parsing OpenAPI document', { input: config.input });
|
|
162
158
|
reportCore(20, 'parsing openapi document');
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
const openApiResult = await this.openApiDataWithUrl(config, projectPath, {
|
|
160
|
+
// Plugin: beforeSpecParse — receives the raw spec string, may return a modified string
|
|
161
|
+
beforeSpecParse: (spec) => pluginDriver.hookPipe('beforeSpecParse', spec, (_p, current, _ctx) => ({
|
|
162
|
+
config: frozenConfig,
|
|
163
|
+
spec: current,
|
|
164
|
+
projectPath,
|
|
165
|
+
})),
|
|
166
|
+
});
|
|
165
167
|
let document = openApiResult.data;
|
|
166
168
|
const resolvedInput = openApiResult.resolvedUrl;
|
|
167
169
|
if (!document) {
|
|
@@ -174,10 +176,10 @@ class GeneratorHelper {
|
|
|
174
176
|
version: document?.info?.version,
|
|
175
177
|
paths: Object.keys(document?.paths || {}).length,
|
|
176
178
|
});
|
|
177
|
-
reportCore(35, '
|
|
178
|
-
// Plugin: handle after parse openapi (
|
|
179
|
-
helper_2.logger.debug('Running
|
|
180
|
-
const
|
|
179
|
+
reportCore(35, 'specParsed');
|
|
180
|
+
// Plugin: handle after parse openapi (specParsed)
|
|
181
|
+
helper_2.logger.debug('Running specParsed hook', { pluginCount });
|
|
182
|
+
const specParsed = await pluginDriver.hookSeqEach('specParsed', (_p, prevResult, _ctx) => {
|
|
181
183
|
if (prevResult) {
|
|
182
184
|
document = prevResult;
|
|
183
185
|
}
|
|
@@ -187,11 +189,11 @@ class GeneratorHelper {
|
|
|
187
189
|
projectPath,
|
|
188
190
|
};
|
|
189
191
|
});
|
|
190
|
-
if (
|
|
191
|
-
document =
|
|
192
|
-
helper_2.logger.debug('
|
|
192
|
+
if (specParsed) {
|
|
193
|
+
document = specParsed;
|
|
194
|
+
helper_2.logger.debug('specParsed hook modified document');
|
|
193
195
|
}
|
|
194
|
-
reportCore(45, '
|
|
196
|
+
reportCore(45, 'specParsed');
|
|
195
197
|
const output = node_path_1.default.resolve(projectPath, config.output);
|
|
196
198
|
const templateType = await GeneratorHelper.getTemplateType(config, projectPath);
|
|
197
199
|
helper_2.logger.debug('Resolved output and template type', { output, templateType });
|
|
@@ -26,8 +26,8 @@ exports.zFetchOptions = v3_1.z.record(v3_1.z.string(), v3_1.z.any());
|
|
|
26
26
|
exports.zApiPlugin = v3_1.z.object({
|
|
27
27
|
name: v3_1.z.string().optional(),
|
|
28
28
|
config: v3_1.z.function().optional(),
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
beforeSpecParse: v3_1.z.function().optional(),
|
|
30
|
+
specParsed: v3_1.z.function().optional(),
|
|
31
31
|
beforeCodeGenerate: v3_1.z.function().optional(),
|
|
32
32
|
beforeFileWrite: v3_1.z.function().optional(),
|
|
33
33
|
codeGenerated: v3_1.z.function().optional(),
|
package/dist/helper/progress.js
CHANGED
|
@@ -8,9 +8,9 @@ exports.CORE_PROGRESS_SOURCE = 'core';
|
|
|
8
8
|
*/
|
|
9
9
|
exports.GeneratorStage = {
|
|
10
10
|
INIT: 'init',
|
|
11
|
-
|
|
11
|
+
BEFORE_SPEC_PARSE: 'beforeSpecParse',
|
|
12
12
|
PARSE_OPENAPI: 'parseOpenapi',
|
|
13
|
-
|
|
13
|
+
SPEC_PARSED: 'specParsed',
|
|
14
14
|
TEMPLATE_LOADED: 'templateLoaded',
|
|
15
15
|
TEMPLATE_DATA_PARSED: 'templateDataParsed',
|
|
16
16
|
BEFORE_CODE_GENERATE: 'beforeCodeGenerate',
|
|
@@ -15,11 +15,13 @@ function toSchemaObject(base, s) {
|
|
|
15
15
|
delete schema.required;
|
|
16
16
|
return schema;
|
|
17
17
|
};
|
|
18
|
-
//
|
|
18
|
+
// Native array type (elements are Schema)
|
|
19
19
|
if (Array.isArray(s)) {
|
|
20
|
-
const
|
|
20
|
+
const arr = s;
|
|
21
21
|
cleanType(result);
|
|
22
|
-
result.
|
|
22
|
+
result.type = 'array';
|
|
23
|
+
const items = arr.map(item => toSchemaObject({}, item));
|
|
24
|
+
result.items = (items.length === 1 ? items[0] : items);
|
|
23
25
|
return result;
|
|
24
26
|
}
|
|
25
27
|
// Primitive types and no-op primitives
|
|
@@ -33,18 +35,21 @@ function toSchemaObject(base, s) {
|
|
|
33
35
|
const baseOneOf = base.oneOf || [];
|
|
34
36
|
cleanType(result);
|
|
35
37
|
result.oneOf = spec.oneOf.map((item, idx) => toSchemaObject(baseOneOf[idx] || {}, item));
|
|
38
|
+
return result;
|
|
36
39
|
}
|
|
37
40
|
if (s.anyOf) {
|
|
38
41
|
const spec = s;
|
|
39
42
|
const baseAnyOf = base.anyOf || [];
|
|
40
43
|
cleanType(result);
|
|
41
44
|
result.anyOf = spec.anyOf.map((item, idx) => toSchemaObject(baseAnyOf[idx] || {}, item));
|
|
45
|
+
return result;
|
|
42
46
|
}
|
|
43
47
|
if (s.allOf) {
|
|
44
48
|
const spec = s;
|
|
45
49
|
const baseAllOf = base.allOf || [];
|
|
46
50
|
cleanType(result);
|
|
47
51
|
result.allOf = spec.allOf.map((item, idx) => toSchemaObject(baseAllOf[idx] || {}, item));
|
|
52
|
+
return result;
|
|
48
53
|
}
|
|
49
54
|
// Enum: set enum and optional type
|
|
50
55
|
if (s.enum) {
|
|
@@ -53,30 +58,15 @@ function toSchemaObject(base, s) {
|
|
|
53
58
|
if (spec.type) {
|
|
54
59
|
result.type = spec.type;
|
|
55
60
|
}
|
|
56
|
-
}
|
|
57
|
-
// Array: set/merge items
|
|
58
|
-
if (s.type === 'array') {
|
|
59
|
-
const spec = s;
|
|
60
|
-
result.type = 'array';
|
|
61
|
-
const baseItems = result.items;
|
|
62
|
-
if (Array.isArray(spec.items)) {
|
|
63
|
-
// Tuple items: replace entire items with tuple
|
|
64
|
-
const items = spec.items.map(item => toSchemaObject({}, item));
|
|
65
|
-
result.items = items;
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
// Single items: merge into existing items schema
|
|
69
|
-
const patchItem = toSchemaObject(typeof baseItems === 'object' ? baseItems : {}, spec.items);
|
|
70
|
-
result.items = patchItem;
|
|
71
|
-
}
|
|
72
61
|
return result;
|
|
73
62
|
}
|
|
74
|
-
// Object (reference-like map):
|
|
63
|
+
// Object (reference-like map): replace properties and required with handler's spec
|
|
64
|
+
// (the SchemaReference returned by the handler fully replaces this field, only keeping scalar fields like description from base)
|
|
75
65
|
const ref = s;
|
|
76
66
|
if (ref && typeof ref === 'object') {
|
|
77
67
|
result.type = 'object';
|
|
78
|
-
const properties = {
|
|
79
|
-
const requiredSet = new Set(
|
|
68
|
+
const properties = {};
|
|
69
|
+
const requiredSet = new Set();
|
|
80
70
|
for (const key in ref) {
|
|
81
71
|
const val = ref[key];
|
|
82
72
|
if (!val) {
|
|
@@ -137,16 +127,16 @@ function toSchemaSpec(obj) {
|
|
|
137
127
|
const type = typeof obj.type === 'string' ? obj.type : undefined;
|
|
138
128
|
return { enum: obj.enum, type };
|
|
139
129
|
}
|
|
140
|
-
// Array
|
|
130
|
+
// Array -> native array
|
|
141
131
|
if (obj.type === 'array' || obj.items) {
|
|
142
132
|
const items = obj.items;
|
|
143
133
|
if (Array.isArray(items)) {
|
|
144
|
-
return
|
|
134
|
+
return items.map((it) => toSchemaSpec(it));
|
|
145
135
|
}
|
|
146
136
|
if (items) {
|
|
147
|
-
return
|
|
137
|
+
return [toSchemaSpec(items)];
|
|
148
138
|
}
|
|
149
|
-
return
|
|
139
|
+
return ['unknown'];
|
|
150
140
|
}
|
|
151
141
|
// Object
|
|
152
142
|
if (obj.type === 'object' || obj.properties) {
|
|
@@ -160,7 +150,7 @@ function toSchemaSpec(obj) {
|
|
|
160
150
|
}
|
|
161
151
|
return result;
|
|
162
152
|
}
|
|
163
|
-
// type union as array
|
|
153
|
+
// type union as array -> oneOf
|
|
164
154
|
if (Array.isArray(obj.type)) {
|
|
165
155
|
const typeArr = obj.type;
|
|
166
156
|
const mapped = typeArr.map(schemaTypeToPrimitiveType);
|
|
@@ -175,21 +165,27 @@ function applyModifierSchema(schema, config, { required }) {
|
|
|
175
165
|
}
|
|
176
166
|
const cloned = { ...schema };
|
|
177
167
|
const currentSpec = toSchemaSpec(cloned);
|
|
178
|
-
|
|
168
|
+
// When the field is itself optional and is a primitive, wrap it as { required, type } before passing to handler
|
|
169
|
+
const handlerInput = (required === false && typeof currentSpec === 'string')
|
|
170
|
+
? { required: false, type: currentSpec }
|
|
171
|
+
: currentSpec;
|
|
172
|
+
const ret = config.handler(handlerInput);
|
|
179
173
|
if (!ret) {
|
|
180
174
|
return {
|
|
181
175
|
required,
|
|
182
|
-
|
|
176
|
+
schema: null,
|
|
183
177
|
};
|
|
184
178
|
}
|
|
185
|
-
|
|
179
|
+
// A returned { required, type } means changing requiredness (driven by the `type` field)
|
|
180
|
+
if (typeof ret === 'object' && !Array.isArray(ret) && 'required' in ret && 'type' in ret) {
|
|
181
|
+
const opt = ret;
|
|
186
182
|
return {
|
|
187
|
-
required: !!(
|
|
188
|
-
|
|
183
|
+
required: !!(opt.required ?? required),
|
|
184
|
+
schema: toSchemaObject(cloned, opt.type),
|
|
189
185
|
};
|
|
190
186
|
}
|
|
191
187
|
return {
|
|
192
188
|
required,
|
|
193
|
-
|
|
189
|
+
schema: toSchemaObject(cloned, ret),
|
|
194
190
|
};
|
|
195
191
|
}
|
|
@@ -28,13 +28,13 @@ function modifySchemaProperties(schema, config) {
|
|
|
28
28
|
if (!(0, utils_1.isMatch)(key, config.match)) {
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
|
-
const { required: requiredOverride,
|
|
31
|
+
const { required: requiredOverride, schema: schemaValue } = (0, hepler_1.applyModifierSchema)(props[key], config, { required: required.includes(key) });
|
|
32
32
|
required = required.filter(r => r !== key);
|
|
33
|
-
if (!
|
|
33
|
+
if (!schemaValue) {
|
|
34
34
|
delete props[key];
|
|
35
35
|
continue;
|
|
36
36
|
}
|
|
37
|
-
props[key] =
|
|
37
|
+
props[key] = schemaValue;
|
|
38
38
|
if (requiredOverride) {
|
|
39
39
|
required.push(key);
|
|
40
40
|
}
|
|
@@ -53,7 +53,7 @@ function modifyParameters(parameters, type, config) {
|
|
|
53
53
|
if (!(0, utils_1.isMatch)(param.name, config.match)) {
|
|
54
54
|
return param;
|
|
55
55
|
}
|
|
56
|
-
const {
|
|
56
|
+
const { schema, required } = (0, hepler_1.applyModifierSchema)(param.schema, config, { required: !!param.required });
|
|
57
57
|
if (!schema) {
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
@@ -73,22 +73,22 @@ function payloadModifierApiDescriptor(apiDescriptor, config) {
|
|
|
73
73
|
const newDescriptor = { ...apiDescriptor };
|
|
74
74
|
const { scope } = config;
|
|
75
75
|
switch (scope) {
|
|
76
|
-
case
|
|
76
|
+
case 'params':
|
|
77
77
|
if (newDescriptor.parameters) {
|
|
78
78
|
newDescriptor.parameters = modifyParameters(newDescriptor.parameters, constant_1.ParameterIn.QUERY, config);
|
|
79
79
|
}
|
|
80
80
|
break;
|
|
81
|
-
case
|
|
81
|
+
case 'pathParams':
|
|
82
82
|
if (newDescriptor.parameters) {
|
|
83
83
|
newDescriptor.parameters = modifyParameters(newDescriptor.parameters, constant_1.ParameterIn.PATH, config);
|
|
84
84
|
}
|
|
85
85
|
break;
|
|
86
|
-
case
|
|
86
|
+
case 'data':
|
|
87
87
|
if (newDescriptor.requestBody) {
|
|
88
88
|
newDescriptor.requestBody = modifySchemaProperties(newDescriptor.requestBody, config);
|
|
89
89
|
}
|
|
90
90
|
break;
|
|
91
|
-
case
|
|
91
|
+
case 'response':
|
|
92
92
|
if (newDescriptor.responses) {
|
|
93
93
|
newDescriptor.responses = modifySchemaProperties(newDescriptor.responses, config);
|
|
94
94
|
}
|
|
@@ -131,6 +131,8 @@ function renameUrl(url, config, apiDescriptor) {
|
|
|
131
131
|
* Recursively processes a schema (supports nested objects and arrays),
|
|
132
132
|
* applying property renaming to nested objects.
|
|
133
133
|
* @param schema current schema
|
|
134
|
+
* @param config rename configuration
|
|
135
|
+
* @param apiDescriptor API descriptor
|
|
134
136
|
* @param level current nesting level (starts from 0)
|
|
135
137
|
* @param scopeLabel used to identify the scope in duplicate-name errors
|
|
136
138
|
*/
|
|
@@ -158,6 +160,9 @@ function transformSchema(schema, config, apiDescriptor, level, scopeLabel) {
|
|
|
158
160
|
}
|
|
159
161
|
/**
|
|
160
162
|
* Transforms object properties using the renaming rules (supports deep recursive renaming)
|
|
163
|
+
* @param obj object whose properties to transform
|
|
164
|
+
* @param config rename configuration
|
|
165
|
+
* @param apiDescriptor API descriptor
|
|
161
166
|
* @param level current nesting level (starts from 0), passed through to the match function
|
|
162
167
|
* @param scopeLabel used to identify the scope in duplicate-name errors
|
|
163
168
|
*/
|
|
@@ -38,6 +38,10 @@ function processApiTags(apiDescriptor, handler) {
|
|
|
38
38
|
// Process each tag and filter out null/undefined results
|
|
39
39
|
newDescriptor.tags = newDescriptor.tags
|
|
40
40
|
.map((tag) => {
|
|
41
|
+
// Resolve the tag to keep when the modification cannot be applied:
|
|
42
|
+
// keep the original tag only if it is itself valid, otherwise drop it.
|
|
43
|
+
// This guarantees that the final output never contains an invalid tag.
|
|
44
|
+
const keepOriginalOrDrop = () => isValidTagName(tag) ? tag : null;
|
|
41
45
|
try {
|
|
42
46
|
// Call user provided handler function
|
|
43
47
|
const modifiedTag = handler(tag);
|
|
@@ -47,12 +51,12 @@ function processApiTags(apiDescriptor, handler) {
|
|
|
47
51
|
}
|
|
48
52
|
// Validate if modified tag follows naming conventions
|
|
49
53
|
if (!isValidTagName(modifiedTag)) {
|
|
50
|
-
return
|
|
54
|
+
return keepOriginalOrDrop(); // Keep original tag if valid, otherwise drop
|
|
51
55
|
}
|
|
52
56
|
return modifiedTag.trim(); // Return trimmed modified tag
|
|
53
57
|
}
|
|
54
58
|
catch {
|
|
55
|
-
return
|
|
59
|
+
return keepOriginalOrDrop(); // Keep original tag if valid, otherwise drop on error
|
|
56
60
|
}
|
|
57
61
|
})
|
|
58
62
|
.filter((tag) => tag != null); // Filter out null/undefined values
|
package/package.json
CHANGED
package/typings/index.d.ts
CHANGED
|
@@ -54,12 +54,14 @@ export interface ConfigHookParams {
|
|
|
54
54
|
projectPath: string;
|
|
55
55
|
reportProgress: ReportProgress;
|
|
56
56
|
}
|
|
57
|
-
export interface
|
|
57
|
+
export interface BeforeSpecParseHookParams {
|
|
58
58
|
config: Readonly<GeneratorConfig>;
|
|
59
|
+
/** The raw OpenAPI specification text (JSON or YAML), before it is parsed. */
|
|
60
|
+
spec: string;
|
|
59
61
|
projectPath: string;
|
|
60
62
|
reportProgress: ReportProgress;
|
|
61
63
|
}
|
|
62
|
-
export interface
|
|
64
|
+
export interface SpecParsedHookParams {
|
|
63
65
|
config: Readonly<GeneratorConfig>;
|
|
64
66
|
document: OpenAPIDocument;
|
|
65
67
|
projectPath: string;
|
|
@@ -140,14 +142,16 @@ export interface ApiPlugin {
|
|
|
140
142
|
*/
|
|
141
143
|
config?: (params: ConfigHookParams) => MaybePromise<GeneratorConfig | undefined | null | void>;
|
|
142
144
|
/**
|
|
143
|
-
* Called
|
|
145
|
+
* Called after the raw OpenAPI spec text is fetched but before it is parsed.
|
|
146
|
+
* Return a (possibly modified) string to replace the spec text that will be
|
|
147
|
+
* parsed. Returning nothing keeps the original spec text.
|
|
144
148
|
*/
|
|
145
|
-
|
|
149
|
+
beforeSpecParse?: (params: BeforeSpecParseHookParams) => MaybePromise<string | undefined | null | void>;
|
|
146
150
|
/**
|
|
147
151
|
* Manipulate the openapi document after parsing.
|
|
148
152
|
* Returning null does NOT replacing anything.
|
|
149
153
|
*/
|
|
150
|
-
|
|
154
|
+
specParsed?: (params: SpecParsedHookParams) => MaybePromise<OpenAPIDocument | undefined | null | void>;
|
|
151
155
|
/**
|
|
152
156
|
* Called before code generation. Mutate `params.data` directly to inject
|
|
153
157
|
* configuration data (no longer returns a value).
|
package/typings/plugins.d.ts
CHANGED
|
@@ -42,12 +42,14 @@ export interface ConfigHookParams {
|
|
|
42
42
|
projectPath: string;
|
|
43
43
|
reportProgress: ReportProgress;
|
|
44
44
|
}
|
|
45
|
-
export interface
|
|
45
|
+
export interface BeforeSpecParseHookParams {
|
|
46
46
|
config: Readonly<GeneratorConfig>;
|
|
47
|
+
/** The raw OpenAPI specification text (JSON or YAML), before it is parsed. */
|
|
48
|
+
spec: string;
|
|
47
49
|
projectPath: string;
|
|
48
50
|
reportProgress: ReportProgress;
|
|
49
51
|
}
|
|
50
|
-
export interface
|
|
52
|
+
export interface SpecParsedHookParams {
|
|
51
53
|
config: Readonly<GeneratorConfig>;
|
|
52
54
|
document: OpenAPIDocument;
|
|
53
55
|
projectPath: string;
|
|
@@ -128,14 +130,16 @@ export interface ApiPlugin {
|
|
|
128
130
|
*/
|
|
129
131
|
config?: (params: ConfigHookParams) => MaybePromise<GeneratorConfig | undefined | null | void>;
|
|
130
132
|
/**
|
|
131
|
-
* Called
|
|
133
|
+
* Called after the raw OpenAPI spec text is fetched but before it is parsed.
|
|
134
|
+
* Return a (possibly modified) string to replace the spec text that will be
|
|
135
|
+
* parsed. Returning nothing keeps the original spec text.
|
|
132
136
|
*/
|
|
133
|
-
|
|
137
|
+
beforeSpecParse?: (params: BeforeSpecParseHookParams) => MaybePromise<string | undefined | null | void>;
|
|
134
138
|
/**
|
|
135
139
|
* Manipulate the openapi document after parsing.
|
|
136
140
|
* Returning null does NOT replacing anything.
|
|
137
141
|
*/
|
|
138
|
-
|
|
142
|
+
specParsed?: (params: SpecParsedHookParams) => MaybePromise<OpenAPIDocument | undefined | null | void>;
|
|
139
143
|
/**
|
|
140
144
|
* Called before code generation. Mutate `params.data` directly to inject
|
|
141
145
|
* configuration data (no longer returns a value).
|
|
@@ -529,36 +533,29 @@ export interface ImportTypeOptions {
|
|
|
529
533
|
export declare function importType(imports: Record<string, string[]>, options?: {
|
|
530
534
|
files?: string[];
|
|
531
535
|
}): ApiPlugin;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
PATH_PARAMS = "pathParams",
|
|
535
|
-
DATA = "data",
|
|
536
|
-
RESPONSE = "response"
|
|
537
|
-
}
|
|
538
|
-
export type SchemaPrimitive = "number" | "string" | "boolean" | "undefined" | "null" | "unknown" | "any" | "never" | ({} & string);
|
|
536
|
+
export type ModifierScope = "params" | "pathParams" | "data" | "response";
|
|
537
|
+
export type SchemaPrimitive = "number" | "string" | "boolean" | "undefined" | "null" | "unknown" | "any" | "never";
|
|
539
538
|
/**
|
|
540
|
-
*
|
|
539
|
+
* Array type: a native JS array whose elements are Schemas.
|
|
540
|
+
* e.g. ['string'] means string[]; ['string', 'number'] means the tuple [string, number]
|
|
541
541
|
*/
|
|
542
|
-
export
|
|
543
|
-
type: "array";
|
|
544
|
-
items: Schema | Schema[];
|
|
545
|
-
}
|
|
542
|
+
export type SchemaArray = Schema[];
|
|
546
543
|
/**
|
|
547
|
-
*
|
|
548
|
-
*
|
|
544
|
+
* Object/reference type.
|
|
545
|
+
* Append '?' to the end of a key to mark it optional.
|
|
549
546
|
*/
|
|
550
547
|
export interface SchemaReference {
|
|
551
548
|
[attr: string]: Schema;
|
|
552
549
|
}
|
|
553
550
|
/**
|
|
554
|
-
*
|
|
551
|
+
* Enum type representation.
|
|
555
552
|
*/
|
|
556
553
|
export interface SchemaEnum {
|
|
557
554
|
enum: Array<string | number | boolean | null>;
|
|
558
555
|
type?: SchemaPrimitive;
|
|
559
556
|
}
|
|
560
557
|
/**
|
|
561
|
-
*
|
|
558
|
+
* Composite types (oneOf / anyOf / allOf).
|
|
562
559
|
*/
|
|
563
560
|
export interface SchemaOneOf {
|
|
564
561
|
oneOf: Schema[];
|
|
@@ -570,31 +567,47 @@ export interface SchemaAllOf {
|
|
|
570
567
|
allOf: Schema[];
|
|
571
568
|
}
|
|
572
569
|
/**
|
|
573
|
-
*
|
|
574
|
-
*
|
|
570
|
+
* Standalone primitive type that is itself optional (driven by the `type` field).
|
|
571
|
+
* Used in handler input/output to mean "this field is optional / make it optional".
|
|
572
|
+
*/
|
|
573
|
+
export interface SchemaOptional {
|
|
574
|
+
required: boolean;
|
|
575
|
+
type: Schema;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* The data Schema.
|
|
579
|
+
* - SchemaArray is a native array (elements are Schemas)
|
|
580
|
+
* - composite types use { oneOf | anyOf | allOf: Schema[] }
|
|
581
|
+
* - optional object properties use a trailing '?' on the key;
|
|
582
|
+
* a standalone optional primitive uses the SchemaOptional wrapper
|
|
575
583
|
*/
|
|
576
|
-
export type Schema = SchemaPrimitive | SchemaReference | SchemaArray | SchemaEnum | SchemaOneOf | SchemaAnyOf | SchemaAllOf |
|
|
577
|
-
export interface ModifierConfig
|
|
584
|
+
export type Schema = SchemaPrimitive | SchemaReference | SchemaArray | SchemaEnum | SchemaOneOf | SchemaAnyOf | SchemaAllOf | SchemaOptional;
|
|
585
|
+
export interface ModifierConfig {
|
|
578
586
|
/**
|
|
579
|
-
*
|
|
587
|
+
* The scope the modifier applies to (which parameter location to process).
|
|
580
588
|
*/
|
|
581
589
|
scope: ModifierScope;
|
|
582
590
|
/**
|
|
583
|
-
*
|
|
584
|
-
* string
|
|
591
|
+
* Match rule. Only matched fields are transformed; when omitted, all fields are transformed.
|
|
592
|
+
* - string: the original field name contains this string
|
|
593
|
+
* - RegExp: the original field name matches this pattern
|
|
594
|
+
* - function: receives the key and returns a boolean indicating a match
|
|
585
595
|
*/
|
|
586
596
|
match?: string | RegExp | ((key: string) => boolean);
|
|
587
597
|
/**
|
|
588
|
-
* handler
|
|
589
|
-
* @param schema Schema
|
|
590
|
-
*
|
|
598
|
+
* handler flexibly modifies the parameter type value.
|
|
599
|
+
* @param schema the original field type, already converted to the user-facing Schema representation.
|
|
600
|
+
* When the field itself is optional and is a primitive, it is passed as { required: false, type: 'string' }.
|
|
601
|
+
* Narrow the type inside handler if needed (e.g. with a cast).
|
|
602
|
+
* @returns Schema to change the type; { required: boolean, type: Schema } to change requiredness (driven by `type`);
|
|
603
|
+
* void | null | undefined to remove the field.
|
|
591
604
|
*/
|
|
592
|
-
handler: (schema:
|
|
605
|
+
handler: (schema: Schema) => Schema | {
|
|
593
606
|
required: boolean;
|
|
594
|
-
|
|
607
|
+
type: Schema;
|
|
595
608
|
} | void | null | undefined;
|
|
596
609
|
}
|
|
597
|
-
export type PayloadModifierConfig = ModifierConfig
|
|
610
|
+
export type PayloadModifierConfig = ModifierConfig;
|
|
598
611
|
export declare function payloadModifier(configs: PayloadModifierConfig[]): ApiPlugin;
|
|
599
612
|
/**
|
|
600
613
|
* FastAPI platform plugin.
|