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'; 19import * as argModule from 'arg'; 20const arg = argModule.default; 21 22const googProtobufPath = require.resolve('google-protobuf'); 23const googProtobufModule = fs.readFileSync(googProtobufPath, 'utf-8'); 24 25const args = arg({ 26 // Types 27 '--proto': [String], 28 '--out': String, 29 30 // Aliases 31 '-p': '--proto', 32}); 33 34const protos = args['--proto']; 35const outDir = args['--out'] || 'protos'; 36 37fs.mkdirSync(outDir, { recursive: true }); 38 39const run = function (executable: string, args: string[]) { 40 return new Promise<void>((resolve) => { 41 exec( 42 `${executable} ${args.join(' ')}`, 43 { cwd: process.cwd() }, 44 (error: ExecException | null, stdout: string | Buffer) => { 45 if (error) { 46 throw error; 47 } 48 49 console.log(stdout); 50 resolve(); 51 }, 52 ); 53 }); 54}; 55 56const protoc = async function (protos: string[], outDir: string) { 57 const PROTOC_GEN_TS_PATH = path.resolve( 58 path.dirname(require.resolve('ts-protoc-gen/generate.js')), 59 '..', 60 '.bin', 61 'protoc-gen-ts', 62 ); 63 64 await run('protoc', [ 65 `--plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}"`, 66 `--descriptor_set_out=${outDir}/descriptor.bin`, 67 `--js_out=import_style=commonjs,binary:${outDir}`, 68 `--ts_out=${outDir}`, 69 `--proto_path=${process.cwd()}`, 70 ...protos, 71 ]); 72 73 // ES6 workaround: Replace google-protobuf imports with entire library. 74 protos.forEach((protoPath) => { 75 const outPath = path.join(outDir, protoPath.replace('.proto', '_pb.js')); 76 77 if (fs.existsSync(outPath)) { 78 let data = fs.readFileSync(outPath, 'utf8'); 79 data = data.replace( 80 "var jspb = require('google-protobuf');", 81 googProtobufModule, 82 ); 83 data = data.replace('var goog = jspb;', ''); 84 fs.writeFileSync(outPath, data); 85 } 86 }); 87}; 88 89const makeProtoCollection = function ( 90 descriptorBinPath: string, 91 protoPath: string, 92 importPath: string, 93) { 94 const outputCollectionName = 95 path.extname(require.resolve('./ts_proto_collection.template')) === '.ts' 96 ? 'collection.ts' 97 : 'collection.js'; 98 generateTemplate( 99 `${protoPath}/${outputCollectionName}`, 100 descriptorBinPath, 101 require.resolve('./ts_proto_collection.template'), 102 importPath, 103 ); 104}; 105 106protoc(protos, outDir).then(() => { 107 makeProtoCollection( 108 path.join(outDir, 'descriptor.bin'), 109 outDir, 110 'pigweedjs/protos', 111 ); 112}); 113