Skip to content
This repository was archived by the owner on Nov 18, 2025. It is now read-only.

Commit 1334c6d

Browse files
feat: compileProtos bin script (#547)
* feat: compileProtos bin script * added newlines * added license * add compileProtos.js to files list * test recursive walking * fix proto path in test
1 parent a431c63 commit 1334c6d

File tree

6 files changed

+252
-0
lines changed

6 files changed

+252
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ build
1414
package-lock.json
1515
.system-test-run/
1616
.kitchen-sink/
17+
.compileProtos-test/
1718
__pycache__
1819
doc/

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
"types": "build/src/index.d.ts",
77
"files": [
88
"build/src",
9+
"build/tools/compileProtos.js",
910
"protos"
1011
],
12+
"bin": {
13+
"compileProtos": "./build/tools/compileProtos.js"
14+
},
1115
"dependencies": {
1216
"@grpc/grpc-js": "^0.5.2",
1317
"@grpc/proto-loader": "^0.5.1",

test/compileProtos.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2019 Google LLC
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
* * Redistributions in binary form must reproduce the above
12+
* copyright notice, this list of conditions and the following disclaimer
13+
* in the documentation and/or other materials provided with the
14+
* distribution.
15+
* * Neither the name of Google Inc. nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
import {expect} from 'chai';
33+
import * as assert from 'assert';
34+
import * as fs from 'fs';
35+
import * as rimraf from 'rimraf';
36+
import * as util from 'util';
37+
import * as path from 'path';
38+
import * as protobuf from 'protobufjs';
39+
import * as compileProtos from '../tools/compileProtos';
40+
41+
const readFile = util.promisify(fs.readFile);
42+
const mkdir = util.promisify(fs.mkdir);
43+
const rmrf = util.promisify(rimraf);
44+
45+
const testDir = path.join(process.cwd(), '.compileProtos-test');
46+
const resultDir = path.join(testDir, 'protos');
47+
const cwd = process.cwd();
48+
49+
describe('compileProtos tool', () => {
50+
before(async () => {
51+
if (fs.existsSync(testDir)) {
52+
await rmrf(testDir);
53+
}
54+
await mkdir(testDir);
55+
await mkdir(resultDir);
56+
57+
process.chdir(testDir);
58+
});
59+
60+
after(() => {
61+
process.chdir(cwd);
62+
});
63+
64+
it('compiles protos to JSON', async () => {
65+
await compileProtos.main([
66+
path.join(__dirname, '..', '..', 'test', 'fixtures', 'protoLists'),
67+
]);
68+
const expectedResultFile = path.join(resultDir, 'protos.json');
69+
assert(fs.existsSync(expectedResultFile));
70+
console.log(expectedResultFile);
71+
72+
const json = await readFile(expectedResultFile);
73+
const root = protobuf.Root.fromJSON(JSON.parse(json.toString()));
74+
75+
assert(root.lookup('TestMessage'));
76+
assert(root.lookup('LibraryService'));
77+
});
78+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
"../../google/example/library/v1/test.proto"
3+
]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
"../google/example/library/v1/library.proto"
3+
]

tools/compileProtos.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env node
2+
3+
/*
4+
* Copyright 2019 Google LLC
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are
9+
* met:
10+
*
11+
* * Redistributions of source code must retain the above copyright
12+
* notice, this list of conditions and the following disclaimer.
13+
* * Redistributions in binary form must reproduce the above
14+
* copyright notice, this list of conditions and the following disclaimer
15+
* in the documentation and/or other materials provided with the
16+
* distribution.
17+
* * Neither the name of Google Inc. nor the names of its
18+
* contributors may be used to endorse or promote products derived from
19+
* this software without specific prior written permission.
20+
*
21+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+
*/
33+
34+
import * as fs from 'fs';
35+
import * as path from 'path';
36+
import * as util from 'util';
37+
import * as pbjs from 'protobufjs/cli/pbjs';
38+
39+
const readdir = util.promisify(fs.readdir);
40+
const readFile = util.promisify(fs.readFile);
41+
const stat = util.promisify(fs.stat);
42+
const pbjsMain = util.promisify(pbjs.main);
43+
44+
const PROTO_LIST_REGEX = /_proto_list.json$/;
45+
46+
/**
47+
* Recursively scans directories starting from `directory` and finds all files
48+
* matching `PROTO_LIST_REGEX`.
49+
*
50+
* @param {string} directory Path to start the scan from.
51+
* @return {Promise<string[]} Resolves to an array of strings, each element is a full path to a matching file.
52+
*/
53+
async function findProtoJsonFiles(directory: string): Promise<string[]> {
54+
const result: string[] = [];
55+
const files = await readdir(directory);
56+
for (const file of files) {
57+
const fullPath = path.join(directory, file);
58+
const fileStat = await stat(fullPath);
59+
if (fileStat.isFile() && file.match(PROTO_LIST_REGEX)) {
60+
result.push(fullPath);
61+
} else if (fileStat.isDirectory()) {
62+
const nested = await findProtoJsonFiles(fullPath);
63+
result.push(...nested);
64+
}
65+
}
66+
return result;
67+
}
68+
69+
/**
70+
* Normalizes the Linux path for the current operating system.
71+
*
72+
* @param {string} filePath Linux-style path (with forward slashes)
73+
* @return {string} Normalized path.
74+
*/
75+
function normalizePath(filePath: string): string {
76+
return path.join(...filePath.split('/'));
77+
}
78+
79+
/**
80+
* Returns a combined list of proto files listed in all JSON files given.
81+
*
82+
* @param {string[]} protoJsonFiles List of JSON files to parse
83+
* @return {Promise<string[]>} Resolves to an array of proto files.
84+
*/
85+
async function buildListOfProtos(protoJsonFiles: string[]): Promise<string[]> {
86+
const result: string[] = [];
87+
for (const file of protoJsonFiles) {
88+
const directory = path.dirname(file);
89+
const content = await readFile(file);
90+
const list = JSON.parse(content.toString()).map((filePath: string) =>
91+
path.join(directory, normalizePath(filePath))
92+
);
93+
result.push(...list);
94+
}
95+
return result;
96+
}
97+
98+
/**
99+
* Runs `pbjs` to compile the given proto files, placing the result into
100+
* `./protos/protos.json`.
101+
*
102+
* @param {string[]} protos List of proto files to compile.
103+
*/
104+
async function compileProtos(protos: string[]): Promise<void> {
105+
const pbjsArgs = [
106+
'--target',
107+
'json',
108+
'-p',
109+
path.join('node_modules', 'google-gax', 'protos'),
110+
'-p',
111+
'protos',
112+
'-o',
113+
path.join('protos', 'protos.json'),
114+
];
115+
pbjsArgs.push(...protos);
116+
await pbjsMain(pbjsArgs);
117+
}
118+
119+
/**
120+
* Main function. Takes an array of directories to process.
121+
* Looks for JSON files matching `PROTO_LIST_REGEX`, parses them to get a list of all
122+
* proto files used by the client library, and calls `pbjs` to compile them all into
123+
* JSON (`pbjs -t json`).
124+
*
125+
* Exported to be called from a test.
126+
*
127+
* @param {string[]} directories List of directories to process. Normally, just the
128+
* `./src` folder of the given client library.
129+
*/
130+
export async function main(directories: string[]): Promise<void> {
131+
const protoJsonFiles: string[] = [];
132+
for (const directory of directories) {
133+
protoJsonFiles.push(...(await findProtoJsonFiles(directory)));
134+
}
135+
const protos = await buildListOfProtos(protoJsonFiles);
136+
await compileProtos(protos);
137+
}
138+
139+
/**
140+
* Shows the usage information.
141+
*/
142+
function usage() {
143+
console.log(`Usage: node ${process.argv[1]} directory ...`);
144+
console.log(
145+
`Finds all files matching ${PROTO_LIST_REGEX} in the given directories.`
146+
);
147+
console.log(
148+
`Each of those files should contain a JSON array of proto files used by the`
149+
);
150+
console.log(
151+
`client library. Those proto files will be compiled to JSON using pbjs tool`
152+
);
153+
console.log(`from protobufjs.`);
154+
}
155+
156+
if (require.main === module) {
157+
if (process.argv.length <= 2) {
158+
usage();
159+
process.exit(1);
160+
}
161+
// argv[0] is node.js binary, argv[1] is script path
162+
main(process.argv.slice(2));
163+
}

0 commit comments

Comments
 (0)