diff --git a/.github/workflows/protographic.yaml b/.github/workflows/protographic.yaml index 6eadfdbc52..3d3ba4362c 100644 --- a/.github/workflows/protographic.yaml +++ b/.github/workflows/protographic.yaml @@ -3,8 +3,8 @@ on: pull_request: paths: - 'pnpm-lock.yaml' - - "protographic/**/*" - - ".github/workflows/protographic.yaml" + - 'protographic/**/*' + - '.github/workflows/protographic.yaml' concurrency: group: ${{github.workflow}}-${{github.head_ref}} @@ -29,6 +29,10 @@ jobs: - name: Generate code run: pnpm generate + # We run linter + formatting and fail CI if uncommitted changes are detected + - name: Lint & format + run: pnpm run --filter ./protographic lint:fix + - uses: ./.github/actions/git-dirty-check with: package-name: protographic @@ -39,9 +43,6 @@ jobs: - name: Test run: pnpm run --filter ./protographic test:coverage - - name: Lint - run: pnpm run --filter ./protographic lint - - name: Upload test results to Codecov uses: ./.github/actions/codecov-upload-pr with: diff --git a/protographic/OPERATIONS_TO_PROTO.md b/protographic/OPERATIONS_TO_PROTO.md index 9951b6f943..55cfb42b43 100644 --- a/protographic/OPERATIONS_TO_PROTO.md +++ b/protographic/OPERATIONS_TO_PROTO.md @@ -42,6 +42,7 @@ The operations-to-proto compiler generates Protocol Buffer service definitions d ### When to Use Operations-Based Generation Use operations-based generation when: + - You want to minimize the proto API surface area - You have a large GraphQL schema but only use a subset of it - You want proto definitions that exactly match your client operations @@ -57,6 +58,7 @@ Use operations-based generation when: All operations must have a name. The operation name becomes the RPC method name in the generated proto. **✅ Correct: Named operation** + ```graphql query GetUser($id: ID!) { user(id: $id) { @@ -66,6 +68,7 @@ query GetUser($id: ID!) { ``` **❌ Incorrect: Anonymous operation** + ```graphql query { user(id: "123") { @@ -88,6 +91,7 @@ wgc grpc-service generate MyService \ ``` **Generates:** + - Proto messages only for fields used in operations - Request/response messages per operation - `service.proto.lock.json` for field number stability @@ -133,36 +137,36 @@ wgc grpc-service generate [name] [options] #### Required Arguments -| Argument | Description | -|----------|-------------| -| `name` | The name of the proto service (e.g., `UserService`) | +| Argument | Description | +| -------- | --------------------------------------------------- | +| `name` | The name of the proto service (e.g., `UserService`) | #### Required Options -| Option | Description | -|--------|-------------| +| Option | Description | +| -------------------- | ------------------------------- | | `-i, --input ` | Path to the GraphQL schema file | #### Output Options -| Option | Default | Description | -|--------|---------|-------------| -| `-o, --output ` | `.` | Output directory for generated files | -| `-p, --package-name ` | `service.v1` | Proto package name | +| Option | Default | Description | +| --------------------------- | ------------ | ------------------------------------ | +| `-o, --output ` | `.` | Output directory for generated files | +| `-p, --package-name ` | `service.v1` | Proto package name | #### Operations Mode Options -| Option | Description | -|--------|-------------| -| `-w, --with-operations ` | Path to directory containing `.graphql` or `.gql` operation files. Subdirectories are traversed recursively. Enables operations-based generation. | -| `--prefix-operation-type` | Prefix RPC method names with operation type (Query/Mutation/Subscription) | -| `--custom-scalar-mapping ` | Custom scalar type mappings as inline JSON string. Example: `'{"DateTime":"google.protobuf.Timestamp","UUID":"string"}'` | -| `--custom-scalar-mapping-file ` | Path to JSON file containing custom scalar type mappings. Example: `./mappings.json` | +| Option | Description | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-w, --with-operations ` | Path to directory containing `.graphql` or `.gql` operation files. Subdirectories are traversed recursively. Enables operations-based generation. | +| `--prefix-operation-type` | Prefix RPC method names with operation type (Query/Mutation/Subscription) | +| `--custom-scalar-mapping ` | Custom scalar type mappings as inline JSON string. Example: `'{"DateTime":"google.protobuf.Timestamp","UUID":"string"}'` | +| `--custom-scalar-mapping-file ` | Path to JSON file containing custom scalar type mappings. Example: `./mappings.json` | #### Language-Specific Options -| Option | Description | -|--------|-------------| +| Option | Description | +| ------------------------- | ------------------------------------------ | | `-g, --go-package ` | Adds `option go_package` to the proto file | ### Examples @@ -216,7 +220,6 @@ wgc grpc-service generate UserService \ --go-package github.com/myorg/myapp/proto/user/v1 ``` - --- ## API Reference @@ -229,25 +232,25 @@ Compiles GraphQL operations to Protocol Buffer definitions. function compileOperationsToProto( operationSource: string | DocumentNode, schemaOrSDL: GraphQLSchema | string, - options?: OperationsToProtoOptions -): CompileOperationsToProtoResult + options?: OperationsToProtoOptions, +): CompileOperationsToProtoResult; ``` #### Parameters -| Parameter | Type | Description | -|-----------|------|-------------| -| `operationSource` | `string \| DocumentNode` | GraphQL operations as a string or parsed DocumentNode | -| `schemaOrSDL` | `GraphQLSchema \| string` | GraphQL schema or SDL string | -| `options` | `OperationsToProtoOptions` | Optional configuration | +| Parameter | Type | Description | +| ----------------- | -------------------------- | ----------------------------------------------------- | +| `operationSource` | `string \| DocumentNode` | GraphQL operations as a string or parsed DocumentNode | +| `schemaOrSDL` | `GraphQLSchema \| string` | GraphQL schema or SDL string | +| `options` | `OperationsToProtoOptions` | Optional configuration | #### Returns ```typescript interface CompileOperationsToProtoResult { - proto: string; // Generated proto text - root: protobuf.Root; // Protobufjs AST root - lockData: ProtoLock; // Lock data for field stability + proto: string; // Generated proto text + root: protobuf.Root; // Protobufjs AST root + lockData: ProtoLock; // Lock data for field stability } ``` @@ -256,9 +259,9 @@ interface CompileOperationsToProtoResult { ```typescript interface OperationsToProtoOptions { // Service Configuration - serviceName?: string; // Default: "DefaultService" - packageName?: string; // Default: "service.v1" - + serviceName?: string; // Default: "DefaultService" + packageName?: string; // Default: "service.v1" + // Language Options goPackage?: string; javaPackage?: string; @@ -270,15 +273,15 @@ interface OperationsToProtoOptions { phpMetadataNamespace?: string; objcClassPrefix?: string; swiftPrefix?: string; - + // Generation Options - includeComments?: boolean; // Default: true - prefixOperationType?: boolean; // Default: false - queryIdempotency?: 'NO_SIDE_EFFECTS' | 'DEFAULT'; // Optional - maxDepth?: number; // Default: 50 - + includeComments?: boolean; // Default: true + prefixOperationType?: boolean; // Default: false + queryIdempotency?: 'NO_SIDE_EFFECTS' | 'DEFAULT'; // Optional + maxDepth?: number; // Default: 50 + // Field Stability - lockData?: ProtoLock; // Previous lock data + lockData?: ProtoLock; // Previous lock data } ``` @@ -296,11 +299,11 @@ const result = compileOperationsToProto(operations, schema, { packageName: 'myorg.user.v1', goPackage: 'github.com/myorg/myapp/proto/user/v1', prefixOperationType: true, - queryIdempotency: 'NO_SIDE_EFFECTS', // All queries are marked as idempotent + queryIdempotency: 'NO_SIDE_EFFECTS', // All queries are marked as idempotent includeComments: true, customScalarMappings: { - 'DateTime': 'google.protobuf.Timestamp', - 'UUID': 'string' + DateTime: 'google.protobuf.Timestamp', + UUID: 'string', }, }); @@ -426,9 +429,9 @@ option go_package = "github.com/myorg/myapp/proto/user/v1"; service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse) {} - + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {} - + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {} } @@ -442,7 +445,7 @@ message GetUserResponse { string name = 2; google.protobuf.StringValue email = 3; } - + User user = 1; } @@ -539,7 +542,7 @@ query GetUser($id: ID!) { id name email - age # New field + age # New field } } ``` @@ -550,20 +553,19 @@ Regenerate - the lock file preserves existing field numbers and assigns the next ## Advanced Topics - ### Custom Scalar Mappings GraphQL custom scalars can be mapped to proto types using either inline JSON or a separate configuration file. #### Common Scalar Mappings -| GraphQL Scalar | Recommended Proto Type | -|----------------|----------------------| -| `DateTime` | `google.protobuf.Timestamp` | -| `Date` | `google.protobuf.Timestamp` | -| `JSON` | `google.protobuf.Struct` | -| `UUID` | `string` | -| `BigInt` | `int64` | +| GraphQL Scalar | Recommended Proto Type | +| -------------- | --------------------------- | +| `DateTime` | `google.protobuf.Timestamp` | +| `Date` | `google.protobuf.Timestamp` | +| `JSON` | `google.protobuf.Struct` | +| `UUID` | `string` | +| `BigInt` | `int64` | #### Using Inline JSON @@ -582,6 +584,7 @@ wgc grpc-service generate UserService \ Create a JSON file with your scalar mappings: **scalar-mappings.json:** + ```json { "DateTime": "google.protobuf.Timestamp", @@ -611,10 +614,10 @@ When using the API directly, pass the mappings as an object: ```typescript const result = compileOperationsToProto(operations, schema, { customScalarMappings: { - 'DateTime': 'google.protobuf.Timestamp', - 'UUID': 'string', - 'JSON': 'google.protobuf.Struct' - } + DateTime: 'google.protobuf.Timestamp', + UUID: 'string', + JSON: 'google.protobuf.Struct', + }, }); ``` @@ -655,11 +658,13 @@ The lock file maintains field number stability across generations. #### No Operation Files Found **Error:** + ```text No GraphQL operation files (.graphql, .gql) found in ./operations ``` **Solution:** + - Ensure your operation files have `.graphql`, `.gql`, `.graphqls`, or `.gqls` extensions - Check the path to your operations directory - Verify files contain valid GraphQL operations @@ -668,6 +673,7 @@ No GraphQL operation files (.graphql, .gql) found in ./operations #### Anonymous Operations Not Supported **Error:** + ```text Operations must be named ``` @@ -694,11 +700,13 @@ query GetUser { #### Field Number Conflicts **Error:** + ```text Field number conflict in message X ``` **Solution:** + - Delete the lock file and regenerate (breaking change) - Or manually resolve conflicts in the lock file (advanced) diff --git a/protographic/README.md b/protographic/README.md index 7754e55c78..0e78a1ad83 100644 --- a/protographic/README.md +++ b/protographic/README.md @@ -54,7 +54,7 @@ const protoOutput = compileGraphQLToProto( packageName: 'user.v1', // Package name goPackage: 'cosmo/pkg/my_package', // Go package name lockFilePath: './proto.lock.json', // Optional: Path to proto.lock.json for deterministic field ordering - } + }, ); ``` @@ -68,21 +68,21 @@ import { compileGraphQLToProto } from '@wundergraph/protographic'; // First generation with a new lock file const result1 = compileGraphQLToProto(initialSchema, { serviceName: 'MyService', - lockFilePath: './proto.lock.json' // Creates lock file if it doesn't exist + lockFilePath: './proto.lock.json', // Creates lock file if it doesn't exist }); // Later generation with schema changes but preserving field order const result2 = compileGraphQLToProto(updatedSchema, { serviceName: 'MyService', - lockFilePath: './proto.lock.json' // Uses existing lock file + lockFilePath: './proto.lock.json', // Uses existing lock file }); ``` When providing a `lockFilePath`, the function returns an object with both the proto definition and the lock data: ```typescript -const { proto, lockData } = compileGraphQLToProto(schema, { - lockFilePath: './proto.lock.json' +const { proto, lockData } = compileGraphQLToProto(schema, { + lockFilePath: './proto.lock.json', }); ``` @@ -93,7 +93,7 @@ import { compileGraphQLToProto, ProtoLock } from '@wundergraph/protographic'; // First generation - creates initial lock data const result1 = compileGraphQLToProto(initialSchema, { - serviceName: 'MyService' + serviceName: 'MyService', }); const proto1 = result1.proto; const lockData = result1.lockData; @@ -104,11 +104,12 @@ const lockData = result1.lockData; // Later generation with the saved lock data const result2 = compileGraphQLToProto(updatedSchema, { serviceName: 'MyService', - lockData: lockData // Use previously generated lock data + lockData: lockData, // Use previously generated lock data }); ``` The lock data records the order of: + - Service methods - Message fields - Enum values @@ -141,7 +142,7 @@ query GetUser($userId: ID!) { const result = compileOperationsToProto(operation, schema, { serviceName: 'UserService', packageName: 'user.v1', - prefixOperationType: true + prefixOperationType: true, }); ``` diff --git a/protographic/SDL_MAPPING_RULES.md b/protographic/SDL_MAPPING_RULES.md index b9b3032aac..1fe1f4c8dc 100644 --- a/protographic/SDL_MAPPING_RULES.md +++ b/protographic/SDL_MAPPING_RULES.md @@ -41,6 +41,7 @@ Query operations (fields on the Query type) are mapped to RPC methods: - Response message names: `Query{CapitalizedFieldName}Response` Example: + ```graphql type Query { user(id: ID!): User @@ -48,6 +49,7 @@ type Query { ``` Maps to an operation with: + - `original: "user"` - `mapped: "QueryUser"` - `request: "QueryUserRequest"` @@ -73,6 +75,7 @@ type Product @key(fields: "id") { ``` Generates entity mappings with: + - `typeName: "Product"` - `kind: "entity"` - `key: "id"` (first key field from directive) @@ -92,6 +95,7 @@ enum Role { ``` Maps to enum mappings with values: + - `original: "ADMIN"` → `mapped: "ROLE_ADMIN"` - `original: "USER"` → `mapped: "ROLE_USER"` @@ -119,6 +123,7 @@ input UserInput { ``` Maps to field mappings with: + - `original: "name"` → `mapped: "name"` - `original: "emailAddress"` → `mapped: "email_address"` @@ -134,4 +139,4 @@ Maps to field mappings with: - Generated RPC service methods follow `{OperationType}{CapitalizedFieldName}` pattern - Request/response message types follow `{Method}Request` and `{Method}Response` pattern -This mapping structure provides a complete representation of the GraphQL schema that can be used to generate valid Protocol Buffer definitions while maintaining semantic equivalence. \ No newline at end of file +This mapping structure provides a complete representation of the GraphQL schema that can be used to generate valid Protocol Buffer definitions while maintaining semantic equivalence. diff --git a/protographic/SDL_PROTO_RULES.md b/protographic/SDL_PROTO_RULES.md index 8c32a1b476..d1e8336d8a 100644 --- a/protographic/SDL_PROTO_RULES.md +++ b/protographic/SDL_PROTO_RULES.md @@ -166,7 +166,7 @@ type Product @key(fields: "id") { Maps to: ```protobuf -rpc RequireProductStockHealthScoreById(RequireProductStockHealthScoreByIdRequest) +rpc RequireProductStockHealthScoreById(RequireProductStockHealthScoreByIdRequest) returns (RequireProductStockHealthScoreByIdResponse) {} message RequireProductStockHealthScoreByIdRequest { @@ -198,6 +198,7 @@ message RequireProductStockHealthScoreByIdFields { **Naming Convention**: `Require{EntityType}{FieldName}By{KeyFields}` For the example above: `RequireProductStockHealthScoreById` + - Entity type: `Product` - Field name: `StockHealthScore` - Key fields: `Id` (from the `@key` directive) @@ -245,7 +246,7 @@ type ActionResult { Maps to: ```protobuf -rpc RequireProductNameById(RequireProductNameByIdRequest) +rpc RequireProductNameById(RequireProductNameByIdRequest) returns (RequireProductNameByIdResponse) {} message RequireProductNameByIdRequest { @@ -271,17 +272,18 @@ message RequireProductNameByIdFields { string status = 1; string message = 2; } - + string description = 1; ActionResult review_summary = 2; } - + string manufacturer_id = 1; ProductDetails details = 2; } ``` **Key Points**: + - The `Fields` message contains only the selected subset from the `@requires` directive, not the full types - Nested types are generated as nested proto messages within the `Fields` message - Only the selected fields from nested types are included (e.g., `description` and `reviewSummary` from `ProductDetails`, not `id` or `title`) @@ -381,20 +383,23 @@ If the `@connect__fieldResolver` directive is **not specified** on a field with type User { id: ID! name: String! - posts(limit: Int!): [Post!]! # No directive: automatically uses "id" as context + posts(limit: Int!): [Post!]! # No directive: automatically uses "id" as context } ``` #### Context Validation Rules When the `@connect__fieldResolver` directive is specified: + - The `context` parameter is **required** - you must explicitly specify which field(s) to use When the directive is NOT specified (automatic inference): + - If no `ID` field exists, an error is raised - If multiple `ID` fields exist, an error is raised (you must use the directive with explicit context) In all cases: + - Context fields are converted from camelCase to snake_case following Protocol Buffer naming conventions ### Field Name Conversion @@ -421,6 +426,7 @@ message ResolveUserPostContext { ``` This conversion applies to: + - Context field names - Argument field names - Result field names @@ -517,8 +523,8 @@ Field resolvers can return both scalar and list types: ```graphql type User { id: ID! - posts(limit: Int!): [Post!]! # Returns list - activePost: Post # Returns single item (nullable) + posts(limit: Int!): [Post!]! # Returns list + activePost: Post # Returns single item (nullable) } ``` @@ -650,11 +656,12 @@ Protographic handles GraphQL list nullability by creating wrapper messages when ### Core Concepts - **Non-nullable single-level lists**: Use the `repeated` keyword directly -- **Nullable lists**: Wrapped in `ListOf{Type}` messages +- **Nullable lists**: Wrapped in `ListOf{Type}` messages - **Nested lists**: Always use wrapper messages with multiple `ListOf` prefixes based on nesting level (e.g., `ListOfListOfString`) - **Nullable list items**: Currently ignored (no wrapper generated for item nullability) ### Non-Nullable Single Lists + Non-nullable lists use `repeated` fields directly: ```graphql @@ -759,9 +766,6 @@ message User { } ``` - - - ## Field Numbering and Stability Protographic ensures stable field numbering across schema changes by using a "proto lock" mechanism: @@ -867,14 +871,14 @@ Protographic preserves documentation from GraphQL schemas and converts it to Pro ### Comment Conversion -| GraphQL Documentation | Protocol Buffer Representation | -| -------------------- | ------------------------------ | -| Single-line descriptions (`"description"`) | Single-line comments (`// comment`) | +| GraphQL Documentation | Protocol Buffer Representation | +| --------------------------------------------- | ------------------------------------- | +| Single-line descriptions (`"description"`) | Single-line comments (`// comment`) | | Multi-line descriptions (`"""description"""`) | Multi-line comments (`/* comment */`) | -| Field descriptions | Field comments | -| Type descriptions | Message/enum comments | -| Enum value descriptions | Enum value comments | -| Operation descriptions | RPC method comments | +| Field descriptions | Field comments | +| Type descriptions | Message/enum comments | +| Enum value descriptions | Enum value comments | +| Operation descriptions | RPC method comments | ### Comment Preservation diff --git a/protographic/package.json b/protographic/package.json index 9cd6538fd6..51a86aaf06 100644 --- a/protographic/package.json +++ b/protographic/package.json @@ -27,7 +27,7 @@ "bench": "vitest bench --run", "lint": "eslint --cache --ext .ts,.mjs,.cjs . && prettier -c src", "lint:fix": "eslint --cache --fix --ext .ts,.mjs,.cjs . && pnpm format", - "format": "prettier -w -c ." + "format": "prettier -w ." }, "devDependencies": { "@types/lodash-es": "4.17.12", diff --git a/protographic/plugin_development.md b/protographic/plugin_development.md index f26c1ad5bd..4781ca5a44 100644 --- a/protographic/plugin_development.md +++ b/protographic/plugin_development.md @@ -38,7 +38,7 @@ project_dir - main.go # Main entry point of the plugin - main_test.go # Test of the plugin - generated - - service.proto # Generated from schema + - service.proto # Generated from schema - mapping.json # Generated from schema - service.proto.lock.json # Generated lock file - service.pb.go # Generated gRPC code @@ -85,7 +85,6 @@ The plugin binary is hosted on the CDN. The URL is constructed as follows: The plugin name, version are embedded in the router execution config. The router will download the plugin binary from the CDN and store it in the plugin directory. The architecture is detected at runtime and the correct binary is downloaded. - ## Example Go Plugin We will provide our own SDK for plugin development. It will be a Go package that will be used to implement the plugin. @@ -129,4 +128,4 @@ func (p *GRPCDataSourcePlugin) GRPCServer(broker *cosmo.GRPCBroker, server *cosm func (p *GRPCDataSourcePlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *cosmo.ClientConn) (interface{}, error) { return nil, nil } -``` \ No newline at end of file +``` diff --git a/protographic/tests/abstract-selection-rewriter/01-basics.test.ts b/protographic/tests/abstract-selection-rewriter/01-basics.test.ts index 5ede9d4de0..4799b1d933 100644 --- a/protographic/tests/abstract-selection-rewriter/01-basics.test.ts +++ b/protographic/tests/abstract-selection-rewriter/01-basics.test.ts @@ -37,7 +37,6 @@ const schema = buildSchema( }, ); - function normalizeFieldSet(fieldSet: string, typeName: string, s: GraphQLSchema = schema): string { const doc = parse(`{ ${fieldSet} }`); const objectType = s.getTypeMap()[typeName] as GraphQLObjectType; @@ -388,7 +387,6 @@ describe('AbstractSelectionRewriter', () => { }); }); - const schemaWithComplexInterfaces = buildSchema( ` interface Iface { @@ -417,11 +415,12 @@ const schemaWithComplexInterfaces = buildSchema( type Query { iface: Iface } - `, { + `, + { assumeValid: true, assumeValidSDL: true, - } -) + }, +); describe('AbstractSelectionRewriter', () => { it('should handle complex interfaces', () => { @@ -483,5 +482,5 @@ describe('AbstractSelectionRewriter', () => { } }" `); - }) -}); \ No newline at end of file + }); +}); diff --git a/protographic/tests/abstract-selection-rewriter/02-advanced.test.ts b/protographic/tests/abstract-selection-rewriter/02-advanced.test.ts index ff4b85abf5..b04b3a4d43 100644 --- a/protographic/tests/abstract-selection-rewriter/02-advanced.test.ts +++ b/protographic/tests/abstract-selection-rewriter/02-advanced.test.ts @@ -207,7 +207,7 @@ describe('Union type', () => { } }" `); - }) + }); }); describe('AbstractSelectionRewriter - Advanced Cases', () => { diff --git a/protographic/tests/operations/proto-text-generator.test.ts b/protographic/tests/operations/proto-text-generator.test.ts index ce29c60700..2f39e53fd0 100644 --- a/protographic/tests/operations/proto-text-generator.test.ts +++ b/protographic/tests/operations/proto-text-generator.test.ts @@ -1,6 +1,12 @@ import { describe, expect, test } from 'vitest'; import * as protobuf from 'protobufjs'; -import { rootToProtoText, serviceToProtoText, messageToProtoText, enumToProtoText, formatField } from '../../src/index.js'; +import { + rootToProtoText, + serviceToProtoText, + messageToProtoText, + enumToProtoText, + formatField, +} from '../../src/index.js'; import { expectValidProto } from '../util.js'; /** diff --git a/protographic/tests/operations/request-builder.test.ts b/protographic/tests/operations/request-builder.test.ts index cbd273c545..5bb748f9bb 100644 --- a/protographic/tests/operations/request-builder.test.ts +++ b/protographic/tests/operations/request-builder.test.ts @@ -1,6 +1,11 @@ import { describe, expect, test } from 'vitest'; import { buildSchema, parse, GraphQLInputObjectType, GraphQLEnumType } from 'graphql'; -import { buildRequestMessage, buildInputObjectMessage, buildEnumType, createFieldNumberManager } from '../../src/index.js'; +import { + buildRequestMessage, + buildInputObjectMessage, + buildEnumType, + createFieldNumberManager, +} from '../../src/index.js'; describe('Request Builder', () => { describe('buildRequestMessage', () => { diff --git a/protographic/vite.config.ts b/protographic/vite.config.ts index b2c35efdc2..a562146505 100644 --- a/protographic/vite.config.ts +++ b/protographic/vite.config.ts @@ -5,4 +5,4 @@ export default defineConfig({ // Ensure always the CJS version is used otherwise we might conflict with multiple versions of graphql alias: [{ find: /^graphql$/, replacement: 'graphql/index.js' }], }, -}); \ No newline at end of file +});