@forwardimpact/libcodegen 0.1.59 → 0.1.61
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.
- package/bin/fit-codegen.js +36 -16
- package/package.json +1 -1
- package/src/base.js +34 -16
- package/src/long-init.js +17 -0
- package/templates/client.js.mustache +3 -2
package/bin/fit-codegen.js
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import "@forwardimpact/libpreflight/node22";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import
|
|
5
|
+
// Bind protobufjs's `util.Long` before any other import evaluates. The
|
|
6
|
+
// `@grpc/proto-loader` import below pulls in protobufjs's descriptor extension,
|
|
7
|
+
// which calls `Root.fromJSON(...).resolveAll()` at module-evaluation time —
|
|
8
|
+
// resolving a 64-bit field default needs `util.Long`, which `bun --compile`
|
|
9
|
+
// leaves undefined (see long-init.js). This side-effect import must precede the
|
|
10
|
+
// proto-loader import so the binding is in place before that resolveAll runs.
|
|
11
|
+
import "../src/long-init.js";
|
|
12
|
+
|
|
13
|
+
import fs from "node:fs";
|
|
7
14
|
import path from "node:path";
|
|
8
15
|
import { execFileSync } from "node:child_process";
|
|
9
16
|
|
|
@@ -11,7 +18,7 @@ import protoLoader from "@grpc/proto-loader";
|
|
|
11
18
|
import mustache from "mustache";
|
|
12
19
|
|
|
13
20
|
import { createCli, SummaryRenderer } from "@forwardimpact/libcli";
|
|
14
|
-
import {
|
|
21
|
+
import { createDefaultRuntime } from "@forwardimpact/libutil/runtime";
|
|
15
22
|
import { Logger } from "@forwardimpact/libtelemetry";
|
|
16
23
|
import {
|
|
17
24
|
CodegenBase,
|
|
@@ -22,17 +29,8 @@ import {
|
|
|
22
29
|
} from "@forwardimpact/libcodegen";
|
|
23
30
|
import { createStorage } from "@forwardimpact/libstorage";
|
|
24
31
|
|
|
25
|
-
// `bun build --compile` injects FIT_CODEGEN_VERSION via --define, eliminating
|
|
26
|
-
// the readFileSync branch in the compiled binary (which would ENOENT against
|
|
27
|
-
// the bunfs virtual mount). Source execution falls through to package.json.
|
|
28
|
-
const VERSION =
|
|
29
|
-
process.env.FIT_CODEGEN_VERSION ||
|
|
30
|
-
JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"))
|
|
31
|
-
.version;
|
|
32
|
-
|
|
33
32
|
const definition = {
|
|
34
33
|
name: "fit-codegen",
|
|
35
|
-
version: VERSION,
|
|
36
34
|
description:
|
|
37
35
|
"Generate protobuf types, service clients, and definitions from .proto files in installed @forwardimpact/* packages (node_modules/@forwardimpact/*/proto/) and an optional project-local proto/ directory.",
|
|
38
36
|
globalOptions: {
|
|
@@ -62,7 +60,11 @@ const definition = {
|
|
|
62
60
|
],
|
|
63
61
|
};
|
|
64
62
|
|
|
65
|
-
const
|
|
63
|
+
const runtime = createDefaultRuntime();
|
|
64
|
+
const cli = createCli(definition, {
|
|
65
|
+
runtime,
|
|
66
|
+
packageJsonUrl: new URL("../package.json", import.meta.url),
|
|
67
|
+
});
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
70
|
* Create tar.gz bundle of all directories inside sourcePath
|
|
@@ -110,6 +112,7 @@ function parseFlags() {
|
|
|
110
112
|
const { values } = parsed;
|
|
111
113
|
const doAll = values.all;
|
|
112
114
|
return {
|
|
115
|
+
doAll,
|
|
113
116
|
doTypes: doAll || values.type,
|
|
114
117
|
doServices: doAll || values.service,
|
|
115
118
|
doClients: doAll || values.client,
|
|
@@ -178,6 +181,7 @@ function createCodegen(
|
|
|
178
181
|
mustache,
|
|
179
182
|
protoLoader,
|
|
180
183
|
fs,
|
|
184
|
+
runtime,
|
|
181
185
|
) {
|
|
182
186
|
const base = new CodegenBase(
|
|
183
187
|
protoDirs,
|
|
@@ -186,6 +190,7 @@ function createCodegen(
|
|
|
186
190
|
mustache,
|
|
187
191
|
protoLoader,
|
|
188
192
|
fs,
|
|
193
|
+
runtime,
|
|
189
194
|
);
|
|
190
195
|
return {
|
|
191
196
|
types: new CodegenTypes(base),
|
|
@@ -327,6 +332,17 @@ async function runCodegen(protoDirs, projectRoot, finder) {
|
|
|
327
332
|
|
|
328
333
|
await generatedStorage.ensureBucket();
|
|
329
334
|
|
|
335
|
+
// Full regeneration (--all) clears the content directories first so that a
|
|
336
|
+
// renamed or removed proto leaves no orphaned per-proto artifacts. The
|
|
337
|
+
// services exports step scans the services/ directory, so a stale service
|
|
338
|
+
// dir would otherwise be re-exported and import types that no longer exist.
|
|
339
|
+
// Partial flags intentionally preserve sibling artifacts and are not cleaned.
|
|
340
|
+
if (parsedFlags.doAll) {
|
|
341
|
+
for (const dir of ["types", "services", "definitions", "proto"]) {
|
|
342
|
+
fs.rmSync(path.join(sourcePath, dir), { recursive: true, force: true });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
330
346
|
// Write package.json with "type": "module" so Node.js treats generated
|
|
331
347
|
// ES module files correctly and avoids MODULE_TYPELESS_PACKAGE_JSON warnings.
|
|
332
348
|
const generatedPkgPath = path.join(sourcePath, "package.json");
|
|
@@ -344,6 +360,7 @@ async function runCodegen(protoDirs, projectRoot, finder) {
|
|
|
344
360
|
mustache,
|
|
345
361
|
protoLoader,
|
|
346
362
|
fs,
|
|
363
|
+
runtime,
|
|
347
364
|
);
|
|
348
365
|
await executeGeneration(codegens, sourcePath, parsedFlags);
|
|
349
366
|
|
|
@@ -358,8 +375,11 @@ async function runCodegen(protoDirs, projectRoot, finder) {
|
|
|
358
375
|
*/
|
|
359
376
|
async function main() {
|
|
360
377
|
try {
|
|
361
|
-
const logger = new Logger("codegen");
|
|
362
|
-
|
|
378
|
+
const logger = new Logger("codegen", runtime);
|
|
379
|
+
// The shared runtime.finder carries a no-op logger; bind this CLI's logger
|
|
380
|
+
// so createPackageSymlinks (the one logging Finder consumer) keeps emitting
|
|
381
|
+
// symlink debug logs.
|
|
382
|
+
const finder = runtime.finder.withLogger(logger);
|
|
363
383
|
const projectRoot = finder.findProjectRoot(process.cwd());
|
|
364
384
|
|
|
365
385
|
const protoDirs = discoverProtoDirs(projectRoot);
|
|
@@ -373,7 +393,7 @@ async function main() {
|
|
|
373
393
|
|
|
374
394
|
await runCodegen(protoDirs, projectRoot, finder);
|
|
375
395
|
} catch (err) {
|
|
376
|
-
const logger = new Logger("codegen");
|
|
396
|
+
const logger = new Logger("codegen", runtime);
|
|
377
397
|
logger.exception("main", err);
|
|
378
398
|
cli.error(err.message);
|
|
379
399
|
process.exit(1);
|
package/package.json
CHANGED
package/src/base.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { fileURLToPath } from "node:url";
|
|
3
2
|
import protobuf from "protobufjs";
|
|
3
|
+
import "./long-init.js";
|
|
4
4
|
|
|
5
5
|
/** Convert camelCase to snake_case (protobufjs normalizes field names) */
|
|
6
6
|
function camelToSnake(str) {
|
|
@@ -61,6 +61,7 @@ export class CodegenBase {
|
|
|
61
61
|
#mustache;
|
|
62
62
|
#protoLoader;
|
|
63
63
|
#fs;
|
|
64
|
+
#subprocess;
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Creates a new codegen base instance with dependency injection
|
|
@@ -70,8 +71,17 @@ export class CodegenBase {
|
|
|
70
71
|
* @param {object} mustache - Mustache template rendering module
|
|
71
72
|
* @param {object} protoLoader - Protocol buffer loader module
|
|
72
73
|
* @param {object} fs - File system module (sync operations only)
|
|
74
|
+
* @param {import("@forwardimpact/libutil/runtime").Runtime} runtime - Injected runtime bag
|
|
73
75
|
*/
|
|
74
|
-
constructor(
|
|
76
|
+
constructor(
|
|
77
|
+
protoDirs,
|
|
78
|
+
projectRoot,
|
|
79
|
+
path,
|
|
80
|
+
mustache,
|
|
81
|
+
protoLoader,
|
|
82
|
+
fs,
|
|
83
|
+
runtime,
|
|
84
|
+
) {
|
|
75
85
|
if (!protoDirs || !Array.isArray(protoDirs) || protoDirs.length === 0) {
|
|
76
86
|
throw new Error("protoDirs must be a non-empty array");
|
|
77
87
|
}
|
|
@@ -80,6 +90,7 @@ export class CodegenBase {
|
|
|
80
90
|
if (!mustache) throw new Error("mustache module is required");
|
|
81
91
|
if (!protoLoader) throw new Error("protoLoader module is required");
|
|
82
92
|
if (!fs) throw new Error("fs module is required");
|
|
93
|
+
if (!runtime) throw new Error("runtime is required");
|
|
83
94
|
|
|
84
95
|
this.#protoDirs = protoDirs;
|
|
85
96
|
this.#projectRoot = projectRoot;
|
|
@@ -87,6 +98,7 @@ export class CodegenBase {
|
|
|
87
98
|
this.#mustache = mustache;
|
|
88
99
|
this.#protoLoader = protoLoader;
|
|
89
100
|
this.#fs = fs;
|
|
101
|
+
this.#subprocess = runtime.subprocess;
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
/**
|
|
@@ -149,7 +161,7 @@ export class CodegenBase {
|
|
|
149
161
|
|
|
150
162
|
/**
|
|
151
163
|
* Load mustache template for given kind
|
|
152
|
-
* @param {"service"|"client"|"
|
|
164
|
+
* @param {"service"|"client"|"definition"|"definitions-exports"|"services-exports"} kind - Template kind
|
|
153
165
|
* @returns {string} Template content
|
|
154
166
|
*/
|
|
155
167
|
loadTemplate(kind) {
|
|
@@ -182,22 +194,28 @@ export class CodegenBase {
|
|
|
182
194
|
* Run a command with arguments and options
|
|
183
195
|
* @param {string} cmd - Command to execute
|
|
184
196
|
* @param {string[]} args - Command-line arguments
|
|
185
|
-
* @param {object} [opts] -
|
|
197
|
+
* @param {object} [opts] - Subprocess options (e.g. cwd)
|
|
186
198
|
* @returns {Promise<void>} Resolves when the command completes successfully
|
|
187
199
|
*/
|
|
188
|
-
run(cmd, args, opts = {}) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
child.on("error", reject);
|
|
200
|
+
async run(cmd, args, opts = {}) {
|
|
201
|
+
// `stdio: "inherit"` forwards to the underlying execFile/spawn so the
|
|
202
|
+
// child's stdout/stderr go straight to the parent's fds — preserving the
|
|
203
|
+
// exact pre-1370 behavior (origin/main also ran execFile with
|
|
204
|
+
// stdio:"inherit"), so `just codegen` shows protoc/pbjs progress live.
|
|
205
|
+
// With inherited stdio the buffered result is empty, so the error path
|
|
206
|
+
// below falls back to the exit code. Capture-mode callers override via
|
|
207
|
+
// `opts.stdio`.
|
|
208
|
+
const result = await this.#subprocess.run(cmd, args, {
|
|
209
|
+
stdio: "inherit",
|
|
210
|
+
...opts,
|
|
200
211
|
});
|
|
212
|
+
if (result.exitCode !== 0) {
|
|
213
|
+
const msg =
|
|
214
|
+
result.stderr?.trim() ||
|
|
215
|
+
result.stdout?.trim() ||
|
|
216
|
+
`exited with code ${result.exitCode}`;
|
|
217
|
+
throw new Error(`Command failed: ${cmd} ${args.join(" ")}\n${msg}`);
|
|
218
|
+
}
|
|
201
219
|
}
|
|
202
220
|
|
|
203
221
|
/**
|
package/src/long-init.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// protobufjs populates `util.Long` through a dynamic `inquire("long")` that
|
|
2
|
+
// `bun build --compile` cannot resolve, leaving it undefined when a 64-bit
|
|
3
|
+
// field default is computed — crashing the compiled fit-codegen binary at
|
|
4
|
+
// startup (`util.Long.fromNumber is not a function`). Bind it explicitly.
|
|
5
|
+
//
|
|
6
|
+
// Imported for its side effect both by `bin/fit-codegen.js` (ahead of the
|
|
7
|
+
// `@grpc/proto-loader` import, whose descriptor extension runs `resolveAll()`
|
|
8
|
+
// at module-evaluation time) and by base.js (ahead of its own runtime
|
|
9
|
+
// `Root.resolveAll()`). ES module imports evaluate in source order, so an
|
|
10
|
+
// ordered side-effect import binds before the proto-loading code runs —
|
|
11
|
+
// inline binding statements in an entry body would not, since imports hoist
|
|
12
|
+
// above them.
|
|
13
|
+
import protobuf from "protobufjs";
|
|
14
|
+
import Long from "long";
|
|
15
|
+
|
|
16
|
+
protobuf.util.Long = Long;
|
|
17
|
+
protobuf.configure();
|
|
@@ -13,12 +13,13 @@ export class {{className}} extends Client {
|
|
|
13
13
|
/**
|
|
14
14
|
* Creates a new {{serviceName}} client instance
|
|
15
15
|
* @param {object} config - Service configuration
|
|
16
|
+
* @param {import("@forwardimpact/libutil/runtime").Runtime} runtime - Injected runtime bag (required; the default auth factory reads SERVICE_SECRET from it)
|
|
16
17
|
* @param {import("@forwardimpact/libtelemetry").Logger} [logger] - Optional logger instance
|
|
17
18
|
* @param {import("@forwardimpact/libtelemetry").Tracer} [tracer] - Optional tracer for distributed tracing
|
|
18
19
|
* @param {Function} [authFn] - Optional authentication function
|
|
19
20
|
*/
|
|
20
|
-
constructor(config, logger = null, tracer = null, authFn = createAuth) {
|
|
21
|
-
super(config, logger, tracer, createObserver, createGrpc, authFn);
|
|
21
|
+
constructor(config, runtime, logger = null, tracer = null, authFn = createAuth) {
|
|
22
|
+
super(config, runtime, logger, tracer, createObserver, createGrpc, authFn);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
{{#methods}}
|