From 55835232be72648b299345ff4215d81e70d32035 Mon Sep 17 00:00:00 2001 From: Xiaozhen Liu Date: Tue, 19 Nov 2019 16:07:32 -0800 Subject: [PATCH] feat: let user pass package name to the generator (#148) * let user pass package name to generator * feedback * use param map for options * throw error if grpc file does not exist * make paramMap a member of generator class * comment --- templates/typescript_gapic/package.json.njk | 2 +- .../fixtures/sample/src/index.js.njk | 2 +- .../fixtures/sample/src/index.ts.njk | 2 +- typescript/src/generator.ts | 55 ++++++++++++++----- typescript/src/schema/api.ts | 7 ++- typescript/src/start_script.ts | 6 ++ .../texttospeech/package.json.baseline | 2 +- .../fixtures/sample/src/index.js.baseline | 2 +- .../fixtures/sample/src/index.ts.baseline | 2 +- ...{grpc-client-config.ts => extra-option.ts} | 20 ++++++- 10 files changed, 76 insertions(+), 24 deletions(-) rename typescript/test/unit/{grpc-client-config.ts => extra-option.ts} (76%) diff --git a/templates/typescript_gapic/package.json.njk b/templates/typescript_gapic/package.json.njk index eaf401433..9421ea11b 100644 --- a/templates/typescript_gapic/package.json.njk +++ b/templates/typescript_gapic/package.json.njk @@ -16,7 +16,7 @@ limitations under the License. -#} { - "name": "{{ api.naming.productName.toKebabCase() }}", + "name": "{{ api.publishName }}", "version": "0.1.0", "description": "{{ api.naming.productName }} client for Node.js", "repository": "googleapis/nodejs-{{ api.naming.productName.toKebabCase() }}", diff --git a/templates/typescript_gapic/system-test/fixtures/sample/src/index.js.njk b/templates/typescript_gapic/system-test/fixtures/sample/src/index.js.njk index 4314786a6..1e869dba9 100644 --- a/templates/typescript_gapic/system-test/fixtures/sample/src/index.js.njk +++ b/templates/typescript_gapic/system-test/fixtures/sample/src/index.js.njk @@ -19,7 +19,7 @@ limitations under the License. {{license.license()}} /* eslint-disable node/no-missing-require, no-unused-vars */ -const {{ api.naming.productName.toKebabCase()}} = require('{{ api.naming.productName.toKebabCase() }}'); +const {{ api.naming.productName.toKebabCase()}} = require('{{ api.publishName }}'); function main() { {%- for service in api.services %} diff --git a/templates/typescript_gapic/system-test/fixtures/sample/src/index.ts.njk b/templates/typescript_gapic/system-test/fixtures/sample/src/index.ts.njk index 78ba439d9..9f617615f 100644 --- a/templates/typescript_gapic/system-test/fixtures/sample/src/index.ts.njk +++ b/templates/typescript_gapic/system-test/fixtures/sample/src/index.ts.njk @@ -23,7 +23,7 @@ import { {{- serviceJoiner() -}} {{- service.name.toPascalCase() + 'Client' -}} {%- endfor -%} -} from '{{ api.naming.productName.toKebabCase() }}'; +} from '{{ api.publishName }}'; function main() { {%- for service in api.services %} diff --git a/typescript/src/generator.ts b/typescript/src/generator.ts index 91479ea56..e24dc904b 100644 --- a/typescript/src/generator.ts +++ b/typescript/src/generator.ts @@ -23,6 +23,9 @@ import { API } from './schema/api'; import { processTemplates } from './templater'; import { commonPrefix, duration } from './util'; +export interface OptionsMap { + [name: string]: string; +} const readFile = util.promisify(fs.readFile); const templateDirectory = path.join( @@ -32,6 +35,7 @@ const templateDirectory = path.join( 'templates', 'typescript_gapic' ); + // If needed, we can make it possible to load templates from different locations // to generate code for other languages. @@ -39,11 +43,15 @@ export class Generator { request: plugin.google.protobuf.compiler.CodeGeneratorRequest; response: plugin.google.protobuf.compiler.CodeGeneratorResponse; grpcServiceConfig: plugin.grpc.service_config.ServiceConfig; + paramMap: OptionsMap; + // This field is for users passing proper publish package name like @google-cloud/text-to-speech. + publishName?: string; constructor() { this.request = plugin.google.protobuf.compiler.CodeGeneratorRequest.create(); this.response = plugin.google.protobuf.compiler.CodeGeneratorResponse.create(); this.grpcServiceConfig = plugin.grpc.service_config.ServiceConfig.create(); + this.paramMap = {}; } // Fixes gRPC service config to replace string google.protobuf.Duration @@ -65,22 +73,36 @@ export class Generator { } } - private async readGrpcServiceConfig(parameter: string) { - const match = parameter.match(/^["']?grpc-service-config=([^"]+)["']?$/); - if (!match) { - throw new Error(`Parameter ${parameter} was not recognized.`); + private getParamMap(parameter: string) { + // Example: "grpc-service-config=texamplejson","package-name=packageName" + const parameters = parameter.split(','); + for (let param of parameters) { + // remove double quote + param = param.substring(1, param.length - 1); + const arr = param.split('='); + this.paramMap[arr[0].toKebabCase()] = arr[1]; } - const filename = match[1]; - if (!fs.existsSync(filename)) { - throw new Error(`File ${filename} cannot be opened.`); + } + + private async readGrpcServiceConfig(map: OptionsMap) { + if (map && map['grpc-service-config']) { + const filename = map['grpc-service-config']; + if (!fs.existsSync(filename)) { + throw new Error(`File ${filename} cannot be opened.`); + } + const content = await readFile(filename); + const json = JSON.parse(content.toString()); + Generator.updateDuration(json); + this.grpcServiceConfig = plugin.grpc.service_config.ServiceConfig.fromObject( + json + ); } + } - const content = await readFile(filename); - const json = JSON.parse(content.toString()); - Generator.updateDuration(json); - this.grpcServiceConfig = plugin.grpc.service_config.ServiceConfig.fromObject( - json - ); + private async readPublishPackageName(map: OptionsMap) { + if (map && map['package-name']) { + this.publishName = map['package-name']; + } } async initializeFromStdin() { @@ -89,7 +111,9 @@ export class Generator { inputBuffer ); if (this.request.parameter) { - await this.readGrpcServiceConfig(this.request.parameter); + this.getParamMap(this.request.parameter); + await this.readGrpcServiceConfig(this.paramMap); + await this.readPublishPackageName(this.paramMap); } } @@ -125,7 +149,8 @@ export class Generator { const api = new API( this.request.protoFile, packageName, - this.grpcServiceConfig + this.grpcServiceConfig, + this.publishName ); return api; } diff --git a/typescript/src/schema/api.ts b/typescript/src/schema/api.ts index 3782cd3e0..0b17c0d70 100644 --- a/typescript/src/schema/api.ts +++ b/typescript/src/schema/api.ts @@ -32,19 +32,24 @@ export class API { hostName?: string; port?: string; mainServiceName?: string; + // This field is for users passing proper publish package name like @google-cloud/text-to-speech. + publishName: string; // oauth_scopes: plugin.google.protobuf.IServiceOptions.prototype[".google.api.oauthScopes"]; // TODO: subpackages constructor( fileDescriptors: plugin.google.protobuf.IFileDescriptorProto[], packageName: string, - grpcServiceConfig: plugin.grpc.service_config.ServiceConfig + grpcServiceConfig: plugin.grpc.service_config.ServiceConfig, + publishName?: string ) { this.naming = new Naming( fileDescriptors.filter( fd => fd.package && fd.package.startsWith(packageName) ) ); + // users specify the actual package name, if not, set it to product name. + this.publishName = publishName || this.naming.productName.toKebabCase(); // construct resource map const resourceMap = getResourceMap(fileDescriptors); // parse resource map to Proto constructor diff --git a/typescript/src/start_script.ts b/typescript/src/start_script.ts index 80c6dccab..e4af8ca17 100755 --- a/typescript/src/start_script.ts +++ b/typescript/src/start_script.ts @@ -34,6 +34,8 @@ const argv = yargs .describe('output_dir', 'Path to a directory for the generated code') .alias('grpc-service-config', 'grpc_service_config') .describe('grpc-service-config', 'Path to gRPC service config JSON') + .alias('package-name', 'package_name') + .describe('package-name', 'Publish package name') .alias('common-proto-path', 'common_protos_path') .describe( 'common_proto_path', @@ -43,6 +45,7 @@ const argv = yargs google/example/api/v1/api.proto`).argv; const outputDir = argv.outputDir as string; const grpcServiceConfig = argv.grpcServiceConfig as string | undefined; +const packageName = argv.packageName as string | undefined; const protoDirs: string[] = []; if (argv.I) { @@ -71,6 +74,9 @@ if (grpcServiceConfig) { `--typescript_gapic_opt="grpc-service-config=${grpcServiceConfig}"` ); } +if (packageName) { + protocCommand.push(`--typescript_gapic_opt="package-name=${packageName}"`); +} protocCommand.push(...protoDirsArg); protocCommand.push(...protoFiles); try { diff --git a/typescript/test/testdata/texttospeech/package.json.baseline b/typescript/test/testdata/texttospeech/package.json.baseline index 015bcea81..a55c5077d 100644 --- a/typescript/test/testdata/texttospeech/package.json.baseline +++ b/typescript/test/testdata/texttospeech/package.json.baseline @@ -1,5 +1,5 @@ { - "name": "texttospeech", + "name": "@google-cloud/text-to-speech", "version": "0.1.0", "description": "Texttospeech client for Node.js", "repository": "googleapis/nodejs-texttospeech", diff --git a/typescript/test/testdata/texttospeech/system-test/fixtures/sample/src/index.js.baseline b/typescript/test/testdata/texttospeech/system-test/fixtures/sample/src/index.js.baseline index cb4f4325c..d76868e97 100644 --- a/typescript/test/testdata/texttospeech/system-test/fixtures/sample/src/index.js.baseline +++ b/typescript/test/testdata/texttospeech/system-test/fixtures/sample/src/index.js.baseline @@ -18,7 +18,7 @@ /* eslint-disable node/no-missing-require, no-unused-vars */ -const texttospeech = require('texttospeech'); +const texttospeech = require('@google-cloud/text-to-speech'); function main() { const textToSpeechClient = new texttospeech.TextToSpeechClient(); diff --git a/typescript/test/testdata/texttospeech/system-test/fixtures/sample/src/index.ts.baseline b/typescript/test/testdata/texttospeech/system-test/fixtures/sample/src/index.ts.baseline index 2e64b80ef..3d5e706fb 100644 --- a/typescript/test/testdata/texttospeech/system-test/fixtures/sample/src/index.ts.baseline +++ b/typescript/test/testdata/texttospeech/system-test/fixtures/sample/src/index.ts.baseline @@ -16,7 +16,7 @@ // ** https://github.com/googleapis/gapic-generator-typescript ** // ** All changes to this file may be overwritten. ** -import {TextToSpeechClient} from 'texttospeech'; +import {TextToSpeechClient} from '@google-cloud/text-to-speech'; function main() { const textToSpeechClient = new TextToSpeechClient(); diff --git a/typescript/test/unit/grpc-client-config.ts b/typescript/test/unit/extra-option.ts similarity index 76% rename from typescript/test/unit/grpc-client-config.ts rename to typescript/test/unit/extra-option.ts index 350a9662b..435fcad98 100644 --- a/typescript/test/unit/grpc-client-config.ts +++ b/typescript/test/unit/extra-option.ts @@ -58,12 +58,13 @@ const BASELINE_DIR = path.join( 'texttospeech' ); +const PACKAGE_NAME = '@google-cloud/text-to-speech'; const SRCDIR = path.join(cwd, 'build', 'src'); const CLI = path.join(SRCDIR, 'cli.js'); -describe('gRPC Client Config', () => { +describe('Package Name & grpc Config', () => { describe('Generate Text-to-Speech library', () => { - it('Generated proto list should have same output with baseline.', function() { + it('Generated library name & grpc Config should be same with baseline.', function() { this.timeout(10000); if (fs.existsSync(OUTPUT_DIR)) { rimraf.sync(OUTPUT_DIR); @@ -82,6 +83,21 @@ describe('gRPC Client Config', () => { `-I ${GOOGLE_GAX_PROTOS_DIR} ` + `-I ${PROTOS_DIR} ` + `--grpc-service-config=${GRPC_SERVICE_CONFIG} ` + + `--package-name=${PACKAGE_NAME} ` + + TTS_PROTO_FILE + ); + assert(equalToBaseline(OUTPUT_DIR, BASELINE_DIR)); + }); + + it('Use alias name should also work.', function() { + this.timeout(10000); + execSync( + `node build/src/start_script.js ` + + `--output-dir=${OUTPUT_DIR} ` + + `-I ${GOOGLE_GAX_PROTOS_DIR} ` + + `-I ${PROTOS_DIR} ` + + `--grpc_service_config=${GRPC_SERVICE_CONFIG} ` + + `--package_name=${PACKAGE_NAME} ` + TTS_PROTO_FILE ); assert(equalToBaseline(OUTPUT_DIR, BASELINE_DIR));