diff --git a/README.md b/README.md index f768ee7..30d5078 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Create a Prisma 8 app with Prisma Composer built in. ## Supported versions -create-prisma supports exactly what Prisma ORM supports: Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 and newer (`^22.18.0 || ^24.11.0 || >=26.0.0`). Node.js 23 and 24.0 to 24.10 are not supported. create-prisma refuses unsupported Node.js versions before it writes any files, and the generated minimal, hono, elysia, nest and turborepo projects declare the same range in `engines.node`. +create-prisma supports exactly what Prisma ORM supports: Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 and newer (`^22.18.0 || ^24.11.0 || >=26.0.0`). create-prisma refuses unsupported Node.js versions before it writes any files, and the generated minimal, hono, elysia, nest and turborepo projects declare the same range in `engines.node`. The next, svelte, astro and tanstack-start templates declare no `engines` field. -Each supported Node.js release works with the npm it ships, including npm 10 on Node.js 22. pnpm, Yarn 4.10 or newer, Bun and Deno are also supported. Yarn 1 is not supported. When create-prisma runs on Bun or Deno instead of Node.js, it skips the Node.js check. +Each supported Node.js release works with the npm it ships, including npm 10 on Node.js 22. pnpm, Yarn 4.10 or newer, Bun and Deno (local minimal PostgreSQL apps only, see below) are also supported. Yarn 1 is not supported. When create-prisma runs on Bun or Deno instead of Node.js, it skips the Node.js check. The one exception is the `nuxt` template. Nuxt itself requires Node.js `^22.22.3 || ^24.15.0 || >=26.0.0`, so Nuxt projects declare that narrower range. @@ -59,7 +59,7 @@ workspace. Choosing another workspace also updates the Prisma CLI's active works - `nuxt` - `tanstack-start` -PostgreSQL and MongoDB are supported with PSL or TypeScript contract authoring. npm, pnpm, Yarn, and Bun are supported. +PostgreSQL and MongoDB are supported with PSL or TypeScript contract authoring. For supported package managers, see [Supported versions](#supported-versions). Deno is supported for local minimal PostgreSQL apps: diff --git a/src/utils/node-version.ts b/src/utils/node-version.ts index 9378354..422e3fb 100644 --- a/src/utils/node-version.ts +++ b/src/utils/node-version.ts @@ -1,18 +1,27 @@ type Version = readonly [major: number, minor: number, patch: number]; -export const SUPPORTED_NODE_RANGE = "^22.18.0 || ^24.11.0 || >=26.0.0"; - -const minimumByMajor: ReadonlyMap = new Map([ - [22, [22, 18, 0]], - [24, [24, 11, 0]], -]); +const supportedNodeLines = [ + { major: 22, minimum: [22, 18, 0] }, + { major: 24, minimum: [24, 11, 0] }, +] as const satisfies readonly { major: number; minimum: Version }[]; const FIRST_MAJOR_WITH_EVERY_RELEASE_SUPPORTED = 26; -export const MINIMUM_NODE_VERSION = minimumByMajor.get(22)!; +export const SUPPORTED_NODE_RANGE = [ + ...supportedNodeLines.map(({ minimum }) => `^${minimum.join(".")}`), + `>=${FIRST_MAJOR_WITH_EVERY_RELEASE_SUPPORTED}.0.0`, +].join(" || "); + +export const MINIMUM_NODE_VERSION: Version = supportedNodeLines[0].minimum; + +const SUPPORTED_NODE_DESCRIPTION = `${supportedNodeLines + .map(({ major, minimum }) => `${minimum[0]}.${minimum[1]} or newer on the ${major} line`) + .join(", ")}, or ${FIRST_MAJOR_WITH_EVERY_RELEASE_SUPPORTED} and newer`; + +const RELEASE_VERSION_PATTERN = /^v?(\d+)\.(\d+)\.(\d+)(?:\+[0-9A-Za-z.-]+)?$/; -function parseVersion(version: string): Version { - const [major = "0", minor = "0", patch = "0"] = version.replace(/^v/, "").split("."); - return [Number(major), Number(minor), Number.parseInt(patch, 10)]; +function parseReleaseVersion(version: string): Version | undefined { + const match = RELEASE_VERSION_PATTERN.exec(version); + return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined; } function isAtLeast(current: Version, minimum: Version): boolean { @@ -29,16 +38,17 @@ export function runsOnNode( } export function supportsPrisma(nodeVersion = process.versions.node): boolean { - const current = parseVersion(nodeVersion); + const current = parseReleaseVersion(nodeVersion); + if (!current) return false; if (current[0] >= FIRST_MAJOR_WITH_EVERY_RELEASE_SUPPORTED) return true; - const minimum = minimumByMajor.get(current[0]); - return minimum !== undefined && isAtLeast(current, minimum); + const line = supportedNodeLines.find(({ major }) => major === current[0]); + return line !== undefined && isAtLeast(current, line.minimum); } export function getUnsupportedNodeMessage(nodeVersion = process.versions.node): string { return [ `Node.js ${nodeVersion} is unsupported by create-prisma@latest.`, - "Required: Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 and newer.", + `Required: Node.js ${SUPPORTED_NODE_DESCRIPTION}.`, "Update Node.js and run the command again.", ].join("\n"); } diff --git a/tests/node-version.test.ts b/tests/node-version.test.ts index 52e5308..6a57cb2 100644 --- a/tests/node-version.test.ts +++ b/tests/node-version.test.ts @@ -14,23 +14,43 @@ import { npmForMinimumNode } from "../src/utils/package-manager"; const repositoryRoot = path.join(import.meta.dir, ".."); describe("Prisma 8 Node compatibility", () => { - test("accepts exactly the Node.js versions Prisma ORM supports", () => { + test("accepts exactly the versions in the supported range, as semver decides", () => { + for (const version of [ + "20.19.0", + "22.17.9", + "22.18.0", + "22.22.3", + "23.0.0", + "23.11.0", + "24.0.0", + "24.10.9", + "24.11.0", + "24.15.0", + "25.9.0", + "26.0.0", + "27.1.0", + "22.18.0-rc.1", + "24.11.0-rc.1", + "26.0.0-nightly20261001abcdef", + "27.0.0-pre", + ]) { + expect([version, supportsPrisma(version)]).toEqual([ + version, + Bun.semver.satisfies(version, SUPPORTED_NODE_RANGE), + ]); + } + }); + + test("checks the boundaries of the supported range", () => { for (const [version, supported] of [ - ["20.19.0", false], ["22.17.9", false], - ["v22.17.9", false], - ["22.18.0", true], ["v22.18.0", true], - ["22.22.3", true], ["23.0.0", false], - ["23.11.0", false], - ["24.0.0", false], ["24.10.9", false], ["24.11.0", true], - ["24.15.0", true], - ["25.9.0", false], + ["24.11.0-rc.1", false], ["26.0.0", true], - ["27.1.0", true], + ["26.0.0-nightly20261001abcdef", false], ] as const) { expect([version, supportsPrisma(version)]).toEqual([version, supported]); }