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 52const protoc = async function ( 53 protos: string[], 54 outDir: string, 55 cwd: string = process.cwd(), 56) { 57 const PROTOC_GEN_TS_PATH = getRealPathOfSymlink( 58 path.resolve( 59 path.dirname(require.resolve('ts-protoc-gen/generate.js')), 60 'bin', 61 'protoc-gen-ts', 62 ), 63 ); 64 65 const protocBinary = require.resolve('@protobuf-ts/protoc/protoc.js'); 66 67 await run( 68 protocBinary, 69 [ 70 `--plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}"`, 71 `--descriptor_set_out=${path.join(outDir, 'descriptor.bin')}`, 72 `--js_out=import_style=commonjs,binary:${outDir}`, 73 `--ts_out=${outDir}`, 74 `--proto_path=${cwd}`, 75 ...protos, 76 ], 77 cwd, 78 ); 79 80 // ES6 workaround: Replace google-protobuf imports with entire library. 81 protos.forEach((protoPath) => { 82 const outPath = path.join(outDir, protoPath.replace('.proto', '_pb.js')); 83 84 if (fs.existsSync(outPath)) { 85 let data = fs.readFileSync(outPath, 'utf8'); 86 data = data.replace( 87 "var jspb = require('google-protobuf');", 88 googProtobufModule, 89 ); 90 data = data.replace('var goog = jspb;', ''); 91 fs.writeFileSync(outPath, data); 92 } 93 }); 94}; 95 96const makeProtoCollection = function ( 97 descriptorBinPath: string, 98 protoPath: string, 99 outputCollectionName: string, 100) { 101 generateTemplate(`${protoPath}/${outputCollectionName}`, descriptorBinPath); 102}; 103 104export function buildProtos( 105 protos: string[], 106 outDir: string, 107 outputCollectionName = 'collection.js', 108 cwd: string = process.cwd(), 109) { 110 protoc(protos, outDir, cwd).then(() => { 111 makeProtoCollection( 112 path.join(outDir, 'descriptor.bin'), 113 outDir, 114 outputCollectionName, 115 ); 116 }); 117} 118