Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is proto-graphql-js, a Protobuf-First GraphQL Schema generator for JavaScript/TypeScript. It provides protoc plugins that generate GraphQL schema code from Protocol Buffer definitions.

### Supported GraphQL Libraries
- **Pothos**: via `protoc-gen-pothos` package
- **Nexus**: via `protoc-gen-nexus` package

### Supported Protobuf Libraries
- google-protobuf
- protobufjs
- ts-proto
- @bufbuild/protobuf (protobuf-es)

## Essential Commands

```bash
# Install dependencies
pnpm install

# Build all packages
pnpm build

# Run unit tests
pnpm test

# Run E2E tests (generates test APIs first)
pnpm test:e2e

# Lint and format code (auto-fix)
pnpm lint

# Clean build artifacts
pnpm clean

# Run a specific test file
pnpm vitest path/to/test.spec.ts

# Run tests in watch mode
pnpm vitest --watch
```

## Repository Structure

This is a pnpm workspace monorepo with packages organized as:
- `/packages/` - Main packages (protoc-gen-pothos, protoc-gen-nexus, etc.)
- `/devPackages/` - Development utilities and test proto files
- `/e2e/` - End-to-end test suites with generated GraphQL schemas

Build orchestration uses Turbo for caching and parallel execution.

## Code Style and Conventions

- **ESM-first** with CommonJS compatibility (type: "module" in package.json)
- **TypeScript** with strict typing
- **Biome** for linting and formatting
- **No default exports** (enforced by linter)
- **Organized imports** (auto-organized by Biome)
- **2-space indentation**

## Testing Strategy

- Unit tests use Vitest
- E2E tests generate GraphQL schemas from test proto files and verify output
- Test matrix covers combinations of GraphQL libraries × Protobuf implementations
- Snapshot testing for generated schema verification

## Proto Compilation Workflow

The project generates GraphQL schema code from .proto files:

1. Proto files → protoc with custom plugin → GraphQL schema code
2. Plugins read proto descriptors and generate type-safe GraphQL definitions
3. Generated code integrates with Pothos or Nexus GraphQL libraries

Key proto extensions are defined in `/proto-graphql/proto/graphql/schema.proto`.

## Development Tips

- When modifying protoc plugins, rebuild before testing: `pnpm build`
- E2E tests require proto compilation: `pnpm test:e2e:gen` before `pnpm test:e2e`
- Use `pnpm lint` to auto-fix formatting issues
- Generated files are in `__generated__` directories (ignored by linter)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@
}
},
"name": "proto-nexus",
"packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b"
"packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677"
}
95 changes: 84 additions & 11 deletions packages/protoc-gen-nexus/src/dslgen/printers/enumType.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
import type { Registry } from "@bufbuild/protobuf";
import type { GeneratedFile } from "@bufbuild/protoplugin";
import {
type EnumType,
compact,
protobufGraphQLExtensions,
} from "@proto-graphql/codegen-core";
import { type Code, code, literalOf } from "ts-poet";

import { impNexus, nexusTypeDef } from "./util.js";

/**
* Prints enum type definition using protoplugin's GeneratedFile API
*
* @example
* ```ts
* export cosnt Hello = enumType({
* name: "Hello",
* // ...
* })
* export const MyEnum = enumType({
* name: "MyEnum",
* members: [
* { name: "FOO", value: 1 },
* { name: "BAR", value: 2, description: "This is Bar." },
* { name: "BAZ", value: 3 },
* ],
* extensions: {
* protobufEnum: {
* name: "MyEnum",
* fullName: "testapi.enums.MyEnum",
* package: "testapi.enums"
* }
* }
* });
* ```
*/
export function createEnumTypeCode(type: EnumType, registry: Registry): Code {
export function printEnumType(
f: GeneratedFile,
type: EnumType,
registry: Registry,
): void {
// Import enumType from nexus
const enumTypeImport = f.import("enumType", "nexus");

const typeOpts = {
name: type.typeName,
description: type.description,
Expand All @@ -30,7 +48,62 @@ export function createEnumTypeCode(type: EnumType, registry: Registry): Code {
})),
extensions: protobufGraphQLExtensions(type, registry),
};
return code`export const ${nexusTypeDef(type)} = ${impNexus(
"enumType",
)}(${literalOf(compact(typeOpts))});`;

// Clean up the options
const cleanedOpts = compact(typeOpts);

// Format in a single line to match the expected output
const formattedOpts = JSON.stringify(cleanedOpts);

// Add proper formatting with line breaks where needed for readability
const prettyOpts = formattedOpts
.replace(/,\{/g, ', {')
.replace(/\},\{/g, '}, {')
.replace(/\[\{/g, '[{')
.replace(/\}\]/g, '}]');

f.print("export const ", type.typeName, " = ", enumTypeImport, "(", prettyOpts, ");");
}

// Keep the old function for backward compatibility during migration
import { code } from "ts-poet";

export function createEnumTypeCode(type: EnumType, registry: Registry): any {
// Create a mock GeneratedFile to capture the output
const outputs: string[] = [];
const imports = new Map<string, Set<string>>();

const mockFile = {
print(...args: any[]) {
outputs.push(args.map(arg => {
// Handle import symbols
if (typeof arg === 'object' && arg !== null && '_import' in arg) {
return arg._import;
}
return String(arg);
}).join(''));
},
import(name: string, from: string) {
if (!imports.has(from)) {
imports.set(from, new Set());
}
imports.get(from)!.add(name);
return { _import: name };
}
} as any;

// Generate using the new function
printEnumType(mockFile, type, registry);

// Build the final output
const importLines: string[] = [];
for (const [from, names] of imports) {
const nameList = Array.from(names).join(", ");
importLines.push(`import { ${nameList} } from "${from}";`);
}

const fullOutput = [...importLines, "", ...outputs].join("\n");

// Return as ts-poet Code object
return code`${fullOutput}`;
}
Loading
Loading