1// Copyright 2022 The Pigweed Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14 15import { exec, ExecException } from 'child_process'; 16import fs from 'fs'; 17import path from 'path'; 18import generateTemplate from './codegen/template_replacement'; 19const googProtobufPath = require.resolve('google-protobuf'); 20const googProtobufModule = fs.readFileSync(googProtobufPath, 'utf-8'); 21 22const run = function ( 23 executable: string, 24 args: string[], 25 cwd: string = process.cwd(), 26) { 27 return new Promise<void>((resolve) => { 28 exec( 29 `${executable} ${args.join(' ')}`, 30 { cwd }, 31 (error: ExecException | null, stdout: string | Buffer) => { 32 if (error) { 33 throw error; 34 } 35 36 console.log(stdout); 37 resolve(); 38 }, 39 ); 40 }); 41}; 42 43function getRealPathOfSymlink(path: string) { 44 const stats = fs.statSync(path); 45 if (stats.isSymbolicLink()) { 46 return fs.realpathSync(path); 47 } else { 48 return path; 49 } 50} 51 52function getJSPluginPath() { 53 const EXT = process.platform === 'win32' ? '.exe' : ''; 54 const pluginPath = getRealPathOfSymlink( 55 path.resolve( 56 path.dirname(require.resolve('protoc-gen-js')), 57 'bin', 58 'protoc-gen-js' + EXT, 59 ), 60 ); 61 return pluginPath; 62} 63 64const protoc = async function ( 65 protos: string[], 66 outDir: string, 67 cwd: string = process.cwd(), 68) { 69 const PROTOC_GEN_TS_PATH = getRealPathOfSymlink( 70 path.resolve( 71 path.dirname(require.resolve('ts-protoc-gen/generate.js')), 72 'bin', 73 'protoc-gen-ts', 74 ), 75 ); 76 const PROTOC_GEN_JS_PATH = getJSPluginPath(); 77 78 const protocBinary = require.resolve('@protobuf-ts/protoc/protoc.js'); 79 80 await run( 81 protocBinary, 82 [ 83 `--plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}"`, 84 `--plugin="protoc-gen-js=${PROTOC_GEN_JS_PATH}"`, 85 `--descriptor_set_out=${path.join(outDir, 'descriptor.bin')}`, 86 `--js_out=import_style=commonjs,binary:${outDir}`, 87 `--ts_out=${outDir}`, 88 `--proto_path=${cwd}`, 89 ...protos, 90 ], 91 cwd, 92 ); 93 94 // ES6 workaround: Replace google-protobuf imports with entire library. 95 protos.forEach((protoPath) => { 96 const outPath = path.join(outDir, protoPath.replace('.proto', '_pb.js')); 97 98 if (fs.existsSync(outPath)) { 99 let data = fs.readFileSync(outPath, 'utf8'); 100 data = data.replace( 101 "var jspb = require('google-protobuf');", 102 googProtobufModule, 103 ); 104 data = data.replace('var goog = jspb;', ''); 105 fs.writeFileSync(outPath, data); 106 } 107 }); 108}; 109 110const makeProtoCollection = function ( 111 descriptorBinPath: string, 112 protoPath: string, 113 outputCollectionName: string, 114) { 115 generateTemplate(`${protoPath}/${outputCollectionName}`, descriptorBinPath); 116}; 117 118export function buildProtos( 119 protos: string[], 120 outDir: string, 121 outputCollectionName = 'collection.js', 122 cwd: string = process.cwd(), 123) { 124 protoc(protos, outDir, cwd).then(() => { 125 makeProtoCollection( 126 path.join(outDir, 'descriptor.bin'), 127 outDir, 128 outputCollectionName, 129 ); 130 }); 131} 132