@workos/oagen-emitters 0.18.4 → 0.19.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.
Files changed (58) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/{plugin-Cciic50q.mjs → plugin-DXIciTnN.mjs} +668 -164
  6. package/dist/plugin-DXIciTnN.mjs.map +1 -0
  7. package/dist/plugin.mjs +1 -1
  8. package/package.json +4 -4
  9. package/src/dotnet/enums.ts +11 -5
  10. package/src/dotnet/fixtures.ts +28 -7
  11. package/src/dotnet/index.ts +42 -1
  12. package/src/dotnet/models.ts +11 -5
  13. package/src/dotnet/resources.ts +3 -3
  14. package/src/dotnet/tests.ts +4 -4
  15. package/src/go/enums.ts +91 -18
  16. package/src/go/fixtures.ts +25 -3
  17. package/src/go/flat-merge.ts +253 -0
  18. package/src/go/models.ts +85 -20
  19. package/src/go/resources.ts +3 -3
  20. package/src/go/tests.ts +7 -5
  21. package/src/kotlin/enums.ts +21 -11
  22. package/src/kotlin/models.ts +53 -11
  23. package/src/kotlin/resources.ts +2 -2
  24. package/src/kotlin/tests.ts +38 -3
  25. package/src/node/enums.ts +8 -5
  26. package/src/node/models.ts +29 -21
  27. package/src/node/resources.ts +12 -1
  28. package/src/node/tests.ts +7 -2
  29. package/src/php/enums.ts +18 -5
  30. package/src/php/index.ts +11 -3
  31. package/src/php/models.ts +11 -5
  32. package/src/php/resources.ts +6 -4
  33. package/src/php/tests.ts +6 -3
  34. package/src/python/enums.ts +39 -28
  35. package/src/python/fixtures.ts +34 -6
  36. package/src/python/models.ts +138 -45
  37. package/src/python/resources.ts +3 -3
  38. package/src/python/tests.ts +31 -12
  39. package/src/ruby/enums.ts +28 -19
  40. package/src/ruby/models.ts +23 -12
  41. package/src/ruby/rbi.ts +17 -6
  42. package/src/ruby/resources.ts +2 -2
  43. package/src/ruby/tests.ts +37 -4
  44. package/src/rust/enums.ts +29 -7
  45. package/src/rust/fixtures.ts +12 -3
  46. package/src/rust/models.ts +37 -6
  47. package/src/rust/resources.ts +8 -1
  48. package/src/rust/tests.ts +3 -3
  49. package/src/shared/resolved-ops.ts +104 -0
  50. package/test/dotnet/scoped-aggregates.test.ts +247 -0
  51. package/test/go/scoping.test.ts +324 -0
  52. package/test/kotlin/models.test.ts +74 -0
  53. package/test/kotlin/tests.test.ts +33 -0
  54. package/test/python/scoped-aggregates.test.ts +205 -0
  55. package/test/ruby/tests.test.ts +130 -0
  56. package/test/rust/fixtures.test.ts +13 -7
  57. package/test/shared/synthetic-enum-seed.test.ts +79 -0
  58. package/dist/plugin-Cciic50q.mjs.map +0 -1
@@ -75,6 +75,98 @@ function groupByMount(ctx) {
75
75
  return groups;
76
76
  }
77
77
  /**
78
+ * Like {@link groupByMount}, but for a scoped (`--services`) run returns ONLY the
79
+ * mount groups the run selected (`ctx.scopedServices`, POST-MOUNT names). When
80
+ * scoping is inactive the full set is returned unchanged.
81
+ *
82
+ * Use this for PER-SERVICE resource/test emission. Do NOT use it for
83
+ * aggregate/barrel files (Rust `mod.rs`, Ruby `client.rbi`, the root client) —
84
+ * those must continue to list every service, so they keep calling
85
+ * {@link groupByMount} over the full set; otherwise a scoped run would drop
86
+ * sibling modules and break the build/type-check.
87
+ */
88
+ function scopedMountGroups(ctx) {
89
+ const groups = groupByMount(ctx);
90
+ const scope = ctx.scopedServices;
91
+ if (!scope || scope.size === 0) return groups;
92
+ return new Map([...groups].filter(([mountName]) => scope.has(mountName)));
93
+ }
94
+ /**
95
+ * True when a POST-MOUNT service name should be emitted in the current run.
96
+ * Inactive scoping (no `ctx.scopedServices`) ⇒ everything is in scope. Use this
97
+ * for inline per-service gates (e.g. manifest loops keyed by `getMountTarget`).
98
+ */
99
+ function isMountInScope(mountName, ctx) {
100
+ const scope = ctx.scopedServices;
101
+ return !scope || scope.size === 0 || scope.has(mountName);
102
+ }
103
+ /**
104
+ * True when a MODEL's per-model FILE should be written in the current run (FR-1.4).
105
+ * A scoped run sets `ctx.scopedModelNames` to the models reachable from the
106
+ * selected services; out-of-scope models are left untouched on disk. Inactive
107
+ * scoping ⇒ everything is in scope. NOTE: gate only the per-model FILE write —
108
+ * the model must still appear in barrels/indexes (built from the full set) so the
109
+ * untouched on-disk file stays importable.
110
+ */
111
+ function isModelInScope(modelName, ctx) {
112
+ const scope = ctx.scopedModelNames;
113
+ return !scope || scope.has(modelName);
114
+ }
115
+ /** Like {@link isModelInScope} but for an ENUM's per-enum file (`ctx.scopedEnumNames`). */
116
+ function isEnumInScope(enumName, ctx) {
117
+ const scope = ctx.scopedEnumNames;
118
+ return !scope || scope.has(enumName);
119
+ }
120
+ /** True when a scoped (`--services`) run is active. */
121
+ function isScopedRun(ctx) {
122
+ return !!ctx.scopedServices && ctx.scopedServices.size > 0;
123
+ }
124
+ /**
125
+ * Barrel/index inclusion gate for one item (model, enum, fixture) under a
126
+ * scoped run. A barrel/index may only reference an item whose per-item FILE
127
+ * EXISTS ON DISK after the run — otherwise the reference dangles and the SDK
128
+ * fails to compile (the bug this guards: a brand-new model belonging to an
129
+ * out-of-scope service gets declared in `mod.rs`/`__init__.py` but its source
130
+ * file is never emitted). That on-disk set is:
131
+ * in-scope items (freshly emitted this run) ∪ items already on disk
132
+ * (recorded in the prior manifest, left untouched because scoped runs never
133
+ * prune).
134
+ * Inactive scoping ⇒ always true (a full run emits and declares everything).
135
+ *
136
+ * `relPath` is the per-item file path the emitter writes (e.g.
137
+ * `src/models/foo.rs`); `inScope` is the per-item scope predicate result
138
+ * (`isModelInScope` / `isEnumInScope`).
139
+ */
140
+ function fileExistsAfterRun(relPath, inScope, ctx) {
141
+ if (!isScopedRun(ctx)) return true;
142
+ return inScope || (ctx.priorTargetManifestPaths?.has(relPath) ?? false);
143
+ }
144
+ /**
145
+ * Per-item basenames recorded in the prior manifest directly under `dir` (e.g.
146
+ * `src/models`) with extension `ext` (e.g. `.rs`), EXCLUDING `reserved` names
147
+ * (barrels such as `mod`, `_unions`, `__init__`). A scoped run uses this to
148
+ * RETAIN barrel declarations for items that were renamed/removed from the spec
149
+ * but whose files still sit on disk — and may still be referenced by
150
+ * out-of-scope code the scoped run did not regenerate (e.g. a stale resource
151
+ * file). Returns `[]` for a full run or when no prior manifest is available;
152
+ * the caller is responsible for de-duping against items it already emitted.
153
+ */
154
+ function priorManifestBasenames(ctx, dir, ext, reserved = /* @__PURE__ */ new Set()) {
155
+ if (!isScopedRun(ctx)) return [];
156
+ const paths = ctx.priorTargetManifestPaths;
157
+ if (!paths) return [];
158
+ const prefix = dir.endsWith("/") ? dir : `${dir}/`;
159
+ const out = [];
160
+ for (const p of paths) {
161
+ if (!p.startsWith(prefix) || !p.endsWith(ext)) continue;
162
+ const base = p.slice(prefix.length, p.length - ext.length);
163
+ if (base.includes("/")) continue;
164
+ if (reserved.has(base)) continue;
165
+ out.push(base);
166
+ }
167
+ return out;
168
+ }
169
+ /**
78
170
  * Get the mount target for an IR service.
79
171
  * Checks the first resolved operation that belongs to this service.
80
172
  * Falls back to PascalCase of the service name if no resolved ops exist.
@@ -4655,7 +4747,7 @@ function generateEnums$7(enums, ctx) {
4655
4747
  lines.push(`export type ${enumDef.name} =`);
4656
4748
  lines.push(` (typeof ${enumDef.name})[keyof typeof ${enumDef.name}];`);
4657
4749
  }
4658
- files.push({
4750
+ if (isEnumInScope(enumDef.name, ctx)) files.push({
4659
4751
  path: `src/${dirName}/interfaces/${fileName$3(enumDef.name)}.interface.ts`,
4660
4752
  content: lines.join("\n"),
4661
4753
  skipIfExists: !hasNewValues
@@ -5865,12 +5957,13 @@ function generateResources$7(services, ctx) {
5865
5957
  if (services.length === 0) return [];
5866
5958
  const files = [];
5867
5959
  const mountGroups = groupByMount(ctx);
5868
- const mergedServices = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
5960
+ const mergedServices = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
5869
5961
  name,
5870
5962
  operations: group.operations
5871
5963
  })) : services;
5872
5964
  const topLevelEnumNames = new Set(ctx.spec.enums.map((e) => e.name));
5873
5965
  for (const service of mergedServices) {
5966
+ if (!isMountInScope(service.name, ctx)) continue;
5874
5967
  const isOwnedService = isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx));
5875
5968
  if (!isOwnedService && isServiceCoveredByExisting(service, ctx)) {
5876
5969
  if (!hasMethodsAbsentFromBaseline(service, ctx)) continue;
@@ -5897,6 +5990,7 @@ function generateResources$7(services, ctx) {
5897
5990
  }
5898
5991
  }
5899
5992
  for (const service of mergedServices) {
5993
+ if (!isMountInScope(service.name, ctx)) continue;
5900
5994
  if (!isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx)) && isServiceCoveredByExisting(service, ctx) && !hasMethodsAbsentFromBaseline(service, ctx)) continue;
5901
5995
  files.push(...generateOptionsInterfaces(service, ctx, topLevelEnumNames));
5902
5996
  }
@@ -7455,7 +7549,7 @@ function generateModels$7(models, ctx, shared) {
7455
7549
  "",
7456
7550
  ...aliasExports
7457
7551
  ] : [...aliasExports];
7458
- files.push({
7552
+ if (isModelInScope(model.name, ctx)) files.push({
7459
7553
  path: aliasPath,
7460
7554
  content: aliasLines.join("\n"),
7461
7555
  overwriteExisting: true
@@ -7713,7 +7807,7 @@ function generateModels$7(models, ctx, shared) {
7713
7807
  for (const [alias, typeExpr] of typeDecls) if (new RegExp(`\\b${alias}\\b`).test(bodyText)) usedDecls.push(`type ${alias} = ${typeExpr};`);
7714
7808
  if (usedDecls.length > 0) lines.splice(typeDeclInsertIdx, 0, ...usedDecls, "");
7715
7809
  }
7716
- files.push({
7810
+ if (isModelInScope(model.name, ctx)) files.push({
7717
7811
  path: filePath,
7718
7812
  content: pruneUnusedImports(lines).join("\n"),
7719
7813
  overwriteExisting: true
@@ -7826,7 +7920,7 @@ function generateSerializers(models, ctx, shared) {
7826
7920
  if (!canonSkipDeserialize) parts.push(`deserialize${canonDomainName} as deserialize${domainName}`);
7827
7921
  if (!canonSkipSerialize) parts.push(`serialize${canonDomainName} as serialize${domainName}`);
7828
7922
  const reexportContent = `export { ${parts.join(", ")} } from '${rel}';`;
7829
- files.push({
7923
+ if (isModelInScope(model.name, ctx)) files.push({
7830
7924
  path: serializerPath,
7831
7925
  content: reexportContent,
7832
7926
  overwriteExisting: true
@@ -7854,7 +7948,7 @@ function generateSerializers(models, ctx, shared) {
7854
7948
  responseReachableModels,
7855
7949
  ctx
7856
7950
  }), ...emitSerializerBody(model, domainName, responseName, typeParams, baselineDomain, baselineResponse, skipFormatFields, shouldSkipSerialize, shouldSkipDeserialize, ctx)];
7857
- files.push({
7951
+ if (isModelInScope(model.name, ctx)) files.push({
7858
7952
  path: serializerPath,
7859
7953
  content: pruneUnusedImports(lines).join("\n"),
7860
7954
  overwriteExisting: true
@@ -8661,7 +8755,7 @@ function generateTests$7(spec, ctx) {
8661
8755
  const mountGroups = groupByMount(ctx);
8662
8756
  const mountAccessors = /* @__PURE__ */ new Map();
8663
8757
  for (const r of ctx.resolvedOperations ?? []) if (!mountAccessors.has(r.mountOn)) mountAccessors.set(r.mountOn, servicePropertyName$4(r.mountOn));
8664
- const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
8758
+ const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
8665
8759
  name,
8666
8760
  operations: group.operations
8667
8761
  })) : spec.services.map((s) => ({
@@ -8672,6 +8766,7 @@ function generateTests$7(spec, ctx) {
8672
8766
  if (ctx.apiSurface?.classes?.["WorkOS"]?.methods) for (const name of Object.keys(ctx.apiSurface.classes["WorkOS"].methods)) baselineWorkOSProps.add(name);
8673
8767
  if (ctx.apiSurface?.classes?.["WorkOS"]?.properties) for (const name of Object.keys(ctx.apiSurface.classes["WorkOS"].properties)) baselineWorkOSProps.add(name);
8674
8768
  for (const { name: mountName, operations } of testEntries) {
8769
+ if (!isMountInScope(mountName, ctx)) continue;
8675
8770
  if (operations.length === 0) continue;
8676
8771
  const mergedService = {
8677
8772
  name: mountName,
@@ -11471,6 +11566,7 @@ function generateEnums$6(enums, ctx) {
11471
11566
  const aliasOf = placement.enumAliases;
11472
11567
  for (const enumDef of enums) {
11473
11568
  const dirName = resolveDir(enumToService.get(enumDef.name));
11569
+ const enumInScope = isEnumInScope(enumDef.name, ctx);
11474
11570
  const canonicalName = aliasOf.get(enumDef.name);
11475
11571
  if (canonicalName) {
11476
11572
  if (fileName$2(enumDef.name) === fileName$2(canonicalName)) continue;
@@ -11497,7 +11593,7 @@ function generateEnums$6(enums, ctx) {
11497
11593
  lines.push(" raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")");
11498
11594
  }
11499
11595
  lines.push(`__all__ = ["${aliasCls}"]`);
11500
- files.push({
11596
+ if (enumInScope) files.push({
11501
11597
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
11502
11598
  content: lines.join("\n"),
11503
11599
  integrateTarget: true,
@@ -11528,7 +11624,7 @@ function generateEnums$6(enums, ctx) {
11528
11624
  `__all__ = ["${aliasName}"]`
11529
11625
  ].join("\n");
11530
11626
  }
11531
- files.push({
11627
+ if (enumInScope) files.push({
11532
11628
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
11533
11629
  content: compatContent,
11534
11630
  integrateTarget: true,
@@ -11627,24 +11723,26 @@ function generateEnums$6(enums, ctx) {
11627
11723
  lines.push("");
11628
11724
  lines.push(`${cls}Literal: TypeAlias = Literal[${uniqueValues.map((v) => typeof v.value === "string" ? `"${v.value}"` : typeof v.value === "boolean" ? v.value ? "True" : "False" : String(v.value)).join(", ")}]`);
11629
11725
  }
11630
- files.push({
11631
- path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
11632
- content: lines.join("\n"),
11633
- integrateTarget: true,
11634
- overwriteExisting: true
11635
- });
11636
- for (const aliasName of compatAliases.get(enumDef.name) ?? []) files.push({
11637
- path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
11638
- content: [
11639
- "from typing import TypeAlias",
11640
- `from .${fileName$2(enumDef.name)} import ${cls}`,
11641
- "",
11642
- `${aliasName}: TypeAlias = ${cls}`,
11643
- `__all__ = ["${aliasName}"]`
11644
- ].join("\n"),
11645
- integrateTarget: true,
11646
- overwriteExisting: true
11647
- });
11726
+ if (enumInScope) {
11727
+ files.push({
11728
+ path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
11729
+ content: lines.join("\n"),
11730
+ integrateTarget: true,
11731
+ overwriteExisting: true
11732
+ });
11733
+ for (const aliasName of compatAliases.get(enumDef.name) ?? []) files.push({
11734
+ path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
11735
+ content: [
11736
+ "from typing import TypeAlias",
11737
+ `from .${fileName$2(enumDef.name)} import ${cls}`,
11738
+ "",
11739
+ `${aliasName}: TypeAlias = ${cls}`,
11740
+ `__all__ = ["${aliasName}"]`
11741
+ ].join("\n"),
11742
+ integrateTarget: true,
11743
+ overwriteExisting: true
11744
+ });
11745
+ }
11648
11746
  }
11649
11747
  return files;
11650
11748
  }
@@ -11781,23 +11879,26 @@ function generateModels$6(models, ctx) {
11781
11879
  dispLines.push(" if dispatch_cls is not None:");
11782
11880
  dispLines.push(` return cast("${variantTypeName}", dispatch_cls.from_dict(data))`);
11783
11881
  dispLines.push(` return ${unknownClassName}.from_dict(data)`);
11784
- files.push({
11882
+ const dispInScope = isModelInScope(model.name, ctx);
11883
+ if (dispInScope) files.push({
11785
11884
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
11786
11885
  content: dispLines.join("\n"),
11787
11886
  integrateTarget: true,
11788
11887
  overwriteExisting: true
11789
11888
  });
11790
- if (!emittedModelSymbolsByDir.has(dirName)) emittedModelSymbolsByDir.set(dirName, []);
11791
- emittedModelSymbolsByDir.get(dirName).push(model.name);
11792
- emittedModelSymbolsByDir.get(dirName).push(variantTypeName);
11793
- symbolToFile.set(variantTypeName, fileName$2(model.name));
11794
- emittedModelSymbolsByDir.get(dirName).push(unknownClassName);
11795
- symbolToFile.set(unknownClassName, fileName$2(model.name));
11796
- const dispatcherNatural = originalModelToService.get(model.name);
11797
- if (dispatcherNatural) {
11798
- symbolToOriginalService.set(model.name, dispatcherNatural);
11799
- symbolToOriginalService.set(variantTypeName, dispatcherNatural);
11800
- symbolToOriginalService.set(unknownClassName, dispatcherNatural);
11889
+ if (fileExistsAfterRun(`src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`, dispInScope, ctx)) {
11890
+ if (!emittedModelSymbolsByDir.has(dirName)) emittedModelSymbolsByDir.set(dirName, []);
11891
+ emittedModelSymbolsByDir.get(dirName).push(model.name);
11892
+ emittedModelSymbolsByDir.get(dirName).push(variantTypeName);
11893
+ symbolToFile.set(variantTypeName, fileName$2(model.name));
11894
+ emittedModelSymbolsByDir.get(dirName).push(unknownClassName);
11895
+ symbolToFile.set(unknownClassName, fileName$2(model.name));
11896
+ const dispatcherNatural = originalModelToService.get(model.name);
11897
+ if (dispatcherNatural) {
11898
+ symbolToOriginalService.set(model.name, dispatcherNatural);
11899
+ symbolToOriginalService.set(variantTypeName, dispatcherNatural);
11900
+ symbolToOriginalService.set(unknownClassName, dispatcherNatural);
11901
+ }
11801
11902
  }
11802
11903
  continue;
11803
11904
  }
@@ -11812,16 +11913,20 @@ function generateModels$6(models, ctx) {
11812
11913
  else lines.push(`from ${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)} import ${canonicalClassName}`);
11813
11914
  lines.push("");
11814
11915
  lines.push(`${modelClassName}: TypeAlias = ${canonicalClassName}`);
11815
- files.push({
11816
- path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
11916
+ const aliasInScope = isModelInScope(model.name, ctx);
11917
+ const aliasPath = `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`;
11918
+ if (aliasInScope) files.push({
11919
+ path: aliasPath,
11817
11920
  content: lines.join("\n"),
11818
11921
  integrateTarget: true,
11819
11922
  overwriteExisting: true
11820
11923
  });
11821
- if (!emittedModelSymbolsByDir.has(dirName)) emittedModelSymbolsByDir.set(dirName, []);
11822
- emittedModelSymbolsByDir.get(dirName).push(model.name);
11823
- const aliasNatural = originalModelToService.get(model.name);
11824
- if (aliasNatural) symbolToOriginalService.set(model.name, aliasNatural);
11924
+ if (fileExistsAfterRun(aliasPath, aliasInScope, ctx)) {
11925
+ if (!emittedModelSymbolsByDir.has(dirName)) emittedModelSymbolsByDir.set(dirName, []);
11926
+ emittedModelSymbolsByDir.get(dirName).push(model.name);
11927
+ const aliasNatural = originalModelToService.get(model.name);
11928
+ if (aliasNatural) symbolToOriginalService.set(model.name, aliasNatural);
11929
+ }
11825
11930
  continue;
11826
11931
  }
11827
11932
  const seenFieldNames = /* @__PURE__ */ new Set();
@@ -11966,16 +12071,19 @@ function generateModels$6(models, ctx) {
11966
12071
  }
11967
12072
  }
11968
12073
  lines.push(" return result");
11969
- files.push({
11970
- path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
12074
+ const regularInScope = isModelInScope(model.name, ctx);
12075
+ if (regularInScope) files.push({
12076
+ path: modelFilePath,
11971
12077
  content: lines.join("\n"),
11972
12078
  integrateTarget: true,
11973
12079
  overwriteExisting: true
11974
12080
  });
11975
- if (!emittedModelSymbolsByDir.has(dirName)) emittedModelSymbolsByDir.set(dirName, []);
11976
- emittedModelSymbolsByDir.get(dirName).push(model.name);
11977
- const regularNatural = originalModelToService.get(model.name);
11978
- if (regularNatural) symbolToOriginalService.set(model.name, regularNatural);
12081
+ if (fileExistsAfterRun(modelFilePath, regularInScope, ctx)) {
12082
+ if (!emittedModelSymbolsByDir.has(dirName)) emittedModelSymbolsByDir.set(dirName, []);
12083
+ emittedModelSymbolsByDir.get(dirName).push(model.name);
12084
+ const regularNatural = originalModelToService.get(model.name);
12085
+ if (regularNatural) symbolToOriginalService.set(model.name, regularNatural);
12086
+ }
11979
12087
  }
11980
12088
  const symbolsByDir = /* @__PURE__ */ new Map();
11981
12089
  for (const [dirName, names] of emittedModelSymbolsByDir) {
@@ -11984,11 +12092,35 @@ function generateModels$6(models, ctx) {
11984
12092
  for (const name of names) symbolsByDir.get(key).push({ name });
11985
12093
  }
11986
12094
  const reachableEnumNames = collectReachableEnumNames(ctx);
11987
- const enumSymbolsByDir = collectGeneratedEnumSymbolsByDir(ctx.spec.enums.filter((enumDef) => reachableEnumNames.has(enumDef.name)), ctx);
12095
+ const emittedEnums = ctx.spec.enums.filter((enumDef) => reachableEnumNames.has(enumDef.name));
12096
+ const enumSymbolsByDir = collectGeneratedEnumSymbolsByDir(emittedEnums, ctx);
12097
+ const aliasToCanonicalEnum = /* @__PURE__ */ new Map();
12098
+ for (const [canonical, aliasNames] of collectCompatEnumAliases(emittedEnums, ctx)) for (const aliasName of aliasNames) aliasToCanonicalEnum.set(aliasName, canonical);
11988
12099
  for (const [dirName, names] of enumSymbolsByDir) {
11989
12100
  const key = `src/${ctx.namespace}/${dirName}/models`;
11990
12101
  if (!symbolsByDir.has(key)) symbolsByDir.set(key, []);
11991
- for (const name of names) symbolsByDir.get(key).push({ name });
12102
+ for (const name of names) if (fileExistsAfterRun(`${key}/${fileName$2(name)}.py`, isEnumInScope(aliasToCanonicalEnum.get(name) ?? name, ctx), ctx)) symbolsByDir.get(key).push({ name });
12103
+ }
12104
+ const manifestModelDirs = /* @__PURE__ */ new Set();
12105
+ for (const p of ctx.priorTargetManifestPaths ?? []) {
12106
+ const m = p.match(new RegExp(`^(src/${ctx.namespace}/[^/]+/models)/[^/]+\\.py$`));
12107
+ if (m) manifestModelDirs.add(m[1]);
12108
+ }
12109
+ for (const dirPath of manifestModelDirs) {
12110
+ if (!symbolsByDir.has(dirPath)) symbolsByDir.set(dirPath, []);
12111
+ const referencedBasenames = /* @__PURE__ */ new Set();
12112
+ for (const sym of symbolsByDir.get(dirPath)) {
12113
+ if (sym.reExport || sym.retainBasename) continue;
12114
+ referencedBasenames.add(symbolToFile.get(sym.name) ?? fileName$2(sym.name));
12115
+ }
12116
+ for (const base of priorManifestBasenames(ctx, dirPath, ".py", new Set(["__init__"]))) {
12117
+ if (referencedBasenames.has(base)) continue;
12118
+ referencedBasenames.add(base);
12119
+ symbolsByDir.get(dirPath).push({
12120
+ name: base,
12121
+ retainBasename: base
12122
+ });
12123
+ }
11992
12124
  }
11993
12125
  const commonDirName = "common";
11994
12126
  const addReExport = (naturalService, name, sourceFile) => {
@@ -12033,6 +12165,10 @@ function generateModels$6(models, ctx) {
12033
12165
  const uniqueSymbols = [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
12034
12166
  const importLines = [];
12035
12167
  for (const sym of uniqueSymbols) {
12168
+ if (sym.retainBasename) {
12169
+ importLines.push(`from .${sym.retainBasename} import * # noqa: F401,F403`);
12170
+ continue;
12171
+ }
12036
12172
  const cls = className$5(sym.name);
12037
12173
  if (sym.reExport) importLines.push(`from ${ctx.namespace}.${dirToModule(sym.reExport.fromDir)}.models.${sym.reExport.file} import ${cls} as ${cls}`);
12038
12174
  else {
@@ -12049,7 +12185,7 @@ function generateModels$6(models, ctx) {
12049
12185
  });
12050
12186
  if (!serviceDirModelPaths.has(dirPath)) {
12051
12187
  const parentDir = dirPath.replace(/\/models$/, "");
12052
- const reExports = uniqueSymbols.map((sym) => `from .models import ${className$5(sym.name)} as ${className$5(sym.name)}`).join("\n");
12188
+ const reExports = [...new Set(uniqueSymbols.map((sym) => sym.retainBasename ? "from .models import * # noqa: F401,F403" : `from .models import ${className$5(sym.name)} as ${className$5(sym.name)}`))].join("\n");
12053
12189
  files.push({
12054
12190
  path: `${parentDir}/__init__.py`,
12055
12191
  content: reExports,
@@ -13134,8 +13270,8 @@ function generateResources$6(services, ctx) {
13134
13270
  const resolvedLookup = buildResolvedLookup(ctx);
13135
13271
  const files = [];
13136
13272
  const mountDirMap = buildMountDirMap$1(ctx);
13137
- const mountGroups = groupByMount(ctx);
13138
- const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
13273
+ const mountGroups = scopedMountGroups(ctx);
13274
+ const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
13139
13275
  name,
13140
13276
  operations: group.operations
13141
13277
  })) : services.map((s) => ({
@@ -13769,18 +13905,30 @@ const ID_PREFIXES$3 = {
13769
13905
  };
13770
13906
  /**
13771
13907
  * Generate JSON fixture files for test data.
13772
- */
13773
- function generateFixtures$4(spec) {
13908
+ *
13909
+ * `ctx` is optional so unit tests can call with a bare spec; when supplied, a
13910
+ * scoped (`--services`) run only emits a fixture for a model whose per-model
13911
+ * file will exist on disk after the run (in-scope, or already present from a
13912
+ * prior manifest). Brand-new out-of-scope models are skipped: the round-trip
13913
+ * test that consumes these fixtures is gated the same way, and the per-service
13914
+ * tests only reference fixtures for their (in-scope) models.
13915
+ */
13916
+ function generateFixtures$4(spec, ctx) {
13774
13917
  if (spec.models.length === 0) return [];
13775
13918
  const modelMap = new Map(spec.models.map((m) => [m.name, m]));
13776
13919
  const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
13777
13920
  const files = [];
13921
+ const fixtureEmitted = (path, modelName) => {
13922
+ if (!ctx) return true;
13923
+ return fileExistsAfterRun(path, isModelInScope(modelName, ctx), ctx);
13924
+ };
13778
13925
  const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
13779
13926
  const listMetadataNeeded = collectReferencedListMetadataModels(spec.models, nonPaginatedRefs);
13780
13927
  for (const model of spec.models) {
13781
13928
  if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
13782
13929
  if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
13783
13930
  if (model.fields.length === 0) continue;
13931
+ if (!fixtureEmitted(`tests/fixtures/${fileName$2(model.name)}.json`, model.name)) continue;
13784
13932
  const fixture = generateModelFixture$4(model, modelMap, enumMap);
13785
13933
  files.push({
13786
13934
  path: `tests/fixtures/${fileName$2(model.name)}.json`,
@@ -13793,6 +13941,7 @@ function generateFixtures$4(spec) {
13793
13941
  const unwrapped = unwrapListModel$3(itemModel, modelMap);
13794
13942
  if (unwrapped) itemModel = unwrapped;
13795
13943
  if (itemModel.fields.length === 0) continue;
13944
+ if (!fixtureEmitted(`tests/fixtures/list_${fileName$2(itemModel.name)}.json`, itemModel.name)) continue;
13796
13945
  const listFixture = {
13797
13946
  data: [generateModelFixture$4(itemModel, modelMap, enumMap)],
13798
13947
  list_metadata: {
@@ -13935,7 +14084,7 @@ function buildDeleteSuccessResponseSetup(op) {
13935
14084
  */
13936
14085
  function generateTests$6(spec, ctx) {
13937
14086
  const files = [];
13938
- const fixtures = generateFixtures$4(spec);
14087
+ const fixtures = generateFixtures$4(spec, ctx);
13939
14088
  for (const fixture of fixtures) files.push({
13940
14089
  path: fixture.path,
13941
14090
  content: fixture.content,
@@ -13944,8 +14093,8 @@ function generateTests$6(spec, ctx) {
13944
14093
  overwriteExisting: true
13945
14094
  });
13946
14095
  const accessPaths = buildServiceAccessPaths$3(spec.services, ctx);
13947
- const mountGroups = groupByMount(ctx);
13948
- const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
14096
+ const mountGroups = scopedMountGroups(ctx);
14097
+ const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
13949
14098
  name,
13950
14099
  operations: group.operations,
13951
14100
  resolvedOps: group.resolvedOps
@@ -14912,11 +15061,17 @@ function generateModelRoundTripTests(spec, ctx) {
14912
15061
  for (const name of responseModelNames) requestOnlyModelNames.delete(name);
14913
15062
  const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
14914
15063
  const listMetadataNeeded = collectReferencedListMetadataModels(spec.models, nonPaginatedRefs);
14915
- const models = spec.models.filter((m) => !(isListWrapperModel(m) && !nonPaginatedRefs.has(m.name)) && !(isListMetadataModel(m) && !listMetadataNeeded.has(m.name)) && !requestOnlyModelNames.has(m.name));
14916
- if (models.length === 0) return null;
14917
- const modelToService = computeSchemaPlacement(spec, ctx).originalModelToService;
15064
+ const placement = computeSchemaPlacement(spec, ctx);
15065
+ const modelToService = placement.originalModelToService;
15066
+ const relocatedModelToService = placement.modelToService;
14918
15067
  const roundTripDirMap = buildMountDirMap$1(ctx);
14919
15068
  const resolveDir = (irService) => irService ? roundTripDirMap.get(irService) ?? "common" : "common";
15069
+ const modelFileExists = (name) => {
15070
+ const dir = resolveDir(relocatedModelToService.get(name));
15071
+ return fileExistsAfterRun(`src/${ctx.namespace}/${dir}/models/${fileName$2(name)}.py`, isModelInScope(name, ctx), ctx);
15072
+ };
15073
+ const models = spec.models.filter((m) => !(isListWrapperModel(m) && !nonPaginatedRefs.has(m.name)) && !(isListMetadataModel(m) && !listMetadataNeeded.has(m.name)) && !requestOnlyModelNames.has(m.name) && modelFileExists(m.name));
15074
+ if (models.length === 0) return null;
14920
15075
  const lines = [];
14921
15076
  lines.push("\"\"\"Model round-trip tests: from_dict(to_dict()) preserves data.\"\"\"");
14922
15077
  lines.push("");
@@ -15436,7 +15591,7 @@ function generateModels$5(models, ctx) {
15436
15591
  lines.push(" ];");
15437
15592
  lines.push(" }");
15438
15593
  lines.push("}");
15439
- files.push({
15594
+ if (isModelInScope(model.name, ctx)) files.push({
15440
15595
  path: `lib/Resource/${name}.php`,
15441
15596
  content: lines.join("\n"),
15442
15597
  overwriteExisting: true
@@ -15594,6 +15749,7 @@ function generateEnums$5(enums, ctx) {
15594
15749
  const canonical = resolveEnumName$1(e.name);
15595
15750
  if (emittedCanonical.has(canonical)) continue;
15596
15751
  emittedCanonical.add(canonical);
15752
+ const enumInScope = enums.some((other) => resolveEnumName$1(other.name) === canonical && isEnumInScope(other.name, ctx));
15597
15753
  const name = className$4(canonical);
15598
15754
  e.values.every((v) => typeof v.value === "string");
15599
15755
  const backingType = e.values.every((v) => typeof v.value === "number" && Number.isInteger(v.value)) ? "int" : "string";
@@ -15619,7 +15775,7 @@ function generateEnums$5(enums, ctx) {
15619
15775
  else lines.push(` case ${caseName} = ${val.value};`);
15620
15776
  }
15621
15777
  lines.push("}");
15622
- files.push({
15778
+ if (enumInScope) files.push({
15623
15779
  path: `lib/Resource/${name}.php`,
15624
15780
  content: lines.join("\n"),
15625
15781
  overwriteExisting: true
@@ -15760,8 +15916,8 @@ function generateResources$5(services, ctx) {
15760
15916
  if (services.length === 0) return [];
15761
15917
  const files = [];
15762
15918
  const modelMap = new Map(ctx.spec.models.map((m) => [m.name, m]));
15763
- const mountGroups = groupByMount(ctx);
15764
- const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
15919
+ const mountGroups = scopedMountGroups(ctx);
15920
+ const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
15765
15921
  name,
15766
15922
  operations: group.operations
15767
15923
  })) : services.map((s) => ({
@@ -16531,9 +16687,9 @@ function generatePrimitiveValue$2(type, format, name, modelName) {
16531
16687
  */
16532
16688
  function generateTests$5(spec, ctx) {
16533
16689
  const files = [];
16534
- const mountGroupsFromResolved = groupByMount(ctx);
16690
+ const mountGroupsFromResolved = scopedMountGroups(ctx);
16535
16691
  const mountGroups = /* @__PURE__ */ new Map();
16536
- if (mountGroupsFromResolved.size > 0) for (const [target, group] of mountGroupsFromResolved) mountGroups.set(target, group.resolvedOps.map((r) => ({
16692
+ if (mountGroupsFromResolved.size > 0 || ctx.scopedServices?.size) for (const [target, group] of mountGroupsFromResolved) mountGroups.set(target, group.resolvedOps.map((r) => ({
16537
16693
  op: r.operation,
16538
16694
  service: r.service,
16539
16695
  resolvedOp: r
@@ -16983,9 +17139,17 @@ function ensureTrailingNewlines$5(files) {
16983
17139
  * classes (no sum types), so a discriminated base whose IR fields the
16984
17140
  * parser stripped (post-allOf-aware detection) gets its original fields
16985
17141
  * restored to avoid silently dropping variant data.
16986
- */
16987
- function enrichModelsForPhp(models) {
16988
- const enriched = enrichModelsFromSpec(models);
17142
+ *
17143
+ * `enums` is forwarded to seed `enrichModelsFromSpec`'s collision set: an
17144
+ * inline oneOf enum whose synthetic name (`Parent_field`) snake-collapses
17145
+ * onto an existing IR enum (e.g. `DataIntegrationAccessTokenResponse_error`
17146
+ * vs `DataIntegrationAccessTokenResponseError`) must NOT spawn a duplicate
17147
+ * synthetic. Otherwise both collapse to the same `lib/Resource/X.php` path
17148
+ * and the later writer wins by array order — which differs between a full
17149
+ * and a scoped (`--services`) run, producing a non-deterministic case order.
17150
+ */
17151
+ function enrichModelsForPhp(models, enums) {
17152
+ const enriched = enrichModelsFromSpec(models, enums);
16989
17153
  const originalByName = new Map(models.map((m) => [m.name, m]));
16990
17154
  return enriched.map((m) => {
16991
17155
  if (m.discriminator && m.fields.length === 0) {
@@ -17002,7 +17166,7 @@ const phpEmitter = {
17002
17166
  language: "php",
17003
17167
  generateModels(models, ctx) {
17004
17168
  ensureNamingInitialized(ctx);
17005
- return ensureTrailingNewlines$5(generateModels$5(enrichModelsForPhp(models), ctx));
17169
+ return ensureTrailingNewlines$5(generateModels$5(enrichModelsForPhp(models, ctx.spec.enums), ctx));
17006
17170
  },
17007
17171
  generateEnums(enums, ctx) {
17008
17172
  ensureNamingInitialized(ctx);
@@ -17240,6 +17404,169 @@ function unionResolverName(ref) {
17240
17404
  return null;
17241
17405
  }
17242
17406
  //#endregion
17407
+ //#region src/go/flat-merge.ts
17408
+ /** Read the prior on-disk content of a generated file, or null when unavailable. */
17409
+ function readPriorFile(relPath, ctx) {
17410
+ if (!ctx.outputDir) return null;
17411
+ const abs = resolve(ctx.outputDir, relPath);
17412
+ if (!existsSync(abs)) return null;
17413
+ try {
17414
+ return readFileSync(abs, "utf-8");
17415
+ } catch {
17416
+ return null;
17417
+ }
17418
+ }
17419
+ /**
17420
+ * Parse a flat Go file into the set of top-level type names it declares, mapped
17421
+ * to the verbatim text of each declaration block (including its leading doc
17422
+ * comment). Used to recover the per-type "present before" signal a scoped run
17423
+ * needs. Recognizes the exact shapes the Go emitter produces:
17424
+ * - `type Name struct { ... }` (brace-balanced)
17425
+ * - `type Name = Other` (single-line alias)
17426
+ * - `type Name string` + `const ( ... )` (string enum)
17427
+ * - `type ( A = X\n B = X )` (batched alias block — declares many)
17428
+ * The leading `package` clause and any standalone trailing `const (...)` block
17429
+ * (e.g. the events file) are returned separately by {@link parseFlatGoBlocks}.
17430
+ */
17431
+ function parseFlatGoBlocks(content) {
17432
+ const lines = content.split("\n");
17433
+ const blocks = [];
17434
+ let i = 0;
17435
+ while (i < lines.length) {
17436
+ const t = lines[i].trim();
17437
+ if (t.startsWith("package ") || t === "" || t.startsWith("// Code generated")) {
17438
+ i++;
17439
+ continue;
17440
+ }
17441
+ break;
17442
+ }
17443
+ while (i < lines.length) {
17444
+ const start = i;
17445
+ while (i < lines.length && lines[i].trim().startsWith("//")) i++;
17446
+ if (i >= lines.length) break;
17447
+ const trimmed = lines[i].trim();
17448
+ if (trimmed === "type (") {
17449
+ const names = [];
17450
+ i++;
17451
+ while (i < lines.length && lines[i].trim() !== ")") {
17452
+ const m = lines[i].trim().match(/^(\w+)\s*=/);
17453
+ if (m) names.push(m[1]);
17454
+ i++;
17455
+ }
17456
+ i++;
17457
+ blocks.push({
17458
+ names,
17459
+ text: lines.slice(start, i).join("\n")
17460
+ });
17461
+ } else if (/^type\s+(\w+)\s+struct\s*\{/.test(trimmed)) {
17462
+ const name = trimmed.match(/^type\s+(\w+)/)[1];
17463
+ let depth = 0;
17464
+ let sawOpen = false;
17465
+ while (i < lines.length) {
17466
+ for (const ch of lines[i]) if (ch === "{") {
17467
+ depth++;
17468
+ sawOpen = true;
17469
+ } else if (ch === "}") depth--;
17470
+ i++;
17471
+ if (sawOpen && depth === 0) break;
17472
+ }
17473
+ blocks.push({
17474
+ names: [name],
17475
+ text: lines.slice(start, i).join("\n")
17476
+ });
17477
+ } else if (/^type\s+(\w+)\s+\w+\s+string\b/.test(trimmed) || /^type\s+(\w+)\s+string\b/.test(trimmed)) {
17478
+ const name = trimmed.match(/^type\s+(\w+)/)[1];
17479
+ i++;
17480
+ let j = i;
17481
+ while (j < lines.length && lines[j].trim() === "") j++;
17482
+ if (j < lines.length && lines[j].trim() === "const (") {
17483
+ i = j;
17484
+ while (i < lines.length && lines[i].trim() !== ")") i++;
17485
+ i++;
17486
+ }
17487
+ blocks.push({
17488
+ names: [name],
17489
+ text: lines.slice(start, i).join("\n")
17490
+ });
17491
+ } else if (/^type\s+(\w+)\s*=/.test(trimmed)) {
17492
+ const name = trimmed.match(/^type\s+(\w+)/)[1];
17493
+ i++;
17494
+ blocks.push({
17495
+ names: [name],
17496
+ text: lines.slice(start, i).join("\n")
17497
+ });
17498
+ } else i++;
17499
+ while (i < lines.length && lines[i].trim() === "") i++;
17500
+ }
17501
+ const byName = /* @__PURE__ */ new Map();
17502
+ for (const b of blocks) for (const n of b.names) byName.set(n, b);
17503
+ return {
17504
+ blocks,
17505
+ byName
17506
+ };
17507
+ }
17508
+ /**
17509
+ * Reconcile freshly generated named blocks against the prior on-disk file for a
17510
+ * scoped run. Returns the ordered list of block texts to emit. See file-level
17511
+ * docs for the keep/carry-over rules. In a scoped run a block is emitted as:
17512
+ * - IN-SCOPE → the freshly generated text (apply the new spec).
17513
+ * - out-of-scope, existed before → the PRIOR on-disk text, FROZEN, so an
17514
+ * unrelated change to that type in the same spec delta doesn't leak into a
17515
+ * scoped batch (Option B). (Falls back to fresh text only when the new
17516
+ * block's names span multiple prior blocks — a batched-alias regrouping —
17517
+ * which can't be frozen 1:1.)
17518
+ * - out-of-scope, brand-new → dropped (the addition that broke the build).
17519
+ * Then any prior block the new spec no longer produces at all is carried over
17520
+ * verbatim (renamed/removed types still referenced by un-regenerated code).
17521
+ *
17522
+ * @param newBlocks Per-type blocks the current spec produced (in emit order),
17523
+ * each tagged with `inScope`.
17524
+ * @param relPath Flat file path (e.g. `models.go`) for reading the prior file.
17525
+ * @param ctx Emitter context (provides `outputDir` + scope sets).
17526
+ * @param alsoEmitted Names this file emits OUTSIDE `newBlocks` (e.g. the fixed
17527
+ * `PaginationParams` struct). They must be excluded from the
17528
+ * carry-over, or the prior copy would be re-appended and
17529
+ * redeclare the separately-emitted one.
17530
+ */
17531
+ function reconcileFlatBlocks(newBlocks, relPath, ctx, alsoEmitted = /* @__PURE__ */ new Set()) {
17532
+ if (!isScopedRun(ctx)) return newBlocks.map((b) => b.text);
17533
+ const prior = readPriorFile(relPath, ctx);
17534
+ const priorParsed = prior ? parseFlatGoBlocks(prior) : {
17535
+ blocks: [],
17536
+ byName: /* @__PURE__ */ new Map()
17537
+ };
17538
+ const out = [];
17539
+ const emittedNames = /* @__PURE__ */ new Set();
17540
+ for (const block of newBlocks) {
17541
+ if (block.inScope) {
17542
+ out.push(block.text);
17543
+ for (const n of block.names) emittedNames.add(n);
17544
+ continue;
17545
+ }
17546
+ const priorBlocks = block.names.map((n) => priorParsed.byName.get(n)).filter((b) => !!b);
17547
+ const uniquePrior = new Set(priorBlocks);
17548
+ if (uniquePrior.size === 1) {
17549
+ const pb = priorBlocks[0];
17550
+ if (!pb.names.some((n) => emittedNames.has(n))) {
17551
+ out.push(pb.text);
17552
+ for (const n of pb.names) emittedNames.add(n);
17553
+ }
17554
+ } else if (uniquePrior.size > 1) for (const pb of uniquePrior) {
17555
+ if (pb.names.some((n) => emittedNames.has(n))) continue;
17556
+ out.push(pb.text);
17557
+ for (const n of pb.names) emittedNames.add(n);
17558
+ }
17559
+ }
17560
+ for (const block of priorParsed.blocks) {
17561
+ if (block.names.some((n) => alsoEmitted.has(n))) continue;
17562
+ if (block.names.every((n) => !emittedNames.has(n))) {
17563
+ out.push(block.text);
17564
+ for (const n of block.names) emittedNames.add(n);
17565
+ }
17566
+ }
17567
+ return out;
17568
+ }
17569
+ //#endregion
17243
17570
  //#region src/go/models.ts
17244
17571
  /**
17245
17572
  * Collect names of models that are referenced **only** as a named request body
@@ -17307,6 +17634,8 @@ function generateModels$4(models, ctx) {
17307
17634
  if (!hashGroups.has(hash)) hashGroups.set(hash, []);
17308
17635
  hashGroups.get(hash).push(model.name);
17309
17636
  }
17637
+ const priorModelNames = isScopedRun(ctx) ? new Set(parseFlatGoBlocks(readPriorFile("models.go", ctx) ?? "").blocks.flatMap((b) => b.names)) : /* @__PURE__ */ new Set();
17638
+ const aliasEmitted = (name) => !isScopedRun(ctx) || isModelInScope(name, ctx) || priorModelNames.has(className$3(name));
17310
17639
  const aliasOf = /* @__PURE__ */ new Map();
17311
17640
  for (const [hash, names] of hashGroups) {
17312
17641
  if (names.length <= 1) continue;
@@ -17315,6 +17644,11 @@ function generateModels$4(models, ctx) {
17315
17644
  const canonical = sorted[0];
17316
17645
  for (let i = 1; i < sorted.length; i++) aliasOf.set(sorted[i], canonical);
17317
17646
  }
17647
+ const forcedCanonicals = /* @__PURE__ */ new Set();
17648
+ if (isScopedRun(ctx)) {
17649
+ for (const [aliasName, canonical] of aliasOf) if (isModelInScope(aliasName, ctx)) forcedCanonicals.add(canonical);
17650
+ }
17651
+ const modelBlocks = [];
17318
17652
  const batchedAliases = /* @__PURE__ */ new Set();
17319
17653
  for (const model of models) {
17320
17654
  if (skipAsListWrapper(model) || skipAsListMetadata(model)) continue;
@@ -17327,29 +17661,44 @@ function generateModels$4(models, ctx) {
17327
17661
  if (structName === canonicalStruct) continue;
17328
17662
  const hash = modelHashMap.get(model.name);
17329
17663
  const aliases = (hashGroups.get(hash) ?? []).filter((n) => aliasOf.has(n) && className$3(n) !== className$3(aliasOf.get(n)));
17664
+ const blockLines = [];
17665
+ const blockNames = [];
17666
+ let blockInScope = false;
17330
17667
  if (aliases.length >= 5) {
17331
17668
  for (const aliasName of aliases) batchedAliases.add(aliasName);
17332
- lines.push(`// The following types are structurally identical to ${canonicalStruct}.`);
17333
- lines.push("type (");
17334
- for (const aliasName of aliases) lines.push(`\t${className$3(aliasName)} = ${canonicalStruct}`);
17335
- lines.push(")");
17336
- lines.push("");
17669
+ const emittedAliases = aliases.filter(aliasEmitted);
17670
+ if (emittedAliases.length === 0) continue;
17671
+ blockLines.push(`// The following types are structurally identical to ${canonicalStruct}.`);
17672
+ blockLines.push("type (");
17673
+ for (const aliasName of emittedAliases) {
17674
+ blockLines.push(`\t${className$3(aliasName)} = ${canonicalStruct}`);
17675
+ blockNames.push(className$3(aliasName));
17676
+ if (isModelInScope(aliasName, ctx)) blockInScope = true;
17677
+ }
17678
+ blockLines.push(")");
17337
17679
  } else {
17338
- lines.push(`// ${structName} is an alias for ${canonicalStruct}.`);
17339
- lines.push(`type ${structName} = ${canonicalStruct}`);
17340
- lines.push("");
17341
- }
17680
+ blockLines.push(`// ${structName} is an alias for ${canonicalStruct}.`);
17681
+ blockLines.push(`type ${structName} = ${canonicalStruct}`);
17682
+ blockNames.push(structName);
17683
+ if (isModelInScope(model.name, ctx)) blockInScope = true;
17684
+ }
17685
+ modelBlocks.push({
17686
+ names: blockNames,
17687
+ text: blockLines.join("\n"),
17688
+ inScope: blockInScope
17689
+ });
17342
17690
  continue;
17343
17691
  }
17692
+ const blockLines = [];
17344
17693
  if (model.description) {
17345
17694
  const descLines = model.description.split("\n").filter((l) => l.trim());
17346
- lines.push(`// ${structName} ${lowerFirst$1(descLines[0])}`);
17347
- for (let i = 1; i < descLines.length; i++) lines.push(`// ${descLines[i].trim()}`);
17695
+ blockLines.push(`// ${structName} ${lowerFirst$1(descLines[0])}`);
17696
+ for (let i = 1; i < descLines.length; i++) blockLines.push(`// ${descLines[i].trim()}`);
17348
17697
  } else {
17349
17698
  const humanized = humanize$3(model.name);
17350
- lines.push(`// ${structName} represents ${articleFor$1(humanized)} ${humanized}.`);
17699
+ blockLines.push(`// ${structName} represents ${articleFor$1(humanized)} ${humanized}.`);
17351
17700
  }
17352
- lines.push(`type ${structName} struct {`);
17701
+ blockLines.push(`type ${structName} struct {`);
17353
17702
  const seenFieldNames = /* @__PURE__ */ new Set();
17354
17703
  for (const field of model.fields) {
17355
17704
  const goFieldName = domainFieldName$3(field);
@@ -17359,17 +17708,26 @@ function generateModels$4(models, ctx) {
17359
17708
  const jsonTag = field.required ? `json:"${field.name}"` : `json:"${field.name},omitempty"`;
17360
17709
  if (field.description) {
17361
17710
  const fdLines = field.description.split("\n").filter((l) => l.trim());
17362
- lines.push(`\t// ${fieldDocComment(goFieldName, fdLines[0])}`);
17363
- for (let i = 1; i < fdLines.length; i++) lines.push(`\t// ${fdLines[i].trim()}`);
17711
+ blockLines.push(`\t// ${fieldDocComment(goFieldName, fdLines[0])}`);
17712
+ for (let i = 1; i < fdLines.length; i++) blockLines.push(`\t// ${fdLines[i].trim()}`);
17364
17713
  }
17365
17714
  if (field.deprecated) {
17366
- if (field.description) lines.push(`\t//`);
17715
+ if (field.description) blockLines.push(`\t//`);
17367
17716
  const deprecationReason = extractDeprecationReason(field.description);
17368
- lines.push(`\t// Deprecated: ${deprecationReason}`);
17717
+ blockLines.push(`\t// Deprecated: ${deprecationReason}`);
17369
17718
  }
17370
- lines.push(`\t${goFieldName} ${goType} \`${jsonTag}\``);
17719
+ blockLines.push(`\t${goFieldName} ${goType} \`${jsonTag}\``);
17371
17720
  }
17372
- lines.push("}");
17721
+ blockLines.push("}");
17722
+ modelBlocks.push({
17723
+ names: [structName],
17724
+ text: blockLines.join("\n"),
17725
+ inScope: isModelInScope(model.name, ctx) || forcedCanonicals.has(model.name)
17726
+ });
17727
+ }
17728
+ const reconciled = reconcileFlatBlocks(modelBlocks, "models.go", ctx, new Set(["PaginationParams"]));
17729
+ for (const text of reconciled) {
17730
+ lines.push(text);
17373
17731
  lines.push("");
17374
17732
  }
17375
17733
  const orderEnumType = detectSharedOrderEnum(ctx.spec.services);
@@ -17532,27 +17890,36 @@ function extractDeprecationReason(description) {
17532
17890
  function generateEnums$4(enums, ctx) {
17533
17891
  if (enums.length === 0) return [];
17534
17892
  const aliasOf = collectEnumAliasOf$2(enums);
17893
+ const forcedCanonicals = /* @__PURE__ */ new Set();
17894
+ if (isScopedRun(ctx)) {
17895
+ for (const [aliasName, canonical] of aliasOf) if (isEnumInScope(aliasName, ctx)) forcedCanonicals.add(canonical);
17896
+ }
17535
17897
  const files = [];
17536
17898
  const lines = [];
17537
17899
  lines.push(`package ${ctx.namespace}`);
17538
17900
  lines.push("");
17901
+ const enumBlocks = [];
17539
17902
  for (const enumDef of enums) {
17540
17903
  const canonicalName = aliasOf.get(enumDef.name);
17541
17904
  if (canonicalName) {
17542
17905
  const aliasType = className$3(enumDef.name);
17543
17906
  const canonicalType = className$3(canonicalName);
17544
17907
  if (aliasType === canonicalType) continue;
17545
- lines.push(`// ${aliasType} is an alias for ${canonicalType}.`);
17546
- lines.push(`type ${aliasType} = ${canonicalType}`);
17547
- lines.push("");
17908
+ enumBlocks.push({
17909
+ names: [aliasType],
17910
+ text: [`// ${aliasType} is an alias for ${canonicalType}.`, `type ${aliasType} = ${canonicalType}`].join("\n"),
17911
+ inScope: isEnumInScope(enumDef.name, ctx)
17912
+ });
17548
17913
  continue;
17549
17914
  }
17550
17915
  const typeName = className$3(enumDef.name);
17551
17916
  if (enumDef.values.length === 0) {
17552
17917
  const humanized = humanize$2(enumDef.name);
17553
- lines.push(`// ${typeName} represents ${humanized} values.`);
17554
- lines.push(`type ${typeName} = string`);
17555
- lines.push("");
17918
+ enumBlocks.push({
17919
+ names: [typeName],
17920
+ text: [`// ${typeName} represents ${humanized} values.`, `type ${typeName} = string`].join("\n"),
17921
+ inScope: isEnumInScope(enumDef.name, ctx) || forcedCanonicals.has(enumDef.name)
17922
+ });
17556
17923
  continue;
17557
17924
  }
17558
17925
  const seenValues = /* @__PURE__ */ new Set();
@@ -17565,10 +17932,11 @@ function generateEnums$4(enums, ctx) {
17565
17932
  }
17566
17933
  }
17567
17934
  const humanized = humanize$2(enumDef.name);
17568
- lines.push(`// ${typeName} represents ${humanized} values.`);
17569
- lines.push(`type ${typeName} string`);
17570
- lines.push("");
17571
- lines.push("const (");
17935
+ const blockLines = [];
17936
+ blockLines.push(`// ${typeName} represents ${humanized} values.`);
17937
+ blockLines.push(`type ${typeName} string`);
17938
+ blockLines.push("");
17939
+ blockLines.push("const (");
17572
17940
  const usedNames = /* @__PURE__ */ new Set();
17573
17941
  for (const v of uniqueValues) {
17574
17942
  let constSuffix = className$3(String(v.value));
@@ -17580,14 +17948,23 @@ function generateEnums$4(enums, ctx) {
17580
17948
  const constName = `${typeName}${constSuffix}`;
17581
17949
  usedNames.add(constName);
17582
17950
  const valueStr = typeof v.value === "string" ? `"${v.value}"` : String(v.value);
17583
- if (v.description) lines.push(`\t// ${constName} is ${v.description}.`);
17951
+ if (v.description) blockLines.push(`\t// ${constName} is ${v.description}.`);
17584
17952
  if (v.deprecated) {
17585
- if (v.description) lines.push(`\t//`);
17586
- lines.push(`\t// Deprecated: this value is deprecated.`);
17953
+ if (v.description) blockLines.push(`\t//`);
17954
+ blockLines.push(`\t// Deprecated: this value is deprecated.`);
17587
17955
  }
17588
- lines.push(`\t${constName} ${typeName} = ${valueStr}`);
17956
+ blockLines.push(`\t${constName} ${typeName} = ${valueStr}`);
17589
17957
  }
17590
- lines.push(")");
17958
+ blockLines.push(")");
17959
+ enumBlocks.push({
17960
+ names: [typeName],
17961
+ text: blockLines.join("\n"),
17962
+ inScope: isEnumInScope(enumDef.name, ctx) || forcedCanonicals.has(enumDef.name)
17963
+ });
17964
+ }
17965
+ const reconciled = reconcileFlatBlocks(enumBlocks, "enums.go", ctx);
17966
+ for (const text of reconciled) {
17967
+ lines.push(text);
17591
17968
  lines.push("");
17592
17969
  }
17593
17970
  files.push({
@@ -17595,7 +17972,7 @@ function generateEnums$4(enums, ctx) {
17595
17972
  content: lines.join("\n"),
17596
17973
  overwriteExisting: true
17597
17974
  });
17598
- const eventConstantsFile = generateEventConstantsFile(enums);
17975
+ const eventConstantsFile = generateEventConstantsFile(enums, ctx);
17599
17976
  if (eventConstantsFile) files.push(eventConstantsFile);
17600
17977
  return files;
17601
17978
  }
@@ -17657,9 +18034,15 @@ function collectEnumAliasOf$2(enums) {
17657
18034
  }
17658
18035
  return aliasOf;
17659
18036
  }
17660
- function generateEventConstantsFile(enums) {
18037
+ const EVENTS_FILE_PATH = "pkg/events/events.go";
18038
+ function generateEventConstantsFile(enums, ctx) {
17661
18039
  const enumDef = findWebhookEventEnum(enums);
17662
18040
  if (!enumDef) return null;
18041
+ let priorEventConsts = null;
18042
+ if (isScopedRun(ctx)) {
18043
+ const prior = readPriorFile(EVENTS_FILE_PATH, ctx);
18044
+ if (prior !== null) priorEventConsts = collectPriorEventConstNames(prior);
18045
+ }
17663
18046
  const lines = [];
17664
18047
  lines.push("package events");
17665
18048
  lines.push("");
@@ -17674,6 +18057,7 @@ function generateEventConstantsFile(enums) {
17674
18057
  if (seenValues.has(valueStr)) continue;
17675
18058
  seenValues.add(valueStr);
17676
18059
  const constName = uniqueEventConstantName(valueStr, usedNames);
18060
+ if (priorEventConsts !== null && !priorEventConsts.has(constName)) continue;
17677
18061
  usedNames.add(constName);
17678
18062
  if (value.description) lines.push(`\t// ${constName} is ${value.description}.`);
17679
18063
  if (value.deprecated) {
@@ -17685,11 +18069,25 @@ function generateEventConstantsFile(enums) {
17685
18069
  lines.push(")");
17686
18070
  lines.push("");
17687
18071
  return {
17688
- path: "pkg/events/events.go",
18072
+ path: EVENTS_FILE_PATH,
17689
18073
  content: lines.join("\n"),
17690
18074
  overwriteExisting: true
17691
18075
  };
17692
18076
  }
18077
+ /**
18078
+ * Collect the constant names declared in a prior `pkg/events/events.go`. Each
18079
+ * event constant is emitted as `\tConstName = "wire.value"` inside the single
18080
+ * `const (...)` block, so a simple line scan recovers the prior name set used
18081
+ * to keep the file byte-stable across scoped runs.
18082
+ */
18083
+ function collectPriorEventConstNames(content) {
18084
+ const names = /* @__PURE__ */ new Set();
18085
+ for (const raw of content.split("\n")) {
18086
+ const m = raw.trim().match(/^(\w+)\s*=\s*"/);
18087
+ if (m) names.add(m[1]);
18088
+ }
18089
+ return names;
18090
+ }
17693
18091
  function findWebhookEventEnum(enums) {
17694
18092
  return enums.find((enumDef) => enumDef.name === "CreateWebhookEndpointEvents") ?? enums.find((enumDef) => isWebhookEventEnumName(enumDef.name) && enumDef.values.length > 0 && enumDef.values.every((value) => typeof value.value === "string" && value.value.includes("."))) ?? null;
17695
18093
  }
@@ -17939,8 +18337,8 @@ function resolveResourceClassName$1(service, ctx) {
17939
18337
  function generateResources$4(services, ctx) {
17940
18338
  if (services.length === 0) return [];
17941
18339
  const files = [];
17942
- const mountGroups = groupByMount(ctx);
17943
- const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
18340
+ const mountGroups = scopedMountGroups(ctx);
18341
+ const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
17944
18342
  name,
17945
18343
  operations: group.operations
17946
18344
  })) : services.map((s) => ({
@@ -18902,8 +19300,15 @@ const ID_PREFIXES$1 = {
18902
19300
  };
18903
19301
  /**
18904
19302
  * Generate JSON fixture files for test data.
18905
- */
18906
- function generateFixtures$2(spec) {
19303
+ *
19304
+ * Scoped runs only emit a fixture for a model whose per-model fixture FILE will
19305
+ * exist on disk after the run (in-scope, or already present from a prior run —
19306
+ * the fixture path is per-model so the prior manifest records it directly).
19307
+ * This drops brand-new out-of-scope fixtures (the round-trip ADDITION case) and
19308
+ * keeps prior fixtures untouched, mirroring the Rust fixtures fix. `ctx` is
19309
+ * optional so unit tests that assert raw content can call it for a full run.
19310
+ */
19311
+ function generateFixtures$2(spec, ctx) {
18907
19312
  if (spec.models.length === 0) return {
18908
19313
  files: [],
18909
19314
  pathRewrites: /* @__PURE__ */ new Map()
@@ -18913,12 +19318,15 @@ function generateFixtures$2(spec) {
18913
19318
  const files = [];
18914
19319
  const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
18915
19320
  const listMetadataNeeded = collectReferencedListMetadataModels(spec.models, nonPaginatedRefs);
19321
+ const fixtureInScope = (relPath, modelName) => !ctx || fileExistsAfterRun(relPath, isModelInScope(modelName, ctx), ctx);
18916
19322
  for (const model of spec.models) {
18917
19323
  if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
18918
19324
  if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
19325
+ const path = `testdata/${fileName$1(model.name)}.json`;
19326
+ if (!fixtureInScope(path, model.name)) continue;
18919
19327
  const fixture = model.fields.length === 0 ? {} : generateModelFixture$2(model, modelMap, enumMap);
18920
19328
  files.push({
18921
- path: `testdata/${fileName$1(model.name)}.json`,
19329
+ path,
18922
19330
  content: JSON.stringify(fixture, null, 2)
18923
19331
  });
18924
19332
  }
@@ -18932,6 +19340,7 @@ function generateFixtures$2(spec) {
18932
19340
  const path = `testdata/list_${fileName$1(itemModel.name)}.json`;
18933
19341
  if (seenListPaths.has(path)) continue;
18934
19342
  seenListPaths.add(path);
19343
+ if (!fixtureInScope(path, itemModel.name)) continue;
18935
19344
  const listFixture = {
18936
19345
  data: [generateModelFixture$2(itemModel, modelMap, enumMap)],
18937
19346
  list_metadata: {
@@ -19093,15 +19502,15 @@ function generateTests$4(spec, ctx) {
19093
19502
  content: helperLines.join("\n"),
19094
19503
  overwriteExisting: true
19095
19504
  });
19096
- const { files: fixtures, pathRewrites: fixtureRewrites } = generateFixtures$2(spec);
19505
+ const { files: fixtures, pathRewrites: fixtureRewrites } = generateFixtures$2(spec, ctx);
19097
19506
  for (const fixture of fixtures) files.push({
19098
19507
  path: fixture.path,
19099
19508
  content: fixture.content,
19100
19509
  headerPlacement: "skip"
19101
19510
  });
19102
19511
  const accessPaths = buildServiceAccessPaths$1(spec.services, ctx);
19103
- const mountGroups = groupByMount(ctx);
19104
- const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
19512
+ const mountGroups = scopedMountGroups(ctx);
19513
+ const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
19105
19514
  name,
19106
19515
  operations: group.operations
19107
19516
  })) : spec.services.map((s) => ({
@@ -20244,7 +20653,7 @@ function generateModels$3(models, ctx, discCtx) {
20244
20653
  }
20245
20654
  lines.push(" }");
20246
20655
  lines.push("}");
20247
- files.push({
20656
+ if (isModelInScope(model.name, ctx)) files.push({
20248
20657
  path: `Entities/${csClassName}.cs`,
20249
20658
  content: lines.join("\n"),
20250
20659
  overwriteExisting: true
@@ -20488,7 +20897,7 @@ function generateEnums$3(enums, ctx) {
20488
20897
  }
20489
20898
  lines.push(" }");
20490
20899
  lines.push("}");
20491
- files.push({
20900
+ if (isEnumInScope(enumDef.name, ctx)) files.push({
20492
20901
  path: `Enums/${typeName}.cs`,
20493
20902
  content: lines.join("\n"),
20494
20903
  overwriteExisting: true
@@ -20745,8 +21154,8 @@ function resolveResourceClassName(service, ctx) {
20745
21154
  function generateResources$3(services, ctx) {
20746
21155
  if (services.length === 0) return [];
20747
21156
  const files = [];
20748
- const mountGroups = groupByMount(ctx);
20749
- const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
21157
+ const mountGroups = scopedMountGroups(ctx);
21158
+ const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
20750
21159
  name,
20751
21160
  operations: group.operations
20752
21161
  })) : services.map((s) => ({
@@ -21393,6 +21802,13 @@ function generateClientFile(spec, ctx) {
21393
21802
  //#endregion
21394
21803
  //#region src/dotnet/fixtures.ts
21395
21804
  /**
21805
+ * Prefix the per-model fixture path with the .NET test project layout so the
21806
+ * scoped-run gate can compare it against the prefixed paths recorded in the
21807
+ * prior manifest. Must mirror `TEST_PREFIX` in index.ts; the fixture path
21808
+ * itself (`testdata/<name>.json`) is what `prefixTestPaths` later prepends to.
21809
+ */
21810
+ const TEST_PREFIX$2 = "test/WorkOSTests/";
21811
+ /**
21396
21812
  * Prefix mapping for generating realistic ID fixture values.
21397
21813
  */
21398
21814
  const ID_PREFIXES = {
@@ -21413,7 +21829,7 @@ const ID_PREFIXES = {
21413
21829
  /**
21414
21830
  * Generate JSON fixture files for test data.
21415
21831
  */
21416
- function generateFixtures$1(spec) {
21832
+ function generateFixtures$1(spec, ctx) {
21417
21833
  if (spec.models.length === 0) return [];
21418
21834
  const modelMap = new Map(spec.models.map((m) => [m.name, m]));
21419
21835
  const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
@@ -21422,9 +21838,11 @@ function generateFixtures$1(spec) {
21422
21838
  for (const model of spec.models) {
21423
21839
  if (isListMetadataModel(model)) continue;
21424
21840
  if (isListWrapperModel(model) && !nonPaginatedWrapperRefs.has(model.name)) continue;
21841
+ const fixturePath = `testdata/${fixtureFileName(model.name)}.json`;
21842
+ if (!fileExistsAfterRun(`${TEST_PREFIX$2}${fixturePath}`, isModelInScope(model.name, ctx), ctx)) continue;
21425
21843
  const fixture = model.fields.length === 0 ? {} : generateModelFixture$1(model, modelMap, enumMap);
21426
21844
  files.push({
21427
- path: `testdata/${fixtureFileName(model.name)}.json`,
21845
+ path: fixturePath,
21428
21846
  content: JSON.stringify(fixture, null, 2)
21429
21847
  });
21430
21848
  if (model.fields.some((f) => !f.required || f.type.kind === "nullable") && model.fields.length > 0) {
@@ -21551,14 +21969,14 @@ function generatePrimitiveValue(type, format, name, modelName) {
21551
21969
  */
21552
21970
  function generateTests$3(spec, ctx) {
21553
21971
  const files = [];
21554
- const fixtures = generateFixtures$1(spec);
21972
+ const fixtures = generateFixtures$1(spec, ctx);
21555
21973
  for (const fixture of fixtures) files.push({
21556
21974
  path: fixture.path,
21557
21975
  content: fixture.content,
21558
21976
  headerPlacement: "skip"
21559
21977
  });
21560
- const mountGroups = groupByMount(ctx);
21561
- const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
21978
+ const mountGroups = scopedMountGroups(ctx);
21979
+ const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
21562
21980
  name,
21563
21981
  operations: group.operations
21564
21982
  })) : spec.services.map((s) => ({
@@ -22083,6 +22501,26 @@ function prefixTestPaths(files) {
22083
22501
  for (const f of files) f.path = `${TEST_PREFIX$1}${f.path}`;
22084
22502
  return files;
22085
22503
  }
22504
+ /**
22505
+ * Scoped-run gate for a discriminated-union variant referenced by a generated
22506
+ * converter (the event-registry / polymorphic dispatch aggregate). A converter
22507
+ * may only name a variant whose per-model `Entities/<Class>.cs` file will exist
22508
+ * on disk after the run — otherwise the `new <Class>()` / `ToObject<<Class>>`
22509
+ * arm references a type whose file is never emitted (CS0246). The set that
22510
+ * exists after the run is: in-scope variants (emitted this run) ∪ variants
22511
+ * already on disk from a prior manifest (scoped runs never prune), so a
22512
+ * renamed/removed-but-still-on-disk variant is RETAINED while a brand-new
22513
+ * out-of-scope variant is EXCLUDED. A full run includes everything.
22514
+ *
22515
+ * The variant name is resolved through the model-alias map first, exactly as
22516
+ * the converter emits it (`modelClassName(resolveModelName(name))`), and the
22517
+ * candidate path carries the `src/WorkOS.net/` prefix so it matches the
22518
+ * prefixed paths recorded in `priorTargetManifestPaths`.
22519
+ */
22520
+ function variantFileExistsAfterRun(variantModelName, ctx) {
22521
+ const canonical = resolveModelName(variantModelName);
22522
+ return fileExistsAfterRun(`${SRC_PREFIX}Entities/${modelClassName(canonical)}.cs`, isModelInScope(canonical, ctx), ctx);
22523
+ }
22086
22524
  const dotnetEmitter = {
22087
22525
  language: "dotnet",
22088
22526
  generateModels(models, ctx) {
@@ -22142,6 +22580,7 @@ const dotnetEmitter = {
22142
22580
  lines.push(" switch (discriminatorValue)");
22143
22581
  lines.push(" {");
22144
22582
  for (const [value, modelName] of Object.entries(disc.mapping)) {
22583
+ if (!variantFileExistsAfterRun(modelName, c)) continue;
22145
22584
  const csName = modelClassName(resolveModelName(modelName));
22146
22585
  lines.push(` case "${value}": return jObject.ToObject<${csName}>(serializer);`);
22147
22586
  }
@@ -22162,6 +22601,7 @@ const dotnetEmitter = {
22162
22601
  });
22163
22602
  }
22164
22603
  for (const [baseName, disc] of modelDiscriminators) {
22604
+ if (!variantFileExistsAfterRun(baseName, c)) continue;
22165
22605
  const baseClass = modelClassName(baseName);
22166
22606
  const converterName = `${baseClass}DiscriminatorConverter`;
22167
22607
  const lines = [];
@@ -22190,6 +22630,7 @@ const dotnetEmitter = {
22190
22630
  lines.push(" switch (discriminatorValue)");
22191
22631
  lines.push(" {");
22192
22632
  for (const [value, variantModelName] of Object.entries(disc.mapping)) {
22633
+ if (!variantFileExistsAfterRun(variantModelName, c)) continue;
22193
22634
  const csName = modelClassName(variantModelName);
22194
22635
  lines.push(` case "${value}": target = new ${csName}(); break;`);
22195
22636
  }
@@ -22536,7 +22977,7 @@ const enumCanonicalMap = /* @__PURE__ */ new Map();
22536
22977
  * shortest PascalCase name becomes canonical and the rest emit `typealias`
22537
22978
  * files pointing at the canonical class.
22538
22979
  */
22539
- function generateEnums$2(enums, _ctx) {
22980
+ function generateEnums$2(enums, ctx) {
22540
22981
  if (enums.length === 0) return [];
22541
22982
  enumCanonicalMap.clear();
22542
22983
  const hashGroups = /* @__PURE__ */ new Map();
@@ -22573,6 +23014,7 @@ function generateEnums$2(enums, _ctx) {
22573
23014
  for (const enumDef of enums) {
22574
23015
  if (enumDef.values.length === 0) continue;
22575
23016
  const typeName = canonicalEnumTypeName(enumDef);
23017
+ const enumInScope = isEnumInScope(enumDef.name, ctx);
22576
23018
  const canonicalName = sharedSortEmitters.has(enumDef.name) ? void 0 : aliasOf.get(enumDef.name) ?? enumCanonicalMap.get(enumDef.name);
22577
23019
  if (canonicalName) {
22578
23020
  const canonicalType = className$1(canonicalName);
@@ -22585,7 +23027,7 @@ function generateEnums$2(enums, _ctx) {
22585
23027
  aliasLine,
22586
23028
  ""
22587
23029
  ].join("\n");
22588
- files.push({
23030
+ if (enumInScope) files.push({
22589
23031
  path: `${KOTLIN_SRC_PREFIX$3}${ENUMS_DIR}/${typeName}.kt`,
22590
23032
  content: aliasContent,
22591
23033
  overwriteExisting: true
@@ -22641,7 +23083,7 @@ function generateEnums$2(enums, _ctx) {
22641
23083
  }
22642
23084
  lines.push("}");
22643
23085
  lines.push("");
22644
- files.push({
23086
+ if (enumInScope) files.push({
22645
23087
  path: `${KOTLIN_SRC_PREFIX$3}${ENUMS_DIR}/${typeName}.kt`,
22646
23088
  content: lines.join("\n"),
22647
23089
  overwriteExisting: true
@@ -22746,6 +23188,16 @@ const KOTLIN_SRC_PREFIX$2 = "src/main/kotlin/";
22746
23188
  const MODELS_PACKAGE = "com.workos.models";
22747
23189
  const MODELS_DIR = "com/workos/models";
22748
23190
  /**
23191
+ * The relative path (target-root-relative, matching the prior manifest) of the
23192
+ * per-model `.kt` FILE the emitter writes for a model. The aggregate gate
23193
+ * ({@link fileExistsAfterRun}) checks this exact path against the freshly-emitted
23194
+ * in-scope set and the prior manifest. Must stay in sync with the path used in
23195
+ * {@link emitDataClass} / {@link emitSealedUnion} / the typealias branch.
23196
+ */
23197
+ function modelFilePath$1(modelName) {
23198
+ return `${KOTLIN_SRC_PREFIX$2}${MODELS_DIR}/${className$1(modelName)}.kt`;
23199
+ }
23200
+ /**
22749
23201
  * Some specs leave string fields without `format: date-time` even though the
22750
23202
  * description (or the example) makes clear they carry an ISO-8601 timestamp.
22751
23203
  * Detect that here so we can promote the type to `OffsetDateTime` in the
@@ -22838,8 +23290,9 @@ function generateModels$2(models, ctx) {
22838
23290
  for (const model of models) {
22839
23291
  if (skipAsListWrapper(model) || skipAsListMetadata(model)) continue;
22840
23292
  const typeName = className$1(model.name);
23293
+ const modelInScope = isModelInScope(model.name, ctx);
22841
23294
  if (model.fields.length === 0 && discriminatedUnions.has(typeName)) {
22842
- files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName)));
23295
+ if (modelInScope) files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName), ctx));
22843
23296
  continue;
22844
23297
  }
22845
23298
  const canonical = aliasOf.get(model.name);
@@ -22853,20 +23306,21 @@ function generateModels$2(models, ctx) {
22853
23306
  `typealias ${typeName} = ${canonicalType}`,
22854
23307
  ""
22855
23308
  ].join("\n");
22856
- files.push({
23309
+ if (modelInScope) files.push({
22857
23310
  path: `${KOTLIN_SRC_PREFIX$2}${MODELS_DIR}/${typeName}.kt`,
22858
23311
  content: aliasContent,
22859
23312
  overwriteExisting: true
22860
23313
  });
22861
23314
  continue;
22862
23315
  }
22863
- files.push(emitDataClass(model));
23316
+ if (modelInScope) files.push(emitDataClass(model));
22864
23317
  }
22865
23318
  const eventMapping = [];
22866
23319
  for (const model of models) {
22867
23320
  if (skipAsListWrapper(model) || skipAsListMetadata(model)) continue;
22868
23321
  if (aliasOf.has(model.name)) continue;
22869
23322
  if (!isEventEnvelopeModel(model)) continue;
23323
+ if (!fileExistsAfterRun(modelFilePath$1(model.name), isModelInScope(model.name, ctx), ctx)) continue;
22870
23324
  const eventField = model.fields.find((f) => f.name === "event");
22871
23325
  if (eventField && eventField.type.kind === "literal" && typeof eventField.type.value === "string") eventMapping.push({
22872
23326
  wireValue: eventField.type.value,
@@ -22924,15 +23378,15 @@ function emitDataClass(model) {
22924
23378
  overwriteExisting: true
22925
23379
  };
22926
23380
  }
22927
- function emitSealedUnion(typeName, disc) {
23381
+ function emitSealedUnion(typeName, disc, ctx) {
22928
23382
  const lines = [];
22929
23383
  lines.push(`package ${MODELS_PACKAGE}`);
22930
23384
  lines.push("");
22931
23385
  lines.push("import com.fasterxml.jackson.annotation.JsonSubTypes");
22932
23386
  lines.push("import com.fasterxml.jackson.annotation.JsonTypeInfo");
22933
23387
  lines.push("");
22934
- const exampleVariantWire = Object.keys(disc.mapping)[0];
22935
- const exampleVariantType = exampleVariantWire ? className$1(disc.mapping[exampleVariantWire]) : null;
23388
+ const entries = Object.entries(disc.mapping).filter(([, modelName]) => fileExistsAfterRun(modelFilePath$1(modelName), isModelInScope(modelName, ctx), ctx));
23389
+ const exampleVariantType = entries.length > 0 ? className$1(entries[0][1]) : null;
22936
23390
  lines.push("/**");
22937
23391
  lines.push(` * Discriminated union over ${typeName} variants. Selected by \`${disc.property}\`.`);
22938
23392
  if (exampleVariantType) {
@@ -22961,7 +23415,6 @@ function emitSealedUnion(typeName, disc) {
22961
23415
  lines.push(" visible = true");
22962
23416
  lines.push(")");
22963
23417
  lines.push("@JsonSubTypes(");
22964
- const entries = Object.entries(disc.mapping);
22965
23418
  for (let i = 0; i < entries.length; i++) {
22966
23419
  const [wireValue, modelName] = entries[i];
22967
23420
  const variantType = className$1(modelName);
@@ -23554,7 +24007,7 @@ function promoteFieldType(f) {
23554
24007
  */
23555
24008
  function generateResources$2(services, ctx) {
23556
24009
  if (services.length === 0) return [];
23557
- const mountGroups = groupByMount(ctx);
24010
+ const mountGroups = scopedMountGroups(ctx);
23558
24011
  if (mountGroups.size === 0) return [];
23559
24012
  const files = [];
23560
24013
  const resolvedLookup = buildResolvedLookup(ctx);
@@ -24418,6 +24871,14 @@ function deduplicateByMount(services, ctx) {
24418
24871
  //#endregion
24419
24872
  //#region src/kotlin/tests.ts
24420
24873
  const TEST_PREFIX = "src/test/kotlin/";
24874
+ const MODELS_FILE_PREFIX = "src/main/kotlin/com/workos/models/";
24875
+ const ENUMS_FILE_PREFIX = "src/main/kotlin/com/workos/types/";
24876
+ function modelFilePath(modelName) {
24877
+ return `${MODELS_FILE_PREFIX}${className$1(modelName)}.kt`;
24878
+ }
24879
+ function enumFilePath(enumName) {
24880
+ return `${ENUMS_FILE_PREFIX}${className$1(enumName)}.kt`;
24881
+ }
24421
24882
  /**
24422
24883
  * Mirror the ISO-8601 hint promotion the resource/model emitters use so tests
24423
24884
  * synthesize values whose Kotlin type matches the generated method signature.
@@ -24462,7 +24923,7 @@ function promoteIso8601TypeRef(type, description) {
24462
24923
  */
24463
24924
  function generateTests$2(spec, ctx) {
24464
24925
  const files = [];
24465
- const mountGroups = groupByMount(ctx);
24926
+ const mountGroups = scopedMountGroups(ctx);
24466
24927
  const resolvedLookup = buildResolvedLookup(ctx);
24467
24928
  const exportedClasses = buildExportedClassNameSet$1(ctx);
24468
24929
  for (const [mountName, group] of mountGroups) {
@@ -25168,6 +25629,7 @@ function generateModelRoundTripTest$1(spec, ctx) {
25168
25629
  for (const m of spec.models) {
25169
25630
  if (isListWrapperModel(m) || isListMetadataModel(m)) continue;
25170
25631
  if (m.fields.length === 0) continue;
25632
+ if (!fileExistsAfterRun(modelFilePath(m.name), isModelInScope(m.name, ctx), ctx)) continue;
25171
25633
  const cls = className$1(m.name);
25172
25634
  if (seenModelClassNames.has(cls)) continue;
25173
25635
  seenModelClassNames.add(cls);
@@ -25216,10 +25678,11 @@ function generateModelRoundTripTest$1(spec, ctx) {
25216
25678
  */
25217
25679
  const MAX_ENUM_FORWARD_COMPAT = 15;
25218
25680
  function generateForwardCompatTest(spec, ctx) {
25219
- const enumTargets = spec.enums.filter((e) => e.values.length > 0).slice(0, MAX_ENUM_FORWARD_COMPAT);
25681
+ const enumTargets = spec.enums.filter((e) => e.values.length > 0).filter((e) => fileExistsAfterRun(enumFilePath(e.name), isEnumInScope(e.name, ctx), ctx)).slice(0, MAX_ENUM_FORWARD_COMPAT);
25220
25682
  const modelTarget = spec.models.find((m) => {
25221
25683
  if (isListWrapperModel(m) || isListMetadataModel(m)) return false;
25222
25684
  if (m.fields.length === 0) return false;
25685
+ if (!fileExistsAfterRun(modelFilePath(m.name), isModelInScope(m.name, ctx), ctx)) return false;
25223
25686
  return synthJsonForModelName(m.name, ctx, /* @__PURE__ */ new Set()) !== null;
25224
25687
  });
25225
25688
  if (enumTargets.length === 0 && !modelTarget) return null;
@@ -25624,7 +26087,7 @@ function generateModels$1(models, ctx) {
25624
26087
  lines.push("module WorkOS");
25625
26088
  lines.push(` ${cls} = ${canonCls}`);
25626
26089
  lines.push("end");
25627
- files.push({
26090
+ if (isModelInScope(model.name, ctx)) files.push({
25628
26091
  path: `lib/workos/${dirFor(model.name)}/${file}.rb`,
25629
26092
  content: lines.join("\n"),
25630
26093
  integrateTarget: true,
@@ -25696,7 +26159,7 @@ function generateModels$1(models, ctx) {
25696
26159
  lines.push(" end");
25697
26160
  lines.push(" end");
25698
26161
  lines.push("end");
25699
- files.push({
26162
+ if (isModelInScope(model.name, ctx)) files.push({
25700
26163
  path: `lib/workos/${dirFor(model.name)}/${file}.rb`,
25701
26164
  content: lines.join("\n"),
25702
26165
  integrateTarget: true,
@@ -25805,6 +26268,7 @@ function generateEnums$1(enums, ctx) {
25805
26268
  const aliasOf = collectEnumAliasOf(enums);
25806
26269
  for (const enumDef of enums) {
25807
26270
  const cls = className(enumDef.name);
26271
+ const enumInScope = isEnumInScope(enumDef.name, ctx);
25808
26272
  const canonicalName = aliasOf.get(enumDef.name);
25809
26273
  if (canonicalName) {
25810
26274
  const canonicalCls = className(canonicalName);
@@ -25814,7 +26278,7 @@ function generateEnums$1(enums, ctx) {
25814
26278
  lines.push(` ${cls} = ${canonicalCls}`);
25815
26279
  lines.push(" end");
25816
26280
  lines.push("end");
25817
- files.push({
26281
+ if (enumInScope) files.push({
25818
26282
  path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
25819
26283
  content: lines.join("\n"),
25820
26284
  integrateTarget: true,
@@ -25843,7 +26307,7 @@ function generateEnums$1(enums, ctx) {
25843
26307
  lines.push(" end");
25844
26308
  lines.push(" end");
25845
26309
  lines.push("end");
25846
- files.push({
26310
+ if (enumInScope) files.push({
25847
26311
  path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
25848
26312
  content: lines.join("\n"),
25849
26313
  integrateTarget: true,
@@ -25880,7 +26344,7 @@ function generateEnums$1(enums, ctx) {
25880
26344
  lines.push(" end");
25881
26345
  lines.push(" end");
25882
26346
  lines.push("end");
25883
- files.push({
26347
+ if (enumInScope) files.push({
25884
26348
  path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
25885
26349
  content: lines.join("\n"),
25886
26350
  integrateTarget: true,
@@ -26258,7 +26722,7 @@ function emitInlineVariantRbi(v) {
26258
26722
  */
26259
26723
  function generateResources$1(services, ctx) {
26260
26724
  const files = [];
26261
- const groups = groupByMount(ctx);
26725
+ const groups = scopedMountGroups(ctx);
26262
26726
  const lookup = buildResolvedLookup(ctx);
26263
26727
  const modelNames = new Set(ctx.spec.models.map((m) => m.name));
26264
26728
  const enumNames = new Set(ctx.spec.enums.map((e) => e.name));
@@ -27036,7 +27500,7 @@ function generateClientClass(spec, ctx) {
27036
27500
  */
27037
27501
  function generateTests$1(spec, ctx) {
27038
27502
  const files = [];
27039
- const groups = groupByMount(ctx);
27503
+ const groups = scopedMountGroups(ctx);
27040
27504
  const models = spec.models;
27041
27505
  const modelByName = /* @__PURE__ */ new Map();
27042
27506
  for (const m of models) modelByName.set(m.name, m);
@@ -27160,15 +27624,26 @@ function generateTests$1(spec, ctx) {
27160
27624
  overwriteExisting: true
27161
27625
  });
27162
27626
  }
27163
- files.push(generateModelRoundTripTest(spec));
27627
+ files.push(generateModelRoundTripTest(spec, ctx));
27164
27628
  return files;
27165
27629
  }
27166
27630
  /**
27167
27631
  * Emit test/workos/model_round_trip_test.rb that round-trips every non-wrapper
27168
27632
  * model through `.new(json)` and `.to_json`, asserting the result is a Hash and
27169
27633
  * that required fields appear with the seeded values.
27170
- */
27171
- function generateModelRoundTripTest(spec) {
27634
+ *
27635
+ * This is a whole-suite AGGREGATE: it references `WorkOS::<Class>` for every
27636
+ * model. Under a scoped (`--services`) run only the selected services' per-model
27637
+ * `.rb` files are (re)written, so a test referencing a brand-new out-of-scope
27638
+ * model whose file was never emitted would raise `NameError: uninitialized
27639
+ * constant`. Each per-model test is therefore gated by {@link fileExistsAfterRun}
27640
+ * on the SAME path `models.ts` writes (`lib/workos/<dir>/<file>.rb`): emit only
27641
+ * when that file will exist on disk after the run — in-scope (emitted this run)
27642
+ * OR already present from a prior run (`priorTargetManifestPaths`). Brand-new
27643
+ * out-of-scope models are excluded; renamed/removed-but-on-disk models are
27644
+ * retained. A full run (scoping inert) keeps every model.
27645
+ */
27646
+ function generateModelRoundTripTest(spec, ctx) {
27172
27647
  const lines = [];
27173
27648
  lines.push(`require 'test_helper'`);
27174
27649
  lines.push("");
@@ -27176,7 +27651,15 @@ function generateModelRoundTripTest(spec) {
27176
27651
  const models = spec.models.filter((m) => !isListWrapperModel(m) && !isListMetadataModel(m));
27177
27652
  const enumNames = new Set(spec.enums.map((e) => e.name));
27178
27653
  const emitted = /* @__PURE__ */ new Set();
27654
+ const modelToService = assignModelsToServices(models, ctx.spec.services, ctx.modelHints);
27655
+ const mountDirMap = buildMountDirMap(ctx);
27656
+ const dirFor = (modelName) => {
27657
+ const service = modelToService.get(modelName);
27658
+ if (!service) return classifyUnassignedModel(modelName);
27659
+ return mountDirMap.get(service) ?? classifyUnassignedModel(modelName);
27660
+ };
27179
27661
  for (const model of models) {
27662
+ if (!fileExistsAfterRun(`lib/workos/${dirFor(model.name)}/${fileName(model.name)}.rb`, isModelInScope(model.name, ctx), ctx)) continue;
27180
27663
  const fileBase = fileName(model.name);
27181
27664
  if (emitted.has(fileBase)) continue;
27182
27665
  emitted.add(fileBase);
@@ -27532,7 +28015,7 @@ function generateRbiFiles(spec, ctx) {
27532
28015
  lines.push(" def to_json(*args); end");
27533
28016
  lines.push(" end");
27534
28017
  lines.push("end");
27535
- files.push({
28018
+ if (isModelInScope(model.name, ctx)) files.push({
27536
28019
  path: `rbi/workos/${fileName(model.name)}.rbi`,
27537
28020
  content: lines.join("\n"),
27538
28021
  integrateTarget: true,
@@ -27548,6 +28031,7 @@ function generateRbiFiles(spec, ctx) {
27548
28031
  const groupOwners = buildGroupOwnerMap(ctx);
27549
28032
  const exportedClasses = buildExportedClassNameSet(ctx);
27550
28033
  for (const [mountTarget, group] of groups) {
28034
+ if (!isMountInScope(mountTarget, ctx)) continue;
27551
28035
  const resolvedTarget = resolveServiceTarget(mountTarget, exportedClasses);
27552
28036
  const cls = className(resolvedTarget);
27553
28037
  const lines = [];
@@ -28162,13 +28646,19 @@ function generateModels(models, ctx, registry) {
28162
28646
  const mod = moduleName(model.name);
28163
28647
  if (seen.has(mod)) continue;
28164
28648
  seen.add(mod);
28165
- moduleNames.push(mod);
28649
+ const inScope = isModelInScope(model.name, ctx);
28166
28650
  const path = ctx.overlayLookup?.fileBySymbol?.get(model.name) ?? `src/models/${mod}.rs`;
28167
- files.push({
28651
+ const content = renderModel(model, registry, taggedVariantFields.get(model.name));
28652
+ if (inScope) files.push({
28168
28653
  path,
28169
- content: renderModel(model, registry, taggedVariantFields.get(model.name)),
28654
+ content,
28170
28655
  overwriteExisting: true
28171
28656
  });
28657
+ if (fileExistsAfterRun(path, inScope, ctx)) moduleNames.push(mod);
28658
+ }
28659
+ for (const base of priorManifestBasenames(ctx, "src/models", ".rs", new Set([UNIONS_MODULE, "mod"]))) if (!seen.has(base)) {
28660
+ seen.add(base);
28661
+ moduleNames.push(base);
28172
28662
  }
28173
28663
  moduleNames.push(UNIONS_MODULE);
28174
28664
  files.push({
@@ -28325,7 +28815,7 @@ function formatDefault$1(value) {
28325
28815
  * variant and re-serialize as the canonical wire string.
28326
28816
  * - `Display`, `FromStr`, and `AsRef<str>` are implemented for ergonomics.
28327
28817
  */
28328
- function generateEnums(enums, _ctx) {
28818
+ function generateEnums(enums, ctx) {
28329
28819
  const files = [];
28330
28820
  const seen = /* @__PURE__ */ new Set();
28331
28821
  const moduleNames = [];
@@ -28334,12 +28824,18 @@ function generateEnums(enums, _ctx) {
28334
28824
  const mod = moduleName(e.name);
28335
28825
  if (seen.has(mod)) continue;
28336
28826
  seen.add(mod);
28337
- moduleNames.push(mod);
28338
- files.push({
28339
- path: `src/enums/${mod}.rs`,
28827
+ const inScope = isEnumInScope(e.name, ctx);
28828
+ const path = `src/enums/${mod}.rs`;
28829
+ if (inScope) files.push({
28830
+ path,
28340
28831
  content: renderEnum(e),
28341
28832
  overwriteExisting: true
28342
28833
  });
28834
+ if (fileExistsAfterRun(path, inScope, ctx)) moduleNames.push(mod);
28835
+ }
28836
+ for (const base of priorManifestBasenames(ctx, "src/enums", ".rs", new Set(["mod"]))) if (!seen.has(base)) {
28837
+ seen.add(base);
28838
+ moduleNames.push(base);
28343
28839
  }
28344
28840
  files.push({
28345
28841
  path: "src/enums/mod.rs",
@@ -28494,6 +28990,7 @@ function generateResources(_services, ctx, registry) {
28494
28990
  module: basename,
28495
28991
  struct
28496
28992
  });
28993
+ if (!isMountInScope(mountName, ctx)) continue;
28497
28994
  files.push({
28498
28995
  path: `src/resources/${basename}.rs`,
28499
28996
  content: renderMountGroup(mountName, group.resolvedOps, ctx, registry, lookup),
@@ -29478,8 +29975,14 @@ function renderResourcesApi(ctx) {
29478
29975
  /**
29479
29976
  * Generate JSON test fixture files under `tests/fixtures/`. The Rust tests
29480
29977
  * pull these in via `include_str!` so no I/O is required at test time.
29978
+ *
29979
+ * Scoped runs only emit a fixture for a model whose file will exist on disk
29980
+ * after the run (in-scope, or already present from a prior run). Emitting a
29981
+ * fixture for a brand-new out-of-scope model would add a stray file for a model
29982
+ * the SDK can't even reference yet; in-scope tests only `include_str!` fixtures
29983
+ * for in-scope models, so gating here is safe.
29481
29984
  */
29482
- function generateFixtures(spec) {
29985
+ function generateFixtures(spec, ctx) {
29483
29986
  const files = [];
29484
29987
  const modelMap = new Map(spec.models.map((m) => [m.name, m]));
29485
29988
  const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
@@ -29488,8 +29991,9 @@ function generateFixtures(spec) {
29488
29991
  if (seen.has(model.name)) continue;
29489
29992
  if (model.fields.length === 0) continue;
29490
29993
  seen.add(model.name);
29491
- const fixture = generateModelFixture(model, modelMap, enumMap, /* @__PURE__ */ new Set());
29492
29994
  const path = `tests/fixtures/${moduleName(model.name)}.json`;
29995
+ if (!fileExistsAfterRun(path, isModelInScope(model.name, ctx), ctx)) continue;
29996
+ const fixture = generateModelFixture(model, modelMap, enumMap, /* @__PURE__ */ new Set());
29493
29997
  files.push({
29494
29998
  path,
29495
29999
  content: JSON.stringify(fixture, null, 2) + "\n",
@@ -29636,13 +30140,13 @@ function matchPrimitive(value, primitive) {
29636
30140
  */
29637
30141
  function generateTests(spec, ctx) {
29638
30142
  const files = [];
29639
- files.push(...generateFixtures(spec));
30143
+ files.push(...generateFixtures(spec, ctx));
29640
30144
  files.push({
29641
30145
  path: "tests/common/mod.rs",
29642
30146
  content: renderCommon(ctx),
29643
30147
  overwriteExisting: true
29644
30148
  });
29645
- const groups = groupByMount(ctx);
30149
+ const groups = scopedMountGroups(ctx);
29646
30150
  const modelMap = new Map(spec.models.map((m) => [m.name, m]));
29647
30151
  const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
29648
30152
  for (const [mountName, group] of groups) {
@@ -30274,4 +30778,4 @@ const workosEmittersPlugin = {
30274
30778
  //#endregion
30275
30779
  export { fieldName$2 as A, servicePropertyName$2 as B, apiClassName as C, dotnetEmitter as D, propertyName as E, fieldName$3 as F, fieldName$5 as H, methodName$3 as I, trimMountedResourceFromMethod$2 as L, trimMountedResourceFromMethod$1 as M, goEmitter as N, appendAsyncSuffix as O, className$3 as P, phpEmitter as R, kotlinEmitter as S, packageSegment as T, safeParamName$1 as U, pythonEmitter as V, nodeEmitter as W, rubyEmitter as _, rustExtractor as a, resolveServiceTarget as b, pythonExtractor as c, rustEmitter as d, fieldName as f, typeName as g, resourceAccessorName as h, kotlinExtractor as i, methodName$2 as j, className$2 as k, rubyExtractor as l, moduleName as m, elixirExtractor as n, goExtractor as o, methodName as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u, buildExportedClassNameSet as v, methodName$1 as w, safeParamName as x, fieldName$1 as y, fieldName$4 as z };
30276
30780
 
30277
- //# sourceMappingURL=plugin-Cciic50q.mjs.map
30781
+ //# sourceMappingURL=plugin-DXIciTnN.mjs.map