wormajs 0.1.0 → 0.2.1
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/functions/prepareConfig.js +4 -0
- package/dist/generate.js +5 -6
- package/dist/helper/config/ConfigHelper.js +3 -14
- package/dist/helper/config/ConfigManager.js +1 -9
- 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 +139 -46
- package/dist/plugins/presets/payloadModifier/index.js +88 -40
- package/dist/plugins/presets/rename.js +5 -0
- package/dist/plugins/presets/tagModifier.js +6 -2
- package/dist/readConfig.js +2 -5
- package/package.json +1 -1
- package/typings/index.d.ts +10 -6
- package/typings/plugins.d.ts +51 -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,
|
|
@@ -12,6 +12,10 @@ function extendsConfig(config, newConfig) {
|
|
|
12
12
|
}
|
|
13
13
|
// handleApi is a special case, we need to merge the functions
|
|
14
14
|
if (typeof newValue === 'function' && typeof srcValue === 'function') {
|
|
15
|
+
// Avoid chaining the same function reference with itself (idempotency guard).
|
|
16
|
+
if (newValue === srcValue) {
|
|
17
|
+
return newValue;
|
|
18
|
+
}
|
|
15
19
|
// chain the functions
|
|
16
20
|
return (...args) => {
|
|
17
21
|
const result = srcValue(...args);
|
package/dist/generate.js
CHANGED
|
@@ -19,12 +19,11 @@ async function generate(config, options) {
|
|
|
19
19
|
return [];
|
|
20
20
|
const projectPath = options?.projectPath ?? process.cwd();
|
|
21
21
|
const emit = options?.onProgress;
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const generators =
|
|
27
|
-
helper_1.logger.debug('Config loaded', { generatorCount: generators.length });
|
|
22
|
+
// Each generate() call creates its own ConfigHelper / ConfigManager,
|
|
23
|
+
// so multiple concurrent calls never share mutable config state.
|
|
24
|
+
const helper = new helper_1.ConfigHelper();
|
|
25
|
+
await helper.load(config, projectPath);
|
|
26
|
+
const generators = helper.getConfig().generator;
|
|
28
27
|
// Run all generators in parallel, each with its own ProgressTracker
|
|
29
28
|
const results = await Promise.all(generators.map(async (gen, i) => {
|
|
30
29
|
const generatorName = gen.input || `generator-${i}`;
|
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ConfigHelper = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
5
|
const helper_1 = require("../../helper");
|
|
6
|
-
const logger_1 = require("../../helper/logger");
|
|
7
6
|
const ConfigManager_1 = require("./ConfigManager");
|
|
8
7
|
const GeneratorHelper_1 = require("./GeneratorHelper");
|
|
9
8
|
class ConfigHelper {
|
|
10
9
|
constructor() {
|
|
11
|
-
this.configManager = ConfigManager_1.ConfigManager
|
|
12
|
-
}
|
|
13
|
-
static getInstance() {
|
|
14
|
-
if (!ConfigHelper.instance) {
|
|
15
|
-
ConfigHelper.instance = new ConfigHelper();
|
|
16
|
-
}
|
|
17
|
-
return ConfigHelper.instance;
|
|
10
|
+
this.configManager = new ConfigManager_1.ConfigManager();
|
|
18
11
|
}
|
|
19
12
|
async load(config, projectPath = process.cwd(), tracker) {
|
|
20
13
|
this.projectPath = projectPath;
|
|
21
|
-
logger_1.logger.debug('ConfigHelper.load — loading config manager', { projectPath, generatorCount: config.generator?.length ?? 0 });
|
|
22
14
|
await this.configManager.load(config, projectPath, tracker);
|
|
23
|
-
logger_1.logger.debug('ConfigHelper.load — reading cache data');
|
|
24
15
|
await this.readAlovaJson();
|
|
25
|
-
logger_1.logger.debug('ConfigHelper.load — complete');
|
|
26
16
|
}
|
|
27
|
-
async readUserConfig(userConfig) {
|
|
17
|
+
static async readUserConfig(userConfig) {
|
|
28
18
|
if (typeof userConfig === 'function') {
|
|
29
19
|
return await userConfig();
|
|
30
20
|
}
|
|
@@ -76,4 +66,3 @@ class ConfigHelper {
|
|
|
76
66
|
}
|
|
77
67
|
}
|
|
78
68
|
exports.ConfigHelper = ConfigHelper;
|
|
79
|
-
exports.configHelper = ConfigHelper.getInstance();
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.ConfigManager = void 0;
|
|
7
7
|
const zod_validation_error_1 = require("zod-validation-error");
|
|
8
8
|
const prepareConfig_1 = __importDefault(require("../../functions/prepareConfig"));
|
|
9
9
|
const GeneratorHelper_1 = require("../../helper/config/GeneratorHelper");
|
|
@@ -17,12 +17,6 @@ class ConfigManager {
|
|
|
17
17
|
this.defaultGeneratorConfig = GeneratorHelper_1.generatorHelper.getDefaultConfig();
|
|
18
18
|
this.config = this.defaultConfig;
|
|
19
19
|
}
|
|
20
|
-
static getInstance() {
|
|
21
|
-
if (!ConfigManager.instance) {
|
|
22
|
-
ConfigManager.instance = new ConfigManager();
|
|
23
|
-
}
|
|
24
|
-
return ConfigManager.instance;
|
|
25
|
-
}
|
|
26
20
|
/**
|
|
27
21
|
* 加载并验证配置
|
|
28
22
|
*/
|
|
@@ -84,5 +78,3 @@ class ConfigManager {
|
|
|
84
78
|
}
|
|
85
79
|
}
|
|
86
80
|
exports.ConfigManager = ConfigManager;
|
|
87
|
-
// 导出单例实例
|
|
88
|
-
exports.configManager = ConfigManager.getInstance();
|
|
@@ -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',
|
|
@@ -1,8 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.applyModifierSchema = applyModifierSchema;
|
|
4
|
+
// Detect a `SchemaOptional` wrapper: { required: boolean, type: Schema }.
|
|
5
|
+
// The `required` must be a literal boolean so a plain SchemaReference whose
|
|
6
|
+
// property happens to be named "required" (e.g. { required: 'boolean' }) is not misread.
|
|
7
|
+
function isSchemaOptional(val) {
|
|
8
|
+
return !!val
|
|
9
|
+
&& typeof val === 'object'
|
|
10
|
+
&& !Array.isArray(val)
|
|
11
|
+
&& typeof val.required === 'boolean'
|
|
12
|
+
&& 'type' in val;
|
|
13
|
+
}
|
|
14
|
+
// Collapse (possibly nested) `SchemaOptional` wrappers.
|
|
15
|
+
// - The OUTERMOST `required` wins; inner `required` fields are ignored.
|
|
16
|
+
// - A non-wrapped value defaults to required (required: true).
|
|
17
|
+
function unwrapOptional(s) {
|
|
18
|
+
if (!isSchemaOptional(s)) {
|
|
19
|
+
return { required: true, type: s };
|
|
20
|
+
}
|
|
21
|
+
const required = s.required;
|
|
22
|
+
let type = s.type;
|
|
23
|
+
while (isSchemaOptional(type)) {
|
|
24
|
+
type = type.type;
|
|
25
|
+
}
|
|
26
|
+
return { required, type };
|
|
27
|
+
}
|
|
28
|
+
// Remove the internal `_$ref` marker that `removeAll$ref` stamps onto dereferenced
|
|
29
|
+
// component schemas. When a handler replaces a schema, the result must NOT inherit the
|
|
30
|
+
// original component's `_$ref` — otherwise `mergeObject`/`removeBaseReference` downstream
|
|
31
|
+
// treats the replacement as a reference to the original component and discards the change.
|
|
32
|
+
function stripInternalRef(schema) {
|
|
33
|
+
if (!schema || typeof schema !== 'object') {
|
|
34
|
+
return schema;
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(schema)) {
|
|
37
|
+
return schema.map(stripInternalRef);
|
|
38
|
+
}
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const key of Object.keys(schema)) {
|
|
41
|
+
if (key === '_$ref') {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
out[key] = stripInternalRef(schema[key]);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
// Set of valid SchemaPrimitive values for O(1) validation lookup
|
|
49
|
+
const VALID_PRIMITIVES = new Set([
|
|
50
|
+
'number',
|
|
51
|
+
'string',
|
|
52
|
+
'boolean',
|
|
53
|
+
'undefined',
|
|
54
|
+
'null',
|
|
55
|
+
'unknown',
|
|
56
|
+
'any',
|
|
57
|
+
'never',
|
|
58
|
+
]);
|
|
59
|
+
function validatePrimitive(val) {
|
|
60
|
+
if (!VALID_PRIMITIVES.has(val)) {
|
|
61
|
+
throw new Error(`[payloadModifier] Invalid schema type "${val}". Must be one of: ${[...VALID_PRIMITIVES].join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
4
64
|
// Convert Schema (custom spec) -> OpenAPI SchemaObject
|
|
5
65
|
function toSchemaObject(base, s) {
|
|
66
|
+
// A `SchemaOptional` wrapper only affects requiredness (handled by the caller);
|
|
67
|
+
// here we care about the type shape, so fully unwrap nested wrappers first.
|
|
68
|
+
if (isSchemaOptional(s)) {
|
|
69
|
+
s = unwrapOptional(s).type;
|
|
70
|
+
}
|
|
6
71
|
const result = { ...base };
|
|
7
72
|
const cleanType = (schema) => {
|
|
8
73
|
delete schema.type;
|
|
@@ -15,15 +80,18 @@ function toSchemaObject(base, s) {
|
|
|
15
80
|
delete schema.required;
|
|
16
81
|
return schema;
|
|
17
82
|
};
|
|
18
|
-
//
|
|
83
|
+
// Native array type (elements are Schema)
|
|
19
84
|
if (Array.isArray(s)) {
|
|
20
|
-
const
|
|
85
|
+
const arr = s;
|
|
21
86
|
cleanType(result);
|
|
22
|
-
result.
|
|
87
|
+
result.type = 'array';
|
|
88
|
+
const items = arr.map(item => toSchemaObject({}, item));
|
|
89
|
+
result.items = (items.length === 1 ? items[0] : items);
|
|
23
90
|
return result;
|
|
24
91
|
}
|
|
25
|
-
// Primitive types
|
|
92
|
+
// Primitive types — validate against SchemaPrimitive set during conversion
|
|
26
93
|
if (typeof s === 'string') {
|
|
94
|
+
validatePrimitive(s);
|
|
27
95
|
result.type = s;
|
|
28
96
|
return result;
|
|
29
97
|
}
|
|
@@ -33,64 +101,68 @@ function toSchemaObject(base, s) {
|
|
|
33
101
|
const baseOneOf = base.oneOf || [];
|
|
34
102
|
cleanType(result);
|
|
35
103
|
result.oneOf = spec.oneOf.map((item, idx) => toSchemaObject(baseOneOf[idx] || {}, item));
|
|
104
|
+
return result;
|
|
36
105
|
}
|
|
37
106
|
if (s.anyOf) {
|
|
38
107
|
const spec = s;
|
|
39
108
|
const baseAnyOf = base.anyOf || [];
|
|
40
109
|
cleanType(result);
|
|
41
110
|
result.anyOf = spec.anyOf.map((item, idx) => toSchemaObject(baseAnyOf[idx] || {}, item));
|
|
111
|
+
return result;
|
|
42
112
|
}
|
|
43
113
|
if (s.allOf) {
|
|
44
114
|
const spec = s;
|
|
45
115
|
const baseAllOf = base.allOf || [];
|
|
46
116
|
cleanType(result);
|
|
47
117
|
result.allOf = spec.allOf.map((item, idx) => toSchemaObject(baseAllOf[idx] || {}, item));
|
|
118
|
+
return result;
|
|
48
119
|
}
|
|
49
120
|
// Enum: set enum and optional type
|
|
50
121
|
if (s.enum) {
|
|
51
122
|
const spec = s;
|
|
52
123
|
result.enum = spec.enum;
|
|
53
124
|
if (spec.type) {
|
|
125
|
+
if (typeof spec.type === 'string') {
|
|
126
|
+
validatePrimitive(spec.type);
|
|
127
|
+
}
|
|
54
128
|
result.type = spec.type;
|
|
55
129
|
}
|
|
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
130
|
return result;
|
|
73
131
|
}
|
|
74
|
-
// Object (reference-like map):
|
|
132
|
+
// Object (reference-like map): replace properties and required with handler's spec
|
|
133
|
+
// (the SchemaReference returned by the handler fully replaces this field, only keeping
|
|
134
|
+
// scalar fields like description from base)
|
|
75
135
|
const ref = s;
|
|
76
136
|
if (ref && typeof ref === 'object') {
|
|
77
137
|
result.type = 'object';
|
|
78
|
-
const properties = {
|
|
79
|
-
const requiredSet = new Set(
|
|
138
|
+
const properties = {};
|
|
139
|
+
const requiredSet = new Set();
|
|
80
140
|
for (const key in ref) {
|
|
81
141
|
const val = ref[key];
|
|
82
142
|
if (!val) {
|
|
83
143
|
continue;
|
|
84
144
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
145
|
+
// SchemaOptional wrapper: optionality expressed via { required, type };
|
|
146
|
+
// bare value defaults to required. Nested wrappers are collapsed — outermost
|
|
147
|
+
// `required` wins, inner ones are ignored.
|
|
148
|
+
let isOptional;
|
|
149
|
+
let effectiveVal;
|
|
150
|
+
if (isSchemaOptional(val)) {
|
|
151
|
+
const { required, type } = unwrapOptional(val);
|
|
152
|
+
isOptional = !required;
|
|
153
|
+
effectiveVal = type;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
isOptional = false;
|
|
157
|
+
effectiveVal = val;
|
|
158
|
+
}
|
|
159
|
+
const baseProp = properties[key];
|
|
160
|
+
properties[key] = toSchemaObject(baseProp || {}, effectiveVal);
|
|
161
|
+
if (isOptional) {
|
|
162
|
+
requiredSet.delete(key);
|
|
91
163
|
}
|
|
92
164
|
else {
|
|
93
|
-
requiredSet.add(
|
|
165
|
+
requiredSet.add(key);
|
|
94
166
|
}
|
|
95
167
|
}
|
|
96
168
|
result.properties = properties;
|
|
@@ -137,16 +209,16 @@ function toSchemaSpec(obj) {
|
|
|
137
209
|
const type = typeof obj.type === 'string' ? obj.type : undefined;
|
|
138
210
|
return { enum: obj.enum, type };
|
|
139
211
|
}
|
|
140
|
-
// Array
|
|
212
|
+
// Array -> native array
|
|
141
213
|
if (obj.type === 'array' || obj.items) {
|
|
142
214
|
const items = obj.items;
|
|
143
215
|
if (Array.isArray(items)) {
|
|
144
|
-
return
|
|
216
|
+
return items.map((it) => toSchemaSpec(it));
|
|
145
217
|
}
|
|
146
218
|
if (items) {
|
|
147
|
-
return
|
|
219
|
+
return [toSchemaSpec(items)];
|
|
148
220
|
}
|
|
149
|
-
return
|
|
221
|
+
return ['unknown'];
|
|
150
222
|
}
|
|
151
223
|
// Object
|
|
152
224
|
if (obj.type === 'object' || obj.properties) {
|
|
@@ -155,12 +227,12 @@ function toSchemaSpec(obj) {
|
|
|
155
227
|
const result = {};
|
|
156
228
|
for (const key of Object.keys(properties)) {
|
|
157
229
|
const spec = toSchemaSpec(properties[key]);
|
|
158
|
-
|
|
159
|
-
result[
|
|
230
|
+
// Required fields are written bare; optional fields wrapped with SchemaOptional
|
|
231
|
+
result[key] = requiredSet.has(key) ? spec : { required: false, type: spec };
|
|
160
232
|
}
|
|
161
233
|
return result;
|
|
162
234
|
}
|
|
163
|
-
// type union as array
|
|
235
|
+
// type union as array -> oneOf
|
|
164
236
|
if (Array.isArray(obj.type)) {
|
|
165
237
|
const typeArr = obj.type;
|
|
166
238
|
const mapped = typeArr.map(schemaTypeToPrimitiveType);
|
|
@@ -169,27 +241,48 @@ function toSchemaSpec(obj) {
|
|
|
169
241
|
return schemaTypeToPrimitiveType(obj.type);
|
|
170
242
|
}
|
|
171
243
|
// Replace whole schema based on handler result (used for params/pathParams)
|
|
172
|
-
function applyModifierSchema(schema, config, { required }) {
|
|
244
|
+
function applyModifierSchema(schema, config, { required, key }) {
|
|
173
245
|
if (!schema || typeof schema !== 'object') {
|
|
174
|
-
return schema;
|
|
246
|
+
return { required, schema: schema };
|
|
175
247
|
}
|
|
176
248
|
const cloned = { ...schema };
|
|
177
249
|
const currentSpec = toSchemaSpec(cloned);
|
|
178
|
-
|
|
250
|
+
// When the field is itself optional and is a primitive, wrap it as { required, type } before passing to handler
|
|
251
|
+
const handlerInput = (required === false && typeof currentSpec === 'string')
|
|
252
|
+
? { required: false, type: currentSpec }
|
|
253
|
+
: currentSpec;
|
|
254
|
+
const ret = config.handler(handlerInput, key);
|
|
179
255
|
if (!ret) {
|
|
180
256
|
return {
|
|
181
257
|
required,
|
|
182
|
-
|
|
258
|
+
schema: null,
|
|
183
259
|
};
|
|
184
260
|
}
|
|
185
|
-
|
|
261
|
+
// A returned SchemaOptional means changing requiredness (driven by the `type` field).
|
|
262
|
+
// Nested wrappers are collapsed: the outermost `required` wins, inner ones are ignored;
|
|
263
|
+
// `type` may be any Schema expression (primitive, object, array, union, ...).
|
|
264
|
+
if (isSchemaOptional(ret)) {
|
|
265
|
+
const { required: nextRequired, type } = unwrapOptional(ret);
|
|
266
|
+
let r = stripInternalRef(toSchemaObject(cloned, type));
|
|
267
|
+
// When handler explicitly sets required=false, propagate to object-level required array
|
|
268
|
+
// so all properties become nullable as semantically expected
|
|
269
|
+
if (!nextRequired && r && typeof r === 'object' && !Array.isArray(r)) {
|
|
270
|
+
const robj = r;
|
|
271
|
+
if (robj.type === 'object' && Array.isArray(robj.required) && robj.required.length > 0) {
|
|
272
|
+
r = { ...r, required: [] };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
186
275
|
return {
|
|
187
|
-
required:
|
|
188
|
-
|
|
276
|
+
required: nextRequired,
|
|
277
|
+
schema: r,
|
|
189
278
|
};
|
|
190
279
|
}
|
|
280
|
+
// Non-SchemaOptional return: handler explicitly provides a type value,
|
|
281
|
+
// so default to required=true (the handler had the chance to wrap with
|
|
282
|
+
// { required: false, type: ... } if it wanted to keep it optional).
|
|
283
|
+
const r = stripInternalRef(toSchemaObject(cloned, ret));
|
|
191
284
|
return {
|
|
192
|
-
required,
|
|
193
|
-
|
|
285
|
+
required: true,
|
|
286
|
+
schema: r,
|
|
194
287
|
};
|
|
195
288
|
}
|
|
@@ -4,13 +4,58 @@ exports.payloadModifier = payloadModifier;
|
|
|
4
4
|
const constant_1 = require("../../../constant");
|
|
5
5
|
const utils_1 = require("../utils");
|
|
6
6
|
const hepler_1 = require("./hepler");
|
|
7
|
-
//
|
|
7
|
+
// Convert parameters of a specific type (query/path) into an object schema
|
|
8
|
+
function parametersToSchema(parameters, type) {
|
|
9
|
+
if (!parameters || !Array.isArray(parameters)) {
|
|
10
|
+
return { type: 'object', properties: {}, required: [] };
|
|
11
|
+
}
|
|
12
|
+
const schema = { type: 'object', properties: {}, required: [] };
|
|
13
|
+
for (const param of parameters) {
|
|
14
|
+
if (param.in === type) {
|
|
15
|
+
;
|
|
16
|
+
schema.properties[param.name] = param.schema;
|
|
17
|
+
if (param.required) {
|
|
18
|
+
;
|
|
19
|
+
schema.required.push(param.name);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return schema;
|
|
24
|
+
}
|
|
25
|
+
// Convert an object schema back to parameters, keeping other types untouched
|
|
26
|
+
function schemaToParameters(parameters, schema, type) {
|
|
27
|
+
if (!parameters || !Array.isArray(parameters)) {
|
|
28
|
+
return parameters;
|
|
29
|
+
}
|
|
30
|
+
if (!schema || typeof schema !== 'object' || !schema.properties) {
|
|
31
|
+
return parameters.filter(param => param.in !== type);
|
|
32
|
+
}
|
|
33
|
+
const requiredSet = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
34
|
+
const newParameters = [];
|
|
35
|
+
for (const param of parameters) {
|
|
36
|
+
if (param.in !== type) {
|
|
37
|
+
newParameters.push(param);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const propSchema = schema.properties[param.name];
|
|
41
|
+
if (!propSchema) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
newParameters.push({
|
|
45
|
+
...param,
|
|
46
|
+
schema: propSchema,
|
|
47
|
+
required: requiredSet.has(param.name),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return newParameters;
|
|
51
|
+
}
|
|
52
|
+
// Apply modifications to properties of an object schema (used when `match` is set)
|
|
8
53
|
function modifySchemaProperties(schema, config) {
|
|
9
54
|
if (!schema || typeof schema !== 'object') {
|
|
10
55
|
return schema;
|
|
11
56
|
}
|
|
12
57
|
const targetSchema = { ...schema };
|
|
13
|
-
// union
|
|
58
|
+
// recurse into union keywords
|
|
14
59
|
if (Array.isArray(targetSchema.oneOf)) {
|
|
15
60
|
targetSchema.oneOf = targetSchema.oneOf.map(item => modifySchemaProperties(item, config));
|
|
16
61
|
}
|
|
@@ -20,7 +65,7 @@ function modifySchemaProperties(schema, config) {
|
|
|
20
65
|
if (Array.isArray(targetSchema.allOf)) {
|
|
21
66
|
targetSchema.allOf = targetSchema.allOf.map(item => modifySchemaProperties(item, config));
|
|
22
67
|
}
|
|
23
|
-
// modify properties
|
|
68
|
+
// modify matched properties
|
|
24
69
|
if (targetSchema.properties) {
|
|
25
70
|
const props = { ...targetSchema.properties };
|
|
26
71
|
let required = Array.isArray(targetSchema.required) ? [...targetSchema.required] : [];
|
|
@@ -28,13 +73,13 @@ function modifySchemaProperties(schema, config) {
|
|
|
28
73
|
if (!(0, utils_1.isMatch)(key, config.match)) {
|
|
29
74
|
continue;
|
|
30
75
|
}
|
|
31
|
-
const { required: requiredOverride,
|
|
76
|
+
const { required: requiredOverride, schema: schemaValue } = (0, hepler_1.applyModifierSchema)(props[key], config, { required: required.includes(key), key });
|
|
32
77
|
required = required.filter(r => r !== key);
|
|
33
|
-
if (!
|
|
78
|
+
if (!schemaValue) {
|
|
34
79
|
delete props[key];
|
|
35
80
|
continue;
|
|
36
81
|
}
|
|
37
|
-
props[key] =
|
|
82
|
+
props[key] = schemaValue;
|
|
38
83
|
if (requiredOverride) {
|
|
39
84
|
required.push(key);
|
|
40
85
|
}
|
|
@@ -44,56 +89,59 @@ function modifySchemaProperties(schema, config) {
|
|
|
44
89
|
}
|
|
45
90
|
return targetSchema;
|
|
46
91
|
}
|
|
92
|
+
// Apply modifications to matched parameters (used when `match` is set for params/pathParams)
|
|
47
93
|
function modifyParameters(parameters, type, config) {
|
|
48
94
|
if (!parameters || !Array.isArray(parameters)) {
|
|
49
95
|
return parameters;
|
|
50
96
|
}
|
|
51
97
|
return parameters.map((param) => {
|
|
52
|
-
if (param.in
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
return {
|
|
61
|
-
...param,
|
|
62
|
-
schema,
|
|
63
|
-
required,
|
|
64
|
-
};
|
|
98
|
+
if (param.in !== type || !(0, utils_1.isMatch)(param.name, config.match)) {
|
|
99
|
+
return param;
|
|
100
|
+
}
|
|
101
|
+
const { schema, required } = (0, hepler_1.applyModifierSchema)(param.schema, config, { required: !!param.required, key: param.name });
|
|
102
|
+
if (!schema) {
|
|
103
|
+
return null;
|
|
65
104
|
}
|
|
66
|
-
return param;
|
|
105
|
+
return { ...param, schema, required };
|
|
67
106
|
}).filter(item => item !== null);
|
|
68
107
|
}
|
|
108
|
+
// Apply config to a parameter scope (params or pathParams)
|
|
109
|
+
function applyToParameters(parameters, type, config) {
|
|
110
|
+
if (!parameters)
|
|
111
|
+
return undefined;
|
|
112
|
+
if (config.match) {
|
|
113
|
+
return modifyParameters(parameters, type, config);
|
|
114
|
+
}
|
|
115
|
+
const schema = parametersToSchema(parameters, type);
|
|
116
|
+
const result = (0, hepler_1.applyModifierSchema)(schema, config, { required: false });
|
|
117
|
+
return schemaToParameters(parameters, result.schema, type);
|
|
118
|
+
}
|
|
119
|
+
// Apply config to a schema scope (data or response)
|
|
120
|
+
function applyToSchemaField(schema, config) {
|
|
121
|
+
if (!schema)
|
|
122
|
+
return undefined;
|
|
123
|
+
if (config.match) {
|
|
124
|
+
return modifySchemaProperties(schema, config);
|
|
125
|
+
}
|
|
126
|
+
return (0, hepler_1.applyModifierSchema)(schema, config, { required: false }).schema ?? undefined;
|
|
127
|
+
}
|
|
69
128
|
function payloadModifierApiDescriptor(apiDescriptor, config) {
|
|
70
|
-
if (!apiDescriptor)
|
|
129
|
+
if (!apiDescriptor)
|
|
71
130
|
return null;
|
|
72
|
-
}
|
|
73
131
|
const newDescriptor = { ...apiDescriptor };
|
|
74
132
|
const { scope } = config;
|
|
75
133
|
switch (scope) {
|
|
76
|
-
case
|
|
77
|
-
|
|
78
|
-
newDescriptor.parameters = modifyParameters(newDescriptor.parameters, constant_1.ParameterIn.QUERY, config);
|
|
79
|
-
}
|
|
134
|
+
case 'params':
|
|
135
|
+
newDescriptor.parameters = applyToParameters(newDescriptor.parameters, constant_1.ParameterIn.QUERY, config);
|
|
80
136
|
break;
|
|
81
|
-
case
|
|
82
|
-
|
|
83
|
-
newDescriptor.parameters = modifyParameters(newDescriptor.parameters, constant_1.ParameterIn.PATH, config);
|
|
84
|
-
}
|
|
137
|
+
case 'pathParams':
|
|
138
|
+
newDescriptor.parameters = applyToParameters(newDescriptor.parameters, constant_1.ParameterIn.PATH, config);
|
|
85
139
|
break;
|
|
86
|
-
case
|
|
87
|
-
|
|
88
|
-
newDescriptor.requestBody = modifySchemaProperties(newDescriptor.requestBody, config);
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
case constant_1.ModifierScope.RESPONSE:
|
|
92
|
-
if (newDescriptor.responses) {
|
|
93
|
-
newDescriptor.responses = modifySchemaProperties(newDescriptor.responses, config);
|
|
94
|
-
}
|
|
140
|
+
case 'data':
|
|
141
|
+
newDescriptor.requestBody = applyToSchemaField(newDescriptor.requestBody, config);
|
|
95
142
|
break;
|
|
96
|
-
|
|
143
|
+
case 'response':
|
|
144
|
+
newDescriptor.responses = applyToSchemaField(newDescriptor.responses, config);
|
|
97
145
|
break;
|
|
98
146
|
}
|
|
99
147
|
return newDescriptor;
|
|
@@ -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/dist/readConfig.js
CHANGED
|
@@ -34,7 +34,6 @@ async function readConfig(projectPath = process.cwd()) {
|
|
|
34
34
|
name: 'readConfig',
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
await helper_1.configHelper.load(config, projectPath);
|
|
38
37
|
return config;
|
|
39
38
|
}
|
|
40
39
|
// 获取用户已安装的依赖
|
|
@@ -67,10 +66,8 @@ async function readConfig(projectPath = process.cwd()) {
|
|
|
67
66
|
finally {
|
|
68
67
|
await (0, promises_1.unlink)(outfile);
|
|
69
68
|
}
|
|
70
|
-
const config = await helper_1.
|
|
71
|
-
|
|
72
|
-
await helper_1.configHelper.load(config, projectPath);
|
|
73
|
-
return helper_1.configHelper.getConfig();
|
|
69
|
+
const config = await helper_1.ConfigHelper.readUserConfig(module.default || module);
|
|
70
|
+
return config;
|
|
74
71
|
}
|
|
75
72
|
/**
|
|
76
73
|
* Get cached API docs. Cache is self-describing — no config needed.
|
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).
|
|
@@ -493,7 +497,7 @@ export declare const logger: Logger$1;
|
|
|
493
497
|
* @param projectPath The project path where the configuration file is located. The default value is `process.cwd()`.
|
|
494
498
|
* @returns a promise instance that contains configuration object.
|
|
495
499
|
*/
|
|
496
|
-
export declare function readConfig(projectPath?: string): Promise<
|
|
500
|
+
export declare function readConfig(projectPath?: string): Promise<Config>;
|
|
497
501
|
/**
|
|
498
502
|
* Get cached API docs. Cache is self-describing — no config needed.
|
|
499
503
|
* In monorepo, pass ANY sub-package path; cache is always read from the unified cacheRoot.
|
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,31 @@ 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
|
+
* Required properties are written directly; optional properties are wrapped
|
|
546
|
+
* with the `SchemaOptional` form `{ required: false, type: Schema }`
|
|
547
|
+
* (consistent with how a standalone optional primitive is represented).
|
|
549
548
|
*/
|
|
550
549
|
export interface SchemaReference {
|
|
551
550
|
[attr: string]: Schema;
|
|
552
551
|
}
|
|
553
552
|
/**
|
|
554
|
-
*
|
|
553
|
+
* Enum type representation.
|
|
555
554
|
*/
|
|
556
555
|
export interface SchemaEnum {
|
|
557
556
|
enum: Array<string | number | boolean | null>;
|
|
558
557
|
type?: SchemaPrimitive;
|
|
559
558
|
}
|
|
560
559
|
/**
|
|
561
|
-
*
|
|
560
|
+
* Composite types (oneOf / anyOf / allOf).
|
|
562
561
|
*/
|
|
563
562
|
export interface SchemaOneOf {
|
|
564
563
|
oneOf: Schema[];
|
|
@@ -570,31 +569,49 @@ export interface SchemaAllOf {
|
|
|
570
569
|
allOf: Schema[];
|
|
571
570
|
}
|
|
572
571
|
/**
|
|
573
|
-
*
|
|
574
|
-
*
|
|
572
|
+
* Standalone primitive type that is itself optional (driven by the `type` field).
|
|
573
|
+
* Used in handler input/output to mean "this field is optional / make it optional".
|
|
574
|
+
*/
|
|
575
|
+
export interface SchemaOptional {
|
|
576
|
+
required: boolean;
|
|
577
|
+
type: Schema;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* The data Schema.
|
|
581
|
+
* - SchemaArray is a native array (elements are Schemas)
|
|
582
|
+
* - composite types use { oneOf | anyOf | allOf: Schema[] }
|
|
583
|
+
* - optional object properties are wrapped with `SchemaOptional` ({ required: false, type: Schema });
|
|
584
|
+
* a standalone optional primitive uses the same SchemaOptional wrapper
|
|
575
585
|
*/
|
|
576
|
-
export type Schema = SchemaPrimitive | SchemaReference | SchemaArray | SchemaEnum | SchemaOneOf | SchemaAnyOf | SchemaAllOf |
|
|
577
|
-
export interface ModifierConfig
|
|
586
|
+
export type Schema = SchemaPrimitive | SchemaReference | SchemaArray | SchemaEnum | SchemaOneOf | SchemaAnyOf | SchemaAllOf | SchemaOptional;
|
|
587
|
+
export interface ModifierConfig {
|
|
578
588
|
/**
|
|
579
|
-
*
|
|
589
|
+
* The scope the modifier applies to (which parameter location to process).
|
|
580
590
|
*/
|
|
581
591
|
scope: ModifierScope;
|
|
582
592
|
/**
|
|
583
|
-
*
|
|
584
|
-
* string
|
|
593
|
+
* Match rule. Only matched fields are transformed; when omitted, all fields are transformed.
|
|
594
|
+
* - string: the original field name contains this string
|
|
595
|
+
* - RegExp: the original field name matches this pattern
|
|
596
|
+
* - function: receives the key and returns a boolean indicating a match
|
|
585
597
|
*/
|
|
586
598
|
match?: string | RegExp | ((key: string) => boolean);
|
|
587
599
|
/**
|
|
588
|
-
* handler
|
|
589
|
-
* @param schema Schema
|
|
590
|
-
*
|
|
600
|
+
* handler flexibly modifies the parameter type value.
|
|
601
|
+
* @param schema the original field type, already converted to the user-facing Schema representation.
|
|
602
|
+
* When the field itself is optional and is a primitive, it is passed as { required: false, type: 'string' }.
|
|
603
|
+
* Narrow the type inside handler if needed (e.g. with a cast).
|
|
604
|
+
* @param key the matched field key. When `match` is omitted, the whole scope object is passed to the handler
|
|
605
|
+
* once and `key` is `undefined`; when `match` is set, `key` is the matched field name for each call.
|
|
606
|
+
* @returns Schema to change the type; { required: boolean, type: Schema } to change requiredness (driven by `type`);
|
|
607
|
+
* void | null | undefined to remove the field.
|
|
591
608
|
*/
|
|
592
|
-
handler: (schema:
|
|
609
|
+
handler: (schema: Schema, key?: string) => Schema | {
|
|
593
610
|
required: boolean;
|
|
594
|
-
|
|
611
|
+
type: Schema;
|
|
595
612
|
} | void | null | undefined;
|
|
596
613
|
}
|
|
597
|
-
export type PayloadModifierConfig = ModifierConfig
|
|
614
|
+
export type PayloadModifierConfig = ModifierConfig;
|
|
598
615
|
export declare function payloadModifier(configs: PayloadModifierConfig[]): ApiPlugin;
|
|
599
616
|
/**
|
|
600
617
|
* FastAPI platform plugin.
|