Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
88e8fae
fix(go): pass OAuth client credentials in generated wire tests (#17391)
fern-api[bot] Aug 20, 2026
d1e5fcc
chore(go): release 1.57.5
github-actions[bot] Aug 20, 2026
395c3b4
fix(cli-generator): patch two lockfile advisories and make releases r…
devin-ai-integration[bot] Aug 20, 2026
c2fcdb3
feat(python): add `stream_abstraction` for SSE event metadata (#17482)
adidavid014 Aug 20, 2026
2dd0089
chore(cli-generator): release 0.35.2
github-actions[bot] Aug 20, 2026
927265d
fix(go): an empty string terminates cursor pagination for aliased cur…
devin-ai-integration[bot] Aug 20, 2026
b10941a
chore(go): release 1.57.6
github-actions[bot] Aug 20, 2026
fa8c77a
chore(python): release 5.29.0
github-actions[bot] Aug 20, 2026
547d1f2
fix(go,python,php,ruby,rust): skip auth matchers on OAuth token endpo…
fern-api[bot] Aug 20, 2026
c3f433b
chore(go): release 1.57.7
github-actions[bot] Aug 20, 2026
9a79a4f
chore(php): release 2.20.3
github-actions[bot] Aug 20, 2026
86a272f
chore(python): release 5.29.1
github-actions[bot] Aug 20, 2026
f7464de
chore(ruby-v2): release 1.23.1
github-actions[bot] Aug 20, 2026
ffc21f8
chore(rust): release 0.46.1
github-actions[bot] Aug 20, 2026
7d1a081
chore(cli-generator): release 0.35.3
github-actions[bot] Aug 20, 2026
be9b751
fix(typescript): honor x-fern-retries disabled at the endpoint level …
fern-api[bot] Aug 20, 2026
4d971f8
fix(ruby): respect endpoint-level disabled retries in ruby-v2 (#17439)
fern-api[bot] Aug 20, 2026
48ef752
fix(php): honor x-fern-retries disabled at the endpoint level (#17433)
fern-api[bot] Aug 20, 2026
0bf26c6
fix(java): honor x-fern-retries disabled at the endpoint level (#17437)
fern-api[bot] Aug 20, 2026
8cf8438
fix(go): honor x-fern-retries disabled at the endpoint level (#17440)
fern-api[bot] Aug 20, 2026
707ab87
fix(rust): disable retries for endpoints with retries disabled (#17434)
fern-api[bot] Aug 20, 2026
60e4578
chore(go): release 1.57.8
github-actions[bot] Aug 20, 2026
95f4721
chore(java): release 4.19.1
github-actions[bot] Aug 20, 2026
0118f60
chore(php): release 2.20.4
github-actions[bot] Aug 20, 2026
eb10467
chore(ruby-v2): release 1.23.2
github-actions[bot] Aug 20, 2026
a0aa989
chore(rust): release 0.46.2
github-actions[bot] Aug 20, 2026
48fef0d
chore(typescript): release 3.88.1
github-actions[bot] Aug 20, 2026
7af06a7
chore(cli-generator): release 0.35.4
github-actions[bot] Aug 20, 2026
bda7a80
fix(swift): do not retry endpoints with retries disabled (#17435)
fern-api[bot] Aug 20, 2026
5d3c63c
chore(swift): release 0.36.1
github-actions[bot] Aug 20, 2026
c285c7a
fix(csharp): honor x-fern-retries disabled at the endpoint level (#17…
fern-api[bot] Aug 20, 2026
324d2d8
chore(csharp): release 2.83.1
github-actions[bot] Aug 20, 2026
c53bafb
fix(cli): bump @fern-api/fdr-sdk to pick up the basepath product-slug…
Ryan-Amirthan Aug 20, 2026
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
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ updates:
directory: "/generators/go"
schedule:
interval: "weekly"
# The CLI generator vendors a Rust SDK whose Cargo.lock ships verbatim into
# every generated CLI, so an advisory here reaches every consumer.
- package-ecosystem: "cargo"
directory: "/generators/cli/sdk"
schedule:
interval: "weekly"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# yaml-language-server: $schema=../../../../fern-changes-yml.schema.json

- summary: |
Refresh the vendored Cargo.lock to clear two advisories that shipped into
every generated CLI: h2 0.4.15 -> 0.4.16 (RUSTSEC-2026-0258) and
webbrowser 1.2.1 -> 1.2.4 (RUSTSEC-2026-0257).
type: fix
- summary: |
Make the generated release workflow re-runnable. The final step ran a bare
`gh release create`, so a release that already existed for the tag - cut
from the GitHub UI, or a re-run of a failed `host` job - failed the release
after every build had already succeeded. It now edits and re-uploads into
an existing release, and only creates when there is none.
type: fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- summary: |
Includes Rust SDK Generator 0.46.1 updates.
type: fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- summary: |
Includes Rust SDK Generator 0.46.2 updates.
type: fix
59 changes: 46 additions & 13 deletions generators/cli/sdk/Cargo.lock

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

18 changes: 18 additions & 0 deletions generators/cli/src/__test__/emitReleaseWorkflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,24 @@ describe("constructReleaseWorkflowYaml", () => {
expect(yaml).not.toContain("HOMEBREW_TAP_TOKEN");
});

it("updates an existing GitHub release and overwrites its assets", () => {
const yaml = constructReleaseWorkflowYaml({});
const releaseStep = yaml.slice(yaml.indexOf("gh release view"));

expect(releaseStep).toContain('gh release view "${{ needs.plan.outputs.tag }}"');
expect(releaseStep).toContain("gh release edit");
expect(releaseStep).toContain("--prerelease=false");
// A release drafted in the UI must be published, not just filled in —
// and its tag must land on the commit that was actually built.
expect(releaseStep).toContain("--draft=false");
for (const edit of releaseStep.split("\n").filter((line) => line.includes("gh release edit"))) {
expect(edit).toContain('--target "$RELEASE_COMMIT"');
}
expect(releaseStep).toContain("gh release upload");
expect(releaseStep).toContain("--clobber");
expect(releaseStep).toContain("gh release create");
});

describe("with homebrew configured", () => {
const yaml = constructReleaseWorkflowYaml({ homebrew: { tap: "acme/homebrew-tap" } });

Expand Down
17 changes: 16 additions & 1 deletion generators/cli/src/emitReleaseWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,5 +626,20 @@ jobs:
# Write and read notes from a file to avoid quoting breaking things
echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt

gh release create "\${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
# Re-runs and pre-created releases must not fail after a successful build.
# --draft=false publishes a release that was drafted in the UI:
# gh release view finds drafts, so without it the assets upload into
# a release nobody can see and the job still reports success. A draft
# has no tag yet, so --target decides where that tag lands; for an
# already-published release the tag exists and GitHub ignores it.
if gh release view "\${{ needs.plan.outputs.tag }}" > /dev/null 2>&1; then
if [[ -n "$PRERELEASE_FLAG" ]]; then
gh release edit "\${{ needs.plan.outputs.tag }}" --title "$ANNOUNCEMENT_TITLE" --target "$RELEASE_COMMIT" --notes-file "$RUNNER_TEMP/notes.txt" --draft=false --prerelease
else
gh release edit "\${{ needs.plan.outputs.tag }}" --title "$ANNOUNCEMENT_TITLE" --target "$RELEASE_COMMIT" --notes-file "$RUNNER_TEMP/notes.txt" --draft=false --prerelease=false
fi
gh release upload "\${{ needs.plan.outputs.tag }}" artifacts/* --clobber
else
gh release create "\${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
fi
`;
30 changes: 30 additions & 0 deletions generators/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: 0.35.4
changelogEntry:
- summary: |
Includes Rust SDK Generator 0.46.2 updates.
type: fix
createdAt: "2026-08-20"
irVersion: 67
- version: 0.35.3
changelogEntry:
- summary: |
Includes Rust SDK Generator 0.46.1 updates.
type: fix
createdAt: "2026-08-20"
irVersion: 67
- version: 0.35.2
changelogEntry:
- summary: |
Refresh the vendored Cargo.lock to clear two advisories that shipped into
every generated CLI: h2 0.4.15 -> 0.4.16 (RUSTSEC-2026-0258) and
webbrowser 1.2.1 -> 1.2.4 (RUSTSEC-2026-0257).
type: fix
- summary: |
Make the generated release workflow re-runnable. The final step ran a bare
`gh release create`, so a release that already existed for the tag - cut
from the GitHub UI, or a re-run of a failed `host` job - failed the release
after every build had already succeeded. It now edits and re-uploads into
an existing release, and only creates when there is none.
type: fix
createdAt: "2026-08-20"
irVersion: 67
- version: 0.35.1
changelogEntry:
- summary: |
Expand Down
7 changes: 7 additions & 0 deletions generators/csharp/base/src/asIs/BaseRequest.Template.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ internal abstract record BaseRequest
internal Dictionary<string, string> Headers { get; init; } = new(StringComparer.OrdinalIgnoreCase);

internal IRequestOptions? Options { get; init; }
<% if (hasRetriesDisabledEndpoints) { %>

/// <summary>
/// Whether retries are disabled for this request, regardless of the client or request options.
/// </summary>
internal bool RetriesDisabled { get; init; }
<% } %>

internal abstract HttpContent? CreateContent();

Expand Down
12 changes: 7 additions & 5 deletions generators/csharp/base/src/asIs/RawClient.Template.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ internal partial class RawClient(ClientOptions clientOptions)

var httpRequest = await CreateHttpRequestAsync(request).ConfigureAwait(false);
// Send the request.
return await SendWithRetriesAsync(httpRequest, request.Options, cts.Token)
return await SendWithRetriesAsync(httpRequest, request.Options, cts.Token<% if (hasRetriesDisabledEndpoints) { %>, request.RetriesDisabled<% } %>)
.ConfigureAwait(false);
}

internal async global::System.Threading.Tasks.Task<global::<%= namespace%>.ApiResponse> SendRequestAsync(
HttpRequestMessage request,
IRequestOptions? options,
CancellationToken cancellationToken = default
CancellationToken cancellationToken = default<% if (hasRetriesDisabledEndpoints) { %>,
bool retriesDisabled = false<% } %>
)
{
// Apply the request timeout.
Expand All @@ -61,7 +62,7 @@ internal partial class RawClient(ClientOptions clientOptions)
cts.CancelAfter(timeout);

// Send the request.
return await SendWithRetriesAsync(request, options, cts.Token).ConfigureAwait(false);
return await SendWithRetriesAsync(request, options, cts.Token<% if (hasRetriesDisabledEndpoints) { %>, retriesDisabled<% } %>).ConfigureAwait(false);
}

private static async global::System.Threading.Tasks.Task<HttpRequestMessage> CloneRequestAsync(
Expand Down Expand Up @@ -142,11 +143,12 @@ internal partial class RawClient(ClientOptions clientOptions)
private async global::System.Threading.Tasks.Task<global::<%= namespace%>.ApiResponse> SendWithRetriesAsync(
HttpRequestMessage request,
IRequestOptions? options,
CancellationToken cancellationToken
CancellationToken cancellationToken<% if (hasRetriesDisabledEndpoints) { %>,
bool retriesDisabled<% } %>
)
{
var httpClient = options?.HttpClient ?? Options.HttpClient;
var maxRetries = Math.Max(0, options?.MaxRetries ?? Options.MaxRetries);
var maxRetries = <% if (hasRetriesDisabledEndpoints) { %>retriesDisabled ? 0 : <% } %>Math.Max(0, options?.MaxRetries ?? Options.MaxRetries);
var isRetryableContent = IsRetryableContent(request);

if (!isRetryableContent || maxRetries == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,38 @@ public async SystemTask SendRequestAsync_ShouldRetry_WhenHandlerDisposesRequestC
}
}

<% if (hasRetriesDisabledEndpoints) { %>
[Test]
public async SystemTask SendRequestAsync_ShouldNotRetry_WhenRetriesDisabledForEndpoint()
{
_server
.Given(WireMockRequest.Create().WithPath("/test").UsingGet())
.RespondWith(
WireMockResponse
.Create()
.WithStatusCode(<%= retryStatusCodes === "recommended" ? 503 : 500 %>)
.WithBody("Failure")
);

var request = new <%= namespaces.qualifiedCore %>.EmptyRequest
{
BaseUrl = _baseUrl,
Method = HttpMethod.Get,
Path = "/test",
Options = new global::<%= namespace%>.RequestOptions { MaxRetries = MaxRetries },
RetriesDisabled = true,
};

var response = await _rawClient.SendRequestAsync(request);

using (Assert.EnterMultipleScope())
{
Assert.That(response.StatusCode, Is.EqualTo(<%= retryStatusCodes === "recommended" ? 503 : 500 %>));
Assert.That(_server.LogEntries, Has.Count.EqualTo(1));
}
}
<% } %>

[TearDown]
public void TearDown()
{
Expand Down
17 changes: 17 additions & 0 deletions generators/csharp/base/src/context/GeneratorContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,23 @@ export abstract class GeneratorContext extends AbstractGeneratorContext {
);
}

/**
* Checks if the API has any endpoints that disable retries.
* @returns True if the API has any endpoints configured with `retries: { disabled: true }`.
*/
public get hasRetriesDisabledEndpoints(): boolean {
return Object.values(this.ir.services).some((service) =>
service.endpoints.some((endpoint) => this.areRetriesDisabled(endpoint))
);
}

/**
* Checks if the endpoint disables retries, regardless of the client or per-request options.
*/
public areRetriesDisabled(endpoint: HttpEndpoint): boolean {
return endpoint.retries?.disabled === true;
}

/**
* Checks if the endpoint has a resumable SSE streaming result.
*/
Expand Down
Loading
Loading