@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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-Cciic50q.mjs → plugin-DXIciTnN.mjs} +668 -164
- package/dist/plugin-DXIciTnN.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +4 -4
- package/src/dotnet/enums.ts +11 -5
- package/src/dotnet/fixtures.ts +28 -7
- package/src/dotnet/index.ts +42 -1
- package/src/dotnet/models.ts +11 -5
- package/src/dotnet/resources.ts +3 -3
- package/src/dotnet/tests.ts +4 -4
- package/src/go/enums.ts +91 -18
- package/src/go/fixtures.ts +25 -3
- package/src/go/flat-merge.ts +253 -0
- package/src/go/models.ts +85 -20
- package/src/go/resources.ts +3 -3
- package/src/go/tests.ts +7 -5
- package/src/kotlin/enums.ts +21 -11
- package/src/kotlin/models.ts +53 -11
- package/src/kotlin/resources.ts +2 -2
- package/src/kotlin/tests.ts +38 -3
- package/src/node/enums.ts +8 -5
- package/src/node/models.ts +29 -21
- package/src/node/resources.ts +12 -1
- package/src/node/tests.ts +7 -2
- package/src/php/enums.ts +18 -5
- package/src/php/index.ts +11 -3
- package/src/php/models.ts +11 -5
- package/src/php/resources.ts +6 -4
- package/src/php/tests.ts +6 -3
- package/src/python/enums.ts +39 -28
- package/src/python/fixtures.ts +34 -6
- package/src/python/models.ts +138 -45
- package/src/python/resources.ts +3 -3
- package/src/python/tests.ts +31 -12
- package/src/ruby/enums.ts +28 -19
- package/src/ruby/models.ts +23 -12
- package/src/ruby/rbi.ts +17 -6
- package/src/ruby/resources.ts +2 -2
- package/src/ruby/tests.ts +37 -4
- package/src/rust/enums.ts +29 -7
- package/src/rust/fixtures.ts +12 -3
- package/src/rust/models.ts +37 -6
- package/src/rust/resources.ts +8 -1
- package/src/rust/tests.ts +3 -3
- package/src/shared/resolved-ops.ts +104 -0
- package/test/dotnet/scoped-aggregates.test.ts +247 -0
- package/test/go/scoping.test.ts +324 -0
- package/test/kotlin/models.test.ts +74 -0
- package/test/kotlin/tests.test.ts +33 -0
- package/test/python/scoped-aggregates.test.ts +205 -0
- package/test/ruby/tests.test.ts +130 -0
- package/test/rust/fixtures.test.ts +13 -7
- package/test/shared/synthetic-enum-seed.test.ts +79 -0
- 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
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
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
|
-
|
|
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 (
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
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
|
-
|
|
11816
|
-
|
|
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 (
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
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
|
-
|
|
11970
|
-
|
|
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 (
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
14916
|
-
|
|
14917
|
-
const
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
16988
|
-
|
|
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
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
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
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
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
|
-
|
|
17347
|
-
for (let i = 1; i < descLines.length; i++)
|
|
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
|
-
|
|
17699
|
+
blockLines.push(`// ${structName} represents ${articleFor$1(humanized)} ${humanized}.`);
|
|
17351
17700
|
}
|
|
17352
|
-
|
|
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
|
-
|
|
17363
|
-
for (let i = 1; i < fdLines.length; i++)
|
|
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)
|
|
17715
|
+
if (field.description) blockLines.push(`\t//`);
|
|
17367
17716
|
const deprecationReason = extractDeprecationReason(field.description);
|
|
17368
|
-
|
|
17717
|
+
blockLines.push(`\t// Deprecated: ${deprecationReason}`);
|
|
17369
17718
|
}
|
|
17370
|
-
|
|
17719
|
+
blockLines.push(`\t${goFieldName} ${goType} \`${jsonTag}\``);
|
|
17371
17720
|
}
|
|
17372
|
-
|
|
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
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
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
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
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
|
-
|
|
17569
|
-
|
|
17570
|
-
|
|
17571
|
-
|
|
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)
|
|
17951
|
+
if (v.description) blockLines.push(`\t// ${constName} is ${v.description}.`);
|
|
17584
17952
|
if (v.deprecated) {
|
|
17585
|
-
if (v.description)
|
|
17586
|
-
|
|
17953
|
+
if (v.description) blockLines.push(`\t//`);
|
|
17954
|
+
blockLines.push(`\t// Deprecated: this value is deprecated.`);
|
|
17587
17955
|
}
|
|
17588
|
-
|
|
17956
|
+
blockLines.push(`\t${constName} ${typeName} = ${valueStr}`);
|
|
17589
17957
|
}
|
|
17590
|
-
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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,
|
|
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
|
|
22935
|
-
const exampleVariantType =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
28649
|
+
const inScope = isModelInScope(model.name, ctx);
|
|
28166
28650
|
const path = ctx.overlayLookup?.fileBySymbol?.get(model.name) ?? `src/models/${mod}.rs`;
|
|
28167
|
-
|
|
28651
|
+
const content = renderModel(model, registry, taggedVariantFields.get(model.name));
|
|
28652
|
+
if (inScope) files.push({
|
|
28168
28653
|
path,
|
|
28169
|
-
content
|
|
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,
|
|
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
|
-
|
|
28338
|
-
|
|
28339
|
-
|
|
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 =
|
|
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-
|
|
30781
|
+
//# sourceMappingURL=plugin-DXIciTnN.mjs.map
|