import * as LibFs from 'mz/fs'; import * as program from 'commander'; import * as LibPath from 'path'; import { addIntoRpcMethodImportPathInfos, genRpcMethodInfo, mkdir, parseMsgNamesFromProto, parseProto, parseServicesFromProto, Proto, ProtoFile, ProtoMsgImportInfos, ProtoParseResult, readProtoList, RpcMethodImportPathInfos, RpcMethodInfo, RpcProtoClientInfo, ucfirst, } from './lib/lib'; import { TplEngine } from './lib/template'; import { IParserResult as ProtoIParserResult, Service as ProtobufService } from 'protobufjs'; const pkg = require('../../package.json'); program.version(pkg.version) .option('-p, --proto ', 'directory of proto files') .option('-i, --import ', 'third party proto import path: e.g path1,path2,path3', val => val.split(',')) .option('-o, --output ', 'directory to output service codes') .option('-e, --exclude ', 'files or paths in -p shall be excluded: e.g file1,path1,path2,file2', val => val.split(',')) .option('-z, --zipkin', 'need add zipkin plugin') .parse(process.argv); const PROTO_DIR = (program as any).proto === undefined ? undefined : LibPath.normalize((program as any).proto); const IMPORTS = (program as any).import === undefined ? [] : (program as any).import; const OUTPUT_DIR = (program as any).output === undefined ? undefined : LibPath.normalize((program as any).output); const EXCLUDES = (program as any).exclude === undefined ? [] : (program as any).exclude; const ZIPKIN = (program as any).zipkin === undefined ? undefined : true; class ClientCLI { private _protoFiles: Array = []; private _packageName: string; static instance() { return new ClientCLI(); } /** * Read import proto and generate DependencyClients * Only import proto need a client */ public async run() { console.log('ClientCLI start.'); await this._validate(); await this._loadSelfProtos(); await this._loadProtos(); await this._genProtoDependencyClients(); } private async _validate() { console.log('ClientCLI validate.'); if (!PROTO_DIR) { throw new Error('--proto is required'); } if (!OUTPUT_DIR) { throw new Error('--output is required'); } let outputStat = await LibFs.stat(OUTPUT_DIR); if (!outputStat.isDirectory()) { throw new Error('--output is not a directory'); } } private async _loadSelfProtos() { console.log('ClientCLI load self proto file.'); // Package name is same so only need parse one proto const protoFiles: ProtoFile[] = await readProtoList(PROTO_DIR, OUTPUT_DIR); const parseResult: ProtoIParserResult = await parseProto(protoFiles[0]); this._packageName = parseResult.package; } private async _loadProtos() { console.log('ClientCLI load proto files.'); if (IMPORTS.length > 0) { for (let i = 0; i < IMPORTS.length; i++) { this._protoFiles = this._protoFiles.concat(await readProtoList(LibPath.normalize(IMPORTS[i]), OUTPUT_DIR)); } } if (this._protoFiles.length === 0) { throw new Error('no proto files found'); } } private async _genProtoDependencyClients() { console.log('ClientCLI generate clients.'); let protoMsgImportInfos: ProtoMsgImportInfos = {}; let parseResults = [] as Array; for (let i = 0; i < this._protoFiles.length; i++) { let protoFile = this._protoFiles[i]; if (!protoFile) { continue; } let parseResult = {} as ProtoParseResult; parseResult.result = await parseProto(protoFile); parseResult.protoFile = protoFile; parseResults.push(parseResult); let msgImportInfos = parseMsgNamesFromProto(parseResult.result, protoFile); Object.assign(protoMsgImportInfos, msgImportInfos); } await mkdir(LibPath.join(OUTPUT_DIR, 'clients')); for (let i = 0; i < parseResults.length; i++) { let protoInfo = parseResults[i] as ProtoParseResult; let services = parseServicesFromProto(protoInfo.result); if (services.length === 0) { continue; } /** * Only MS need client & one MS only have one client */ const service = services[0]; // handle excludes let protoFilePath = LibPath.join(protoInfo.protoFile.protoPath, protoInfo.protoFile.relativePath, protoInfo.protoFile.fileName); let shallIgnore = false; if (EXCLUDES.length > 0) { EXCLUDES.forEach((exclude: string) => { if (protoFilePath.indexOf(LibPath.normalize(exclude)) !== -1) { shallIgnore = true; } }); } if (shallIgnore) { continue; } const protoName: string = protoInfo.result.package; const ucBaseName: string = ucfirst(protoName); let protoClientInfo = { packageName: this._packageName.toUpperCase(), protoName: protoName, ucProtoName: protoName.toUpperCase(), className: `MS${ucBaseName}Client`, protoFile: protoInfo.protoFile, protoImportPath: Proto.genProtoClientImportPath(protoInfo.protoFile).replace(/\\/g, '/'), methodList: {} as Array, useZipkin: ZIPKIN, } as RpcProtoClientInfo; const outputPath = Proto.genFullOutputClientPath(protoInfo.protoFile); let methodInfos = this._genMethodInfos(protoInfo.protoFile, service, outputPath, protoMsgImportInfos); protoClientInfo.clientName = service.name; protoClientInfo.methodList = methodInfos; let allMethodImportPath: RpcMethodImportPathInfos = {}; for (const method of protoClientInfo.methodList) { for (const key of Object.keys(method.protoMsgImportPath)) { for (const value of method.protoMsgImportPath[key]) { addIntoRpcMethodImportPathInfos(allMethodImportPath, value, key); } } } const importPath = Object.keys(allMethodImportPath)[0]; protoClientInfo.allMethodImportPath = importPath.replace(/\\/g, '/'); const moduleSet = new Set(allMethodImportPath[importPath]); // check same name protoClientInfo.allMethodImportModule = [...moduleSet]; await mkdir(LibPath.dirname(outputPath)); let content = TplEngine.render('rpcs/client', { ...protoClientInfo, }); await LibFs.writeFile(outputPath, content); } } private _genMethodInfos(protoFile: ProtoFile, service: ProtobufService, outputPath: string, importInfos: ProtoMsgImportInfos): Array { console.log('ClientCLI generate method infos: %s', service.name); let methodKeys = Object.keys(service.methods); if (methodKeys.length === 0) { return; } let methodInfos = []; for (const methodKey of methodKeys) { const method = service.methods[methodKey]; const methodInfo = genRpcMethodInfo(protoFile, method, outputPath, importInfos, 'clients'); methodInfos.push(methodInfo); } return methodInfos; } } ClientCLI.instance().run().catch((err: Error) => { console.log('err: ', err.message); });