wormajs 0.3.1 → 0.4.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.
@@ -39,6 +39,9 @@ function aiDoc(config) {
39
39
  ? (node_path_1.default.isAbsolute(customTemplatePath) ? customTemplatePath : node_path_1.default.resolve(projectPath, customTemplatePath))
40
40
  : (0, template_1.getPresetTemplatePath)(constant_1.PresetTemplateName.AI_DOC);
41
41
  const serverName = capturedServerName || templateData.title || 'API';
42
+ // Skill name written into SKILL.md frontmatter. Defaults to `apis-<title>`
43
+ // which is the historical name the generated skill used before this option existed.
44
+ const skillName = config?.skillName ?? `apis-${templateData.title ?? ''}`;
42
45
  // Compute file location for each API (relative path from project root to generated file)
43
46
  // Skip fileLocation for alova-globals since APIs are called globally, not from a specific file
44
47
  const isGlobals = templateData.config?.templateName === 'alova-globals';
@@ -65,6 +68,7 @@ function aiDoc(config) {
65
68
  data: {
66
69
  ...enrichedData,
67
70
  serverName,
71
+ skillName,
68
72
  },
69
73
  });
70
74
  if (agentValue) {
@@ -55,7 +55,6 @@ const VALID_PRIMITIVES = new Set([
55
55
  'unknown',
56
56
  'any',
57
57
  'never',
58
- 'integer',
59
58
  ]);
60
59
  function validatePrimitive(val) {
61
60
  if (!VALID_PRIMITIVES.has(val)) {
@@ -86,13 +85,20 @@ function toSchemaObject(base, s) {
86
85
  const arr = s;
87
86
  cleanType(result);
88
87
  result.type = 'array';
89
- const items = arr.map(item => toSchemaObject({}, item));
88
+ // Pass the original `items` down as the base of each element so documentation
89
+ // fields (e.g. `description`) survive the round-trip.
90
+ const baseItems = base.items;
91
+ const baseItemsList = Array.isArray(baseItems) ? baseItems : (baseItems ? [baseItems] : []);
92
+ const items = arr.map((item, idx) => toSchemaObject((baseItemsList[idx] || {}), item));
90
93
  result.items = (items.length === 1 ? items[0] : items);
91
94
  return result;
92
95
  }
93
96
  // Primitive types — validate against SchemaPrimitive set during conversion
94
97
  if (typeof s === 'string') {
95
98
  validatePrimitive(s);
99
+ // Drop the structural fields inherited from the base so they don't leak into the
100
+ // new type; documentation fields such as `description` are kept.
101
+ cleanType(result);
96
102
  result.type = s;
97
103
  return result;
98
104
  }
@@ -118,15 +124,28 @@ function toSchemaObject(base, s) {
118
124
  result.allOf = spec.allOf.map((item, idx) => toSchemaObject(baseAllOf[idx] || {}, item));
119
125
  return result;
120
126
  }
121
- // Enum: set enum and optional type
127
+ // Enum: write the enum values back, converting the TS primitive type from the
128
+ // handler into its OpenAPI counterpart (`number` -> `integer`/`number`, `string`, ...).
122
129
  if (s.enum) {
123
130
  const spec = s;
131
+ // Drop the structural fields inherited from the base; documentation fields are kept.
132
+ cleanType(result);
124
133
  result.enum = spec.enum;
125
134
  if (spec.type) {
126
135
  if (typeof spec.type === 'string') {
127
136
  validatePrimitive(spec.type);
128
137
  }
129
- result.type = spec.type;
138
+ // `number` only becomes `integer` when every enum value is an integer.
139
+ result.type = enumTypeToSchemaType(spec.type, spec.enum);
140
+ }
141
+ else {
142
+ // No type from the handler: keep an OpenAPI 3.1 type array (e.g. `['string', 'null']`)
143
+ // as-is, otherwise infer the type from the enum values.
144
+ const fallback = Array.isArray(base.type) ? base.type : inferEnumType(spec.enum);
145
+ if (fallback) {
146
+ result.type = fallback;
147
+ }
148
+ // otherwise the enum stays untyped (mixed or empty values)
130
149
  }
131
150
  return result;
132
151
  }
@@ -135,9 +154,14 @@ function toSchemaObject(base, s) {
135
154
  // scalar fields like description from base)
136
155
  const ref = s;
137
156
  if (ref && typeof ref === 'object') {
157
+ // Drop the structural fields inherited from the base; documentation fields are kept.
158
+ cleanType(result);
138
159
  result.type = 'object';
139
160
  const properties = {};
140
161
  const requiredSet = new Set();
162
+ // The base of each property is taken from the ORIGINAL `base.properties` (not from the
163
+ // object being built) so documentation fields like `description` survive the round-trip.
164
+ const baseProperties = (base.properties || {});
141
165
  for (const key in ref) {
142
166
  const val = ref[key];
143
167
  if (!val) {
@@ -157,7 +181,7 @@ function toSchemaObject(base, s) {
157
181
  isOptional = false;
158
182
  effectiveVal = val;
159
183
  }
160
- const baseProp = properties[key];
184
+ const baseProp = baseProperties[key];
161
185
  properties[key] = toSchemaObject(baseProp || {}, effectiveVal);
162
186
  if (isOptional) {
163
187
  requiredSet.delete(key);
@@ -187,6 +211,37 @@ function schemaTypeToPrimitiveType(t) {
187
211
  }
188
212
  return t;
189
213
  }
214
+ // Convert a SchemaPrimitive (the TS type used by the handler) back into the OpenAPI type
215
+ // of an enum. A numeric enum is written as `integer` (the OpenAPI counterpart of the TS
216
+ // `number`) only when every value is an integer, otherwise it stays `number` so the type
217
+ // matches the values. `string`/`boolean` map 1:1, TS-only types are written through as-is.
218
+ function enumTypeToSchemaType(t, enumValues) {
219
+ if (t === 'number') {
220
+ return enumValues.every(v => typeof v === 'number' && Number.isInteger(v))
221
+ ? 'integer'
222
+ : 'number';
223
+ }
224
+ return t;
225
+ }
226
+ // Infer the OpenAPI type of an enum from its values, used when no type is declared so the
227
+ // enum does not stay untyped. Returns `undefined` for mixed or empty values so that no
228
+ // (possibly wrong) type is written.
229
+ function inferEnumType(enumValues) {
230
+ if (!enumValues.length) {
231
+ return undefined;
232
+ }
233
+ if (enumValues.every(v => typeof v === 'string')) {
234
+ return 'string';
235
+ }
236
+ if (enumValues.every(v => typeof v === 'boolean')) {
237
+ return 'boolean';
238
+ }
239
+ if (enumValues.every(v => typeof v === 'number')) {
240
+ return enumValues.every(v => Number.isInteger(v)) ? 'integer' : 'number';
241
+ }
242
+ // mixed value types -> leave the enum untyped
243
+ return undefined;
244
+ }
190
245
  // Convert existing OpenAPI SchemaObject -> Schema (best-effort, for handler input)
191
246
  function toSchemaSpec(obj) {
192
247
  if (!obj || typeof obj !== 'object') {
@@ -205,9 +260,12 @@ function toSchemaSpec(obj) {
205
260
  const arr = obj.allOf;
206
261
  return { allOf: arr.map(item => toSchemaSpec(item)) };
207
262
  }
208
- // Enum
263
+ // Enum: the OpenAPI type is normalized to its TS primitive counterpart
264
+ // (e.g. `integer` -> `number`), consistent with how plain primitives are converted.
209
265
  if (Array.isArray(obj.enum) && obj.enum.length > 0) {
210
- const type = typeof obj.type === 'string' ? obj.type : undefined;
266
+ const type = typeof obj.type === 'string'
267
+ ? schemaTypeToPrimitiveType(obj.type)
268
+ : undefined;
211
269
  return { enum: obj.enum, type };
212
270
  }
213
271
  // Array -> native array
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: apis-{{{title}}}
2
+ name: {{{skillName}}}
3
3
  description: >-
4
4
  API reference documentation for {{{serverName}}} ({{{title}}} v{{{version}}}).
5
5
  Contains specifications for all REST API endpoints, including HTTP request methods,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormajs",
3
- "version": "0.3.1",
3
+ "version": "0.4.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",
@@ -438,6 +438,12 @@ export type SkillAgent = "aider-desk" | "amp" | "antigravity" | "antigravity-cli
438
438
  export interface AiDocConfig {
439
439
  template?: string;
440
440
  outputDir?: string;
441
+ /**
442
+ * Name written into the generated skill's `SKILL.md` frontmatter. This is the
443
+ * name the skill is installed/referenced under. When omitted, the skill keeps
444
+ * its default name derived from the API title: `apis-<title>`.
445
+ */
446
+ skillName?: string;
441
447
  /**
442
448
  * Which coding agent(s) to install the generated skill into.
443
449
  * - omitted: do NOT install the skill.
@@ -575,7 +581,7 @@ export declare function importType(imports: Record<string, string[]>, options?:
575
581
  files?: string[];
576
582
  }): ApiPlugin;
577
583
  export type ModifierScope = "params" | "pathParams" | "data" | "response";
578
- export type SchemaPrimitive = "number" | "string" | "boolean" | "undefined" | "null" | "unknown" | "any" | "never" | "integer";
584
+ export type SchemaPrimitive = "number" | "string" | "boolean" | "undefined" | "null" | "unknown" | "any" | "never";
579
585
  /**
580
586
  * Array type: a native JS array whose elements are Schemas.
581
587
  * e.g. ['string'] means string[]; ['string', 'number'] means the tuple [string, number]