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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 15 additions & 1 deletion .claude/release-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ If a path falls under **`softwareDirectory`**, use the matching **`changelogFold
| `java` | `generators/java` | `generators/java/sdk/changes/unreleased/` |
| `php` | `generators/php` | `generators/php/sdk/changes/unreleased/` |
| `python` | `generators/python` | `generators/python/sdk/changes/unreleased/` |
| `ruby` | `generators/ruby` | `generators/ruby/sdk/changes/unreleased/` |
| `ruby-v2` | `generators/ruby-v2` | `generators/ruby-v2/sdk/changes/unreleased/` |
| `rust` | `generators/rust` | `generators/rust/sdk/changes/unreleased/` |
| `swift` | `generators/swift` | `generators/swift/sdk/changes/unreleased/` |
| `typescript` | `generators/typescript` | `generators/typescript/sdk/changes/unreleased/` |
| `cli-generator` | `generators/cli` | `generators/cli/changes/unreleased/` |

Pick the narrowest matching **`softwareDirectory`** when several could apply (e.g. generator-only work under `generators/python/...` uses the Python generator row, not CLI). If a single PR spans multiple **`softwareDirectory`** trees, add a changelog file in **each** corresponding **`changelogFolder/unreleased/`**.

Expand All @@ -47,3 +47,17 @@ Pick the narrowest matching **`softwareDirectory`** when several could apply (e.
- **`type`**: `fix` | `chore` | `feat` | `internal`

Those types drive semver bumps when releases run (`fix`/`chore` → patch, `feat`/`internal` → minor). The automated release flow does **not** produce major version bumps. To ship a major release, a human edits the relevant `versions.yml` directly so the breaking change is explicitly acknowledged in review. The validator rejects `type: break` in `unreleased/`.

## Cross-product propagation (`propagatesTo`)

Some products embed another product's output. For example, the CLI Generator bundles the Rust SDK Generator's generated code. When the Rust SDK ships a fix, the CLI Generator must also release a new version with that fix.

**`propagatesTo`** in `release-config.json` automates this. When a software entry with `propagatesTo` is released, the release script creates an auto-generated changelog entry (`auto-propagated-from-<source>.yml`) in each target's `unreleased/` folder. The entry inherits the highest severity type from the source so the target receives at least a matching semver bump.

Current propagation relationships:

| Source | Propagates to |
|--------|--------------|
| `rust` | `cli-generator` |

**You do not need to manually add a CLI Generator changelog entry when only the Rust SDK changes.** The automation handles it. If your PR also changes CLI Generator code (under `generators/cli/`), add a separate changelog entry as usual — the propagated entry will be combined with it during release.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- summary: |
Fix request body serialization when flattenRequestParameters is enabled with
a non-object reference body (e.g., `list<string>`) and header parameters. Previously
the generated code used spread destructuring which wrapped the body in an object.
type: fix
Original file line number Diff line number Diff line change
Expand Up @@ -848,11 +848,20 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
}
return FernIr.HttpRequestBody._visit(requestBody, {
inlinedRequestBody: () => false,
reference: () => {
reference: (referenceToRequestBody) => {
if (!this.flattenRequestParameters) {
return true;
}
return false;
// Only named object types can be flattened into individual properties.
// Non-named types (e.g., list<string>) cannot be flattened and get
// wrapped in a body property instead.
if (referenceToRequestBody.requestBodyType.type === "named") {
const typeDeclaration = this.getTypeDeclaration(referenceToRequestBody.requestBodyType, context);
if (typeDeclaration?.shape.type === "object") {
return false;
}
}
return true;
},
bytes: () => false,
fileUpload: () => false,
Expand Down Expand Up @@ -936,8 +945,8 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
context.case.pascalSafe(referenceToRequestBody.requestBodyType.name)
);
properties.push(...typeProperties);
return properties;
}
return properties;
}

const type = context.type.getReferenceToType(referenceToRequestBody.requestBodyType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ describe("GeneratedRequestWrapperImpl", () => {
expect(wrapper.hasBodyProperty(context)).toBe(true);
});

it("returns false for reference body with flattenRequestParameters=true", () => {
it("returns true for non-named reference body with flattenRequestParameters=true", () => {
const init = createDefaultInit({
flattenRequestParameters: true,
endpoint: createHttpEndpoint({
Expand All @@ -982,6 +982,25 @@ describe("GeneratedRequestWrapperImpl", () => {
});
const wrapper = new GeneratedRequestWrapperImpl(init);
const { context } = createMockContext();
expect(wrapper.hasBodyProperty(context)).toBe(true);
});

it("returns false for named object reference body with flattenRequestParameters=true", () => {
const namedType = createNamedTypeReference("MyObject");
const init = createDefaultInit({
flattenRequestParameters: true,
endpoint: createHttpEndpoint({
requestBody: FernIr.HttpRequestBody.reference({
requestBodyType: namedType,
docs: undefined,
contentType: undefined,
v2Examples: undefined
}),
sdkRequest: createSdkRequestWrapper()
})
});
const wrapper = new GeneratedRequestWrapperImpl(init);
const { context } = createMockContext();
expect(wrapper.hasBodyProperty(context)).toBe(false);
});

Expand Down Expand Up @@ -2305,7 +2324,7 @@ describe("GeneratedRequestWrapperImpl", () => {
expect(properties[0]?.docs).toEqual(["The raw body"]);
});

it("returns empty for non-object type declaration when flattened", () => {
it("wraps non-object named type as body property when flattened", () => {
const namedBodyRef = createNamedTypeReference("AliasPayload");
const init = createDefaultInit({
flattenRequestParameters: true,
Expand Down Expand Up @@ -2343,8 +2362,9 @@ describe("GeneratedRequestWrapperImpl", () => {
getTypeDeclarationFn: () => aliasTypeDeclaration
});
const properties = wrapper.getRequestProperties(context);
// Alias type can't be flattened into properties
expect(properties).toHaveLength(0);
// Named non-object types get wrapped as a body property
expect(properties).toHaveLength(1);
expect(properties[0]?.name).toBe("body");
});

it("uses enableInlineTypes createNamespacedPropertyType when flattening named reference body", () => {
Expand Down Expand Up @@ -2472,7 +2492,7 @@ describe("GeneratedRequestWrapperImpl", () => {
expect(wrapper.hasBodyProperty(context)).toBe(true);
});

it("returns false for reference request body when flattened", () => {
it("returns true for non-named reference request body when flattened", () => {
const init = createDefaultInit({
flattenRequestParameters: true,
endpoint: createHttpEndpoint({
Expand All @@ -2487,6 +2507,25 @@ describe("GeneratedRequestWrapperImpl", () => {
});
const wrapper = new GeneratedRequestWrapperImpl(init);
const { context } = createMockContext();
expect(wrapper.hasBodyProperty(context)).toBe(true);
});

it("returns false for named object reference request body when flattened", () => {
const namedType = createNamedTypeReference("MyObject");
const init = createDefaultInit({
flattenRequestParameters: true,
endpoint: createHttpEndpoint({
requestBody: FernIr.HttpRequestBody.reference({
requestBodyType: namedType,
contentType: undefined,
docs: undefined,
v2Examples: undefined
}),
sdkRequest: createSdkRequestWrapper()
})
});
const wrapper = new GeneratedRequestWrapperImpl(init);
const { context } = createMockContext();
expect(wrapper.hasBodyProperty(context)).toBe(false);
});

Expand Down
9 changes: 9 additions & 0 deletions generators/typescript/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 3.71.9
changelogEntry:
- summary: |
Fix request body serialization when flattenRequestParameters is enabled with
a non-object reference body (e.g., `list<string>`) and header parameters. Previously
the generated code used spread destructuring which wrapped the body in an object.
type: fix
createdAt: "2026-06-12"
irVersion: 67
- version: 3.71.8
changelogEntry:
- summary: |
Expand Down
Loading
Loading