@forwardimpact/libcodegen 0.1.37 → 0.1.41

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.
Files changed (2) hide show
  1. package/bin/fit-codegen.js +127 -48
  2. package/package.json +3 -2
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from "node:fs";
3
+ import fs, { readFileSync } from "node:fs";
4
4
  import fsAsync from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import { execFileSync } from "node:child_process";
7
- import { parseArgs } from "node:util";
8
7
 
9
8
  import protoLoader from "@grpc/proto-loader";
10
9
  import mustache from "mustache";
11
10
 
11
+ import { createCli, SummaryRenderer } from "@forwardimpact/libcli";
12
12
  import { Finder } from "@forwardimpact/libutil";
13
13
  import { Logger } from "@forwardimpact/libtelemetry";
14
14
  import {
@@ -19,6 +19,39 @@ import {
19
19
  } from "@forwardimpact/libcodegen";
20
20
  import { createStorage } from "@forwardimpact/libstorage";
21
21
 
22
+ const { version: VERSION } = JSON.parse(
23
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
24
+ );
25
+
26
+ const definition = {
27
+ name: "fit-codegen",
28
+ version: VERSION,
29
+ description: "Generate protobuf types, service clients, and definitions",
30
+ options: {
31
+ all: { type: "boolean", description: "Generate all code" },
32
+ type: { type: "boolean", description: "Generate protobuf types only" },
33
+ service: {
34
+ type: "boolean",
35
+ description: "Generate service bases only",
36
+ },
37
+ client: { type: "boolean", description: "Generate clients only" },
38
+ definition: {
39
+ type: "boolean",
40
+ description: "Generate service definitions only",
41
+ },
42
+ help: { type: "boolean", short: "h", description: "Show this help" },
43
+ version: { type: "boolean", description: "Show version" },
44
+ json: { type: "boolean", description: "Output help as JSON" },
45
+ },
46
+ examples: [
47
+ "npx fit-codegen --all",
48
+ "npx fit-codegen --type",
49
+ "npx fit-codegen --service",
50
+ ],
51
+ };
52
+
53
+ const cli = createCli(definition);
54
+
22
55
  /**
23
56
  * Create tar.gz bundle of all directories inside sourcePath
24
57
  * @param {string} sourcePath - Path containing directories to bundle
@@ -53,51 +86,16 @@ async function createBundle(sourcePath) {
53
86
  }
54
87
 
55
88
  /**
56
- * Print CLI usage help
57
- */
58
- function printUsage() {
59
- process.stdout.write(
60
- [
61
- "Usage:",
62
- ` npx fit-codegen --all # Generate all code`,
63
- ` npx fit-codegen --type # Generate protobuf types only`,
64
- ` npx fit-codegen --service # Generate service bases only`,
65
- ` npx fit-codegen --client # Generate clients only`,
66
- ` npx fit-codegen --definition # Generate service definitions only`,
67
- ].join("\n") + "\n",
68
- );
69
- }
70
-
71
- /**
72
- * Parse command line flags
89
+ * Parse command line flags using libcli
73
90
  * @returns {object} Parsed flags with convenience methods
74
91
  */
75
92
  function parseFlags() {
76
- const { values } = parseArgs({
77
- options: {
78
- all: {
79
- type: "boolean",
80
- default: false,
81
- },
82
- type: {
83
- type: "boolean",
84
- default: false,
85
- },
86
- service: {
87
- type: "boolean",
88
- default: false,
89
- },
90
- client: {
91
- type: "boolean",
92
- default: false,
93
- },
94
- definition: {
95
- type: "boolean",
96
- default: false,
97
- },
98
- },
99
- });
93
+ const parsed = cli.parse(process.argv.slice(2));
94
+ if (!parsed) {
95
+ process.exit(0);
96
+ }
100
97
 
98
+ const { values } = parsed;
101
99
  const doAll = values.all;
102
100
  return {
103
101
  doTypes: doAll || values.type,
@@ -179,6 +177,72 @@ function createCodegen(
179
177
  };
180
178
  }
181
179
 
180
+ /**
181
+ * Count files recursively in a directory
182
+ * @param {string} dirPath - Directory to count files in
183
+ * @returns {number} Total file count
184
+ */
185
+ function countFiles(dirPath) {
186
+ let count = 0;
187
+ if (!fs.existsSync(dirPath)) return count;
188
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
189
+ if (entry.isDirectory()) {
190
+ count += countFiles(path.join(dirPath, entry.name));
191
+ } else if (!entry.name.endsWith(".tar.gz")) {
192
+ count++;
193
+ }
194
+ }
195
+ return count;
196
+ }
197
+
198
+ /**
199
+ * Print a summary of generated code
200
+ * @param {string} sourcePath - Path to generated directory
201
+ * @param {object} flags - Parsed generation flags
202
+ */
203
+ function printSummary(sourcePath, flags) {
204
+ const totalFiles = countFiles(sourcePath);
205
+ const relPath = path.relative(process.cwd(), sourcePath);
206
+
207
+ const dirLabels = {
208
+ types: "Protocol Buffer types",
209
+ proto: "Proto source files",
210
+ services: "Service bases and clients",
211
+ definitions: "Service definitions",
212
+ };
213
+
214
+ const items = [];
215
+ if (fs.existsSync(sourcePath)) {
216
+ const dirs = fs
217
+ .readdirSync(sourcePath, { withFileTypes: true })
218
+ .filter((e) => e.isDirectory());
219
+
220
+ for (const dir of dirs) {
221
+ const label = dirLabels[dir.name];
222
+ if (label) items.push({ label: `${dir.name}/`, description: label });
223
+ }
224
+ }
225
+
226
+ const summary = new SummaryRenderer({ process });
227
+ summary.render(
228
+ {
229
+ title: `Generated ${totalFiles} files in ./${relPath}/`,
230
+ items,
231
+ },
232
+ process.stdout,
233
+ );
234
+
235
+ const generated = [
236
+ flags.doTypes && "types",
237
+ flags.doServices && "services",
238
+ flags.doClients && "clients",
239
+ flags.doDefinitions && "definitions",
240
+ ].filter(Boolean);
241
+ process.stdout.write(
242
+ `\nCode generation complete (${generated.join(", ")}).\n`,
243
+ );
244
+ }
245
+
182
246
  /**
183
247
  * Execute code generation tasks
184
248
  * @param {object} codegens - Codegen instances
@@ -229,9 +293,10 @@ async function runCodegen(protoDirs, projectRoot, finder) {
229
293
  const parsedFlags = parseFlags();
230
294
 
231
295
  if (!parsedFlags.hasGenerationFlags()) {
232
- printUsage();
233
- process.exitCode = 1;
234
- return;
296
+ cli.usageError(
297
+ "no generation flags specified (use --all, --type, --service, --client, or --definition)",
298
+ );
299
+ process.exit(2);
235
300
  }
236
301
 
237
302
  const generatedStorage = createStorage("generated", "local");
@@ -239,6 +304,16 @@ async function runCodegen(protoDirs, projectRoot, finder) {
239
304
 
240
305
  await generatedStorage.ensureBucket();
241
306
 
307
+ // Write package.json with "type": "module" so Node.js treats generated
308
+ // ES module files correctly and avoids MODULE_TYPELESS_PACKAGE_JSON warnings.
309
+ const generatedPkgPath = path.join(sourcePath, "package.json");
310
+ if (!fs.existsSync(generatedPkgPath)) {
311
+ fs.writeFileSync(
312
+ generatedPkgPath,
313
+ JSON.stringify({ type: "module" }, null, 2) + "\n",
314
+ );
315
+ }
316
+
242
317
  const codegens = createCodegen(
243
318
  protoDirs,
244
319
  projectRoot,
@@ -251,6 +326,8 @@ async function runCodegen(protoDirs, projectRoot, finder) {
251
326
 
252
327
  await finder.createPackageSymlinks(sourcePath);
253
328
  await createBundle(sourcePath);
329
+
330
+ printSummary(sourcePath, parsedFlags);
254
331
  }
255
332
 
256
333
  /**
@@ -273,12 +350,14 @@ async function main() {
273
350
 
274
351
  await runCodegen(protoDirs, projectRoot, finder);
275
352
  } catch (err) {
276
- process.stderr.write(`Error: ${err.message}\n`);
353
+ const logger = new Logger("codegen");
354
+ logger.exception("main", err);
355
+ cli.error(err.message);
277
356
  process.exit(1);
278
357
  }
279
358
  }
280
359
 
281
360
  main().catch((err) => {
282
- process.stderr.write(`Unexpected error: ${err.message}\n`);
361
+ cli.error(err.message);
283
362
  process.exit(1);
284
363
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/libcodegen",
3
- "version": "0.1.37",
3
+ "version": "0.1.41",
4
4
  "description": "Protocol Buffer code generation utilities for Guide",
5
5
  "license": "Apache-2.0",
6
6
  "author": "D. Olsson <hi@senzilla.io>",
@@ -17,13 +17,14 @@
17
17
  "test": "bun run node --test test/*.test.js"
18
18
  },
19
19
  "dependencies": {
20
+ "@forwardimpact/libcli": "^0.1.0",
20
21
  "@forwardimpact/libstorage": "^0.1.53",
21
22
  "@forwardimpact/libtelemetry": "^0.1.22",
22
23
  "@forwardimpact/libutil": "^0.1.64",
23
24
  "@grpc/proto-loader": "^0.8.0",
24
25
  "mustache": "^4.2.0",
25
26
  "protobufjs": "^7.5.4",
26
- "protobufjs-cli": "^2.0.0"
27
+ "protobufjs-cli": "^2.0.1"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@forwardimpact/libharness": "^0.1.5"