@workos/oagen-emitters 0.19.0 → 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 +7 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-BXDPA9pJ.mjs → plugin-DXIciTnN.mjs} +535 -96
- package/dist/plugin-DXIciTnN.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +1 -1
- package/src/dotnet/fixtures.ts +28 -7
- package/src/dotnet/index.ts +42 -1
- package/src/dotnet/tests.ts +1 -1
- 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/tests.ts +4 -2
- package/src/kotlin/models.ts +36 -6
- package/src/kotlin/tests.ts +36 -1
- package/src/python/fixtures.ts +34 -6
- package/src/python/models.ts +118 -34
- package/src/python/tests.ts +28 -9
- package/src/ruby/tests.ts +35 -2
- package/src/rust/enums.ts +29 -15
- package/src/rust/fixtures.ts +12 -3
- package/src/rust/models.ts +26 -8
- package/src/rust/tests.ts +1 -1
- package/src/shared/resolved-ops.ts +57 -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/dist/plugin-BXDPA9pJ.mjs.map +0 -1
|
@@ -117,6 +117,55 @@ function isEnumInScope(enumName, ctx) {
|
|
|
117
117
|
const scope = ctx.scopedEnumNames;
|
|
118
118
|
return !scope || scope.has(enumName);
|
|
119
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
|
+
}
|
|
120
169
|
/**
|
|
121
170
|
* Get the mount target for an IR service.
|
|
122
171
|
* Checks the first resolved operation that belongs to this service.
|
|
@@ -11830,23 +11879,26 @@ function generateModels$6(models, ctx) {
|
|
|
11830
11879
|
dispLines.push(" if dispatch_cls is not None:");
|
|
11831
11880
|
dispLines.push(` return cast("${variantTypeName}", dispatch_cls.from_dict(data))`);
|
|
11832
11881
|
dispLines.push(` return ${unknownClassName}.from_dict(data)`);
|
|
11833
|
-
|
|
11882
|
+
const dispInScope = isModelInScope(model.name, ctx);
|
|
11883
|
+
if (dispInScope) files.push({
|
|
11834
11884
|
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
|
|
11835
11885
|
content: dispLines.join("\n"),
|
|
11836
11886
|
integrateTarget: true,
|
|
11837
11887
|
overwriteExisting: true
|
|
11838
11888
|
});
|
|
11839
|
-
if (
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
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
|
+
}
|
|
11850
11902
|
}
|
|
11851
11903
|
continue;
|
|
11852
11904
|
}
|
|
@@ -11861,16 +11913,20 @@ function generateModels$6(models, ctx) {
|
|
|
11861
11913
|
else lines.push(`from ${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)} import ${canonicalClassName}`);
|
|
11862
11914
|
lines.push("");
|
|
11863
11915
|
lines.push(`${modelClassName}: TypeAlias = ${canonicalClassName}`);
|
|
11864
|
-
|
|
11865
|
-
|
|
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,
|
|
11866
11920
|
content: lines.join("\n"),
|
|
11867
11921
|
integrateTarget: true,
|
|
11868
11922
|
overwriteExisting: true
|
|
11869
11923
|
});
|
|
11870
|
-
if (
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
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
|
+
}
|
|
11874
11930
|
continue;
|
|
11875
11931
|
}
|
|
11876
11932
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
@@ -12015,16 +12071,19 @@ function generateModels$6(models, ctx) {
|
|
|
12015
12071
|
}
|
|
12016
12072
|
}
|
|
12017
12073
|
lines.push(" return result");
|
|
12018
|
-
|
|
12019
|
-
|
|
12074
|
+
const regularInScope = isModelInScope(model.name, ctx);
|
|
12075
|
+
if (regularInScope) files.push({
|
|
12076
|
+
path: modelFilePath,
|
|
12020
12077
|
content: lines.join("\n"),
|
|
12021
12078
|
integrateTarget: true,
|
|
12022
12079
|
overwriteExisting: true
|
|
12023
12080
|
});
|
|
12024
|
-
if (
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
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
|
+
}
|
|
12028
12087
|
}
|
|
12029
12088
|
const symbolsByDir = /* @__PURE__ */ new Map();
|
|
12030
12089
|
for (const [dirName, names] of emittedModelSymbolsByDir) {
|
|
@@ -12033,11 +12092,35 @@ function generateModels$6(models, ctx) {
|
|
|
12033
12092
|
for (const name of names) symbolsByDir.get(key).push({ name });
|
|
12034
12093
|
}
|
|
12035
12094
|
const reachableEnumNames = collectReachableEnumNames(ctx);
|
|
12036
|
-
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);
|
|
12037
12099
|
for (const [dirName, names] of enumSymbolsByDir) {
|
|
12038
12100
|
const key = `src/${ctx.namespace}/${dirName}/models`;
|
|
12039
12101
|
if (!symbolsByDir.has(key)) symbolsByDir.set(key, []);
|
|
12040
|
-
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
|
+
}
|
|
12041
12124
|
}
|
|
12042
12125
|
const commonDirName = "common";
|
|
12043
12126
|
const addReExport = (naturalService, name, sourceFile) => {
|
|
@@ -12082,6 +12165,10 @@ function generateModels$6(models, ctx) {
|
|
|
12082
12165
|
const uniqueSymbols = [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
12083
12166
|
const importLines = [];
|
|
12084
12167
|
for (const sym of uniqueSymbols) {
|
|
12168
|
+
if (sym.retainBasename) {
|
|
12169
|
+
importLines.push(`from .${sym.retainBasename} import * # noqa: F401,F403`);
|
|
12170
|
+
continue;
|
|
12171
|
+
}
|
|
12085
12172
|
const cls = className$5(sym.name);
|
|
12086
12173
|
if (sym.reExport) importLines.push(`from ${ctx.namespace}.${dirToModule(sym.reExport.fromDir)}.models.${sym.reExport.file} import ${cls} as ${cls}`);
|
|
12087
12174
|
else {
|
|
@@ -12098,7 +12185,7 @@ function generateModels$6(models, ctx) {
|
|
|
12098
12185
|
});
|
|
12099
12186
|
if (!serviceDirModelPaths.has(dirPath)) {
|
|
12100
12187
|
const parentDir = dirPath.replace(/\/models$/, "");
|
|
12101
|
-
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");
|
|
12102
12189
|
files.push({
|
|
12103
12190
|
path: `${parentDir}/__init__.py`,
|
|
12104
12191
|
content: reExports,
|
|
@@ -13818,18 +13905,30 @@ const ID_PREFIXES$3 = {
|
|
|
13818
13905
|
};
|
|
13819
13906
|
/**
|
|
13820
13907
|
* Generate JSON fixture files for test data.
|
|
13821
|
-
|
|
13822
|
-
|
|
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) {
|
|
13823
13917
|
if (spec.models.length === 0) return [];
|
|
13824
13918
|
const modelMap = new Map(spec.models.map((m) => [m.name, m]));
|
|
13825
13919
|
const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
|
|
13826
13920
|
const files = [];
|
|
13921
|
+
const fixtureEmitted = (path, modelName) => {
|
|
13922
|
+
if (!ctx) return true;
|
|
13923
|
+
return fileExistsAfterRun(path, isModelInScope(modelName, ctx), ctx);
|
|
13924
|
+
};
|
|
13827
13925
|
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
|
|
13828
13926
|
const listMetadataNeeded = collectReferencedListMetadataModels(spec.models, nonPaginatedRefs);
|
|
13829
13927
|
for (const model of spec.models) {
|
|
13830
13928
|
if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
|
|
13831
13929
|
if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
|
|
13832
13930
|
if (model.fields.length === 0) continue;
|
|
13931
|
+
if (!fixtureEmitted(`tests/fixtures/${fileName$2(model.name)}.json`, model.name)) continue;
|
|
13833
13932
|
const fixture = generateModelFixture$4(model, modelMap, enumMap);
|
|
13834
13933
|
files.push({
|
|
13835
13934
|
path: `tests/fixtures/${fileName$2(model.name)}.json`,
|
|
@@ -13842,6 +13941,7 @@ function generateFixtures$4(spec) {
|
|
|
13842
13941
|
const unwrapped = unwrapListModel$3(itemModel, modelMap);
|
|
13843
13942
|
if (unwrapped) itemModel = unwrapped;
|
|
13844
13943
|
if (itemModel.fields.length === 0) continue;
|
|
13944
|
+
if (!fixtureEmitted(`tests/fixtures/list_${fileName$2(itemModel.name)}.json`, itemModel.name)) continue;
|
|
13845
13945
|
const listFixture = {
|
|
13846
13946
|
data: [generateModelFixture$4(itemModel, modelMap, enumMap)],
|
|
13847
13947
|
list_metadata: {
|
|
@@ -13984,7 +14084,7 @@ function buildDeleteSuccessResponseSetup(op) {
|
|
|
13984
14084
|
*/
|
|
13985
14085
|
function generateTests$6(spec, ctx) {
|
|
13986
14086
|
const files = [];
|
|
13987
|
-
const fixtures = generateFixtures$4(spec);
|
|
14087
|
+
const fixtures = generateFixtures$4(spec, ctx);
|
|
13988
14088
|
for (const fixture of fixtures) files.push({
|
|
13989
14089
|
path: fixture.path,
|
|
13990
14090
|
content: fixture.content,
|
|
@@ -14961,11 +15061,17 @@ function generateModelRoundTripTests(spec, ctx) {
|
|
|
14961
15061
|
for (const name of responseModelNames) requestOnlyModelNames.delete(name);
|
|
14962
15062
|
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
|
|
14963
15063
|
const listMetadataNeeded = collectReferencedListMetadataModels(spec.models, nonPaginatedRefs);
|
|
14964
|
-
const
|
|
14965
|
-
|
|
14966
|
-
const
|
|
15064
|
+
const placement = computeSchemaPlacement(spec, ctx);
|
|
15065
|
+
const modelToService = placement.originalModelToService;
|
|
15066
|
+
const relocatedModelToService = placement.modelToService;
|
|
14967
15067
|
const roundTripDirMap = buildMountDirMap$1(ctx);
|
|
14968
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;
|
|
14969
15075
|
const lines = [];
|
|
14970
15076
|
lines.push("\"\"\"Model round-trip tests: from_dict(to_dict()) preserves data.\"\"\"");
|
|
14971
15077
|
lines.push("");
|
|
@@ -17298,6 +17404,169 @@ function unionResolverName(ref) {
|
|
|
17298
17404
|
return null;
|
|
17299
17405
|
}
|
|
17300
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
|
|
17301
17570
|
//#region src/go/models.ts
|
|
17302
17571
|
/**
|
|
17303
17572
|
* Collect names of models that are referenced **only** as a named request body
|
|
@@ -17365,6 +17634,8 @@ function generateModels$4(models, ctx) {
|
|
|
17365
17634
|
if (!hashGroups.has(hash)) hashGroups.set(hash, []);
|
|
17366
17635
|
hashGroups.get(hash).push(model.name);
|
|
17367
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));
|
|
17368
17639
|
const aliasOf = /* @__PURE__ */ new Map();
|
|
17369
17640
|
for (const [hash, names] of hashGroups) {
|
|
17370
17641
|
if (names.length <= 1) continue;
|
|
@@ -17373,6 +17644,11 @@ function generateModels$4(models, ctx) {
|
|
|
17373
17644
|
const canonical = sorted[0];
|
|
17374
17645
|
for (let i = 1; i < sorted.length; i++) aliasOf.set(sorted[i], canonical);
|
|
17375
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 = [];
|
|
17376
17652
|
const batchedAliases = /* @__PURE__ */ new Set();
|
|
17377
17653
|
for (const model of models) {
|
|
17378
17654
|
if (skipAsListWrapper(model) || skipAsListMetadata(model)) continue;
|
|
@@ -17385,29 +17661,44 @@ function generateModels$4(models, ctx) {
|
|
|
17385
17661
|
if (structName === canonicalStruct) continue;
|
|
17386
17662
|
const hash = modelHashMap.get(model.name);
|
|
17387
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;
|
|
17388
17667
|
if (aliases.length >= 5) {
|
|
17389
17668
|
for (const aliasName of aliases) batchedAliases.add(aliasName);
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
|
|
17393
|
-
|
|
17394
|
-
|
|
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(")");
|
|
17395
17679
|
} else {
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
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
|
+
});
|
|
17400
17690
|
continue;
|
|
17401
17691
|
}
|
|
17692
|
+
const blockLines = [];
|
|
17402
17693
|
if (model.description) {
|
|
17403
17694
|
const descLines = model.description.split("\n").filter((l) => l.trim());
|
|
17404
|
-
|
|
17405
|
-
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()}`);
|
|
17406
17697
|
} else {
|
|
17407
17698
|
const humanized = humanize$3(model.name);
|
|
17408
|
-
|
|
17699
|
+
blockLines.push(`// ${structName} represents ${articleFor$1(humanized)} ${humanized}.`);
|
|
17409
17700
|
}
|
|
17410
|
-
|
|
17701
|
+
blockLines.push(`type ${structName} struct {`);
|
|
17411
17702
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
17412
17703
|
for (const field of model.fields) {
|
|
17413
17704
|
const goFieldName = domainFieldName$3(field);
|
|
@@ -17417,17 +17708,26 @@ function generateModels$4(models, ctx) {
|
|
|
17417
17708
|
const jsonTag = field.required ? `json:"${field.name}"` : `json:"${field.name},omitempty"`;
|
|
17418
17709
|
if (field.description) {
|
|
17419
17710
|
const fdLines = field.description.split("\n").filter((l) => l.trim());
|
|
17420
|
-
|
|
17421
|
-
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()}`);
|
|
17422
17713
|
}
|
|
17423
17714
|
if (field.deprecated) {
|
|
17424
|
-
if (field.description)
|
|
17715
|
+
if (field.description) blockLines.push(`\t//`);
|
|
17425
17716
|
const deprecationReason = extractDeprecationReason(field.description);
|
|
17426
|
-
|
|
17717
|
+
blockLines.push(`\t// Deprecated: ${deprecationReason}`);
|
|
17427
17718
|
}
|
|
17428
|
-
|
|
17719
|
+
blockLines.push(`\t${goFieldName} ${goType} \`${jsonTag}\``);
|
|
17429
17720
|
}
|
|
17430
|
-
|
|
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);
|
|
17431
17731
|
lines.push("");
|
|
17432
17732
|
}
|
|
17433
17733
|
const orderEnumType = detectSharedOrderEnum(ctx.spec.services);
|
|
@@ -17590,27 +17890,36 @@ function extractDeprecationReason(description) {
|
|
|
17590
17890
|
function generateEnums$4(enums, ctx) {
|
|
17591
17891
|
if (enums.length === 0) return [];
|
|
17592
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
|
+
}
|
|
17593
17897
|
const files = [];
|
|
17594
17898
|
const lines = [];
|
|
17595
17899
|
lines.push(`package ${ctx.namespace}`);
|
|
17596
17900
|
lines.push("");
|
|
17901
|
+
const enumBlocks = [];
|
|
17597
17902
|
for (const enumDef of enums) {
|
|
17598
17903
|
const canonicalName = aliasOf.get(enumDef.name);
|
|
17599
17904
|
if (canonicalName) {
|
|
17600
17905
|
const aliasType = className$3(enumDef.name);
|
|
17601
17906
|
const canonicalType = className$3(canonicalName);
|
|
17602
17907
|
if (aliasType === canonicalType) continue;
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
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
|
+
});
|
|
17606
17913
|
continue;
|
|
17607
17914
|
}
|
|
17608
17915
|
const typeName = className$3(enumDef.name);
|
|
17609
17916
|
if (enumDef.values.length === 0) {
|
|
17610
17917
|
const humanized = humanize$2(enumDef.name);
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
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
|
+
});
|
|
17614
17923
|
continue;
|
|
17615
17924
|
}
|
|
17616
17925
|
const seenValues = /* @__PURE__ */ new Set();
|
|
@@ -17623,10 +17932,11 @@ function generateEnums$4(enums, ctx) {
|
|
|
17623
17932
|
}
|
|
17624
17933
|
}
|
|
17625
17934
|
const humanized = humanize$2(enumDef.name);
|
|
17626
|
-
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17935
|
+
const blockLines = [];
|
|
17936
|
+
blockLines.push(`// ${typeName} represents ${humanized} values.`);
|
|
17937
|
+
blockLines.push(`type ${typeName} string`);
|
|
17938
|
+
blockLines.push("");
|
|
17939
|
+
blockLines.push("const (");
|
|
17630
17940
|
const usedNames = /* @__PURE__ */ new Set();
|
|
17631
17941
|
for (const v of uniqueValues) {
|
|
17632
17942
|
let constSuffix = className$3(String(v.value));
|
|
@@ -17638,14 +17948,23 @@ function generateEnums$4(enums, ctx) {
|
|
|
17638
17948
|
const constName = `${typeName}${constSuffix}`;
|
|
17639
17949
|
usedNames.add(constName);
|
|
17640
17950
|
const valueStr = typeof v.value === "string" ? `"${v.value}"` : String(v.value);
|
|
17641
|
-
if (v.description)
|
|
17951
|
+
if (v.description) blockLines.push(`\t// ${constName} is ${v.description}.`);
|
|
17642
17952
|
if (v.deprecated) {
|
|
17643
|
-
if (v.description)
|
|
17644
|
-
|
|
17953
|
+
if (v.description) blockLines.push(`\t//`);
|
|
17954
|
+
blockLines.push(`\t// Deprecated: this value is deprecated.`);
|
|
17645
17955
|
}
|
|
17646
|
-
|
|
17956
|
+
blockLines.push(`\t${constName} ${typeName} = ${valueStr}`);
|
|
17647
17957
|
}
|
|
17648
|
-
|
|
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);
|
|
17649
17968
|
lines.push("");
|
|
17650
17969
|
}
|
|
17651
17970
|
files.push({
|
|
@@ -17653,7 +17972,7 @@ function generateEnums$4(enums, ctx) {
|
|
|
17653
17972
|
content: lines.join("\n"),
|
|
17654
17973
|
overwriteExisting: true
|
|
17655
17974
|
});
|
|
17656
|
-
const eventConstantsFile = generateEventConstantsFile(enums);
|
|
17975
|
+
const eventConstantsFile = generateEventConstantsFile(enums, ctx);
|
|
17657
17976
|
if (eventConstantsFile) files.push(eventConstantsFile);
|
|
17658
17977
|
return files;
|
|
17659
17978
|
}
|
|
@@ -17715,9 +18034,15 @@ function collectEnumAliasOf$2(enums) {
|
|
|
17715
18034
|
}
|
|
17716
18035
|
return aliasOf;
|
|
17717
18036
|
}
|
|
17718
|
-
|
|
18037
|
+
const EVENTS_FILE_PATH = "pkg/events/events.go";
|
|
18038
|
+
function generateEventConstantsFile(enums, ctx) {
|
|
17719
18039
|
const enumDef = findWebhookEventEnum(enums);
|
|
17720
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
|
+
}
|
|
17721
18046
|
const lines = [];
|
|
17722
18047
|
lines.push("package events");
|
|
17723
18048
|
lines.push("");
|
|
@@ -17732,6 +18057,7 @@ function generateEventConstantsFile(enums) {
|
|
|
17732
18057
|
if (seenValues.has(valueStr)) continue;
|
|
17733
18058
|
seenValues.add(valueStr);
|
|
17734
18059
|
const constName = uniqueEventConstantName(valueStr, usedNames);
|
|
18060
|
+
if (priorEventConsts !== null && !priorEventConsts.has(constName)) continue;
|
|
17735
18061
|
usedNames.add(constName);
|
|
17736
18062
|
if (value.description) lines.push(`\t// ${constName} is ${value.description}.`);
|
|
17737
18063
|
if (value.deprecated) {
|
|
@@ -17743,11 +18069,25 @@ function generateEventConstantsFile(enums) {
|
|
|
17743
18069
|
lines.push(")");
|
|
17744
18070
|
lines.push("");
|
|
17745
18071
|
return {
|
|
17746
|
-
path:
|
|
18072
|
+
path: EVENTS_FILE_PATH,
|
|
17747
18073
|
content: lines.join("\n"),
|
|
17748
18074
|
overwriteExisting: true
|
|
17749
18075
|
};
|
|
17750
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
|
+
}
|
|
17751
18091
|
function findWebhookEventEnum(enums) {
|
|
17752
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;
|
|
17753
18093
|
}
|
|
@@ -18960,8 +19300,15 @@ const ID_PREFIXES$1 = {
|
|
|
18960
19300
|
};
|
|
18961
19301
|
/**
|
|
18962
19302
|
* Generate JSON fixture files for test data.
|
|
18963
|
-
|
|
18964
|
-
|
|
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) {
|
|
18965
19312
|
if (spec.models.length === 0) return {
|
|
18966
19313
|
files: [],
|
|
18967
19314
|
pathRewrites: /* @__PURE__ */ new Map()
|
|
@@ -18971,12 +19318,15 @@ function generateFixtures$2(spec) {
|
|
|
18971
19318
|
const files = [];
|
|
18972
19319
|
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
|
|
18973
19320
|
const listMetadataNeeded = collectReferencedListMetadataModels(spec.models, nonPaginatedRefs);
|
|
19321
|
+
const fixtureInScope = (relPath, modelName) => !ctx || fileExistsAfterRun(relPath, isModelInScope(modelName, ctx), ctx);
|
|
18974
19322
|
for (const model of spec.models) {
|
|
18975
19323
|
if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
|
|
18976
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;
|
|
18977
19327
|
const fixture = model.fields.length === 0 ? {} : generateModelFixture$2(model, modelMap, enumMap);
|
|
18978
19328
|
files.push({
|
|
18979
|
-
path
|
|
19329
|
+
path,
|
|
18980
19330
|
content: JSON.stringify(fixture, null, 2)
|
|
18981
19331
|
});
|
|
18982
19332
|
}
|
|
@@ -18990,6 +19340,7 @@ function generateFixtures$2(spec) {
|
|
|
18990
19340
|
const path = `testdata/list_${fileName$1(itemModel.name)}.json`;
|
|
18991
19341
|
if (seenListPaths.has(path)) continue;
|
|
18992
19342
|
seenListPaths.add(path);
|
|
19343
|
+
if (!fixtureInScope(path, itemModel.name)) continue;
|
|
18993
19344
|
const listFixture = {
|
|
18994
19345
|
data: [generateModelFixture$2(itemModel, modelMap, enumMap)],
|
|
18995
19346
|
list_metadata: {
|
|
@@ -19151,7 +19502,7 @@ function generateTests$4(spec, ctx) {
|
|
|
19151
19502
|
content: helperLines.join("\n"),
|
|
19152
19503
|
overwriteExisting: true
|
|
19153
19504
|
});
|
|
19154
|
-
const { files: fixtures, pathRewrites: fixtureRewrites } = generateFixtures$2(spec);
|
|
19505
|
+
const { files: fixtures, pathRewrites: fixtureRewrites } = generateFixtures$2(spec, ctx);
|
|
19155
19506
|
for (const fixture of fixtures) files.push({
|
|
19156
19507
|
path: fixture.path,
|
|
19157
19508
|
content: fixture.content,
|
|
@@ -21451,6 +21802,13 @@ function generateClientFile(spec, ctx) {
|
|
|
21451
21802
|
//#endregion
|
|
21452
21803
|
//#region src/dotnet/fixtures.ts
|
|
21453
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
|
+
/**
|
|
21454
21812
|
* Prefix mapping for generating realistic ID fixture values.
|
|
21455
21813
|
*/
|
|
21456
21814
|
const ID_PREFIXES = {
|
|
@@ -21471,7 +21829,7 @@ const ID_PREFIXES = {
|
|
|
21471
21829
|
/**
|
|
21472
21830
|
* Generate JSON fixture files for test data.
|
|
21473
21831
|
*/
|
|
21474
|
-
function generateFixtures$1(spec) {
|
|
21832
|
+
function generateFixtures$1(spec, ctx) {
|
|
21475
21833
|
if (spec.models.length === 0) return [];
|
|
21476
21834
|
const modelMap = new Map(spec.models.map((m) => [m.name, m]));
|
|
21477
21835
|
const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
|
|
@@ -21480,9 +21838,11 @@ function generateFixtures$1(spec) {
|
|
|
21480
21838
|
for (const model of spec.models) {
|
|
21481
21839
|
if (isListMetadataModel(model)) continue;
|
|
21482
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;
|
|
21483
21843
|
const fixture = model.fields.length === 0 ? {} : generateModelFixture$1(model, modelMap, enumMap);
|
|
21484
21844
|
files.push({
|
|
21485
|
-
path:
|
|
21845
|
+
path: fixturePath,
|
|
21486
21846
|
content: JSON.stringify(fixture, null, 2)
|
|
21487
21847
|
});
|
|
21488
21848
|
if (model.fields.some((f) => !f.required || f.type.kind === "nullable") && model.fields.length > 0) {
|
|
@@ -21609,7 +21969,7 @@ function generatePrimitiveValue(type, format, name, modelName) {
|
|
|
21609
21969
|
*/
|
|
21610
21970
|
function generateTests$3(spec, ctx) {
|
|
21611
21971
|
const files = [];
|
|
21612
|
-
const fixtures = generateFixtures$1(spec);
|
|
21972
|
+
const fixtures = generateFixtures$1(spec, ctx);
|
|
21613
21973
|
for (const fixture of fixtures) files.push({
|
|
21614
21974
|
path: fixture.path,
|
|
21615
21975
|
content: fixture.content,
|
|
@@ -22141,6 +22501,26 @@ function prefixTestPaths(files) {
|
|
|
22141
22501
|
for (const f of files) f.path = `${TEST_PREFIX$1}${f.path}`;
|
|
22142
22502
|
return files;
|
|
22143
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
|
+
}
|
|
22144
22524
|
const dotnetEmitter = {
|
|
22145
22525
|
language: "dotnet",
|
|
22146
22526
|
generateModels(models, ctx) {
|
|
@@ -22200,6 +22580,7 @@ const dotnetEmitter = {
|
|
|
22200
22580
|
lines.push(" switch (discriminatorValue)");
|
|
22201
22581
|
lines.push(" {");
|
|
22202
22582
|
for (const [value, modelName] of Object.entries(disc.mapping)) {
|
|
22583
|
+
if (!variantFileExistsAfterRun(modelName, c)) continue;
|
|
22203
22584
|
const csName = modelClassName(resolveModelName(modelName));
|
|
22204
22585
|
lines.push(` case "${value}": return jObject.ToObject<${csName}>(serializer);`);
|
|
22205
22586
|
}
|
|
@@ -22220,6 +22601,7 @@ const dotnetEmitter = {
|
|
|
22220
22601
|
});
|
|
22221
22602
|
}
|
|
22222
22603
|
for (const [baseName, disc] of modelDiscriminators) {
|
|
22604
|
+
if (!variantFileExistsAfterRun(baseName, c)) continue;
|
|
22223
22605
|
const baseClass = modelClassName(baseName);
|
|
22224
22606
|
const converterName = `${baseClass}DiscriminatorConverter`;
|
|
22225
22607
|
const lines = [];
|
|
@@ -22248,6 +22630,7 @@ const dotnetEmitter = {
|
|
|
22248
22630
|
lines.push(" switch (discriminatorValue)");
|
|
22249
22631
|
lines.push(" {");
|
|
22250
22632
|
for (const [value, variantModelName] of Object.entries(disc.mapping)) {
|
|
22633
|
+
if (!variantFileExistsAfterRun(variantModelName, c)) continue;
|
|
22251
22634
|
const csName = modelClassName(variantModelName);
|
|
22252
22635
|
lines.push(` case "${value}": target = new ${csName}(); break;`);
|
|
22253
22636
|
}
|
|
@@ -22805,6 +23188,16 @@ const KOTLIN_SRC_PREFIX$2 = "src/main/kotlin/";
|
|
|
22805
23188
|
const MODELS_PACKAGE = "com.workos.models";
|
|
22806
23189
|
const MODELS_DIR = "com/workos/models";
|
|
22807
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
|
+
/**
|
|
22808
23201
|
* Some specs leave string fields without `format: date-time` even though the
|
|
22809
23202
|
* description (or the example) makes clear they carry an ISO-8601 timestamp.
|
|
22810
23203
|
* Detect that here so we can promote the type to `OffsetDateTime` in the
|
|
@@ -22899,7 +23292,7 @@ function generateModels$2(models, ctx) {
|
|
|
22899
23292
|
const typeName = className$1(model.name);
|
|
22900
23293
|
const modelInScope = isModelInScope(model.name, ctx);
|
|
22901
23294
|
if (model.fields.length === 0 && discriminatedUnions.has(typeName)) {
|
|
22902
|
-
if (modelInScope) files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName)));
|
|
23295
|
+
if (modelInScope) files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName), ctx));
|
|
22903
23296
|
continue;
|
|
22904
23297
|
}
|
|
22905
23298
|
const canonical = aliasOf.get(model.name);
|
|
@@ -22927,6 +23320,7 @@ function generateModels$2(models, ctx) {
|
|
|
22927
23320
|
if (skipAsListWrapper(model) || skipAsListMetadata(model)) continue;
|
|
22928
23321
|
if (aliasOf.has(model.name)) continue;
|
|
22929
23322
|
if (!isEventEnvelopeModel(model)) continue;
|
|
23323
|
+
if (!fileExistsAfterRun(modelFilePath$1(model.name), isModelInScope(model.name, ctx), ctx)) continue;
|
|
22930
23324
|
const eventField = model.fields.find((f) => f.name === "event");
|
|
22931
23325
|
if (eventField && eventField.type.kind === "literal" && typeof eventField.type.value === "string") eventMapping.push({
|
|
22932
23326
|
wireValue: eventField.type.value,
|
|
@@ -22984,15 +23378,15 @@ function emitDataClass(model) {
|
|
|
22984
23378
|
overwriteExisting: true
|
|
22985
23379
|
};
|
|
22986
23380
|
}
|
|
22987
|
-
function emitSealedUnion(typeName, disc) {
|
|
23381
|
+
function emitSealedUnion(typeName, disc, ctx) {
|
|
22988
23382
|
const lines = [];
|
|
22989
23383
|
lines.push(`package ${MODELS_PACKAGE}`);
|
|
22990
23384
|
lines.push("");
|
|
22991
23385
|
lines.push("import com.fasterxml.jackson.annotation.JsonSubTypes");
|
|
22992
23386
|
lines.push("import com.fasterxml.jackson.annotation.JsonTypeInfo");
|
|
22993
23387
|
lines.push("");
|
|
22994
|
-
const
|
|
22995
|
-
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;
|
|
22996
23390
|
lines.push("/**");
|
|
22997
23391
|
lines.push(` * Discriminated union over ${typeName} variants. Selected by \`${disc.property}\`.`);
|
|
22998
23392
|
if (exampleVariantType) {
|
|
@@ -23021,7 +23415,6 @@ function emitSealedUnion(typeName, disc) {
|
|
|
23021
23415
|
lines.push(" visible = true");
|
|
23022
23416
|
lines.push(")");
|
|
23023
23417
|
lines.push("@JsonSubTypes(");
|
|
23024
|
-
const entries = Object.entries(disc.mapping);
|
|
23025
23418
|
for (let i = 0; i < entries.length; i++) {
|
|
23026
23419
|
const [wireValue, modelName] = entries[i];
|
|
23027
23420
|
const variantType = className$1(modelName);
|
|
@@ -24478,6 +24871,14 @@ function deduplicateByMount(services, ctx) {
|
|
|
24478
24871
|
//#endregion
|
|
24479
24872
|
//#region src/kotlin/tests.ts
|
|
24480
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
|
+
}
|
|
24481
24882
|
/**
|
|
24482
24883
|
* Mirror the ISO-8601 hint promotion the resource/model emitters use so tests
|
|
24483
24884
|
* synthesize values whose Kotlin type matches the generated method signature.
|
|
@@ -25228,6 +25629,7 @@ function generateModelRoundTripTest$1(spec, ctx) {
|
|
|
25228
25629
|
for (const m of spec.models) {
|
|
25229
25630
|
if (isListWrapperModel(m) || isListMetadataModel(m)) continue;
|
|
25230
25631
|
if (m.fields.length === 0) continue;
|
|
25632
|
+
if (!fileExistsAfterRun(modelFilePath(m.name), isModelInScope(m.name, ctx), ctx)) continue;
|
|
25231
25633
|
const cls = className$1(m.name);
|
|
25232
25634
|
if (seenModelClassNames.has(cls)) continue;
|
|
25233
25635
|
seenModelClassNames.add(cls);
|
|
@@ -25276,10 +25678,11 @@ function generateModelRoundTripTest$1(spec, ctx) {
|
|
|
25276
25678
|
*/
|
|
25277
25679
|
const MAX_ENUM_FORWARD_COMPAT = 15;
|
|
25278
25680
|
function generateForwardCompatTest(spec, ctx) {
|
|
25279
|
-
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);
|
|
25280
25682
|
const modelTarget = spec.models.find((m) => {
|
|
25281
25683
|
if (isListWrapperModel(m) || isListMetadataModel(m)) return false;
|
|
25282
25684
|
if (m.fields.length === 0) return false;
|
|
25685
|
+
if (!fileExistsAfterRun(modelFilePath(m.name), isModelInScope(m.name, ctx), ctx)) return false;
|
|
25283
25686
|
return synthJsonForModelName(m.name, ctx, /* @__PURE__ */ new Set()) !== null;
|
|
25284
25687
|
});
|
|
25285
25688
|
if (enumTargets.length === 0 && !modelTarget) return null;
|
|
@@ -27221,15 +27624,26 @@ function generateTests$1(spec, ctx) {
|
|
|
27221
27624
|
overwriteExisting: true
|
|
27222
27625
|
});
|
|
27223
27626
|
}
|
|
27224
|
-
files.push(generateModelRoundTripTest(spec));
|
|
27627
|
+
files.push(generateModelRoundTripTest(spec, ctx));
|
|
27225
27628
|
return files;
|
|
27226
27629
|
}
|
|
27227
27630
|
/**
|
|
27228
27631
|
* Emit test/workos/model_round_trip_test.rb that round-trips every non-wrapper
|
|
27229
27632
|
* model through `.new(json)` and `.to_json`, asserting the result is a Hash and
|
|
27230
27633
|
* that required fields appear with the seeded values.
|
|
27231
|
-
|
|
27232
|
-
|
|
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) {
|
|
27233
27647
|
const lines = [];
|
|
27234
27648
|
lines.push(`require 'test_helper'`);
|
|
27235
27649
|
lines.push("");
|
|
@@ -27237,7 +27651,15 @@ function generateModelRoundTripTest(spec) {
|
|
|
27237
27651
|
const models = spec.models.filter((m) => !isListWrapperModel(m) && !isListMetadataModel(m));
|
|
27238
27652
|
const enumNames = new Set(spec.enums.map((e) => e.name));
|
|
27239
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
|
+
};
|
|
27240
27661
|
for (const model of models) {
|
|
27662
|
+
if (!fileExistsAfterRun(`lib/workos/${dirFor(model.name)}/${fileName(model.name)}.rb`, isModelInScope(model.name, ctx), ctx)) continue;
|
|
27241
27663
|
const fileBase = fileName(model.name);
|
|
27242
27664
|
if (emitted.has(fileBase)) continue;
|
|
27243
27665
|
emitted.add(fileBase);
|
|
@@ -28224,14 +28646,19 @@ function generateModels(models, ctx, registry) {
|
|
|
28224
28646
|
const mod = moduleName(model.name);
|
|
28225
28647
|
if (seen.has(mod)) continue;
|
|
28226
28648
|
seen.add(mod);
|
|
28227
|
-
|
|
28649
|
+
const inScope = isModelInScope(model.name, ctx);
|
|
28228
28650
|
const path = ctx.overlayLookup?.fileBySymbol?.get(model.name) ?? `src/models/${mod}.rs`;
|
|
28229
28651
|
const content = renderModel(model, registry, taggedVariantFields.get(model.name));
|
|
28230
|
-
if (
|
|
28652
|
+
if (inScope) files.push({
|
|
28231
28653
|
path,
|
|
28232
28654
|
content,
|
|
28233
28655
|
overwriteExisting: true
|
|
28234
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);
|
|
28235
28662
|
}
|
|
28236
28663
|
moduleNames.push(UNIONS_MODULE);
|
|
28237
28664
|
files.push({
|
|
@@ -28397,13 +28824,18 @@ function generateEnums(enums, ctx) {
|
|
|
28397
28824
|
const mod = moduleName(e.name);
|
|
28398
28825
|
if (seen.has(mod)) continue;
|
|
28399
28826
|
seen.add(mod);
|
|
28400
|
-
|
|
28401
|
-
|
|
28402
|
-
files.push({
|
|
28403
|
-
path
|
|
28827
|
+
const inScope = isEnumInScope(e.name, ctx);
|
|
28828
|
+
const path = `src/enums/${mod}.rs`;
|
|
28829
|
+
if (inScope) files.push({
|
|
28830
|
+
path,
|
|
28404
28831
|
content: renderEnum(e),
|
|
28405
28832
|
overwriteExisting: true
|
|
28406
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);
|
|
28407
28839
|
}
|
|
28408
28840
|
files.push({
|
|
28409
28841
|
path: "src/enums/mod.rs",
|
|
@@ -29543,8 +29975,14 @@ function renderResourcesApi(ctx) {
|
|
|
29543
29975
|
/**
|
|
29544
29976
|
* Generate JSON test fixture files under `tests/fixtures/`. The Rust tests
|
|
29545
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.
|
|
29546
29984
|
*/
|
|
29547
|
-
function generateFixtures(spec) {
|
|
29985
|
+
function generateFixtures(spec, ctx) {
|
|
29548
29986
|
const files = [];
|
|
29549
29987
|
const modelMap = new Map(spec.models.map((m) => [m.name, m]));
|
|
29550
29988
|
const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
|
|
@@ -29553,8 +29991,9 @@ function generateFixtures(spec) {
|
|
|
29553
29991
|
if (seen.has(model.name)) continue;
|
|
29554
29992
|
if (model.fields.length === 0) continue;
|
|
29555
29993
|
seen.add(model.name);
|
|
29556
|
-
const fixture = generateModelFixture(model, modelMap, enumMap, /* @__PURE__ */ new Set());
|
|
29557
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());
|
|
29558
29997
|
files.push({
|
|
29559
29998
|
path,
|
|
29560
29999
|
content: JSON.stringify(fixture, null, 2) + "\n",
|
|
@@ -29701,7 +30140,7 @@ function matchPrimitive(value, primitive) {
|
|
|
29701
30140
|
*/
|
|
29702
30141
|
function generateTests(spec, ctx) {
|
|
29703
30142
|
const files = [];
|
|
29704
|
-
files.push(...generateFixtures(spec));
|
|
30143
|
+
files.push(...generateFixtures(spec, ctx));
|
|
29705
30144
|
files.push({
|
|
29706
30145
|
path: "tests/common/mod.rs",
|
|
29707
30146
|
content: renderCommon(ctx),
|
|
@@ -30339,4 +30778,4 @@ const workosEmittersPlugin = {
|
|
|
30339
30778
|
//#endregion
|
|
30340
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 };
|
|
30341
30780
|
|
|
30342
|
-
//# sourceMappingURL=plugin-
|
|
30781
|
+
//# sourceMappingURL=plugin-DXIciTnN.mjs.map
|