@workos/oagen 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -284,17 +284,26 @@ function buildInlineModel(name, schema) {
284
284
 
285
285
  // src/parser/schemas.ts
286
286
  function extractSchemas(schemas) {
287
- const models = [];
288
287
  const enums = [];
289
- if (!schemas) return { models, enums };
288
+ if (!schemas) return { models: [], enums };
289
+ const modelsByCleanName = /* @__PURE__ */ new Map();
290
290
  for (const [name, schema] of Object.entries(schemas)) {
291
291
  const pascalName = cleanSchemaName(toPascalCase(name));
292
292
  if (schema.enum) {
293
293
  enums.push(extractEnum(pascalName, schema));
294
294
  } else {
295
- models.push(extractModel(pascalName, schema, schemas));
295
+ const model = extractModel(pascalName, schema, schemas);
296
+ const existing = modelsByCleanName.get(pascalName);
297
+ if (existing) {
298
+ if (model.fields.length > existing.fields.length) {
299
+ modelsByCleanName.set(pascalName, model);
300
+ }
301
+ } else {
302
+ modelsByCleanName.set(pascalName, model);
303
+ }
296
304
  }
297
305
  }
306
+ const models = [...modelsByCleanName.values()];
298
307
  const modelNames = new Set(models.map((m) => m.name));
299
308
  const inlineQueue = [...models];
300
309
  while (inlineQueue.length > 0) {
@@ -385,9 +394,9 @@ function extractNestedSchema(name, schema) {
385
394
  if (variant.properties && (variant.type === "object" || !variant.type)) {
386
395
  const requiredSet = new Set(variant.required ?? []);
387
396
  const fields = [];
388
- for (const [fn, fs4] of Object.entries(variant.properties)) {
389
- if (!fs4) continue;
390
- fields.push(buildFieldFromSchema(fn, fs4, name, requiredSet));
397
+ for (const [fn, fs2] of Object.entries(variant.properties)) {
398
+ if (!fs2) continue;
399
+ fields.push(buildFieldFromSchema(fn, fs2, name, requiredSet));
391
400
  }
392
401
  const variantName = models.length === 0 ? name : `${name}${models.length + 1}`;
393
402
  models.push({ name: variantName, description: variant.description, fields });
@@ -398,18 +407,18 @@ function extractNestedSchema(name, schema) {
398
407
  if (schema.properties && (schema.type === "object" || !schema.type)) {
399
408
  const requiredSet = new Set(schema.required ?? []);
400
409
  const fields = [];
401
- for (const [fn, fs4] of Object.entries(schema.properties)) {
402
- if (!fs4) continue;
403
- fields.push(buildFieldFromSchema(fn, fs4, name, requiredSet));
410
+ for (const [fn, fs2] of Object.entries(schema.properties)) {
411
+ if (!fs2) continue;
412
+ fields.push(buildFieldFromSchema(fn, fs2, name, requiredSet));
404
413
  }
405
414
  return [{ name, description: schema.description, fields }];
406
415
  }
407
416
  if (schema.type === "array" && schema.items?.properties) {
408
417
  const requiredSet = new Set(schema.items.required ?? []);
409
418
  const fields = [];
410
- for (const [fn, fs4] of Object.entries(schema.items.properties)) {
411
- if (!fs4) continue;
412
- fields.push(buildFieldFromSchema(fn, fs4, name, requiredSet));
419
+ for (const [fn, fs2] of Object.entries(schema.items.properties)) {
420
+ if (!fs2) continue;
421
+ fields.push(buildFieldFromSchema(fn, fs2, name, requiredSet));
413
422
  }
414
423
  return [{ name, description: schema.items.description, fields }];
415
424
  }
@@ -724,6 +733,18 @@ function classifyAndExtractResponse(schema, contextName) {
724
733
  }
725
734
  return extractDirectResource(schema, contextName);
726
735
  }
736
+ var KNOWN_DATA_PATHS = /* @__PURE__ */ new Set(["data", "items", "results", "records", "entries", "values", "nodes", "edges"]);
737
+ var PAGINATION_METADATA_PATTERNS = [
738
+ "metadata",
739
+ "pagination",
740
+ "cursor",
741
+ "has_more",
742
+ "page_info",
743
+ "total",
744
+ "next_page",
745
+ "previous_page",
746
+ "offset"
747
+ ];
727
748
  function detectListEnvelope(schema) {
728
749
  const propSources = [];
729
750
  if (schema.allOf) {
@@ -740,17 +761,26 @@ function detectListEnvelope(schema) {
740
761
  Object.assign(mergedProps, source);
741
762
  }
742
763
  const arrayProps = [];
743
- let nonArrayCount = 0;
764
+ const nonArrayKeys = [];
744
765
  for (const [key, propSchema] of Object.entries(mergedProps)) {
745
766
  if (!propSchema) continue;
746
767
  if (propSchema.type === "array") {
747
768
  arrayProps.push(key);
748
769
  } else {
749
- nonArrayCount++;
770
+ nonArrayKeys.push(key);
750
771
  }
751
772
  }
752
- if (arrayProps.length === 1 && nonArrayCount >= 1) {
753
- return { isEnvelope: true, dataPath: arrayProps[0] };
773
+ if (arrayProps.length === 1 && nonArrayKeys.length >= 1) {
774
+ const arrayPropName = arrayProps[0];
775
+ if (KNOWN_DATA_PATHS.has(arrayPropName)) {
776
+ return { isEnvelope: true, dataPath: arrayPropName };
777
+ }
778
+ const hasPaginationCompanion = nonArrayKeys.some(
779
+ (key) => PAGINATION_METADATA_PATTERNS.some((pattern) => key.toLowerCase().includes(pattern))
780
+ );
781
+ if (hasPaginationCompanion) {
782
+ return { isEnvelope: true, dataPath: arrayPropName };
783
+ }
754
784
  }
755
785
  return { isEnvelope: false, dataPath: null };
756
786
  }
@@ -1015,15 +1045,15 @@ function extractOperations(paths, operationIdTransform) {
1015
1045
  if (!paths) return { services: [], inlineModels: [] };
1016
1046
  const serviceMap = /* @__PURE__ */ new Map();
1017
1047
  const inlineModels = [];
1018
- for (const [path8, pathItem] of Object.entries(paths)) {
1048
+ for (const [path6, pathItem] of Object.entries(paths)) {
1019
1049
  const pathLevelParams = pathItem.parameters ?? [];
1020
1050
  for (const method of HTTP_METHODS) {
1021
1051
  const op = pathItem[method];
1022
1052
  if (!op) continue;
1023
- const serviceName = inferServiceName(path8, op.tags?.[0]);
1053
+ const serviceName = inferServiceName(path6, op.tags?.[0]);
1024
1054
  const { operation, inlineModels: opModels } = buildOperation(
1025
1055
  method,
1026
- path8,
1056
+ path6,
1027
1057
  op,
1028
1058
  pathLevelParams,
1029
1059
  operationIdTransform
@@ -1043,28 +1073,28 @@ function extractOperations(paths, operationIdTransform) {
1043
1073
  }));
1044
1074
  return { services, inlineModels };
1045
1075
  }
1046
- function inferServiceName(path8, tag) {
1076
+ function inferServiceName(path6, tag) {
1047
1077
  if (tag) {
1048
1078
  return toPascalCase(tag);
1049
1079
  }
1050
- const segments = path8.split("/").filter(Boolean);
1080
+ const segments = path6.split("/").filter(Boolean);
1051
1081
  const first = segments[0] ?? "Default";
1052
1082
  if (first.startsWith("{")) return "Default";
1053
1083
  return toPascalCase(first);
1054
1084
  }
1055
- function inferOperationName(method, path8, operationId, operationIdTransform) {
1085
+ function inferOperationName(method, path6, operationId, operationIdTransform) {
1056
1086
  if (operationId) {
1057
1087
  if (operationIdTransform) {
1058
1088
  return operationIdTransform(operationId);
1059
1089
  }
1060
1090
  return toCamelCase(operationId);
1061
1091
  }
1062
- const verb = inferVerb(method, path8);
1063
- const resource = inferResourceFromPath(path8);
1092
+ const verb = inferVerb(method, path6);
1093
+ const resource = inferResourceFromPath(path6);
1064
1094
  return resource ? toCamelCase(`${verb}_${resource}`) : verb;
1065
1095
  }
1066
- function inferVerb(method, path8) {
1067
- const segments = path8.split("/").filter(Boolean);
1096
+ function inferVerb(method, path6) {
1097
+ const segments = path6.split("/").filter(Boolean);
1068
1098
  const hasTrailingParam = segments.length > 0 && segments[segments.length - 1].startsWith("{");
1069
1099
  switch (method) {
1070
1100
  case "get":
@@ -1086,8 +1116,8 @@ function inferVerb(method, path8) {
1086
1116
  return method;
1087
1117
  }
1088
1118
  }
1089
- function inferResourceFromPath(path8) {
1090
- const segments = path8.split("/").filter(Boolean);
1119
+ function inferResourceFromPath(path6) {
1120
+ const segments = path6.split("/").filter(Boolean);
1091
1121
  if (segments.length <= 1) return null;
1092
1122
  let resourceSegment = null;
1093
1123
  for (let i = segments.length - 1; i >= 0; i--) {
@@ -1135,15 +1165,15 @@ function disambiguateOperationNames(operations) {
1135
1165
  }
1136
1166
  }
1137
1167
  }
1138
- function inferDeeperContext(path8) {
1139
- const segments = path8.split("/").filter(Boolean);
1168
+ function inferDeeperContext(path6) {
1169
+ const segments = path6.split("/").filter(Boolean);
1140
1170
  const nonParams = segments.filter((s) => !s.startsWith("{"));
1141
1171
  if (nonParams.length >= 3) {
1142
1172
  return toPascalCase(nonParams.slice(1, -1).join("_"));
1143
1173
  }
1144
1174
  return null;
1145
1175
  }
1146
- function buildOperation(method, path8, op, pathLevelParams, operationIdTransform) {
1176
+ function buildOperation(method, path6, op, pathLevelParams, operationIdTransform) {
1147
1177
  const allParams = [...pathLevelParams, ...op.parameters ?? []];
1148
1178
  const hasIdempotencyHeader = allParams.some((p) => p.in === "header" && p.name.toLowerCase() === "idempotency-key");
1149
1179
  const pathParams = extractParams(allParams, "path");
@@ -1160,7 +1190,7 @@ function buildOperation(method, path8, op, pathLevelParams, operationIdTransform
1160
1190
  isPaginated,
1161
1191
  dataPath: responseDataPath,
1162
1192
  itemType: responseItemType
1163
- } = extractResponses(op.responses, op, path8, method);
1193
+ } = extractResponses(op.responses, op, path6, method);
1164
1194
  inlineModels.push(...reqBodyModels);
1165
1195
  const paginationFromParams = detectPagination(response, queryParams, responseDataPath);
1166
1196
  let pagination;
@@ -1178,10 +1208,10 @@ function buildOperation(method, path8, op, pathLevelParams, operationIdTransform
1178
1208
  }
1179
1209
  return {
1180
1210
  operation: {
1181
- name: inferOperationName(method, path8, op.operationId, operationIdTransform),
1211
+ name: inferOperationName(method, path6, op.operationId, operationIdTransform),
1182
1212
  description: buildDescription(op.summary, op.description),
1183
1213
  httpMethod: method,
1184
- path: path8,
1214
+ path: path6,
1185
1215
  pathParams,
1186
1216
  queryParams,
1187
1217
  headerParams,
@@ -1312,15 +1342,15 @@ function deriveOneOfVariantName(variant, baseName, existingModels) {
1312
1342
  }
1313
1343
  return name;
1314
1344
  }
1315
- function deriveResponseName(op, path8, method) {
1345
+ function deriveResponseName(op, path6, method) {
1316
1346
  if (op?.operationId) {
1317
1347
  return cleanSchemaName(toPascalCase(op.operationId) + "Response");
1318
1348
  }
1319
- const segments = path8.split("/").filter(Boolean);
1349
+ const segments = path6.split("/").filter(Boolean);
1320
1350
  const resource = segments[0] ?? "Unknown";
1321
1351
  return toPascalCase(resource) + toPascalCase(method) + "Response";
1322
1352
  }
1323
- function extractResponses(responses, op, path8, method) {
1353
+ function extractResponses(responses, op, path6, method) {
1324
1354
  const errors = [];
1325
1355
  let response = { kind: "primitive", type: "string" };
1326
1356
  let inlineModels = [];
@@ -1335,7 +1365,7 @@ function extractResponses(responses, op, path8, method) {
1335
1365
  let extractedType = { kind: "primitive", type: "unknown" };
1336
1366
  const jsonContent = resp.content?.["application/json"];
1337
1367
  if (jsonContent?.schema) {
1338
- const contextName = deriveResponseName(op, path8 ?? "/", method ?? "get");
1368
+ const contextName = deriveResponseName(op, path6 ?? "/", method ?? "get");
1339
1369
  const result = classifyAndExtractResponse(jsonContent.schema, contextName);
1340
1370
  extractedType = result.response;
1341
1371
  inlineModels = result.inlineModels;
@@ -1354,7 +1384,7 @@ function extractResponses(responses, op, path8, method) {
1354
1384
  if (firstContent?.schema) {
1355
1385
  extractedType = schemaToTypeRef(
1356
1386
  firstContent.schema,
1357
- deriveResponseName(op, path8 ?? "/", method ?? "get")
1387
+ deriveResponseName(op, path6 ?? "/", method ?? "get")
1358
1388
  );
1359
1389
  }
1360
1390
  }
@@ -1387,20 +1417,24 @@ function keepLargestByName(models) {
1387
1417
  }
1388
1418
  return [...byName.values()];
1389
1419
  }
1390
- function rewriteModelRefsInServices(services, oldName, newName) {
1420
+ function rewriteModelRef(ref, oldName, newName) {
1421
+ walkTypeRef(ref, {
1422
+ model: (r) => {
1423
+ if (r.name === oldName) r.name = newName;
1424
+ }
1425
+ });
1426
+ }
1427
+ function rewriteModelRefs(models, services, oldName, newName) {
1428
+ for (const model of models) {
1429
+ for (const field of model.fields) {
1430
+ rewriteModelRef(field.type, oldName, newName);
1431
+ }
1432
+ }
1391
1433
  for (const service of services) {
1392
1434
  for (const op of service.operations) {
1393
- walkTypeRef(op.response, {
1394
- model: (r) => {
1395
- if (r.name === oldName) r.name = newName;
1396
- }
1397
- });
1435
+ rewriteModelRef(op.response, oldName, newName);
1398
1436
  if (op.requestBody) {
1399
- walkTypeRef(op.requestBody, {
1400
- model: (r) => {
1401
- if (r.name === oldName) r.name = newName;
1402
- }
1403
- });
1437
+ rewriteModelRef(op.requestBody, oldName, newName);
1404
1438
  }
1405
1439
  }
1406
1440
  }
@@ -1449,9 +1483,30 @@ function mergeFieldInlineModels(existingModels, fieldInlineModels) {
1449
1483
  });
1450
1484
  return [...existingModels, ...keepLargestByName(deduplicatedFieldModels)];
1451
1485
  }
1486
+ function hasModelConstDiscriminant(model) {
1487
+ return model.fields.some(
1488
+ (f) => (f.name === "object" || f.name === "type") && f.type.kind === "literal" && typeof f.type.value === "string"
1489
+ );
1490
+ }
1491
+ function isModelReferencedByOthers(name, models, excludeNames) {
1492
+ for (const model of models) {
1493
+ if (excludeNames.has(model.name)) continue;
1494
+ for (const field of model.fields) {
1495
+ let found = false;
1496
+ walkTypeRef(field.type, {
1497
+ model: (r) => {
1498
+ if (r.name === name) found = true;
1499
+ }
1500
+ });
1501
+ if (found) return true;
1502
+ }
1503
+ }
1504
+ return false;
1505
+ }
1452
1506
  function collapseJsonSuffixModels(models, services) {
1453
1507
  const normalizedModels = [...models];
1454
1508
  const byName = new Map(normalizedModels.map((m) => [m.name, m]));
1509
+ const mergeCandidates = [];
1455
1510
  for (const model of normalizedModels) {
1456
1511
  if (!model.name.endsWith("Json")) continue;
1457
1512
  const baseName = model.name.slice(0, -4);
@@ -1459,13 +1514,20 @@ function collapseJsonSuffixModels(models, services) {
1459
1514
  if (!baseModel || model.fields.length <= baseModel.fields.length) continue;
1460
1515
  const isSuperset = baseModel.fields.every((f) => model.fields.some((mf) => mf.name === f.name));
1461
1516
  if (!isSuperset) continue;
1462
- baseModel.fields = model.fields;
1463
- const jsonIdx = normalizedModels.indexOf(model);
1464
- if (jsonIdx !== -1) normalizedModels.splice(jsonIdx, 1);
1465
- rewriteModelRefsInServices(services, model.name, baseName);
1466
- console.warn(`[oagen] Merged "${model.name}" into "${baseName}" (${model.fields.length} fields, superset)`);
1517
+ if (hasModelConstDiscriminant(model) && hasModelConstDiscriminant(baseModel)) continue;
1518
+ if (isModelReferencedByOthers(model.name, normalizedModels, /* @__PURE__ */ new Set([model.name, baseName]))) continue;
1519
+ mergeCandidates.push({ jsonModel: model, baseModel });
1520
+ }
1521
+ const toRemove = /* @__PURE__ */ new Set();
1522
+ for (const { jsonModel, baseModel } of mergeCandidates) {
1523
+ baseModel.fields = jsonModel.fields;
1524
+ toRemove.add(jsonModel.name);
1525
+ rewriteModelRefs(normalizedModels, services, jsonModel.name, baseModel.name);
1526
+ console.warn(
1527
+ `[oagen] Merged "${jsonModel.name}" into "${baseModel.name}" (${jsonModel.fields.length} fields, superset)`
1528
+ );
1467
1529
  }
1468
- return normalizedModels;
1530
+ return normalizedModels.filter((m) => !toRemove.has(m.name));
1469
1531
  }
1470
1532
 
1471
1533
  // src/parser/collect-inline-enums.ts
@@ -1527,11 +1589,11 @@ async function parseSpec(specPath, options) {
1527
1589
  options?.operationIdTransform
1528
1590
  );
1529
1591
  const responseNormalizedModels = mergeInlineResponseModels(models, inlineModels);
1530
- const jsonCollapsedModels = collapseJsonSuffixModels(responseNormalizedModels, services);
1531
1592
  const fieldInlineModels = extractInlineModelsFromSchemas(
1532
1593
  spec.components?.schemas
1533
1594
  );
1534
- const finalModels = mergeFieldInlineModels(jsonCollapsedModels, fieldInlineModels);
1595
+ const fieldMergedModels = mergeFieldInlineModels(responseNormalizedModels, fieldInlineModels);
1596
+ const finalModels = collapseJsonSuffixModels(fieldMergedModels, services);
1535
1597
  collectInlineEnumsFromModels(finalModels, enums);
1536
1598
  const auth = extractAuthSchemes(spec.components?.securitySchemes);
1537
1599
  const serverEntries = (spec.servers ?? []).map((s) => ({ url: s.url ?? "", description: s.description })).filter((s) => s.url);
@@ -1617,11 +1679,11 @@ function declarationKey(node) {
1617
1679
  function importEntries(node, source) {
1618
1680
  const entries = [];
1619
1681
  const collect = (spec) => {
1620
- const path8 = spec.childForFieldName("path")?.text ?? spec.text;
1682
+ const path6 = spec.childForFieldName("path")?.text ?? spec.text;
1621
1683
  const name = spec.childForFieldName("name")?.text ?? "";
1622
1684
  const text = source.slice(spec.startIndex, spec.endIndex);
1623
1685
  entries.push({
1624
- key: `${name}:${path8.replace(/^"|"$/g, "")}`,
1686
+ key: `${name}:${path6.replace(/^"|"$/g, "")}`,
1625
1687
  text
1626
1688
  });
1627
1689
  };
@@ -2427,75 +2489,11 @@ async function extractStatements(source, language) {
2427
2489
  const tree = safeParse(parser, source);
2428
2490
  return adapter.parseStatements(tree, source);
2429
2491
  }
2430
- async function mergeIntoExisting(existingContent, generatedContent, language, header, options) {
2492
+ async function mergeIntoExisting(existingContent, generatedContent, language, header) {
2431
2493
  const adapter = getMergeAdapter(language);
2432
2494
  if (!adapter) {
2433
2495
  throw new Error(`No merge adapter configured for language "${language}"`);
2434
2496
  }
2435
- if (options?.docstringOnly) {
2436
- let result2 = existingContent;
2437
- let docstringUpdates2 = 0;
2438
- const parser = await getParser(language);
2439
- const resultTree = safeParse(parser, result2);
2440
- const generatedTree = safeParse(parser, generatedContent);
2441
- const resultDocs = adapter.extractDocstrings(resultTree, result2);
2442
- const generatedDocs = adapter.extractDocstrings(generatedTree, generatedContent);
2443
- const ignoredRegions = findIgnoredRegions(result2);
2444
- const ignoredSymbols = buildIgnoredSymbolNames(resultDocs, ignoredRegions);
2445
- const headerLine2 = header.trim();
2446
- const edits = [];
2447
- for (const [symbolName, genInfo] of generatedDocs) {
2448
- const existInfo = resultDocs.get(symbolName);
2449
- if (!existInfo) continue;
2450
- if (ignoredSymbols.has(symbolName)) continue;
2451
- const genDoc = genInfo.docstring && genInfo.docstring.text.trim() !== headerLine2 ? genInfo.docstring : null;
2452
- if (genDoc) {
2453
- if (existInfo.docstring) {
2454
- const isPreserved = existInfo.docstring.text.includes("@oagen-ignore");
2455
- if (!isPreserved && existInfo.docstring.text !== genDoc.text) {
2456
- edits.push({
2457
- start: existInfo.docstring.startIndex,
2458
- end: existInfo.docstring.endIndex,
2459
- newText: genDoc.text
2460
- });
2461
- docstringUpdates2++;
2462
- }
2463
- } else {
2464
- const lineStart = existInfo.declStartIndex - existInfo.declColumn;
2465
- const indent = " ".repeat(existInfo.declColumn);
2466
- edits.push({ start: lineStart, end: lineStart, newText: indent + genDoc.text + "\n" });
2467
- docstringUpdates2++;
2468
- }
2469
- }
2470
- for (const [memberName, genMember] of genInfo.members) {
2471
- const existMember = existInfo.members.get(memberName);
2472
- if (!existMember || !genMember.docstring) continue;
2473
- if (existMember.docstring) {
2474
- const isPreserved = existMember.docstring.text.includes("@oagen-ignore");
2475
- if (!isPreserved && existMember.docstring.text !== genMember.docstring.text) {
2476
- edits.push({
2477
- start: existMember.docstring.startIndex,
2478
- end: existMember.docstring.endIndex,
2479
- newText: genMember.docstring.text
2480
- });
2481
- docstringUpdates2++;
2482
- }
2483
- } else {
2484
- const lineStart = existMember.declStartIndex - existMember.declColumn;
2485
- const indent = " ".repeat(existMember.declColumn);
2486
- edits.push({ start: lineStart, end: lineStart, newText: indent + genMember.docstring.text + "\n" });
2487
- docstringUpdates2++;
2488
- }
2489
- }
2490
- }
2491
- if (edits.length > 0) {
2492
- edits.sort((a, b) => b.start - a.start);
2493
- for (const edit of edits) {
2494
- result2 = result2.slice(0, edit.start) + edit.newText + result2.slice(edit.end);
2495
- }
2496
- }
2497
- return { content: result2, added: 0, preserved: 0, changed: docstringUpdates2 > 0 };
2498
- }
2499
2497
  const existingStatements = await extractStatements(existingContent, language);
2500
2498
  const generatedStatements = await extractStatements(generatedContent, language);
2501
2499
  const existingKeys = /* @__PURE__ */ new Set();
@@ -2544,6 +2542,14 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
2544
2542
  preserved++;
2545
2543
  continue;
2546
2544
  }
2545
+ const braceMatch = imp.text.match(/\{([^}]+)\}/);
2546
+ if (braceMatch) {
2547
+ const names = braceMatch[1].split(",").map((n) => n.replace(/\btype\b/, "").trim()).filter(Boolean);
2548
+ if (names.length > 0 && names.every((n) => existingImportedNames.has(n))) {
2549
+ preserved++;
2550
+ continue;
2551
+ }
2552
+ }
2547
2553
  newImports.push(imp);
2548
2554
  }
2549
2555
  for (const stmt of generatedStatements.statements) {
@@ -2768,22 +2774,6 @@ async function writeFiles(files, outputDir, options) {
2768
2774
  }
2769
2775
  if (language && hasGrammar(language)) {
2770
2776
  try {
2771
- if (file.mergeMode === "docstring-only") {
2772
- const mergeResult2 = await mergeIntoExisting(existingContent, file.content, language, header, {
2773
- docstringOnly: true
2774
- });
2775
- let finalContent2 = mergeResult2.content;
2776
- if (header && !finalContent2.startsWith(header)) {
2777
- finalContent2 = header + "\n\n" + finalContent2;
2778
- }
2779
- if (finalContent2 === existingContent) {
2780
- result.identical.push(file.path);
2781
- } else {
2782
- await fs.writeFile(fullPath, finalContent2, "utf-8");
2783
- result.merged.push(file.path);
2784
- }
2785
- continue;
2786
- }
2787
2777
  const mergeResult = await mergeIntoExisting(existingContent, file.content, language, header);
2788
2778
  let finalContent = mergeResult.content;
2789
2779
  if (header && !finalContent.startsWith(header)) {
@@ -2849,24 +2839,20 @@ function generateFiles(spec, emitter, options) {
2849
2839
  }
2850
2840
 
2851
2841
  // src/engine/integrate.ts
2852
- import * as fs2 from "fs";
2853
- import * as path2 from "path";
2854
- function mapFilesForTargetIntegration(files, language, targetDir) {
2842
+ function mapFilesForTargetIntegration(files, language) {
2855
2843
  const langPrefix = `${language}/`;
2856
2844
  return files.filter((f) => f.integrateTarget !== false).map((f) => {
2857
2845
  const stripped = f.path.startsWith(langPrefix) ? f.path.replace(langPrefix, "") : f.path;
2858
- const existsInTarget = targetDir ? fs2.existsSync(path2.join(targetDir, stripped)) : false;
2859
2846
  return {
2860
2847
  ...f,
2861
2848
  skipIfExists: false,
2862
2849
  // Always merge in target — never hard-skip
2863
- mergeMode: existsInTarget ? "docstring-only" : "full",
2864
2850
  path: stripped
2865
2851
  };
2866
2852
  });
2867
2853
  }
2868
2854
  async function integrateGeneratedFiles(opts) {
2869
- return writeFiles(mapFilesForTargetIntegration(opts.files, opts.language, opts.targetDir), opts.targetDir, {
2855
+ return writeFiles(mapFilesForTargetIntegration(opts.files, opts.language), opts.targetDir, {
2870
2856
  language: opts.language,
2871
2857
  header: opts.header
2872
2858
  });
@@ -2933,8 +2919,8 @@ function getEmitter(language) {
2933
2919
  }
2934
2920
 
2935
2921
  // src/cli/overlay-loader.ts
2936
- import { readFileSync, existsSync as existsSync2 } from "fs";
2937
- import * as path3 from "path";
2922
+ import { readFileSync, existsSync } from "fs";
2923
+ import * as path2 from "path";
2938
2924
 
2939
2925
  // src/compat/language-hints.ts
2940
2926
  var UNTYPED_MAP_PATTERNS = /* @__PURE__ */ new Set([
@@ -3157,8 +3143,8 @@ function buildOverlayLookup(surface, manifest, spec, hints, options) {
3157
3143
  for (const name of Object.keys(surface.typeAliases)) {
3158
3144
  lookup.typeAliasByName.set(name, name);
3159
3145
  }
3160
- for (const [path8, symbols] of Object.entries(surface.exports)) {
3161
- lookup.requiredExports.set(path8, new Set(symbols));
3146
+ for (const [path6, symbols] of Object.entries(surface.exports)) {
3147
+ lookup.requiredExports.set(path6, new Set(symbols));
3162
3148
  }
3163
3149
  for (const record of [surface.classes, surface.interfaces, surface.typeAliases, surface.enums]) {
3164
3150
  for (const [name, item] of Object.entries(record)) {
@@ -3325,11 +3311,11 @@ function patchOverlay(overlay, violations, baseline) {
3325
3311
  if (v.category === "export-structure") {
3326
3312
  const match = v.symbolPath.match(/^exports\[(.+?)\]\.(.+)$/);
3327
3313
  if (match) {
3328
- const [, path8, symbol] = match;
3329
- if (!patched.requiredExports.has(path8)) {
3330
- patched.requiredExports.set(path8, /* @__PURE__ */ new Set());
3314
+ const [, path6, symbol] = match;
3315
+ if (!patched.requiredExports.has(path6)) {
3316
+ patched.requiredExports.set(path6, /* @__PURE__ */ new Set());
3331
3317
  }
3332
- patched.requiredExports.get(path8).add(symbol);
3318
+ patched.requiredExports.get(path6).add(symbol);
3333
3319
  }
3334
3320
  }
3335
3321
  }
@@ -3374,8 +3360,8 @@ function loadOverlayContext(opts) {
3374
3360
  );
3375
3361
  }
3376
3362
  let manifest;
3377
- const resolvedManifestPath = opts.manifestPath ?? path3.join(opts.outputDir, "smoke-manifest.json");
3378
- if (opts.manifestPath || existsSync2(resolvedManifestPath)) {
3363
+ const resolvedManifestPath = opts.manifestPath ?? path2.join(opts.outputDir, "smoke-manifest.json");
3364
+ if (opts.manifestPath || existsSync(resolvedManifestPath)) {
3379
3365
  try {
3380
3366
  const parsed = JSON.parse(readFileSync(resolvedManifestPath, "utf-8"));
3381
3367
  if (Array.isArray(parsed)) {
@@ -3862,288 +3848,21 @@ function summarize(changes) {
3862
3848
  return { added, removed, modified, breaking, additive };
3863
3849
  }
3864
3850
 
3865
- // src/differ/file-map.ts
3866
- function mapChangesToFiles(changes, emitter, ctx) {
3867
- const regenerate = /* @__PURE__ */ new Set();
3868
- const toDelete = /* @__PURE__ */ new Set();
3869
- const modelFiles = /* @__PURE__ */ new Map();
3870
- const enumFiles = /* @__PURE__ */ new Map();
3871
- const serviceFiles = /* @__PURE__ */ new Map();
3872
- for (const model of ctx.spec.models) {
3873
- const files = emitter.generateModels([model], ctx);
3874
- const sigs = emitter.generateTypeSignatures?.({ ...ctx.spec, models: [model], enums: [], services: [] }, ctx) ?? [];
3875
- modelFiles.set(
3876
- model.name,
3877
- [...files, ...sigs].map((f) => f.path)
3878
- );
3879
- }
3880
- for (const enumDef of ctx.spec.enums) {
3881
- const files = emitter.generateEnums([enumDef], ctx);
3882
- enumFiles.set(
3883
- enumDef.name,
3884
- files.map((f) => f.path)
3885
- );
3886
- }
3887
- for (const service of ctx.spec.services) {
3888
- const files = [
3889
- ...emitter.generateResources([service], ctx),
3890
- ...emitter.generateTests({ ...ctx.spec, services: [service], models: [], enums: [] }, ctx)
3891
- ];
3892
- const sigs = emitter.generateTypeSignatures?.({ ...ctx.spec, services: [service], models: [], enums: [] }, ctx) ?? [];
3893
- serviceFiles.set(
3894
- service.name,
3895
- [...files, ...sigs].map((f) => f.path)
3896
- );
3897
- }
3898
- const modelToServices = buildReferenceGraph(ctx.spec);
3899
- for (const change of changes) {
3900
- switch (change.kind) {
3901
- case "model-added":
3902
- case "model-modified": {
3903
- const files = modelFiles.get(change.name) ?? [];
3904
- files.forEach((f) => regenerate.add(f));
3905
- const refs = modelToServices.get(change.name) ?? [];
3906
- for (const svc of refs) {
3907
- const svcFiles = serviceFiles.get(svc) ?? [];
3908
- svcFiles.forEach((f) => regenerate.add(f));
3909
- }
3910
- break;
3911
- }
3912
- case "model-removed": {
3913
- const files = modelFiles.get(change.name) ?? [];
3914
- files.forEach((f) => toDelete.add(f));
3915
- const refs = modelToServices.get(change.name) ?? [];
3916
- for (const svc of refs) {
3917
- const svcFiles = serviceFiles.get(svc) ?? [];
3918
- svcFiles.forEach((f) => regenerate.add(f));
3919
- }
3920
- break;
3921
- }
3922
- case "enum-added":
3923
- case "enum-modified": {
3924
- const files = enumFiles.get(change.name) ?? [];
3925
- files.forEach((f) => regenerate.add(f));
3926
- const refs = modelToServices.get(change.name) ?? [];
3927
- for (const svc of refs) {
3928
- const svcFiles = serviceFiles.get(svc) ?? [];
3929
- svcFiles.forEach((f) => regenerate.add(f));
3930
- }
3931
- break;
3932
- }
3933
- case "enum-removed": {
3934
- const files = enumFiles.get(change.name) ?? [];
3935
- files.forEach((f) => toDelete.add(f));
3936
- const refs = modelToServices.get(change.name) ?? [];
3937
- for (const svc of refs) {
3938
- const svcFiles = serviceFiles.get(svc) ?? [];
3939
- svcFiles.forEach((f) => regenerate.add(f));
3940
- }
3941
- break;
3942
- }
3943
- case "service-added": {
3944
- const files = serviceFiles.get(change.name) ?? [];
3945
- files.forEach((f) => regenerate.add(f));
3946
- break;
3947
- }
3948
- case "service-removed": {
3949
- const files = serviceFiles.get(change.name) ?? [];
3950
- files.forEach((f) => toDelete.add(f));
3951
- break;
3952
- }
3953
- case "operation-added":
3954
- case "operation-removed":
3955
- case "operation-modified": {
3956
- const files = serviceFiles.get(change.serviceName) ?? [];
3957
- files.forEach((f) => regenerate.add(f));
3958
- break;
3959
- }
3960
- }
3961
- }
3962
- for (const f of toDelete) {
3963
- regenerate.delete(f);
3964
- }
3965
- return {
3966
- regenerate: [...regenerate].sort(),
3967
- delete: [...toDelete].sort()
3968
- };
3969
- }
3970
- function buildReferenceGraph(spec) {
3971
- const refs = /* @__PURE__ */ new Map();
3972
- function trackRef(typeRef, serviceName) {
3973
- const addName = (name) => {
3974
- if (!refs.has(name)) refs.set(name, /* @__PURE__ */ new Set());
3975
- refs.get(name).add(serviceName);
3976
- };
3977
- walkTypeRef(typeRef, {
3978
- model: (r) => addName(r.name),
3979
- enum: (r) => addName(r.name)
3980
- });
3981
- }
3982
- for (const service of spec.services) {
3983
- for (const op of service.operations) {
3984
- for (const p of [...op.pathParams, ...op.queryParams, ...op.headerParams]) {
3985
- trackRef(p.type, service.name);
3986
- }
3987
- if (op.requestBody) trackRef(op.requestBody, service.name);
3988
- trackRef(op.response, service.name);
3989
- }
3990
- }
3991
- const modelDeps = /* @__PURE__ */ new Map();
3992
- function collectDeps(typeRef, deps) {
3993
- walkTypeRef(typeRef, {
3994
- model: (r) => deps.add(r.name),
3995
- enum: (r) => deps.add(r.name)
3996
- });
3997
- }
3998
- for (const model of spec.models) {
3999
- const deps = /* @__PURE__ */ new Set();
4000
- for (const field of model.fields) {
4001
- collectDeps(field.type, deps);
4002
- }
4003
- deps.delete(model.name);
4004
- if (deps.size > 0) {
4005
- modelDeps.set(model.name, deps);
4006
- }
4007
- }
4008
- let changed = true;
4009
- while (changed) {
4010
- changed = false;
4011
- for (const [modelName, services] of refs) {
4012
- const deps = modelDeps.get(modelName);
4013
- if (!deps) continue;
4014
- for (const dep of deps) {
4015
- if (!refs.has(dep)) refs.set(dep, /* @__PURE__ */ new Set());
4016
- const depServices = refs.get(dep);
4017
- for (const svc of services) {
4018
- if (!depServices.has(svc)) {
4019
- depServices.add(svc);
4020
- changed = true;
4021
- }
4022
- }
4023
- }
4024
- }
4025
- }
4026
- const result = /* @__PURE__ */ new Map();
4027
- for (const [name, svcSet] of refs) {
4028
- result.set(name, [...svcSet]);
4029
- }
4030
- return result;
4031
- }
4032
-
4033
- // src/engine/incremental.ts
4034
- import * as fs3 from "fs/promises";
4035
- import * as path4 from "path";
4036
- async function generateIncremental(oldSpec, newSpec, emitter, options) {
4037
- const diff = diffSpecs(oldSpec, newSpec);
4038
- if (diff.changes.length === 0) {
4039
- return { generated: [], deleted: [], diff };
4040
- }
4041
- const ctx = buildEmitterContext(newSpec, options);
4042
- const affected = mapChangesToFiles(diff.changes, emitter, ctx);
4043
- const allFiles = generateAllFiles(newSpec, emitter, ctx);
4044
- const header = emitter.fileHeader();
4045
- const affectedSet = new Set(affected.regenerate);
4046
- const generated = applyFileHeaders(
4047
- allFiles.filter((f) => affectedSet.has(f.path)),
4048
- header
4049
- );
4050
- if (!options.dryRun) {
4051
- const writeResult = await writeFiles(generated, options.outputDir, {
4052
- language: emitter.language,
4053
- header
4054
- });
4055
- if (writeResult.ignored.length > 0) {
4056
- console.log(`Ignored ${writeResult.ignored.length} files (@oagen-ignore-file)`);
4057
- }
4058
- if (options.target) {
4059
- const targetResult = await integrateGeneratedFiles({
4060
- files: generated,
4061
- language: emitter.language,
4062
- targetDir: options.target,
4063
- header
4064
- });
4065
- if (targetResult.written.length > 0) {
4066
- console.log(`Target: created ${targetResult.written.length} new files`);
4067
- }
4068
- if (targetResult.merged.length > 0) {
4069
- console.log(`Target: merged into ${targetResult.merged.length} existing files (additive only)`);
4070
- }
4071
- if (targetResult.skipped.length > 0) {
4072
- console.log(`Target: skipped ${targetResult.skipped.length} files (excluded or no grammar)`);
4073
- }
4074
- if (targetResult.ignored.length > 0) {
4075
- console.log(`Target: ignored ${targetResult.ignored.length} files (@oagen-ignore-file)`);
4076
- }
4077
- }
4078
- if (options.force) {
4079
- for (const filePath of affected.delete) {
4080
- const fullPath = path4.join(options.outputDir, filePath);
4081
- await fs3.rm(fullPath, { force: true });
4082
- }
4083
- }
4084
- }
4085
- return {
4086
- generated,
4087
- deleted: options.force ? affected.delete : [],
4088
- diff
4089
- };
4090
- }
4091
-
4092
3851
  // src/cli/diff.ts
4093
3852
  async function diffCommand(opts) {
4094
3853
  const parseOptions = { operationIdTransform: opts.operationIdTransform };
4095
- let [oldSpec, newSpec] = await Promise.all([
4096
- parseSpec(opts.old, parseOptions),
4097
- parseSpec(opts.new, parseOptions)
4098
- ]);
3854
+ let [oldSpec, newSpec] = await Promise.all([parseSpec(opts.old, parseOptions), parseSpec(opts.new, parseOptions)]);
4099
3855
  if (opts.docUrl) {
4100
3856
  oldSpec = expandDocUrls(oldSpec, opts.docUrl);
4101
3857
  newSpec = expandDocUrls(newSpec, opts.docUrl);
4102
3858
  }
4103
- if (opts.report) {
4104
- const diff = diffSpecs(oldSpec, newSpec);
4105
- console.log(JSON.stringify(diff, null, 2));
4106
- throw new CommandError(
4107
- "",
4108
- "",
4109
- diff.summary.breaking > 0 ? 2 : diff.summary.modified > 0 || diff.summary.removed > 0 ? 1 : diff.summary.added > 0 ? 1 : 0
4110
- );
4111
- }
4112
- if (!opts.lang || !opts.output) {
4113
- throw new CommandError("--lang and --output are required for incremental generation", "", 1);
4114
- }
4115
- let apiSurface;
4116
- let overlayLookup;
4117
- if (opts.apiSurface) {
4118
- const ctx = loadOverlayContext({
4119
- apiSurfacePath: opts.apiSurface,
4120
- manifestPath: opts.manifest,
4121
- outputDir: opts.output,
4122
- lang: opts.lang
4123
- });
4124
- apiSurface = ctx.apiSurface;
4125
- overlayLookup = ctx.overlayLookup;
4126
- }
4127
- const emitter = getEmitter(opts.lang);
4128
- const result = await generateIncremental(oldSpec, newSpec, emitter, {
4129
- namespace: newSpec.name,
4130
- outputDir: opts.output,
4131
- force: opts.force,
4132
- target: opts.target,
4133
- apiSurface,
4134
- overlayLookup
4135
- });
4136
- if (result.diff.changes.length === 0) {
4137
- console.log("No changes detected");
4138
- } else {
4139
- console.log(`Regenerated ${result.generated.length} files`);
4140
- if (result.deleted.length > 0) {
4141
- console.log(`Deleted ${result.deleted.length} files`);
4142
- }
4143
- if (!opts.force && result.diff.changes.some((c) => c.kind.endsWith("-removed"))) {
4144
- console.log("Use --force to delete files for removed schemas");
4145
- }
4146
- }
3859
+ const diff = diffSpecs(oldSpec, newSpec);
3860
+ console.log(JSON.stringify(diff, null, 2));
3861
+ throw new CommandError(
3862
+ "",
3863
+ "",
3864
+ diff.summary.breaking > 0 ? 2 : diff.summary.modified > 0 || diff.summary.removed > 0 ? 1 : diff.summary.added > 0 ? 1 : 0
3865
+ );
4147
3866
  }
4148
3867
 
4149
3868
  // src/cli/extract.ts
@@ -4158,7 +3877,7 @@ async function extractCommand(opts) {
4158
3877
  }
4159
3878
 
4160
3879
  // src/cli/verify.ts
4161
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
3880
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
4162
3881
 
4163
3882
  // src/compat/spec-filter.ts
4164
3883
  function specDerivedNames(spec, hints) {
@@ -4257,9 +3976,9 @@ function filterSurface(surface, allowedNames, opts) {
4257
3976
  let filteredClasses = filterRecord(surface.classes, allowedNames);
4258
3977
  if (methodPaths) {
4259
3978
  const serviceClassNames = /* @__PURE__ */ new Set();
4260
- for (const path8 of methodPaths) {
4261
- const dot = path8.indexOf(".");
4262
- if (dot !== -1) serviceClassNames.add(path8.slice(0, dot));
3979
+ for (const path6 of methodPaths) {
3980
+ const dot = path6.indexOf(".");
3981
+ if (dot !== -1) serviceClassNames.add(path6.slice(0, dot));
4263
3982
  }
4264
3983
  const newClasses = {};
4265
3984
  for (const [name, cls] of Object.entries(filteredClasses)) {
@@ -4686,17 +4405,17 @@ function diffSurfaces(baseline, candidate, hints) {
4686
4405
  additions.push({ symbolPath: name, symbolType: "enum" });
4687
4406
  }
4688
4407
  }
4689
- for (const [path8, baseExports] of Object.entries(baseline.exports)) {
4690
- const candExports = candidate.exports[path8];
4408
+ for (const [path6, baseExports] of Object.entries(baseline.exports)) {
4409
+ const candExports = candidate.exports[path6];
4691
4410
  if (!candExports) {
4692
4411
  for (const exp of baseExports) {
4693
4412
  violations.push({
4694
4413
  category: "export-structure",
4695
4414
  severity: "warning",
4696
- symbolPath: `exports[${path8}].${exp}`,
4415
+ symbolPath: `exports[${path6}].${exp}`,
4697
4416
  baseline: exp,
4698
4417
  candidate: "(missing)",
4699
- message: `Export "${exp}" from "${path8}" not found in generated output`
4418
+ message: `Export "${exp}" from "${path6}" not found in generated output`
4700
4419
  });
4701
4420
  }
4702
4421
  continue;
@@ -4707,10 +4426,10 @@ function diffSurfaces(baseline, candidate, hints) {
4707
4426
  violations.push({
4708
4427
  category: "export-structure",
4709
4428
  severity: "warning",
4710
- symbolPath: `exports[${path8}].${exp}`,
4429
+ symbolPath: `exports[${path6}].${exp}`,
4711
4430
  baseline: exp,
4712
4431
  candidate: "(missing)",
4713
- message: `Export "${exp}" from "${path8}" not found in generated output`
4432
+ message: `Export "${exp}" from "${path6}" not found in generated output`
4714
4433
  });
4715
4434
  }
4716
4435
  }
@@ -4764,8 +4483,8 @@ async function runCompatCheck(baseline, outputDir, lang, spec) {
4764
4483
  }
4765
4484
 
4766
4485
  // src/verify/run-overlay-retry-loop.ts
4767
- import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, unlinkSync, statSync, openSync, readSync, closeSync } from "fs";
4768
- import * as path5 from "path";
4486
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, unlinkSync, statSync, openSync, readSync, closeSync } from "fs";
4487
+ import * as path3 from "path";
4769
4488
  var PATCHABLE_CATEGORIES = /* @__PURE__ */ new Set(["public-api", "export-structure"]);
4770
4489
  var GENERATED_MARKERS = ["Code generated by oagen", "auto-generated by oagen", "This file is auto-generated"];
4771
4490
  function cleanGeneratedFiles(dir) {
@@ -4776,7 +4495,7 @@ function cleanGeneratedFiles(dir) {
4776
4495
  return;
4777
4496
  }
4778
4497
  for (const entry of entries) {
4779
- const full = path5.join(dir, entry);
4498
+ const full = path3.join(dir, entry);
4780
4499
  let stat;
4781
4500
  try {
4782
4501
  stat = statSync(full);
@@ -4805,8 +4524,8 @@ function cleanGeneratedFiles(dir) {
4805
4524
  }
4806
4525
  }
4807
4526
  function loadManifestFromDir(outputDir) {
4808
- const manifestPath = path5.join(outputDir, "smoke-manifest.json");
4809
- if (!existsSync3(manifestPath)) return void 0;
4527
+ const manifestPath = path3.join(outputDir, "smoke-manifest.json");
4528
+ if (!existsSync2(manifestPath)) return void 0;
4810
4529
  try {
4811
4530
  const parsed = JSON.parse(readFileSync2(manifestPath, "utf-8"));
4812
4531
  if (Array.isArray(parsed)) return parsed;
@@ -4994,17 +4713,17 @@ function runStalenessCheck(baseline, oldSpec, newSpec, lang) {
4994
4713
 
4995
4714
  // src/verify/run-smoke-check.ts
4996
4715
  import { execFileSync } from "child_process";
4997
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
4716
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
4998
4717
  import { fileURLToPath } from "url";
4999
- import * as path6 from "path";
4718
+ import * as path4 from "path";
5000
4719
  var __filename = fileURLToPath(import.meta.url);
5001
- var __dirname = path6.dirname(__filename);
4720
+ var __dirname = path4.dirname(__filename);
5002
4721
  function resolveScript(scriptRelPath) {
5003
- const distScript = path6.resolve(__dirname, "..", "..", "dist", "scripts", scriptRelPath.replace(/\.ts$/, ".js"));
5004
- if (existsSync4(distScript)) {
4722
+ const distScript = path4.resolve(__dirname, "..", "..", "dist", "scripts", scriptRelPath.replace(/\.ts$/, ".js"));
4723
+ if (existsSync3(distScript)) {
5005
4724
  return { bin: "node", script: distScript };
5006
4725
  }
5007
- const srcScript = path6.resolve(__dirname, "..", "..", "scripts", scriptRelPath);
4726
+ const srcScript = path4.resolve(__dirname, "..", "..", "scripts", scriptRelPath);
5008
4727
  return { bin: "npx", script: srcScript };
5009
4728
  }
5010
4729
  function runPackagedScript(scriptRelPath, args) {
@@ -5016,7 +4735,7 @@ function runSmokeCheck(opts) {
5016
4735
  const { spec, lang, output, rawResults, smokeConfig, smokeRunner } = opts;
5017
4736
  let baselinePath = rawResults ?? "smoke-results-raw.json";
5018
4737
  let generatedBaseline = false;
5019
- if (!rawResults && !existsSync4("smoke-results-raw.json")) {
4738
+ if (!rawResults && !existsSync3("smoke-results-raw.json")) {
5020
4739
  if (!spec) {
5021
4740
  throw new CommandError(
5022
4741
  "error: --spec <path> or OPENAPI_SPEC_PATH env var is required when no raw baseline exists",
@@ -5045,10 +4764,10 @@ function runSmokeCheck(opts) {
5045
4764
  }
5046
4765
  return { passed: true, baselinePath, generatedBaseline };
5047
4766
  } catch {
5048
- if (existsSync4("smoke-compile-errors.json")) {
4767
+ if (existsSync3("smoke-compile-errors.json")) {
5049
4768
  return { passed: false, compileErrors: true, baselinePath, generatedBaseline };
5050
4769
  }
5051
- const findingsCount = existsSync4("smoke-diff-findings.json") ? JSON.parse(readFileSync3("smoke-diff-findings.json", "utf-8")).length : void 0;
4770
+ const findingsCount = existsSync3("smoke-diff-findings.json") ? JSON.parse(readFileSync3("smoke-diff-findings.json", "utf-8")).length : void 0;
5052
4771
  return { passed: false, findingsCount, baselinePath, generatedBaseline };
5053
4772
  }
5054
4773
  }
@@ -5206,7 +4925,7 @@ ${separator}`);
5206
4925
  }
5207
4926
  stepNum++;
5208
4927
  }
5209
- const needsBaselineStep = !rawResults && !existsSync5("smoke-results-raw.json");
4928
+ const needsBaselineStep = !rawResults && !existsSync4("smoke-results-raw.json");
5210
4929
  if (needsBaselineStep) {
5211
4930
  console.log(`
5212
4931
  ${separator}`);
@@ -5259,7 +4978,7 @@ Remediation guide (by finding type):
5259
4978
 
5260
4979
  // src/cli/init.ts
5261
4980
  import { resolve as resolve3, relative } from "path";
5262
- import { existsSync as existsSync6, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync5 } from "fs";
4981
+ import { existsSync as existsSync5, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync5 } from "fs";
5263
4982
 
5264
4983
  // src/cli/templates/init.ts
5265
4984
  function oagenScripts(lang) {
@@ -5425,7 +5144,7 @@ export const ${varName}: Emitter = {
5425
5144
  async function initCommand(opts) {
5426
5145
  const projectDir = resolve3(opts.project ?? ".");
5427
5146
  const lang = opts.lang;
5428
- if (existsSync6(resolve3(projectDir, "oagen.config.ts"))) {
5147
+ if (existsSync5(resolve3(projectDir, "oagen.config.ts"))) {
5429
5148
  throw new Error("Project already initialized \u2014 oagen.config.ts exists");
5430
5149
  }
5431
5150
  const dirs = [`src/${lang}`, "test", "smoke", "docs/sdk-architecture"];
@@ -5442,7 +5161,7 @@ async function initCommand(opts) {
5442
5161
  ];
5443
5162
  const packageJsonPath = resolve3(projectDir, "package.json");
5444
5163
  let packageJsonContent;
5445
- if (existsSync6(packageJsonPath)) {
5164
+ if (existsSync5(packageJsonPath)) {
5446
5165
  const existingContent = readFileSync5(packageJsonPath, "utf-8");
5447
5166
  const existingPkg = JSON.parse(existingContent);
5448
5167
  const newPkg = JSON.parse(packageJson(lang));
@@ -5467,7 +5186,7 @@ async function initCommand(opts) {
5467
5186
  }
5468
5187
  const gitignorePath = resolve3(projectDir, ".gitignore");
5469
5188
  let gitignoreContent;
5470
- if (existsSync6(gitignorePath)) {
5189
+ if (existsSync5(gitignorePath)) {
5471
5190
  const existingContent = readFileSync5(gitignorePath, "utf-8");
5472
5191
  const newContent = gitignore();
5473
5192
  const existingLines = new Set(
@@ -5505,14 +5224,14 @@ async function initCommand(opts) {
5505
5224
  }
5506
5225
 
5507
5226
  // src/cli/config-loader.ts
5508
- import * as path7 from "path";
5509
- import { existsSync as existsSync7 } from "fs";
5227
+ import * as path5 from "path";
5228
+ import { existsSync as existsSync6 } from "fs";
5510
5229
  import { pathToFileURL as pathToFileURL2 } from "url";
5511
5230
  var CONFIG_NAMES = ["oagen.config.ts", "oagen.config.js", "oagen.config.mjs"];
5512
5231
  async function loadConfig(cwd = process.cwd()) {
5513
5232
  for (const name of CONFIG_NAMES) {
5514
- const configPath = path7.resolve(cwd, name);
5515
- if (!existsSync7(configPath)) continue;
5233
+ const configPath = path5.resolve(cwd, name);
5234
+ if (!existsSync6(configPath)) continue;
5516
5235
  try {
5517
5236
  const mod = await import(pathToFileURL2(configPath).href);
5518
5237
  const config = mod.default ?? mod;
@@ -5578,7 +5297,7 @@ program.command("generate").description("Run a registered emitter against an Ope
5578
5297
  handleError
5579
5298
  );
5580
5299
  });
5581
- program.command("diff").description("Incrementally generate from spec changes").requiredOption("--old <path>", "Path to old spec").requiredOption("--new <path>", "Path to new spec").option("--lang <language>", "Target language (required unless --report)").option("--output <dir>", "Output directory").option("--report", "Output diff report as JSON").option("--force", "Allow file deletions without confirmation").option("--target <dir>", "Target directory for live SDK integration (merged output)").option("--api-surface <path>", "Path to baseline API surface JSON for compat overlay").option("--manifest <path>", "Path to smoke-manifest.json for method overlay").action((opts) => {
5300
+ program.command("diff").description("Compare two OpenAPI specs and output a diff report").requiredOption("--old <path>", "Path to old spec").requiredOption("--new <path>", "Path to new spec").action((opts) => {
5582
5301
  diffCommand({ ...opts, operationIdTransform: configOperationIdTransform, docUrl: configDocUrl }).catch(handleError);
5583
5302
  });
5584
5303
  program.command("extract").description("Extract public API surface from a live SDK").requiredOption("--sdk-path <path>", "Path to the live SDK").requiredOption("--lang <language>", "Target language").option("--output <path>", "Output file path").action((opts) => {