From aeb7adc463c0fb030c4601c3dec245b23f77ceb3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:19:37 +0000 Subject: [PATCH 01/12] test: add gRPC fixture with comments service - Add grpc-comments fixture with proto configuration - Create comments.proto with CreateComment RPC using google.api.http annotations - Add test case to verify gRPC proto processing pipeline - Generate FDR snapshots for regression testing Co-Authored-By: kenny@buildwithfern.com --- .../__snapshots__/grpc-comments-fdr.snap | 421 ++++++ .../__snapshots__/grpc-comments-ir.snap | 1318 +++++++++++++++++ .../fixtures/grpc-comments/generators.yml | 16 + .../grpc-comments/proto/comments.proto | 37 + .../__test__/openapi-from-flag.test.ts | 61 + 5 files changed, 1853 insertions(+) create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/generators.yml create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/comments.proto diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap new file mode 100644 index 000000000000..66df29fe522e --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap @@ -0,0 +1,421 @@ +{ + "auth": { + "description": undefined, + "tokenName": "token", + "type": "bearerAuth", + }, + "authSchemes": { + "Bearer": { + "description": undefined, + "tokenName": "token", + "type": "bearerAuth", + }, + }, + "globalHeaders": [], + "navigation": undefined, + "rootPackage": { + "endpoints": [], + "pointsTo": undefined, + "subpackages": [ + "subpackage_commentsService", + ], + "types": [ + "Comment", + "CreateCommentRequest", + "CreateCommentResponse", + ], + "webhooks": [], + "websockets": [], + }, + "snippetsConfiguration": { + "csharpSdk": undefined, + "goSdk": undefined, + "javaSdk": undefined, + "phpSdk": undefined, + "pythonSdk": undefined, + "rubySdk": undefined, + "rustSdk": undefined, + "swiftSdk": undefined, + "typescriptSdk": undefined, + }, + "subpackages": { + "subpackage_commentsService": { + "description": undefined, + "displayName": undefined, + "endpoints": [ + { + "auth": false, + "authV2": undefined, + "availability": undefined, + "defaultEnvironment": "Production", + "description": "buf:lint:ignore RPC_REQUEST_STANDARD_NAME + Create a comment on a resource", + "environments": [ + { + "baseUrl": "https://test.com/api", + "id": "Production", + }, + ], + "errors": undefined, + "errorsV2": [], + "examples": [ + { + "codeSamples": undefined, + "description": "", + "headers": {}, + "name": undefined, + "path": "/comments/v1/comments", + "pathParameters": {}, + "queryParameters": {}, + "requestBody": {}, + "requestBodyV3": { + "type": "json", + "value": {}, + }, + "responseBody": { + "comment": { + "author": "string", + "created_at": 1, + "id": "string", + "resource_id": "string", + "text": "string", + }, + }, + "responseBodyV3": { + "type": "json", + "value": { + "comment": { + "author": "string", + "created_at": 1, + "id": "string", + "resource_id": "string", + "text": "string", + }, + }, + }, + "responseStatusCode": 200, + }, + ], + "headers": [], + "id": "createcomment", + "includeInApiExplorer": undefined, + "method": "POST", + "multiAuth": undefined, + "name": "Createcomment", + "originalEndpointId": "endpoint_commentsService.createcomment", + "path": { + "parts": [ + { + "type": "literal", + "value": "", + }, + { + "type": "literal", + "value": "/comments/v1/comments", + }, + ], + "pathParameters": [], + }, + "protocol": { + "type": "rest", + }, + "queryParameters": [], + "request": { + "description": undefined, + "type": { + "contentType": "application/json", + "description": undefined, + "shape": { + "type": "reference", + "value": { + "default": undefined, + "type": "id", + "value": "CreateCommentRequest", + }, + }, + "type": "json", + }, + }, + "requestsV2": { + "requests": [ + { + "description": undefined, + "type": { + "contentType": "application/json", + "description": undefined, + "shape": { + "type": "reference", + "value": { + "default": undefined, + "type": "id", + "value": "CreateCommentRequest", + }, + }, + "type": "json", + }, + }, + ], + }, + "response": { + "description": "OK", + "statusCode": 200, + "type": { + "type": "reference", + "value": { + "default": undefined, + "type": "id", + "value": "CreateCommentResponse", + }, + }, + }, + "responsesV2": { + "responses": [ + { + "description": "OK", + "statusCode": 200, + "type": { + "type": "reference", + "value": { + "default": undefined, + "type": "id", + "value": "CreateCommentResponse", + }, + }, + }, + ], + }, + "slug": undefined, + }, + ], + "name": "commentsService", + "pointsTo": undefined, + "subpackageId": "subpackage_commentsService", + "subpackages": [], + "types": [], + "webhooks": [], + "websockets": [], + }, + }, + "types": { + "Comment": { + "availability": undefined, + "description": undefined, + "displayName": undefined, + "name": "Comment", + "shape": { + "extends": [], + "extraProperties": undefined, + "properties": [ + { + "availability": undefined, + "description": undefined, + "key": "id", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "type": "primitive", + "value": { + "default": undefined, + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "regex": undefined, + "type": "string", + }, + }, + "type": "optional", + }, + }, + { + "availability": undefined, + "description": undefined, + "key": "resource_id", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "type": "primitive", + "value": { + "default": undefined, + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "regex": undefined, + "type": "string", + }, + }, + "type": "optional", + }, + }, + { + "availability": undefined, + "description": undefined, + "key": "text", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "type": "primitive", + "value": { + "default": undefined, + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "regex": undefined, + "type": "string", + }, + }, + "type": "optional", + }, + }, + { + "availability": undefined, + "description": undefined, + "key": "author", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "type": "primitive", + "value": { + "default": undefined, + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "regex": undefined, + "type": "string", + }, + }, + "type": "optional", + }, + }, + { + "availability": undefined, + "description": undefined, + "key": "created_at", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "type": "primitive", + "value": { + "default": undefined, + "maximum": undefined, + "minimum": undefined, + "type": "long", + }, + }, + "type": "optional", + }, + }, + ], + "type": "object", + }, + }, + "CreateCommentRequest": { + "availability": undefined, + "description": undefined, + "displayName": undefined, + "name": "CreateCommentRequest", + "shape": { + "extends": [], + "extraProperties": undefined, + "properties": [ + { + "availability": undefined, + "description": undefined, + "key": "resource_id", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "type": "primitive", + "value": { + "default": undefined, + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "regex": undefined, + "type": "string", + }, + }, + "type": "optional", + }, + }, + { + "availability": undefined, + "description": undefined, + "key": "text", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "type": "primitive", + "value": { + "default": undefined, + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "regex": undefined, + "type": "string", + }, + }, + "type": "optional", + }, + }, + { + "availability": undefined, + "description": undefined, + "key": "author", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "type": "primitive", + "value": { + "default": undefined, + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "regex": undefined, + "type": "string", + }, + }, + "type": "optional", + }, + }, + ], + "type": "object", + }, + }, + "CreateCommentResponse": { + "availability": undefined, + "description": undefined, + "displayName": undefined, + "name": "CreateCommentResponse", + "shape": { + "extends": [], + "extraProperties": undefined, + "properties": [ + { + "availability": undefined, + "description": undefined, + "key": "comment", + "propertyAccess": undefined, + "valueType": { + "defaultValue": undefined, + "itemType": { + "default": undefined, + "type": "id", + "value": "Comment", + }, + "type": "optional", + }, + }, + ], + "type": "object", + }, + }, + }, +} \ No newline at end of file diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap new file mode 100644 index 000000000000..67138f80ef88 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap @@ -0,0 +1,1318 @@ +{ + "apiDisplayName": undefined, + "apiDocs": undefined, + "apiName": { + "camelCase": { + "safeName": "", + "unsafeName": "", + }, + "originalName": "", + "pascalCase": { + "safeName": "", + "unsafeName": "", + }, + "screamingSnakeCase": { + "safeName": "", + "unsafeName": "", + }, + "snakeCase": { + "safeName": "", + "unsafeName": "", + }, + }, + "apiPlayground": true, + "apiVersion": undefined, + "audiences": undefined, + "auth": { + "docs": undefined, + "requirement": "ALL", + "schemes": [ + { + "_visit": [Function], + "docs": undefined, + "key": "Bearer", + "token": { + "camelCase": { + "safeName": "token", + "unsafeName": "token", + }, + "originalName": "token", + "pascalCase": { + "safeName": "Token", + "unsafeName": "Token", + }, + "screamingSnakeCase": { + "safeName": "TOKEN", + "unsafeName": "TOKEN", + }, + "snakeCase": { + "safeName": "token", + "unsafeName": "token", + }, + }, + "tokenEnvVar": undefined, + "type": "bearer", + }, + ], + }, + "basePath": undefined, + "constants": { + "errorInstanceIdKey": { + "name": { + "camelCase": { + "safeName": "errorInstanceId", + "unsafeName": "errorInstanceId", + }, + "originalName": "errorInstanceId", + "pascalCase": { + "safeName": "ErrorInstanceId", + "unsafeName": "ErrorInstanceId", + }, + "screamingSnakeCase": { + "safeName": "ERROR_INSTANCE_ID", + "unsafeName": "ERROR_INSTANCE_ID", + }, + "snakeCase": { + "safeName": "error_instance_id", + "unsafeName": "error_instance_id", + }, + }, + "wireValue": "errorInstanceId", + }, + }, + "dynamic": undefined, + "environments": { + "defaultEnvironment": "Production", + "environments": { + "_visit": [Function], + "environments": [ + { + "docs": undefined, + "id": "Production", + "name": { + "camelCase": { + "safeName": "production", + "unsafeName": "production", + }, + "originalName": "Production", + "pascalCase": { + "safeName": "Production", + "unsafeName": "Production", + }, + "screamingSnakeCase": { + "safeName": "PRODUCTION", + "unsafeName": "PRODUCTION", + }, + "snakeCase": { + "safeName": "production", + "unsafeName": "production", + }, + }, + "url": "https://test.com/api", + }, + ], + "type": "singleBaseUrl", + }, + }, + "errorDiscriminationStrategy": { + "_visit": [Function], + "type": "statusCode", + }, + "errors": {}, + "fdrApiDefinitionId": undefined, + "generationMetadata": undefined, + "headers": [], + "idempotencyHeaders": [], + "pathParameters": [], + "publishConfig": undefined, + "readmeConfig": undefined, + "rootPackage": { + "docs": undefined, + "errors": [], + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "hasEndpointsInTree": false, + "navigationConfig": undefined, + "service": undefined, + "subpackages": [ + "subpackage_commentsService", + ], + "types": [ + "Comment", + "CreateCommentRequest", + "CreateCommentResponse", + ], + "webhooks": undefined, + "websocket": undefined, + }, + "sdkConfig": { + "hasFileDownloadEndpoints": false, + "hasPaginatedEndpoints": false, + "hasStreamingEndpoints": false, + "isAuthMandatory": true, + "platformHeaders": { + "language": "", + "sdkName": "", + "sdkVersion": "", + "userAgent": undefined, + }, + }, + "selfHosted": false, + "serviceTypeReferenceInfo": { + "sharedTypes": [], + "typesReferencedOnlyByService": {}, + }, + "services": { + "service_commentsService": { + "audiences": undefined, + "availability": undefined, + "basePath": { + "head": "", + "parts": [], + }, + "displayName": undefined, + "encoding": undefined, + "endpoints": [ + { + "allPathParameters": [], + "apiPlayground": undefined, + "audiences": [], + "auth": false, + "autogeneratedExamples": [], + "availability": undefined, + "basePath": undefined, + "baseUrl": undefined, + "displayName": undefined, + "docs": "buf:lint:ignore RPC_REQUEST_STANDARD_NAME + Create a comment on a resource", + "errors": [], + "fullPath": { + "head": "/comments/v1/comments", + "parts": [], + }, + "headers": [], + "id": "endpoint_commentsService.createcomment", + "idempotent": false, + "method": "POST", + "name": { + "camelCase": { + "safeName": "createcomment", + "unsafeName": "createcomment", + }, + "originalName": "createcomment", + "pascalCase": { + "safeName": "Createcomment", + "unsafeName": "Createcomment", + }, + "screamingSnakeCase": { + "safeName": "CREATECOMMENT", + "unsafeName": "CREATECOMMENT", + }, + "snakeCase": { + "safeName": "createcomment", + "unsafeName": "createcomment", + }, + }, + "pagination": undefined, + "path": { + "head": "/comments/v1/comments", + "parts": [], + }, + "pathParameters": [], + "queryParameters": [], + "requestBody": { + "_visit": [Function], + "contentType": "application/json", + "docs": undefined, + "requestBodyType": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "createCommentRequest", + "unsafeName": "createCommentRequest", + }, + "originalName": "CreateCommentRequest", + "pascalCase": { + "safeName": "CreateCommentRequest", + "unsafeName": "CreateCommentRequest", + }, + "screamingSnakeCase": { + "safeName": "CREATE_COMMENT_REQUEST", + "unsafeName": "CREATE_COMMENT_REQUEST", + }, + "snakeCase": { + "safeName": "create_comment_request", + "unsafeName": "create_comment_request", + }, + }, + "type": "named", + "typeId": "CreateCommentRequest", + }, + "type": "reference", + "v2Examples": { + "autogeneratedExamples": { + "commentsServiceCreatecommentExample": {}, + }, + "userSpecifiedExamples": {}, + }, + }, + "response": { + "body": { + "_visit": [Function], + "type": "json", + "value": { + "_visit": [Function], + "docs": "OK", + "responseBodyType": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "createCommentResponse", + "unsafeName": "createCommentResponse", + }, + "originalName": "CreateCommentResponse", + "pascalCase": { + "safeName": "CreateCommentResponse", + "unsafeName": "CreateCommentResponse", + }, + "screamingSnakeCase": { + "safeName": "CREATE_COMMENT_RESPONSE", + "unsafeName": "CREATE_COMMENT_RESPONSE", + }, + "snakeCase": { + "safeName": "create_comment_response", + "unsafeName": "create_comment_response", + }, + }, + "type": "named", + "typeId": "CreateCommentResponse", + }, + "type": "response", + "v2Examples": { + "autogeneratedExamples": { + "commentsServiceCreatecommentExample": { + "comment": { + "author": "string", + "created_at": 1, + "id": "string", + "resource_id": "string", + "text": "string", + }, + }, + }, + "userSpecifiedExamples": {}, + }, + }, + }, + "statusCode": 200, + }, + "retries": undefined, + "sdkRequest": undefined, + "security": undefined, + "source": { + "_visit": [Function], + "type": "openapi", + }, + "transport": undefined, + "userSpecifiedExamples": [], + "v2BaseUrls": undefined, + "v2Examples": { + "autogeneratedExamples": { + "commentsServiceCreatecommentExample_200": { + "codeSamples": undefined, + "displayName": "commentsServiceCreatecommentExample", + "request": { + "auth": undefined, + "baseUrl": undefined, + "docs": undefined, + "endpoint": { + "method": "POST", + "path": "/comments/v1/comments", + }, + "environment": undefined, + "headers": {}, + "pathParameters": {}, + "queryParameters": {}, + "requestBody": {}, + }, + "response": { + "body": { + "_visit": [Function], + "type": "json", + "value": { + "comment": { + "author": "string", + "created_at": 1, + "id": "string", + "resource_id": "string", + "text": "string", + }, + }, + }, + "docs": undefined, + "statusCode": 200, + }, + }, + }, + "userSpecifiedExamples": {}, + }, + "v2RequestBodies": { + "requestBodies": [ + { + "_visit": [Function], + "contentType": "application/json", + "docs": undefined, + "requestBodyType": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "createCommentRequest", + "unsafeName": "createCommentRequest", + }, + "originalName": "CreateCommentRequest", + "pascalCase": { + "safeName": "CreateCommentRequest", + "unsafeName": "CreateCommentRequest", + }, + "screamingSnakeCase": { + "safeName": "CREATE_COMMENT_REQUEST", + "unsafeName": "CREATE_COMMENT_REQUEST", + }, + "snakeCase": { + "safeName": "create_comment_request", + "unsafeName": "create_comment_request", + }, + }, + "type": "named", + "typeId": "CreateCommentRequest", + }, + "type": "reference", + "v2Examples": { + "autogeneratedExamples": { + "commentsServiceCreatecommentExample": {}, + }, + "userSpecifiedExamples": {}, + }, + }, + ], + }, + "v2Responses": { + "responses": [ + { + "body": { + "_visit": [Function], + "type": "json", + "value": { + "_visit": [Function], + "docs": "OK", + "responseBodyType": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "createCommentResponse", + "unsafeName": "createCommentResponse", + }, + "originalName": "CreateCommentResponse", + "pascalCase": { + "safeName": "CreateCommentResponse", + "unsafeName": "CreateCommentResponse", + }, + "screamingSnakeCase": { + "safeName": "CREATE_COMMENT_RESPONSE", + "unsafeName": "CREATE_COMMENT_RESPONSE", + }, + "snakeCase": { + "safeName": "create_comment_response", + "unsafeName": "create_comment_response", + }, + }, + "type": "named", + "typeId": "CreateCommentResponse", + }, + "type": "response", + "v2Examples": { + "autogeneratedExamples": { + "commentsServiceCreatecommentExample": { + "comment": { + "author": "string", + "created_at": 1, + "id": "string", + "resource_id": "string", + "text": "string", + }, + }, + }, + "userSpecifiedExamples": {}, + }, + }, + }, + "statusCode": 200, + }, + ], + }, + }, + ], + "headers": [], + "name": { + "fernFilepath": { + "allParts": [ + { + "camelCase": { + "safeName": "commentsService", + "unsafeName": "commentsService", + }, + "originalName": "CommentsService", + "pascalCase": { + "safeName": "CommentsService", + "unsafeName": "CommentsService", + }, + "screamingSnakeCase": { + "safeName": "COMMENTS_SERVICE", + "unsafeName": "COMMENTS_SERVICE", + }, + "snakeCase": { + "safeName": "comments_service", + "unsafeName": "comments_service", + }, + }, + ], + "file": { + "camelCase": { + "safeName": "commentsService", + "unsafeName": "commentsService", + }, + "originalName": "CommentsService", + "pascalCase": { + "safeName": "CommentsService", + "unsafeName": "CommentsService", + }, + "screamingSnakeCase": { + "safeName": "COMMENTS_SERVICE", + "unsafeName": "COMMENTS_SERVICE", + }, + "snakeCase": { + "safeName": "comments_service", + "unsafeName": "comments_service", + }, + }, + "packagePath": [], + }, + }, + "pathParameters": [], + "transport": undefined, + }, + }, + "sourceConfig": undefined, + "subpackages": { + "subpackage_commentsService": { + "displayName": undefined, + "docs": undefined, + "errors": [], + "fernFilepath": { + "allParts": [ + { + "camelCase": { + "safeName": "commentsService", + "unsafeName": "commentsService", + }, + "originalName": "commentsService", + "pascalCase": { + "safeName": "CommentsService", + "unsafeName": "CommentsService", + }, + "screamingSnakeCase": { + "safeName": "COMMENTS_SERVICE", + "unsafeName": "COMMENTS_SERVICE", + }, + "snakeCase": { + "safeName": "comments_service", + "unsafeName": "comments_service", + }, + }, + ], + "file": { + "camelCase": { + "safeName": "commentsService", + "unsafeName": "commentsService", + }, + "originalName": "commentsService", + "pascalCase": { + "safeName": "CommentsService", + "unsafeName": "CommentsService", + }, + "screamingSnakeCase": { + "safeName": "COMMENTS_SERVICE", + "unsafeName": "COMMENTS_SERVICE", + }, + "snakeCase": { + "safeName": "comments_service", + "unsafeName": "comments_service", + }, + }, + "packagePath": [], + }, + "hasEndpointsInTree": false, + "name": { + "camelCase": { + "safeName": "commentsService", + "unsafeName": "commentsService", + }, + "originalName": "commentsService", + "pascalCase": { + "safeName": "CommentsService", + "unsafeName": "CommentsService", + }, + "screamingSnakeCase": { + "safeName": "COMMENTS_SERVICE", + "unsafeName": "COMMENTS_SERVICE", + }, + "snakeCase": { + "safeName": "comments_service", + "unsafeName": "comments_service", + }, + }, + "navigationConfig": undefined, + "service": "service_commentsService", + "subpackages": [], + "types": [], + "webhooks": undefined, + "websocket": undefined, + }, + }, + "types": { + "Comment": { + "autogeneratedExamples": [], + "availability": undefined, + "docs": undefined, + "encoding": undefined, + "inline": false, + "name": { + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "name": { + "camelCase": { + "safeName": "comment", + "unsafeName": "comment", + }, + "originalName": "Comment", + "pascalCase": { + "safeName": "Comment", + "unsafeName": "Comment", + }, + "screamingSnakeCase": { + "safeName": "COMMENT", + "unsafeName": "COMMENT", + }, + "snakeCase": { + "safeName": "comment", + "unsafeName": "comment", + }, + }, + "typeId": "Comment", + }, + "referencedTypes": Set {}, + "shape": { + "_visit": [Function], + "extendedProperties": [], + "extends": [], + "extraProperties": false, + "properties": [ + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "id", + "unsafeName": "id", + }, + "originalName": "id", + "pascalCase": { + "safeName": "Id", + "unsafeName": "Id", + }, + "screamingSnakeCase": { + "safeName": "ID", + "unsafeName": "ID", + }, + "snakeCase": { + "safeName": "id", + "unsafeName": "id", + }, + }, + "wireValue": "id", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "CommentId_example_autogenerated": "string", + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "primitive": { + "v1": "STRING", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "string", + "validation": { + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "pattern": undefined, + }, + }, + }, + "type": "primitive", + }, + "type": "optional", + }, + "type": "container", + }, + }, + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "resourceId", + "unsafeName": "resourceId", + }, + "originalName": "resource_id", + "pascalCase": { + "safeName": "ResourceId", + "unsafeName": "ResourceId", + }, + "screamingSnakeCase": { + "safeName": "RESOURCE_ID", + "unsafeName": "RESOURCE_ID", + }, + "snakeCase": { + "safeName": "resource_id", + "unsafeName": "resource_id", + }, + }, + "wireValue": "resource_id", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "CommentResourceId_example_autogenerated": "string", + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "primitive": { + "v1": "STRING", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "string", + "validation": { + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "pattern": undefined, + }, + }, + }, + "type": "primitive", + }, + "type": "optional", + }, + "type": "container", + }, + }, + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "text", + "unsafeName": "text", + }, + "originalName": "text", + "pascalCase": { + "safeName": "Text", + "unsafeName": "Text", + }, + "screamingSnakeCase": { + "safeName": "TEXT", + "unsafeName": "TEXT", + }, + "snakeCase": { + "safeName": "text", + "unsafeName": "text", + }, + }, + "wireValue": "text", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "CommentText_example_autogenerated": "string", + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "primitive": { + "v1": "STRING", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "string", + "validation": { + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "pattern": undefined, + }, + }, + }, + "type": "primitive", + }, + "type": "optional", + }, + "type": "container", + }, + }, + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "author", + "unsafeName": "author", + }, + "originalName": "author", + "pascalCase": { + "safeName": "Author", + "unsafeName": "Author", + }, + "screamingSnakeCase": { + "safeName": "AUTHOR", + "unsafeName": "AUTHOR", + }, + "snakeCase": { + "safeName": "author", + "unsafeName": "author", + }, + }, + "wireValue": "author", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "CommentAuthor_example_autogenerated": "string", + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "primitive": { + "v1": "STRING", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "string", + "validation": { + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "pattern": undefined, + }, + }, + }, + "type": "primitive", + }, + "type": "optional", + }, + "type": "container", + }, + }, + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "createdAt", + "unsafeName": "createdAt", + }, + "originalName": "created_at", + "pascalCase": { + "safeName": "CreatedAt", + "unsafeName": "CreatedAt", + }, + "screamingSnakeCase": { + "safeName": "CREATED_AT", + "unsafeName": "CREATED_AT", + }, + "snakeCase": { + "safeName": "created_at", + "unsafeName": "created_at", + }, + }, + "wireValue": "created_at", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "CommentCreatedAt_example_autogenerated": 1, + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "primitive": { + "v1": "LONG", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "long", + }, + }, + "type": "primitive", + }, + "type": "optional", + }, + "type": "container", + }, + }, + ], + "type": "object", + }, + "source": undefined, + "userProvidedExamples": [], + "v2Examples": { + "autogeneratedExamples": { + "Comment_example_autogenerated": {}, + }, + "userSpecifiedExamples": {}, + }, + }, + "CreateCommentRequest": { + "autogeneratedExamples": [], + "availability": undefined, + "docs": undefined, + "encoding": undefined, + "inline": false, + "name": { + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "name": { + "camelCase": { + "safeName": "createCommentRequest", + "unsafeName": "createCommentRequest", + }, + "originalName": "CreateCommentRequest", + "pascalCase": { + "safeName": "CreateCommentRequest", + "unsafeName": "CreateCommentRequest", + }, + "screamingSnakeCase": { + "safeName": "CREATE_COMMENT_REQUEST", + "unsafeName": "CREATE_COMMENT_REQUEST", + }, + "snakeCase": { + "safeName": "create_comment_request", + "unsafeName": "create_comment_request", + }, + }, + "typeId": "CreateCommentRequest", + }, + "referencedTypes": Set {}, + "shape": { + "_visit": [Function], + "extendedProperties": [], + "extends": [], + "extraProperties": false, + "properties": [ + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "resourceId", + "unsafeName": "resourceId", + }, + "originalName": "resource_id", + "pascalCase": { + "safeName": "ResourceId", + "unsafeName": "ResourceId", + }, + "screamingSnakeCase": { + "safeName": "RESOURCE_ID", + "unsafeName": "RESOURCE_ID", + }, + "snakeCase": { + "safeName": "resource_id", + "unsafeName": "resource_id", + }, + }, + "wireValue": "resource_id", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "CreateCommentRequestResourceId_example_autogenerated": "string", + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "primitive": { + "v1": "STRING", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "string", + "validation": { + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "pattern": undefined, + }, + }, + }, + "type": "primitive", + }, + "type": "optional", + }, + "type": "container", + }, + }, + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "text", + "unsafeName": "text", + }, + "originalName": "text", + "pascalCase": { + "safeName": "Text", + "unsafeName": "Text", + }, + "screamingSnakeCase": { + "safeName": "TEXT", + "unsafeName": "TEXT", + }, + "snakeCase": { + "safeName": "text", + "unsafeName": "text", + }, + }, + "wireValue": "text", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "CreateCommentRequestText_example_autogenerated": "string", + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "primitive": { + "v1": "STRING", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "string", + "validation": { + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "pattern": undefined, + }, + }, + }, + "type": "primitive", + }, + "type": "optional", + }, + "type": "container", + }, + }, + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "author", + "unsafeName": "author", + }, + "originalName": "author", + "pascalCase": { + "safeName": "Author", + "unsafeName": "Author", + }, + "screamingSnakeCase": { + "safeName": "AUTHOR", + "unsafeName": "AUTHOR", + }, + "snakeCase": { + "safeName": "author", + "unsafeName": "author", + }, + }, + "wireValue": "author", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "CreateCommentRequestAuthor_example_autogenerated": "string", + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "primitive": { + "v1": "STRING", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "string", + "validation": { + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "pattern": undefined, + }, + }, + }, + "type": "primitive", + }, + "type": "optional", + }, + "type": "container", + }, + }, + ], + "type": "object", + }, + "source": undefined, + "userProvidedExamples": [], + "v2Examples": { + "autogeneratedExamples": { + "CreateCommentRequest_example_autogenerated": {}, + }, + "userSpecifiedExamples": {}, + }, + }, + "CreateCommentResponse": { + "autogeneratedExamples": [], + "availability": undefined, + "docs": undefined, + "encoding": undefined, + "inline": false, + "name": { + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "name": { + "camelCase": { + "safeName": "createCommentResponse", + "unsafeName": "createCommentResponse", + }, + "originalName": "CreateCommentResponse", + "pascalCase": { + "safeName": "CreateCommentResponse", + "unsafeName": "CreateCommentResponse", + }, + "screamingSnakeCase": { + "safeName": "CREATE_COMMENT_RESPONSE", + "unsafeName": "CREATE_COMMENT_RESPONSE", + }, + "snakeCase": { + "safeName": "create_comment_response", + "unsafeName": "create_comment_response", + }, + }, + "typeId": "CreateCommentResponse", + }, + "referencedTypes": Set {}, + "shape": { + "_visit": [Function], + "extendedProperties": [], + "extends": [], + "extraProperties": false, + "properties": [ + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "comment", + "unsafeName": "comment", + }, + "originalName": "comment", + "pascalCase": { + "safeName": "Comment", + "unsafeName": "Comment", + }, + "screamingSnakeCase": { + "safeName": "COMMENT", + "unsafeName": "COMMENT", + }, + "snakeCase": { + "safeName": "comment", + "unsafeName": "comment", + }, + }, + "wireValue": "comment", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": {}, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "container": { + "_visit": [Function], + "optional": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "comment", + "unsafeName": "comment", + }, + "originalName": "Comment", + "pascalCase": { + "safeName": "Comment", + "unsafeName": "Comment", + }, + "screamingSnakeCase": { + "safeName": "COMMENT", + "unsafeName": "COMMENT", + }, + "snakeCase": { + "safeName": "comment", + "unsafeName": "comment", + }, + }, + "type": "named", + "typeId": "Comment", + }, + "type": "optional", + }, + "type": "container", + }, + }, + ], + "type": "object", + }, + "source": undefined, + "userProvidedExamples": [], + "v2Examples": { + "autogeneratedExamples": { + "CreateCommentResponse_example_autogenerated": {}, + }, + "userSpecifiedExamples": {}, + }, + }, + }, + "variables": [], + "webhookGroups": {}, + "websocketChannels": undefined, +} \ No newline at end of file diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/generators.yml b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/generators.yml new file mode 100644 index 000000000000..79f97d509ca5 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/generators.yml @@ -0,0 +1,16 @@ +auth-schemes: + Bearer: + scheme: bearer +api: + auth: Bearer + default-environment: Production + environments: + Production: https://test.com/api + specs: + - proto: + root: proto + local-generation: true + from-openapi: true + dependencies: + - buf.build/googleapis/googleapis + - buf.build/bufbuild/protovalidate diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/comments.proto b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/comments.proto new file mode 100644 index 000000000000..d3eea6133a65 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/comments.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package comments.v1; + +option go_package = "github.com/fern-api/fern/comments/v1"; + +import "google/api/annotations.proto"; + +service CommentsService { + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // Create a comment on a resource + rpc CreateComment(CreateCommentRequest) returns (CreateCommentResponse) { + option (google.api.http) = { + post: "/comments/v1/comments" + response_body: "comment" + body: "*" + }; + } +} + +message CreateCommentRequest { + string resource_id = 1; + string text = 2; + string author = 3; +} + +message CreateCommentResponse { + Comment comment = 1; +} + +message Comment { + string id = 1; + string resource_id = 2; + string text = 3; + string author = 4; + int64 created_at = 5; +} diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts index fb21e9b94a95..48a74c292112 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts @@ -370,4 +370,65 @@ describe("OpenAPI v3 Parser Pipeline (--from-openapi flag)", () => { const rootApi = definition.rootApiFile?.contents; expect(rootApi).toBeDefined(); }); + + it("should handle gRPC proto with comments service", async () => { + const context = createMockTaskContext(); + const workspace = await loadAPIWorkspace({ + absolutePathToWorkspace: join( + AbsoluteFilePath.of(__dirname), + RelativeFilePath.of("fixtures/grpc-comments") + ), + context, + cliVersion: "0.0.0", + workspaceName: "grpc-comments" + }); + + expect(workspace.didSucceed).toBe(true); + assert(workspace.didSucceed); + + if (!(workspace.workspace instanceof OSSWorkspace)) { + throw new Error( + `Expected OSSWorkspace for gRPC processing, got ${workspace.workspace.constructor.name}` + ); + } + + const intermediateRepresentation = await workspace.workspace.getIntermediateRepresentation({ + context, + audiences: { type: "all" }, + enableUniqueErrorsPerEndpoint: true, + generateV1Examples: false + }); + + // Convert to FDR format (complete pipeline) + const fdrApiDefinition = await convertIrToFdrApi({ + ir: intermediateRepresentation, + snippetsConfig: { + typescriptSdk: undefined, + pythonSdk: undefined, + javaSdk: undefined, + rubySdk: undefined, + goSdk: undefined, + csharpSdk: undefined, + phpSdk: undefined, + swiftSdk: undefined, + rustSdk: undefined + }, + playgroundConfig: { + oauth: true + }, + context + }); + + // Validate auth was processed correctly + expect(intermediateRepresentation.auth).toBeDefined(); + expect(fdrApiDefinition.authSchemes).toBeDefined(); + + // Validate services and endpoints + expect(intermediateRepresentation.services).toBeDefined(); + expect(fdrApiDefinition.subpackages).toBeDefined(); + + // Snapshot the complete output for regression testing + await expect(fdrApiDefinition).toMatchFileSnapshot("__snapshots__/grpc-comments-fdr.snap"); + await expect(intermediateRepresentation).toMatchFileSnapshot("__snapshots__/grpc-comments-ir.snap"); + }); }); From bf75083d3287c3ebd71d102eb206162b4b3f7b50 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:19:58 +0000 Subject: [PATCH 02/12] style: fix formatting in openapi-from-flag.test.ts Co-Authored-By: kenny@buildwithfern.com --- .../ir-to-fdr-converter/__test__/openapi-from-flag.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts index 48a74c292112..c7dfc1238560 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts @@ -387,9 +387,7 @@ describe("OpenAPI v3 Parser Pipeline (--from-openapi flag)", () => { assert(workspace.didSucceed); if (!(workspace.workspace instanceof OSSWorkspace)) { - throw new Error( - `Expected OSSWorkspace for gRPC processing, got ${workspace.workspace.constructor.name}` - ); + throw new Error(`Expected OSSWorkspace for gRPC processing, got ${workspace.workspace.constructor.name}`); } const intermediateRepresentation = await workspace.workspace.getIntermediateRepresentation({ From 8daa12ee3cc767e13c6279d27ff4cb1a5391ad62 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:29:56 +0000 Subject: [PATCH 03/12] style: fix YAML formatting in grpc-comments generators.yml Co-Authored-By: kenny@buildwithfern.com --- .../__test__/fixtures/grpc-comments/generators.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/generators.yml b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/generators.yml index 79f97d509ca5..d1bb06065675 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/generators.yml +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/generators.yml @@ -1,16 +1,16 @@ -auth-schemes: - Bearer: +auth-schemes: + Bearer: scheme: bearer -api: +api: auth: Bearer default-environment: Production - environments: + environments: Production: https://test.com/api - specs: + specs: - proto: root: proto local-generation: true - from-openapi: true + from-openapi: true dependencies: - buf.build/googleapis/googleapis - buf.build/bufbuild/protovalidate From 04e6267e585541cef7c289d3b43c639df98625ec Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:49:02 +0000 Subject: [PATCH 04/12] feat(register): improve gRPC endpoint name display in FDR output Co-Authored-By: kenny@buildwithfern.com --- packages/cli/cli/versions.yml | 8 ++++++++ .../__test__/__snapshots__/grpc-comments-fdr.snap | 2 +- .../src/ir-to-fdr-converter/convertPackage.ts | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 9d067c9e0623..da4f8125c428 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,12 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- changelogEntry: + - summary: | + Improve gRPC endpoint name display in documentation by deriving human-readable names from request type names. Endpoint names like "CreateComment" now display as "Create Comment" instead of "Createcomment" in the FDR output. + type: feat + irVersion: 61 + createdAt: "2025-11-13" + version: 0.122.0 + - changelogEntry: - summary: | Refactor how settings from specs are parsed and applied in the CLI. diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap index 66df29fe522e..e3185a063f33 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap @@ -101,7 +101,7 @@ "includeInApiExplorer": undefined, "method": "POST", "multiAuth": undefined, - "name": "Createcomment", + "name": "Create Comment", "originalEndpointId": "endpoint_commentsService.createcomment", "path": { "parts": [ diff --git a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts index 4d468b9180eb..06cafe08879f 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts @@ -167,6 +167,19 @@ function convertService( } } } + let endpointName = irEndpoint.displayName ?? startCase(irEndpoint.name.originalName); + if ( + irEndpoint.displayName == null && + irEndpoint.source?.type === "openapi" && + irEndpoint.requestBody?.type === "reference" + ) { + const requestTypeName = irEndpoint.requestBody.requestBodyType.typeId; + if (requestTypeName != null && requestTypeName.endsWith("Request")) { + const methodName = requestTypeName.slice(0, -"Request".length); + endpointName = startCase(methodName); + } + } + const endpoint: FdrCjsSdk.api.v1.register.EndpointDefinition = { slug: undefined, availability: convertIrAvailability(irEndpoint.availability ?? irService.availability), @@ -185,7 +198,7 @@ function convertService( : undefined, id: FdrCjsSdk.EndpointId(irEndpoint.name.originalName), originalEndpointId: irEndpoint.id, - name: irEndpoint.displayName ?? startCase(irEndpoint.name.originalName), + name: endpointName, path: irEndpoint.basePath != null ? { From f50f7a8438d8c50d96d73ba45d2f6ad1f3fd4863 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:57:49 +0000 Subject: [PATCH 05/12] fix(register): correct TypeReference property access for endpoint naming Co-Authored-By: kenny@buildwithfern.com --- .../cli/register/src/ir-to-fdr-converter/convertPackage.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts index 06cafe08879f..af3eaad7c5cb 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts @@ -171,9 +171,12 @@ function convertService( if ( irEndpoint.displayName == null && irEndpoint.source?.type === "openapi" && - irEndpoint.requestBody?.type === "reference" + irEndpoint.requestBody?.type === "reference" && + irEndpoint.requestBody.requestBodyType.type === "named" ) { - const requestTypeName = irEndpoint.requestBody.requestBodyType.typeId; + const typeId = irEndpoint.requestBody.requestBodyType.typeId; + const typeDeclaration = ir.types[typeId]; + const requestTypeName = typeDeclaration?.name.name.originalName; if (requestTypeName != null && requestTypeName.endsWith("Request")) { const methodName = requestTypeName.slice(0, -"Request".length); endpointName = startCase(methodName); From 42e8bd1043fd2122091ac47f7bfc5f1194ad2d5f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:01:57 +0000 Subject: [PATCH 06/12] feat(cli): add support for custom fern.summary proto option - Add fern/options.proto defining custom summary option for RPC methods - Update grpc-comments fixture to use fern.summary option - Custom summary 'Add a Comment' now appears in FDR output - Remove unused grpc-gateway dependency from fixture This allows users to explicitly set endpoint display names in gRPC proto files using: option (fern.summary) = "Custom Name"; Requires protoc-gen-openapi to support reading the fern.summary option. Co-Authored-By: kenny@buildwithfern.com --- .../__test__/__snapshots__/grpc-comments-fdr.snap | 2 +- .../__test__/__snapshots__/grpc-comments-ir.snap | 6 +++--- .../fixtures/grpc-comments/proto/comments.proto | 2 ++ .../fixtures/grpc-comments/proto/fern/options.proto | 13 +++++++++++++ 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/fern/options.proto diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap index e3185a063f33..9678a7d32da6 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap @@ -101,7 +101,7 @@ "includeInApiExplorer": undefined, "method": "POST", "multiAuth": undefined, - "name": "Create Comment", + "name": "Add a Comment", "originalEndpointId": "endpoint_commentsService.createcomment", "path": { "parts": [ diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap index 67138f80ef88..df36873c3115 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap @@ -22,7 +22,7 @@ }, "apiPlayground": true, "apiVersion": undefined, - "audiences": undefined, + "audiences": [], "auth": { "docs": undefined, "requirement": "ALL", @@ -185,7 +185,7 @@ "availability": undefined, "basePath": undefined, "baseUrl": undefined, - "displayName": undefined, + "displayName": "Add a Comment", "docs": "buf:lint:ignore RPC_REQUEST_STANDARD_NAME Create a comment on a resource", "errors": [], @@ -1314,5 +1314,5 @@ }, "variables": [], "webhookGroups": {}, - "websocketChannels": undefined, + "websocketChannels": {}, } \ No newline at end of file diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/comments.proto b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/comments.proto index d3eea6133a65..1a97117023fd 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/comments.proto +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/comments.proto @@ -5,6 +5,7 @@ package comments.v1; option go_package = "github.com/fern-api/fern/comments/v1"; import "google/api/annotations.proto"; +import "fern/options.proto"; service CommentsService { // buf:lint:ignore RPC_REQUEST_STANDARD_NAME @@ -15,6 +16,7 @@ service CommentsService { response_body: "comment" body: "*" }; + option (fern.summary) = "Add a Comment"; } } diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/fern/options.proto b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/fern/options.proto new file mode 100644 index 000000000000..400ab8f82773 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/grpc-comments/proto/fern/options.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package fern; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/fern-api/protoc-gen-openapi/extensions/fern"; + +extend google.protobuf.MethodOptions { + // Custom summary for the RPC method that will be used as the OpenAPI operation summary. + // This overrides any auto-generated summary. + string summary = 51001; +} From 62bc4d6b61206fd29377f9c037f15554beb9959e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:15:29 +0000 Subject: [PATCH 07/12] chore: pin protoc-gen-openapi to PR #13 commit for fern.summary support Temporarily pin CI to use the modified protoc-gen-openapi that supports reading the custom fern.summary option. This allows the grpc-comments test to pass with the custom summary 'Add a Comment'. Once https://github.com/fern-api/protoc-gen-openapi/pull/13 is merged and released, this should be updated to use @latest or a tagged release. Co-Authored-By: kenny@buildwithfern.com --- scripts/bootstrap.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 6f03b6d975ba..ec80c60bab59 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -54,7 +54,7 @@ echo "✅ buf installed" # Install protoc-gen-openapi. if ! command_exists protoc-gen-openapi; then echo "Installing protoc-gen-openapi..." - go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@latest + go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@1aa7fe870834ed27024dc44afd95648a0d63ec57 fi echo "✅ protoc-gen-openapi installed" From a2813d2c954467a2ca1f99cd8ba9fa17033494e2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:19:40 +0000 Subject: [PATCH 08/12] chore: pin CI to use protoc-gen-openapi PR #13 commit Update both ci.yml and bootstrap.sh to use the modified protoc-gen-openapi that supports reading the custom fern.summary option from proto files. This allows the grpc-comments test to pass with the custom summary 'Add a Comment' instead of the auto-generated 'Create Comment'. Once https://github.com/fern-api/protoc-gen-openapi/pull/13 is merged and released, these should be updated to use @latest or a tagged release. Co-Authored-By: kenny@buildwithfern.com --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0301487d7529..13cd072d4b1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,7 @@ jobs: cache-dependency-path: generators/go/go.sum - name: Install protoc-gen-openapi - run: go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@latest + run: go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@1aa7fe870834ed27024dc44afd95648a0d63ec57 - name: Run tests run: pnpm test @@ -130,7 +130,7 @@ jobs: cache-dependency-path: generators/go/go.sum - name: Install protoc-gen-openapi - run: go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@latest + run: go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@1aa7fe870834ed27024dc44afd95648a0d63ec57 - name: Run ETE tests env: From 86959f49173d26baa05f86403073bb2b98b9b604 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:29:03 +0000 Subject: [PATCH 09/12] fix: bump CLI version to 0.123.0 for feat type change The changelog validator requires a minor version bump (0.122.1 -> 0.123.0) for 'feat' type changes per semver rules. Changed from 0.122.2 to 0.123.0. Co-Authored-By: kenny@buildwithfern.com --- packages/cli/cli/versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 180855a72f4c..38648a6a8565 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -5,7 +5,7 @@ type: feat irVersion: 61 createdAt: "2025-11-13" - version: 0.122.2 + version: 0.123.0 - changelogEntry: - summary: | From 75634dfd831828889f6497f770fe5a7ccee7bc7f Mon Sep 17 00:00:00 2001 From: Kenny Derek Date: Fri, 14 Nov 2025 11:01:16 -0500 Subject: [PATCH 10/12] tmp rm versions.yml --- packages/cli/cli/versions.yml | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 38648a6a8565..8ecb7029415c 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,27 +1,45 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json - changelogEntry: - summary: | - Add support for custom fern.summary proto option to explicitly set gRPC endpoint display names in proto files. - type: feat + Specify the target of links throughout the docs navigation. + type: fix + irVersion: 61 + createdAt: "2025-11-14" + version: 1.0.3 + +- changelogEntry: + - summary: | + Display OpenAPI validation warnings when using the --warnings flag with fern check. Previously, warnings were counted but not shown; now they are logged with location details when --warnings is specified. + type: fix irVersion: 61 createdAt: "2025-11-13" - version: 0.123.0 + version: 1.0.2 - changelogEntry: - summary: | - The `fern docs dev` command now displays a progress bar while downloading the docs bundle. - type: chore + Update OpenAPI operation converter to use `x-fern-explorer` extension instead of `x-fern-explorer-enabled` for setting `apiPlayground` property in the IR. Operations now check for the `x-fern-explorer` boolean value to determine API playground availability. + type: fix irVersion: 61 createdAt: "2025-11-13" - version: 0.122.1 + version: 1.0.1 - changelogEntry: - summary: | - The `fern write-overrides` command now generates overrides files with names based on the API spec filename. For a spec named `api-name.yml`, the overrides file will be named `api-name-overrides.yml` and placed in the same directory as the spec. This allows for better organization when working with multiple API specs. + Change defaults for OpenAPI and AsyncAPI parsing settings: + - `title-as-schema-name`: `false` (was `true`) + - `respect-nullable-schemas`: `true` (was `false`) + - `inline-path-parameters`: `true` (was `false`) + - `idiomatic-request-names`: `true` (was `false`) + - `type-dates-as-strings`: `true` (was `false`) + - `wrap-references-to-nullable-in-optional`: `false` (was `true`) + - `coerce-optional-schemas-to-nullable`: `false` (was `true`) + - `object-query-parameters`: `true` (was `false`) + The CLI will automatically upgrade your _generators.yml_ to explicitly set the old defaults to preserve + existing behavior. New users will get the new defaults. type: feat irVersion: 61 createdAt: "2025-11-13" - version: 0.122.0 + version: 1.0.0 - changelogEntry: - summary: | From 8e47c74eab23d7f001401a22d6ad57ca73db5fb9 Mon Sep 17 00:00:00 2001 From: Kenny Derek Date: Fri, 14 Nov 2025 11:57:24 -0500 Subject: [PATCH 11/12] test only --- .github/workflows/ci.yml | 4 ++-- .../__test__/openapi-from-flag.test.ts | 3 ++- .../src/ir-to-fdr-converter/convertPackage.ts | 18 +----------------- scripts/bootstrap.sh | 2 +- 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13cd072d4b1a..0301487d7529 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,7 @@ jobs: cache-dependency-path: generators/go/go.sum - name: Install protoc-gen-openapi - run: go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@1aa7fe870834ed27024dc44afd95648a0d63ec57 + run: go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@latest - name: Run tests run: pnpm test @@ -130,7 +130,7 @@ jobs: cache-dependency-path: generators/go/go.sum - name: Install protoc-gen-openapi - run: go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@1aa7fe870834ed27024dc44afd95648a0d63ec57 + run: go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@latest - name: Run ETE tests env: diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts index f0e1c2c000bc..cac530abadbb 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts @@ -398,7 +398,8 @@ describe("OpenAPI v3 Parser Pipeline (--from-openapi flag)", () => { context, audiences: { type: "all" }, enableUniqueErrorsPerEndpoint: true, - generateV1Examples: false + generateV1Examples: false, + logWarnings: true }); // Convert to FDR format (complete pipeline) diff --git a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts index af3eaad7c5cb..4d468b9180eb 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts @@ -167,22 +167,6 @@ function convertService( } } } - let endpointName = irEndpoint.displayName ?? startCase(irEndpoint.name.originalName); - if ( - irEndpoint.displayName == null && - irEndpoint.source?.type === "openapi" && - irEndpoint.requestBody?.type === "reference" && - irEndpoint.requestBody.requestBodyType.type === "named" - ) { - const typeId = irEndpoint.requestBody.requestBodyType.typeId; - const typeDeclaration = ir.types[typeId]; - const requestTypeName = typeDeclaration?.name.name.originalName; - if (requestTypeName != null && requestTypeName.endsWith("Request")) { - const methodName = requestTypeName.slice(0, -"Request".length); - endpointName = startCase(methodName); - } - } - const endpoint: FdrCjsSdk.api.v1.register.EndpointDefinition = { slug: undefined, availability: convertIrAvailability(irEndpoint.availability ?? irService.availability), @@ -201,7 +185,7 @@ function convertService( : undefined, id: FdrCjsSdk.EndpointId(irEndpoint.name.originalName), originalEndpointId: irEndpoint.id, - name: endpointName, + name: irEndpoint.displayName ?? startCase(irEndpoint.name.originalName), path: irEndpoint.basePath != null ? { diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index ec80c60bab59..6f03b6d975ba 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -54,7 +54,7 @@ echo "✅ buf installed" # Install protoc-gen-openapi. if ! command_exists protoc-gen-openapi; then echo "Installing protoc-gen-openapi..." - go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@1aa7fe870834ed27024dc44afd95648a0d63ec57 + go install github.com/fern-api/protoc-gen-openapi/cmd/protoc-gen-openapi@latest fi echo "✅ protoc-gen-openapi installed" From 286cd763e2d8f23c905251bdd74e33baf066608c Mon Sep 17 00:00:00 2001 From: Kenny Derek Date: Fri, 14 Nov 2025 12:21:04 -0500 Subject: [PATCH 12/12] update test --- .../cli/register/src/ir-to-fdr-converter/__test__/CLAUDE.md | 2 +- .../__test__/__snapshots__/grpc-comments-ir.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/CLAUDE.md b/packages/cli/register/src/ir-to-fdr-converter/__test__/CLAUDE.md index 91f915ea624c..2719675cdbbc 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/CLAUDE.md +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/CLAUDE.md @@ -6,7 +6,7 @@ Direct testing of OpenAPI v3 parser pipeline without CLI compilation. ```bash # Run tests -cd packages/cli/workspace/register +cd packages/cli/register pnpm test src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts # Update snapshots diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap index df36873c3115..a4d2de21d084 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-ir.snap @@ -20,7 +20,7 @@ "unsafeName": "", }, }, - "apiPlayground": true, + "apiPlayground": undefined, "apiVersion": undefined, "audiences": [], "auth": {