From c4aebdfc8d641c7e0a68f9955889eb5f5f2bcb1e Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 1 Jun 2026 10:41:14 +0000 Subject: [PATCH] feat(gapic-generator-typescript): add compatibility-resources flag This allows resources which have multiple patterns to also still generate the functions/templates which would have been generated if it were a single-pattern resource. Addresses #7718 but in a selective way. (We don't *always* generate the legacy pattern, only when requested.) --- .../src/gapic-generator-typescript.ts | 9 +++++++++ .../typescript/src/generator.ts | 9 +++++++++ .../typescript/src/schema/naming.ts | 1 + .../typescript/src/schema/proto.ts | 10 ++++++++++ .../src/schema/resource-database.ts | 20 +++++++++++++++++++ 5 files changed, 49 insertions(+) diff --git a/core/generator/gapic-generator-typescript/typescript/src/gapic-generator-typescript.ts b/core/generator/gapic-generator-typescript/typescript/src/gapic-generator-typescript.ts index 86f94791dc57..ebb6057e2c8a 100755 --- a/core/generator/gapic-generator-typescript/typescript/src/gapic-generator-typescript.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/gapic-generator-typescript.ts @@ -136,6 +136,11 @@ async function main(processArgv: string[]) { 'Override the list of mixins to use. Semicolon-separated list of API names to mixin, e.g. google.longrunning.Operations. Use "none" to disable all mixins.', ) .string('mixins') + .describe( + 'compatibility-resources', + 'A list of multi-pattern resources names which should also generate old single-pattern functions. Semicolon-separated list of resource names, e.g. secretmanager.googleapis.com/Secret;secretmanager.googleapis.com/SecretVersion', + ) + .string('compatibility-resources') .describe('protoc', 'Path to protoc binary') .usage('Usage: $0 -I /path/to/googleapis') .usage(' --output_dir /path/to/output_directory') @@ -158,6 +163,7 @@ async function main(processArgv: string[]) { const legacyProtoLoad = argv.legacyProtoLoad as boolean | undefined; const restNumericEnums = argv.restNumericEnums as boolean | undefined; const mixins = argv.mixins as string | undefined; + const compatibilityResources = argv.compatibilityResources as string | undefined; // --protoc can be taken from environment or from the command line let protocParameter = argv.protoc as string | string[] | undefined; @@ -247,6 +253,9 @@ async function main(processArgv: string[]) { if (mixins) { protocCommand.push(`--typescript_gapic_opt="mixins=${mixins}"`); } + if (compatibilityResources) { + protocCommand.push(`--typescript_gapic_opt="compatibility-resources=${compatibilityResources}"`); + } protocCommand.push(...protoDirsArg); protocCommand.push(...protoFiles); protocCommand.push(`-I${commonProtoPath}`); diff --git a/core/generator/gapic-generator-typescript/typescript/src/generator.ts b/core/generator/gapic-generator-typescript/typescript/src/generator.ts index 84a8e818781f..96867e3d50aa 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/generator.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/generator.ts @@ -86,6 +86,7 @@ export class Generator { restNumericEnums?: boolean; mixinsOverride?: string[]; format?: string | string[]; + compatibilityResources?: string[]; private root: protobuf.Root; @@ -245,6 +246,12 @@ export class Generator { } } + private readCompatibilityResources() { + if (this.paramMap['compatibility-resources']) { + this.compatibilityResources = this.paramMap['compatibility-resources'].split(';'); + } + } + async initializeFromStdin() { const inputBuffer = await getStdin(); const CodeGeneratorRequest = this.root.lookupType('CodeGeneratorRequest'); @@ -274,6 +281,7 @@ export class Generator { this.readLegacyProtoLoad(); this.readRestNumericEnums(); this.readFormat(); + this.readCompatibilityResources(); } } @@ -334,6 +342,7 @@ export class Generator { legacyProtoLoad: this.legacyProtoLoad, restNumericEnums: this.restNumericEnums, mixinsOverridden: this.mixinsOverride !== undefined, + compatibilityResources: this.compatibilityResources, }); return api; } diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts index 0d95f68a732c..d1ce56958ba7 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts @@ -30,6 +30,7 @@ export interface Options { legacyProtoLoad?: boolean; restNumericEnums?: boolean; mixinsOverridden?: boolean; + compatibilityResources?: string[]; } export class Naming { diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/proto.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/proto.ts index 60c6673b67bd..6c5414266c51 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/proto.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/proto.ts @@ -1193,6 +1193,16 @@ export function augmentService(parameters: AugmentServiceParameters) { } } } + if (parameters.options.compatibilityResources) { + for (const type of parameters.options.compatibilityResources) { + const compatibilityResource = parameters.allResourceDatabase.getCompatibilityResourceByType(type) + if (compatibilityResource) { + uniqueResources[compatibilityResource.name] = compatibilityResource; + } else { + throw new Error('Resource "${type} has no compatibility pattern') + } + } + } augmentedService.pathTemplates = Object.values(uniqueResources).sort( (resourceA, resourceB) => { // Path templates names can be cased differently diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/resource-database.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/resource-database.ts index e4415e6169d3..852559d36920 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/resource-database.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/resource-database.ts @@ -24,10 +24,12 @@ export interface ResourceDescriptor export class ResourceDatabase { patterns: {[pattern: string]: ResourceDescriptor}; types: {[type: string]: ResourceDescriptor}; + compatibilityResources: {[type: string]: ResourceDescriptor} constructor() { this.patterns = {}; this.types = {}; + this.compatibilityResources = {}; } registerResource( @@ -104,6 +106,18 @@ export class ResourceDatabase { this.types[resource.type] = resourceDescriptor; } } + // Create the pattern we *would* have generated if it were single. Whether + // this is actually generated depends on the compatibility-resources + // option. + const pattern = patterns![0] + const name = arr![1]; + const params = this.getParams(pattern); + let compatibilityResourceDescriptor = this.getResourceDescriptor( + name, + params, + resource, + ); + this.compatibilityResources[resource.type] = compatibilityResourceDescriptor } } @@ -180,6 +194,12 @@ export class ResourceDatabase { return result; } + getCompatibilityResourceByType( + type: string, + ): ResourceDescriptor | undefined { + return this.compatibilityResources[type]; + } + private getParams(pattern: string): string[] { const params = pattern.match(/{[a-zA-Z_]+(?:=.*?)?}/g) || []; const result = params.map(p => p.replace(/{([a-zA-Z_]+).*/, '$1'));