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
16 changes: 10 additions & 6 deletions generators/java/sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Stage 1: Java V2
# Updated: WebSocket feature support added
FROM node:23.11.1-bookworm AS node
# Updated Node.js to 24.13.0 to fix CVE-2025-59465, CVE-2025-55131, CVE-2025-55130, CVE-2026-21637, CVE-2025-59466, CVE-2025-55132
FROM node:24.13.0-bookworm AS node
COPY generators/java-v2/sdk/dist/cli.cjs /dist/cli.cjs
COPY generators/java-v2/sdk/features.yml /assets/features.yml

# Stage 2: Java V1
# Apple Silicon: FROM bitnami/gradle:latest
# Note: OpenJDK CVEs (CVE-2026-21945, CVE-2026-21932, CVE-2026-21925, CVE-2026-21933) require 11.0.30 which is not yet available
FROM gradle:jdk11-jammy

# Remove vulnerable jars from Gradle base image - not used by this generator
Expand All @@ -30,6 +32,7 @@ RUN rm -f /opt/gradle/lib/plugins/org.eclipse.jgit-*.jar \
# CVE-2025-64720, CVE-2025-65018, CVE-2025-64505, CVE-2025-64506, CVE-2025-66293: libpng16-16 vulnerabilities
# CVE-2021-46848, CVE-2025-13151: libtasn1-6 vulnerabilities
# CVE-2025-68973: gnupg/gpg vulnerabilities
# CVE-2025-15467, CVE-2026-22796, CVE-2026-22795, CVE-2025-69420, CVE-2025-69419, CVE-2025-68160, CVE-2025-69421, CVE-2025-69418: openssl/libssl3 vulnerabilities
# Use purge instead of remove to fully remove packages from dpkg database
# Add retry logic for apt-get update to handle transient mirror sync issues
RUN set -eux; \
Expand All @@ -46,7 +49,8 @@ RUN set -eux; \
done; \
apt-get install -y --only-upgrade libpng16-16 libtasn1-6 \
dirmngr gnupg gnupg-l10n gnupg-utils gpg gpg-agent \
gpg-wks-client gpg-wks-server gpgconf gpgsm gpgv && \
gpg-wks-client gpg-wks-server gpgconf gpgsm gpgv \
openssl libssl3 && \
apt-get purge -y --allow-remove-essential \
python3.10 python3.10-minimal \
python3 python3-minimal \
Expand Down Expand Up @@ -81,14 +85,14 @@ RUN cd /usr/local/lib/node_modules/npm/node_modules && \
mv package glob && \
rm glob-11.1.0.tgz

# Patch npm's bundled tar to 7.5.3 to fix CVE-2026-23745 (path traversal via hardlinks/symlinks)
# Patch npm's bundled tar to 7.5.7 to fix GHSA-34x7-hfp2-rc4v, GHSA-r6q2-hw4h-h46w (path traversal via hardlinks/symlinks)
# Note: We use curl instead of npm pack because npm pack depends on tar itself
RUN cd /usr/local/lib/node_modules/npm/node_modules && \
rm -rf tar && \
curl -sL https://registry.npmjs.org/tar/-/tar-7.5.3.tgz -o tar-7.5.3.tgz && \
tar -xzf tar-7.5.3.tgz && \
curl -sL https://registry.npmjs.org/tar/-/tar-7.5.7.tgz -o tar-7.5.7.tgz && \
tar -xzf tar-7.5.7.tgz && \
mv package tar && \
rm tar-7.5.3.tgz
rm tar-7.5.7.tgz

# Copy Node CLI from first stage and rename it /bin/java-v2
COPY --from=node /dist/cli.cjs /bin/java-v2
Expand Down
6 changes: 3 additions & 3 deletions generators/java/versions.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Run ./gradlew --write-locks to regenerate this file
ch.qos.logback:logback-classic:1.3.16 (5 constraints: be3e4b23)
ch.qos.logback:logback-core:1.3.16 (5 constraints: bd46f351)
ch.qos.logback:logback-classic:1.5.25 (5 constraints: c03ea424)
ch.qos.logback:logback-core:1.5.25 (5 constraints: bf464c53)
com.atlassian.commonmark:commonmark:0.12.1 (1 constraints: 36052a3b)
com.fasterxml.jackson:jackson-bom:2.18.2 (9 constraints: 399efb2b)
com.fasterxml.jackson.core:jackson-annotations:2.18.2 (8 constraints: 307798b9)
Expand Down Expand Up @@ -46,7 +46,7 @@ org.glassfish.jersey.core:jersey-common:2.35 (4 constraints: ce3826bc)
org.immutables:value:2.8.8 (5 constraints: b93e4b0c)
org.jetbrains:annotations:13.0 (1 constraints: df0e795c)
org.jetbrains.kotlin:kotlin-stdlib:2.2.20 (2 constraints: f019d436)
org.slf4j:slf4j-api:2.0.7 (6 constraints: ad4c80b3)
org.slf4j:slf4j-api:2.0.17 (6 constraints: d64c29e0)
org.springframework:spring-beans:6.1.14 (5 constraints: 484870a3)
org.springframework:spring-core:6.1.14 (6 constraints: f556bae3)
org.springframework:spring-jcl:6.1.14 (5 constraints: b34866ee)
Expand Down
6 changes: 4 additions & 2 deletions generators/java/versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ com.google.guava:guava = 32.0.1-jre
org.springframework:spring-web = 6.1.14

# slf4j
org.slf4j:slf4j-api = 1.7.36
# Updated to 2.0.16 for compatibility with logback 1.5.x
org.slf4j:slf4j-api = 2.0.16

# okhttp
com.squareup.okhttp3:okhttp = 5.2.1

# misc
com.palantir.common:streams = 2.0.0
ch.qos.logback:logback-classic = 1.3.16
# Updated to 1.5.25 to fix GHSA-qqpg-mvqg-649v (class instantiation vulnerability)
ch.qos.logback:logback-classic = 1.5.25
com.atlassian.commonmark:* = 0.12.1
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class GeneratedEnumTypeImpl<Context extends BaseContext>
private static readonly VISIT_PROPERTTY_NAME = "_visit";
private static readonly VISIT_VALUE_PARAMETER_NAME = "value";
private static readonly VISITOR_PARAMETER_NAME = "visitor";
private static readonly VALUES_SUFFIX = "Values";

public readonly type = "enum";
private includeEnumUtils: boolean;
Expand All @@ -44,10 +45,13 @@ export class GeneratedEnumTypeImpl<Context extends BaseContext>
this.enableForwardCompatibleEnums = enableForwardCompatibleEnums;
}

private getValuesConstName(): string {
return `${this.typeName}${GeneratedEnumTypeImpl.VALUES_SUFFIX}`;
}

private generateEnumType(context: Context): TypeAliasDeclarationStructure {
const typeofConst = this.includeEnumUtils
? `Omit<typeof ${this.typeName}, "${GeneratedEnumTypeImpl.VISIT_PROPERTTY_NAME}">`
: `typeof ${this.typeName}`;
// When visitor is enabled, reference the Values const to avoid circular reference
const typeofConst = this.includeEnumUtils ? `typeof ${this.getValuesConstName()}` : `typeof ${this.typeName}`;
const baseType = `${typeofConst}[keyof ${typeofConst}]`;
const shouldWidenType = this.enableForwardCompatibleEnums;
const type: TypeAliasDeclarationStructure = {
Expand Down Expand Up @@ -84,14 +88,20 @@ export class GeneratedEnumTypeImpl<Context extends BaseContext>
public generateStatements(
context: Context
): string | WriterFunction | (string | WriterFunction | StatementStructures)[] {
const statements: (string | WriterFunction | StatementStructures)[] = [
this.generateConst(context),
this.generateEnumType(context)
];
const statements: (string | WriterFunction | StatementStructures)[] = [];

if (this.includeEnumUtils) {
// When visitor is enabled, generate an intermediate Values const,
// then the type alias, then the const with visitor to avoid circular reference
statements.push(this.generateValuesConst(context));
statements.push(this.generateEnumType(context));
statements.push(this.generateConstWithVisitor(context));
statements.push(this.generateModule());
} else {
statements.push(this.generateConst(context));
statements.push(this.generateEnumType(context));
}

return statements;
}

Expand All @@ -102,6 +112,204 @@ export class GeneratedEnumTypeImpl<Context extends BaseContext>
return getTextOfTsNode(ts.factory.createJSDocComment(docs)) + "\n";
}

private getEnumValueProperties(): ts.PropertyAssignment[] {
return this.shape.values.map((value) =>
ts.factory.createPropertyAssignment(
ts.factory.createIdentifier(this.printDocs(value.docs) + getPropertyKey(this.getEnumValueName(value))),
ts.factory.createStringLiteral(value.name.wireValue)
)
);
}

private generateValuesConst(context: Context): VariableStatementStructure {
const docs = this.getDocs({ context });
return {
kind: StructureKind.VariableStatement,
declarationKind: VariableDeclarationKind.Const,
isExported: false,
declarations: [
{
name: this.getValuesConstName(),
initializer: getTextOfTsNode(
ts.factory.createAsExpression(
ts.factory.createObjectLiteralExpression(this.getEnumValueProperties(), true),
ts.factory.createTypeReferenceNode("const")
)
)
}
],
docs: docs ? [docs] : undefined
};
}

private generateConstWithVisitor(context: Context): VariableStatementStructure {
const visitProperty = ts.factory.createPropertyAssignment(
GeneratedEnumTypeImpl.VISIT_PROPERTTY_NAME,
ts.factory.createArrowFunction(
undefined,
[
ts.factory.createTypeParameterDeclaration(
undefined,
GeneratedEnumTypeImpl.VISITOR_RETURN_TYPE_PARAMETER,
undefined,
undefined
)
],
[
ts.factory.createParameterDeclaration(
undefined,
undefined,
GeneratedEnumTypeImpl.VISIT_VALUE_PARAMETER_NAME,
undefined,
ts.factory.createTypeReferenceNode(this.typeName, undefined)
),
ts.factory.createParameterDeclaration(
undefined,
undefined,
GeneratedEnumTypeImpl.VISITOR_PARAMETER_NAME,
undefined,
ts.factory.createTypeReferenceNode(
ts.factory.createQualifiedName(
ts.factory.createIdentifier(this.typeName),
GeneratedEnumTypeImpl.VISITOR_INTERFACE_NAME
),
[
ts.factory.createTypeReferenceNode(
GeneratedEnumTypeImpl.VISITOR_RETURN_TYPE_PARAMETER,
undefined
)
]
)
)
],
ts.factory.createTypeReferenceNode(GeneratedEnumTypeImpl.VISITOR_RETURN_TYPE_PARAMETER, undefined),
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
ts.factory.createBlock(
[
ts.factory.createSwitchStatement(
ts.factory.createIdentifier(GeneratedEnumTypeImpl.VISIT_VALUE_PARAMETER_NAME),
ts.factory.createCaseBlock([
...this.shape.values.map((enumValue) =>
ts.factory.createCaseClause(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier(this.typeName),
this.getEnumValueName(enumValue)
),
[
ts.factory.createReturnStatement(
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier(
GeneratedEnumTypeImpl.VISITOR_PARAMETER_NAME
),
this.getEnumValueVisitPropertyName(enumValue)
),
undefined,
[]
)
)
]
)
),
ts.factory.createDefaultClause([
ts.factory.createReturnStatement(
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier(
GeneratedEnumTypeImpl.VISITOR_PARAMETER_NAME
),
GeneratedEnumTypeImpl.OTHER_VISITOR_METHOD_NAME
),
undefined,
[]
)
)
])
])
)
],
true
)
)
);

// Build the explicit type annotation for isolatedDeclarations compatibility:
// typeof ValuesConst & { _visit: <R>(value: EnumName, visitor: EnumName.Visitor<R>) => R }
const visitMethodType = ts.factory.createFunctionTypeNode(
[
ts.factory.createTypeParameterDeclaration(
undefined,
GeneratedEnumTypeImpl.VISITOR_RETURN_TYPE_PARAMETER,
undefined,
undefined
)
],
[
ts.factory.createParameterDeclaration(
undefined,
undefined,
GeneratedEnumTypeImpl.VISIT_VALUE_PARAMETER_NAME,
undefined,
ts.factory.createTypeReferenceNode(this.typeName, undefined)
),
ts.factory.createParameterDeclaration(
undefined,
undefined,
GeneratedEnumTypeImpl.VISITOR_PARAMETER_NAME,
undefined,
ts.factory.createTypeReferenceNode(
ts.factory.createQualifiedName(
ts.factory.createIdentifier(this.typeName),
GeneratedEnumTypeImpl.VISITOR_INTERFACE_NAME
),
[
ts.factory.createTypeReferenceNode(
GeneratedEnumTypeImpl.VISITOR_RETURN_TYPE_PARAMETER,
undefined
)
]
)
)
],
ts.factory.createTypeReferenceNode(GeneratedEnumTypeImpl.VISITOR_RETURN_TYPE_PARAMETER, undefined)
);

const constTypeAnnotation = ts.factory.createIntersectionTypeNode([
ts.factory.createTypeQueryNode(ts.factory.createIdentifier(this.getValuesConstName())),
ts.factory.createTypeLiteralNode([
ts.factory.createPropertySignature(
undefined,
GeneratedEnumTypeImpl.VISIT_PROPERTTY_NAME,
undefined,
visitMethodType
)
])
]);

return {
kind: StructureKind.VariableStatement,
declarationKind: VariableDeclarationKind.Const,
isExported: true,
declarations: [
{
name: this.typeName,
type: getTextOfTsNode(constTypeAnnotation),
initializer: getTextOfTsNode(
ts.factory.createObjectLiteralExpression(
[
ts.factory.createSpreadAssignment(
ts.factory.createIdentifier(this.getValuesConstName())
),
visitProperty
],
true
)
)
}
]
};
}

private generateConst(context: Context): VariableStatementStructure {
const constProperties = this.shape.values.map((value) =>
ts.factory.createPropertyAssignment(
Expand Down
10 changes: 10 additions & 0 deletions generators/typescript/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 3.46.2
changelogEntry:
- summary: |
Fix TypeScript circular reference error (TS7022/TS2456) when generating enums with visitor utilities enabled.
The enum type alias now references an intermediate `Values` const instead of using `Omit<typeof EnumName, "_visit">`,
which avoids the circular reference in the type's own initializer.
type: fix
createdAt: "2026-01-30"
irVersion: 63

- version: 3.46.1
changelogEntry:
- summary: |
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 3.54.1
changelogEntry:
- summary: |
Fix dynamic snippets not being generated when using the v3 OpenAPI parser. The workspace is now loaded for dynamic snippet generation even when the v3 parser successfully generates the IR, ensuring that generators.yml configuration is available for snippet generation.
type: fix
createdAt: "2026-01-30"
irVersion: 63

- version: 3.54.0
changelogEntry:
- summary: |
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/docs-resolver/src/DocsDefinitionResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,27 @@ export class DocsDefinitionResolver {
context: this.taskContext,
sourceResolver: new SourceResolverImpl(this.taskContext, workspace)
});
} else if (Object.keys(snippetsConfig).length > 0) {
// When using the v3 parser (ir != null), we still need to load the workspace
// for dynamic snippet generation, which requires access to generators.yml configuration.
// Only load if there's a snippets configuration to avoid unnecessary work.
try {
workspace = await this.getFernWorkspaceForApiSection(item).toFernWorkspace(
{ context: this.taskContext },
{
enableUniqueErrorsPerEndpoint: true,
detectGlobalHeaders: false,
objectQueryParameters: true,
preserveSchemaIds: true
}
);
} catch (error) {
// If we can't load the workspace for dynamic snippets, log a warning but continue
// since the IR was already successfully generated by the v3 parser
this.taskContext.logger.debug(
`Could not load workspace for dynamic snippets: ${error}. Dynamic snippets may not be available.`
);
}
}

// Apply environment variable substitution to the IR if enabled in docs.yml settings
Expand Down
Loading
Loading