Skip to content
Open
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
28 changes: 28 additions & 0 deletions packages/cli/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import { LOG_LEVELS, LogLevel } from "@fern-api/logger";
import { askToLogin, login, logout } from "@fern-api/login";
import { protocGenFern } from "@fern-api/protoc-gen-fern";
import { FernCliError, LoggableFernCliError } from "@fern-api/task-context";
import { spawnSync } from "child_process";
import getPort from "get-port";
import v8 from "v8";
import { Argv } from "yargs";
import { hideBin } from "yargs/helpers";
import yargs from "yargs/yargs";
Expand Down Expand Up @@ -63,6 +65,32 @@ import { FERN_CWD_ENV_VAR } from "./cwd";
import { rerunFernCliAtVersion } from "./rerunFernCliAtVersion";
import { RUNTIME } from "./runtime";

// Increase heap size for large API generation if not already set
// Skip during tests to avoid breaking test execution
const DESIRED_HEAP_SIZE_MB = 8192; // 8GB
const isTestEnvironment = process.env.VITEST != null || process.env.NODE_ENV === "test";
const heapStats = v8.getHeapStatistics();
const currentHeapLimitMB = Math.floor(heapStats.heap_size_limit / (1024 * 1024));

// Only re-spawn if:
// 1. Not in test environment
// 2. Heap is below desired size
// 3. Haven't already tried re-spawning
if (!isTestEnvironment && currentHeapLimitMB < DESIRED_HEAP_SIZE_MB && process.env.FERN_HEAP_INCREASED !== "true") {
const result = spawnSync(
process.execPath,
[`--max-old-space-size=${DESIRED_HEAP_SIZE_MB}`, ...process.argv.slice(1)],
{
stdio: "inherit",
env: {
...process.env,
FERN_HEAP_INCREASED: "true"
}
}
);
process.exit(result.status ?? 1);
}

void runCli();

const USE_NODE_18_OR_ABOVE_MESSAGE = "The Fern CLI requires Node 18+ or above.";
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 3.58.1
changelogEntry:
- summary: |
Fix memory issues during SDK generation for large APIs by skipping dynamic IR generation during the main IR generation phase. Dynamic IR is now only generated when needed for uploading to FDR, reducing peak memory usage significantly.
type: fix
- summary: |
Automatically increase Node.js heap size to 8GB for large API generation. The CLI now detects when the heap limit is below 8GB and re-spawns with increased memory to prevent out-of-memory errors.
type: fix
createdAt: "2026-02-03"
irVersion: 63

- version: 3.58.0
changelogEntry:
- summary: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export declare namespace generateIntermediateRepresentation {
sourceResolver: SourceResolver;
fdrApiDefinitionId?: string;
disableDynamicExamples?: boolean;
skipDynamicIr?: boolean;
dynamicGeneratorConfig?: dynamic.GeneratorConfig;
generationMetadata?: FernIr.GenerationMetadata;
}
Expand All @@ -94,6 +95,7 @@ export function generateIntermediateRepresentation({
fdrApiDefinitionId,
sourceResolver,
disableDynamicExamples,
skipDynamicIr,
dynamicGeneratorConfig,
generationMetadata
}: generateIntermediateRepresentation.Args): IntermediateRepresentation {
Expand Down Expand Up @@ -551,13 +553,15 @@ export function generateIntermediateRepresentation({

return {
...finalIR,
dynamic: convertIrToDynamicSnippetsIr({
ir: finalIR,
generatorConfig: dynamicGeneratorConfig,
disableExamples: disableDynamicExamples,
generationLanguage,
smartCasing
})
dynamic: skipDynamicIr
? undefined
: convertIrToDynamicSnippetsIr({
ir: finalIR,
generatorConfig: dynamicGeneratorConfig,
disableExamples: disableDynamicExamples,
generationLanguage,
smartCasing
})
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export async function runRemoteGenerationForGenerator({
context: interactiveTaskContext,
sourceResolver: new SourceResolverImpl(interactiveTaskContext, workspace),
dynamicGeneratorConfig,
skipDynamicIr: true,
generationMetadata: {
cliVersion: workspace.cliVersion,
generatorName: generatorInvocation.name,
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.

Loading
Loading