@workos/oagen 0.21.0 → 0.22.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.
@@ -1697,10 +1697,10 @@ async function extractClassEndLines(source, language) {
1697
1697
  const tree = safeParse(await getParser(language), source);
1698
1698
  const map = /* @__PURE__ */ new Map();
1699
1699
  function processClassNode(node) {
1700
- const nameNode = node.childForFieldName("name");
1700
+ const nameNode = node.childForFieldName("name") ?? node.children.find((c) => c.type === "type_identifier" || c.type === "identifier");
1701
1701
  if (!nameNode) return;
1702
1702
  const name = nameNode.text;
1703
- const body = node.childForFieldName("body");
1703
+ const body = node.childForFieldName("body") ?? node.children.find((c) => c.type === "class_body" || c.type === "body");
1704
1704
  const endRow = body ? body.endPosition.row : node.endPosition.row;
1705
1705
  if (source[body ? body.endIndex - 1 : node.endIndex - 1] === "}") map.set(name, { bodyEndLine: endRow });
1706
1706
  else map.set(name, { bodyEndLine: endRow + 1 });
@@ -2249,9 +2249,16 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
2249
2249
  if (!existingLines[i].includes("@oagen-ignore-start")) continue;
2250
2250
  const startIdx = i;
2251
2251
  while (i < existingLines.length && !existingLines[i].includes("@oagen-ignore-end")) i++;
2252
+ let anchor = "";
2253
+ for (let a = i + 1; a < existingLines.length; a++) if (existingLines[a].trim()) {
2254
+ anchor = existingLines[a].trimStart();
2255
+ break;
2256
+ }
2252
2257
  blocks.push({
2253
2258
  containingClass: findContainingClass(existingLines, startIdx),
2254
- lines: existingLines.slice(startIdx, i + 1)
2259
+ containingScope: findContainingDescribeScope(existingLines, startIdx),
2260
+ lines: existingLines.slice(startIdx, i + 1),
2261
+ anchor
2255
2262
  });
2256
2263
  }
2257
2264
  if (blocks.length === 0) return generatedContent;
@@ -2261,15 +2268,19 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
2261
2268
  const blocksByClass = /* @__PURE__ */ new Map();
2262
2269
  for (const b of blocks) {
2263
2270
  if (!blocksByClass.has(b.containingClass)) blocksByClass.set(b.containingClass, []);
2264
- blocksByClass.get(b.containingClass).push(b.lines);
2271
+ blocksByClass.get(b.containingClass).push({
2272
+ containingScope: b.containingScope,
2273
+ lines: b.lines,
2274
+ anchor: b.anchor
2275
+ });
2265
2276
  }
2266
2277
  const replacedClasses = /* @__PURE__ */ new Set();
2267
2278
  const handledBlockKeys = /* @__PURE__ */ new Set();
2268
2279
  const replacements = [];
2269
2280
  for (const [className, classBlocks] of blocksByClass) {
2270
2281
  let targetClass = className;
2271
- if (!targetClass) for (const blockLines of classBlocks) {
2272
- for (const l of blockLines) {
2282
+ if (!targetClass) for (const entry of classBlocks) {
2283
+ for (const l of entry.lines) {
2273
2284
  const m = l.match(/^(?:export\s+)?class\s+(\w+)/);
2274
2285
  if (m && classEndLines.has(m[1])) {
2275
2286
  targetClass = m[1];
@@ -2279,7 +2290,7 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
2279
2290
  if (targetClass) break;
2280
2291
  }
2281
2292
  if (!targetClass) continue;
2282
- if (!classBlocks.some((blockLines) => blockLines.some((l) => {
2293
+ if (!classBlocks.some((entry) => entry.lines.some((l) => {
2283
2294
  return l.match(/^(?:export\s+)?class\s+(\w+)/)?.[1] === targetClass;
2284
2295
  }))) continue;
2285
2296
  const info = classEndLines.get(targetClass);
@@ -2287,9 +2298,9 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
2287
2298
  const startLine = findClassDeclarationStart(result, targetClass, info.bodyEndLine);
2288
2299
  if (startLine < 0) continue;
2289
2300
  const content = [];
2290
- for (const block of classBlocks) {
2301
+ for (const entry of classBlocks) {
2291
2302
  content.push("");
2292
- content.push(...block);
2303
+ content.push(...entry.lines);
2293
2304
  }
2294
2305
  replacements.push({
2295
2306
  startLine,
@@ -2304,22 +2315,46 @@ async function overwriteWithPreservedRegions(existingContent, generatedContent,
2304
2315
  let effectiveClassEndLines = classEndLines;
2305
2316
  if (replacedClasses.size > 0) effectiveClassEndLines = await extractClassEndLines(result.join("\n"), language);
2306
2317
  const insertions = [];
2318
+ let insertionOrder = 0;
2307
2319
  for (const [className, classBlocks] of blocksByClass) {
2308
2320
  if (handledBlockKeys.has(className) || replacedClasses.has(className)) continue;
2309
2321
  const info = className ? effectiveClassEndLines.get(className) : void 0;
2310
- const insertLine = info ? info.bodyEndLine : result.length;
2311
- const content = [];
2312
- for (const block of classBlocks) {
2313
- content.push("");
2314
- content.push(...block);
2322
+ for (const entry of classBlocks) {
2323
+ let insertLine;
2324
+ if (entry.anchor && !entry.anchor.includes("@oagen-ignore") && /[a-zA-Z]|^\/\*|^\/\//.test(entry.anchor)) {
2325
+ for (let li = 0; li < result.length; li++) if (result[li].trimStart() === entry.anchor) {
2326
+ insertLine = li;
2327
+ break;
2328
+ }
2329
+ }
2330
+ if (insertLine === void 0) if (info) insertLine = info.bodyEndLine;
2331
+ else {
2332
+ const scopeEndLine = entry.containingScope ? findDescribeScopeEndLine(result, entry.containingScope) : void 0;
2333
+ const blockHasImportsFallback = entry.lines.some((l) => /^\s*(import|from)\s/.test(l));
2334
+ if (scopeEndLine !== void 0) insertLine = scopeEndLine;
2335
+ else if (blockHasImportsFallback || hasTopLevelDeclaration(entry.lines)) {
2336
+ let lastImportLine = 0;
2337
+ for (let li = 0; li < result.length; li++) {
2338
+ const trimmed = result[li].trimStart();
2339
+ if (/^(import|from)\s/.test(trimmed)) lastImportLine = li + 1;
2340
+ }
2341
+ insertLine = lastImportLine;
2342
+ } else insertLine = result.length;
2343
+ }
2344
+ 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--;
2345
+ const prevLine = insertLine > 0 ? result[insertLine - 1]?.trimStart() ?? "" : "";
2346
+ const blockContent = /^import\s/.test(prevLine) || /^using\s/.test(prevLine) || /^from\s/.test(prevLine) || insertLine > 0 && prevLine === "" ? [...entry.lines] : ["", ...entry.lines];
2347
+ if ((result[insertLine]?.trimStart() ?? "").startsWith("///")) blockContent.push("");
2348
+ insertions.push({
2349
+ line: insertLine,
2350
+ order: insertionOrder++,
2351
+ content: blockContent
2352
+ });
2315
2353
  }
2316
- insertions.push({
2317
- line: insertLine,
2318
- content
2319
- });
2320
2354
  }
2321
- insertions.sort((a, b) => b.line - a.line);
2355
+ insertions.sort((a, b) => b.line - a.line || b.order - a.order);
2322
2356
  for (const ins of insertions) result.splice(ins.line, 0, ...ins.content);
2357
+ removeDuplicateImportsCoveredByIgnoreBlocks(result);
2323
2358
  return result.join("\n");
2324
2359
  }
2325
2360
  /**
@@ -2347,12 +2382,47 @@ function stripIgnoreRegions(lines) {
2347
2382
  }
2348
2383
  /** Scan backwards from `lineIdx` to find the nearest class declaration name. */
2349
2384
  function findContainingClass(lines, lineIdx) {
2385
+ if (/^\S/.test(lines[lineIdx] ?? "")) return "";
2350
2386
  for (let j = lineIdx - 1; j >= 0; j--) {
2351
- const m = lines[j].match(/^\s*(?:export\s+)?class\s+(\w+)/);
2387
+ const m = lines[j].match(/^\s*(?:(?:export|public|private|protected|internal|abstract|sealed|static|partial)\s+)*class\s+(\w+)/);
2352
2388
  if (m) return m[1];
2353
2389
  }
2354
2390
  return "";
2355
2391
  }
2392
+ function findContainingDescribeScope(lines, lineIdx) {
2393
+ const markerLine = lines[lineIdx] ?? "";
2394
+ if (/^\S/.test(markerLine)) return "";
2395
+ const markerIndent = leadingSpaces(markerLine);
2396
+ for (let j = lineIdx - 1; j >= 0; j--) {
2397
+ const line = lines[j];
2398
+ if (leadingSpaces(line) >= markerIndent) continue;
2399
+ const match = line.match(/^\s*describe\((['"`])(.+?)\1\s*,/);
2400
+ if (match) return match[2];
2401
+ if (/^\s*(?:export\s+)?class\s+\w+/.test(line)) return "";
2402
+ }
2403
+ return "";
2404
+ }
2405
+ function findDescribeScopeEndLine(lines, scopeName) {
2406
+ for (let i = 0; i < lines.length; i++) {
2407
+ const match = lines[i].match(/^\s*describe\((['"`])(.+?)\1\s*,/);
2408
+ if (!match || match[2] !== scopeName) continue;
2409
+ let depth = 0;
2410
+ for (let j = i; j < lines.length; j++) {
2411
+ depth += countChar(lines[j], "{");
2412
+ depth -= countChar(lines[j], "}");
2413
+ if (j > i && depth <= 0) return j;
2414
+ }
2415
+ }
2416
+ }
2417
+ function leadingSpaces(line) {
2418
+ return line.match(/^\s*/)?.[0].length ?? 0;
2419
+ }
2420
+ function countChar(line, char) {
2421
+ return [...line].filter((c) => c === char).length;
2422
+ }
2423
+ function hasTopLevelDeclaration(lines) {
2424
+ return lines.some((line) => /^(?:export\s+)?(?:interface|type|class|function|const|let|var|enum)\s+\w+/.test(line.trim()));
2425
+ }
2356
2426
  /**
2357
2427
  * Find the start line of a class declaration (including decorators above it)
2358
2428
  * by scanning backwards from `beforeLine`.
@@ -2430,9 +2500,13 @@ function collectTsImports(lines) {
2430
2500
  const mod = m[4];
2431
2501
  if (defaultName) out.defaults.set(mod, defaultName);
2432
2502
  const names = namesRaw.split(",").map((s) => s.trim().replace(/\s+as\s+\w+$/, "")).filter(Boolean);
2433
- const target = isTypeOnly ? out.typeOnly : out.named;
2434
- if (!target.has(mod)) target.set(mod, /* @__PURE__ */ new Set());
2435
- for (const n of names) target.get(mod).add(n);
2503
+ for (const rawName of names) {
2504
+ const isInlineTypeOnly = rawName.startsWith("type ");
2505
+ const name = rawName.replace(/^type\s+/, "");
2506
+ const target = isTypeOnly || isInlineTypeOnly ? out.typeOnly : out.named;
2507
+ if (!target.has(mod)) target.set(mod, /* @__PURE__ */ new Set());
2508
+ target.get(mod).add(name);
2509
+ }
2436
2510
  continue;
2437
2511
  }
2438
2512
  m = normalized.match(/^import\s+(?:type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]$/);
@@ -2455,12 +2529,99 @@ function collectTsImports(lines) {
2455
2529
  *
2456
2530
  * Mutates `resultLines` in place.
2457
2531
  */
2532
+ function filterOutIgnoreRegions(lines) {
2533
+ const out = [];
2534
+ let skipping = false;
2535
+ for (const line of lines) {
2536
+ if (!skipping && line.includes("@oagen-ignore-start")) {
2537
+ skipping = true;
2538
+ continue;
2539
+ }
2540
+ if (skipping) {
2541
+ if (line.includes("@oagen-ignore-end")) skipping = false;
2542
+ continue;
2543
+ }
2544
+ out.push(line);
2545
+ }
2546
+ return out;
2547
+ }
2548
+ function collectIgnoreRegionUsageText(lines) {
2549
+ const out = [];
2550
+ let skipping = false;
2551
+ for (const line of lines) {
2552
+ if (!skipping && line.includes("@oagen-ignore-start")) {
2553
+ skipping = true;
2554
+ continue;
2555
+ }
2556
+ if (!skipping) continue;
2557
+ if (/^\s*(import|from)\s/.test(line)) continue;
2558
+ out.push(line);
2559
+ if (line.includes("@oagen-ignore-end")) skipping = false;
2560
+ }
2561
+ return out.join("\n");
2562
+ }
2563
+ function removeDuplicateImportsCoveredByIgnoreBlocks(lines) {
2564
+ const ignoredImports = /* @__PURE__ */ new Set();
2565
+ const importStatements = collectImportStatements(lines);
2566
+ for (const stmt of importStatements) if (stmt.inIgnore) ignoredImports.add(normalizeImportStatement(stmt.lines));
2567
+ if (ignoredImports.size === 0) return;
2568
+ for (const stmt of [...importStatements].reverse()) {
2569
+ if (stmt.inIgnore) continue;
2570
+ if (ignoredImports.has(normalizeImportStatement(stmt.lines))) lines.splice(stmt.start, stmt.end - stmt.start + 1);
2571
+ }
2572
+ }
2573
+ function collectImportStatements(lines) {
2574
+ const statements = [];
2575
+ let inIgnore = false;
2576
+ for (let i = 0; i < lines.length; i++) {
2577
+ const line = lines[i];
2578
+ if (!inIgnore && line.includes("@oagen-ignore-start")) {
2579
+ inIgnore = true;
2580
+ continue;
2581
+ }
2582
+ if (inIgnore && line.includes("@oagen-ignore-end")) {
2583
+ inIgnore = false;
2584
+ continue;
2585
+ }
2586
+ if (!isImportLine(line)) continue;
2587
+ const start = i;
2588
+ let end = i;
2589
+ const statementLines = [line];
2590
+ if ((line.match(/[({]/g) ?? []).length > (line.match(/[)}]/g) ?? []).length) {
2591
+ while (end + 1 < lines.length) {
2592
+ end++;
2593
+ statementLines.push(lines[end]);
2594
+ if ((lines[end].match(/[)}]/g) ?? []).length > 0 && /\bfrom\b/.test(lines[end])) break;
2595
+ }
2596
+ i = end;
2597
+ }
2598
+ statements.push({
2599
+ start,
2600
+ end,
2601
+ lines: statementLines,
2602
+ inIgnore
2603
+ });
2604
+ }
2605
+ return statements;
2606
+ }
2607
+ function normalizeImportStatement(lines) {
2608
+ return lines.join("\n").replace(/,\s*}/g, " }").replace(/\s+/g, " ").trim();
2609
+ }
2610
+ function isImportLine(line) {
2611
+ const trimmed = line.trim();
2612
+ return /^import(\s|$)/.test(trimmed) || /^from\s+/.test(trimmed) || /^use\s+[\w\\]/.test(trimmed) || /^require(_relative)?\s+['"]/.test(trimmed);
2613
+ }
2614
+ function identifierUsed(source, identifier) {
2615
+ return new RegExp(`\\b${identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(source);
2616
+ }
2458
2617
  function spliceExtraImports(existingLines, resultLines) {
2618
+ const existingOutsideIgnore = filterOutIgnoreRegions(existingLines);
2619
+ const ignoreRegionText = collectIgnoreRegionUsageText(existingLines);
2459
2620
  const isPythonSimpleImport = (l) => /^import\s+\w/.test(l) && !/\sfrom\s/.test(l) && !/[{}]/.test(l);
2460
- const existingSimple = new Set(existingLines.filter(isPythonSimpleImport).map((l) => l.trim()));
2621
+ const existingSimple = new Set(existingOutsideIgnore.filter(isPythonSimpleImport).map((l) => l.trim()));
2461
2622
  const generatedSimple = new Set(resultLines.filter(isPythonSimpleImport).map((l) => l.trim()));
2462
2623
  const extraSimple = [...existingSimple].filter((l) => !generatedSimple.has(l));
2463
- const existingFrom = collectFromImports(existingLines);
2624
+ const existingFrom = collectFromImports(existingOutsideIgnore);
2464
2625
  const generatedFrom = collectFromImports(resultLines);
2465
2626
  const allGeneratedIds = /* @__PURE__ */ new Set();
2466
2627
  for (const ids of generatedFrom.values()) for (const id of ids) allGeneratedIds.add(id);
@@ -2475,29 +2636,33 @@ function spliceExtraImports(existingLines, resultLines) {
2475
2636
  if (extra.length > 0) extraFromLines.push(`from ${mod} import ${extra.join(", ")}`);
2476
2637
  }
2477
2638
  }
2478
- const existingTs = collectTsImports(existingLines);
2639
+ const existingTs = collectTsImports(existingOutsideIgnore);
2479
2640
  const generatedTs = collectTsImports(resultLines);
2480
2641
  const extraTsLines = [];
2481
2642
  for (const mod of existingTs.sideEffects) if (!generatedTs.sideEffects.has(mod)) extraTsLines.push(`import '${mod}';`);
2482
2643
  for (const [mod, name] of existingTs.defaults) {
2483
2644
  if (generatedTs.defaults.has(mod)) continue;
2645
+ if (!identifierUsed(ignoreRegionText, name)) continue;
2484
2646
  extraTsLines.push(`import ${name} from '${mod}';`);
2485
2647
  }
2486
- for (const [mod, name] of existingTs.namespaces) if (!generatedTs.namespaces.has(mod)) extraTsLines.push(`import * as ${name} from '${mod}';`);
2648
+ for (const [mod, name] of existingTs.namespaces) {
2649
+ if (!identifierUsed(ignoreRegionText, name)) continue;
2650
+ if (!generatedTs.namespaces.has(mod)) extraTsLines.push(`import * as ${name} from '${mod}';`);
2651
+ }
2487
2652
  for (const [mod, existIds] of existingTs.named) {
2488
2653
  const genIds = generatedTs.named.get(mod) ?? /* @__PURE__ */ new Set();
2489
2654
  const genTypeIds = generatedTs.typeOnly.get(mod) ?? /* @__PURE__ */ new Set();
2490
- const extra = [...existIds].filter((id) => !genIds.has(id) && !genTypeIds.has(id));
2655
+ const extra = [...existIds].filter((id) => !genIds.has(id) && !genTypeIds.has(id) && identifierUsed(ignoreRegionText, id));
2491
2656
  if (extra.length > 0) extraTsLines.push(`import { ${extra.join(", ")} } from '${mod}';`);
2492
2657
  }
2493
2658
  for (const [mod, existIds] of existingTs.typeOnly) {
2494
2659
  const genIds = generatedTs.typeOnly.get(mod) ?? /* @__PURE__ */ new Set();
2495
2660
  const genNamedIds = generatedTs.named.get(mod) ?? /* @__PURE__ */ new Set();
2496
- const extra = [...existIds].filter((id) => !genIds.has(id) && !genNamedIds.has(id));
2661
+ const extra = [...existIds].filter((id) => !genIds.has(id) && !genNamedIds.has(id) && identifierUsed(ignoreRegionText, id));
2497
2662
  if (extra.length > 0) extraTsLines.push(`import type { ${extra.join(", ")} } from '${mod}';`);
2498
2663
  }
2499
2664
  const isPhpUseImport = (l) => /^use\s+[\w\\]+\\[\w\\]*(\s+as\s+\w+)?\s*;?$/.test(l);
2500
- const existingPhpUse = new Set(existingLines.filter(isPhpUseImport).map((l) => l.trim()));
2665
+ const existingPhpUse = new Set(existingOutsideIgnore.filter(isPhpUseImport).map((l) => l.trim()));
2501
2666
  const generatedPhpUse = new Set(resultLines.filter(isPhpUseImport).map((l) => l.trim()));
2502
2667
  const extraPhpUse = [...existingPhpUse].filter((l) => !generatedPhpUse.has(l));
2503
2668
  const rubyRequireRe = /^(require(?:_relative)?)\s+['"]([^'"]+)['"]\s*$/;
@@ -2512,7 +2677,7 @@ function spliceExtraImports(existingLines, resultLines) {
2512
2677
  }
2513
2678
  const seenExistingKeys = /* @__PURE__ */ new Set();
2514
2679
  const extraRubyRequire = [];
2515
- for (const l of existingLines) {
2680
+ for (const l of existingOutsideIgnore) {
2516
2681
  const key = rubyRequireKey(l);
2517
2682
  if (!key) continue;
2518
2683
  if (generatedRubyRequireKeys.has(key)) continue;
@@ -2904,6 +3069,7 @@ async function readManifest(dir) {
2904
3069
  }
2905
3070
  /** Write `.oagen-manifest.json` to a directory with sorted paths. */
2906
3071
  async function writeManifest(dir, opts) {
3072
+ const manifestPath = path$1.join(dir, MANIFEST_FILENAME);
2907
3073
  const manifest = {
2908
3074
  version: MANIFEST_VERSION,
2909
3075
  language: opts.language,
@@ -2918,10 +3084,16 @@ async function writeManifest(dir, opts) {
2918
3084
  files: [...new Set(opts.files)].sort(),
2919
3085
  ...opts.operations !== void 0 ? { operations: opts.operations } : {}
2920
3086
  };
2921
- const manifestPath = path$1.join(dir, MANIFEST_FILENAME);
3087
+ const previous = await readManifest(dir);
3088
+ if (previous && sameManifestExceptGeneratedAt(previous, manifest)) manifest.generatedAt = previous.generatedAt;
2922
3089
  await fs.mkdir(dir, { recursive: true });
2923
3090
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
2924
3091
  }
3092
+ function sameManifestExceptGeneratedAt(a, b) {
3093
+ const { generatedAt: _aGeneratedAt, ...aComparable } = a;
3094
+ const { generatedAt: _bGeneratedAt, ...bComparable } = b;
3095
+ return JSON.stringify(aComparable) === JSON.stringify(bComparable);
3096
+ }
2925
3097
  /** Return the set of previous paths no longer present in the current emission. */
2926
3098
  function computeStalePaths(prev, currentPaths) {
2927
3099
  const current = new Set(currentPaths);
@@ -3540,4 +3712,4 @@ function summarize(changes) {
3540
3712
  //#endregion
3541
3713
  export { MANIFEST_FILENAME as a, treeShakeFiles as c, collectReferencedNames as d, generateAllFiles as f, resolveOperations as h, generate as i, applyFileHeaders as l, deriveMethodName as m, getEmitter as n, integrateGeneratedFiles as o, generateFiles as p, registerEmitter as r, mapFilesForTargetIntegration as s, diffSpecs as t, buildEmitterContext as u };
3542
3714
 
3543
- //# sourceMappingURL=diff-CBFCoTCw.mjs.map
3715
+ //# sourceMappingURL=diff-Dobf9PHC.mjs.map