Home

Awesome

node-protoc-plugin

Create protoc code-generation plugins easily in nodejs

installation

npm i -S protoc-plugin

usage

You can checkout the code in the example/ dir, but here is a quick example:

#! /usr/bin/env node
const protocPlugin = require('protoc-plugin')

protocPlugin(protos => {
  // do stuff here with protos
  // return array like [{name: 'filename', content: 'CONTENTS'}]
})

Make sure not to output anything to stdout (for example with console.log) because protoc uses stdout. I use console.error to output stuff to the user.

Since it's a promise, you can throw or just return Promise.reject('reason'), and you can do async stuff with promises.

Once you have made your plugin, save it as protoc-gen-NAME, give it executable permissions, then run it like this:

protoc --plugin=protoc-gen-NAME --NAME_out=generated yourfile.proto

If you put it in your path, you don't need the --plugin=protoc-gen-NAME part.

PRO TIP - use npm's bin in your package.json to get your plugin script installed, cross-platform, in the user's path.

findCommentByPath

There is a utility included for finding comments in various places in the protobuf file. It's a lil obtuse, but you can look in the spec for more info.

Here are some locationList addresses I use a lot in protoc plugins:

 * [4, m] - message comments
 * [4, m, 2, f] - field comments in message
 * [6, s] - service comments
 * [6, s, 2, r] - rpc comments in service

where:

like this:

// [4, 0] is right here 
message MyMessage {
  // [4, 0, 2, 0] is right here
  int32 field1 = 1;
}

// [6, 0] is right here
service MyService {
  // [6, 0, 2, 0] is here!
  rpc (MyMessage) returns (MyMessage);
}

There are more addresses, but you will have to look at the the spec to figure it out.

usage

const protocPlugin = require('protoc-plugin')
const findCommentByPath = protocPlugin.findCommentByPath

// output comments for services & messages to stderr
protocPlugin(protos => {
  protos.forEach(proto => {
    proto.serviceList.forEach((service, s) => {
      console.error('SERVICE', service.name, findCommentByPath([6, s], proto.sourceCodeInfo.locationList))
      service.methodList.forEach((rpc, r) => {
        console.error('RPC', rpc.name, findCommentByPath([6, s, 2, r], proto.sourceCodeInfo.locationList))
      })
    })
    proto.messageList.forEach((message, m) => {
      console.error('MESSAGE', message.name, findCommentByPath([4, m], proto.sourceCodeInfo.locationList))
      message.fieldList.forEach((field, f) => {
        console.error('FIELD', field.name, findCommentByPath([4, m, 2, f], proto.sourceCodeInfo.locationList))
      })
    })
  })
  
  // no files written
  return []
})

advanced usage

If you need more from the incoming stdin CodeGeneratorRequest have a look at example/protoc-gen-extendedlogger.

extensions

I am currently including google/api/annotations proto file, so gRPC-annotions will work out of the box (for example see proto/helloworld.proto) For any other extensions, you will need to generate the google-protobuf representation, and require it before parsing. You can easily generate them with a command like this:

protoc --js_out=import_style=commonjs,binary:YOURDIR/ -I PROTODIR/ PROTODIR/YOURFILE.proto

then require like this:

require('./NAMESPACE_pb')

You can see how I have done this with google/api/annotations_pb in index.js.