// @ts-check /** * Custom protoc-gen-es sibling plugin. Emits `src/generated/index.ts`, a * single rollup that re-exports every generated `_pb` module and adds * `MessageInitShape` param aliases for every `Command_*` message. * * Wired into `buf.gen.yaml` as a second local plugin. Runs with the same * descriptor set protoc-gen-es consumes, so output always tracks the protos. */ import { createEcmaScriptPlugin, runNodeJs } from '@bufbuild/protoplugin'; const HEADER = [ '// @generated by protoc-gen-data. DO NOT EDIT.', '// Rollup of all proto modules + MessageInitShape param aliases for every Command_*.', '/* eslint-disable */', '', '', ].join('\n'); const inner = createEcmaScriptPlugin({ name: 'protoc-gen-data', version: 'v0.1.0', generateTs(schema) { const f = schema.generateFile('index.ts'); const MessageInitShape = f.import('MessageInitShape', '@bufbuild/protobuf', true); const MessageType = f.import('Message', '@bufbuild/protobuf', true); const GenExtensionType = f.import('GenExtension', '@bufbuild/protobuf/codegenv2', true); const sortedFiles = [...schema.files].sort((a, b) => a.name.localeCompare(b.name)); for (const file of sortedFiles) { f.print('export * from ', f.string(`./proto/${file.name}_pb`), ';'); } f.print(); const commandMessages = []; for (const file of sortedFiles) { for (const msg of file.messages) { if (msg.name.startsWith('Command_')) { commandMessages.push(msg); } } } commandMessages.sort((a, b) => a.name.localeCompare(b.name)); // importSchema() resolves paths relative to this plugin's `out` dir, which // yields `./_pb` — but the _pb files live under ./proto/ (protoc-gen-es's // out). Build the import path explicitly so it points inside the proto subdir. for (const msg of commandMessages) { const alias = msg.name.slice('Command_'.length) + 'Params'; const schemaName = `${msg.name}Schema`; const schemaSym = f.import(schemaName, `./proto/${msg.file.name}_pb`, true); f.print('export type ', alias, ' = ', MessageInitShape, ';'); } f.print(); // Generic extension registry infrastructure. Consolidates the three // near-duplicate registry types and helpers that used to live in // src/websocket/services/protobuf-types.ts into one generic pair. // Specialised aliases (Session/Room/Game) still live in protobuf-types.ts // because GameExtensionRegistry needs GameEventMeta — a hand-written // domain type whose import would create a generated/ ↔ types/ cycle. f.print('export type RegistryEntry = ['); f.print(' ', GenExtensionType, ','); f.print(' (value: V, meta: M) => void,'); f.print('];'); f.print(); // Return type widens V to `unknown` so the heterogeneous entries that // callers build can be stored in a homogeneous `RegistryEntry[]` // array. This is the actual value-add over a bare tuple literal. f.print('export function makeEntry('); f.print(' ext: ', GenExtensionType, ','); f.print(' handler: (value: V, meta: M) => void,'); f.print('): RegistryEntry {'); f.print(' return [ext, handler] as unknown as RegistryEntry;'); f.print('}'); }, }); // Skip f.preamble() above and inject a custom rollup-aware header here instead — // preamble() would write "@generated from file X.proto" which is misleading for // a rollup file built from every input proto. /** @type {import('@bufbuild/protoplugin').Plugin} */ const plugin = { name: inner.name, version: inner.version, run(request) { const response = inner.run(request); for (const file of response.file) { if (file.name === 'index.ts' && typeof file.content === 'string') { file.content = HEADER + file.content; } } return response; }, }; runNodeJs(plugin);