@workos/oagen 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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();
@@ -2768,22 +2766,6 @@ async function writeFiles(files, outputDir, options) {
2768
2766
  }
2769
2767
  if (language && hasGrammar(language)) {
2770
2768
  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
2769
  const mergeResult = await mergeIntoExisting(existingContent, file.content, language, header);
2788
2770
  let finalContent = mergeResult.content;
2789
2771
  if (header && !finalContent.startsWith(header)) {
@@ -2849,24 +2831,20 @@ function generateFiles(spec, emitter, options) {
2849
2831
  }
2850
2832
 
2851
2833
  // src/engine/integrate.ts
2852
- import * as fs2 from "fs";
2853
- import * as path2 from "path";
2854
- function mapFilesForTargetIntegration(files, language, targetDir) {
2834
+ function mapFilesForTargetIntegration(files, language) {
2855
2835
  const langPrefix = `${language}/`;
2856
2836
  return files.filter((f) => f.integrateTarget !== false).map((f) => {
2857
2837
  const stripped = f.path.startsWith(langPrefix) ? f.path.replace(langPrefix, "") : f.path;
2858
- const existsInTarget = targetDir ? fs2.existsSync(path2.join(targetDir, stripped)) : false;
2859
2838
  return {
2860
2839
  ...f,
2861
2840
  skipIfExists: false,
2862
2841
  // Always merge in target — never hard-skip
2863
- mergeMode: existsInTarget ? "docstring-only" : "full",
2864
2842
  path: stripped
2865
2843
  };
2866
2844
  });
2867
2845
  }
2868
2846
  async function integrateGeneratedFiles(opts) {
2869
- return writeFiles(mapFilesForTargetIntegration(opts.files, opts.language, opts.targetDir), opts.targetDir, {
2847
+ return writeFiles(mapFilesForTargetIntegration(opts.files, opts.language), opts.targetDir, {
2870
2848
  language: opts.language,
2871
2849
  header: opts.header
2872
2850
  });
@@ -2933,8 +2911,8 @@ function getEmitter(language) {
2933
2911
  }
2934
2912
 
2935
2913
  // src/cli/overlay-loader.ts
2936
- import { readFileSync, existsSync as existsSync2 } from "fs";
2937
- import * as path3 from "path";
2914
+ import { readFileSync, existsSync } from "fs";
2915
+ import * as path2 from "path";
2938
2916
 
2939
2917
  // src/compat/language-hints.ts
2940
2918
  var UNTYPED_MAP_PATTERNS = /* @__PURE__ */ new Set([
@@ -3157,8 +3135,8 @@ function buildOverlayLookup(surface, manifest, spec, hints, options) {
3157
3135
  for (const name of Object.keys(surface.typeAliases)) {
3158
3136
  lookup.typeAliasByName.set(name, name);
3159
3137
  }
3160
- for (const [path8, symbols] of Object.entries(surface.exports)) {
3161
- lookup.requiredExports.set(path8, new Set(symbols));
3138
+ for (const [path6, symbols] of Object.entries(surface.exports)) {
3139
+ lookup.requiredExports.set(path6, new Set(symbols));
3162
3140
  }
3163
3141
  for (const record of [surface.classes, surface.interfaces, surface.typeAliases, surface.enums]) {
3164
3142
  for (const [name, item] of Object.entries(record)) {
@@ -3325,11 +3303,11 @@ function patchOverlay(overlay, violations, baseline) {
3325
3303
  if (v.category === "export-structure") {
3326
3304
  const match = v.symbolPath.match(/^exports\[(.+?)\]\.(.+)$/);
3327
3305
  if (match) {
3328
- const [, path8, symbol] = match;
3329
- if (!patched.requiredExports.has(path8)) {
3330
- patched.requiredExports.set(path8, /* @__PURE__ */ new Set());
3306
+ const [, path6, symbol] = match;
3307
+ if (!patched.requiredExports.has(path6)) {
3308
+ patched.requiredExports.set(path6, /* @__PURE__ */ new Set());
3331
3309
  }
3332
- patched.requiredExports.get(path8).add(symbol);
3310
+ patched.requiredExports.get(path6).add(symbol);
3333
3311
  }
3334
3312
  }
3335
3313
  }
@@ -3374,8 +3352,8 @@ function loadOverlayContext(opts) {
3374
3352
  );
3375
3353
  }
3376
3354
  let manifest;
3377
- const resolvedManifestPath = opts.manifestPath ?? path3.join(opts.outputDir, "smoke-manifest.json");
3378
- if (opts.manifestPath || existsSync2(resolvedManifestPath)) {
3355
+ const resolvedManifestPath = opts.manifestPath ?? path2.join(opts.outputDir, "smoke-manifest.json");
3356
+ if (opts.manifestPath || existsSync(resolvedManifestPath)) {
3379
3357
  try {
3380
3358
  const parsed = JSON.parse(readFileSync(resolvedManifestPath, "utf-8"));
3381
3359
  if (Array.isArray(parsed)) {
@@ -3862,288 +3840,21 @@ function summarize(changes) {
3862
3840
  return { added, removed, modified, breaking, additive };
3863
3841
  }
3864
3842
 
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
3843
  // src/cli/diff.ts
4093
3844
  async function diffCommand(opts) {
4094
3845
  const parseOptions = { operationIdTransform: opts.operationIdTransform };
4095
- let [oldSpec, newSpec] = await Promise.all([
4096
- parseSpec(opts.old, parseOptions),
4097
- parseSpec(opts.new, parseOptions)
4098
- ]);
3846
+ let [oldSpec, newSpec] = await Promise.all([parseSpec(opts.old, parseOptions), parseSpec(opts.new, parseOptions)]);
4099
3847
  if (opts.docUrl) {
4100
3848
  oldSpec = expandDocUrls(oldSpec, opts.docUrl);
4101
3849
  newSpec = expandDocUrls(newSpec, opts.docUrl);
4102
3850
  }
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
- }
3851
+ const diff = diffSpecs(oldSpec, newSpec);
3852
+ console.log(JSON.stringify(diff, null, 2));
3853
+ throw new CommandError(
3854
+ "",
3855
+ "",
3856
+ diff.summary.breaking > 0 ? 2 : diff.summary.modified > 0 || diff.summary.removed > 0 ? 1 : diff.summary.added > 0 ? 1 : 0
3857
+ );
4147
3858
  }
4148
3859
 
4149
3860
  // src/cli/extract.ts
@@ -4158,7 +3869,7 @@ async function extractCommand(opts) {
4158
3869
  }
4159
3870
 
4160
3871
  // src/cli/verify.ts
4161
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
3872
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
4162
3873
 
4163
3874
  // src/compat/spec-filter.ts
4164
3875
  function specDerivedNames(spec, hints) {
@@ -4257,9 +3968,9 @@ function filterSurface(surface, allowedNames, opts) {
4257
3968
  let filteredClasses = filterRecord(surface.classes, allowedNames);
4258
3969
  if (methodPaths) {
4259
3970
  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));
3971
+ for (const path6 of methodPaths) {
3972
+ const dot = path6.indexOf(".");
3973
+ if (dot !== -1) serviceClassNames.add(path6.slice(0, dot));
4263
3974
  }
4264
3975
  const newClasses = {};
4265
3976
  for (const [name, cls] of Object.entries(filteredClasses)) {
@@ -4686,17 +4397,17 @@ function diffSurfaces(baseline, candidate, hints) {
4686
4397
  additions.push({ symbolPath: name, symbolType: "enum" });
4687
4398
  }
4688
4399
  }
4689
- for (const [path8, baseExports] of Object.entries(baseline.exports)) {
4690
- const candExports = candidate.exports[path8];
4400
+ for (const [path6, baseExports] of Object.entries(baseline.exports)) {
4401
+ const candExports = candidate.exports[path6];
4691
4402
  if (!candExports) {
4692
4403
  for (const exp of baseExports) {
4693
4404
  violations.push({
4694
4405
  category: "export-structure",
4695
4406
  severity: "warning",
4696
- symbolPath: `exports[${path8}].${exp}`,
4407
+ symbolPath: `exports[${path6}].${exp}`,
4697
4408
  baseline: exp,
4698
4409
  candidate: "(missing)",
4699
- message: `Export "${exp}" from "${path8}" not found in generated output`
4410
+ message: `Export "${exp}" from "${path6}" not found in generated output`
4700
4411
  });
4701
4412
  }
4702
4413
  continue;
@@ -4707,10 +4418,10 @@ function diffSurfaces(baseline, candidate, hints) {
4707
4418
  violations.push({
4708
4419
  category: "export-structure",
4709
4420
  severity: "warning",
4710
- symbolPath: `exports[${path8}].${exp}`,
4421
+ symbolPath: `exports[${path6}].${exp}`,
4711
4422
  baseline: exp,
4712
4423
  candidate: "(missing)",
4713
- message: `Export "${exp}" from "${path8}" not found in generated output`
4424
+ message: `Export "${exp}" from "${path6}" not found in generated output`
4714
4425
  });
4715
4426
  }
4716
4427
  }
@@ -4764,8 +4475,8 @@ async function runCompatCheck(baseline, outputDir, lang, spec) {
4764
4475
  }
4765
4476
 
4766
4477
  // 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";
4478
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, unlinkSync, statSync, openSync, readSync, closeSync } from "fs";
4479
+ import * as path3 from "path";
4769
4480
  var PATCHABLE_CATEGORIES = /* @__PURE__ */ new Set(["public-api", "export-structure"]);
4770
4481
  var GENERATED_MARKERS = ["Code generated by oagen", "auto-generated by oagen", "This file is auto-generated"];
4771
4482
  function cleanGeneratedFiles(dir) {
@@ -4776,7 +4487,7 @@ function cleanGeneratedFiles(dir) {
4776
4487
  return;
4777
4488
  }
4778
4489
  for (const entry of entries) {
4779
- const full = path5.join(dir, entry);
4490
+ const full = path3.join(dir, entry);
4780
4491
  let stat;
4781
4492
  try {
4782
4493
  stat = statSync(full);
@@ -4805,8 +4516,8 @@ function cleanGeneratedFiles(dir) {
4805
4516
  }
4806
4517
  }
4807
4518
  function loadManifestFromDir(outputDir) {
4808
- const manifestPath = path5.join(outputDir, "smoke-manifest.json");
4809
- if (!existsSync3(manifestPath)) return void 0;
4519
+ const manifestPath = path3.join(outputDir, "smoke-manifest.json");
4520
+ if (!existsSync2(manifestPath)) return void 0;
4810
4521
  try {
4811
4522
  const parsed = JSON.parse(readFileSync2(manifestPath, "utf-8"));
4812
4523
  if (Array.isArray(parsed)) return parsed;
@@ -4994,17 +4705,17 @@ function runStalenessCheck(baseline, oldSpec, newSpec, lang) {
4994
4705
 
4995
4706
  // src/verify/run-smoke-check.ts
4996
4707
  import { execFileSync } from "child_process";
4997
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
4708
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
4998
4709
  import { fileURLToPath } from "url";
4999
- import * as path6 from "path";
4710
+ import * as path4 from "path";
5000
4711
  var __filename = fileURLToPath(import.meta.url);
5001
- var __dirname = path6.dirname(__filename);
4712
+ var __dirname = path4.dirname(__filename);
5002
4713
  function resolveScript(scriptRelPath) {
5003
- const distScript = path6.resolve(__dirname, "..", "..", "dist", "scripts", scriptRelPath.replace(/\.ts$/, ".js"));
5004
- if (existsSync4(distScript)) {
4714
+ const distScript = path4.resolve(__dirname, "..", "..", "dist", "scripts", scriptRelPath.replace(/\.ts$/, ".js"));
4715
+ if (existsSync3(distScript)) {
5005
4716
  return { bin: "node", script: distScript };
5006
4717
  }
5007
- const srcScript = path6.resolve(__dirname, "..", "..", "scripts", scriptRelPath);
4718
+ const srcScript = path4.resolve(__dirname, "..", "..", "scripts", scriptRelPath);
5008
4719
  return { bin: "npx", script: srcScript };
5009
4720
  }
5010
4721
  function runPackagedScript(scriptRelPath, args) {
@@ -5016,7 +4727,7 @@ function runSmokeCheck(opts) {
5016
4727
  const { spec, lang, output, rawResults, smokeConfig, smokeRunner } = opts;
5017
4728
  let baselinePath = rawResults ?? "smoke-results-raw.json";
5018
4729
  let generatedBaseline = false;
5019
- if (!rawResults && !existsSync4("smoke-results-raw.json")) {
4730
+ if (!rawResults && !existsSync3("smoke-results-raw.json")) {
5020
4731
  if (!spec) {
5021
4732
  throw new CommandError(
5022
4733
  "error: --spec <path> or OPENAPI_SPEC_PATH env var is required when no raw baseline exists",
@@ -5045,10 +4756,10 @@ function runSmokeCheck(opts) {
5045
4756
  }
5046
4757
  return { passed: true, baselinePath, generatedBaseline };
5047
4758
  } catch {
5048
- if (existsSync4("smoke-compile-errors.json")) {
4759
+ if (existsSync3("smoke-compile-errors.json")) {
5049
4760
  return { passed: false, compileErrors: true, baselinePath, generatedBaseline };
5050
4761
  }
5051
- const findingsCount = existsSync4("smoke-diff-findings.json") ? JSON.parse(readFileSync3("smoke-diff-findings.json", "utf-8")).length : void 0;
4762
+ const findingsCount = existsSync3("smoke-diff-findings.json") ? JSON.parse(readFileSync3("smoke-diff-findings.json", "utf-8")).length : void 0;
5052
4763
  return { passed: false, findingsCount, baselinePath, generatedBaseline };
5053
4764
  }
5054
4765
  }
@@ -5206,7 +4917,7 @@ ${separator}`);
5206
4917
  }
5207
4918
  stepNum++;
5208
4919
  }
5209
- const needsBaselineStep = !rawResults && !existsSync5("smoke-results-raw.json");
4920
+ const needsBaselineStep = !rawResults && !existsSync4("smoke-results-raw.json");
5210
4921
  if (needsBaselineStep) {
5211
4922
  console.log(`
5212
4923
  ${separator}`);
@@ -5259,7 +4970,7 @@ Remediation guide (by finding type):
5259
4970
 
5260
4971
  // src/cli/init.ts
5261
4972
  import { resolve as resolve3, relative } from "path";
5262
- import { existsSync as existsSync6, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync5 } from "fs";
4973
+ import { existsSync as existsSync5, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync5 } from "fs";
5263
4974
 
5264
4975
  // src/cli/templates/init.ts
5265
4976
  function oagenScripts(lang) {
@@ -5425,7 +5136,7 @@ export const ${varName}: Emitter = {
5425
5136
  async function initCommand(opts) {
5426
5137
  const projectDir = resolve3(opts.project ?? ".");
5427
5138
  const lang = opts.lang;
5428
- if (existsSync6(resolve3(projectDir, "oagen.config.ts"))) {
5139
+ if (existsSync5(resolve3(projectDir, "oagen.config.ts"))) {
5429
5140
  throw new Error("Project already initialized \u2014 oagen.config.ts exists");
5430
5141
  }
5431
5142
  const dirs = [`src/${lang}`, "test", "smoke", "docs/sdk-architecture"];
@@ -5442,7 +5153,7 @@ async function initCommand(opts) {
5442
5153
  ];
5443
5154
  const packageJsonPath = resolve3(projectDir, "package.json");
5444
5155
  let packageJsonContent;
5445
- if (existsSync6(packageJsonPath)) {
5156
+ if (existsSync5(packageJsonPath)) {
5446
5157
  const existingContent = readFileSync5(packageJsonPath, "utf-8");
5447
5158
  const existingPkg = JSON.parse(existingContent);
5448
5159
  const newPkg = JSON.parse(packageJson(lang));
@@ -5467,7 +5178,7 @@ async function initCommand(opts) {
5467
5178
  }
5468
5179
  const gitignorePath = resolve3(projectDir, ".gitignore");
5469
5180
  let gitignoreContent;
5470
- if (existsSync6(gitignorePath)) {
5181
+ if (existsSync5(gitignorePath)) {
5471
5182
  const existingContent = readFileSync5(gitignorePath, "utf-8");
5472
5183
  const newContent = gitignore();
5473
5184
  const existingLines = new Set(
@@ -5505,14 +5216,14 @@ async function initCommand(opts) {
5505
5216
  }
5506
5217
 
5507
5218
  // src/cli/config-loader.ts
5508
- import * as path7 from "path";
5509
- import { existsSync as existsSync7 } from "fs";
5219
+ import * as path5 from "path";
5220
+ import { existsSync as existsSync6 } from "fs";
5510
5221
  import { pathToFileURL as pathToFileURL2 } from "url";
5511
5222
  var CONFIG_NAMES = ["oagen.config.ts", "oagen.config.js", "oagen.config.mjs"];
5512
5223
  async function loadConfig(cwd = process.cwd()) {
5513
5224
  for (const name of CONFIG_NAMES) {
5514
- const configPath = path7.resolve(cwd, name);
5515
- if (!existsSync7(configPath)) continue;
5225
+ const configPath = path5.resolve(cwd, name);
5226
+ if (!existsSync6(configPath)) continue;
5516
5227
  try {
5517
5228
  const mod = await import(pathToFileURL2(configPath).href);
5518
5229
  const config = mod.default ?? mod;
@@ -5578,7 +5289,7 @@ program.command("generate").description("Run a registered emitter against an Ope
5578
5289
  handleError
5579
5290
  );
5580
5291
  });
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) => {
5292
+ 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
5293
  diffCommand({ ...opts, operationIdTransform: configOperationIdTransform, docUrl: configDocUrl }).catch(handleError);
5583
5294
  });
5584
5295
  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) => {