@workos/oagen 0.21.0 → 0.21.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.
@@ -433,7 +433,7 @@ function qualifyInlineModelName(baseName, parentName) {
433
433
  function extractInlineModelsFromSchemas(schemas) {
434
434
  if (!schemas) return [];
435
435
  const inlineModels = [];
436
- for (const [schemaName, schema] of Object.entries(schemas)) extractInlineModelsFromProperties(schema, inlineModels, cleanSchemaName(toPascalCase(schemaName)));
436
+ for (const [schemaName, schema] of Object.entries(schemas)) extractInlineModelsFromProperties(schema, inlineModels, resolveSchemaName(schemaName));
437
437
  return inlineModels;
438
438
  }
439
439
  function extractInlineModelsFromProperties(schema, results, parentName) {
@@ -2617,6 +2617,27 @@ function collapseJsonSuffixModels(models, services) {
2617
2617
  rewriteModelRefs(normalizedModels, services, jsonModel.name, baseModel.name);
2618
2618
  console.warn(`[oagen] Merged "${jsonModel.name}" into "${baseModel.name}" (${jsonModel.fields.length} fields, superset)`);
2619
2619
  }
2620
+ for (const { jsonModel, baseModel } of mergeCandidates) {
2621
+ const jsonPrefix = jsonModel.name;
2622
+ const basePrefix = baseModel.name;
2623
+ for (const model of normalizedModels) {
2624
+ if (toRemove.has(model.name)) continue;
2625
+ if (model.name.length <= jsonPrefix.length) continue;
2626
+ if (!model.name.startsWith(jsonPrefix)) continue;
2627
+ const newName = basePrefix + model.name.slice(jsonPrefix.length);
2628
+ const existing = byName.get(newName);
2629
+ if (existing && !toRemove.has(existing.name)) {
2630
+ toRemove.add(model.name);
2631
+ rewriteModelRefs(normalizedModels, services, model.name, newName);
2632
+ console.warn(`[oagen] Collapsed child "${model.name}" into existing "${newName}"`);
2633
+ } else {
2634
+ rewriteModelRefs(normalizedModels, services, model.name, newName);
2635
+ model.name = newName;
2636
+ byName.set(newName, model);
2637
+ console.warn(`[oagen] Renamed child "${jsonPrefix}…" → "${newName}"`);
2638
+ }
2639
+ }
2640
+ }
2620
2641
  return normalizedModels.filter((m) => !toRemove.has(m.name));
2621
2642
  }
2622
2643
  //#endregion
@@ -2669,8 +2690,10 @@ async function parseSpec(specPath, options) {
2669
2690
  if (!version.startsWith("3.")) throw new SpecParseError(`Unsupported OpenAPI version: ${version}. oagen requires OpenAPI 3.x`, `Update the spec to OpenAPI 3.0 or 3.1. If you are using Swagger 2.x, convert it first with \`npx swagger2openapi ${specPath}\`.`);
2670
2691
  const { models, enums } = extractSchemas(spec.components?.schemas, { schemaNameTransform: options?.schemaNameTransform });
2671
2692
  const { services, inlineModels } = extractOperations(spec.paths, options?.operationIdTransform, spec.components?.schemas);
2693
+ const responseNormalizedModels = mergeInlineResponseModels(models, inlineModels);
2694
+ const fieldInlineModels = extractInlineModelsFromSchemas(spec.components?.schemas);
2672
2695
  clearSchemaNameTransform();
2673
- const finalModels = collapseJsonSuffixModels(mergeFieldInlineModels(mergeInlineResponseModels(models, inlineModels), extractInlineModelsFromSchemas(spec.components?.schemas)), services);
2696
+ const finalModels = collapseJsonSuffixModels(mergeFieldInlineModels(responseNormalizedModels, fieldInlineModels), services);
2674
2697
  collectInlineEnumsFromModels(finalModels, enums);
2675
2698
  collectInlineEnumsFromOperations(services, enums);
2676
2699
  const auth = extractAuthSchemes(spec.components?.securitySchemes);
@@ -4279,10 +4302,10 @@ async function extractClassEndLines(source, language) {
4279
4302
  const tree = safeParse(await getParser(language), source);
4280
4303
  const map = /* @__PURE__ */ new Map();
4281
4304
  function processClassNode(node) {
4282
- const nameNode = node.childForFieldName("name");
4305
+ const nameNode = node.childForFieldName("name") ?? node.children.find((c) => c.type === "type_identifier" || c.type === "identifier");
4283
4306
  if (!nameNode) return;
4284
4307
  const name = nameNode.text;
4285
- const body = node.childForFieldName("body");
4308
+ const body = node.childForFieldName("body") ?? node.children.find((c) => c.type === "class_body" || c.type === "body");
4286
4309
  const endRow = body ? body.endPosition.row : node.endPosition.row;
4287
4310
  if (source[body ? body.endIndex - 1 : node.endIndex - 1] === "}") map.set(name, { bodyEndLine: endRow });
4288
4311
  else map.set(name, { bodyEndLine: endRow + 1 });
@@ -4831,9 +4854,16 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
4831
4854
  if (!existingLines[i].includes("@oagen-ignore-start")) continue;
4832
4855
  const startIdx = i;
4833
4856
  while (i < existingLines.length && !existingLines[i].includes("@oagen-ignore-end")) i++;
4857
+ let anchor = "";
4858
+ for (let a = i + 1; a < existingLines.length; a++) if (existingLines[a].trim()) {
4859
+ anchor = existingLines[a].trimStart();
4860
+ break;
4861
+ }
4834
4862
  blocks.push({
4835
4863
  containingClass: findContainingClass(existingLines, startIdx),
4836
- lines: existingLines.slice(startIdx, i + 1)
4864
+ containingScope: findContainingDescribeScope(existingLines, startIdx),
4865
+ lines: existingLines.slice(startIdx, i + 1),
4866
+ anchor
4837
4867
  });
4838
4868
  }
4839
4869
  if (blocks.length === 0) return generatedContent;
@@ -4843,15 +4873,19 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
4843
4873
  const blocksByClass = /* @__PURE__ */ new Map();
4844
4874
  for (const b of blocks) {
4845
4875
  if (!blocksByClass.has(b.containingClass)) blocksByClass.set(b.containingClass, []);
4846
- blocksByClass.get(b.containingClass).push(b.lines);
4876
+ blocksByClass.get(b.containingClass).push({
4877
+ containingScope: b.containingScope,
4878
+ lines: b.lines,
4879
+ anchor: b.anchor
4880
+ });
4847
4881
  }
4848
4882
  const replacedClasses = /* @__PURE__ */ new Set();
4849
4883
  const handledBlockKeys = /* @__PURE__ */ new Set();
4850
4884
  const replacements = [];
4851
4885
  for (const [className, classBlocks] of blocksByClass) {
4852
4886
  let targetClass = className;
4853
- if (!targetClass) for (const blockLines of classBlocks) {
4854
- for (const l of blockLines) {
4887
+ if (!targetClass) for (const entry of classBlocks) {
4888
+ for (const l of entry.lines) {
4855
4889
  const m = l.match(/^(?:export\s+)?class\s+(\w+)/);
4856
4890
  if (m && classEndLines.has(m[1])) {
4857
4891
  targetClass = m[1];
@@ -4861,7 +4895,7 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
4861
4895
  if (targetClass) break;
4862
4896
  }
4863
4897
  if (!targetClass) continue;
4864
- if (!classBlocks.some((blockLines) => blockLines.some((l) => {
4898
+ if (!classBlocks.some((entry) => entry.lines.some((l) => {
4865
4899
  return l.match(/^(?:export\s+)?class\s+(\w+)/)?.[1] === targetClass;
4866
4900
  }))) continue;
4867
4901
  const info = classEndLines.get(targetClass);
@@ -4869,9 +4903,9 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
4869
4903
  const startLine = findClassDeclarationStart(result, targetClass, info.bodyEndLine);
4870
4904
  if (startLine < 0) continue;
4871
4905
  const content = [];
4872
- for (const block of classBlocks) {
4906
+ for (const entry of classBlocks) {
4873
4907
  content.push("");
4874
- content.push(...block);
4908
+ content.push(...entry.lines);
4875
4909
  }
4876
4910
  replacements.push({
4877
4911
  startLine,
@@ -4886,22 +4920,46 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
4886
4920
  let effectiveClassEndLines = classEndLines;
4887
4921
  if (replacedClasses.size > 0) effectiveClassEndLines = await extractClassEndLines(result.join("\n"), language);
4888
4922
  const insertions = [];
4923
+ let insertionOrder = 0;
4889
4924
  for (const [className, classBlocks] of blocksByClass) {
4890
4925
  if (handledBlockKeys.has(className) || replacedClasses.has(className)) continue;
4891
4926
  const info = className ? effectiveClassEndLines.get(className) : void 0;
4892
- const insertLine = info ? info.bodyEndLine : result.length;
4893
- const content = [];
4894
- for (const block of classBlocks) {
4895
- content.push("");
4896
- content.push(...block);
4927
+ for (const entry of classBlocks) {
4928
+ let insertLine;
4929
+ if (entry.anchor && !entry.anchor.includes("@oagen-ignore") && /[a-zA-Z]|^\/\*|^\/\//.test(entry.anchor)) {
4930
+ for (let li = 0; li < result.length; li++) if (result[li].trimStart() === entry.anchor) {
4931
+ insertLine = li;
4932
+ break;
4933
+ }
4934
+ }
4935
+ if (insertLine === void 0) if (info) insertLine = info.bodyEndLine;
4936
+ else {
4937
+ const scopeEndLine = entry.containingScope ? findDescribeScopeEndLine(result, entry.containingScope) : void 0;
4938
+ const blockHasImportsFallback = entry.lines.some((l) => /^\s*(import|from)\s/.test(l));
4939
+ if (scopeEndLine !== void 0) insertLine = scopeEndLine;
4940
+ else if (blockHasImportsFallback || hasTopLevelDeclaration(entry.lines)) {
4941
+ let lastImportLine = 0;
4942
+ for (let li = 0; li < result.length; li++) {
4943
+ const trimmed = result[li].trimStart();
4944
+ if (/^(import|from)\s/.test(trimmed)) lastImportLine = li + 1;
4945
+ }
4946
+ insertLine = lastImportLine;
4947
+ } else insertLine = result.length;
4948
+ }
4949
+ if (entry.lines.some((l) => /^\s*import\s/.test(l))) while (insertLine >= 2 && result[insertLine - 1]?.trim() === "" && /^\s*(import|using|from)\s/.test(result[insertLine - 2] ?? "")) insertLine--;
4950
+ const prevLine = insertLine > 0 ? result[insertLine - 1]?.trimStart() ?? "" : "";
4951
+ const blockContent = /^import\s/.test(prevLine) || /^using\s/.test(prevLine) || /^from\s/.test(prevLine) || insertLine > 0 && prevLine === "" ? [...entry.lines] : ["", ...entry.lines];
4952
+ if ((result[insertLine]?.trimStart() ?? "").startsWith("///")) blockContent.push("");
4953
+ insertions.push({
4954
+ line: insertLine,
4955
+ order: insertionOrder++,
4956
+ content: blockContent
4957
+ });
4897
4958
  }
4898
- insertions.push({
4899
- line: insertLine,
4900
- content
4901
- });
4902
4959
  }
4903
- insertions.sort((a, b) => b.line - a.line);
4960
+ insertions.sort((a, b) => b.line - a.line || b.order - a.order);
4904
4961
  for (const ins of insertions) result.splice(ins.line, 0, ...ins.content);
4962
+ removeDuplicateImportsCoveredByIgnoreBlocks(result);
4905
4963
  return result.join("\n");
4906
4964
  }
4907
4965
  /**
@@ -4929,12 +4987,47 @@ function stripIgnoreRegions(lines) {
4929
4987
  }
4930
4988
  /** Scan backwards from `lineIdx` to find the nearest class declaration name. */
4931
4989
  function findContainingClass(lines, lineIdx) {
4990
+ if (/^\S/.test(lines[lineIdx] ?? "")) return "";
4932
4991
  for (let j = lineIdx - 1; j >= 0; j--) {
4933
- const m = lines[j].match(/^\s*(?:export\s+)?class\s+(\w+)/);
4992
+ const m = lines[j].match(/^\s*(?:(?:export|public|private|protected|internal|abstract|sealed|static|partial)\s+)*class\s+(\w+)/);
4934
4993
  if (m) return m[1];
4935
4994
  }
4936
4995
  return "";
4937
4996
  }
4997
+ function findContainingDescribeScope(lines, lineIdx) {
4998
+ const markerLine = lines[lineIdx] ?? "";
4999
+ if (/^\S/.test(markerLine)) return "";
5000
+ const markerIndent = leadingSpaces(markerLine);
5001
+ for (let j = lineIdx - 1; j >= 0; j--) {
5002
+ const line = lines[j];
5003
+ if (leadingSpaces(line) >= markerIndent) continue;
5004
+ const match = line.match(/^\s*describe\((['"`])(.+?)\1\s*,/);
5005
+ if (match) return match[2];
5006
+ if (/^\s*(?:export\s+)?class\s+\w+/.test(line)) return "";
5007
+ }
5008
+ return "";
5009
+ }
5010
+ function findDescribeScopeEndLine(lines, scopeName) {
5011
+ for (let i = 0; i < lines.length; i++) {
5012
+ const match = lines[i].match(/^\s*describe\((['"`])(.+?)\1\s*,/);
5013
+ if (!match || match[2] !== scopeName) continue;
5014
+ let depth = 0;
5015
+ for (let j = i; j < lines.length; j++) {
5016
+ depth += countChar(lines[j], "{");
5017
+ depth -= countChar(lines[j], "}");
5018
+ if (j > i && depth <= 0) return j;
5019
+ }
5020
+ }
5021
+ }
5022
+ function leadingSpaces(line) {
5023
+ return line.match(/^\s*/)?.[0].length ?? 0;
5024
+ }
5025
+ function countChar(line, char) {
5026
+ return [...line].filter((c) => c === char).length;
5027
+ }
5028
+ function hasTopLevelDeclaration(lines) {
5029
+ return lines.some((line) => /^(?:export\s+)?(?:interface|type|class|function|const|let|var|enum)\s+\w+/.test(line.trim()));
5030
+ }
4938
5031
  /**
4939
5032
  * Find the start line of a class declaration (including decorators above it)
4940
5033
  * by scanning backwards from `beforeLine`.
@@ -5012,9 +5105,13 @@ function collectTsImports(lines) {
5012
5105
  const mod = m[4];
5013
5106
  if (defaultName) out.defaults.set(mod, defaultName);
5014
5107
  const names = namesRaw.split(",").map((s) => s.trim().replace(/\s+as\s+\w+$/, "")).filter(Boolean);
5015
- const target = isTypeOnly ? out.typeOnly : out.named;
5016
- if (!target.has(mod)) target.set(mod, /* @__PURE__ */ new Set());
5017
- for (const n of names) target.get(mod).add(n);
5108
+ for (const rawName of names) {
5109
+ const isInlineTypeOnly = rawName.startsWith("type ");
5110
+ const name = rawName.replace(/^type\s+/, "");
5111
+ const target = isTypeOnly || isInlineTypeOnly ? out.typeOnly : out.named;
5112
+ if (!target.has(mod)) target.set(mod, /* @__PURE__ */ new Set());
5113
+ target.get(mod).add(name);
5114
+ }
5018
5115
  continue;
5019
5116
  }
5020
5117
  m = normalized.match(/^import\s+(?:type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]$/);
@@ -5037,12 +5134,99 @@ function collectTsImports(lines) {
5037
5134
  *
5038
5135
  * Mutates `resultLines` in place.
5039
5136
  */
5137
+ function filterOutIgnoreRegions(lines) {
5138
+ const out = [];
5139
+ let skipping = false;
5140
+ for (const line of lines) {
5141
+ if (!skipping && line.includes("@oagen-ignore-start")) {
5142
+ skipping = true;
5143
+ continue;
5144
+ }
5145
+ if (skipping) {
5146
+ if (line.includes("@oagen-ignore-end")) skipping = false;
5147
+ continue;
5148
+ }
5149
+ out.push(line);
5150
+ }
5151
+ return out;
5152
+ }
5153
+ function collectIgnoreRegionUsageText(lines) {
5154
+ const out = [];
5155
+ let skipping = false;
5156
+ for (const line of lines) {
5157
+ if (!skipping && line.includes("@oagen-ignore-start")) {
5158
+ skipping = true;
5159
+ continue;
5160
+ }
5161
+ if (!skipping) continue;
5162
+ if (/^\s*(import|from)\s/.test(line)) continue;
5163
+ out.push(line);
5164
+ if (line.includes("@oagen-ignore-end")) skipping = false;
5165
+ }
5166
+ return out.join("\n");
5167
+ }
5168
+ function removeDuplicateImportsCoveredByIgnoreBlocks(lines) {
5169
+ const ignoredImports = /* @__PURE__ */ new Set();
5170
+ const importStatements = collectImportStatements(lines);
5171
+ for (const stmt of importStatements) if (stmt.inIgnore) ignoredImports.add(normalizeImportStatement(stmt.lines));
5172
+ if (ignoredImports.size === 0) return;
5173
+ for (const stmt of [...importStatements].reverse()) {
5174
+ if (stmt.inIgnore) continue;
5175
+ if (ignoredImports.has(normalizeImportStatement(stmt.lines))) lines.splice(stmt.start, stmt.end - stmt.start + 1);
5176
+ }
5177
+ }
5178
+ function collectImportStatements(lines) {
5179
+ const statements = [];
5180
+ let inIgnore = false;
5181
+ for (let i = 0; i < lines.length; i++) {
5182
+ const line = lines[i];
5183
+ if (!inIgnore && line.includes("@oagen-ignore-start")) {
5184
+ inIgnore = true;
5185
+ continue;
5186
+ }
5187
+ if (inIgnore && line.includes("@oagen-ignore-end")) {
5188
+ inIgnore = false;
5189
+ continue;
5190
+ }
5191
+ if (!isImportLine(line)) continue;
5192
+ const start = i;
5193
+ let end = i;
5194
+ const statementLines = [line];
5195
+ if ((line.match(/[({]/g) ?? []).length > (line.match(/[)}]/g) ?? []).length) {
5196
+ while (end + 1 < lines.length) {
5197
+ end++;
5198
+ statementLines.push(lines[end]);
5199
+ if ((lines[end].match(/[)}]/g) ?? []).length > 0 && /\bfrom\b/.test(lines[end])) break;
5200
+ }
5201
+ i = end;
5202
+ }
5203
+ statements.push({
5204
+ start,
5205
+ end,
5206
+ lines: statementLines,
5207
+ inIgnore
5208
+ });
5209
+ }
5210
+ return statements;
5211
+ }
5212
+ function normalizeImportStatement(lines) {
5213
+ return lines.join("\n").replace(/,\s*}/g, " }").replace(/\s+/g, " ").trim();
5214
+ }
5215
+ function isImportLine(line) {
5216
+ const trimmed = line.trim();
5217
+ return /^import(\s|$)/.test(trimmed) || /^from\s+/.test(trimmed) || /^use\s+[\w\\]/.test(trimmed) || /^require(_relative)?\s+['"]/.test(trimmed);
5218
+ }
5219
+ function identifierUsed(source, identifier) {
5220
+ return new RegExp(`\\b${identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(source);
5221
+ }
5040
5222
  function spliceExtraImports(existingLines, resultLines) {
5223
+ const existingOutsideIgnore = filterOutIgnoreRegions(existingLines);
5224
+ const ignoreRegionText = collectIgnoreRegionUsageText(existingLines);
5041
5225
  const isPythonSimpleImport = (l) => /^import\s+\w/.test(l) && !/\sfrom\s/.test(l) && !/[{}]/.test(l);
5042
- const existingSimple = new Set(existingLines.filter(isPythonSimpleImport).map((l) => l.trim()));
5226
+ const existingSimple = new Set(existingOutsideIgnore.filter(isPythonSimpleImport).map((l) => l.trim()));
5043
5227
  const generatedSimple = new Set(resultLines.filter(isPythonSimpleImport).map((l) => l.trim()));
5044
5228
  const extraSimple = [...existingSimple].filter((l) => !generatedSimple.has(l));
5045
- const existingFrom = collectFromImports(existingLines);
5229
+ const existingFrom = collectFromImports(existingOutsideIgnore);
5046
5230
  const generatedFrom = collectFromImports(resultLines);
5047
5231
  const allGeneratedIds = /* @__PURE__ */ new Set();
5048
5232
  for (const ids of generatedFrom.values()) for (const id of ids) allGeneratedIds.add(id);
@@ -5057,29 +5241,33 @@ function spliceExtraImports(existingLines, resultLines) {
5057
5241
  if (extra.length > 0) extraFromLines.push(`from ${mod} import ${extra.join(", ")}`);
5058
5242
  }
5059
5243
  }
5060
- const existingTs = collectTsImports(existingLines);
5244
+ const existingTs = collectTsImports(existingOutsideIgnore);
5061
5245
  const generatedTs = collectTsImports(resultLines);
5062
5246
  const extraTsLines = [];
5063
5247
  for (const mod of existingTs.sideEffects) if (!generatedTs.sideEffects.has(mod)) extraTsLines.push(`import '${mod}';`);
5064
5248
  for (const [mod, name] of existingTs.defaults) {
5065
5249
  if (generatedTs.defaults.has(mod)) continue;
5250
+ if (!identifierUsed(ignoreRegionText, name)) continue;
5066
5251
  extraTsLines.push(`import ${name} from '${mod}';`);
5067
5252
  }
5068
- for (const [mod, name] of existingTs.namespaces) if (!generatedTs.namespaces.has(mod)) extraTsLines.push(`import * as ${name} from '${mod}';`);
5253
+ for (const [mod, name] of existingTs.namespaces) {
5254
+ if (!identifierUsed(ignoreRegionText, name)) continue;
5255
+ if (!generatedTs.namespaces.has(mod)) extraTsLines.push(`import * as ${name} from '${mod}';`);
5256
+ }
5069
5257
  for (const [mod, existIds] of existingTs.named) {
5070
5258
  const genIds = generatedTs.named.get(mod) ?? /* @__PURE__ */ new Set();
5071
5259
  const genTypeIds = generatedTs.typeOnly.get(mod) ?? /* @__PURE__ */ new Set();
5072
- const extra = [...existIds].filter((id) => !genIds.has(id) && !genTypeIds.has(id));
5260
+ const extra = [...existIds].filter((id) => !genIds.has(id) && !genTypeIds.has(id) && identifierUsed(ignoreRegionText, id));
5073
5261
  if (extra.length > 0) extraTsLines.push(`import { ${extra.join(", ")} } from '${mod}';`);
5074
5262
  }
5075
5263
  for (const [mod, existIds] of existingTs.typeOnly) {
5076
5264
  const genIds = generatedTs.typeOnly.get(mod) ?? /* @__PURE__ */ new Set();
5077
5265
  const genNamedIds = generatedTs.named.get(mod) ?? /* @__PURE__ */ new Set();
5078
- const extra = [...existIds].filter((id) => !genIds.has(id) && !genNamedIds.has(id));
5266
+ const extra = [...existIds].filter((id) => !genIds.has(id) && !genNamedIds.has(id) && identifierUsed(ignoreRegionText, id));
5079
5267
  if (extra.length > 0) extraTsLines.push(`import type { ${extra.join(", ")} } from '${mod}';`);
5080
5268
  }
5081
5269
  const isPhpUseImport = (l) => /^use\s+[\w\\]+\\[\w\\]*(\s+as\s+\w+)?\s*;?$/.test(l);
5082
- const existingPhpUse = new Set(existingLines.filter(isPhpUseImport).map((l) => l.trim()));
5270
+ const existingPhpUse = new Set(existingOutsideIgnore.filter(isPhpUseImport).map((l) => l.trim()));
5083
5271
  const generatedPhpUse = new Set(resultLines.filter(isPhpUseImport).map((l) => l.trim()));
5084
5272
  const extraPhpUse = [...existingPhpUse].filter((l) => !generatedPhpUse.has(l));
5085
5273
  const rubyRequireRe = /^(require(?:_relative)?)\s+['"]([^'"]+)['"]\s*$/;
@@ -5094,7 +5282,7 @@ function spliceExtraImports(existingLines, resultLines) {
5094
5282
  }
5095
5283
  const seenExistingKeys = /* @__PURE__ */ new Set();
5096
5284
  const extraRubyRequire = [];
5097
- for (const l of existingLines) {
5285
+ for (const l of existingOutsideIgnore) {
5098
5286
  const key = rubyRequireKey(l);
5099
5287
  if (!key) continue;
5100
5288
  if (generatedRubyRequireKeys.has(key)) continue;
@@ -5626,6 +5814,7 @@ async function readManifest(dir) {
5626
5814
  }
5627
5815
  /** Write `.oagen-manifest.json` to a directory with sorted paths. */
5628
5816
  async function writeManifest(dir, opts) {
5817
+ const manifestPath = path$1.join(dir, MANIFEST_FILENAME);
5629
5818
  const manifest = {
5630
5819
  version: MANIFEST_VERSION,
5631
5820
  language: opts.language,
@@ -5640,10 +5829,16 @@ async function writeManifest(dir, opts) {
5640
5829
  files: [...new Set(opts.files)].sort(),
5641
5830
  ...opts.operations !== void 0 ? { operations: opts.operations } : {}
5642
5831
  };
5643
- const manifestPath = path$1.join(dir, MANIFEST_FILENAME);
5832
+ const previous = await readManifest(dir);
5833
+ if (previous && sameManifestExceptGeneratedAt(previous, manifest)) manifest.generatedAt = previous.generatedAt;
5644
5834
  await fs.mkdir(dir, { recursive: true });
5645
5835
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
5646
5836
  }
5837
+ function sameManifestExceptGeneratedAt(a, b) {
5838
+ const { generatedAt: _aGeneratedAt, ...aComparable } = a;
5839
+ const { generatedAt: _bGeneratedAt, ...bComparable } = b;
5840
+ return JSON.stringify(aComparable) === JSON.stringify(bComparable);
5841
+ }
5647
5842
  /** Return the set of previous paths no longer present in the current emission. */
5648
5843
  function computeStalePaths(prev, currentPaths) {
5649
5844
  const current = new Set(currentPaths);