wormajs 0.0.2-beta.2 → 0.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.
Files changed (30) hide show
  1. package/dist/constant.js +6 -1
  2. package/dist/core/workerPool/worker.js +1 -2
  3. package/dist/plugins/index.js +6 -2
  4. package/dist/plugins/presets/platform/fastapi.js +22 -0
  5. package/dist/plugins/presets/platform/index.js +15 -0
  6. package/dist/plugins/presets/platform/knife4j.js +24 -0
  7. package/dist/plugins/presets/platform/shared.js +45 -0
  8. package/dist/plugins/presets/platform/swagger.js +32 -0
  9. package/dist/plugins/presets/platform/yapi.js +75 -0
  10. package/dist/plugins/presets/rename.js +149 -21
  11. package/dist/plugins/presets/utils.js +11 -10
  12. package/dist/template/index.js +0 -34
  13. package/dist/template/presets/alova/partials/dts-extra-config.handlebars +3 -3
  14. package/dist/template/presets/alova/partials/dts-fn-declare.handlebars +1 -1
  15. package/dist/template/presets/alova/typescript/services/{tag}.ts.handlebars +5 -5
  16. package/dist/template/presets/axios/partials/dts-types.handlebars +4 -4
  17. package/dist/template/presets/axios/typescript/services/{tag}.ts.handlebars +4 -4
  18. package/dist/template/presets/config/common/worma.config.js.handlebars +1 -1
  19. package/dist/template/presets/config/module/worma.config.js.handlebars +1 -1
  20. package/dist/template/presets/config/partials/generator-content.handlebars +45 -45
  21. package/dist/template/presets/config/typescript/worma.config.ts.handlebars +1 -1
  22. package/dist/template/presets/fetch/partials/dts-types.handlebars +4 -4
  23. package/dist/template/presets/fetch/typescript/services/{tag}.ts.handlebars +4 -4
  24. package/dist/template/presets/ky/partials/dts-types.handlebars +4 -4
  25. package/dist/template/presets/ky/typescript/services/{tag}.ts.handlebars +4 -4
  26. package/dist/tsconfig.build.tsbuildinfo +1 -1
  27. package/dist/utils/template.js +23 -0
  28. package/package.json +1 -1
  29. package/typings/plugins.d.ts +87 -14
  30. package/dist/plugins/presets/platform.js +0 -73
package/dist/constant.js CHANGED
@@ -26,6 +26,7 @@ var PlatformTypeEnum;
26
26
  (function (PlatformTypeEnum) {
27
27
  PlatformTypeEnum["SWAGGER"] = "swagger";
28
28
  PlatformTypeEnum["KNIFE4J"] = "knife4j";
29
+ PlatformTypeEnum["FASTAPI"] = "fastapi";
29
30
  PlatformTypeEnum["YAPI"] = "yapi";
30
31
  })(PlatformTypeEnum || (exports.PlatformTypeEnum = PlatformTypeEnum = {}));
31
32
  /** 模块体系类型 */
@@ -77,6 +78,7 @@ var RenameScope;
77
78
  RenameScope["DATA"] = "data";
78
79
  RenameScope["RESPONSE"] = "response";
79
80
  RenameScope["REF_NAME"] = "refName";
81
+ RenameScope["NAME"] = "name";
80
82
  })(RenameScope || (exports.RenameScope = RenameScope = {}));
81
83
  /** 过滤 scope 类型 */
82
84
  var FilterScope;
@@ -102,7 +104,10 @@ var PluginName;
102
104
  PluginName["IMPORT_TYPE"] = "importType";
103
105
  PluginName["AI_DOC"] = "aiDoc";
104
106
  PluginName["APIFOX"] = "apifox";
105
- PluginName["PLATFORM"] = "platform";
107
+ PluginName["SWAGGER"] = "swagger";
108
+ PluginName["KNIFE4J"] = "knife4j";
109
+ PluginName["FASTAPI"] = "fastapi";
110
+ PluginName["YAPI"] = "yapi";
106
111
  PluginName["TEMPLATE_ALOVA"] = "templateAlova";
107
112
  PluginName["TEMPLATE_ALOVA_GLOBALS"] = "templateAlovaGlobals";
108
113
  PluginName["TEMPLATE_AXIOS"] = "templateAxios";
@@ -16,7 +16,7 @@ const refNameMap = new Map(refNameMapEntries);
16
16
  const schemaCache = new Map();
17
17
  async function processSchema(key, schema) {
18
18
  const results = [];
19
- const tsStr = await loader_1.schemaLoader.transform(schema, {
19
+ await loader_1.schemaLoader.transform(schema, {
20
20
  document,
21
21
  deep: false,
22
22
  noEnum: true,
@@ -43,7 +43,6 @@ async function processSchema(key, schema) {
43
43
  }
44
44
  },
45
45
  });
46
- results.push({ key, result: tsStr });
47
46
  return results;
48
47
  }
49
48
  node_worker_threads_1.parentPort?.on('message', async (batch) => {
@@ -14,14 +14,18 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.ky = exports.config = exports.axios = exports.alovaGlobals = exports.alova = void 0;
17
+ exports.ky = exports.config = exports.axios = exports.alovaGlobals = exports.alova = exports.yapi = exports.swagger = exports.knife4j = exports.fastapi = void 0;
18
18
  __exportStar(require("./createPlugin"), exports);
19
19
  __exportStar(require("./presets/aiDoc"), exports);
20
20
  __exportStar(require("./presets/apifox"), exports);
21
21
  __exportStar(require("./presets/filterApi"), exports);
22
22
  __exportStar(require("./presets/importType"), exports);
23
23
  __exportStar(require("./presets/payloadModifier"), exports);
24
- __exportStar(require("./presets/platform"), exports);
24
+ var platform_1 = require("./presets/platform");
25
+ Object.defineProperty(exports, "fastapi", { enumerable: true, get: function () { return platform_1.fastapi; } });
26
+ Object.defineProperty(exports, "knife4j", { enumerable: true, get: function () { return platform_1.knife4j; } });
27
+ Object.defineProperty(exports, "swagger", { enumerable: true, get: function () { return platform_1.swagger; } });
28
+ Object.defineProperty(exports, "yapi", { enumerable: true, get: function () { return platform_1.yapi; } });
25
29
  __exportStar(require("./presets/rename"), exports);
26
30
  __exportStar(require("./presets/tagModifier"), exports);
27
31
  // Template preset plugins
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fastapi = void 0;
4
+ const constant_1 = require("../../../constant");
5
+ const shared_1 = require("./shared");
6
+ /**
7
+ * FastAPI platform plugin.
8
+ *
9
+ * Pass the base URL of your FastAPI app; the plugin will try `/openapi.json`
10
+ * first, then fall back to the bare base URL.
11
+ *
12
+ * @param input - base URL string, or an array of base URLs
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * plugins: [fastapi('http://fastapi-example.dokkuapp.com'), alovaGlobals()]
17
+ * ```
18
+ */
19
+ exports.fastapi = (0, shared_1.defineUrlPlatformPlugin)(constant_1.PluginName.FASTAPI, base => [
20
+ `${base}/openapi.json`,
21
+ base,
22
+ ]);
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.yapi = exports.swagger = exports.withCookie = exports.normalizeBase = exports.defineUrlPlatformPlugin = exports.knife4j = exports.fastapi = void 0;
4
+ var fastapi_1 = require("./fastapi");
5
+ Object.defineProperty(exports, "fastapi", { enumerable: true, get: function () { return fastapi_1.fastapi; } });
6
+ var knife4j_1 = require("./knife4j");
7
+ Object.defineProperty(exports, "knife4j", { enumerable: true, get: function () { return knife4j_1.knife4j; } });
8
+ var shared_1 = require("./shared");
9
+ Object.defineProperty(exports, "defineUrlPlatformPlugin", { enumerable: true, get: function () { return shared_1.defineUrlPlatformPlugin; } });
10
+ Object.defineProperty(exports, "normalizeBase", { enumerable: true, get: function () { return shared_1.normalizeBase; } });
11
+ Object.defineProperty(exports, "withCookie", { enumerable: true, get: function () { return shared_1.withCookie; } });
12
+ var swagger_1 = require("./swagger");
13
+ Object.defineProperty(exports, "swagger", { enumerable: true, get: function () { return swagger_1.swagger; } });
14
+ var yapi_1 = require("./yapi");
15
+ Object.defineProperty(exports, "yapi", { enumerable: true, get: function () { return yapi_1.yapi; } });
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.knife4j = void 0;
4
+ const constant_1 = require("../../../constant");
5
+ const shared_1 = require("./shared");
6
+ /**
7
+ * Knife4j platform plugin.
8
+ *
9
+ * Pass the base URL of your Knife4j instance; the plugin will try the OAS3
10
+ * endpoint (springdoc) first, then the Swagger2 endpoint (springfox), then the
11
+ * bare base URL.
12
+ *
13
+ * @param input - base URL string, or an array of base URLs
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * plugins: [knife4j('https://openapi3.demo.knife4jnext.com'), alovaGlobals()]
18
+ * ```
19
+ */
20
+ exports.knife4j = (0, shared_1.defineUrlPlatformPlugin)(constant_1.PluginName.KNIFE4J, base => [
21
+ `${base}/v3/api-docs`,
22
+ `${base}/v2/api-docs`,
23
+ base,
24
+ ]);
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeBase = normalizeBase;
4
+ exports.defineUrlPlatformPlugin = defineUrlPlatformPlugin;
5
+ exports.withCookie = withCookie;
6
+ /** Remove trailing slashes from a base URL */
7
+ function normalizeBase(baseUrl) {
8
+ while (baseUrl.endsWith('/')) {
9
+ baseUrl = baseUrl.slice(0, -1);
10
+ }
11
+ return baseUrl;
12
+ }
13
+ /**
14
+ * Factory for "URL-constructing" platform plugins.
15
+ *
16
+ * Unlike the old `platform()` helper, the platform base URL(s) are supplied as
17
+ * the plugin's own argument (e.g. `swagger('https://petstore.swagger.io')`)
18
+ * rather than being read from `config.input`. The plugin writes the resolved
19
+ * candidate URLs into `config.input` inside its `config` hook, so downstream
20
+ * fetching (which tries each URL in order and keeps the first success) works
21
+ * unchanged.
22
+ */
23
+ function defineUrlPlatformPlugin(name, buildUrls) {
24
+ return (input) => {
25
+ const inputs = Array.isArray(input) ? [...new Set(input)] : [input];
26
+ return {
27
+ name,
28
+ config({ config }) {
29
+ config.input = inputs.flatMap(url => buildUrls(normalizeBase(String(url))));
30
+ return config;
31
+ },
32
+ };
33
+ };
34
+ }
35
+ /** Merge a cookie into config.fetchOptions.headers */
36
+ function withCookie(config, cookie) {
37
+ config.fetchOptions = {
38
+ ...config.fetchOptions,
39
+ headers: {
40
+ ...config.fetchOptions?.headers,
41
+ cookie,
42
+ },
43
+ };
44
+ return config;
45
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.swagger = void 0;
4
+ const constant_1 = require("../../../constant");
5
+ const shared_1 = require("./shared");
6
+ /**
7
+ * Swagger platform plugin.
8
+ *
9
+ * Pass the base URL of your Swagger UI / server; the plugin will try several
10
+ * common OpenAPI document endpoints (OAS3 first, then Swagger2, then the bare
11
+ * base URL) and let the framework pick the first one that responds.
12
+ *
13
+ * @param input - base URL string, or an array of base URLs
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { swagger, alovaGlobals } from 'wormajs/plugin';
18
+ *
19
+ * defineConfig({
20
+ * generator: [{
21
+ * plugins: [swagger('https://petstore3.swagger.io'), alovaGlobals()],
22
+ * output: './src/api',
23
+ * }]
24
+ * });
25
+ * ```
26
+ */
27
+ exports.swagger = (0, shared_1.defineUrlPlatformPlugin)(constant_1.PluginName.SWAGGER, base => [
28
+ `${base}/openapi.json`,
29
+ `${base}/v2/swagger.json`,
30
+ `${base}/api/v3/openapi.json`,
31
+ base,
32
+ ]);
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.yapi = yapi;
4
+ const constant_1 = require("../../../constant");
5
+ const shared_1 = require("./shared");
6
+ /**
7
+ * YApi platform plugin.
8
+ *
9
+ * YApi projects are private, so the OpenAPI document must be exported through
10
+ * YApi's own export endpoint, authenticated with your login cookie. The plugin
11
+ * builds the export URL from the server base URL (`url`) plus the required
12
+ * `pid` and the optional `type` / `status` / `isWiki` query params (which
13
+ * default to `OpenAPIV2`, `all`, and `true` respectively).
14
+ *
15
+ * `url`, `pid` and `cookie` are required — the plugin throws a clear error when
16
+ * any is missing.
17
+ *
18
+ * @param options - `{ url, pid, cookie?, type?, status?, isWiki?, timeout? }`
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { yapi, alovaGlobals } from 'wormajs/plugin';
23
+ *
24
+ * defineConfig({
25
+ * generator: [{
26
+ * plugins: [
27
+ * yapi({
28
+ * url: 'https://yapi.xxx.com',
29
+ * pid: 123,
30
+ * cookie: '_yapi_token=xxx; _yapi_uid=yyy',
31
+ * }),
32
+ * alovaGlobals(),
33
+ * ],
34
+ * output: './src/api',
35
+ * }]
36
+ * });
37
+ * ```
38
+ */
39
+ function yapi(options) {
40
+ return {
41
+ name: constant_1.PluginName.YAPI,
42
+ async config({ config }) {
43
+ const baseUrl = options.url;
44
+ if (!baseUrl) {
45
+ throw new Error('[yapi] `url` is required — the YApi server base URL '
46
+ + '(e.g. yapi({ url: "https://yapi.xxx.com", pid: 123, cookie: "..." })).');
47
+ }
48
+ const pid = options.pid;
49
+ if (pid == null) {
50
+ throw new Error('[yapi] `pid` is required — the YApi project id used to build the export URL '
51
+ + '(e.g. yapi({ url: "https://yapi.xxx.com", pid: 123, cookie: "..." })).');
52
+ }
53
+ const cookie = options.cookie ?? config.fetchOptions?.headers?.cookie;
54
+ if (!cookie) {
55
+ throw new Error('[yapi] `cookie` is required. YApi projects are private, so the export '
56
+ + 'endpoint needs your login cookie to fetch the OpenAPI document.\n'
57
+ + ' e.g. yapi({ url: "https://yapi.xxx.com", pid: 123, cookie: "_yapi_token=xxx; ..." })');
58
+ }
59
+ const type = options.type ?? 'OpenAPIV2';
60
+ const status = options.status ?? 'all';
61
+ const isWiki = options.isWiki ?? true;
62
+ const base = (0, shared_1.normalizeBase)(baseUrl);
63
+ config.input = `${base}/api/plugin/exportSwagger`
64
+ + `?type=${encodeURIComponent(type)}`
65
+ + `&pid=${encodeURIComponent(String(pid))}`
66
+ + `&status=${encodeURIComponent(status)}`
67
+ + `&isWiki=${isWiki}`;
68
+ (0, shared_1.withCookie)(config, cookie);
69
+ if (options.timeout != null) {
70
+ config.fetchOptions = { ...config.fetchOptions, timeout: options.timeout };
71
+ }
72
+ return config;
73
+ },
74
+ };
75
+ }
@@ -27,8 +27,12 @@ function toPascalCase(str) {
27
27
  * Applies renaming rules to the specified value
28
28
  * @returns The renamed value, or original value if not matched
29
29
  */
30
- function applyRenameRule(value, config, apiDescriptor) {
31
- if (!(0, utils_1.isMatch)(value, config.match)) {
30
+ function applyRenameRule(value, config, apiDescriptor, level = 0) {
31
+ // Skip non-string values (e.g. a missing or empty operationId) to avoid runtime errors
32
+ if (typeof value !== 'string') {
33
+ return value;
34
+ }
35
+ if (!(0, utils_1.isMatch)(value, config.match, level)) {
32
36
  return value;
33
37
  }
34
38
  if (config.transform) {
@@ -38,7 +42,7 @@ function applyRenameRule(value, config, apiDescriptor) {
38
42
  return value;
39
43
  }
40
44
  if (config.style === 'kebabCase' && config.scope === 'refName') {
41
- throw new Error(`Invalid rename style: ${config.style}、${config.scope}`);
45
+ throw new Error(`Invalid rename style: ${config.style}, ${config.scope}`);
42
46
  }
43
47
  switch (config.style) {
44
48
  case 'camelCase':
@@ -53,6 +57,59 @@ function applyRenameRule(value, config, apiDescriptor) {
53
57
  throw new Error(`Invalid rename style: ${config.style}`);
54
58
  }
55
59
  }
60
+ /**
61
+ * Validates that no duplicate names exist after renaming.
62
+ * Throws an error with details if duplicates are found within the same scope.
63
+ *
64
+ * @param names the renamed names to check for duplicates
65
+ * @param scopeLabel used to identify the scope in the error message
66
+ * @param apiDescriptor the API descriptor the names belong to
67
+ * @param originalNames optional, the original keys before renaming (same order
68
+ * as `names`). When provided, the error message also lists which original keys
69
+ * were mapped to each duplicated name, making the conflict easier to locate.
70
+ */
71
+ function assertNoDuplicates(names, scopeLabel, apiDescriptor, originalNames) {
72
+ if (!names || names.length === 0)
73
+ return;
74
+ const counts = new Map();
75
+ for (const name of names) {
76
+ counts.set(name, (counts.get(name) ?? 0) + 1);
77
+ }
78
+ const duplicates = [...counts.entries()]
79
+ .filter(([, count]) => count > 1)
80
+ .map(([name]) => name);
81
+ if (duplicates.length === 0)
82
+ return;
83
+ // When original keys are available, group them by the duplicated (renamed) name
84
+ // so users can see exactly which keys collided.
85
+ let detail = '';
86
+ if (originalNames && originalNames.length === names.length) {
87
+ const grouped = new Map();
88
+ for (let i = 0; i < names.length; i++) {
89
+ if (duplicates.includes(names[i])) {
90
+ const list = grouped.get(names[i]) ?? [];
91
+ list.push(originalNames[i]);
92
+ grouped.set(names[i], list);
93
+ }
94
+ }
95
+ detail = [...grouped.entries()]
96
+ .map(([renamed, originals]) => ` ${renamed} <- [${originals.join(', ')}]`)
97
+ .join('\n');
98
+ }
99
+ else {
100
+ detail = ` ${duplicates.join(', ')}`;
101
+ }
102
+ const method = apiDescriptor.method ? String(apiDescriptor.method).toUpperCase() : '';
103
+ const url = apiDescriptor.url ?? '';
104
+ throw new Error(`[rename] Duplicate names found after renaming (scope=${scopeLabel}):\n`
105
+ + ` Conflicting (renamed) keys and their original keys:\n`
106
+ + `${detail}\n`
107
+ + ` Reason: different original names were mapped to the same name after `
108
+ + `match/transform/style processing, which would cause naming conflicts or `
109
+ + `missing references in the generated code.\n`
110
+ + ` Please adjust the rename config to avoid mapping multiple names to the same result.\n`
111
+ + ` API: ${method} ${url}`);
112
+ }
56
113
  /**
57
114
  * renames URL path by processing each segment individually
58
115
  * while keeping path parameter placeholders
@@ -65,27 +122,72 @@ function renameUrl(url, config, apiDescriptor) {
65
122
  if ((segment.startsWith('{') && segment.endsWith('}')) || !segment) {
66
123
  return segment;
67
124
  }
68
- return applyRenameRule(segment, config, apiDescriptor);
125
+ return applyRenameRule(segment, config, apiDescriptor, 0);
69
126
  })
70
127
  .join('/')
71
128
  .replace(/^\/{2,}/g, '/');
72
129
  }
73
130
  /**
74
- * Transforms object properties using the renaming rules
131
+ * Recursively processes a schema (supports nested objects and arrays),
132
+ * applying property renaming to nested objects.
133
+ * @param schema current schema
134
+ * @param level current nesting level (starts from 0)
135
+ * @param scopeLabel used to identify the scope in duplicate-name errors
136
+ */
137
+ function transformSchema(schema, config, apiDescriptor, level, scopeLabel) {
138
+ if (!schema || typeof schema !== 'object')
139
+ return schema;
140
+ // Nested object: recursively process its properties
141
+ if ('properties' in schema && schema.properties) {
142
+ return transformProperties(schema, config, apiDescriptor, level, scopeLabel);
143
+ }
144
+ // Array: recursively process items (supports a single schema or an array of schemas)
145
+ if (schema.items && typeof schema.items === 'object') {
146
+ if (Array.isArray(schema.items)) {
147
+ return {
148
+ ...schema,
149
+ items: schema.items.map((item) => transformSchema(item, config, apiDescriptor, level, scopeLabel)),
150
+ };
151
+ }
152
+ return {
153
+ ...schema,
154
+ items: transformSchema(schema.items, config, apiDescriptor, level, scopeLabel),
155
+ };
156
+ }
157
+ return schema;
158
+ }
159
+ /**
160
+ * Transforms object properties using the renaming rules (supports deep recursive renaming)
161
+ * @param level current nesting level (starts from 0), passed through to the match function
162
+ * @param scopeLabel used to identify the scope in duplicate-name errors
75
163
  */
76
- function transformProperties(obj, config, apiDescriptor) {
77
- if (!obj || typeof obj !== 'object' || !('properties' in obj)) {
164
+ function transformProperties(obj, config, apiDescriptor, level = 0, scopeLabel = config.scope ?? 'data') {
165
+ if (!obj || typeof obj !== 'object' || !('properties' in obj) || !obj.properties) {
78
166
  return obj;
79
167
  }
80
- const properties = { ...obj.properties };
168
+ const properties = obj.properties;
81
169
  const newProperties = {};
170
+ const newKeys = [];
171
+ // Record the oldKey -> newKey mapping, used to keep the same-level required array in sync
172
+ const keyMap = new Map();
82
173
  for (const key in properties) {
83
- const newKey = applyRenameRule(key, config, apiDescriptor);
84
- newProperties[newKey] = properties[key];
174
+ const newKey = applyRenameRule(key, config, apiDescriptor, level);
175
+ newKeys.push(newKey);
176
+ keyMap.set(key, newKey);
177
+ newProperties[newKey] = transformSchema(properties[key], config, apiDescriptor, level + 1, scopeLabel);
178
+ }
179
+ // Duplicate property names within the same object level would overwrite each other, so report an error
180
+ assertNoDuplicates(newKeys, scopeLabel, apiDescriptor, Object.keys(properties));
181
+ // Keep the required array of the current level in sync with the renamed property names;
182
+ // otherwise the generator would crash during normalization because required references a non-existent property
183
+ let newRequired = obj.required;
184
+ if (Array.isArray(newRequired)) {
185
+ newRequired = newRequired.map((r) => keyMap.get(r) ?? r);
85
186
  }
86
187
  return {
87
188
  ...obj,
88
189
  properties: newProperties,
190
+ ...(newRequired !== obj.required ? { required: newRequired } : {}),
89
191
  };
90
192
  }
91
193
  /**
@@ -99,7 +201,7 @@ function transformParameters(parameters, type, config, apiDescriptor) {
99
201
  if (param.in === type) {
100
202
  return {
101
203
  ...param,
102
- name: applyRenameRule(param.name, config, apiDescriptor),
204
+ name: applyRenameRule(param.name, config, apiDescriptor, 0),
103
205
  };
104
206
  }
105
207
  return param;
@@ -110,10 +212,14 @@ function transformRefNameMap(refNameMap, config, apiDescriptor) {
110
212
  return refNameMap;
111
213
  }
112
214
  const newRefNameMap = {};
215
+ const newValues = [];
113
216
  for (const key in refNameMap) {
114
- const newValue = applyRenameRule(refNameMap[key], config, apiDescriptor);
217
+ const newValue = applyRenameRule(refNameMap[key], config, apiDescriptor, 0);
218
+ newValues.push(newValue);
115
219
  newRefNameMap[key] = newValue;
116
220
  }
221
+ // Different $refs mapping to the same type name would generate duplicate interfaces, so report an error
222
+ assertNoDuplicates(newValues, 'refName', apiDescriptor, Object.keys(refNameMap));
117
223
  return newRefNameMap;
118
224
  }
119
225
  /**
@@ -125,6 +231,7 @@ function transformRefNameMap(refNameMap, config, apiDescriptor) {
125
231
  * - pathParams: Renames path parameters and their placeholders in URL
126
232
  * - data: Renames request body properties
127
233
  * - response: Renames response body properties
234
+ * - name: Renames the generated API function name (operationId)
128
235
  */
129
236
  function renameApiDescriptor(apiDescriptor, config) {
130
237
  if (!apiDescriptor)
@@ -134,33 +241,49 @@ function renameApiDescriptor(apiDescriptor, config) {
134
241
  switch (scope) {
135
242
  case constant_1.RenameScope.PARAMS:
136
243
  if (newDescriptor.parameters) {
244
+ const originalParams = newDescriptor.parameters.filter(p => p.in === constant_1.ParameterIn.QUERY).map(p => p.name);
137
245
  newDescriptor.parameters = transformParameters(newDescriptor.parameters, constant_1.ParameterIn.QUERY, config, apiDescriptor);
246
+ // Duplicate query parameter names would be indistinguishable when calling the API, so report an error
247
+ assertNoDuplicates(newDescriptor.parameters.filter(p => p.in === constant_1.ParameterIn.QUERY).map(p => p.name), 'params', apiDescriptor, originalParams);
138
248
  }
139
249
  break;
140
250
  case constant_1.RenameScope.PATH_PARAMS:
141
251
  if (newDescriptor.parameters) {
252
+ const originalParams = newDescriptor.parameters.filter(p => p.in === constant_1.ParameterIn.PATH).map(p => p.name);
142
253
  newDescriptor.parameters = transformParameters(newDescriptor.parameters, constant_1.ParameterIn.PATH, config, apiDescriptor);
143
- }
144
- if (newDescriptor.url) {
145
- newDescriptor.url = newDescriptor.url.replace(/\{([^}]+)\}/g, (match, paramName) => {
146
- const newName = applyRenameRule(paramName, config, apiDescriptor);
147
- return `{${newName}}`;
148
- });
254
+ if (newDescriptor.url) {
255
+ newDescriptor.url = newDescriptor.url.replace(/\{([^}]+)\}/g, (match, paramName) => {
256
+ const newName = applyRenameRule(paramName, config, apiDescriptor, 0);
257
+ return `{${newName}}`;
258
+ });
259
+ }
260
+ // Duplicate path parameter names would make URL placeholders no longer match the parameters, so report an error
261
+ assertNoDuplicates((newDescriptor.parameters ?? []).filter(p => p.in === constant_1.ParameterIn.PATH).map(p => p.name), 'pathParams', apiDescriptor, originalParams);
149
262
  }
150
263
  break;
151
264
  case constant_1.RenameScope.DATA:
152
265
  if (newDescriptor.requestBody) {
153
- newDescriptor.requestBody = transformProperties(newDescriptor.requestBody, config, apiDescriptor);
266
+ newDescriptor.requestBody = transformProperties(newDescriptor.requestBody, config, apiDescriptor, 0, 'data');
154
267
  }
155
268
  break;
156
269
  case constant_1.RenameScope.RESPONSE:
157
270
  if (newDescriptor.responses) {
158
- newDescriptor.responses = transformProperties(newDescriptor.responses, config, apiDescriptor);
271
+ newDescriptor.responses = transformProperties(newDescriptor.responses, config, apiDescriptor, 0, 'response');
159
272
  }
160
273
  break;
161
274
  case constant_1.RenameScope.URL:
162
275
  if (newDescriptor.url) {
276
+ const originalUrlNames = newDescriptor.url
277
+ .split('/')
278
+ .filter(Boolean)
279
+ .map(segment => (segment.startsWith('{') && segment.endsWith('}') ? segment.slice(1, -1) : segment));
163
280
  newDescriptor.url = renameUrl(newDescriptor.url, config, apiDescriptor);
281
+ // Duplicate path segments would make the URL unable to distinguish different resources, so report an error
282
+ const urlNames = newDescriptor.url
283
+ .split('/')
284
+ .filter(Boolean)
285
+ .map(segment => (segment.startsWith('{') && segment.endsWith('}') ? segment.slice(1, -1) : segment));
286
+ assertNoDuplicates(urlNames, 'url', apiDescriptor, originalUrlNames);
164
287
  }
165
288
  break;
166
289
  case constant_1.RenameScope.REF_NAME:
@@ -168,6 +291,11 @@ function renameApiDescriptor(apiDescriptor, config) {
168
291
  newDescriptor.refNameMap = transformRefNameMap(newDescriptor.refNameMap, config, apiDescriptor);
169
292
  }
170
293
  break;
294
+ case constant_1.RenameScope.NAME:
295
+ if (newDescriptor.operationId != null) {
296
+ newDescriptor.operationId = applyRenameRule(newDescriptor.operationId, config, apiDescriptor, 0);
297
+ }
298
+ break;
171
299
  default:
172
300
  // No action needed, keep original descriptor
173
301
  break;
@@ -185,7 +313,7 @@ function rename(config) {
185
313
  throw new Error('at least one of `style` or `transform` is required');
186
314
  }
187
315
  if (conf.style === 'kebabCase' && conf.scope === 'refName') {
188
- throw new Error(`Invalid rename style: ${conf.style}、${conf.scope}`);
316
+ throw new Error(`Invalid rename style: ${conf.style}, ${conf.scope}`);
189
317
  }
190
318
  }
191
319
  return {
@@ -4,23 +4,24 @@ exports.extend = extend;
4
4
  exports.isMatch = isMatch;
5
5
  const prepareConfig_1 = require("../../functions/prepareConfig");
6
6
  /**
7
- * 扩展生成器配置:
8
- * 根据传入的 newConfig(对象或函数)与基础 config 生成合并后的配置。
9
- * 当未提供 newConfig 时,直接回退到原始 config
10
- * @param config 基础配置
11
- * @param newConfig 需要扩展的配置或用于生成扩展配置的函数
12
- * @returns 合并且标准化后的配置
7
+ * Extends the generator configuration:
8
+ * merges the provided newConfig (object or function) with the base config to produce the merged config.
9
+ * When newConfig is not provided, falls back to the original config.
10
+ * @param config base configuration
11
+ * @param newConfig config to merge, or a function that produces the merged config
12
+ * @returns the merged and normalized configuration
13
13
  */
14
14
  function extend(config, newConfig) {
15
- // 根据 newConfig 类型计算扩展配置:函数则调用生成配置,否则直接使用对象;未提供则回退到原始 config
15
+ // Compute the extension config based on newConfig's type: call it if it's a function,
16
+ // otherwise use it directly; fall back to the original config if not provided
16
17
  const pluginExtendsConfig = typeof newConfig === 'function' ? newConfig(config) : (newConfig ?? config);
17
- // 使用 extendsConfig 进行配置合并与标准化,确保最终结构满足生成器需求
18
+ // Use extendsConfig to merge and normalize, ensuring the final structure meets the generator's requirements
18
19
  return (0, prepareConfig_1.extendsConfig)(config, pluginExtendsConfig);
19
20
  }
20
21
  /**
21
22
  * Tests if value matches the specified rule
22
23
  */
23
- function isMatch(value, match) {
24
+ function isMatch(value, match, level = 0) {
24
25
  if (!match)
25
26
  return true;
26
27
  if (typeof match === 'string') {
@@ -30,7 +31,7 @@ function isMatch(value, match) {
30
31
  return match.test(value);
31
32
  }
32
33
  if (typeof match === 'function') {
33
- return match(value);
34
+ return match(value, level);
34
35
  }
35
36
  return false;
36
37
  }
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getPresetTemplatePath = getPresetTemplatePath;
7
- exports.registerProcessTypeHelper = registerProcessTypeHelper;
8
7
  exports.config = config;
9
8
  exports.alovaGlobals = alovaGlobals;
10
9
  exports.alova = alova;
@@ -21,27 +20,6 @@ function getPresetTemplatePath(presetName) {
21
20
  // 编译后在 dist/template/presets 目录
22
21
  return node_path_1.default.join(__dirname, 'presets', presetName);
23
22
  }
24
- /**
25
- * Register processType helper on an hbs instance.
26
- *
27
- * Scans the type string for every PascalCase identifier that is in componentNames
28
- * (and not already prefixed with "ComponentTypes.") and prefixes it.
29
- * Works uniformly for top-level names, generics, object literals, and arrays.
30
- */
31
- function registerProcessTypeHelper(hbs) {
32
- hbs.registerHelper('processType', (_typeStr, _componentNames) => {
33
- const typeStr = _typeStr;
34
- const componentNames = _componentNames;
35
- if (!typeStr || !Array.isArray(componentNames) || componentNames.length === 0) {
36
- return new hbs.SafeString(typeStr || 'unknown');
37
- }
38
- const componentSet = new Set(componentNames);
39
- const result = typeStr.replace(/(?<!ComponentTypes\.)(\b[A-Z]\w*\b)/g, (match) => {
40
- return componentSet.has(match) ? `ComponentTypes.${match}` : match;
41
- });
42
- return new hbs.SafeString(result);
43
- });
44
- }
45
23
  // ========== Template Preset Plugins ==========
46
24
  /**
47
25
  * worma.config 模板预设 - plugin mode
@@ -100,9 +78,6 @@ function alova(opts) {
100
78
  getTemplate() {
101
79
  return { path: getPresetTemplatePath(constant_1.PresetTemplateName.ALOVA) };
102
80
  },
103
- onHandlebarsCreated({ hbs }) {
104
- registerProcessTypeHelper(hbs);
105
- },
106
81
  beforeCodeGenerate({ data }) {
107
82
  data.config = {
108
83
  ...data.config,
@@ -122,9 +97,6 @@ function axios(opts) {
122
97
  getTemplate() {
123
98
  return { path: getPresetTemplatePath(constant_1.PresetTemplateName.AXIOS) };
124
99
  },
125
- onHandlebarsCreated({ hbs }) {
126
- registerProcessTypeHelper(hbs);
127
- },
128
100
  beforeCodeGenerate({ data }) {
129
101
  data.config = {
130
102
  ...data.config,
@@ -144,9 +116,6 @@ function fetch(opts) {
144
116
  getTemplate() {
145
117
  return { path: getPresetTemplatePath(constant_1.PresetTemplateName.FETCH) };
146
118
  },
147
- onHandlebarsCreated({ hbs }) {
148
- registerProcessTypeHelper(hbs);
149
- },
150
119
  beforeCodeGenerate({ data }) {
151
120
  data.config = {
152
121
  ...data.config,
@@ -166,9 +135,6 @@ function ky(opts) {
166
135
  getTemplate() {
167
136
  return { path: getPresetTemplatePath(constant_1.PresetTemplateName.KY) };
168
137
  },
169
- onHandlebarsCreated({ hbs }) {
170
- registerProcessTypeHelper(hbs);
171
- },
172
138
  beforeCodeGenerate({ data }) {
173
139
  data.config = {
174
140
  ...data.config,
@@ -1,8 +1,8 @@
1
1
  {{#or pathParameters queryParameters requestBody}}
2
2
  type {{{name}}}ExtraConfig = {
3
- {{#if pathParameters}}pathParams: {{processType pathParameters @root.componentNames}};
4
- {{/if}}{{#if queryParameters}}params: {{processType queryParameters @root.componentNames}};
5
- {{/if}}{{#if requestBody}}data: {{processType requestBody @root.componentNames}};
3
+ {{#if pathParameters}}pathParams: {{addNamespace pathParameters @root.componentNames}};
4
+ {{/if}}{{#if queryParameters}}params: {{addNamespace queryParameters @root.componentNames}};
5
+ {{/if}}{{#if requestBody}}data: {{addNamespace requestBody @root.componentNames}};
6
6
  {{/if}}
7
7
  };
8
8
  {{/or}}
@@ -1 +1 @@
1
- export declare function {{{name}}}<Config extends Alova2MethodConfig<{{processType response @root.componentNames}}> & {{{name}}}ExtraConfig>(config: Config): Alova2Method<ExtractResponded<Config, '{{{name}}}', {{processType response @root.componentNames}}>>;
1
+ export declare function {{{name}}}<Config extends Alova2MethodConfig<{{addNamespace response @root.componentNames}}> & {{{name}}}ExtraConfig>(config: Config): Alova2Method<ExtractResponded<Config, '{{{name}}}', {{addNamespace response @root.componentNames}}>>;
@@ -12,15 +12,15 @@ import type * as ComponentTypes from '../components';
12
12
  {{#each apis}}
13
13
 
14
14
  interface {{{name}}}ExtraConfig {
15
- {{#if pathParameters}}pathParams: {{processType pathParameters @root.componentNames}};
16
- {{/if}}{{#if queryParameters}}params: {{processType queryParameters @root.componentNames}};
17
- {{/if}}{{#if requestBody}}data: {{processType requestBody @root.componentNames}};
15
+ {{#if pathParameters}}pathParams: {{addNamespace pathParameters @root.componentNames}};
16
+ {{/if}}{{#if queryParameters}}params: {{addNamespace queryParameters @root.componentNames}};
17
+ {{/if}}{{#if requestBody}}data: {{addNamespace requestBody @root.componentNames}};
18
18
  {{/if}}
19
19
  }
20
20
  {{> api-jsdoc}}
21
- export function {{{name}}}<Config extends Alova2MethodConfig<{{processType response @root.componentNames}}> & {{{name}}}ExtraConfig>(config: Config): Alova2Method<ExtractResponded<Config, '{{{name}}}', {{processType response @root.componentNames}}>> {
21
+ export function {{{name}}}<Config extends Alova2MethodConfig<{{addNamespace response @root.componentNames}}> & {{{name}}}ExtraConfig>(config: Config): Alova2Method<ExtractResponded<Config, '{{{name}}}', {{addNamespace response @root.componentNames}}>> {
22
22
  const { url, data, mergedConfig } = buildPayload('{{{path}}}', {{{tag}}}DefaultConfig, {{{name}}}.name, config);
23
- return alovaInstance.Request<ExtractResponded<Config, '{{{name}}}', {{processType response @root.componentNames}}>>({
23
+ return alovaInstance.Request<ExtractResponded<Config, '{{{name}}}', {{addNamespace response @root.componentNames}}>>({
24
24
  ...mergedConfig,
25
25
  url,
26
26
  data,
@@ -1,7 +1,7 @@
1
- type {{{name}}}Response = {{processType response @root.componentNames}};
1
+ type {{{name}}}Response = {{addNamespace response @root.componentNames}};
2
2
  type {{{name}}}ExtraConfig = Omit<AxiosRequestConfig, 'data' | 'params'> & {
3
- {{#if pathParameters}}pathParams: {{processType pathParameters @root.componentNames}};
4
- {{/if}}{{#if queryParameters}}params: {{processType queryParameters @root.componentNames}};
5
- {{/if}}{{#if requestBody}}data: {{processType requestBody @root.componentNames}};
3
+ {{#if pathParameters}}pathParams: {{addNamespace pathParameters @root.componentNames}};
4
+ {{/if}}{{#if queryParameters}}params: {{addNamespace queryParameters @root.componentNames}};
5
+ {{/if}}{{#if requestBody}}data: {{addNamespace requestBody @root.componentNames}};
6
6
  {{/if}}
7
7
  };
@@ -9,11 +9,11 @@ import { {{{tagName}}}DefaultConfig } from '.';
9
9
 
10
10
  {{#each tagedApis}}
11
11
  {{#each apis}}
12
- type {{{name}}}Response = {{processType response @root.componentNames}};
12
+ type {{{name}}}Response = {{addNamespace response @root.componentNames}};
13
13
  type {{{name}}}ExtraConfig = Omit<AxiosRequestConfig, 'data' | 'params'> & {
14
- {{#if pathParameters}}pathParams: {{processType pathParameters @root.componentNames}};
15
- {{/if}}{{#if queryParameters}}params: {{processType queryParameters @root.componentNames}};
16
- {{/if}}{{#if requestBody}}data: {{processType requestBody @root.componentNames}};
14
+ {{#if pathParameters}}pathParams: {{addNamespace pathParameters @root.componentNames}};
15
+ {{/if}}{{#if queryParameters}}params: {{addNamespace queryParameters @root.componentNames}};
16
+ {{/if}}{{#if requestBody}}data: {{addNamespace requestBody @root.componentNames}};
17
17
  {{/if}}
18
18
  };
19
19
  {{> api-jsdoc}}
@@ -1,5 +1,5 @@
1
1
  const { defineConfig } = require('wormajs');
2
- const { platform, aiDoc{{#if templateImport}}, {{{templateImport}}}{{/if}} } = require('wormajs/plugin');
2
+ const { swagger, aiDoc{{#if templateImport}}, {{{templateImport}}}{{/if}} } = require('wormajs/plugin');
3
3
 
4
4
  // For more config detailed visit:
5
5
  // https://github.com/alovajs/devtools
@@ -1,5 +1,5 @@
1
1
  import { defineConfig } from 'wormajs';
2
- import { platform, aiDoc{{#if templateImport}}, {{{templateImport}}}{{/if}} } from 'wormajs/plugin';
2
+ import { swagger, aiDoc{{#if templateImport}}, {{{templateImport}}}{{/if}} } from 'wormajs/plugin';
3
3
 
4
4
  // For more config detailed visit:
5
5
  // https://github.com/alovajs/devtools
@@ -1,45 +1,45 @@
1
- generator: [
2
- {
3
- /**
4
- * file input. support:
5
- * 1. openapi json file url
6
- * 2. local file
7
- * 3. string array — each URL will be tried in order, first success wins
8
- */
9
- input: 'http://localhost:3000',
10
-
11
- /**
12
- * output path of interface file and type file.
13
- * Multiple generators cannot have the same address, otherwise the generated code will overwrite each other.
14
- */
15
- output: 'src/api',
16
-
17
- /**
18
- * platform plugin: auto-resolves OpenAPI file URLs from platform type.
19
- * Uses config.input as base URL to construct candidate OpenAPI endpoints.
20
- */
21
- plugins: [platform('swagger'), aiDoc({ installSkill: true }), {{{templateCall}}}],
22
-
23
- /**
24
- * the mediaType of the generated response data. default is `application/json`
25
- */
26
- // responseMediaType: 'application/json',
27
-
28
- /**
29
- * the bodyMediaType of the generated request body data. default is `application/json`
30
- */
31
- // bodyMediaType: 'application/json',
32
-
33
- /**
34
- * type of generated code. The options are `auto/ts/typescript/module/commonjs`
35
- */
36
- // type: 'auto',
37
-
38
- /**
39
- * filter or convert the generated api information, return an apiDescriptor, if this function is not specified, the apiDescriptor object is not converted
40
- */
41
- // handleApi: apiDescriptor => {
42
- // return apiDescriptor;
43
- // }
44
- }
45
- ]
1
+ generator: [
2
+ {
3
+ /**
4
+ * file input. support:
5
+ * 1. openapi json file url
6
+ * 2. local file
7
+ * 3. string array — each URL will be tried in order, first success wins
8
+ */
9
+ // input: 'http://localhost:3000',
10
+
11
+ /**
12
+ * output path of interface file and type file.
13
+ * Multiple generators cannot have the same address, otherwise the generated code will overwrite each other.
14
+ */
15
+ output: 'src/api',
16
+
17
+ /**
18
+ * swagger platform plugin: auto-resolves OpenAPI file URLs from the base URL.
19
+ * The base URL is passed as the plugin argument (not config.input).
20
+ */
21
+ plugins: [swagger('http://localhost:3000'), aiDoc({ installSkill: true }), {{{templateCall}}}],
22
+
23
+ /**
24
+ * the mediaType of the generated response data. default is `application/json`
25
+ */
26
+ // responseMediaType: 'application/json',
27
+
28
+ /**
29
+ * the bodyMediaType of the generated request body data. default is `application/json`
30
+ */
31
+ // bodyMediaType: 'application/json',
32
+
33
+ /**
34
+ * type of generated code. The options are `auto/ts/typescript/module/commonjs`
35
+ */
36
+ // type: 'auto',
37
+
38
+ /**
39
+ * filter or convert the generated api information, return an apiDescriptor, if this function is not specified, the apiDescriptor object is not converted
40
+ */
41
+ // handleApi: apiDescriptor => {
42
+ // return apiDescriptor;
43
+ // }
44
+ }
45
+ ]
@@ -1,5 +1,5 @@
1
1
  import { defineConfig } from 'wormajs';
2
- import { platform, aiDoc{{#if templateImport}}, {{{templateImport}}}{{/if}} } from 'wormajs/plugin';
2
+ import { swagger, aiDoc{{#if templateImport}}, {{{templateImport}}}{{/if}} } from 'wormajs/plugin';
3
3
 
4
4
  // For more config detailed visit:
5
5
  // https://github.com/alovajs/devtools
@@ -1,7 +1,7 @@
1
- type {{{name}}}Response = {{processType response @root.componentNames}};
1
+ type {{{name}}}Response = {{addNamespace response @root.componentNames}};
2
2
  type {{{name}}}ExtraConfig = Omit<RequestInit, 'body'> & {
3
- {{#if pathParameters}}pathParams: {{processType pathParameters @root.componentNames}};
4
- {{/if}}{{#if queryParameters}}params?: {{processType queryParameters @root.componentNames}};
5
- {{/if}}{{#if requestBody}}body?: {{processType requestBody @root.componentNames}} | BodyInit;
3
+ {{#if pathParameters}}pathParams: {{addNamespace pathParameters @root.componentNames}};
4
+ {{/if}}{{#if queryParameters}}params?: {{addNamespace queryParameters @root.componentNames}};
5
+ {{/if}}{{#if requestBody}}body?: {{addNamespace requestBody @root.componentNames}} | BodyInit;
6
6
  {{/if}}
7
7
  };
@@ -8,11 +8,11 @@ import { {{{tagName}}}DefaultConfig } from '.';
8
8
 
9
9
  {{#each tagedApis}}
10
10
  {{#each apis}}
11
- type {{{name}}}Response = {{processType response @root.componentNames}};
11
+ type {{{name}}}Response = {{addNamespace response @root.componentNames}};
12
12
  type {{{name}}}ExtraConfig = Omit<RequestInit, 'body'> & {
13
- {{#if pathParameters}}pathParams: {{processType pathParameters @root.componentNames}};
14
- {{/if}}{{#if queryParameters}}params?: {{processType queryParameters @root.componentNames}};
15
- {{/if}}{{#if requestBody}}body?: {{processType requestBody @root.componentNames}};
13
+ {{#if pathParameters}}pathParams: {{addNamespace pathParameters @root.componentNames}};
14
+ {{/if}}{{#if queryParameters}}params?: {{addNamespace queryParameters @root.componentNames}};
15
+ {{/if}}{{#if requestBody}}body?: {{addNamespace requestBody @root.componentNames}};
16
16
  {{/if}}
17
17
  };
18
18
  {{> api-jsdoc}}
@@ -1,9 +1,9 @@
1
- type {{{name}}}Response = {{processType response @root.componentNames}};
1
+ type {{{name}}}Response = {{addNamespace response @root.componentNames}};
2
2
  {{#or pathParameters queryParameters requestBody}}
3
3
  type {{{name}}}ExtraConfig = Options & {
4
- {{#if pathParameters}}pathParams: {{processType pathParameters @root.componentNames}};
5
- {{/if}}{{#if queryParameters}}searchParams: {{processType queryParameters @root.componentNames}};
6
- {{/if}}{{#if requestBody}}json: {{processType requestBody @root.componentNames}};
4
+ {{#if pathParameters}}pathParams: {{addNamespace pathParameters @root.componentNames}};
5
+ {{/if}}{{#if queryParameters}}searchParams: {{addNamespace queryParameters @root.componentNames}};
6
+ {{/if}}{{#if requestBody}}json: {{addNamespace requestBody @root.componentNames}};
7
7
  {{/if}}
8
8
  };
9
9
  {{/or}}
@@ -9,11 +9,11 @@ import { {{{tagName}}}DefaultConfig } from '.';
9
9
 
10
10
  {{#each tagedApis}}
11
11
  {{#each apis}}
12
- type {{{name}}}Response = {{processType response @root.componentNames}};
12
+ type {{{name}}}Response = {{addNamespace response @root.componentNames}};
13
13
  type {{{name}}}ExtraConfig = Options & {
14
- {{#if pathParameters}}pathParams: {{processType pathParameters @root.componentNames}};
15
- {{/if}}{{#if queryParameters}}searchParams: {{processType queryParameters @root.componentNames}};
16
- {{/if}}{{#if requestBody}}json: {{processType requestBody @root.componentNames}};
14
+ {{#if pathParameters}}pathParams: {{addNamespace pathParameters @root.componentNames}};
15
+ {{/if}}{{#if queryParameters}}searchParams: {{addNamespace queryParameters @root.componentNames}};
16
+ {{/if}}{{#if requestBody}}json: {{addNamespace requestBody @root.componentNames}};
17
17
  {{/if}}
18
18
  };
19
19
  {{> api-jsdoc}}
@@ -1 +1 @@
1
- {"root":["../src/config.ts","../src/constant.ts","../src/createConfig.ts","../src/defineConfig.ts","../src/generate.ts","../src/index.ts","../src/readConfig.ts","../src/resolveWorkspaces.ts","../src/bin/actions.ts","../src/bin/cli.ts","../src/bin/progressRenderer.ts","../src/bin/renderer.ts","../src/bin/theme.ts","../src/core/WorkerPool.ts","../src/core/loader/index.ts","../src/core/loader/astLoader/helper.ts","../src/core/loader/astLoader/index.ts","../src/core/loader/astLoader/generates/array.ts","../src/core/loader/astLoader/generates/enum.ts","../src/core/loader/astLoader/generates/group.ts","../src/core/loader/astLoader/generates/index.ts","../src/core/loader/astLoader/generates/interface.ts","../src/core/loader/astLoader/generates/simple.ts","../src/core/loader/astLoader/generates/tuple.ts","../src/core/loader/astLoader/generates/type.ts","../src/core/loader/astLoader/generates/utils.ts","../src/core/loader/astLoader/normalize/index.ts","../src/core/loader/astLoader/normalize/normalizer.ts","../src/core/loader/astLoader/normalize/rules/convertTypeArray.ts","../src/core/loader/astLoader/normalize/rules/handleEmptyType.ts","../src/core/loader/astLoader/normalize/rules/inferType.ts","../src/core/loader/astLoader/normalize/rules/mergeAnyOf.ts","../src/core/loader/astLoader/normalize/rules/normalizeCombiningKeywords.ts","../src/core/loader/astLoader/normalize/rules/normalizeEnum.ts","../src/core/loader/astLoader/normalize/rules/normalizeNullType.ts","../src/core/loader/astLoader/normalize/rules/removeRedundantKeywords.ts","../src/core/loader/astLoader/normalize/rules/simplifySingleType.ts","../src/core/loader/astLoader/normalize/rules/validateSchema.ts","../src/core/loader/astLoader/parsers/array.ts","../src/core/loader/astLoader/parsers/const.ts","../src/core/loader/astLoader/parsers/enum.ts","../src/core/loader/astLoader/parsers/group.ts","../src/core/loader/astLoader/parsers/index.ts","../src/core/loader/astLoader/parsers/object.ts","../src/core/loader/astLoader/parsers/reference.ts","../src/core/loader/astLoader/parsers/simple.ts","../src/core/loader/astLoader/parsers/tuple.ts","../src/core/loader/astLoader/parsers/type.ts","../src/core/loader/astLoader/parsers/utils.ts","../src/core/loader/astLoader/parsers/forward/array.ts","../src/core/loader/astLoader/parsers/forward/const.ts","../src/core/loader/astLoader/parsers/forward/enum.ts","../src/core/loader/astLoader/parsers/forward/group.ts","../src/core/loader/astLoader/parsers/forward/index.ts","../src/core/loader/astLoader/parsers/forward/object.ts","../src/core/loader/astLoader/parsers/forward/tuple.ts","../src/core/loader/astLoader/parsers/forward/type.ts","../src/core/loader/callingCodeLoader/helper.ts","../src/core/loader/callingCodeLoader/index.ts","../src/core/loader/schemaLoader/index.ts","../src/core/loader/standardLoader/helper.ts","../src/core/loader/standardLoader/index.ts","../src/core/loader/standardLoader/standards.ts","../src/core/parser/index.ts","../src/core/parser/openApiParser/helper.ts","../src/core/parser/openApiParser/index.ts","../src/core/parser/templateParser/helper.ts","../src/core/parser/templateParser/index.ts","../src/core/workerPool/index.ts","../src/core/workerPool/poolManager.ts","../src/core/workerPool/swagger2Worker.ts","../src/core/workerPool/worker.ts","../src/functions/getAutoTemplateType.ts","../src/functions/getFrameworkTag.ts","../src/functions/prepareConfig.ts","../src/functions/readWormaRc.ts","../src/functions/wormaJson.ts","../src/helper/PluginDriver.ts","../src/helper/comment.ts","../src/helper/index.ts","../src/helper/progress.ts","../src/helper/config/ConfigHelper.ts","../src/helper/config/ConfigManager.ts","../src/helper/config/GeneratorHelper.ts","../src/helper/config/index.ts","../src/helper/config/type.ts","../src/helper/config/zType.ts","../src/helper/document/index.ts","../src/helper/logger/index.ts","../src/helper/template/index.ts","../src/plugins/createPlugin.ts","../src/plugins/index.ts","../src/plugins/presets/aiDoc.ts","../src/plugins/presets/apifox.ts","../src/plugins/presets/filterApi.ts","../src/plugins/presets/importType.ts","../src/plugins/presets/platform.ts","../src/plugins/presets/rename.ts","../src/plugins/presets/tagModifier.ts","../src/plugins/presets/utils.ts","../src/plugins/presets/payloadModifier/hepler.ts","../src/plugins/presets/payloadModifier/index.ts","../src/plugins/presets/payloadModifier/type.ts","../src/template/index.ts","../src/type/api.ts","../src/type/ast.ts","../src/type/base.ts","../src/type/index.ts","../src/type/lib.ts","../src/type/openapi.ts","../src/utils/base.ts","../src/utils/format.ts","../src/utils/index.ts","../src/utils/openapi.ts","../src/utils/readPackageJson.ts","../src/utils/template.ts"],"version":"5.9.3"}
1
+ {"root":["../src/config.ts","../src/constant.ts","../src/createConfig.ts","../src/defineConfig.ts","../src/generate.ts","../src/index.ts","../src/readConfig.ts","../src/resolveWorkspaces.ts","../src/bin/actions.ts","../src/bin/cli.ts","../src/bin/progressRenderer.ts","../src/bin/renderer.ts","../src/bin/theme.ts","../src/core/WorkerPool.ts","../src/core/loader/index.ts","../src/core/loader/astLoader/helper.ts","../src/core/loader/astLoader/index.ts","../src/core/loader/astLoader/generates/array.ts","../src/core/loader/astLoader/generates/enum.ts","../src/core/loader/astLoader/generates/group.ts","../src/core/loader/astLoader/generates/index.ts","../src/core/loader/astLoader/generates/interface.ts","../src/core/loader/astLoader/generates/simple.ts","../src/core/loader/astLoader/generates/tuple.ts","../src/core/loader/astLoader/generates/type.ts","../src/core/loader/astLoader/generates/utils.ts","../src/core/loader/astLoader/normalize/index.ts","../src/core/loader/astLoader/normalize/normalizer.ts","../src/core/loader/astLoader/normalize/rules/convertTypeArray.ts","../src/core/loader/astLoader/normalize/rules/handleEmptyType.ts","../src/core/loader/astLoader/normalize/rules/inferType.ts","../src/core/loader/astLoader/normalize/rules/mergeAnyOf.ts","../src/core/loader/astLoader/normalize/rules/normalizeCombiningKeywords.ts","../src/core/loader/astLoader/normalize/rules/normalizeEnum.ts","../src/core/loader/astLoader/normalize/rules/normalizeNullType.ts","../src/core/loader/astLoader/normalize/rules/removeRedundantKeywords.ts","../src/core/loader/astLoader/normalize/rules/simplifySingleType.ts","../src/core/loader/astLoader/normalize/rules/validateSchema.ts","../src/core/loader/astLoader/parsers/array.ts","../src/core/loader/astLoader/parsers/const.ts","../src/core/loader/astLoader/parsers/enum.ts","../src/core/loader/astLoader/parsers/group.ts","../src/core/loader/astLoader/parsers/index.ts","../src/core/loader/astLoader/parsers/object.ts","../src/core/loader/astLoader/parsers/reference.ts","../src/core/loader/astLoader/parsers/simple.ts","../src/core/loader/astLoader/parsers/tuple.ts","../src/core/loader/astLoader/parsers/type.ts","../src/core/loader/astLoader/parsers/utils.ts","../src/core/loader/astLoader/parsers/forward/array.ts","../src/core/loader/astLoader/parsers/forward/const.ts","../src/core/loader/astLoader/parsers/forward/enum.ts","../src/core/loader/astLoader/parsers/forward/group.ts","../src/core/loader/astLoader/parsers/forward/index.ts","../src/core/loader/astLoader/parsers/forward/object.ts","../src/core/loader/astLoader/parsers/forward/tuple.ts","../src/core/loader/astLoader/parsers/forward/type.ts","../src/core/loader/callingCodeLoader/helper.ts","../src/core/loader/callingCodeLoader/index.ts","../src/core/loader/schemaLoader/index.ts","../src/core/loader/standardLoader/helper.ts","../src/core/loader/standardLoader/index.ts","../src/core/loader/standardLoader/standards.ts","../src/core/parser/index.ts","../src/core/parser/openApiParser/helper.ts","../src/core/parser/openApiParser/index.ts","../src/core/parser/templateParser/helper.ts","../src/core/parser/templateParser/index.ts","../src/core/workerPool/index.ts","../src/core/workerPool/poolManager.ts","../src/core/workerPool/swagger2Worker.ts","../src/core/workerPool/worker.ts","../src/functions/getAutoTemplateType.ts","../src/functions/getFrameworkTag.ts","../src/functions/prepareConfig.ts","../src/functions/readWormaRc.ts","../src/functions/wormaJson.ts","../src/helper/PluginDriver.ts","../src/helper/comment.ts","../src/helper/index.ts","../src/helper/progress.ts","../src/helper/config/ConfigHelper.ts","../src/helper/config/ConfigManager.ts","../src/helper/config/GeneratorHelper.ts","../src/helper/config/index.ts","../src/helper/config/type.ts","../src/helper/config/zType.ts","../src/helper/document/index.ts","../src/helper/logger/index.ts","../src/helper/template/index.ts","../src/plugins/createPlugin.ts","../src/plugins/index.ts","../src/plugins/presets/aiDoc.ts","../src/plugins/presets/apifox.ts","../src/plugins/presets/filterApi.ts","../src/plugins/presets/importType.ts","../src/plugins/presets/rename.ts","../src/plugins/presets/tagModifier.ts","../src/plugins/presets/utils.ts","../src/plugins/presets/payloadModifier/hepler.ts","../src/plugins/presets/payloadModifier/index.ts","../src/plugins/presets/payloadModifier/type.ts","../src/plugins/presets/platform/fastapi.ts","../src/plugins/presets/platform/index.ts","../src/plugins/presets/platform/knife4j.ts","../src/plugins/presets/platform/shared.ts","../src/plugins/presets/platform/swagger.ts","../src/plugins/presets/platform/yapi.ts","../src/template/index.ts","../src/type/api.ts","../src/type/ast.ts","../src/type/base.ts","../src/type/index.ts","../src/type/lib.ts","../src/type/openapi.ts","../src/utils/base.ts","../src/utils/format.ts","../src/utils/index.ts","../src/utils/openapi.ts","../src/utils/readPackageJson.ts","../src/utils/template.ts"],"version":"5.9.3"}
@@ -76,6 +76,29 @@ function registerCommonHelpers(hbs) {
76
76
  return text;
77
77
  return text.split('\n').map(line => line.replace(/^\* ?/, '')).join('\n');
78
78
  });
79
+ /**
80
+ * Scan the type string for every PascalCase identifier that is in componentNames
81
+ * (and not already prefixed with `${importName}.`) and prefix it.
82
+ * Works uniformly for top-level names, generics, object literals, and arrays.
83
+ *
84
+ * @param typeStr The type expression to process.
85
+ * @param componentNames The list of component schema names to prefix.
86
+ * @param importName The imported namespace name to prefix with. Defaults to "ComponentTypes".
87
+ */
88
+ hbs.registerHelper('addNamespace', (typeStr, componentNames, importName) => {
89
+ const type = typeStr;
90
+ const names = componentNames;
91
+ const importPrefix = (typeof importName === 'string' && importName.length > 0) ? importName : 'ComponentTypes';
92
+ if (!type || !Array.isArray(names) || names.length === 0) {
93
+ return new hbs.SafeString(type || 'unknown');
94
+ }
95
+ const nameSet = new Set(names);
96
+ const escapedPrefix = importPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
97
+ const result = type.replace(new RegExp(`(?<!${escapedPrefix}\\.)(\\b[A-Z]\\w*\\b)`, 'g'), (match) => {
98
+ return nameSet.has(match) ? `${importPrefix}.${match}` : match;
99
+ });
100
+ return new hbs.SafeString(result);
101
+ });
79
102
  }
80
103
  /**
81
104
  * Pass in text content and generate a custom file in the specified directory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormajs",
3
- "version": "0.0.2-beta.2",
3
+ "version": "0.1.0",
4
4
  "description": "A modern OpenAPI code generator - Generate type-safe API clients from OpenAPI specs",
5
5
  "author": "worma",
6
6
  "license": "MIT",
@@ -597,33 +597,106 @@ export interface ModifierConfig<T extends Schema> {
597
597
  export type PayloadModifierConfig = ModifierConfig<Schema>;
598
598
  export declare function payloadModifier(configs: PayloadModifierConfig[]): ApiPlugin;
599
599
  /**
600
- * Supported platform types
600
+ * FastAPI platform plugin.
601
+ *
602
+ * Pass the base URL of your FastAPI app; the plugin will try `/openapi.json`
603
+ * first, then fall back to the bare base URL.
604
+ *
605
+ * @param input - base URL string, or an array of base URLs
606
+ *
607
+ * @example
608
+ * ```ts
609
+ * plugins: [fastapi('http://fastapi-example.dokkuapp.com'), alovaGlobals()]
610
+ * ```
601
611
  */
602
- export type PlatformType = "swagger" | "knife4j" | "fastapi" | "yapi";
612
+ export declare const fastapi: (input: string | string[]) => ApiPlugin;
603
613
  /**
604
- * Platform plugin for auto-resolving OpenAPI file URLs.
614
+ * Knife4j platform plugin.
605
615
  *
606
- * Pass a platform type (e.g., `'swagger'`) and the plugin will use `config.input`
607
- * as the base URL to generate candidate OpenAPI file URLs. The framework will try
608
- * each URL in order and use the first successful response.
616
+ * Pass the base URL of your Knife4j instance; the plugin will try the OAS3
617
+ * endpoint (springdoc) first, then the Swagger2 endpoint (springfox), then the
618
+ * bare base URL.
609
619
  *
610
- * @param platformType - The platform type: 'swagger' | 'knife4j' | 'fastapi' | 'yapi'
611
- * @returns ApiPlugin
620
+ * @param input - base URL string, or an array of base URLs
612
621
  *
613
622
  * @example
614
623
  * ```ts
615
- * import { platform, alovaGlobals } from 'wormajs/plugin';
624
+ * plugins: [knife4j('https://openapi3.demo.knife4jnext.com'), alovaGlobals()]
625
+ * ```
626
+ */
627
+ export declare const knife4j: (input: string | string[]) => ApiPlugin;
628
+ /**
629
+ * Swagger platform plugin.
630
+ *
631
+ * Pass the base URL of your Swagger UI / server; the plugin will try several
632
+ * common OpenAPI document endpoints (OAS3 first, then Swagger2, then the bare
633
+ * base URL) and let the framework pick the first one that responds.
634
+ *
635
+ * @param input - base URL string, or an array of base URLs
636
+ *
637
+ * @example
638
+ * ```ts
639
+ * import { swagger, alovaGlobals } from 'wormajs/plugin';
616
640
  *
617
641
  * defineConfig({
618
642
  * generator: [{
619
- * input: 'https://petstore3.swagger.io',
620
- * plugins: [platform('swagger'), alovaGlobals()],
643
+ * plugins: [swagger('https://petstore3.swagger.io'), alovaGlobals()],
621
644
  * output: './src/api',
622
645
  * }]
623
646
  * });
624
647
  * ```
625
648
  */
626
- export declare function platform(platformType: PlatformType): ApiPlugin;
649
+ export declare const swagger: (input: string | string[]) => ApiPlugin;
650
+ export interface YapiOptions {
651
+ /** YApi 服务基础地址,例如 `https://yapi.xxx.com` */
652
+ url: string;
653
+ /** 项目 ID,必填,用于拼装导出地址 */
654
+ pid: string | number;
655
+ /** OpenAPI 类型,默认 `OpenAPIV2` */
656
+ type?: string;
657
+ /** 接口状态,默认 `all` */
658
+ status?: string;
659
+ /** 是否包含 wiki,默认 `true` */
660
+ isWiki?: boolean;
661
+ /** 登录 cookie。也可通过 fetchOptions.headers.cookie 传入 */
662
+ cookie?: string;
663
+ /** 额外的 fetch 超时(毫秒) */
664
+ timeout?: number;
665
+ }
666
+ /**
667
+ * YApi platform plugin.
668
+ *
669
+ * YApi projects are private, so the OpenAPI document must be exported through
670
+ * YApi's own export endpoint, authenticated with your login cookie. The plugin
671
+ * builds the export URL from the server base URL (`url`) plus the required
672
+ * `pid` and the optional `type` / `status` / `isWiki` query params (which
673
+ * default to `OpenAPIV2`, `all`, and `true` respectively).
674
+ *
675
+ * `url`, `pid` and `cookie` are required — the plugin throws a clear error when
676
+ * any is missing.
677
+ *
678
+ * @param options - `{ url, pid, cookie?, type?, status?, isWiki?, timeout? }`
679
+ *
680
+ * @example
681
+ * ```ts
682
+ * import { yapi, alovaGlobals } from 'wormajs/plugin';
683
+ *
684
+ * defineConfig({
685
+ * generator: [{
686
+ * plugins: [
687
+ * yapi({
688
+ * url: 'https://yapi.xxx.com',
689
+ * pid: 123,
690
+ * cookie: '_yapi_token=xxx; _yapi_uid=yyy',
691
+ * }),
692
+ * alovaGlobals(),
693
+ * ],
694
+ * output: './src/api',
695
+ * }]
696
+ * });
697
+ * ```
698
+ */
699
+ export declare function yapi(options: YapiOptions): ApiPlugin;
627
700
  /**
628
701
  * Rename style options
629
702
  */
@@ -635,7 +708,7 @@ export interface RenameConfig {
635
708
  /**
636
709
  * Target scope for renaming, defaults to 'url'
637
710
  */
638
- scope?: "url" | "params" | "pathParams" | "data" | "response" | "refName";
711
+ scope?: "url" | "params" | "pathParams" | "data" | "response" | "refName" | "name";
639
712
  /**
640
713
  * Matching rule for selective renaming:
641
714
  * - string: target contains this string
@@ -643,7 +716,7 @@ export interface RenameConfig {
643
716
  * - function: custom matching logic
644
717
  * If not specified, all targets will be processed
645
718
  */
646
- match?: string | RegExp | ((key: string) => boolean);
719
+ match?: string | RegExp | ((key: string, level?: number) => boolean);
647
720
  /**
648
721
  * Naming style to apply
649
722
  */
@@ -1,73 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.platform = platform;
4
- const constant_1 = require("../../constant");
5
- /**
6
- * Generate OpenAPI file URLs based on platform type.
7
- * For Swagger and Knife4j, generates multiple URLs covering both OAS3 and Swagger2 endpoints.
8
- */
9
- function resolvePlatformUrls(baseUrl, platformType) {
10
- // Normalize: remove trailing slash
11
- const base = baseUrl.replace(/\/+$/, '');
12
- switch (platformType) {
13
- case 'swagger':
14
- // Try OAS3 first, then Swagger2, then generic openapi.json
15
- return [
16
- `${base}/openapi.json`,
17
- `${base}/v2/swagger.json`,
18
- `${base}/api/v3/openapi.json`,
19
- baseUrl,
20
- ];
21
- case 'knife4j':
22
- // Try OAS3 first (springdoc), then Swagger2 (springfox)
23
- return [
24
- `${base}/v3/api-docs`,
25
- `${base}/v2/api-docs`,
26
- baseUrl,
27
- ];
28
- case 'fastapi':
29
- return [`${base}/openapi.json`, baseUrl];
30
- case 'yapi':
31
- // YApi requires pid and token in URL, use input as-is
32
- return [baseUrl];
33
- }
34
- }
35
- /**
36
- * Platform plugin for auto-resolving OpenAPI file URLs.
37
- *
38
- * Pass a platform type (e.g., `'swagger'`) and the plugin will use `config.input`
39
- * as the base URL to generate candidate OpenAPI file URLs. The framework will try
40
- * each URL in order and use the first successful response.
41
- *
42
- * @param platformType - The platform type: 'swagger' | 'knife4j' | 'fastapi' | 'yapi'
43
- * @returns ApiPlugin
44
- *
45
- * @example
46
- * ```ts
47
- * import { platform, alovaGlobals } from 'wormajs/plugin';
48
- *
49
- * defineConfig({
50
- * generator: [{
51
- * input: 'https://petstore3.swagger.io',
52
- * plugins: [platform('swagger'), alovaGlobals()],
53
- * output: './src/api',
54
- * }]
55
- * });
56
- * ```
57
- */
58
- function platform(platformType) {
59
- return {
60
- name: constant_1.PluginName.PLATFORM,
61
- config({ config }) {
62
- const raw = config.input;
63
- if (!raw)
64
- return config;
65
- // Normalize to an array of base URLs, deduplicate
66
- const inputs = Array.isArray(raw) ? [...new Set(raw)] : [raw];
67
- // For each input, generate platform URLs, then flatten
68
- config.input = inputs.flatMap(url => resolvePlatformUrls(url, platformType));
69
- return config;
70
- },
71
- };
72
- }
73
- exports.default = platform;