{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "protoc-gen-protoform", "title": "Protoform binding generator", "description": "Source-copy Buf plugin that emits typed form bindings and source annotations next to generated Protobuf-ES files.", "dependencies": [ "@bufbuild/protobuf", "@bufbuild/protoplugin" ], "registryDependencies": [ "@protoform/protoform-license" ], "files": [ { "path": "registry/base-nova/protoform/scripts/protoc-gen-protoform/generate-annotations.ts", "content": "import type { DescMessage } from \"@bufbuild/protobuf\";\nimport type { GeneratedFile } from \"@bufbuild/protoplugin\";\nimport {\n getProtoAnnotationEntries,\n type ProtoAnnotationEntry,\n} from \"./source-comments.js\";\n\nfunction printAnnotationMap(\n f: GeneratedFile,\n name: string,\n entries: readonly ProtoAnnotationEntry[]\n): void {\n if (entries.length === 0) {\n return;\n }\n f.print(` ${name}: {`);\n for (const entry of entries) {\n f.print(\" \", f.string(entry.key), \": \", f.string(entry.value), \",\");\n }\n f.print(\" },\");\n}\n\nexport function generateMessageAnnotations(\n f: GeneratedFile,\n message: DescMessage,\n runtimeImport: string\n): string {\n const registerProtoAnnotations = f.import(\n \"registerProtoAnnotations\",\n runtimeImport\n );\n const messageSchema = f.importSchema(message);\n const annotationsName = `${message.name}FormAnnotations`;\n const annotations = getProtoAnnotationEntries(message);\n\n f.print(f.jsDoc(`Source documentation for ${message.typeName}.`));\n f.print(f.export(\"const\", annotationsName), \" = {\");\n printAnnotationMap(f, \"fields\", annotations.fields);\n printAnnotationMap(f, \"messages\", annotations.messages);\n printAnnotationMap(f, \"oneofs\", annotations.oneofs);\n f.print(\"} as const;\");\n f.print(\n registerProtoAnnotations,\n \"(\",\n messageSchema,\n \", \",\n annotationsName,\n \");\"\n );\n f.print();\n\n return annotationsName;\n}\n", "type": "registry:lib", "target": "~/scripts/protoc-gen-protoform/generate-annotations.ts" }, { "path": "registry/base-nova/protoform/scripts/protoc-gen-protoform/index.ts", "content": "export { protocGenProtoform } from \"./plugin.js\";\n", "type": "registry:lib", "target": "~/scripts/protoc-gen-protoform/index.ts" }, { "path": "registry/base-nova/protoform/scripts/protoc-gen-protoform/plugin.ts", "content": "import type { DescMessage } from \"@bufbuild/protobuf\";\nimport {\n createEcmaScriptPlugin,\n type GeneratedFile,\n type Schema,\n} from \"@bufbuild/protoplugin\";\nimport { generateMessageAnnotations } from \"./generate-annotations.js\";\n\nconst DEFAULT_RUNTIME_IMPORT = \"@/lib/protobuf-provider\";\nexport const pluginVersion = \"1.0.0\";\n\ninterface ProtoformPluginOptions {\n runtimeImport: string;\n}\n\nfunction parseOptions(\n rawOptions: { key: string; value: string }[]\n): ProtoformPluginOptions {\n const runtimeImports = rawOptions.filter(\n (option) => option.key === \"runtime_import\"\n );\n if (runtimeImports.length > 1) {\n throw new Error(\"runtime_import can only be specified once\");\n }\n const unknown = rawOptions.find(\n (option) => option.key !== \"runtime_import\"\n );\n if (unknown) {\n throw new Error(`unknown option ${unknown.key}`);\n }\n return {\n runtimeImport: runtimeImports[0]?.value || DEFAULT_RUNTIME_IMPORT,\n };\n}\n\nfunction generateMessageBinding(\n f: GeneratedFile,\n message: DescMessage,\n runtimeImport: string\n): void {\n const createProtoFormSchema = f.import(\n \"createProtoFormSchema\",\n runtimeImport\n );\n const parseProtoSchema = f.import(\"parseProtoSchema\", runtimeImport);\n const protoToFormValues = f.import(\"protoToFormValues\", runtimeImport);\n const messageSchema = f.importSchema(message);\n const annotationsName = generateMessageAnnotations(\n f,\n message,\n runtimeImport\n );\n\n f.print(f.jsDoc(`Form binding for message ${message.typeName}.`));\n f.print(f.export(\"const\", `${message.name}FormBinding`), \" = {\");\n f.print(\" annotations: \", annotationsName, \",\");\n f.print(\n \" createFormSchema: (options?: Parameters[1]) => \",\n createProtoFormSchema,\n \"(\",\n messageSchema,\n \", options),\"\n );\n f.print(\n \" defaultValues: () => \",\n protoToFormValues,\n \"(\",\n messageSchema,\n \"),\"\n );\n f.print(\" descriptor: \", messageSchema, \",\");\n f.print(\" parseSchema: () => \", parseProtoSchema, \"(\", messageSchema, \"),\");\n f.print(\"} as const;\");\n}\n\nfunction generateTs(schema: Schema): void {\n for (const file of schema.files) {\n if (file.messages.length === 0) {\n continue;\n }\n const f = schema.generateFile(`${file.name}_form.ts`);\n f.preamble(file);\n for (const [index, message] of file.messages.entries()) {\n if (index > 0) {\n f.print();\n }\n generateMessageBinding(f, message, schema.options.runtimeImport);\n }\n }\n}\n\nexport const protocGenProtoform = createEcmaScriptPlugin({\n generateTs,\n name: \"protoc-gen-protoform\",\n parseOptions,\n version: `v${pluginVersion}`,\n});\n", "type": "registry:lib", "target": "~/scripts/protoc-gen-protoform/plugin.ts" }, { "path": "registry/base-nova/protoform/scripts/protoc-gen-protoform/run.ts", "content": "// Executable entry point: reads a CodeGeneratorRequest from stdin and\n// writes a CodeGeneratorResponse to stdout. Used by the bin wrapper and\n// by repo-internal buf templates running the plugin from source via bun.\nimport { runNodeJs } from \"@bufbuild/protoplugin\";\nimport { protocGenProtoform } from \"./plugin.js\";\n\nrunNodeJs(protocGenProtoform);\n", "type": "registry:lib", "target": "~/scripts/protoc-gen-protoform/run.ts" }, { "path": "registry/base-nova/protoform/scripts/protoc-gen-protoform/source-comments.ts", "content": "import type { DescMessage } from \"@bufbuild/protobuf\";\nimport { getComments } from \"@bufbuild/protoplugin\";\n\nexport interface ProtoAnnotationEntry {\n key: string;\n value: string;\n}\n\nfunction sourceFileMessages(message: DescMessage): DescMessage[] {\n const messages: DescMessage[] = [];\n const visited = new Set();\n\n function visit(current: DescMessage): void {\n if (current.file !== message.file || visited.has(current.typeName)) {\n return;\n }\n visited.add(current.typeName);\n messages.push(current);\n\n for (const nested of current.nestedMessages) {\n visit(nested);\n }\n for (const field of current.fields) {\n if (field.fieldKind === \"message\") {\n visit(field.message);\n } else if (field.fieldKind === \"list\" && field.listKind === \"message\") {\n visit(field.message);\n } else if (field.fieldKind === \"map\" && field.mapKind === \"message\") {\n visit(field.message);\n }\n }\n }\n\n visit(message);\n return messages;\n}\n\nfunction sourceComment(\n desc: Parameters[0]\n): string | undefined {\n const comment = getComments(desc).leading?.trim();\n return comment || undefined;\n}\n\nexport function getProtoAnnotationEntries(message: DescMessage): {\n fields: ProtoAnnotationEntry[];\n messages: ProtoAnnotationEntry[];\n oneofs: ProtoAnnotationEntry[];\n} {\n const messages = sourceFileMessages(message);\n return {\n fields: messages.flatMap((nested) =>\n nested.fields.flatMap((field) => {\n const comment = sourceComment(field);\n return comment\n ? [\n {\n key: `${field.parent.typeName}.${field.localName}`,\n value: comment,\n },\n ]\n : [];\n })\n ),\n messages: messages.flatMap((nested) => {\n const comment = sourceComment(nested);\n return comment ? [{ key: nested.typeName, value: comment }] : [];\n }),\n oneofs: messages.flatMap((nested) =>\n nested.oneofs.flatMap((oneof) => {\n const comment = sourceComment(oneof);\n return comment\n ? [\n {\n key: `${oneof.parent.typeName}.${oneof.localName}`,\n value: comment,\n },\n ]\n : [];\n })\n ),\n };\n}\n", "type": "registry:lib", "target": "~/scripts/protoc-gen-protoform/source-comments.ts" } ], "type": "registry:lib" }