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
2 changes: 1 addition & 1 deletion docker/seed/Dockerfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ RUN git config --global user.email "build@example.com" && \
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /out/tsgolint ./cmd/tsgolint && \
ls -la /out/tsgolint

FROM node:24.16.0-trixie-slim
FROM node:24.17.0-trixie-slim

ENV PNPM_STORE_PATH=/.pnpm-cache
ENV YARN_CACHE_FOLDER=/.yarn-cache
Expand Down
2 changes: 1 addition & 1 deletion generators/cli/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.16-trixie-slim
FROM node:24.17-trixie-slim
RUN apt-get update \
&& apt-get -y --no-install-recommends dist-upgrade \
&& apt-get -y --no-install-recommends install rustfmt \
Expand Down
4 changes: 2 additions & 2 deletions generators/csharp/model/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.16-alpine3.23 AS node
FROM node:24.17-alpine3.23 AS node
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine

ENV YARN_CACHE_FOLDER=/.yarn
Expand All @@ -24,7 +24,7 @@ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx

# Patch brace-expansion to 5.0.6 (GHSA-jxxr-4gwj-5jf2; node:24.16 vendors 5.0.5).
# Patch brace-expansion to 5.0.6 (GHSA-jxxr-4gwj-5jf2; node:24.17 vendors 5.0.5).
RUN cd /usr/local/lib/node_modules/npm/node_modules && \
npm pack brace-expansion@5.0.6 && \
rm -rf brace-expansion && \
Expand Down
2 changes: 1 addition & 1 deletion generators/csharp/sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Node.js
FROM node:24.16-alpine3.23 AS node
FROM node:24.17-alpine3.23 AS node

FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine

Expand Down
2 changes: 1 addition & 1 deletion generators/go/model/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.16-alpine3.23 AS node
FROM node:24.17-alpine3.23 AS node

FROM golang:1.26.4-alpine3.23

Expand Down
2 changes: 1 addition & 1 deletion generators/go/sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Build Node CLI
FROM node:24.16-alpine3.23 AS node
FROM node:24.17-alpine3.23 AS node

RUN apk --no-cache add git zip
RUN git config --global user.email "115122769+fern-api[bot]@users.noreply.github.com" && \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,11 @@ export class EndpointSnippetGenerator {
filePropertyInfo: FilePropertyInfo;
}): java.BuilderParameter[] {
if (this.context.shouldInlineFileProperties()) {
return [...filePropertyInfo.fileFields, ...filePropertyInfo.bodyPropertyFields];
// Body properties are emitted before file properties so that the snippet's builder call
// follows the generated staged-builder order. The dynamic IR does not expose whether a
// file is optional, so file builder parameters carry a concrete (non-optional) value and
// would otherwise be treated as required and ordered ahead of a required body property.
return [...filePropertyInfo.bodyPropertyFields, ...filePropertyInfo.fileFields];
}
return filePropertyInfo.bodyPropertyFields;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,8 @@ exports[`snippets (default) > file-upload > 'POST /' 1`] = `
"package com.example.usage;

import com.acme.acme.AcmeAcmeClient;
import com.acme.acme.core.FileStream;
import com.acme.acme.types.MyRequest;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.io.File;

AcmeAcmeClient client = AcmeAcmeClient
.builder()
Expand All @@ -356,10 +354,16 @@ client.service().post(
MyRequest
.builder()
.file(
new FileStream(ByteArrayInputStream("Hello, world!".getBytes(StandardCharsets.UTF_8)))
new File("Hello, world!")
)
.fileList(
new FileStream(ByteArrayInputStream("First".getBytes(StandardCharsets.UTF_8)))
new File("First")
)
.maybeFile(
new File("path/to/file")
)
.maybeFileList(
new File("path/to/file")
)
.build()
);
Expand All @@ -370,10 +374,8 @@ exports[`snippets (default) > file-upload > 'POST /just-file' 1`] = `
"package com.example.usage;

import com.acme.acme.AcmeAcmeClient;
import com.acme.acme.core.FileStream;
import com.acme.acme.types.JustFileRequest;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.io.File;

AcmeAcmeClient client = AcmeAcmeClient
.builder()
Expand All @@ -383,7 +385,7 @@ client.service().justFile(
JustFileRequest
.builder()
.file(
new FileStream(ByteArrayInputStream("Hello, world!".getBytes(StandardCharsets.UTF_8)))
new File("Hello, world!")
)
.build()
);
Expand All @@ -394,10 +396,8 @@ exports[`snippets (default) > file-upload > 'POST /just-file-with-query-params'
"package com.example.usage;

import com.acme.acme.AcmeAcmeClient;
import com.acme.acme.core.FileStream;
import com.acme.acme.types.JustFileWithQueryParamsRequest;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.io.File;

AcmeAcmeClient client = AcmeAcmeClient
.builder()
Expand All @@ -408,7 +408,7 @@ client.service().justFileWithQueryParams(
.builder()
.integer(42)
.file(
new FileStream(ByteArrayInputStream("Hello, world!".getBytes(StandardCharsets.UTF_8)))
new File("Hello, world!")
)
.maybeString("exists")
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene
java.codeblock((writer) => {
writer.write("new ");
writer.writeNode(this.getFileStreamClassReference());
writer.write("(");
writer.write("(new ");
writer.writeNode(this.getByteArrayInputStreamClassReference());
writer.write("(");
writer.writeNode(java.TypeLiteral.string(content));
Expand All @@ -202,6 +202,25 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene
);
}

public getJavaIoFileFromString(content: string): java.TypeLiteral {
return java.TypeLiteral.reference(
java.codeblock((writer) => {
writer.write("new ");
writer.writeNode(this.getJavaIoFileClassReference());
writer.write("(");
writer.writeNode(java.TypeLiteral.string(content));
writer.write(")");
})
);
}

public getJavaIoFileClassReference(): java.ClassReference {
return java.classReference({
name: "File",
packageName: "java.io"
});
}

public getFileStreamClassReference(): java.ClassReference {
return java.classReference({
name: "FileStream",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export class FilePropertyMapper {
record: Record<string, unknown>;
}): java.TypeLiteral {
const fileValue = this.context.getSingleFileValue({ property, record });
if (this.context.shouldInlineFileProperties()) {
// In inline-file-properties mode the request builder field is typed as `java.io.File`,
// so render a `java.io.File` literal. A required file is a staged builder step that
// cannot be omitted, so fall back to a placeholder path when the example has no value.
return this.context.getJavaIoFileFromString(fileValue ?? "path/to/file");
}
if (fileValue == null) {
return java.TypeLiteral.nop();
}
Expand All @@ -77,6 +83,13 @@ export class FilePropertyMapper {
record: Record<string, unknown>;
}): java.TypeLiteral {
const fileValues = this.context.getFileArrayValues({ property, record });
if (this.context.shouldInlineFileProperties()) {
// In inline-file-properties mode the request builder field is typed as `java.io.File`,
// so render a `java.io.File` literal (the Java SDK does not support file arrays, so a
// single value is emitted) and fall back to a placeholder when no example value exists.
const fileValue = fileValues?.[0] ?? "path/to/file";
return this.context.getJavaIoFileFromString(fileValue);
}
if (fileValues == null) {
return java.TypeLiteral.nop();
}
Expand Down
2 changes: 1 addition & 1 deletion generators/java/sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Stage 1: Java V2
# Non-slim trixie because the patch RUN steps below shell out to curl and tar.
FROM node:24.16-trixie AS node
FROM node:24.17-trixie AS node
COPY generators/java-v2/sdk/dist/cli.cjs /dist/cli.cjs
COPY generators/java-v2/sdk/features.yml /assets/features.yml

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json

- summary: |
Dynamic snippets now compile for endpoints that inline file properties into the
request builder (`inline-file-properties: true`). File builder fields are typed as
`java.io.File`, so snippets render `new File("path/to/file")` (with a placeholder
path when the example omits a file) instead of a `FileStream`, and body properties
are emitted before file properties so a required body property's staged-builder step
precedes an optional file step.
type: fix
12 changes: 12 additions & 0 deletions generators/java/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 4.10.2
changelogEntry:
- summary: |
Dynamic snippets now compile for endpoints that inline file properties into the
request builder (`inline-file-properties: true`). File builder fields are typed as
`java.io.File`, so snippets render `new File("path/to/file")` (with a placeholder
path when the example omits a file) instead of a `FileStream`, and body properties
are emitted before file properties so a required body property's staged-builder step
precedes an optional file step.
type: fix
createdAt: "2026-06-18"
irVersion: 66
- version: 4.10.1
changelogEntry:
- summary: |
Expand Down
2 changes: 1 addition & 1 deletion generators/openapi/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.16-alpine3.23
FROM node:24.17-alpine3.23
RUN apk update && apk upgrade --no-cache \
&& rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
COPY dist /dist
Expand Down
2 changes: 1 addition & 1 deletion generators/php/model/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.16-alpine3.23 AS node
FROM node:24.17-alpine3.23 AS node
FROM composer:2.9.7

ENV YARN_CACHE_FOLDER=/.yarn
Expand Down
2 changes: 1 addition & 1 deletion generators/php/sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.16-alpine3.23 AS node
FROM node:24.17-alpine3.23 AS node
FROM composer:2.9.7

ENV YARN_CACHE_FOLDER=/.yarn
Expand Down
3 changes: 2 additions & 1 deletion generators/python-v2/pydantic-model/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Bundled npm globals are removed below so npm's JS dependencies cannot be
# flagged or invoked at runtime — this image only runs the bundled CLI via node.
FROM node:24.16-trixie-slim
FROM node:24.17-trixie-slim
# Security update 2026-05-18: trixie's krb5 (1.21.3-5), curl (8.14.1),
# and gnutls28 (3.8.9) are still vulnerable to CVE-2026-40355,
# CVE-2026-40356 (krb5), CVE-2026-1965/4873/5545/6253/6429 (curl),
Expand All @@ -20,6 +20,7 @@ RUN echo "Types: deb" > /etc/apt/sources.list.d/sid.sources \
&& apt-get install -y --no-install-recommends -t sid \
libkrb5-3 libkrb5support0 libk5crypto3 libgssapi-krb5-2 \
curl libcurl4t64 libgnutls30t64 \
libssh2-1t64 libtasn1-6 \
perl-base perl \
&& rm -f /etc/apt/sources.list.d/sid.sources /etc/apt/preferences.d/sid-low \
&& rm -rf /var/lib/apt/lists/*
Expand Down
5 changes: 3 additions & 2 deletions generators/python-v2/sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.16-trixie-slim
FROM node:24.17-trixie-slim
# Security update 2026-05-18: trixie's krb5 (1.21.3-5), curl (8.14.1),
# and gnutls28 (3.8.9) are still vulnerable to CVE-2026-40355,
# CVE-2026-40356 (krb5), CVE-2026-1965/4873/5545/6253/6429 (curl),
Expand All @@ -18,10 +18,11 @@ RUN echo "Types: deb" > /etc/apt/sources.list.d/sid.sources \
&& apt-get install -y --no-install-recommends -t sid \
libkrb5-3 libkrb5support0 libk5crypto3 libgssapi-krb5-2 \
curl libcurl4t64 libgnutls30t64 \
libssh2-1t64 libtasn1-6 \
perl-base perl \
&& rm -f /etc/apt/sources.list.d/sid.sources /etc/apt/preferences.d/sid-low \
&& rm -rf /var/lib/apt/lists/*
# Patch brace-expansion to 5.0.6 (GHSA-jxxr-4gwj-5jf2; node:24.16 vendors 5.0.5).
# Patch brace-expansion to 5.0.6 (GHSA-jxxr-4gwj-5jf2; node:24.17 vendors 5.0.5).
RUN cd /usr/local/lib/node_modules/npm/node_modules && \
npm pack brace-expansion@5.0.6 && \
rm -rf brace-expansion && \
Expand Down
3 changes: 2 additions & 1 deletion generators/python/sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Copy Node.js from official image
FROM node:24.16-trixie-slim AS node
FROM node:24.17-trixie-slim AS node

# Stage 2: Base Python image with dependencies
# Pinned to the latest 3.13 patch release to pull in CPython security fixes
Expand Down Expand Up @@ -33,6 +33,7 @@ RUN echo "Types: deb" > /etc/apt/sources.list.d/sid.sources \
libkrb5-3 libkrb5support0 libk5crypto3 libgssapi-krb5-2 \
curl libcurl4t64 libgnutls30t64 \
libexpat1 \
libssh2-1t64 libtasn1-6 \
perl-base perl \
&& rm -f /etc/apt/sources.list.d/sid.sources /etc/apt/preferences.d/sid-low \
&& rm -rf /var/lib/apt/lists/*
Expand Down
4 changes: 2 additions & 2 deletions generators/rust/model/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN rustup component add rustfmt \
&& cp "$RUSTFMT" /rustfmt/rustfmt \
&& cp "$(dirname "$RUSTFMT")/../lib"/librustc_driver-*.so /rustfmt/lib/

FROM node:24.16-alpine3.23
FROM node:24.17-alpine3.23

RUN apk update && apk upgrade --no-cache

Expand All @@ -24,7 +24,7 @@ ENV PATH=/usr/local/lib/rustfmt:$PATH \
LD_LIBRARY_PATH=/usr/local/lib/rustfmt/lib
RUN rustfmt --version

# Patch brace-expansion to 5.0.6 (GHSA-jxxr-4gwj-5jf2; node:24.16 vendors 5.0.5).
# Patch brace-expansion to 5.0.6 (GHSA-jxxr-4gwj-5jf2; node:24.17 vendors 5.0.5).
RUN cd /usr/local/lib/node_modules/npm/node_modules && \
npm pack brace-expansion@5.0.6 && \
rm -rf brace-expansion && \
Expand Down
2 changes: 1 addition & 1 deletion generators/rust/sdk/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
FROM rust:1.91-alpine3.23 AS rust
RUN rustup component add rustfmt

FROM node:24.16-alpine3.23
FROM node:24.17-alpine3.23

RUN apk update && apk upgrade --no-cache

Expand Down
4 changes: 2 additions & 2 deletions generators/swift/model/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM node:24.16-alpine3.23
FROM node:24.17-alpine3.23

RUN apk update && apk upgrade --no-cache && apk --no-cache add curl

# Patch npm's bundled picomatch@4.0.3 -> 4.0.4 (GHSA-c2c7-rcm5-vvqj,
# GHSA-3v7f-55p6-f55p) and brace-expansion@5.0.4 -> 5.0.6
# (GHSA-jxxr-4gwj-5jf2) in node:24.16's bundled npm 11.12.1.
# (GHSA-jxxr-4gwj-5jf2) in node:24.17's bundled npm 11.12.1.
RUN for dir in \
/usr/local/lib/node_modules/npm/node_modules/picomatch \
/usr/local/lib/node_modules/npm/node_modules/tinyglobby/node_modules/picomatch; do \
Expand Down
4 changes: 2 additions & 2 deletions generators/swift/sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.16-alpine3.23
FROM node:24.17-alpine3.23

RUN apk update && apk upgrade --no-cache
RUN apk --no-cache add bash curl git zip
Expand All @@ -8,7 +8,7 @@ RUN git config --global user.email "115122769+fern-api[bot]@users.noreply.github
# Patch picomatch to 4.0.4 (GHSA-c2c7-rcm5-vvqj ReDoS via extglob quantifiers,
# GHSA-3v7f-55p6-f55p method injection in POSIX character classes) and
# brace-expansion to 5.0.6 (GHSA-jxxr-4gwj-5jf2 zero-step sequence hangs)
# in npm's bundled node_modules. node:24.16 ships npm 11.12.1 with
# in npm's bundled node_modules. node:24.17 ships npm 11.12.1 with
# picomatch@4.0.3 and brace-expansion@5.0.4.
RUN for dir in \
/usr/local/lib/node_modules/npm/node_modules/picomatch \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- summary: |
Fix endpoints with a stream-parameter response generating a `JSONValue` return
type, which broke generated wire tests that compared the result against the
concrete response model. The Swift SDK does not yet implement response
streaming, so these endpoints are now generated against their non-streaming
response shape (e.g. the declared JSON response model).
type: fix
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,22 @@ export class EndpointMethodGenerator {
text: () => this.referencer.referenceSwiftType("String"),
bytes: () => this.referencer.referenceAsIsType("JSONValue"), // TODO(kafkas): Handle bytes responses
streaming: () => this.referencer.referenceAsIsType("JSONValue"), // TODO(kafkas): Handle streaming responses
streamParameter: () => this.referencer.referenceAsIsType("JSONValue"), // TODO(kafkas): Handle stream parameter responses
// The Swift SDK does not yet implement response streaming, so a stream-parameter
// endpoint is generated against its non-streaming response shape.
streamParameter: (resp) => this.getMethodReturnTypeForNonStreamResponse(resp.nonStreamResponse),
_other: () => this.referencer.referenceAsIsType("JSONValue")
});
}

private getMethodReturnTypeForNonStreamResponse(
nonStreamResponse: FernIr.NonStreamHttpResponseBody
): swift.TypeReference {
return nonStreamResponse._visit({
json: (resp) =>
this.sdkGeneratorContext.getSwiftTypeReferenceFromScope(resp.responseBodyType, this.parentClassSymbol),
fileDownload: () => this.referencer.referenceFoundationType("Data"),
text: () => this.referencer.referenceSwiftType("String"),
bytes: () => this.referencer.referenceAsIsType("JSONValue"), // TODO(kafkas): Handle bytes responses
_other: () => this.referencer.referenceAsIsType("JSONValue")
});
}
Expand Down
Loading
Loading