@workos/oagen-emitters 0.14.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-BGVaMGqe.mjs → plugin-CO4RFgAW.mjs} +959 -251
- package/dist/plugin-CO4RFgAW.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +7 -7
- package/renovate.json +1 -61
- package/src/go/client.ts +1 -1
- package/src/go/enums.ts +77 -0
- package/src/kotlin/enums.ts +11 -4
- package/src/node/client.ts +119 -2
- package/src/node/discriminated-models.ts +8 -0
- package/src/node/field-plan.ts +64 -8
- package/src/node/index.ts +59 -3
- package/src/node/models.ts +73 -30
- package/src/node/naming.ts +14 -1
- package/src/node/node-overrides.ts +4 -37
- package/src/node/options.ts +29 -1
- package/src/node/resources.ts +533 -83
- package/src/node/tests.ts +108 -7
- package/src/php/fixtures.ts +4 -1
- package/src/php/models.ts +3 -1
- package/src/php/resources.ts +40 -11
- package/src/php/tests.ts +22 -12
- package/src/python/client.ts +0 -8
- package/src/python/enums.ts +41 -15
- package/src/python/fixtures.ts +23 -7
- package/src/python/models.ts +26 -5
- package/src/python/resources.ts +71 -3
- package/src/python/tests.ts +70 -12
- package/src/python/wrappers.ts +25 -4
- package/src/ruby/client.ts +0 -1
- package/src/rust/resources.ts +10 -7
- package/src/shared/non-spec-services.ts +0 -5
- package/test/go/enums.test.ts +24 -0
- package/test/node/resources.test.ts +11 -1
- package/test/node/tests.test.ts +3 -3
- package/test/php/client.test.ts +0 -1
- package/test/php/resources.test.ts +50 -0
- package/test/rust/resources.test.ts +9 -0
- package/dist/plugin-BGVaMGqe.mjs.map +0 -1
package/src/node/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
setBaselineSerializedNames,
|
|
24
24
|
setBaselineInterfaceNames,
|
|
25
25
|
setAdoptedModelNames,
|
|
26
|
+
setDiscriminatedModelNames,
|
|
26
27
|
setStructurallyRenamedDomainNames,
|
|
27
28
|
resolveInterfaceName,
|
|
28
29
|
} from './naming.js';
|
|
@@ -341,14 +342,66 @@ function isOwnedPath(relPath: string, policy: LiveSurfacePolicy): boolean {
|
|
|
341
342
|
return dir !== undefined && policy.ownedServiceDirs.has(dir);
|
|
342
343
|
}
|
|
343
344
|
|
|
345
|
+
function extractRelativeImportPaths(content: string, fromPath: string): string[] {
|
|
346
|
+
const dir = path.dirname(fromPath);
|
|
347
|
+
const paths: string[] = [];
|
|
348
|
+
const re = /from\s+['"](\.[^'"]+)['"]/g;
|
|
349
|
+
let match: RegExpExecArray | null;
|
|
350
|
+
while ((match = re.exec(content)) !== null) {
|
|
351
|
+
paths.push(path.normalize(path.join(dir, match[1])) + '.ts');
|
|
352
|
+
}
|
|
353
|
+
return paths;
|
|
354
|
+
}
|
|
355
|
+
|
|
344
356
|
function applyLiveSurface(files: GeneratedFile[], ctx: EmitterContext, surface: LiveSurface): GeneratedFile[] {
|
|
345
357
|
const out: GeneratedFile[] = [];
|
|
346
358
|
const policy = buildLiveSurfacePolicy(ctx, surface);
|
|
359
|
+
const filesByPath = new Map(files.map((f) => [f.path, f]));
|
|
360
|
+
const dependencyAllowedPaths = new Set<string>();
|
|
361
|
+
const queue: string[] = [];
|
|
362
|
+
|
|
363
|
+
for (const f of files) {
|
|
364
|
+
if (f.integrateTarget === false) continue;
|
|
365
|
+
if (!canCreateNewPath(f.path, policy)) continue;
|
|
366
|
+
for (const importPath of extractRelativeImportPaths(f.content, f.path)) {
|
|
367
|
+
if (
|
|
368
|
+
filesByPath.has(importPath) &&
|
|
369
|
+
!canCreateNewPath(importPath, policy) &&
|
|
370
|
+
!dependencyAllowedPaths.has(importPath)
|
|
371
|
+
) {
|
|
372
|
+
dependencyAllowedPaths.add(importPath);
|
|
373
|
+
queue.push(importPath);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
while (queue.length > 0) {
|
|
379
|
+
const relPath = queue.pop()!;
|
|
380
|
+
const file = filesByPath.get(relPath);
|
|
381
|
+
if (!file) continue;
|
|
382
|
+
for (const importPath of extractRelativeImportPaths(file.content, relPath)) {
|
|
383
|
+
if (
|
|
384
|
+
filesByPath.has(importPath) &&
|
|
385
|
+
!canCreateNewPath(importPath, policy) &&
|
|
386
|
+
!dependencyAllowedPaths.has(importPath)
|
|
387
|
+
) {
|
|
388
|
+
dependencyAllowedPaths.add(importPath);
|
|
389
|
+
queue.push(importPath);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
347
394
|
for (const f of files) {
|
|
348
395
|
const ownedPath = isOwnedPath(f.path, policy);
|
|
349
396
|
if (f.integrateTarget === false) continue;
|
|
350
397
|
if (surface.protectedFiles.has(f.path)) continue;
|
|
351
|
-
if (
|
|
398
|
+
if (
|
|
399
|
+
policy.hasExistingSdk &&
|
|
400
|
+
!policy.managedPaths.has(f.path) &&
|
|
401
|
+
!canCreateNewPath(f.path, policy) &&
|
|
402
|
+
!dependencyAllowedPaths.has(f.path)
|
|
403
|
+
)
|
|
404
|
+
continue;
|
|
352
405
|
|
|
353
406
|
// Hand-written files (on disk, no `auto-generated by oagen` header) →
|
|
354
407
|
// drop. The engine would otherwise prepend the header on
|
|
@@ -372,7 +425,8 @@ function applyLiveSurface(files: GeneratedFile[], ctx: EmitterContext, surface:
|
|
|
372
425
|
const dir = topLevelDir(f.path);
|
|
373
426
|
const isAdoptedDir = dir !== undefined && policy.adoptedServiceDirs.has(dir);
|
|
374
427
|
const isManagedDir = ownedPath || isAdoptedDir;
|
|
375
|
-
if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path))
|
|
428
|
+
if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path) && !(ownedPath && policy.regenerateOwnedTests))
|
|
429
|
+
continue;
|
|
376
430
|
if (!isManagedDir && !surface.autogenFiles.has(f.path)) continue;
|
|
377
431
|
if (isManagedDir && !policy.regenerateOwnedTests) continue;
|
|
378
432
|
}
|
|
@@ -487,7 +541,9 @@ export const nodeEmitter: Emitter = {
|
|
|
487
541
|
// name set on ctx so models.ts skips emitting an interface/serializer —
|
|
488
542
|
// the discriminated module owns those paths instead.
|
|
489
543
|
const discPlans = planDiscriminatedModels(enriched, nodeCtx);
|
|
490
|
-
|
|
544
|
+
const discriminatedNames = new Set(discPlans.keys());
|
|
545
|
+
(nodeCtx as { _discriminatedModelNames?: Set<string> })._discriminatedModelNames = discriminatedNames;
|
|
546
|
+
setDiscriminatedModelNames(discriminatedNames);
|
|
491
547
|
const standardFiles = generateModelsAndSerializers(enriched, nodeCtx);
|
|
492
548
|
const discFiles = generateDiscriminatedFiles(discPlans, nodeCtx);
|
|
493
549
|
return applyLiveSurface([...standardFiles, ...discFiles], nodeCtx, surface);
|
package/src/node/models.ts
CHANGED
|
@@ -223,6 +223,20 @@ export function generateModels(models: Model[], ctx: EmitterContext, shared?: Sh
|
|
|
223
223
|
// wrapper's interface importing from a file that was never written.
|
|
224
224
|
const listMetadataNeeded = collectReferencedListMetadataModels(models, nonPaginatedRefs);
|
|
225
225
|
|
|
226
|
+
for (const originalModel of models) {
|
|
227
|
+
const model = projectedByName.get(originalModel.name) ?? originalModel;
|
|
228
|
+
if (!reachableModels.has(model.name)) continue;
|
|
229
|
+
if (interfaceEligibleModels && !interfaceEligibleModels.has(model.name)) continue;
|
|
230
|
+
const service = modelToService.get(model.name);
|
|
231
|
+
const isOwnedModel = isNodeOwnedService(ctx, service);
|
|
232
|
+
if (!isOwnedModel && !modelHasNewFields(model, ctx) && !forceGenerate.has(model.name)) continue;
|
|
233
|
+
const canonicalName = dedup.get(model.name);
|
|
234
|
+
if (canonicalName) {
|
|
235
|
+
forceGenerate.add(canonicalName);
|
|
236
|
+
if (interfaceEligibleModels) interfaceEligibleModels.add(canonicalName);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
226
240
|
for (const originalModel of models) {
|
|
227
241
|
const model = projectedByName.get(originalModel.name) ?? originalModel;
|
|
228
242
|
if (!reachableModels.has(model.name)) continue;
|
|
@@ -392,6 +406,7 @@ export function generateModels(models: Model[], ctx: EmitterContext, shared?: Sh
|
|
|
392
406
|
const depName = resolveInterfaceName(dep, ctx);
|
|
393
407
|
const depService = modelToService.get(dep);
|
|
394
408
|
const depDir = resolveDir(depService);
|
|
409
|
+
const depIsOwned = isNodeOwnedService(ctx, depService);
|
|
395
410
|
|
|
396
411
|
// When the resolver maps the IR name to a different baseline interface
|
|
397
412
|
// (via `overlayLookup.modelNameByIR` structural match), the import
|
|
@@ -400,7 +415,9 @@ export function generateModels(models: Model[], ctx: EmitterContext, shared?: Sh
|
|
|
400
415
|
// emitter never generates — the canonical baseline file is at a
|
|
401
416
|
// different stem (e.g. `create-audit-log-event-options.interface`).
|
|
402
417
|
const currentFilePath = `src/${dirName}/interfaces/${fileName(model.name)}.interface.ts`;
|
|
403
|
-
const baselineSrc =
|
|
418
|
+
const baselineSrc = depIsOwned
|
|
419
|
+
? undefined
|
|
420
|
+
: (ctx.apiSurface?.interfaces?.[depName] as { sourceFile?: string } | undefined)?.sourceFile;
|
|
404
421
|
|
|
405
422
|
// Self-reference: the dependency lives in the file we're currently
|
|
406
423
|
// emitting. Skip the import — it's already in scope.
|
|
@@ -697,6 +714,13 @@ export function generateSerializers(
|
|
|
697
714
|
const responseReachableModels = resourceUsage
|
|
698
715
|
? expandModelRoots(resourceUsage.responseRoots, projectedByName)
|
|
699
716
|
: undefined;
|
|
717
|
+
// Models reachable from any request — only these need a `serialize<X>`.
|
|
718
|
+
// A model used solely as a response body can safely be deserialize-only;
|
|
719
|
+
// emitting its serialize half is both unused and brittle when it contains
|
|
720
|
+
// legacy nested response models that intentionally have no serialize helper.
|
|
721
|
+
const requestReachableModels = resourceUsage
|
|
722
|
+
? expandModelRoots(resourceUsage.requestRoots, projectedByName)
|
|
723
|
+
: undefined;
|
|
700
724
|
|
|
701
725
|
const serializerReachable = computeNonEventReachable(ctx.spec.services, models);
|
|
702
726
|
|
|
@@ -759,6 +783,20 @@ export function generateSerializers(
|
|
|
759
783
|
// Mirror the interface-emission gate (see `generateModels`).
|
|
760
784
|
const serializerListMetadataNeeded = collectReferencedListMetadataModels(models, serializerNonPaginatedRefs);
|
|
761
785
|
|
|
786
|
+
for (const originalModel of models) {
|
|
787
|
+
const model = projectedByName.get(originalModel.name) ?? originalModel;
|
|
788
|
+
if (!serializerReachable.has(model.name)) continue;
|
|
789
|
+
if (serializerEligibleModels && !serializerEligibleModels.has(model.name)) continue;
|
|
790
|
+
const service = modelToService.get(model.name);
|
|
791
|
+
const isOwnedModel = isNodeOwnedService(ctx, service);
|
|
792
|
+
if (!isOwnedModel && !modelHasNewFields(model, ctx) && !forceGenerateSerializer.has(model.name)) continue;
|
|
793
|
+
const canonicalName = dedup.get(model.name);
|
|
794
|
+
if (canonicalName) {
|
|
795
|
+
forceGenerateSerializer.add(canonicalName);
|
|
796
|
+
if (serializerEligibleModels) serializerEligibleModels.add(canonicalName);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
762
800
|
const eligibleModels: Model[] = [];
|
|
763
801
|
for (const originalModel of models) {
|
|
764
802
|
const model = projectedByName.get(originalModel.name) ?? originalModel;
|
|
@@ -781,14 +819,9 @@ export function generateSerializers(
|
|
|
781
819
|
const responseName = wireInterfaceName(domainName);
|
|
782
820
|
const baselineResponse = ctx.apiSurface?.interfaces?.[responseName];
|
|
783
821
|
const baselineDomain = ctx.apiSurface?.interfaces?.[domainName];
|
|
784
|
-
const shouldSkip =
|
|
785
|
-
model
|
|
786
|
-
baselineResponse,
|
|
787
|
-
baselineDomain,
|
|
788
|
-
dedup,
|
|
789
|
-
skippedSerializeModels,
|
|
790
|
-
ctx,
|
|
791
|
-
);
|
|
822
|
+
const shouldSkip =
|
|
823
|
+
(requestReachableModels !== undefined && !requestReachableModels.has(model.name)) ||
|
|
824
|
+
shouldSkipSerializeForModel(model, baselineResponse, baselineDomain, dedup, skippedSerializeModels, ctx);
|
|
792
825
|
if (shouldSkip) {
|
|
793
826
|
skippedSerializeModels.add(model.name);
|
|
794
827
|
}
|
|
@@ -812,27 +845,37 @@ export function generateSerializers(
|
|
|
812
845
|
|
|
813
846
|
if (serializerPath === canonSerializerPath) continue;
|
|
814
847
|
if (domainName === canonDomainName) continue;
|
|
815
|
-
|
|
816
|
-
const
|
|
817
|
-
const
|
|
818
|
-
responseReachableModels
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
848
|
+
|
|
849
|
+
const aliasNeedsDeserialize = responseReachableModels === undefined || responseReachableModels.has(model.name);
|
|
850
|
+
const canonicalHasDeserialize =
|
|
851
|
+
responseReachableModels === undefined || responseReachableModels.has(canonicalName);
|
|
852
|
+
const canAliasToCanonical = !aliasNeedsDeserialize || canonicalHasDeserialize;
|
|
853
|
+
if (canAliasToCanonical) {
|
|
854
|
+
const rel = relativeImport(serializerPath, canonSerializerPath);
|
|
855
|
+
const canonSkipSerialize = skippedSerializeModels.has(canonicalName) || skippedSerializeModels.has(model.name);
|
|
856
|
+
const canonSkipDeserialize =
|
|
857
|
+
responseReachableModels !== undefined &&
|
|
858
|
+
!responseReachableModels.has(canonicalName) &&
|
|
859
|
+
!responseReachableModels.has(model.name);
|
|
860
|
+
if (canonSkipSerialize && canonSkipDeserialize) continue;
|
|
861
|
+
const parts: string[] = [];
|
|
862
|
+
if (!canonSkipDeserialize) {
|
|
863
|
+
parts.push(`deserialize${canonDomainName} as deserialize${domainName}`);
|
|
864
|
+
}
|
|
865
|
+
if (!canonSkipSerialize) {
|
|
866
|
+
parts.push(`serialize${canonDomainName} as serialize${domainName}`);
|
|
867
|
+
}
|
|
868
|
+
const reexportContent = `export { ${parts.join(', ')} } from '${rel}';`;
|
|
869
|
+
files.push({
|
|
870
|
+
path: serializerPath,
|
|
871
|
+
content: reexportContent,
|
|
872
|
+
overwriteExisting: true,
|
|
873
|
+
});
|
|
874
|
+
continue;
|
|
828
875
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
content: reexportContent,
|
|
833
|
-
overwriteExisting: true,
|
|
834
|
-
});
|
|
835
|
-
continue;
|
|
876
|
+
// The alias is response-reachable, but the canonical model is
|
|
877
|
+
// request-only. Generate a local serializer instead of re-exporting a
|
|
878
|
+
// deserialize helper that the canonical serializer intentionally omits.
|
|
836
879
|
}
|
|
837
880
|
|
|
838
881
|
const dirName = resolveDir(service);
|
|
@@ -906,7 +949,7 @@ export function generateSerializers(
|
|
|
906
949
|
}
|
|
907
950
|
const liveRootForBarrel = ctx.outputDir ?? ctx.targetDir;
|
|
908
951
|
for (const [dir, stems] of serializersByDir) {
|
|
909
|
-
if (liveRootForBarrel) {
|
|
952
|
+
if (liveRootForBarrel && !isNodeOwnedService(ctx, dir)) {
|
|
910
953
|
const serializersDir = path.join(liveRootForBarrel, 'src', dir, 'serializers');
|
|
911
954
|
try {
|
|
912
955
|
for (const entry of fs.readdirSync(serializersDir)) {
|
package/src/node/naming.ts
CHANGED
|
@@ -73,6 +73,16 @@ export function isAdoptedModelName(name: string): boolean {
|
|
|
73
73
|
return adoptedModelNames.has(name);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* IR model names handled by the discriminated-models module. These must not
|
|
78
|
+
* be remapped by the structural matcher because that module emits files and
|
|
79
|
+
* helpers under the original IR names.
|
|
80
|
+
*/
|
|
81
|
+
let discriminatedModelNames: Set<string> = new Set();
|
|
82
|
+
export function setDiscriminatedModelNames(names: Set<string>): void {
|
|
83
|
+
discriminatedModelNames = names;
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
/**
|
|
77
87
|
* Domain names that `resolveInterfaceName` reached via a structural rename
|
|
78
88
|
* — the resolved name differs from the IR model's own name. `wireInterfaceName`
|
|
@@ -226,7 +236,10 @@ export function resolveInterfaceName(name: string, ctx: EmitterContext, opts?: {
|
|
|
226
236
|
const existing = ctx.overlayLookup?.interfaceByName?.get(name);
|
|
227
237
|
if (existing) return existing;
|
|
228
238
|
|
|
229
|
-
let inferred =
|
|
239
|
+
let inferred =
|
|
240
|
+
adoptedModelNames.has(name) || discriminatedModelNames.has(name)
|
|
241
|
+
? undefined
|
|
242
|
+
: ctx.overlayLookup?.modelNameByIR?.get(name);
|
|
230
243
|
if (inferred) {
|
|
231
244
|
if (inferred.startsWith('Serialized')) {
|
|
232
245
|
const stripped = inferred.slice('Serialized'.length);
|
|
@@ -1,41 +1,6 @@
|
|
|
1
1
|
import type { EmitterContext, Model, ResolvedOperation } from '@workos/oagen';
|
|
2
2
|
import { enrichModelsFromSpec } from '../shared/model-utils.js';
|
|
3
|
-
|
|
4
|
-
type OperationOverride = {
|
|
5
|
-
methodName?: string;
|
|
6
|
-
mountOn?: string;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const OPERATION_OVERRIDES: Record<string, OperationOverride> = {
|
|
10
|
-
'POST /organizations/{organizationId}/groups': {
|
|
11
|
-
methodName: 'create_group',
|
|
12
|
-
},
|
|
13
|
-
'GET /organizations/{organizationId}/groups': {
|
|
14
|
-
methodName: 'list_groups',
|
|
15
|
-
},
|
|
16
|
-
'GET /organizations/{organizationId}/groups/{groupId}': {
|
|
17
|
-
methodName: 'get_group',
|
|
18
|
-
},
|
|
19
|
-
'PATCH /organizations/{organizationId}/groups/{groupId}': {
|
|
20
|
-
methodName: 'update_group',
|
|
21
|
-
},
|
|
22
|
-
'DELETE /organizations/{organizationId}/groups/{groupId}': {
|
|
23
|
-
methodName: 'delete_group',
|
|
24
|
-
},
|
|
25
|
-
'POST /organizations/{organizationId}/groups/{groupId}/organization-memberships': {
|
|
26
|
-
methodName: 'add_organization_membership',
|
|
27
|
-
},
|
|
28
|
-
'GET /organizations/{organizationId}/groups/{groupId}/organization-memberships': {
|
|
29
|
-
methodName: 'list_organization_memberships',
|
|
30
|
-
},
|
|
31
|
-
'DELETE /organizations/{organizationId}/groups/{groupId}/organization-memberships/{omId}': {
|
|
32
|
-
methodName: 'remove_organization_membership',
|
|
33
|
-
},
|
|
34
|
-
'GET /user_management/organization_memberships/{omId}/groups': {
|
|
35
|
-
methodName: 'list_groups_for_organization_membership',
|
|
36
|
-
mountOn: 'UserManagement',
|
|
37
|
-
},
|
|
38
|
-
};
|
|
3
|
+
import { nodeOptions } from './options.js';
|
|
39
4
|
|
|
40
5
|
const contextCache = new WeakMap<EmitterContext, EmitterContext>();
|
|
41
6
|
|
|
@@ -88,9 +53,11 @@ export function withNodeOperationOverrides(ctx: EmitterContext): EmitterContext
|
|
|
88
53
|
return next;
|
|
89
54
|
}
|
|
90
55
|
|
|
56
|
+
const configOverrides = nodeOptions(ctx).operationOverrides ?? {};
|
|
57
|
+
|
|
91
58
|
let opsChanged = false;
|
|
92
59
|
const nextResolved = resolvedOperations.map((resolved) => {
|
|
93
|
-
const override =
|
|
60
|
+
const override = configOverrides[operationKey(resolved)];
|
|
94
61
|
if (!override) return resolved;
|
|
95
62
|
|
|
96
63
|
const methodName = override.methodName ?? resolved.methodName;
|
package/src/node/options.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { EmitterContext } from '@workos/oagen';
|
|
2
2
|
|
|
3
|
+
export interface OperationOverride {
|
|
4
|
+
methodName?: string;
|
|
5
|
+
mountOn?: string;
|
|
6
|
+
optionsType?: string;
|
|
7
|
+
bodyFieldMap?: Record<string, string>;
|
|
8
|
+
returnType?: string;
|
|
9
|
+
returnDataProperty?: string;
|
|
10
|
+
returnTypeImports?: string[];
|
|
11
|
+
returnExpression?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
3
14
|
export interface NodeEmitterOptions {
|
|
4
15
|
/**
|
|
5
16
|
* Existing SDK mode normally drops brand-new paths to avoid large accidental
|
|
@@ -21,6 +32,12 @@ export interface NodeEmitterOptions {
|
|
|
21
32
|
* and fixtures remain hand-owned.
|
|
22
33
|
*/
|
|
23
34
|
regenerateOwnedTests?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Node-specific operation overrides keyed by "METHOD /path".
|
|
37
|
+
* Allows renaming methods or remounting operations for the Node SDK
|
|
38
|
+
* without affecting other languages.
|
|
39
|
+
*/
|
|
40
|
+
operationOverrides?: Record<string, OperationOverride>;
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
export function nodeOptions(ctx: EmitterContext): NodeEmitterOptions {
|
|
@@ -32,10 +49,21 @@ function normalizeServiceName(name: string): string {
|
|
|
32
49
|
return name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
|
33
50
|
}
|
|
34
51
|
|
|
52
|
+
function ownedLookupNames(name: string): string[] {
|
|
53
|
+
const names = [name];
|
|
54
|
+
const baselineDirPrefix = '__baseline_dir__:';
|
|
55
|
+
if (name.startsWith(baselineDirPrefix)) {
|
|
56
|
+
names.push(name.slice(baselineDirPrefix.length));
|
|
57
|
+
}
|
|
58
|
+
return names;
|
|
59
|
+
}
|
|
60
|
+
|
|
35
61
|
export function isNodeOwnedService(ctx: EmitterContext, ...names: Array<string | undefined>): boolean {
|
|
36
62
|
const configured = nodeOptions(ctx).ownedServices ?? [];
|
|
37
63
|
if (configured.length === 0) return false;
|
|
38
64
|
|
|
39
65
|
const owned = new Set(configured.map(normalizeServiceName));
|
|
40
|
-
return names.some((name) =>
|
|
66
|
+
return names.some((name) =>
|
|
67
|
+
name !== undefined ? ownedLookupNames(name).some((candidate) => owned.has(normalizeServiceName(candidate))) : false,
|
|
68
|
+
);
|
|
41
69
|
}
|