@forwardimpact/libcodegen 0.1.47 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -238,6 +238,7 @@ function printSummary(sourcePath, flags) {
238
238
  summary.render(
239
239
  {
240
240
  title: `Generated ${totalFiles} files in ./${relPath}/`,
241
+ ok: true,
241
242
  items,
242
243
  },
243
244
  process.stdout,
package/package.json CHANGED
@@ -1,9 +1,27 @@
1
1
  {
2
2
  "name": "@forwardimpact/libcodegen",
3
- "version": "0.1.47",
4
- "description": "Protocol Buffer code generation utilities for Guide",
3
+ "version": "0.1.49",
4
+ "description": "Protobuf code generation produces the types and clients consumed by `libtype` and `librpc`.",
5
+ "keywords": [
6
+ "codegen",
7
+ "protobuf",
8
+ "build-tool",
9
+ "agent"
10
+ ],
11
+ "homepage": "https://www.forwardimpact.team",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/forwardimpact/monorepo.git",
15
+ "directory": "libraries/libcodegen"
16
+ },
5
17
  "license": "Apache-2.0",
6
18
  "author": "D. Olsson <hi@senzilla.io>",
19
+ "forwardimpact": {
20
+ "capability": "agent-infrastructure",
21
+ "needs": [
22
+ "Generate code from .proto files"
23
+ ]
24
+ },
7
25
  "type": "module",
8
26
  "main": "./src/index.js",
9
27
  "exports": {
@@ -19,10 +37,6 @@
19
37
  "templates/**",
20
38
  "README.md"
21
39
  ],
22
- "engines": {
23
- "bun": ">=1.2.0",
24
- "node": ">=18.0.0"
25
- },
26
40
  "scripts": {
27
41
  "test": "bun test test/*.test.js"
28
42
  },
@@ -33,12 +47,16 @@
33
47
  "@forwardimpact/libutil": "^0.1.64",
34
48
  "@grpc/proto-loader": "^0.8.0",
35
49
  "mustache": "^4.2.0",
36
- "protobufjs": "^7.5.4",
50
+ "protobufjs": "^7.5.6",
37
51
  "protobufjs-cli": "2.0.1"
38
52
  },
39
53
  "devDependencies": {
40
54
  "@forwardimpact/libharness": "^0.1.5"
41
55
  },
56
+ "engines": {
57
+ "bun": ">=1.2.0",
58
+ "node": ">=18.0.0"
59
+ },
42
60
  "publishConfig": {
43
61
  "access": "public"
44
62
  }
package/src/base.js CHANGED
@@ -27,6 +27,29 @@ const PBJS_TYPE_MAP = {
27
27
  bytes: "bytes",
28
28
  };
29
29
 
30
+ /**
31
+ * Extract field metadata from a protobufjs Type
32
+ * @param {protobuf.Type} pbjsType - Resolved protobufjs type
33
+ * @returns {object} Map of snake_case field names to metadata
34
+ */
35
+ function extractFields(pbjsType) {
36
+ const fields = {};
37
+ for (const [camelName, field] of Object.entries(pbjsType.fields)) {
38
+ const name = camelToSnake(camelName);
39
+ const typeName = field.resolvedType
40
+ ? "message"
41
+ : PBJS_TYPE_MAP[field.type] || field.type;
42
+ const hasOptionalKeyword = pbjsType.oneofs?.[`_${camelName}`] !== undefined;
43
+ fields[name] = {
44
+ type: typeName,
45
+ optional: hasOptionalKeyword,
46
+ repeated: field.repeated || false,
47
+ description: field.comment || null,
48
+ };
49
+ }
50
+ return fields;
51
+ }
52
+
30
53
  /**
31
54
  * Base class for code generation utilities providing shared functionality
32
55
  * Implements dependency injection pattern with explicit validation
@@ -279,10 +302,29 @@ export class CodegenBase {
279
302
  if (!parsed) return null;
280
303
 
281
304
  const { packageName, serviceName, methods: parsedMethods } = parsed;
305
+ const root = this.#loadProtobufRoot(protoPath);
306
+
307
+ const methods = {};
308
+ for (const method of parsedMethods) {
309
+ const pbjsType = this.#resolveRequestType(root, method);
310
+ const fields = pbjsType ? extractFields(pbjsType) : {};
311
+ methods[method.name] = {
312
+ requestType: `${method.requestTypeNamespace}.${method.requestType}`,
313
+ fields,
314
+ };
315
+ }
282
316
 
283
- // Load with protobufjs for field types, optionality, and comments
317
+ return { packageName, serviceName, methods };
318
+ }
319
+
320
+ /**
321
+ * Load a protobufjs Root with include-path resolution
322
+ * @param {string} protoPath - Absolute path to .proto file
323
+ * @returns {protobuf.Root}
324
+ */
325
+ #loadProtobufRoot(protoPath) {
284
326
  const root = new protobuf.Root();
285
- root.resolvePath = (origin, target) => {
327
+ root.resolvePath = (_origin, target) => {
286
328
  for (const dir of this.includeDirs) {
287
329
  const candidate = this.#path.join(dir, target);
288
330
  if (this.#fs.existsSync(candidate)) return candidate;
@@ -291,50 +333,26 @@ export class CodegenBase {
291
333
  };
292
334
  root.loadSync(protoPath, { alternateCommentMode: true });
293
335
  root.resolveAll();
336
+ return root;
337
+ }
294
338
 
295
- const methods = {};
296
- for (const method of parsedMethods) {
297
- const reqTypeName = `${method.requestTypeNamespace}.${method.requestType}`;
298
- let pbjsType = null;
339
+ /**
340
+ * Resolve the request type for a method from the protobufjs Root
341
+ * @param {protobuf.Root} root - Loaded protobufjs root
342
+ * @param {object} method - Parsed method descriptor
343
+ * @returns {protobuf.Type|null}
344
+ */
345
+ #resolveRequestType(root, method) {
346
+ const qualified = `${method.requestTypeNamespace}.${method.requestType}`;
347
+ try {
348
+ return root.lookupType(qualified);
349
+ } catch {
299
350
  try {
300
- pbjsType = root.lookupType(reqTypeName);
351
+ return root.lookupType(method.requestType);
301
352
  } catch {
302
- try {
303
- pbjsType = root.lookupType(method.requestType);
304
- } catch {
305
- // no type found
306
- }
307
- }
308
-
309
- const fields = {};
310
- if (pbjsType) {
311
- for (const [camelName, field] of Object.entries(pbjsType.fields)) {
312
- const name = camelToSnake(camelName);
313
- // Map protobufjs type to simple type name
314
- const typeName = field.resolvedType
315
- ? "message"
316
- : PBJS_TYPE_MAP[field.type] || field.type;
317
-
318
- // proto3 optional uses synthetic oneofs named _fieldname
319
- const hasOptionalKeyword =
320
- pbjsType.oneofs?.[`_${camelName}`] !== undefined;
321
-
322
- fields[name] = {
323
- type: typeName,
324
- optional: hasOptionalKeyword,
325
- repeated: field.repeated || false,
326
- description: field.comment || null,
327
- };
328
- }
353
+ return null;
329
354
  }
330
-
331
- methods[method.name] = {
332
- requestType: `${method.requestTypeNamespace}.${method.requestType}`,
333
- fields,
334
- };
335
355
  }
336
-
337
- return { packageName, serviceName, methods };
338
356
  }
339
357
 
340
358
  /**