Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/friendly-cycles-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@langchain/langgraph-cli": patch
---

feat(cli): use dynamic validation for Node.js version instead of hardcoded values
13 changes: 11 additions & 2 deletions libs/langgraph-cli/src/utils/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,20 @@ const BaseConfigSchema = z.object({
});

const DEFAULT_PYTHON_VERSION = "3.11" as const;
const DEFAULT_NODE_VERSION = "20" as const;
const DEFAULT_NODE_VERSION = "24" as const;
const MIN_NODE_VERSION = 20;
const PYTHON_EXTENSIONS = [".py", ".pyx", ".pyd", ".pyi"];

const PythonVersionSchema = z.union([z.literal("3.11"), z.literal("3.12")]);
const NodeVersionSchema = z.union([z.literal("20"), z.literal("22")]);
const NodeVersionSchema = z.string().refine(
(version) => {
const majorVersion = parseInt(version.split(".")[0], 10);
return !isNaN(majorVersion) && majorVersion >= MIN_NODE_VERSION;
},
{
message: `Node.js version must be >= ${MIN_NODE_VERSION}`,
}
);

const PythonConfigSchema = BaseConfigSchema.merge(
z.object({
Expand Down
121 changes: 115 additions & 6 deletions libs/langgraph-cli/tests/config.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ it("node config and python config", () => {

// default node
expect(getConfig({ graphs: { agent: "./agent.js:graph" } })).toEqual({
node_version: "20",
node_version: "24",
dockerfile_lines: [],
graphs: { agent: "./agent.js:graph" },
env: {},
Expand All @@ -872,7 +872,7 @@ it("node config and python config", () => {
})
).toEqual({
python_version: "3.12",
node_version: "20",
node_version: "24",
dependencies: ["."],
graphs: { js: "./agent.js:graph", py: "./agent.py:graph" },
dockerfile_lines: [],
Expand Down Expand Up @@ -924,14 +924,123 @@ it("node config and python config", () => {
)
.toThrow();

// Invalid Node version
// Invalid Node version (below minimum)
expect
.soft(() =>
getConfig({
// @ts-expect-error
node_version: "18", // Unsupported version
node_version: "18", // Below minimum version
graphs: { agent: "./agent.js:graph" },
})
)
.toThrow();
.toThrow("Node.js version must be >= 20");

expect
.soft(() =>
getConfig({
node_version: "19",
graphs: { agent: "./agent.js:graph" },
})
)
.toThrow("Node.js version must be >= 20");

// Invalid Node version (not a number)
expect
.soft(() =>
getConfig({
node_version: "invalid",
graphs: { agent: "./agent.js:graph" },
})
)
.toThrow("Node.js version must be >= 20");
});

describe("node version validation", () => {
it("accepts valid major versions >= 20", () => {
// Version 20
expect(
getConfig({
node_version: "20",
graphs: { agent: "./agent.js:graph" },
})
).toMatchObject({ node_version: "20" });

// Version 22
expect(
getConfig({
node_version: "22",
graphs: { agent: "./agent.js:graph" },
})
).toMatchObject({ node_version: "22" });

// Version 24
expect(
getConfig({
node_version: "24",
graphs: { agent: "./agent.js:graph" },
})
).toMatchObject({ node_version: "24" });

// Future version
expect(
getConfig({
node_version: "26",
graphs: { agent: "./agent.js:graph" },
})
).toMatchObject({ node_version: "26" });
});

it("accepts semver-style versions >= 20", () => {
expect(
getConfig({
node_version: "20.10.0",
graphs: { agent: "./agent.js:graph" },
})
).toMatchObject({ node_version: "20.10.0" });

expect(
getConfig({
node_version: "22.1.5",
graphs: { agent: "./agent.js:graph" },
})
).toMatchObject({ node_version: "22.1.5" });
});

it("rejects versions below minimum (20)", () => {
expect(() =>
getConfig({
node_version: "18",
graphs: { agent: "./agent.js:graph" },
})
).toThrow("Node.js version must be >= 20");

expect(() =>
getConfig({
node_version: "16",
graphs: { agent: "./agent.js:graph" },
})
).toThrow("Node.js version must be >= 20");

expect(() =>
getConfig({
node_version: "19.9.0",
graphs: { agent: "./agent.js:graph" },
})
).toThrow("Node.js version must be >= 20");
});

it("rejects invalid version strings", () => {
expect(() =>
getConfig({
node_version: "invalid",
graphs: { agent: "./agent.js:graph" },
})
).toThrow("Node.js version must be >= 20");

expect(() =>
getConfig({
node_version: "abc.def.ghi",
graphs: { agent: "./agent.js:graph" },
})
).toThrow("Node.js version must be >= 20");
});
});