From 3425153399d5ccd2c45cfd098bb9c601a140c3e7 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Mon, 16 Mar 2026 13:36:39 -0400 Subject: [PATCH] fix(typescript): handle baseUrlId for single-URL WebSocket environments (#13564) Co-authored-by: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/GeneratedSingleUrlEnvironmentsImpl.ts | 8 +++----- .../src/__test__/EnvironmentsGenerator.test.ts | 13 ++++++------- generators/typescript/sdk/versions.yml | 12 ++++++++++++ .../openapi/asyncapi-v2-x-fern-parameter-name.json | 3 +-- .../openapi/asyncapi-v3-message-refs.json | 3 +-- .../openapi/asyncapi-v3-x-fern-sdk-method-name.json | 3 +-- .../openapi/openapi-ir-to-fern/src/buildChannel.ts | 8 +++++++- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/generators/typescript/sdk/environments-generator/src/GeneratedSingleUrlEnvironmentsImpl.ts b/generators/typescript/sdk/environments-generator/src/GeneratedSingleUrlEnvironmentsImpl.ts index 09fd2ac2da83..aa91b67179c7 100644 --- a/generators/typescript/sdk/environments-generator/src/GeneratedSingleUrlEnvironmentsImpl.ts +++ b/generators/typescript/sdk/environments-generator/src/GeneratedSingleUrlEnvironmentsImpl.ts @@ -96,11 +96,9 @@ export class GeneratedSingleUrlEnvironmentsImpl implements GeneratedEnvironments referenceToEnvironmentValue: ts.Expression; baseUrlId: FernIr.EnvironmentBaseUrlId | undefined; }): ts.Expression { - if (baseUrlId != null) { - throw new Error( - `Cannot get reference to single environment URL because baseUrlId is defined ("${baseUrlId}")` - ); - } + // For single URL environments, baseUrlId is irrelevant since there's only one URL. + // This can happen when an AsyncAPI spec defines a named server (e.g. "production") + // which gets converted to a baseUrlId on the WebSocket channel. return referenceToEnvironmentValue; } diff --git a/generators/typescript/sdk/environments-generator/src/__test__/EnvironmentsGenerator.test.ts b/generators/typescript/sdk/environments-generator/src/__test__/EnvironmentsGenerator.test.ts index 8dea908e4848..b0cd6e183801 100644 --- a/generators/typescript/sdk/environments-generator/src/__test__/EnvironmentsGenerator.test.ts +++ b/generators/typescript/sdk/environments-generator/src/__test__/EnvironmentsGenerator.test.ts @@ -115,19 +115,18 @@ describe("GeneratedSingleUrlEnvironmentsImpl", () => { expect(getTextOfTsNode(result)).toBe("env"); }); - it("getReferenceToEnvironmentUrl throws when baseUrlId is provided", () => { + it("getReferenceToEnvironmentUrl ignores baseUrlId for single URL", () => { const impl = new GeneratedSingleUrlEnvironmentsImpl({ environmentEnumName: "MyEnvironment", defaultEnvironmentId: undefined, environments: singleUrlEnvironments }); const inputExpr = ts.factory.createIdentifier("env"); - expect(() => - impl.getReferenceToEnvironmentUrl({ - referenceToEnvironmentValue: inputExpr, - baseUrlId: "some-base-url" - }) - ).toThrow(); + const result = impl.getReferenceToEnvironmentUrl({ + referenceToEnvironmentValue: inputExpr, + baseUrlId: "some-base-url" + }); + expect(getTextOfTsNode(result)).toBe("env"); }); it("writeToFile generates environment const and type alias", () => { diff --git a/generators/typescript/sdk/versions.yml b/generators/typescript/sdk/versions.yml index 94faa5ce93c7..fd0c2893b280 100644 --- a/generators/typescript/sdk/versions.yml +++ b/generators/typescript/sdk/versions.yml @@ -1,4 +1,16 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 3.56.3 + changelogEntry: + - summary: | + Fix TypeScript SDK generator crash when generating WebSocket clients for + AsyncAPI specs that define a named server (e.g. `servers.production`) with + a single-URL environment. The `baseUrlId` assigned by the Fern IR converter + is now silently ignored for single-URL environments since there is only one + URL to resolve to. + type: fix + createdAt: "2026-03-16" + irVersion: 65 + - version: 3.56.2 changelogEntry: - summary: | diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v2-x-fern-parameter-name.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v2-x-fern-parameter-name.json index f71b52508567..239973a6c03b 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v2-x-fern-parameter-name.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v2-x-fern-parameter-name.json @@ -44,7 +44,7 @@ "type": "string", }, }, - "url": "production", + "url": undefined, }, "types": { "UsersUserIdMessagesPublish": { @@ -72,7 +72,6 @@ }, "rawContents": "channel: path: /users/{id}/messages - url: production auth: false path-parameters: id: diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v3-message-refs.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v3-message-refs.json index 70e5649f5877..c66c2ec9bd17 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v3-message-refs.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v3-message-refs.json @@ -114,7 +114,7 @@ }, "model": "string", }, - "url": "production", + "url": undefined, }, "imports": { "root": "__package__.yml", @@ -122,7 +122,6 @@ }, "rawContents": "channel: path: /v1/ws/chat - url: production auth: false docs: Chat channel for testing message references query-parameters: diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v3-x-fern-sdk-method-name.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v3-x-fern-sdk-method-name.json index 857b6f7e3f5a..b68d2cdee481 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v3-x-fern-sdk-method-name.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v3-x-fern-sdk-method-name.json @@ -117,7 +117,7 @@ }, }, "path": "/v1/ws/chat", - "url": "production", + "url": undefined, }, "imports": { "root": "__package__.yml", @@ -125,7 +125,6 @@ }, "rawContents": "channel: path: /v1/ws/chat - url: production auth: false connect-method-name: createChatConnection docs: Chat channel for testing custom method names diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildChannel.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildChannel.ts index f2a84a1604da..abc347a2bdeb 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildChannel.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildChannel.ts @@ -21,11 +21,17 @@ export function buildChannel({ declarationFile: RelativeFilePath; }): void { const firstServer = channel.servers[0]; + // Don't set a urlId when it would result in a single-URL environment. + // This happens when there are only WebSocket servers (no HTTP servers) and only one + // WebSocket server — buildEnvironments creates a single-URL environment in that case, + // so a baseUrlId on the channel would be meaningless and can cause generator issues. + const hasSingleWebsocketServerOnly = context.ir.servers.length === 0 && context.ir.websocketServers.length <= 1; // Generate URL ID based on feature flag: // - If groupEnvironmentsByHost is enabled, look up the collision-aware URL ID from the map // - Otherwise, use simple server name for backward compatibility + // - Skip entirely for single-WebSocket-server-only specs (no multi-URL environment) const urlId = - firstServer != null + firstServer != null && !hasSingleWebsocketServerOnly ? context.options.groupEnvironmentsByHost ? (context.getUrlId(firstServer.url) ?? generateWebsocketUrlId(firstServer.name, firstServer.url, true)) : firstServer.name