@workos/oagen 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/cli/index.d.mts +2 -0
  2. package/dist/cli/index.mjs +6018 -0
  3. package/dist/cli/index.mjs.map +1 -0
  4. package/dist/compat/{index.d.ts → index.d.mts} +43 -114
  5. package/dist/compat/index.d.mts.map +1 -0
  6. package/dist/compat/index.mjs +4562 -0
  7. package/dist/compat/index.mjs.map +1 -0
  8. package/dist/diff-DyQF4sel.mjs +2250 -0
  9. package/dist/diff-DyQF4sel.mjs.map +1 -0
  10. package/dist/differ-BwelBJN0.mjs +1100 -0
  11. package/dist/differ-BwelBJN0.mjs.map +1 -0
  12. package/dist/index.d.mts +295 -0
  13. package/dist/index.d.mts.map +1 -0
  14. package/dist/index.mjs +1553 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/scripts/smoke/baseline.d.mts +1 -0
  17. package/dist/scripts/smoke/baseline.mjs +1916 -0
  18. package/dist/scripts/smoke/baseline.mjs.map +1 -0
  19. package/dist/scripts/smoke/sdk-test.d.mts +1 -0
  20. package/dist/scripts/smoke/sdk-test.mjs +111 -0
  21. package/dist/scripts/smoke/sdk-test.mjs.map +1 -0
  22. package/dist/smoke.d.mts +331 -0
  23. package/dist/smoke.d.mts.map +1 -0
  24. package/dist/smoke.mjs +2084 -0
  25. package/dist/smoke.mjs.map +1 -0
  26. package/dist/tree-sitter-BPNfbEzY.mjs +359 -0
  27. package/dist/tree-sitter-BPNfbEzY.mjs.map +1 -0
  28. package/dist/types-O25qZxMl.d.mts +397 -0
  29. package/dist/types-O25qZxMl.d.mts.map +1 -0
  30. package/dist/verify/index.d.mts +82 -0
  31. package/dist/verify/index.d.mts.map +1 -0
  32. package/dist/verify/index.mjs +327 -0
  33. package/dist/verify/index.mjs.map +1 -0
  34. package/package.json +19 -16
  35. package/dist/chunk-2JXFTDTO.js +0 -1669
  36. package/dist/chunk-CIX5NOBF.js +0 -263
  37. package/dist/chunk-IJ5E5AE7.js +0 -1040
  38. package/dist/cli/index.js +0 -5322
  39. package/dist/compat/index.js +0 -4491
  40. package/dist/index.d.ts +0 -277
  41. package/dist/index.js +0 -1565
  42. package/dist/scripts/smoke/baseline.js +0 -1813
  43. package/dist/scripts/smoke/sdk-test.js +0 -93
  44. package/dist/smoke.js +0 -2025
  45. package/dist/types-B5t9O9Wm.d.ts +0 -367
  46. package/dist/verify/index.d.ts +0 -76
  47. package/dist/verify/index.js +0 -336
@@ -1,1813 +0,0 @@
1
- // scripts/smoke/baseline.ts
2
- import { writeFileSync } from "fs";
3
-
4
- // src/errors.ts
5
- var OagenError = class extends Error {
6
- hint;
7
- constructor(message, hint) {
8
- const fullMessage = hint ? `${message}
9
- Hint: ${hint}` : message;
10
- super(fullMessage);
11
- this.name = "OagenError";
12
- this.hint = hint;
13
- }
14
- };
15
- var SpecParseError = class extends OagenError {
16
- constructor(message, hint) {
17
- super(message, hint);
18
- this.name = "SpecParseError";
19
- }
20
- };
21
-
22
- // src/parser/refs.ts
23
- import { readFile } from "fs/promises";
24
- import { resolve } from "path";
25
- import { pathToFileURL } from "url";
26
- import { makeDocumentFromString, bundleDocument, createConfig, BaseResolver } from "@redocly/openapi-core";
27
- async function loadAndBundleSpec(specPath) {
28
- const absolutePath = resolve(specPath);
29
- const content = await readFile(absolutePath, "utf-8");
30
- const document = makeDocumentFromString(content, pathToFileURL(absolutePath).href);
31
- const config = await createConfig({});
32
- const resolver = new BaseResolver();
33
- const result = await bundleDocument({
34
- document,
35
- config: config.styleguide,
36
- externalRefResolver: resolver,
37
- dereference: false
38
- });
39
- if (result.problems.some((p) => p.severity === "error")) {
40
- const errors = result.problems.filter((p) => p.severity === "error").map((p) => p.message).join("\n");
41
- throw new SpecParseError(
42
- `Failed to parse spec: ${errors}`,
43
- `Check the OpenAPI spec at "${absolutePath}" for syntax errors. Run a linter such as \`npx @redocly/cli lint ${specPath}\` to identify issues.`
44
- );
45
- }
46
- return {
47
- parsed: result.bundle.parsed,
48
- specPath: absolutePath
49
- };
50
- }
51
-
52
- // src/ir/types.ts
53
- function assertNever(x) {
54
- const kind = x.kind;
55
- const err = new Error(
56
- `Unexpected TypeRef kind: ${kind}
57
- Hint: If you added a new TypeRef variant, handle it in every switch/case that calls assertNever.`
58
- );
59
- err.name = "InternalError";
60
- throw err;
61
- }
62
- function walkTypeRef(ref, visitor) {
63
- switch (ref.kind) {
64
- case "model":
65
- visitor.model?.(ref);
66
- break;
67
- case "enum":
68
- visitor.enum?.(ref);
69
- break;
70
- case "array":
71
- walkTypeRef(ref.items, visitor);
72
- break;
73
- case "nullable":
74
- walkTypeRef(ref.inner, visitor);
75
- break;
76
- case "union":
77
- for (const v of ref.variants) walkTypeRef(v, visitor);
78
- break;
79
- case "map":
80
- if (ref.keyType) walkTypeRef(ref.keyType, visitor);
81
- walkTypeRef(ref.valueType, visitor);
82
- break;
83
- case "literal":
84
- visitor.literal?.(ref);
85
- break;
86
- case "primitive":
87
- visitor.primitive?.(ref);
88
- break;
89
- default:
90
- assertNever(ref);
91
- }
92
- }
93
-
94
- // src/utils/naming.ts
95
- function splitWords(s) {
96
- if (!s) return [];
97
- return s.replace(/[^a-zA-Z0-9_\-\s.]/g, "_").replace(/([a-z])([A-Z])/g, "$1\0$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1\0$2").replace(/([a-zA-Z])(\d)/g, "$1\0$2").replace(/(\d)([a-zA-Z])/g, "$1\0$2").split(/[\0_\-\s.]+/).filter((w) => w.length > 0);
98
- }
99
- var ACRONYM_SET = /* @__PURE__ */ new Set(["SSO", "FGA", "SAML", "SCIM", "JWT", "HMAC"]);
100
- function toPascalCase(s, acronyms) {
101
- const merged = acronyms ? /* @__PURE__ */ new Set([...ACRONYM_SET, ...acronyms]) : ACRONYM_SET;
102
- return splitWords(s).map((w) => {
103
- const upper = w.toUpperCase();
104
- if (merged.has(upper)) return upper;
105
- if (upper === "OAUTH") return "OAuth";
106
- return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
107
- }).join("");
108
- }
109
- function toCamelCase(s, acronyms) {
110
- const merged = acronyms ? /* @__PURE__ */ new Set([...ACRONYM_SET, ...acronyms]) : ACRONYM_SET;
111
- const words = splitWords(s);
112
- if (words.length === 0) return "";
113
- return words.map((w, i) => {
114
- if (i === 0) return w.toLowerCase();
115
- const upper = w.toUpperCase();
116
- if (merged.has(upper)) return upper;
117
- if (upper === "OAUTH") return "OAuth";
118
- return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
119
- }).join("");
120
- }
121
- function toSnakeCase(s) {
122
- return splitWords(s).map((w) => w.toLowerCase()).join("_");
123
- }
124
- function toUpperSnakeCase(s) {
125
- return splitWords(s).map((w) => w.toUpperCase()).join("_");
126
- }
127
- var BACKEND_SUFFIXES = ["Dto", "DTO", "Controller"];
128
- function stripBackendSuffixes(name) {
129
- for (const suffix of BACKEND_SUFFIXES) {
130
- if (name.endsWith(suffix) && name.length > suffix.length) {
131
- return name.slice(0, -suffix.length);
132
- }
133
- }
134
- return name;
135
- }
136
- function stripBackendPrefixes(name) {
137
- return name.replace(/Userland/g, "").replace(/Controller/g, "");
138
- }
139
- function stripListItemMarkers(name) {
140
- return name.replace(/ListItem/g, "").replace(/ByExternalId/g, "").replace(/ByResourceId/g, "").replace(/ForResource/g, "");
141
- }
142
- var SINGULAR_SAFE_LIST = /* @__PURE__ */ new Set([
143
- "Status",
144
- "Address",
145
- "Access",
146
- "Process",
147
- "Progress",
148
- "Success",
149
- "Radius",
150
- "Canvas",
151
- "Alias",
152
- "Basis",
153
- "Bus"
154
- ]);
155
- function singularize(word) {
156
- if (SINGULAR_SAFE_LIST.has(word)) return word;
157
- if (word.endsWith("ies") && word.length > 4) {
158
- return word.slice(0, -3) + "y";
159
- }
160
- if (word.endsWith("ses") && word.length > 4) {
161
- return word.slice(0, -2);
162
- }
163
- if (word.endsWith("s") && !word.endsWith("ss") && word.length > 4) {
164
- return word.slice(0, -1);
165
- }
166
- return word;
167
- }
168
- function cleanSchemaName(name) {
169
- let result = stripBackendPrefixes(stripBackendSuffixes(name));
170
- result = stripListItemMarkers(result);
171
- const match = result.match(/^([A-Z][a-z]*)/);
172
- if (match) {
173
- const leadWord = match[1];
174
- const singular = singularize(leadWord);
175
- if (singular !== leadWord) {
176
- result = singular + result.slice(leadWord.length);
177
- }
178
- }
179
- return result;
180
- }
181
-
182
- // src/parser/inline-models.ts
183
- function qualifyInlineModelName(baseName, parentName) {
184
- if (!parentName) return baseName;
185
- const cleanParent = stripListItemMarkers(parentName);
186
- if (baseName.startsWith(cleanParent)) return baseName;
187
- const trailingMatch = baseName.match(/^(.*?)([A-Z][a-z]*)$/);
188
- if (trailingMatch) {
189
- const [, prefix, trailingWord] = trailingMatch;
190
- const singular = singularize(trailingWord);
191
- return `${cleanParent}${prefix}${singular}`;
192
- }
193
- return `${cleanParent}${baseName}`;
194
- }
195
- function extractInlineModelsFromSchemas(schemas) {
196
- if (!schemas) return [];
197
- const inlineModels = [];
198
- for (const [schemaName, schema] of Object.entries(schemas)) {
199
- const parentName = cleanSchemaName(toPascalCase(schemaName));
200
- extractInlineModelsFromProperties(schema, inlineModels, parentName);
201
- }
202
- return inlineModels;
203
- }
204
- function extractInlineModelsFromProperties(schema, results, parentName) {
205
- const properties = schema.properties ?? {};
206
- const allOfSchemas = schema.allOf ?? [];
207
- for (const sub of allOfSchemas) {
208
- if (sub.properties) {
209
- extractInlineModelsFromProperties(sub, results, parentName);
210
- }
211
- }
212
- for (const [fieldName, fieldSchema] of Object.entries(properties)) {
213
- if (!fieldSchema) continue;
214
- if (fieldSchema.properties && (fieldSchema.type === "object" || !fieldSchema.type)) {
215
- const baseName = toPascalCase(fieldName);
216
- const modelName = qualifyInlineModelName(baseName, parentName);
217
- results.push(buildInlineModel(modelName, fieldSchema));
218
- extractInlineModelsFromProperties(fieldSchema, results, modelName);
219
- }
220
- if (fieldSchema.type === "array" && fieldSchema.items) {
221
- const items = fieldSchema.items;
222
- if (items.properties && (items.type === "object" || !items.type)) {
223
- const baseName = toPascalCase(fieldName);
224
- const modelName = qualifyInlineModelName(baseName, parentName);
225
- results.push(buildInlineModel(modelName, items));
226
- extractInlineModelsFromProperties(items, results, modelName);
227
- }
228
- }
229
- if (fieldSchema.oneOf) {
230
- const objectVariant = fieldSchema.oneOf.find((v) => v.properties && (v.type === "object" || !v.type));
231
- if (objectVariant) {
232
- const baseName = toPascalCase(fieldName);
233
- const modelName = qualifyInlineModelName(baseName, parentName);
234
- const existingNames = new Set(results.map((r) => r.name));
235
- if (!existingNames.has(modelName)) {
236
- results.push(buildInlineModel(modelName, objectVariant));
237
- extractInlineModelsFromProperties(objectVariant, results, modelName);
238
- }
239
- }
240
- }
241
- }
242
- }
243
- function buildInlineModel(name, schema) {
244
- const requiredSet = new Set(schema.required ?? []);
245
- const fields = [];
246
- for (const [fieldName, fieldSchema] of Object.entries(schema.properties ?? {})) {
247
- if (!fieldSchema) continue;
248
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, name, requiredSet));
249
- }
250
- return { name, description: schema.description, fields };
251
- }
252
-
253
- // src/parser/schemas.ts
254
- function extractSchemas(schemas) {
255
- const enums = [];
256
- if (!schemas) return { models: [], enums };
257
- const modelsByCleanName = /* @__PURE__ */ new Map();
258
- for (const [name, schema] of Object.entries(schemas)) {
259
- const pascalName = cleanSchemaName(toPascalCase(name));
260
- if (schema.enum) {
261
- enums.push(extractEnum(pascalName, schema));
262
- } else {
263
- const model = extractModel(pascalName, schema, schemas);
264
- const existing = modelsByCleanName.get(pascalName);
265
- if (existing) {
266
- if (model.fields.length > existing.fields.length) {
267
- modelsByCleanName.set(pascalName, model);
268
- }
269
- } else {
270
- modelsByCleanName.set(pascalName, model);
271
- }
272
- }
273
- }
274
- const models = [...modelsByCleanName.values()];
275
- const modelNames = new Set(models.map((m) => m.name));
276
- const inlineQueue = [...models];
277
- while (inlineQueue.length > 0) {
278
- const model = inlineQueue.pop();
279
- for (const field of model.fields) {
280
- collectNestedInlineModels(field.type, field.name, model.name, schemas ?? {}, models, modelNames, inlineQueue);
281
- }
282
- }
283
- for (const model of models) {
284
- collectInlineEnums(model.fields, enums);
285
- }
286
- return { models, enums };
287
- }
288
- function collectInlineEnumFromRef(ref, enums, seen) {
289
- walkTypeRef(ref, {
290
- enum: (r) => {
291
- if (r.values && !seen.has(r.name)) {
292
- seen.add(r.name);
293
- enums.push({
294
- name: r.name,
295
- values: r.values.map((v) => ({
296
- name: toUpperSnakeCase(String(v)),
297
- value: v,
298
- description: void 0
299
- }))
300
- });
301
- }
302
- }
303
- });
304
- }
305
- function collectInlineEnums(fields, enums) {
306
- const seen = new Set(enums.map((e) => e.name));
307
- for (const field of fields) {
308
- collectInlineEnumFromRef(field.type, enums, seen);
309
- }
310
- }
311
- function collectNestedInlineModels(ref, fieldName, parentModelName, schemas, models, modelNames, queue) {
312
- walkTypeRef(ref, {
313
- model: (modelRef) => {
314
- if (modelNames.has(modelRef.name)) return;
315
- const isComponent = Object.keys(schemas).some((k) => cleanSchemaName(toPascalCase(k)) === modelRef.name);
316
- if (isComponent) return;
317
- const parentSchema = findSchemaByName(parentModelName, schemas);
318
- if (!parentSchema) return;
319
- const fieldSchema = findNestedFieldSchema(fieldName, parentSchema, schemas);
320
- if (!fieldSchema) return;
321
- const extracted = extractNestedSchema(modelRef.name, fieldSchema);
322
- for (const m of extracted) {
323
- if (!modelNames.has(m.name)) {
324
- models.push(m);
325
- modelNames.add(m.name);
326
- queue.push(m);
327
- }
328
- }
329
- }
330
- });
331
- }
332
- function findSchemaByName(pascalName, schemas) {
333
- for (const [k, v] of Object.entries(schemas)) {
334
- if (cleanSchemaName(toPascalCase(k)) === pascalName) return v;
335
- }
336
- return null;
337
- }
338
- function findNestedFieldSchema(fieldName, parentSchema, schemas) {
339
- if (parentSchema.properties?.[fieldName]) {
340
- return parentSchema.properties[fieldName];
341
- }
342
- if (parentSchema.allOf) {
343
- for (const sub of parentSchema.allOf) {
344
- let resolved = sub;
345
- if (sub.$ref) {
346
- const segments = sub.$ref.split("/");
347
- const refName = segments[segments.length - 1];
348
- if (refName && schemas[refName]) resolved = schemas[refName];
349
- }
350
- if (resolved.properties?.[fieldName]) {
351
- return resolved.properties[fieldName];
352
- }
353
- }
354
- }
355
- return null;
356
- }
357
- function extractNestedSchema(name, schema) {
358
- if (schema.oneOf) {
359
- const models = [];
360
- for (const variant of schema.oneOf) {
361
- if (variant.$ref) continue;
362
- if (variant.properties && (variant.type === "object" || !variant.type)) {
363
- const requiredSet = new Set(variant.required ?? []);
364
- const fields = [];
365
- for (const [fn, fs] of Object.entries(variant.properties)) {
366
- if (!fs) continue;
367
- fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
368
- }
369
- const variantName = models.length === 0 ? name : `${name}${models.length + 1}`;
370
- models.push({ name: variantName, description: variant.description, fields });
371
- }
372
- }
373
- return models;
374
- }
375
- if (schema.properties && (schema.type === "object" || !schema.type)) {
376
- const requiredSet = new Set(schema.required ?? []);
377
- const fields = [];
378
- for (const [fn, fs] of Object.entries(schema.properties)) {
379
- if (!fs) continue;
380
- fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
381
- }
382
- return [{ name, description: schema.description, fields }];
383
- }
384
- if (schema.type === "array" && schema.items?.properties) {
385
- const requiredSet = new Set(schema.items.required ?? []);
386
- const fields = [];
387
- for (const [fn, fs] of Object.entries(schema.items.properties)) {
388
- if (!fs) continue;
389
- fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
390
- }
391
- return [{ name, description: schema.items.description, fields }];
392
- }
393
- return [];
394
- }
395
- function buildFieldFromSchema(fieldName, fieldSchema, contextName, requiredSet) {
396
- return {
397
- name: fieldName,
398
- type: schemaToTypeRef(fieldSchema, fieldName, contextName),
399
- required: requiredSet.has(fieldName),
400
- description: fieldSchema.description,
401
- readOnly: fieldSchema.readOnly || void 0,
402
- writeOnly: fieldSchema.writeOnly || void 0,
403
- deprecated: fieldSchema.deprecated || void 0,
404
- default: fieldSchema.default
405
- };
406
- }
407
- function extractEnum(name, schema) {
408
- const values = (schema.enum ?? []).map((v) => ({
409
- name: toUpperSnakeCase(String(v)),
410
- value: typeof v === "number" ? v : String(v),
411
- description: void 0
412
- }));
413
- return { name, values };
414
- }
415
- function extractModel(name, schema, schemas) {
416
- if (schema.allOf) {
417
- return extractAllOfModel(name, schema, schemas);
418
- }
419
- const requiredSet = new Set(schema.required ?? []);
420
- const fields = [];
421
- for (const [fieldName, fieldSchema] of Object.entries(schema.properties ?? {})) {
422
- if (!fieldSchema) continue;
423
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, name, requiredSet));
424
- }
425
- if (schema.additionalProperties && typeof schema.additionalProperties === "object" && schema.properties) {
426
- const apKeys = Object.keys(schema.additionalProperties);
427
- if (apKeys.length > 0) {
428
- const valueType = schemaToTypeRef(schema.additionalProperties, "additionalProperties", name);
429
- fields.push({
430
- name: "additionalProperties",
431
- type: { kind: "map", valueType },
432
- required: false,
433
- description: "Additional properties not captured by named fields"
434
- });
435
- }
436
- }
437
- return { name, description: schema.description, fields };
438
- }
439
- function extractAllOfModel(name, schema, schemas) {
440
- const fields = [];
441
- const requiredSet = /* @__PURE__ */ new Set();
442
- for (const subSchema of schema.allOf ?? []) {
443
- let resolved = subSchema;
444
- if (subSchema.$ref && schemas) {
445
- const segments = subSchema.$ref.split("/");
446
- const refName = segments[segments.length - 1];
447
- if (refName && schemas[refName]) {
448
- resolved = schemas[refName];
449
- }
450
- }
451
- if (resolved.allOf) {
452
- const nested = extractAllOfModel(name, resolved, schemas);
453
- for (const f of nested.fields) {
454
- fields.push({ ...f, required: false });
455
- if (f.required) requiredSet.add(f.name);
456
- }
457
- } else {
458
- if (resolved.required) {
459
- for (const r of resolved.required) requiredSet.add(r);
460
- }
461
- if (resolved.properties) {
462
- const emptyRequired = /* @__PURE__ */ new Set();
463
- for (const [fieldName, fieldSchema] of Object.entries(resolved.properties)) {
464
- if (!fieldSchema) continue;
465
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, name, emptyRequired));
466
- }
467
- }
468
- }
469
- }
470
- if (schema.required) {
471
- for (const r of schema.required) requiredSet.add(r);
472
- }
473
- for (const f of fields) {
474
- f.required = requiredSet.has(f.name);
475
- }
476
- return { name, description: schema.description, fields };
477
- }
478
- function schemaToTypeRef(schema, contextName, parentModelName) {
479
- if (schema.$ref) {
480
- const segments = schema.$ref.split("/");
481
- const rawName = segments[segments.length - 1];
482
- return { kind: "model", name: cleanSchemaName(toPascalCase(rawName)) };
483
- }
484
- if (Array.isArray(schema.type)) {
485
- const nonNullTypes = schema.type.filter((t) => t !== "null");
486
- if (nonNullTypes.length === 0) {
487
- return { kind: "nullable", inner: { kind: "primitive", type: "unknown" } };
488
- }
489
- if (schema.type.includes("null") && nonNullTypes.length === 1) {
490
- return {
491
- kind: "nullable",
492
- inner: schemaToTypeRef({ ...schema, type: nonNullTypes[0], nullable: false }, contextName)
493
- };
494
- }
495
- if (nonNullTypes.length > 1) {
496
- const variants = nonNullTypes.map((t) => schemaToTypeRef({ ...schema, type: t }, contextName));
497
- const ref = { kind: "union", variants };
498
- if (schema.type.includes("null")) {
499
- return { kind: "nullable", inner: ref };
500
- }
501
- return ref;
502
- }
503
- }
504
- if (schema.nullable && schema.type) {
505
- return {
506
- kind: "nullable",
507
- inner: schemaToTypeRef({ ...schema, nullable: false }, contextName)
508
- };
509
- }
510
- if (schema.allOf) {
511
- const refItem = schema.allOf.find((s) => s.$ref);
512
- if (refItem) {
513
- const hasAugmentation = schema.allOf.some((s) => !s.$ref && (s.properties || s.type === "object"));
514
- if (hasAugmentation) {
515
- const baseName = toPascalCase(contextName ?? "UnknownModel");
516
- return { kind: "model", name: qualifyInlineModelName(baseName, parentModelName) };
517
- }
518
- return schemaToTypeRef(refItem, contextName, parentModelName);
519
- }
520
- if (schema.allOf.length === 1) {
521
- return schemaToTypeRef(schema.allOf[0], contextName, parentModelName);
522
- }
523
- const hasProperties = schema.allOf.some((s) => s.properties);
524
- if (hasProperties) {
525
- const baseName = toPascalCase(contextName ?? "UnknownModel");
526
- return {
527
- kind: "model",
528
- name: qualifyInlineModelName(baseName, parentModelName)
529
- };
530
- }
531
- }
532
- if (schema.oneOf || schema.anyOf) {
533
- const compositionKind = schema.oneOf ? "oneOf" : "anyOf";
534
- const rawVariants = schema.oneOf ?? schema.anyOf ?? [];
535
- const nullVariant = rawVariants.find(
536
- (v) => v.type === "null" || Array.isArray(v.type) && v.type.length === 1 && v.type[0] === "null"
537
- );
538
- const nonNullVariants = rawVariants.filter((v) => v !== nullVariant);
539
- if (nullVariant && nonNullVariants.length === 1) {
540
- return {
541
- kind: "nullable",
542
- inner: schemaToTypeRef(nonNullVariants[0], contextName, parentModelName)
543
- };
544
- }
545
- const variants = rawVariants.filter((v) => v.type !== "null").map((v) => schemaToTypeRef(v, contextName, parentModelName));
546
- const hasNull = !!nullVariant;
547
- const union = {
548
- kind: "union",
549
- variants,
550
- compositionKind,
551
- ...schema.discriminator ? {
552
- discriminator: {
553
- property: schema.discriminator.propertyName,
554
- mapping: Object.fromEntries(
555
- Object.entries(schema.discriminator.mapping ?? {}).map(([k, v]) => [
556
- k,
557
- v.replace(/^#\/components\/schemas\//, "")
558
- ])
559
- )
560
- }
561
- } : {}
562
- };
563
- return hasNull ? { kind: "nullable", inner: union } : union;
564
- }
565
- if (schema.const !== void 0) {
566
- if (typeof schema.const === "string" || typeof schema.const === "number" || typeof schema.const === "boolean") {
567
- return { kind: "literal", value: schema.const };
568
- }
569
- if (schema.const === null) {
570
- return { kind: "nullable", inner: { kind: "primitive", type: "unknown" } };
571
- }
572
- if (typeof schema.const === "object") {
573
- if (Array.isArray(schema.const)) {
574
- return { kind: "array", items: { kind: "primitive", type: "unknown" } };
575
- }
576
- return { kind: "map", valueType: { kind: "primitive", type: "unknown" } };
577
- }
578
- }
579
- if (schema.enum && schema.enum.length === 1) {
580
- const v = schema.enum[0];
581
- return { kind: "literal", value: typeof v === "number" ? v : String(v) };
582
- }
583
- if (schema.enum) {
584
- const baseName = toPascalCase(contextName ?? "UnknownEnum");
585
- const cleanParent = parentModelName ? stripListItemMarkers(parentModelName) : void 0;
586
- const qualifiedName = cleanParent && !baseName.startsWith(cleanParent) ? `${cleanParent}${baseName}` : baseName;
587
- return {
588
- kind: "enum",
589
- name: qualifiedName,
590
- values: schema.enum.map((v) => typeof v === "number" ? v : String(v))
591
- };
592
- }
593
- if (schema.type === "array" && schema.items) {
594
- return {
595
- kind: "array",
596
- items: schemaToTypeRef(schema.items, contextName, parentModelName)
597
- };
598
- }
599
- if (!schema.type && schema.items) {
600
- return {
601
- kind: "array",
602
- items: schemaToTypeRef(schema.items, contextName, parentModelName)
603
- };
604
- }
605
- if (schema.type === "object" && schema.properties) {
606
- const baseName = toPascalCase(contextName ?? "UnknownModel");
607
- return {
608
- kind: "model",
609
- name: qualifyInlineModelName(baseName, parentModelName)
610
- };
611
- }
612
- if (schema.type === "object" && !schema.properties) {
613
- let valueType = { kind: "primitive", type: "unknown" };
614
- if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
615
- const apKeys = Object.keys(schema.additionalProperties);
616
- if (apKeys.length > 0) {
617
- valueType = schemaToTypeRef(schema.additionalProperties, contextName);
618
- }
619
- } else if (schema.patternProperties) {
620
- const patterns = Object.values(schema.patternProperties);
621
- if (patterns.length > 0) {
622
- valueType = schemaToTypeRef(patterns[0], contextName);
623
- }
624
- }
625
- return {
626
- kind: "map",
627
- valueType
628
- };
629
- }
630
- const primitiveMap = {
631
- string: "string",
632
- integer: "integer",
633
- number: "number",
634
- boolean: "boolean"
635
- };
636
- const typeStr = Array.isArray(schema.type) ? schema.type[0] : schema.type;
637
- if (typeStr && primitiveMap[typeStr]) {
638
- return {
639
- kind: "primitive",
640
- type: primitiveMap[typeStr],
641
- ...schema.format ? { format: schema.format } : {}
642
- };
643
- }
644
- if (!schema.type && schema.properties) {
645
- const baseName = toPascalCase(contextName ?? "UnknownModel");
646
- return {
647
- kind: "model",
648
- name: qualifyInlineModelName(baseName, parentModelName)
649
- };
650
- }
651
- if (!schema.type && !schema.$ref && !schema.oneOf && !schema.anyOf && !schema.allOf && !schema.enum && !schema.properties && !schema.items) {
652
- return { kind: "primitive", type: "unknown" };
653
- }
654
- if (contextName) {
655
- console.warn(`[oagen] Warning: Unknown schema shape treated as unknown (context: ${contextName})`);
656
- }
657
- return { kind: "primitive", type: "unknown" };
658
- }
659
-
660
- // src/parser/pagination.ts
661
- var CURSOR_PARAMS = ["cursor", "after", "before", "starting_after", "ending_before", "page_token", "next_token"];
662
- var OFFSET_PARAMS = ["offset", "page", "page_number", "skip"];
663
- var LIMIT_PARAMS = ["limit", "page_size", "per_page", "size", "count"];
664
- function detectPagination(response, queryParams, dataPath) {
665
- const cursorParam = queryParams.find((p) => CURSOR_PARAMS.includes(p.name));
666
- if (cursorParam) {
667
- const itemType = response.kind === "array" ? response.items : response;
668
- return {
669
- strategy: "cursor",
670
- param: cursorParam.name,
671
- dataPath,
672
- itemType
673
- };
674
- }
675
- const offsetParam = queryParams.find((p) => OFFSET_PARAMS.includes(p.name));
676
- const limitParam = queryParams.find((p) => LIMIT_PARAMS.includes(p.name));
677
- if (offsetParam && limitParam) {
678
- const itemType = response.kind === "array" ? response.items : response;
679
- return {
680
- strategy: "offset",
681
- param: offsetParam.name,
682
- limitParam: limitParam.name,
683
- dataPath,
684
- itemType
685
- };
686
- }
687
- return null;
688
- }
689
-
690
- // src/parser/responses.ts
691
- function classifyAndExtractResponse(schema, contextName) {
692
- if (schema.$ref) {
693
- return { response: schemaToTypeRef(schema, contextName), inlineModels: [], isPaginated: false };
694
- }
695
- const envelope = detectListEnvelope(schema);
696
- if (envelope.isEnvelope) {
697
- return extractListResponse(schema, contextName, envelope.dataPath);
698
- }
699
- if (isSingleResourceWrapper(schema)) {
700
- return extractWrappedResource(schema, contextName);
701
- }
702
- return extractDirectResource(schema, contextName);
703
- }
704
- var KNOWN_DATA_PATHS = /* @__PURE__ */ new Set(["data", "items", "results", "records", "entries", "values", "nodes", "edges"]);
705
- var PAGINATION_METADATA_PATTERNS = [
706
- "metadata",
707
- "pagination",
708
- "cursor",
709
- "has_more",
710
- "page_info",
711
- "total",
712
- "next_page",
713
- "previous_page",
714
- "offset"
715
- ];
716
- function detectListEnvelope(schema) {
717
- const propSources = [];
718
- if (schema.allOf) {
719
- for (const sub of schema.allOf) {
720
- if (sub.properties) propSources.push(sub.properties);
721
- }
722
- }
723
- if (schema.properties) {
724
- propSources.push(schema.properties);
725
- }
726
- if (propSources.length === 0) return { isEnvelope: false, dataPath: null };
727
- const mergedProps = {};
728
- for (const source of propSources) {
729
- Object.assign(mergedProps, source);
730
- }
731
- const arrayProps = [];
732
- const nonArrayKeys = [];
733
- for (const [key, propSchema] of Object.entries(mergedProps)) {
734
- if (!propSchema) continue;
735
- if (propSchema.type === "array") {
736
- arrayProps.push(key);
737
- } else {
738
- nonArrayKeys.push(key);
739
- }
740
- }
741
- if (arrayProps.length === 1 && nonArrayKeys.length >= 1) {
742
- const arrayPropName = arrayProps[0];
743
- if (KNOWN_DATA_PATHS.has(arrayPropName)) {
744
- return { isEnvelope: true, dataPath: arrayPropName };
745
- }
746
- const hasPaginationCompanion = nonArrayKeys.some(
747
- (key) => PAGINATION_METADATA_PATTERNS.some((pattern) => key.toLowerCase().includes(pattern))
748
- );
749
- if (hasPaginationCompanion) {
750
- return { isEnvelope: true, dataPath: arrayPropName };
751
- }
752
- }
753
- return { isEnvelope: false, dataPath: null };
754
- }
755
- function extractListResponse(schema, contextName, dataPath) {
756
- let itemTypeRef = { kind: "primitive", type: "string" };
757
- const inlineModels = [];
758
- const propSources = [];
759
- if (schema.allOf) {
760
- for (const sub of schema.allOf) {
761
- if (sub.properties) propSources.push(sub.properties);
762
- }
763
- }
764
- if (schema.properties) {
765
- propSources.push(schema.properties);
766
- }
767
- for (const props of propSources) {
768
- const dataProp = props[dataPath];
769
- if (!dataProp) continue;
770
- if (dataProp.type === "array" && dataProp.items) {
771
- const items = dataProp.items;
772
- if (items.$ref) {
773
- itemTypeRef = schemaToTypeRef(items, contextName);
774
- } else if (items.type === "object" && items.properties) {
775
- const itemName = contextName.replace(/Response$/, "") + "Item";
776
- itemTypeRef = { kind: "model", name: itemName };
777
- inlineModels.push(...extractInlineModel(itemName, items));
778
- }
779
- }
780
- }
781
- return {
782
- response: { kind: "array", items: itemTypeRef },
783
- inlineModels,
784
- isPaginated: true,
785
- dataPath,
786
- itemType: itemTypeRef
787
- };
788
- }
789
- function hasDiscriminantConstField(schema) {
790
- if (!schema.properties) return null;
791
- const preferred = ["object", "type"];
792
- for (const name of preferred) {
793
- const field = schema.properties[name];
794
- if (!field) continue;
795
- if (field.const !== void 0 && typeof field.const === "string") {
796
- return { property: name, value: field.const };
797
- }
798
- if (field.enum && field.enum.length === 1 && typeof field.enum[0] === "string") {
799
- return { property: name, value: field.enum[0] };
800
- }
801
- }
802
- for (const [name, field] of Object.entries(schema.properties)) {
803
- if (!field) continue;
804
- if (field.const !== void 0 && typeof field.const === "string") {
805
- return { property: name, value: field.const };
806
- }
807
- if (field.enum && field.enum.length === 1 && typeof field.enum[0] === "string") {
808
- return { property: name, value: field.enum[0] };
809
- }
810
- }
811
- return null;
812
- }
813
- function isSingleResourceWrapper(schema) {
814
- if (schema.type !== "object") return false;
815
- if (!schema.properties) return false;
816
- let wrapperKey;
817
- if (schema.required && schema.required.length === 1) {
818
- wrapperKey = schema.required[0];
819
- } else {
820
- const propKeys = Object.keys(schema.properties);
821
- if (propKeys.length === 1) {
822
- wrapperKey = propKeys[0];
823
- }
824
- }
825
- if (!wrapperKey) return false;
826
- const propSchema = schema.properties[wrapperKey];
827
- if (!propSchema) return false;
828
- if (propSchema.type === "object" && hasDiscriminantConstField(propSchema) !== null) return true;
829
- if (propSchema.oneOf) {
830
- return propSchema.oneOf.some((v) => v.type === "object" && hasDiscriminantConstField(v) !== null);
831
- }
832
- return false;
833
- }
834
- function extractWrappedResource(schema, contextName) {
835
- const props = schema.properties;
836
- const wrapperKey = schema.required && schema.required.length === 1 ? schema.required[0] : Object.keys(props)[0];
837
- const propSchema = props[wrapperKey];
838
- const inlineModels = [];
839
- const resourceName = contextName.replace(/Response$/, "");
840
- if (propSchema.oneOf) {
841
- const objectVariant = propSchema.oneOf.find((v) => v.type === "object" && hasDiscriminantConstField(v) !== null);
842
- if (objectVariant) {
843
- const modelName2 = deriveModelName(objectVariant, resourceName);
844
- inlineModels.push(...extractInlineModel(modelName2, objectVariant));
845
- const hasNullVariant = propSchema.oneOf.some(
846
- (v) => v.type === "null" || Array.isArray(v.type) && v.type.includes("null")
847
- );
848
- const modelRef = { kind: "model", name: modelName2 };
849
- return {
850
- response: hasNullVariant ? { kind: "nullable", inner: modelRef } : modelRef,
851
- inlineModels,
852
- isPaginated: false
853
- };
854
- }
855
- }
856
- const modelName = deriveModelName(propSchema, resourceName);
857
- inlineModels.push(...extractInlineModel(modelName, propSchema));
858
- return {
859
- response: { kind: "model", name: modelName },
860
- inlineModels,
861
- isPaginated: false
862
- };
863
- }
864
- function extractDirectResource(schema, contextName) {
865
- const inlineModels = [];
866
- if (schema.properties && (schema.type === "object" || !schema.type)) {
867
- const modelName = deriveModelName(schema, contextName);
868
- inlineModels.push(...extractInlineModel(modelName, schema));
869
- return {
870
- response: { kind: "model", name: modelName },
871
- inlineModels,
872
- isPaginated: false
873
- };
874
- }
875
- if (schema.allOf) {
876
- const mergedProperties = {};
877
- const mergedRequired = [];
878
- for (const sub of schema.allOf) {
879
- if (sub.properties) {
880
- Object.assign(mergedProperties, sub.properties);
881
- }
882
- if (sub.required) {
883
- mergedRequired.push(...sub.required);
884
- }
885
- }
886
- if (Object.keys(mergedProperties).length > 0) {
887
- const merged = { type: "object", properties: mergedProperties, required: mergedRequired };
888
- const modelName = deriveModelName(merged, contextName);
889
- inlineModels.push(...extractInlineModel(modelName, merged));
890
- return {
891
- response: { kind: "model", name: modelName },
892
- inlineModels,
893
- isPaginated: false
894
- };
895
- }
896
- }
897
- if (schema.oneOf) {
898
- for (const variant of schema.oneOf) {
899
- if (variant.properties && (variant.type === "object" || !variant.type)) {
900
- const modelName = deriveModelName(variant, contextName);
901
- const existingNames = new Set(inlineModels.map((m) => m.name));
902
- if (!existingNames.has(modelName)) {
903
- inlineModels.push(...extractInlineModel(modelName, variant));
904
- }
905
- }
906
- }
907
- }
908
- if (schema.type === "array" && schema.items) {
909
- const items = schema.items;
910
- if (items.$ref) {
911
- return {
912
- response: { kind: "array", items: schemaToTypeRef(items, contextName) },
913
- inlineModels,
914
- isPaginated: false
915
- };
916
- }
917
- if (items.properties && (items.type === "object" || !items.type)) {
918
- const itemName = contextName.replace(/Response$/, "") + "Item";
919
- inlineModels.push(...extractInlineModel(itemName, items));
920
- return {
921
- response: { kind: "array", items: { kind: "model", name: itemName } },
922
- inlineModels,
923
- isPaginated: false
924
- };
925
- }
926
- }
927
- return {
928
- response: schemaToTypeRef(schema, contextName),
929
- inlineModels,
930
- isPaginated: false
931
- };
932
- }
933
- function deriveModelName(schema, fallback) {
934
- if (!schema.properties) return toPascalCase(fallback);
935
- const preferred = ["object", "type"];
936
- for (const name of preferred) {
937
- const field = schema.properties[name];
938
- if (field && field.const && typeof field.const === "string") {
939
- return toPascalCase(field.const);
940
- }
941
- }
942
- for (const [, field] of Object.entries(schema.properties)) {
943
- if (field && field.const && typeof field.const === "string") {
944
- return toPascalCase(field.const);
945
- }
946
- }
947
- return toPascalCase(fallback);
948
- }
949
- function qualifyNestedName(parentName, fieldName) {
950
- const pascalField = toPascalCase(fieldName);
951
- if (pascalField.startsWith(parentName)) return pascalField;
952
- const cleanParent = stripListItemMarkers(parentName);
953
- const qualified = `${cleanParent}${pascalField}`;
954
- const match = qualified.match(/^(.+?)([A-Z][a-z]+s?)$/);
955
- if (match && match[2]) {
956
- const trailing = singularize(match[2]);
957
- return match[1] + trailing;
958
- }
959
- return qualified;
960
- }
961
- function extractInlineModel(name, schema) {
962
- const requiredSet = new Set(schema.required ?? []);
963
- const fields = [];
964
- const properties = schema.properties ?? {};
965
- const nestedModels = [];
966
- for (const [fieldName, fieldSchema] of Object.entries(properties)) {
967
- if (!fieldSchema) continue;
968
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, name, requiredSet));
969
- if (fieldSchema.type === "object" && fieldSchema.properties) {
970
- const nestedName = qualifyNestedName(name, fieldName);
971
- nestedModels.push(...extractInlineModel(nestedName, fieldSchema));
972
- }
973
- if (fieldSchema.type === "array" && fieldSchema.items) {
974
- const items = fieldSchema.items;
975
- if (items.type === "object" && items.properties) {
976
- const nestedName = qualifyNestedName(name, fieldName);
977
- nestedModels.push(...extractInlineModel(nestedName, items));
978
- }
979
- }
980
- if (fieldSchema.allOf) {
981
- const mergedProperties = {};
982
- const mergedRequired = [];
983
- for (const sub of fieldSchema.allOf) {
984
- if (sub.properties) Object.assign(mergedProperties, sub.properties);
985
- if (sub.required) mergedRequired.push(...sub.required);
986
- }
987
- if (Object.keys(mergedProperties).length > 0) {
988
- const nestedName = qualifyNestedName(name, fieldName);
989
- const merged = { type: "object", properties: mergedProperties, required: mergedRequired };
990
- const existingNames = new Set(nestedModels.map((m) => m.name));
991
- if (!existingNames.has(nestedName)) {
992
- nestedModels.push(...extractInlineModel(nestedName, merged));
993
- }
994
- }
995
- }
996
- if (fieldSchema.oneOf) {
997
- const objectVariant = fieldSchema.oneOf.find((v) => v.properties && (v.type === "object" || !v.type));
998
- if (objectVariant) {
999
- const nestedName = qualifyNestedName(name, fieldName);
1000
- const existingNames = new Set(nestedModels.map((m) => m.name));
1001
- if (!existingNames.has(nestedName)) {
1002
- nestedModels.push(...extractInlineModel(nestedName, objectVariant));
1003
- }
1004
- }
1005
- }
1006
- }
1007
- return [{ name, description: schema.description, fields }, ...nestedModels];
1008
- }
1009
-
1010
- // src/parser/operations.ts
1011
- var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
1012
- function extractOperations(paths, operationIdTransform) {
1013
- if (!paths) return { services: [], inlineModels: [] };
1014
- const serviceMap = /* @__PURE__ */ new Map();
1015
- const inlineModels = [];
1016
- for (const [path, pathItem] of Object.entries(paths)) {
1017
- const pathLevelParams = pathItem.parameters ?? [];
1018
- for (const method of HTTP_METHODS) {
1019
- const op = pathItem[method];
1020
- if (!op) continue;
1021
- const serviceName = inferServiceName(path, op.tags?.[0]);
1022
- const { operation, inlineModels: opModels } = buildOperation(
1023
- method,
1024
- path,
1025
- op,
1026
- pathLevelParams,
1027
- operationIdTransform
1028
- );
1029
- inlineModels.push(...opModels);
1030
- const ops = serviceMap.get(serviceName) ?? [];
1031
- ops.push(operation);
1032
- serviceMap.set(serviceName, ops);
1033
- }
1034
- }
1035
- for (const [, operations] of serviceMap) {
1036
- disambiguateOperationNames(operations);
1037
- }
1038
- const services = Array.from(serviceMap.entries()).map(([name, operations]) => ({
1039
- name,
1040
- operations
1041
- }));
1042
- return { services, inlineModels };
1043
- }
1044
- function inferServiceName(path, tag) {
1045
- if (tag) {
1046
- return toPascalCase(tag);
1047
- }
1048
- const segments = path.split("/").filter(Boolean);
1049
- const first = segments[0] ?? "Default";
1050
- if (first.startsWith("{")) return "Default";
1051
- return toPascalCase(first);
1052
- }
1053
- function inferOperationName(method, path, operationId, operationIdTransform) {
1054
- if (operationId) {
1055
- if (operationIdTransform) {
1056
- return operationIdTransform(operationId);
1057
- }
1058
- return toCamelCase(operationId);
1059
- }
1060
- const verb = inferVerb(method, path);
1061
- const resource = inferResourceFromPath(path);
1062
- return resource ? toCamelCase(`${verb}_${resource}`) : verb;
1063
- }
1064
- function inferVerb(method, path) {
1065
- const segments = path.split("/").filter(Boolean);
1066
- const hasTrailingParam = segments.length > 0 && segments[segments.length - 1].startsWith("{");
1067
- switch (method) {
1068
- case "get":
1069
- return hasTrailingParam ? "get" : "list";
1070
- case "post":
1071
- return "create";
1072
- case "put":
1073
- case "patch":
1074
- return "update";
1075
- case "delete":
1076
- return "delete";
1077
- case "head":
1078
- return "check";
1079
- case "options":
1080
- return "options";
1081
- case "trace":
1082
- return "trace";
1083
- default:
1084
- return method;
1085
- }
1086
- }
1087
- function inferResourceFromPath(path) {
1088
- const segments = path.split("/").filter(Boolean);
1089
- if (segments.length <= 1) return null;
1090
- let resourceSegment = null;
1091
- for (let i = segments.length - 1; i >= 0; i--) {
1092
- if (!segments[i].startsWith("{")) {
1093
- resourceSegment = segments[i];
1094
- break;
1095
- }
1096
- }
1097
- if (!resourceSegment) return null;
1098
- if (resourceSegment === segments[0]) return null;
1099
- return toPascalCase(resourceSegment);
1100
- }
1101
- function disambiguateOperationNames(operations) {
1102
- const byName = /* @__PURE__ */ new Map();
1103
- for (const op of operations) {
1104
- const group = byName.get(op.name) ?? [];
1105
- group.push(op);
1106
- byName.set(op.name, group);
1107
- }
1108
- for (const [name, group] of byName) {
1109
- if (group.length <= 1) continue;
1110
- const uniquePaths = new Set(group.map((op) => op.path));
1111
- if (uniquePaths.size <= 1) continue;
1112
- for (const op of group) {
1113
- const resource = inferResourceFromPath(op.path);
1114
- if (resource) {
1115
- const newName = toCamelCase(`${name}_${resource}`);
1116
- op.name = newName;
1117
- }
1118
- }
1119
- const renamed = /* @__PURE__ */ new Map();
1120
- for (const op of group) {
1121
- const rGroup = renamed.get(op.name) ?? [];
1122
- rGroup.push(op);
1123
- renamed.set(op.name, rGroup);
1124
- }
1125
- for (const [, rGroup] of renamed) {
1126
- if (rGroup.length <= 1) continue;
1127
- for (const op of rGroup) {
1128
- const deeper = inferDeeperContext(op.path);
1129
- if (deeper) {
1130
- op.name = toCamelCase(`${op.name}_${deeper}`);
1131
- }
1132
- }
1133
- }
1134
- }
1135
- }
1136
- function inferDeeperContext(path) {
1137
- const segments = path.split("/").filter(Boolean);
1138
- const nonParams = segments.filter((s) => !s.startsWith("{"));
1139
- if (nonParams.length >= 3) {
1140
- return toPascalCase(nonParams.slice(1, -1).join("_"));
1141
- }
1142
- return null;
1143
- }
1144
- function buildOperation(method, path, op, pathLevelParams, operationIdTransform) {
1145
- const allParams = [...pathLevelParams, ...op.parameters ?? []];
1146
- const hasIdempotencyHeader = allParams.some((p) => p.in === "header" && p.name.toLowerCase() === "idempotency-key");
1147
- const pathParams = extractParams(allParams, "path");
1148
- const queryParams = extractParams(allParams, "query");
1149
- const headerParams = extractParams(allParams, "header").filter((p) => p.name.toLowerCase() !== "idempotency-key");
1150
- const cookieParams = extractParams(allParams, "cookie");
1151
- const reqBodyModels = [];
1152
- const { body: requestBody, encoding: requestBodyEncoding } = extractRequestBody(op.requestBody, op, reqBodyModels);
1153
- const {
1154
- response,
1155
- successResponses,
1156
- errors,
1157
- inlineModels,
1158
- isPaginated,
1159
- dataPath: responseDataPath,
1160
- itemType: responseItemType
1161
- } = extractResponses(op.responses, op, path, method);
1162
- inlineModels.push(...reqBodyModels);
1163
- const paginationFromParams = detectPagination(response, queryParams, responseDataPath);
1164
- let pagination;
1165
- if (isPaginated) {
1166
- const itemType = responseItemType ?? (response.kind === "array" ? response.items : response);
1167
- pagination = {
1168
- strategy: paginationFromParams?.strategy ?? "cursor",
1169
- param: paginationFromParams?.param ?? "after",
1170
- limitParam: paginationFromParams?.limitParam,
1171
- dataPath: responseDataPath ?? paginationFromParams?.dataPath,
1172
- itemType
1173
- };
1174
- } else if (paginationFromParams) {
1175
- pagination = paginationFromParams;
1176
- }
1177
- return {
1178
- operation: {
1179
- name: inferOperationName(method, path, op.operationId, operationIdTransform),
1180
- description: buildDescription(op.summary, op.description),
1181
- httpMethod: method,
1182
- path,
1183
- pathParams,
1184
- queryParams,
1185
- headerParams,
1186
- cookieParams: cookieParams.length > 0 ? cookieParams : void 0,
1187
- requestBody,
1188
- requestBodyEncoding,
1189
- response,
1190
- successResponses,
1191
- errors,
1192
- pagination,
1193
- injectIdempotencyKey: hasIdempotencyHeader,
1194
- deprecated: op.deprecated || void 0,
1195
- async: op["x-oagen-async"]
1196
- },
1197
- inlineModels
1198
- };
1199
- }
1200
- function buildDescription(summary, description) {
1201
- if (summary && description && summary !== description) {
1202
- return `${summary}
1203
-
1204
- ${description}`;
1205
- }
1206
- return description ?? summary;
1207
- }
1208
- function extractParams(params, location) {
1209
- return params.filter((p) => p.in === location).map((p) => ({
1210
- name: p.name,
1211
- type: p.schema ? schemaToTypeRef(p.schema, p.name) : { kind: "primitive", type: "string" },
1212
- required: p.required ?? false,
1213
- description: p.description,
1214
- deprecated: p.deprecated || p.schema?.deprecated || void 0,
1215
- default: p.schema?.default,
1216
- example: p.example ?? p.schema?.example
1217
- }));
1218
- }
1219
- function extractRequestBody(body, op, inlineModels) {
1220
- if (!body?.content) return {};
1221
- let encoding = "json";
1222
- let schema;
1223
- if (body.content["application/json"]?.schema) {
1224
- encoding = "json";
1225
- schema = body.content["application/json"].schema;
1226
- } else if (body.content["multipart/form-data"]?.schema) {
1227
- encoding = "form-data";
1228
- schema = body.content["multipart/form-data"].schema;
1229
- } else if (body.content["application/x-www-form-urlencoded"]?.schema) {
1230
- encoding = "form-urlencoded";
1231
- schema = body.content["application/x-www-form-urlencoded"].schema;
1232
- } else if (body.content["application/octet-stream"]) {
1233
- encoding = "binary";
1234
- schema = body.content["application/octet-stream"].schema;
1235
- } else if (body.content["text/plain"]) {
1236
- encoding = "text";
1237
- schema = body.content["text/plain"].schema;
1238
- }
1239
- if (!schema) {
1240
- if (encoding === "binary") {
1241
- return { body: { kind: "primitive", type: "string", format: "binary" }, encoding };
1242
- }
1243
- if (encoding === "text") {
1244
- return { body: { kind: "primitive", type: "string" }, encoding };
1245
- }
1246
- return {};
1247
- }
1248
- const rawName = op?.operationId ? toPascalCase(op.operationId) + "Request" : "RequestBody";
1249
- const contextName = cleanSchemaName(rawName);
1250
- if (schema.properties && (schema.type === "object" || !schema.type)) {
1251
- const requiredSet = new Set(schema.required ?? []);
1252
- const fields = [];
1253
- for (const [fieldName, fieldSchema] of Object.entries(schema.properties)) {
1254
- if (!fieldSchema) continue;
1255
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, contextName, requiredSet));
1256
- }
1257
- inlineModels.push({ name: contextName, description: void 0, fields });
1258
- return { body: { kind: "model", name: contextName }, encoding };
1259
- }
1260
- if (schema.oneOf) {
1261
- const variants = [];
1262
- for (const variant of schema.oneOf) {
1263
- if (variant.$ref) {
1264
- const ref = schemaToTypeRef(variant, contextName);
1265
- variants.push(ref);
1266
- } else if (variant.type === "null") {
1267
- } else if (variant.properties && (variant.type === "object" || !variant.type)) {
1268
- const variantName = deriveOneOfVariantName(variant, contextName, inlineModels);
1269
- const requiredSet = new Set(variant.required ?? []);
1270
- const fields = [];
1271
- for (const [fieldName, fieldSchema] of Object.entries(variant.properties)) {
1272
- if (!fieldSchema) continue;
1273
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, variantName, requiredSet));
1274
- }
1275
- inlineModels.push({ name: variantName, description: variant.description, fields });
1276
- variants.push({ kind: "model", name: variantName });
1277
- } else {
1278
- variants.push(schemaToTypeRef(variant, contextName));
1279
- }
1280
- }
1281
- const hasNull = schema.oneOf.some(
1282
- (v) => v.type === "null" || Array.isArray(v.type) && v.type.includes("null")
1283
- );
1284
- const union = {
1285
- kind: "union",
1286
- variants,
1287
- ...schema.discriminator ? {
1288
- discriminator: { property: schema.discriminator.propertyName, mapping: schema.discriminator.mapping ?? {} }
1289
- } : {}
1290
- };
1291
- const body2 = hasNull ? { kind: "nullable", inner: union } : union;
1292
- return { body: body2, encoding };
1293
- }
1294
- return { body: schemaToTypeRef(schema, contextName), encoding };
1295
- }
1296
- function deriveOneOfVariantName(variant, baseName, existingModels) {
1297
- if (variant.properties) {
1298
- for (const [, propSchema] of Object.entries(variant.properties)) {
1299
- if (propSchema?.const && typeof propSchema.const === "string") {
1300
- return toPascalCase(propSchema.const) + baseName.replace(/Request$/, "") + "Request";
1301
- }
1302
- }
1303
- }
1304
- const existingNames = new Set(existingModels.map((m) => m.name));
1305
- let name = baseName;
1306
- let suffix = 2;
1307
- while (existingNames.has(name)) {
1308
- name = `${baseName}${suffix}`;
1309
- suffix++;
1310
- }
1311
- return name;
1312
- }
1313
- function deriveResponseName(op, path, method) {
1314
- if (op?.operationId) {
1315
- return cleanSchemaName(toPascalCase(op.operationId) + "Response");
1316
- }
1317
- const segments = path.split("/").filter(Boolean);
1318
- const resource = segments[0] ?? "Unknown";
1319
- return toPascalCase(resource) + toPascalCase(method) + "Response";
1320
- }
1321
- function extractResponses(responses, op, path, method) {
1322
- const errors = [];
1323
- let response = { kind: "primitive", type: "string" };
1324
- let inlineModels = [];
1325
- let isPaginated = false;
1326
- let dataPath;
1327
- let itemType;
1328
- if (!responses) return { response, errors, inlineModels, isPaginated };
1329
- const allSuccessResponses = [];
1330
- for (const [statusCode, resp] of Object.entries(responses)) {
1331
- const code = parseInt(statusCode, 10);
1332
- if (code >= 200 && code < 300) {
1333
- let extractedType = { kind: "primitive", type: "unknown" };
1334
- const jsonContent = resp.content?.["application/json"];
1335
- if (jsonContent?.schema) {
1336
- const contextName = deriveResponseName(op, path ?? "/", method ?? "get");
1337
- const result = classifyAndExtractResponse(jsonContent.schema, contextName);
1338
- extractedType = result.response;
1339
- inlineModels = result.inlineModels;
1340
- isPaginated = result.isPaginated;
1341
- dataPath = result.dataPath;
1342
- itemType = result.itemType;
1343
- } else if (resp.content) {
1344
- if (resp.content["application/octet-stream"] || resp.content["application/pdf"]) {
1345
- extractedType = { kind: "primitive", type: "string", format: "binary" };
1346
- } else if (resp.content["text/plain"] || resp.content["text/html"] || resp.content["text/xml"] || resp.content["application/xml"]) {
1347
- extractedType = { kind: "primitive", type: "string" };
1348
- } else {
1349
- const firstKey = Object.keys(resp.content)[0];
1350
- if (firstKey) {
1351
- const firstContent = resp.content[firstKey];
1352
- if (firstContent?.schema) {
1353
- extractedType = schemaToTypeRef(
1354
- firstContent.schema,
1355
- deriveResponseName(op, path ?? "/", method ?? "get")
1356
- );
1357
- }
1358
- }
1359
- }
1360
- }
1361
- allSuccessResponses.push({ statusCode: code, type: extractedType });
1362
- } else if (code >= 400) {
1363
- const jsonContent = resp.content?.["application/json"];
1364
- const type = jsonContent?.schema ? schemaToTypeRef(jsonContent.schema, `Error${code}`) : void 0;
1365
- errors.push({ statusCode: code, type });
1366
- }
1367
- }
1368
- if (allSuccessResponses.length > 0) {
1369
- const sorted = [...allSuccessResponses].sort((a, b) => a.statusCode - b.statusCode);
1370
- const primary = sorted.find((r) => r.type.kind !== "primitive" || r.type.type !== "unknown") ?? sorted[0];
1371
- response = primary.type;
1372
- }
1373
- const successResponses = allSuccessResponses.length > 1 ? allSuccessResponses : void 0;
1374
- return { response, successResponses, errors, inlineModels, isPaginated, dataPath, itemType };
1375
- }
1376
-
1377
- // src/parser/normalize-inline-models.ts
1378
- function keepLargestByName(models) {
1379
- const byName = /* @__PURE__ */ new Map();
1380
- for (const model of models) {
1381
- const existing = byName.get(model.name);
1382
- if (!existing || model.fields.length > existing.fields.length) {
1383
- byName.set(model.name, model);
1384
- }
1385
- }
1386
- return [...byName.values()];
1387
- }
1388
- function rewriteModelRef(ref, oldName, newName) {
1389
- walkTypeRef(ref, {
1390
- model: (r) => {
1391
- if (r.name === oldName) r.name = newName;
1392
- }
1393
- });
1394
- }
1395
- function rewriteModelRefs(models, services, oldName, newName) {
1396
- for (const model of models) {
1397
- for (const field of model.fields) {
1398
- rewriteModelRef(field.type, oldName, newName);
1399
- }
1400
- }
1401
- for (const service of services) {
1402
- for (const op of service.operations) {
1403
- rewriteModelRef(op.response, oldName, newName);
1404
- if (op.requestBody) {
1405
- rewriteModelRef(op.requestBody, oldName, newName);
1406
- }
1407
- }
1408
- }
1409
- }
1410
- function mergeInlineResponseModels(schemaModels, inlineModels) {
1411
- const mergedSchemaModels = [...schemaModels];
1412
- const schemaModelNames = new Set(mergedSchemaModels.map((m) => m.name));
1413
- const schemaModelsByName = new Map(mergedSchemaModels.map((m) => [m.name, m]));
1414
- const deduplicatedInlineModels = inlineModels.filter((model) => {
1415
- if (!schemaModelNames.has(model.name)) return true;
1416
- const existing = schemaModelsByName.get(model.name);
1417
- const existingFieldNames = new Set(existing.fields.map((f) => f.name));
1418
- const inlineFieldNames = new Set(model.fields.map((f) => f.name));
1419
- const hasDifference = model.fields.some((f) => !existingFieldNames.has(f.name)) || existing.fields.some((f) => !inlineFieldNames.has(f.name));
1420
- if (hasDifference && model.fields.length > existing.fields.length) {
1421
- const idx = mergedSchemaModels.indexOf(existing);
1422
- if (idx !== -1) mergedSchemaModels[idx] = model;
1423
- schemaModelsByName.set(model.name, model);
1424
- console.warn(
1425
- `[oagen] Warning: Inline model "${model.name}" has more fields than component schema (${model.fields.length} vs ${existing.fields.length}) \u2014 using inline response model`
1426
- );
1427
- } else if (hasDifference) {
1428
- console.warn(
1429
- `[oagen] Warning: Inline model "${model.name}" has different fields than component schema \u2014 using component schema`
1430
- );
1431
- }
1432
- return false;
1433
- });
1434
- return [...mergedSchemaModels, ...keepLargestByName(deduplicatedInlineModels)];
1435
- }
1436
- function mergeFieldInlineModels(existingModels, fieldInlineModels) {
1437
- const modelNames = new Set(existingModels.map((m) => m.name));
1438
- const modelsByName = new Map(existingModels.map((m) => [m.name, m]));
1439
- const deduplicatedFieldModels = fieldInlineModels.filter((model) => {
1440
- if (!modelNames.has(model.name)) return true;
1441
- const existing = modelsByName.get(model.name);
1442
- const existingFieldNames = new Set(existing.fields.map((f) => f.name));
1443
- const inlineFieldNames = new Set(model.fields.map((f) => f.name));
1444
- const hasDifference = model.fields.some((f) => !existingFieldNames.has(f.name)) || existing.fields.some((f) => !inlineFieldNames.has(f.name));
1445
- if (hasDifference) {
1446
- console.warn(
1447
- `[oagen] Warning: Inline model "${model.name}" has different fields than component schema \u2014 using component schema`
1448
- );
1449
- }
1450
- return false;
1451
- });
1452
- return [...existingModels, ...keepLargestByName(deduplicatedFieldModels)];
1453
- }
1454
- function hasModelConstDiscriminant(model) {
1455
- return model.fields.some(
1456
- (f) => (f.name === "object" || f.name === "type") && f.type.kind === "literal" && typeof f.type.value === "string"
1457
- );
1458
- }
1459
- function isModelReferencedByOthers(name, models, excludeNames) {
1460
- for (const model of models) {
1461
- if (excludeNames.has(model.name)) continue;
1462
- for (const field of model.fields) {
1463
- let found = false;
1464
- walkTypeRef(field.type, {
1465
- model: (r) => {
1466
- if (r.name === name) found = true;
1467
- }
1468
- });
1469
- if (found) return true;
1470
- }
1471
- }
1472
- return false;
1473
- }
1474
- function collapseJsonSuffixModels(models, services) {
1475
- const normalizedModels = [...models];
1476
- const byName = new Map(normalizedModels.map((m) => [m.name, m]));
1477
- const mergeCandidates = [];
1478
- for (const model of normalizedModels) {
1479
- if (!model.name.endsWith("Json")) continue;
1480
- const baseName = model.name.slice(0, -4);
1481
- const baseModel = byName.get(baseName);
1482
- if (!baseModel || model.fields.length <= baseModel.fields.length) continue;
1483
- const isSuperset = baseModel.fields.every((f) => model.fields.some((mf) => mf.name === f.name));
1484
- if (!isSuperset) continue;
1485
- if (hasModelConstDiscriminant(model) && hasModelConstDiscriminant(baseModel)) continue;
1486
- if (isModelReferencedByOthers(model.name, normalizedModels, /* @__PURE__ */ new Set([model.name, baseName]))) continue;
1487
- mergeCandidates.push({ jsonModel: model, baseModel });
1488
- }
1489
- const toRemove = /* @__PURE__ */ new Set();
1490
- for (const { jsonModel, baseModel } of mergeCandidates) {
1491
- baseModel.fields = jsonModel.fields;
1492
- toRemove.add(jsonModel.name);
1493
- rewriteModelRefs(normalizedModels, services, jsonModel.name, baseModel.name);
1494
- console.warn(
1495
- `[oagen] Merged "${jsonModel.name}" into "${baseModel.name}" (${jsonModel.fields.length} fields, superset)`
1496
- );
1497
- }
1498
- return normalizedModels.filter((m) => !toRemove.has(m.name));
1499
- }
1500
-
1501
- // src/parser/collect-inline-enums.ts
1502
- function collectInlineEnumsFromModels(models, enums) {
1503
- const enumNames = new Set(enums.map((e) => e.name));
1504
- for (const model of models) {
1505
- for (const field of model.fields) {
1506
- collectInlineEnumFromRef(field.type, enums, enumNames);
1507
- }
1508
- }
1509
- }
1510
-
1511
- // src/parser/normalize-model-refs.ts
1512
- function validateModelRefs(spec) {
1513
- const knownNames = /* @__PURE__ */ new Set();
1514
- for (const m of spec.models) knownNames.add(m.name);
1515
- for (const e of spec.enums) knownNames.add(e.name);
1516
- function walkRef(ref, context) {
1517
- walkTypeRef(ref, {
1518
- model: (r) => {
1519
- if (!knownNames.has(r.name)) {
1520
- console.warn(`[oagen] Warning: Unresolved model reference "${r.name}" (context: ${context})`);
1521
- }
1522
- }
1523
- });
1524
- }
1525
- for (const model of spec.models) {
1526
- for (const field of model.fields) {
1527
- walkRef(field.type, `${model.name}.${field.name}`);
1528
- }
1529
- }
1530
- for (const service of spec.services) {
1531
- for (const op of service.operations) {
1532
- for (const p of [...op.pathParams, ...op.queryParams, ...op.headerParams]) {
1533
- walkRef(p.type, `${service.name}.${op.name}.${p.name}`);
1534
- }
1535
- if (op.requestBody) walkRef(op.requestBody, `${service.name}.${op.name}.requestBody`);
1536
- walkRef(op.response, `${service.name}.${op.name}.response`);
1537
- }
1538
- }
1539
- }
1540
-
1541
- // src/parser/parse.ts
1542
- async function parseSpec(specPath, options) {
1543
- const { parsed } = await loadAndBundleSpec(specPath);
1544
- const spec = parsed;
1545
- const version = spec.openapi ?? "";
1546
- if (!version.startsWith("3.")) {
1547
- throw new SpecParseError(
1548
- `Unsupported OpenAPI version: ${version}. oagen requires OpenAPI 3.x`,
1549
- `Update the spec to OpenAPI 3.0 or 3.1. If you are using Swagger 2.x, convert it first with \`npx swagger2openapi ${specPath}\`.`
1550
- );
1551
- }
1552
- const { models, enums } = extractSchemas(
1553
- spec.components?.schemas
1554
- );
1555
- const { services, inlineModels } = extractOperations(
1556
- spec.paths,
1557
- options?.operationIdTransform
1558
- );
1559
- const responseNormalizedModels = mergeInlineResponseModels(models, inlineModels);
1560
- const fieldInlineModels = extractInlineModelsFromSchemas(
1561
- spec.components?.schemas
1562
- );
1563
- const fieldMergedModels = mergeFieldInlineModels(responseNormalizedModels, fieldInlineModels);
1564
- const finalModels = collapseJsonSuffixModels(fieldMergedModels, services);
1565
- collectInlineEnumsFromModels(finalModels, enums);
1566
- const auth = extractAuthSchemes(spec.components?.securitySchemes);
1567
- const serverEntries = (spec.servers ?? []).map((s) => ({ url: s.url ?? "", description: s.description })).filter((s) => s.url);
1568
- const result = {
1569
- name: spec.info?.title ?? "Unknown API",
1570
- version: spec.info?.version ?? "0.0.0",
1571
- description: spec.info?.description,
1572
- baseUrl: serverEntries[0]?.url ?? "",
1573
- servers: serverEntries.length > 0 ? serverEntries : void 0,
1574
- services,
1575
- models: finalModels,
1576
- enums,
1577
- auth
1578
- };
1579
- validateModelRefs(result);
1580
- return result;
1581
- }
1582
- function extractAuthSchemes(securitySchemes) {
1583
- if (!securitySchemes) return void 0;
1584
- const schemes = [];
1585
- for (const [, scheme] of Object.entries(securitySchemes)) {
1586
- if (scheme.type === "http" && scheme.scheme === "bearer") {
1587
- schemes.push({ kind: "bearer" });
1588
- } else if (scheme.type === "apiKey" && scheme.in && scheme.name) {
1589
- schemes.push({ kind: "apiKey", in: scheme.in, name: scheme.name });
1590
- } else if (scheme.type === "oauth2" && scheme.flows) {
1591
- schemes.push({ kind: "oauth2", flows: scheme.flows });
1592
- }
1593
- }
1594
- return schemes.length > 0 ? schemes : void 0;
1595
- }
1596
-
1597
- // scripts/smoke/shared.ts
1598
- import "dotenv/config";
1599
- import { readFileSync } from "fs";
1600
- var SKIP_OPERATIONS = /* @__PURE__ */ new Set();
1601
- var SKIP_SERVICES = /* @__PURE__ */ new Set();
1602
- var SERVICE_PRIORITY = {};
1603
- var SERVICE_PROPERTY_MAP = {};
1604
- function loadSmokeConfig(configPath) {
1605
- if (!configPath) return;
1606
- const raw = readFileSync(configPath, "utf-8");
1607
- const config = JSON.parse(raw);
1608
- if (config.skipOperations) SKIP_OPERATIONS = new Set(config.skipOperations);
1609
- if (config.skipServices) SKIP_SERVICES = new Set(config.skipServices);
1610
- if (config.servicePriority) SERVICE_PRIORITY = config.servicePriority;
1611
- if (config.servicePropertyMap) SERVICE_PROPERTY_MAP = config.servicePropertyMap;
1612
- }
1613
- function operationSortKey(op) {
1614
- const hasParams = op.pathParams.length > 0;
1615
- const method = op.httpMethod;
1616
- if (method === "post" && !hasParams) return 0;
1617
- if (method === "get" && !hasParams && op.pagination) return 1;
1618
- if (method === "get" && !hasParams) return 2;
1619
- if (method === "post" && hasParams) return 3;
1620
- if (method === "get" && hasParams && op.pagination) return 4;
1621
- if (method === "get" && hasParams) return 5;
1622
- if (method === "put" || method === "patch") return 6;
1623
- if (method === "delete") return 7;
1624
- return 99;
1625
- }
1626
- function planOperations(spec) {
1627
- const groups = [];
1628
- for (const service of spec.services) {
1629
- if (SKIP_SERVICES.has(service.name)) continue;
1630
- const ops = [];
1631
- for (const op of service.operations) {
1632
- if (SKIP_OPERATIONS.has(op.name)) continue;
1633
- ops.push({ service: service.name, operation: op });
1634
- }
1635
- if (ops.length === 0) continue;
1636
- ops.sort((a, b) => {
1637
- return operationSortKey(a.operation) - operationSortKey(b.operation);
1638
- });
1639
- groups.push({ service: service.name, operations: ops });
1640
- }
1641
- groups.sort((a, b) => {
1642
- const aPri = SERVICE_PRIORITY[a.service] ?? 50;
1643
- const bPri = SERVICE_PRIORITY[b.service] ?? 50;
1644
- return aPri - bPri;
1645
- });
1646
- return groups;
1647
- }
1648
- function generateFixtureValue(typeRef, fieldName, spec) {
1649
- switch (typeRef.kind) {
1650
- case "primitive":
1651
- return generatePrimitiveFixture(typeRef.type, typeRef.format, fieldName);
1652
- case "array":
1653
- return [generateFixtureValue(typeRef.items, fieldName, spec)];
1654
- case "model": {
1655
- const model = spec.models.find((m) => m.name === typeRef.name);
1656
- if (model) {
1657
- const obj = {};
1658
- for (const field of model.fields) {
1659
- if (field.required) {
1660
- obj[toSnakeCase(field.name)] = generateFixtureValue(field.type, field.name, spec);
1661
- }
1662
- }
1663
- return obj;
1664
- }
1665
- return {};
1666
- }
1667
- case "enum": {
1668
- const e = spec.enums.find((en) => en.name === typeRef.name);
1669
- return e?.values[0]?.value ?? "unknown";
1670
- }
1671
- case "nullable":
1672
- return generateFixtureValue(typeRef.inner, fieldName, spec);
1673
- case "union":
1674
- if (typeRef.variants.length > 0) {
1675
- return generateFixtureValue(typeRef.variants[0], fieldName, spec);
1676
- }
1677
- return null;
1678
- }
1679
- }
1680
- function generatePrimitiveFixture(type, format, fieldName) {
1681
- if (type === "string") {
1682
- if (format === "uuid") return "550e8400-e29b-41d4-a716-446655440000";
1683
- if (format === "date") return "2024-01-01";
1684
- if (format === "date-time") return "2024-01-01T00:00:00Z";
1685
- if (format === "email") return "test@example.com";
1686
- if (format === "uri" || format === "url") return "https://example.com";
1687
- return `test_${toSnakeCase(fieldName)}`;
1688
- }
1689
- if (type === "integer") return 1;
1690
- if (type === "number") return 1;
1691
- if (type === "boolean") return true;
1692
- return null;
1693
- }
1694
- function generatePayload(op, spec) {
1695
- if (!op.requestBody) return null;
1696
- const bodyType = op.requestBody;
1697
- if (bodyType.kind === "model") {
1698
- const model = spec.models.find((m) => m.name === bodyType.name);
1699
- if (!model) return {};
1700
- const payload = {};
1701
- for (const field of model.fields) {
1702
- if (!field.required) continue;
1703
- const key = toSnakeCase(field.name);
1704
- let value = generateFixtureValue(field.type, field.name, spec);
1705
- if ((key === "name" || key === "slug") && typeof value === "string") {
1706
- value = `smoke-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1707
- }
1708
- payload[key] = value;
1709
- }
1710
- return payload;
1711
- }
1712
- return {};
1713
- }
1714
- function generateQueryParams(op, spec) {
1715
- const params = {};
1716
- if (op.pagination) {
1717
- params["limit"] = "1";
1718
- }
1719
- for (const p of op.queryParams) {
1720
- if (!p.required) continue;
1721
- const value = generateFixtureValue(p.type, p.name, spec);
1722
- params[toSnakeCase(p.name)] = String(value);
1723
- }
1724
- const sorted = {};
1725
- for (const key of Object.keys(params).sort()) {
1726
- sorted[key] = params[key];
1727
- }
1728
- return sorted;
1729
- }
1730
- function resolvePath(op, pathParams) {
1731
- let path = op.path;
1732
- for (const [name, value] of Object.entries(pathParams)) {
1733
- path = path.replace(`{${name}}`, value);
1734
- }
1735
- return path;
1736
- }
1737
- function parseCliArgs() {
1738
- const args = process.argv.slice(2);
1739
- const specIdx = args.indexOf("--spec");
1740
- const spec = specIdx !== -1 && args[specIdx + 1] ? args[specIdx + 1] : process.env.OPENAPI_SPEC_PATH;
1741
- if (!spec) {
1742
- console.error("OpenAPI spec path is required. Set OPENAPI_SPEC_PATH env var or pass --spec <path>.");
1743
- process.exit(1);
1744
- }
1745
- const sdkPathIdx = args.indexOf("--sdk-path");
1746
- const sdkPath = sdkPathIdx !== -1 && args[sdkPathIdx + 1] ? args[sdkPathIdx + 1] : void 0;
1747
- const configIdx = args.indexOf("--smoke-config");
1748
- const smokeConfig = configIdx !== -1 && args[configIdx + 1] ? args[configIdx + 1] : process.env.SMOKE_CONFIG;
1749
- return { spec, sdkPath, smokeConfig };
1750
- }
1751
-
1752
- // scripts/smoke/baseline.ts
1753
- async function main() {
1754
- const { spec: specPath, smokeConfig } = parseCliArgs();
1755
- loadSmokeConfig(smokeConfig);
1756
- console.log("Parsing spec...");
1757
- const spec = await parseSpec(specPath);
1758
- console.log(`Spec: ${spec.name} v${spec.version}`);
1759
- const groups = planOperations(spec);
1760
- const exchanges = [];
1761
- for (const group of groups) {
1762
- for (const planned of group.operations) {
1763
- const op = planned.operation;
1764
- const operationId = `${group.service}.${op.name}`;
1765
- const placeholderParams = {};
1766
- for (const p of op.pathParams) {
1767
- placeholderParams[p.name] = "<ID>";
1768
- }
1769
- const path = resolvePath(op, placeholderParams);
1770
- const queryParams = generateQueryParams(op, spec);
1771
- const body = generatePayload(op, spec);
1772
- const defaultSuccess = {
1773
- post: 201,
1774
- get: 200,
1775
- put: 200,
1776
- patch: 200,
1777
- delete: 204
1778
- };
1779
- const status = defaultSuccess[op.httpMethod] ?? 200;
1780
- exchanges.push({
1781
- operationId,
1782
- service: group.service,
1783
- operationName: op.name,
1784
- request: {
1785
- method: op.httpMethod.toUpperCase(),
1786
- path,
1787
- queryParams,
1788
- body
1789
- },
1790
- response: {
1791
- status,
1792
- body: null
1793
- },
1794
- outcome: "success",
1795
- durationMs: 0
1796
- });
1797
- }
1798
- }
1799
- const results = {
1800
- source: "spec-baseline",
1801
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1802
- specVersion: spec.version,
1803
- exchanges
1804
- };
1805
- writeFileSync("smoke-results-spec-baseline.json", JSON.stringify(results, null, 2));
1806
- console.log(`
1807
- Generated ${exchanges.length} operations from spec`);
1808
- console.log("Written to smoke-results-spec-baseline.json");
1809
- }
1810
- main().catch((err) => {
1811
- console.error("Baseline generation failed:", err);
1812
- process.exit(1);
1813
- });