Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def member(type, key: nil)
self
end

def member?(type)
def type_member?(type)
members.any? { |_key, type_fn| type == type_fn.call }
end

Expand Down Expand Up @@ -162,4 +162,4 @@ def load(str)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ module StringOrInteger
end
end

describe "#member" do
describe "#type_member?" do
it "defines Model members" do
assert Shape.member?(Rectangle)
assert Shape.member?(Circle)
refute Shape.member?(Pineapple)
assert Shape.type_member?(Rectangle)
assert Shape.type_member?(Circle)
refute Shape.type_member?(Pineapple)
end

it "defines other members" do
assert StringOrInteger.member?(String)
assert StringOrInteger.member?(Integer)
refute StringOrInteger.member?(Float)
refute StringOrInteger.member?(Pineapple)
assert StringOrInteger.type_member?(String)
assert StringOrInteger.type_member?(Integer)
refute StringOrInteger.type_member?(Float)
refute StringOrInteger.type_member?(Pineapple)
end
end
end
11 changes: 11 additions & 0 deletions generators/ruby-v2/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json

- version: 1.0.2
changelogEntry:
- summary: |
Rename `Union#member?` to `Union#type_member?` to avoid rubocop-minitest's
`Minitest/AssertIncludes` cop incorrectly autocorrecting `assert obj.member?(x)`
to `assert_includes obj, x`. The autocorrected form calls `Module#include?` which
raises `TypeError: wrong argument type Class (expected Module)` on union type tests.
type: fix
createdAt: "2026-02-24"
irVersion: 61

- version: 1.0.1
changelogEntry:
- summary: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,22 @@ import { AsyncAPIV3 } from "../v3/index.js";

export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<AsyncAPIV3.DocumentV3> {
public getExampleMessageReference(message: WebsocketSessionExampleMessage): string {
return `#/channels/${message.channelId}/messages/${message.messageId}`;
const channelId = message.channelId ?? this.getDefaultChannelId();
if (channelId == null) {
throw new Error(
`Cannot resolve example message reference: no channelId provided and no channels found in document`
);
}
return `#/channels/${channelId}/messages/${message.messageId}`;
}

/**
* Returns the first channel ID from the document as a fallback when
* x-fern-examples messages omit `channelId`.
*/
private getDefaultChannelId(): string | undefined {
const channelIds = Object.keys(this.document.channels ?? {});
return channelIds[0];
}

public resolveParameterReference(parameter: OpenAPIV3.ReferenceObject): AsyncAPIV3.ChannelParameter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,39 +355,45 @@ export function parseAsyncAPIV3({
const fernExamples: WebsocketSessionExampleExtension[] = getFernExamples(channel);
const messages: WebsocketMessageSchema[] = channelEvents[channelPath]?.__parsedMessages ?? [];
let examples: WebsocketSessionExample[] = [];
if (fernExamples.length > 0) {
examples = exampleFactory.buildWebsocketSessionExamplesForExtension({
context,
extensionExamples: fernExamples,
handshake: {
headers,
queryParameters
},
source,
namespace: context.namespace
});
} else {
const exampleBuilderInputs: SessionExampleBuilderInput[] = [];
const { examplePublishMessage, exampleSubscribeMessage } = getExampleSchemas({
messages,
messageSchemas: messageSchemas[channelPath] ?? {}
});
if (examplePublishMessage != null) {
exampleBuilderInputs.push(examplePublishMessage);
}
if (exampleSubscribeMessage != null) {
exampleBuilderInputs.push(exampleSubscribeMessage);
}
const autogenExample = exampleFactory.buildWebsocketSessionExample({
handshake: {
headers,
queryParameters
},
messages: exampleBuilderInputs
});
if (autogenExample != null) {
examples.push(autogenExample);
try {
if (fernExamples.length > 0) {
examples = exampleFactory.buildWebsocketSessionExamplesForExtension({
context,
extensionExamples: fernExamples,
handshake: {
headers,
queryParameters
},
source,
namespace: context.namespace
});
} else {
const exampleBuilderInputs: SessionExampleBuilderInput[] = [];
const { examplePublishMessage, exampleSubscribeMessage } = getExampleSchemas({
messages,
messageSchemas: messageSchemas[channelPath] ?? {}
});
if (examplePublishMessage != null) {
exampleBuilderInputs.push(examplePublishMessage);
}
if (exampleSubscribeMessage != null) {
exampleBuilderInputs.push(exampleSubscribeMessage);
}
const autogenExample = exampleFactory.buildWebsocketSessionExample({
handshake: {
headers,
queryParameters
},
messages: exampleBuilderInputs
});
if (autogenExample != null) {
examples.push(autogenExample);
}
}
} catch (error) {
context.logger.warn(
`Failed to build examples for channel ${channelPath}: ${error instanceof Error ? error.message : String(error)}`
);
}

const groupName = getExtension<string | string[] | undefined>(
Expand Down Expand Up @@ -546,21 +552,27 @@ function convertMessageReferencesToWebsocketSchemas({
const results: WebsocketMessageSchema[] = [];

messages.forEach((message, i) => {
const channelMessage = context.resolveMessageReference(message.ref, true);
let schemaId = channelMessage.name as string;
try {
const channelMessage = context.resolveMessageReference(message.ref, true);
let schemaId = channelMessage.name as string;

if (duplicatedMessageIds.includes(schemaId)) {
schemaId = `${channelPath}_${schemaId}`;
}
if (duplicatedMessageIds.includes(schemaId)) {
schemaId = `${channelPath}_${schemaId}`;
}

const schema = messageSchemas[schemaId];
if (schema != null) {
results.push({
origin,
name: schemaId ?? `${origin}Message${i + 1}`,
body: convertSchemaWithExampleToSchema(schema),
methodName: message.methodName
});
const schema = messageSchemas[schemaId];
if (schema != null) {
results.push({
origin,
name: schemaId ?? `${origin}Message${i + 1}`,
body: convertSchemaWithExampleToSchema(schema),
methodName: message.methodName
});
}
} catch (error) {
context.logger.warn(
`Skipping message reference ${message.ref.$ref} in channel ${channelPath}: ${error instanceof Error ? error.message : String(error)}`
);
}
});

Expand Down
30 changes: 30 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 3.85.5
changelogEntry:
- summary: |
Report broken AsyncAPI V3 message references as visible warnings instead
of silently dropping entire specs. When an operation references a
non-existent channel or message (e.g. `$ref: "#/channels/auth/messages/authenticate"`
when no `auth` channel exists), the parser now logs a warning with the
specific broken `$ref` and continues processing the remaining valid
messages and channels.
type: fix
createdAt: "2026-02-24"
irVersion: 65
- version: 3.85.4
changelogEntry:
- summary: |
Skip compatible IR version validation when running in local development
mode (CLI version 0.0.0). This prevents noisy 404 errors from the FDR
registry when the CLI version is not a real published release.
type: fix
createdAt: "2026-02-24"
irVersion: 65
- version: 3.85.3
changelogEntry:
- summary: |
Remove unhelpful "Unable to analyze changes with AI" fallback message from
changelog entries when AI analysis fails during AUTO versioning. The changelog
now shows just the version header with no body instead.
type: fix
createdAt: "2026-02-24"
irVersion: 65
- version: 3.85.2
changelogEntry:
- summary: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ export class LocalTaskHandler {
this.context.logger.warn(`AI analysis failed, falling back to PATCH increment: ${aiError}`);
const newVersion = this.incrementVersion(previousVersion, VersionBump.PATCH);
const fallbackMessage = this.isWhitelabel
? "SDK regeneration\n\nUnable to analyze changes with AI, incrementing PATCH version."
: "SDK regeneration\n\nUnable to analyze changes with AI, incrementing PATCH version.\n\n🌿 Generated with Fern";
? "SDK regeneration"
: "SDK regeneration\n\n🌿 Generated with Fern";
return {
version: newVersion,
commitMessage: fallbackMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CompatibleIrVersionsRule } from "../compatible-ir-versions.js";

describe("compatible-ir-versions", () => {
it("simple failure", async () => {
process.env.DEFAULT_FDR_ORIGIN = "https://registry-dev2.buildwithfern.com";
process.env.DEFAULT_FDR_ORIGIN = "https://registry.buildwithfern.com";
const violations = await getViolationsForRule({
rule: CompatibleIrVersionsRule,
absolutePathToWorkspace: join(
Expand All @@ -30,7 +30,7 @@ describe("compatible-ir-versions", () => {
}, 30_000);

it("simple success", async () => {
process.env.DEFAULT_FDR_ORIGIN = "https://registry-dev2.buildwithfern.com";
process.env.DEFAULT_FDR_ORIGIN = "https://registry.buildwithfern.com";
const violations = await getViolationsForRule({
rule: CompatibleIrVersionsRule,
absolutePathToWorkspace: join(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ export const CompatibleIrVersionsRule: Rule = {
return {
generatorsYml: {
generatorInvocation: async ({ invocation, cliVersion }) => {
const fdr = createFdrGeneratorsSdkService({ token: undefined });
if (cliVersion == null) {
if (cliVersion == null || cliVersion === "0.0.0") {
return [];
}

const fdr = createFdrGeneratorsSdkService({ token: undefined });

// Pull the CLI release to get the IR version
// biome-ignore lint/suspicious/noConsole: intentional debug logging for FDR registry diagnostics
console.debug(`[FDR] compatible-ir-versions: checking CLI release for version=${cliVersion}`);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion seed/ruby-sdk-v2/alias/lib/seed/internal/types/union.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions seed/ruby-sdk-v2/alias/test/unit/internal/types/test_union.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion seed/ruby-sdk-v2/any-auth/lib/seed/internal/types/union.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading