From 7239c451090db3113141c450278d6cb8cae20ecd Mon Sep 17 00:00:00 2001 From: willbot Date: Fri, 9 Oct 2026 11:08:50 +0200 Subject: [PATCH 1/4] fix(packages): declare the supported Node.js range in engines.node Every Prisma 8 tool supports one Node.js range: ^22.18.0 || ^24.11.0 || >=26.0.0. create-prisma and the projects it generates already declare it; Composer's packages declared >=22.18.0, which also admits odd-numbered lines and the Node 24 releases before its first long-term-support release. All 14 manifests that declare engines.node now use the range, and a script test fails when any workspace manifest declares a different one. The DEPLOY.NODE_MISSING fix message names the same range. Signed-off-by: willbot Signed-off-by: Will Madden --- packages/0-framework/1-core/core/package.json | 2 +- .../2-authoring/nextjs/package.json | 2 +- .../0-framework/2-authoring/node/package.json | 2 +- .../2-authoring/service-rpc/package.json | 2 +- .../3-tooling/assemble/package.json | 2 +- .../0-framework/3-tooling/cli/package.json | 2 +- .../cli/src/__tests__/run-alchemy.test.ts | 18 +++++++++ .../3-tooling/cli/src/run-alchemy.ts | 6 ++- .../0-lowering/dev-emulators/package.json | 2 +- .../0-lowering/local-target/package.json | 2 +- .../0-lowering/lowering/package.json | 2 +- .../0-lowering/s3-protocol/package.json | 2 +- .../1-extensions/target/package.json | 2 +- packages/9-public/composer-cli/package.json | 2 +- .../composer-prisma-cloud/package.json | 2 +- packages/9-public/composer/package.json | 2 +- scripts/supported-node-range.test.mjs | 37 +++++++++++++++++++ 17 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 scripts/supported-node-range.test.mjs diff --git a/packages/0-framework/1-core/core/package.json b/packages/0-framework/1-core/core/package.json index 2f7dff977..643cb5a4d 100644 --- a/packages/0-framework/1-core/core/package.json +++ b/packages/0-framework/1-core/core/package.json @@ -37,7 +37,7 @@ "directory": "packages/0-framework/1-core/core" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/0-framework/2-authoring/nextjs/package.json b/packages/0-framework/2-authoring/nextjs/package.json index f3e8d7b70..99fff7cdb 100644 --- a/packages/0-framework/2-authoring/nextjs/package.json +++ b/packages/0-framework/2-authoring/nextjs/package.json @@ -32,7 +32,7 @@ "directory": "packages/0-framework/2-authoring/nextjs" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/0-framework/2-authoring/node/package.json b/packages/0-framework/2-authoring/node/package.json index ac973644f..e2e7acf90 100644 --- a/packages/0-framework/2-authoring/node/package.json +++ b/packages/0-framework/2-authoring/node/package.json @@ -33,7 +33,7 @@ "directory": "packages/0-framework/2-authoring/node" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/0-framework/2-authoring/service-rpc/package.json b/packages/0-framework/2-authoring/service-rpc/package.json index 4e1ecf31d..9d53dada4 100644 --- a/packages/0-framework/2-authoring/service-rpc/package.json +++ b/packages/0-framework/2-authoring/service-rpc/package.json @@ -34,7 +34,7 @@ "directory": "packages/0-framework/2-authoring/service-rpc" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/0-framework/3-tooling/assemble/package.json b/packages/0-framework/3-tooling/assemble/package.json index 984c97148..3be3f6689 100644 --- a/packages/0-framework/3-tooling/assemble/package.json +++ b/packages/0-framework/3-tooling/assemble/package.json @@ -31,7 +31,7 @@ "directory": "packages/0-framework/3-tooling/assemble" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/0-framework/3-tooling/cli/package.json b/packages/0-framework/3-tooling/cli/package.json index a3e5b4e0f..c7ea333de 100644 --- a/packages/0-framework/3-tooling/cli/package.json +++ b/packages/0-framework/3-tooling/cli/package.json @@ -39,7 +39,7 @@ "directory": "packages/0-framework/3-tooling/cli" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true } diff --git a/packages/0-framework/3-tooling/cli/src/__tests__/run-alchemy.test.ts b/packages/0-framework/3-tooling/cli/src/__tests__/run-alchemy.test.ts index bbecb251e..d4782b2b3 100644 --- a/packages/0-framework/3-tooling/cli/src/__tests__/run-alchemy.test.ts +++ b/packages/0-framework/3-tooling/cli/src/__tests__/run-alchemy.test.ts @@ -125,6 +125,24 @@ describe('nodeExecutable()', () => { ).toThrow(expect.objectContaining({ code: 'DEPLOY.NODE_MISSING' })); }); } + + test('names the supported Node.js range in the DEPLOY.NODE_MISSING fix', () => { + expect(() => + nodeExecutable({ + bun: true, + execPath: '/opt/bun/bin/bun', + env: { PATH: '/usr/bin' }, + platform: 'linux', + exists: () => false, + }), + ).toThrow( + expect.objectContaining({ + fix: + 'Install Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 ' + + 'or newer, and put it on PATH, or run `prisma` under Node.', + }), + ); + }); }); describe('alchemyInvocation()', () => { diff --git a/packages/0-framework/3-tooling/cli/src/run-alchemy.ts b/packages/0-framework/3-tooling/cli/src/run-alchemy.ts index fb6529d20..398d21c7c 100644 --- a/packages/0-framework/3-tooling/cli/src/run-alchemy.ts +++ b/packages/0-framework/3-tooling/cli/src/run-alchemy.ts @@ -74,7 +74,11 @@ export function nodeExecutable(runtime: HostRuntime = currentRuntime()): string throw new CliStructuredError( 'DEPLOY.NODE_MISSING', 'Composer starts Alchemy with Node, and no `node` was found on PATH.', - { fix: 'Install Node 22.18 or newer and put it on PATH, or run `prisma` under Node.' }, + { + fix: + 'Install Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 ' + + 'or newer, and put it on PATH, or run `prisma` under Node.', + }, ); } diff --git a/packages/1-prisma-cloud/0-lowering/dev-emulators/package.json b/packages/1-prisma-cloud/0-lowering/dev-emulators/package.json index fd38d49f9..c5c5d30e6 100644 --- a/packages/1-prisma-cloud/0-lowering/dev-emulators/package.json +++ b/packages/1-prisma-cloud/0-lowering/dev-emulators/package.json @@ -41,7 +41,7 @@ "directory": "packages/1-prisma-cloud/0-lowering/dev-emulators" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/1-prisma-cloud/0-lowering/local-target/package.json b/packages/1-prisma-cloud/0-lowering/local-target/package.json index ebe56fbbf..43f914ec0 100644 --- a/packages/1-prisma-cloud/0-lowering/local-target/package.json +++ b/packages/1-prisma-cloud/0-lowering/local-target/package.json @@ -37,7 +37,7 @@ "directory": "packages/1-prisma-cloud/0-lowering/local-target" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/1-prisma-cloud/0-lowering/lowering/package.json b/packages/1-prisma-cloud/0-lowering/lowering/package.json index c64a611ad..e638ad9b9 100644 --- a/packages/1-prisma-cloud/0-lowering/lowering/package.json +++ b/packages/1-prisma-cloud/0-lowering/lowering/package.json @@ -39,7 +39,7 @@ "directory": "packages/1-prisma-cloud/0-lowering/lowering" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/1-prisma-cloud/0-lowering/s3-protocol/package.json b/packages/1-prisma-cloud/0-lowering/s3-protocol/package.json index f4250a215..fae5b2120 100644 --- a/packages/1-prisma-cloud/0-lowering/s3-protocol/package.json +++ b/packages/1-prisma-cloud/0-lowering/s3-protocol/package.json @@ -29,7 +29,7 @@ "directory": "packages/1-prisma-cloud/0-lowering/s3-protocol" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/1-prisma-cloud/1-extensions/target/package.json b/packages/1-prisma-cloud/1-extensions/target/package.json index 798bb9007..dd081af8a 100644 --- a/packages/1-prisma-cloud/1-extensions/target/package.json +++ b/packages/1-prisma-cloud/1-extensions/target/package.json @@ -53,7 +53,7 @@ "directory": "packages/1-prisma-cloud/1-extensions/target" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "private": true, "types": "./dist/index.d.mts" diff --git a/packages/9-public/composer-cli/package.json b/packages/9-public/composer-cli/package.json index 230dac109..66cbf2e37 100644 --- a/packages/9-public/composer-cli/package.json +++ b/packages/9-public/composer-cli/package.json @@ -42,7 +42,7 @@ "directory": "packages/9-public/composer-cli" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/9-public/composer-prisma-cloud/package.json b/packages/9-public/composer-prisma-cloud/package.json index 42c1504e6..f951bb9e4 100644 --- a/packages/9-public/composer-prisma-cloud/package.json +++ b/packages/9-public/composer-prisma-cloud/package.json @@ -87,7 +87,7 @@ "directory": "packages/9-public/composer-prisma-cloud" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/9-public/composer/package.json b/packages/9-public/composer/package.json index 46dac3f0b..2fb5bf055 100644 --- a/packages/9-public/composer/package.json +++ b/packages/9-public/composer/package.json @@ -65,7 +65,7 @@ "directory": "packages/9-public/composer" }, "engines": { - "node": ">=22.18.0" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "publishConfig": { "access": "public" diff --git a/scripts/supported-node-range.test.mjs b/scripts/supported-node-range.test.mjs new file mode 100644 index 000000000..a276e5d2e --- /dev/null +++ b/scripts/supported-node-range.test.mjs @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; + +const SUPPORTED_NODE_RANGE = '^22.18.0 || ^24.11.0 || >=26.0.0'; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +const WORKSPACE_DIRS = ['packages', 'examples', 'test', 'website']; + +const manifests = execFileSync('git', ['ls-files', '--', ...WORKSPACE_DIRS], { + cwd: repoRoot, + encoding: 'utf-8', +}) + .split('\n') + .filter((file) => file === 'package.json' || file.endsWith('/package.json')) + .map((file) => ({ file, json: JSON.parse(readFileSync(join(repoRoot, file), 'utf-8')) })); + +describe('supported Node.js range', () => { + it('is declared by every published package', () => { + const published = manifests.filter(({ file }) => file.startsWith('packages/9-public/')); + assert.ok(published.length > 0); + for (const { file, json } of published) { + assert.equal(json.engines?.node, SUPPORTED_NODE_RANGE, file); + } + }); + + it('is the only engines.node any workspace package declares', () => { + for (const { file, json } of manifests) { + if (json.engines?.node === undefined) continue; + assert.equal(json.engines.node, SUPPORTED_NODE_RANGE, file); + } + }); +}); From f9d8a16e9af85cfb30deebfbaaccbf1232028465 Mon Sep 17 00:00:00 2001 From: willbot Date: Fri, 9 Oct 2026 11:12:56 +0200 Subject: [PATCH 2/4] ci: run the Node floor job on the oldest release of each supported line The node-floor job ran only on 22.18.0. The range now has three lower bounds, so the job is a matrix over 22.18.0, 24.11.0 and 26.0.0. The 22.18 entry keeps its check name, "Node 22.18 floor". On Node 24.11 to 24.13, rmSync on a symlink to a directory throws EISDIR, which failed one script test. The test now removes the link with unlinkSync, the call meant for links. Signed-off-by: willbot Signed-off-by: Will Madden --- .github/workflows/ci.yml | 57 ++++++++++++++-------- scripts/check-cli-engine-pin-host.test.mjs | 12 ++++- scripts/check-floor-imports.mjs | 4 +- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45e42ed3c..468344475 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,16 +163,26 @@ jobs: run: pnpm --dir test/integration exec bun test node-floor: - name: Node 22.18 floor - # The published packages declare `engines.node: >=22.18.0`, but every suite - # above runs under bun, which ignores `engines.node` entirely, and - # `.tool-versions` pins node 24.16.0 — so nothing else here ever executes - # the floor version. This job does. It covers what a Node user hits first: - # the two runtime features the floor rests on, `node --test` over the - # repo's TypeScript scripts, and every published entrypoint of - # @prisma/composer and @prisma/composer-cli. It deliberately does NOT re-run the bun suites under - # node — bun is the runner there, so doing so would prove nothing about - # node. + name: Node ${{ matrix.line }} floor + # The published packages declare `engines.node: ^22.18.0 || ^24.11.0 || >=26.0.0`, + # but every suite above runs under bun, which ignores `engines.node` + # entirely, and `.tool-versions` pins node 24.16.0 — so nothing else here + # ever executes the oldest release of each supported line. This job does. + # It covers what a Node user hits first: the two runtime features the + # floor rests on, `node --test` over the repo's TypeScript scripts, and + # every published entrypoint of @prisma/composer and @prisma/composer-cli. + # It deliberately does NOT re-run the bun suites under node — bun is the + # runner there, so doing so would prove nothing about node. + strategy: + fail-fast: false + matrix: + include: + - line: '22.18' + node: 22.18.0 + - line: '24.11' + node: 24.11.0 + - line: '26.0' + node: 26.0.0 runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -183,27 +193,32 @@ jobs: run: pnpm install --frozen-lockfile # Built on the toolchain `.tool-versions` pins (node 24.16.0) — the same # toolchain publish.yml builds the tarballs with. The floor is a claim - # about CONSUMING that output, so node drops to 22.18.0 only after this. + # about CONSUMING that output, so node drops to the floor only after this. - name: Build packages run: pnpm build # mise is how this repo pins tools; there is no `actions/setup-node` # anywhere in .github. `.tool-versions` stays on 24.16.0 — this prepends - # 22.18.0 to PATH for the steps below only. - - name: Put node 22.18.0 first on PATH + # the floor version to PATH for the steps below only. + - name: Put the floor node first on PATH + env: + FLOOR_NODE: ${{ matrix.node }} run: | - mise install node@22.18.0 - echo "$(mise where node@22.18.0)/bin" >> "$GITHUB_PATH" + mise install "node@$FLOOR_NODE" + echo "$(mise where "node@$FLOOR_NODE")/bin" >> "$GITHUB_PATH" # If that PATH order ever flips back to mise's 24.16.0, every step below # would quietly re-test the version CI already covers. Fail loudly here. - name: Confirm node is exactly the floor version + env: + FLOOR_NODE: ${{ matrix.node }} run: | echo "node --version -> $(node --version)" - test "$(node --version)" = "v22.18.0" - # The two runtime features `>=22.18.0` rests on. Type stripping is what - # sets the floor: it is unflagged in 22.18.0 exactly, and it is how the - # CLI imports the user's `.ts` entry. `node:sqlite` (used by the streams - # module's local stand-in, via @prisma/streams-local's `DatabaseSync`) - # unflagged earlier, in 22.13.0, so the floor already covers it. + test "$(node --version)" = "v$FLOOR_NODE" + # The two runtime features the range rests on. Type stripping is what + # sets the 22.18.0 floor: it is unflagged in 22.18.0 exactly, and it is + # how the CLI imports the user's `.ts` entry. `node:sqlite` (used by the + # streams module's local stand-in, via @prisma/streams-local's + # `DatabaseSync`) unflagged earlier, in 22.13.0, so the floor already + # covers it. 24.11.0 is Node 24's first long-term-support release. - name: Check the runtime features the floor claims run: | printf 'const greeting: string = "type stripping works"\nconsole.log(greeting)\n' > "$RUNNER_TEMP/floor-check.mts" diff --git a/scripts/check-cli-engine-pin-host.test.mjs b/scripts/check-cli-engine-pin-host.test.mjs index ad278282e..711b7c3aa 100644 --- a/scripts/check-cli-engine-pin-host.test.mjs +++ b/scripts/check-cli-engine-pin-host.test.mjs @@ -1,5 +1,13 @@ import assert from 'node:assert/strict'; -import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; +import { + mkdirSync, + mkdtempSync, + realpathSync, + rmSync, + symlinkSync, + unlinkSync, + writeFileSync, +} from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { afterEach, beforeEach, describe, it } from 'node:test'; @@ -99,7 +107,7 @@ describe('checkHost', () => { it('fails when the family cannot be resolved from the host, naming the override and the root link', () => { healthyTree(); - rmSync(join(root, 'node_modules/@prisma/composer-cli')); + unlinkSync(join(root, 'node_modules/@prisma/composer-cli')); const [failure, ...rest] = run(); diff --git a/scripts/check-floor-imports.mjs b/scripts/check-floor-imports.mjs index d35028c2b..818865bbc 100644 --- a/scripts/check-floor-imports.mjs +++ b/scripts/check-floor-imports.mjs @@ -20,8 +20,8 @@ // // Requires both packages to be built (`pnpm turbo run build // --filter=@prisma/composer --filter=@prisma/composer-cli`). Uses whichever -// `node` is running it, which is what lets the node-floor job aim it at -// 22.18.0. +// `node` is running it, which is what lets the node-floor job aim it at the +// oldest release of each supported Node.js line. // // Usage: node scripts/check-floor-imports.mjs From 9107e72668f9730c389028cd62aabc7392363123 Mon Sep 17 00:00:00 2001 From: willbot Date: Fri, 9 Oct 2026 11:12:57 +0200 Subject: [PATCH 3/4] docs: state the supported Node.js range in the README, guides and skill The README, the getting-started and deploying guides, the deploy CLI design doc and the skill now say: Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer, with the npm that Node release ships. The skill gains a failure-mode entry for ERR_UNKNOWN_FILE_EXTENSION on the app's own .ts entry, which is what a too-old Node looks like. Signed-off-by: willbot Signed-off-by: Will Madden --- README.md | 15 +++++---------- docs/design/10-domains/deploy-cli.md | 3 +-- docs/guides/deploying.md | 3 +-- docs/guides/getting-started.md | 6 +----- .../3-tooling/cli/src/entry-resolution.ts | 2 +- skills/prisma-composer-core-concepts/SKILL.md | 1 + 6 files changed, 10 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 084e64570..ffdcb6ac8 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,17 @@ databases, schedules, secrets — compose them into a **Prisma App**, and `prisma deploy` provisions all of it. There is no infrastructure configuration to write or maintain. -## You need Node 22.18 or newer +## You need a supported Node.js release + +Composer supports Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer, each with the npm that Node release ships (npm 10 on Node 22). Check yours first: ```sh node --version ``` -**On anything older, Composer cannot load your app at all.** Your entry file is -TypeScript and `prisma` hands it straight to Node — the framework -never bundles or transforms your code. Node runs `.ts` files directly only from -**22.18.0**, the release that turns type stripping on by default. Before that, -`prisma deploy` stops at `ERR_UNKNOWN_FILE_EXTENSION` naming your own -entry file, which reads as a broken file rather than a Node that is too old. +**On an older Node, Composer cannot load your app at all.** Your entry file is TypeScript and `prisma` hands it straight to Node — the framework never bundles or transforms your code. Node runs `.ts` files directly only from **22.18.0**, the release that turns type stripping on by default. Before that, `prisma deploy` stops at `ERR_UNKNOWN_FILE_EXTENSION` naming your own entry file, which reads as a broken file rather than a Node that is too old. -22.17 is not close enough, and Node 20 never gets it: the default changed in -22.18.0 exactly. The `streams` module's local stand-in additionally needs -Node's built-in `node:sqlite`, unflagged since 22.13 — so 22.18 covers both. +22.17 is not close enough, and Node 20 never gets it: the default changed in 22.18.0 exactly. The `streams` module's local stand-in additionally needs Node's built-in `node:sqlite`, unflagged since 22.13, so 22.18 covers both. On the 24 line, 24.11 is the first long-term-support release. Every Prisma 8 tool supports this same range. ## Start with the skill diff --git a/docs/design/10-domains/deploy-cli.md b/docs/design/10-domains/deploy-cli.md index a4eac0f2b..e273177aa 100644 --- a/docs/design/10-domains/deploy-cli.md +++ b/docs/design/10-domains/deploy-cli.md @@ -51,8 +51,7 @@ already created). The `destroy` operation takes the same and topology emission are out of scope (see § Out of scope). **Runtime.** The command family is runtime-agnostic — no bun-only APIs -anywhere in the CLI or assembly code — so it runs under both bun and node (≥ 22.18, where -type stripping imports the user's `.ts` entry natively). Under node, the CLI +anywhere in the CLI or assembly code — so it runs under both bun and node (Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer; type stripping imports the user's `.ts` entry natively from 22.18). Under node, the CLI also registers a synchronous resolve hook (`node:module` `registerHooks`) so that relative imports inside the entry graph may use `./x.js` or extensionless `./x` specifiers for `.ts` source files without requiring diff --git a/docs/guides/deploying.md b/docs/guides/deploying.md index 2952f432c..9de707d1a 100644 --- a/docs/guides/deploying.md +++ b/docs/guides/deploying.md @@ -267,8 +267,7 @@ if (!result.ok) { ``` Run it with the two variables under [Credentials](#credentials) set, for -example `bun destroy-staging.ts`, or `node destroy-staging.ts` on Node 22.18 -or newer. A `prisma auth login` session is not used. +example `bun destroy-staging.ts`, or `node destroy-staging.ts` on a supported Node.js release (22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer). A `prisma auth login` session is not used. The three teardown shapes differ in what happens to state. Destroying a **stage** removes its resources, then deletes its Branch — and the Branch takes the stage's deploy state with it. Destroying **production** removes the resources and empties production's deploy state as it goes, but the production Branch survives, so an emptied state scope remains until the Project itself is removed. Deleting the **Project** (below, or from the Console) removes every Branch and all state in one stroke. Destroy never creates: tearing down a stage that was never deployed fails with "nothing deployed" rather than provisioning one first. diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index a26ed1d5a..14396503e 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -46,11 +46,7 @@ and [Building an app](building-an-app.md#databases) covers it. You'll need: -- **Node 22.18 or newer** — check with `node --version` before anything else. - Composer hands your TypeScript entry file straight to Node, and Node runs - `.ts` directly only from 22.18.0, the release that turns type stripping on by - default. On anything older `prisma deploy` stops at - `ERR_UNKNOWN_FILE_EXTENSION` naming your own file; 22.17 is not close enough. +- **Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer**, with the npm that Node release ships (npm 10 on Node 22). Check with `node --version` before anything else. Composer hands your TypeScript entry file straight to Node, and Node runs `.ts` directly only from 22.18.0, the release that turns type stripping on by default. On anything older `prisma deploy` stops at `ERR_UNKNOWN_FILE_EXTENSION` naming your own file; 22.17 is not close enough. - [Bun](https://bun.sh) — Prisma Compute runs Bun, so that's what the server code targets (`Bun.serve`), and it's the fastest way to run things locally. - pnpm (or npm). diff --git a/packages/0-framework/3-tooling/cli/src/entry-resolution.ts b/packages/0-framework/3-tooling/cli/src/entry-resolution.ts index 88a424204..71159601f 100644 --- a/packages/0-framework/3-tooling/cli/src/entry-resolution.ts +++ b/packages/0-framework/3-tooling/cli/src/entry-resolution.ts @@ -30,7 +30,7 @@ let registered = false; */ export function registerEntryResolution(): void { if (registered || typeof process.versions.bun === 'string') return; - // registerHooks landed in Node 22.15; the engine floor is 22.18. + // registerHooks landed in Node 22.15; the oldest supported Node.js is 22.18. // Guard here covers any non-standard environment. if (typeof mod.registerHooks !== 'function') return; registered = true; diff --git a/skills/prisma-composer-core-concepts/SKILL.md b/skills/prisma-composer-core-concepts/SKILL.md index bf8ac81c0..b00a85e9d 100644 --- a/skills/prisma-composer-core-concepts/SKILL.md +++ b/skills/prisma-composer-core-concepts/SKILL.md @@ -579,6 +579,7 @@ today the blocks above plus your own Modules are the whole set, so verify a `admin.createUser` from a service wired to the `admin` port. A deployed stack's rpc ports are reachable only from inside its graph, so the app exposes its own operator route that makes that call. +11. **`prisma deploy` stops at `ERR_UNKNOWN_FILE_EXTENSION` naming the app's own `.ts` entry.** Node is too old to strip TypeScript types. Composer supports Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer, with the npm that Node release ships (npm 10 on Node 22); its packages declare `engines.node` as `^22.18.0 || ^24.11.0 || >=26.0.0`. Upgrade Node. ## What Composer doesn't do yet From 126e25de50595554c8d2cd9ab18bf6fcf92ff9bd Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 12 Oct 2026 14:06:20 +0200 Subject: [PATCH 4/4] ci: keep the single Node 22.18 floor job, and name the range in its comment Signed-off-by: willbot Signed-off-by: Will Madden --- .github/workflows/ci.yml | 56 +++++++++++++++------------------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 468344475..0602cf04d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,26 +163,17 @@ jobs: run: pnpm --dir test/integration exec bun test node-floor: - name: Node ${{ matrix.line }} floor + name: Node 22.18 floor # The published packages declare `engines.node: ^22.18.0 || ^24.11.0 || >=26.0.0`, - # but every suite above runs under bun, which ignores `engines.node` - # entirely, and `.tool-versions` pins node 24.16.0 — so nothing else here - # ever executes the oldest release of each supported line. This job does. - # It covers what a Node user hits first: the two runtime features the - # floor rests on, `node --test` over the repo's TypeScript scripts, and - # every published entrypoint of @prisma/composer and @prisma/composer-cli. - # It deliberately does NOT re-run the bun suites under node — bun is the - # runner there, so doing so would prove nothing about node. - strategy: - fail-fast: false - matrix: - include: - - line: '22.18' - node: 22.18.0 - - line: '24.11' - node: 24.11.0 - - line: '26.0' - node: 26.0.0 + # and 22.18.0 is its oldest release, but every suite + # above runs under bun, which ignores `engines.node` entirely, and + # `.tool-versions` pins node 24.16.0 — so nothing else here ever executes + # the floor version. This job does. It covers what a Node user hits first: + # the two runtime features the floor rests on, `node --test` over the + # repo's TypeScript scripts, and every published entrypoint of + # @prisma/composer and @prisma/composer-cli. It deliberately does NOT re-run the bun suites under + # node — bun is the runner there, so doing so would prove nothing about + # node. runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -193,32 +184,27 @@ jobs: run: pnpm install --frozen-lockfile # Built on the toolchain `.tool-versions` pins (node 24.16.0) — the same # toolchain publish.yml builds the tarballs with. The floor is a claim - # about CONSUMING that output, so node drops to the floor only after this. + # about CONSUMING that output, so node drops to 22.18.0 only after this. - name: Build packages run: pnpm build # mise is how this repo pins tools; there is no `actions/setup-node` # anywhere in .github. `.tool-versions` stays on 24.16.0 — this prepends - # the floor version to PATH for the steps below only. - - name: Put the floor node first on PATH - env: - FLOOR_NODE: ${{ matrix.node }} + # 22.18.0 to PATH for the steps below only. + - name: Put node 22.18.0 first on PATH run: | - mise install "node@$FLOOR_NODE" - echo "$(mise where "node@$FLOOR_NODE")/bin" >> "$GITHUB_PATH" + mise install node@22.18.0 + echo "$(mise where node@22.18.0)/bin" >> "$GITHUB_PATH" # If that PATH order ever flips back to mise's 24.16.0, every step below # would quietly re-test the version CI already covers. Fail loudly here. - name: Confirm node is exactly the floor version - env: - FLOOR_NODE: ${{ matrix.node }} run: | echo "node --version -> $(node --version)" - test "$(node --version)" = "v$FLOOR_NODE" - # The two runtime features the range rests on. Type stripping is what - # sets the 22.18.0 floor: it is unflagged in 22.18.0 exactly, and it is - # how the CLI imports the user's `.ts` entry. `node:sqlite` (used by the - # streams module's local stand-in, via @prisma/streams-local's - # `DatabaseSync`) unflagged earlier, in 22.13.0, so the floor already - # covers it. 24.11.0 is Node 24's first long-term-support release. + test "$(node --version)" = "v22.18.0" + # The two runtime features `>=22.18.0` rests on. Type stripping is what + # sets the floor: it is unflagged in 22.18.0 exactly, and it is how the + # CLI imports the user's `.ts` entry. `node:sqlite` (used by the streams + # module's local stand-in, via @prisma/streams-local's `DatabaseSync`) + # unflagged earlier, in 22.13.0, so the floor already covers it. - name: Check the runtime features the floor claims run: | printf 'const greeting: string = "type stripping works"\nconsole.log(greeting)\n' > "$RUNNER_TEMP/floor-check.mts"