From ab254f9566af9a421b0a50833ca702fff9cae84b Mon Sep 17 00:00:00 2001 From: Andre Lars Da Cunha Date: Thu, 17 Sep 2026 13:05:13 +0200 Subject: [PATCH 1/5] feat: declare Node.js 24 support in engines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FNA-1642 `node24` was added as a deploy runtime and made the default in #555, and the local Node version check already accepts Node 24, but `engines.node` was left at `^20.x || ^22.x`. The toolkit therefore defaulted to deploying to node24 while refusing to declare support for running on Node 24, so users on Node 24 saw engine warnings. Widen the range to `^20.x || ^22.x || ^24.x` across the four published packages. The plugins depend on `twilio-run`, so bumping only `twilio-run` would leave them blocking Node 24 — all four move together. Fixes #563. Co-Authored-By: Claude Opus 5 --- .changeset/node24-engines-support.md | 10 ++++++++++ packages/create-twilio-function/package.json | 2 +- packages/plugin-assets/package.json | 2 +- packages/plugin-serverless/package.json | 2 +- packages/twilio-run/package.json | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 .changeset/node24-engines-support.md diff --git a/.changeset/node24-engines-support.md b/.changeset/node24-engines-support.md new file mode 100644 index 00000000..1b7fc1d1 --- /dev/null +++ b/.changeset/node24-engines-support.md @@ -0,0 +1,10 @@ +--- +'twilio-run': patch +'create-twilio-function': patch +'@twilio-labs/plugin-serverless': patch +'@twilio-labs/plugin-assets': patch +--- + +feat: declare Node.js 24 support in `engines` + +`node24` was added as a deploy runtime and made the default in #555, and the local version check already accepts Node 24, but the `engines.node` range was left at `^20.x || ^22.x`. That made the toolkit default to deploying to node24 while refusing to declare support for running on Node 24, so users on Node 24 saw engine warnings. Widening the range to `^20.x || ^22.x || ^24.x` across the four published packages resolves the inconsistency. Fixes #563. diff --git a/packages/create-twilio-function/package.json b/packages/create-twilio-function/package.json index 122aa998..eb5d8b91 100644 --- a/packages/create-twilio-function/package.json +++ b/packages/create-twilio-function/package.json @@ -42,7 +42,7 @@ "yargs": "^17.2.1" }, "engines": { - "node": "^20.x || ^22.x" + "node": "^20.x || ^22.x || ^24.x" }, "files": [ "bin/", diff --git a/packages/plugin-assets/package.json b/packages/plugin-assets/package.json index 92556fd3..ad697859 100644 --- a/packages/plugin-assets/package.json +++ b/packages/plugin-assets/package.json @@ -23,7 +23,7 @@ "rimraf": "^5.0.5" }, "engines": { - "node": "^20.x || ^22.x" + "node": "^20.x || ^22.x || ^24.x" }, "files": [ "/oclif.manifest.json", diff --git a/packages/plugin-serverless/package.json b/packages/plugin-serverless/package.json index 2da2e78a..d808069f 100644 --- a/packages/plugin-serverless/package.json +++ b/packages/plugin-serverless/package.json @@ -22,7 +22,7 @@ "oclif": "^4.0.0" }, "engines": { - "node": "^20.x || ^22.x" + "node": "^20.x || ^22.x || ^24.x" }, "files": [ "/oclif.manifest.json", diff --git a/packages/twilio-run/package.json b/packages/twilio-run/package.json index e9f400d5..d07769fe 100644 --- a/packages/twilio-run/package.json +++ b/packages/twilio-run/package.json @@ -122,7 +122,7 @@ "README.md" ], "engines": { - "node": "^20.x || ^22.x" + "node": "^20.x || ^22.x || ^24.x" }, "gitHead": "6db273648ed19474f4125042556b10c051529912" } From 95a385d3f750f972a941b5973d8c00475dc2ccaa Mon Sep 17 00:00:00 2001 From: Andre Lars Da Cunha Date: Thu, 17 Sep 2026 13:09:23 +0200 Subject: [PATCH 2/5] feat: remove Node.js 20 from supported versions FNA-1642 Node.js 20 is deprecated on Twilio Serverless and can no longer be used for new builds, so drop it from the supported range in the same change that adds Node.js 24. - `engines.node` becomes `^22.x || ^24.x` in the four published packages - the scaffolded project template's engines range follows suit - the local version check no longer accepts Node 20 - `.nvmrc`, CONTRIBUTING and the serverless-twilio-runtime Readme move to the new minimum, and the on-merge-main workflow builds on Node 22 Mirrors the Node.js 18 removal in #544. BREAKING CHANGE: Projects using create-twilio-function, plugin-assets, plugin-serverless, serverless-twilio-runtime or twilio-run will have to migrate to Node.js 22 or 24. Fixes #563. Co-Authored-By: Claude Opus 5 --- .changeset/node24-engines-support.md | 10 ---------- .changeset/node24-support-drop-node20.md | 13 +++++++++++++ .github/workflows/on-merge-main.yml | 4 ++-- .nvmrc | 2 +- docs/CONTRIBUTING.md | 4 ++-- packages/create-twilio-function/package.json | 2 +- .../src/create-twilio-function/versions.js | 2 +- packages/plugin-assets/package.json | 2 +- packages/plugin-serverless/package.json | 2 +- packages/serverless-twilio-runtime/Readme.md | 2 +- packages/twilio-run/package.json | 2 +- packages/twilio-run/src/checks/nodejs-version.ts | 2 +- 12 files changed, 25 insertions(+), 22 deletions(-) delete mode 100644 .changeset/node24-engines-support.md create mode 100644 .changeset/node24-support-drop-node20.md diff --git a/.changeset/node24-engines-support.md b/.changeset/node24-engines-support.md deleted file mode 100644 index 1b7fc1d1..00000000 --- a/.changeset/node24-engines-support.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'twilio-run': patch -'create-twilio-function': patch -'@twilio-labs/plugin-serverless': patch -'@twilio-labs/plugin-assets': patch ---- - -feat: declare Node.js 24 support in `engines` - -`node24` was added as a deploy runtime and made the default in #555, and the local version check already accepts Node 24, but the `engines.node` range was left at `^20.x || ^22.x`. That made the toolkit default to deploying to node24 while refusing to declare support for running on Node 24, so users on Node 24 saw engine warnings. Widening the range to `^20.x || ^22.x || ^24.x` across the four published packages resolves the inconsistency. Fixes #563. diff --git a/.changeset/node24-support-drop-node20.md b/.changeset/node24-support-drop-node20.md new file mode 100644 index 00000000..ecf0c426 --- /dev/null +++ b/.changeset/node24-support-drop-node20.md @@ -0,0 +1,13 @@ +--- +'@twilio-labs/serverless-twilio-runtime': major +'create-twilio-function': major +'@twilio-labs/plugin-serverless': major +'@twilio-labs/plugin-assets': major +'twilio-run': major +--- + +**WHAT**: Add Node.js 24 to supported versions and remove Node.js 20. + +**WHY**: `node24` was added as a deploy runtime and made the default in #555, but `engines.node` was never widened, so the toolkit defaulted to deploying to node24 while refusing to declare support for running on Node 24 (#563). Node.js 20 is deprecated on Twilio Serverless and can no longer be used for new builds, so it is removed at the same time. + +**BREAKING CHANGE**: Projects using `create-twilio-function`, `@twilio-labs/plugin-assets`, `@twilio-labs/plugin-serverless`, `@twilio-labs/serverless-twilio-runtime` or `twilio-run` will have to migrate to Node.js 22 or 24. diff --git a/.github/workflows/on-merge-main.yml b/.github/workflows/on-merge-main.yml index 1d363abb..e64ea07d 100644 --- a/.github/workflows/on-merge-main.yml +++ b/.github/workflows/on-merge-main.yml @@ -21,10 +21,10 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - name: Setup Node.js 20 + - name: Setup Node.js 22 uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 with: - node-version: 20 + node-version: 22 - name: Update npm to 10.x run: | npm i -g npm@10 diff --git a/.nvmrc b/.nvmrc index 209e3ef4..2bd5a0a9 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 +22 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index cfa7ff9d..b0ba9f35 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -14,8 +14,8 @@ If you are planning to contribute something that does not have an open issue yet ## Requirements -Make sure you have Node.js 20 or newer installed. Due to compatibility with Twilio -Functions this project has to support at least Node.js 20.0.0. +Make sure you have Node.js 22 or newer installed. Due to compatibility with Twilio +Functions this project has to support at least Node.js 22.0.0. We are using the npm CLI to manage our project. You'll need at least `npm` version 8 or newer. diff --git a/packages/create-twilio-function/package.json b/packages/create-twilio-function/package.json index eb5d8b91..96012323 100644 --- a/packages/create-twilio-function/package.json +++ b/packages/create-twilio-function/package.json @@ -42,7 +42,7 @@ "yargs": "^17.2.1" }, "engines": { - "node": "^20.x || ^22.x || ^24.x" + "node": "^22.x || ^24.x" }, "files": [ "bin/", diff --git a/packages/create-twilio-function/src/create-twilio-function/versions.js b/packages/create-twilio-function/src/create-twilio-function/versions.js index 134c8acd..056ebaf7 100644 --- a/packages/create-twilio-function/src/create-twilio-function/versions.js +++ b/packages/create-twilio-function/src/create-twilio-function/versions.js @@ -6,7 +6,7 @@ module.exports = { '@twilio/runtime-handler' ].replace(/[\^~]/, ''), twilioRun: pkgJson.dependencies['twilio-run'], - node: '^20.x || ^22.x || ^24.x', + node: '^22.x || ^24.x', typescript: '^5.3.3', serverlessRuntimeTypes: '^4.0.0', copyfiles: '^2.4.1', diff --git a/packages/plugin-assets/package.json b/packages/plugin-assets/package.json index ad697859..0f98c142 100644 --- a/packages/plugin-assets/package.json +++ b/packages/plugin-assets/package.json @@ -23,7 +23,7 @@ "rimraf": "^5.0.5" }, "engines": { - "node": "^20.x || ^22.x || ^24.x" + "node": "^22.x || ^24.x" }, "files": [ "/oclif.manifest.json", diff --git a/packages/plugin-serverless/package.json b/packages/plugin-serverless/package.json index d808069f..030b7116 100644 --- a/packages/plugin-serverless/package.json +++ b/packages/plugin-serverless/package.json @@ -22,7 +22,7 @@ "oclif": "^4.0.0" }, "engines": { - "node": "^20.x || ^22.x || ^24.x" + "node": "^22.x || ^24.x" }, "files": [ "/oclif.manifest.json", diff --git a/packages/serverless-twilio-runtime/Readme.md b/packages/serverless-twilio-runtime/Readme.md index c394493c..74b50695 100644 --- a/packages/serverless-twilio-runtime/Readme.md +++ b/packages/serverless-twilio-runtime/Readme.md @@ -8,7 +8,7 @@ Serverless Framework plugin to deploy to the Twilio Runtime. ### Pre-requisites -- Node.js v20.x (this is the runtime version supported by Twilio Functions) +- Node.js v22.x or v24.x (these are the runtime versions supported by Twilio Functions) - Serverless CLI v1.50.0+. You can run npm i -g serverless if you don't already have it. - A Twilio account. If you don't have one you can [sign up quickly](https://www.twilio.com/try-twilio). diff --git a/packages/twilio-run/package.json b/packages/twilio-run/package.json index d07769fe..5058131e 100644 --- a/packages/twilio-run/package.json +++ b/packages/twilio-run/package.json @@ -122,7 +122,7 @@ "README.md" ], "engines": { - "node": "^20.x || ^22.x || ^24.x" + "node": "^22.x || ^24.x" }, "gitHead": "6db273648ed19474f4125042556b10c051529912" } diff --git a/packages/twilio-run/src/checks/nodejs-version.ts b/packages/twilio-run/src/checks/nodejs-version.ts index b68515ae..6ae9a5dd 100644 --- a/packages/twilio-run/src/checks/nodejs-version.ts +++ b/packages/twilio-run/src/checks/nodejs-version.ts @@ -1,7 +1,7 @@ import { stripIndent } from 'common-tags'; import { logger } from '../utils/logger'; -const SERVERLESS_NODE_JS_VERSION = ['20.', '22.', '24.']; +const SERVERLESS_NODE_JS_VERSION = ['22.', '24.']; export function printVersionWarning( nodeVersion: string, From 722398cb1a7704ee9cdb11407a0e2905e18f780c Mon Sep 17 00:00:00 2001 From: Andre Lars Da Cunha Date: Thu, 17 Sep 2026 13:16:14 +0200 Subject: [PATCH 3/5] feat: warn on deploy when configured runtime is unsupported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FNA-1642 Removing node20 from the supported range does not help existing projects whose .twilioserverlessrc still pins "runtime": "node20" — the CLI keeps sending it and the build fails at the platform with no local hint as to why. Add a `checkForDeprecatedRuntime` check, run from `twilio-run deploy`, that warns when the resolved runtime is not one of node22/node24 and points at the config field to change. It warns rather than fails: the platform remains the authority on which runtimes it accepts, and an unset runtime stays silent. Co-Authored-By: Claude Opus 5 --- .changeset/node24-support-drop-node20.md | 2 + .../checks/deprecated-runtime.test.ts | 48 +++++++++++++++++++ .../src/checks/deprecated-runtime.ts | 30 ++++++++++++ packages/twilio-run/src/commands/deploy.ts | 3 ++ 4 files changed, 83 insertions(+) create mode 100644 packages/twilio-run/__tests__/checks/deprecated-runtime.test.ts create mode 100644 packages/twilio-run/src/checks/deprecated-runtime.ts diff --git a/.changeset/node24-support-drop-node20.md b/.changeset/node24-support-drop-node20.md index ecf0c426..9a72093d 100644 --- a/.changeset/node24-support-drop-node20.md +++ b/.changeset/node24-support-drop-node20.md @@ -8,6 +8,8 @@ **WHAT**: Add Node.js 24 to supported versions and remove Node.js 20. +**ALSO**: `twilio-run deploy` now warns when a project is configured to deploy to an unsupported runtime such as `node20`, pointing at the `runtime` field in `.twilioserverlessrc`. It is a warning, not a failure — the deploy still proceeds and the platform has the final say. + **WHY**: `node24` was added as a deploy runtime and made the default in #555, but `engines.node` was never widened, so the toolkit defaulted to deploying to node24 while refusing to declare support for running on Node 24 (#563). Node.js 20 is deprecated on Twilio Serverless and can no longer be used for new builds, so it is removed at the same time. **BREAKING CHANGE**: Projects using `create-twilio-function`, `@twilio-labs/plugin-assets`, `@twilio-labs/plugin-serverless`, `@twilio-labs/serverless-twilio-runtime` or `twilio-run` will have to migrate to Node.js 22 or 24. diff --git a/packages/twilio-run/__tests__/checks/deprecated-runtime.test.ts b/packages/twilio-run/__tests__/checks/deprecated-runtime.test.ts new file mode 100644 index 00000000..04bc2d15 --- /dev/null +++ b/packages/twilio-run/__tests__/checks/deprecated-runtime.test.ts @@ -0,0 +1,48 @@ +import { mocked } from 'jest-mock'; +import { checkForDeprecatedRuntime } from '../../src/checks/deprecated-runtime'; +import { logger } from '../../src/utils/logger'; + +jest.mock('../../src/utils/logger', () => { + return { + logger: { + warn: jest.fn(), + }, + }; +}); + +describe('checkForDeprecatedRuntime', () => { + beforeEach(() => { + mocked(logger.warn).mockClear(); + }); + + test('should not warn for supported runtimes', () => { + ['node22', 'node24'].forEach((runtime) => { + expect(checkForDeprecatedRuntime(runtime)).toEqual(true); + }); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + test('should not warn when no runtime is configured', () => { + expect(checkForDeprecatedRuntime(undefined)).toEqual(true); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + test('should warn for deprecated runtimes', () => { + expect(checkForDeprecatedRuntime('node20')).toEqual(false); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(mocked(logger.warn).mock.calls[0][0]).toContain('node20'); + }); + + test('should warn for unknown runtime values', () => { + expect(checkForDeprecatedRuntime('node18')).toEqual(false); + expect(checkForDeprecatedRuntime('nope')).toEqual(false); + expect(logger.warn).toHaveBeenCalledTimes(2); + }); + + test('should not exit the process', () => { + const exit = jest.spyOn(process, 'exit').mockImplementation(); + checkForDeprecatedRuntime('node20'); + expect(exit).not.toHaveBeenCalled(); + exit.mockRestore(); + }); +}); diff --git a/packages/twilio-run/src/checks/deprecated-runtime.ts b/packages/twilio-run/src/checks/deprecated-runtime.ts new file mode 100644 index 00000000..9c271729 --- /dev/null +++ b/packages/twilio-run/src/checks/deprecated-runtime.ts @@ -0,0 +1,30 @@ +import { stripIndent } from 'common-tags'; +import { logger } from '../utils/logger'; + +export const SUPPORTED_RUNTIMES = ['node22', 'node24']; + +/** + * Warns if the runtime a project is configured to deploy to is no longer + * supported by Twilio Serverless. Deprecated runtimes are rejected for new + * builds, so this surfaces the problem before the deploy fails. + */ +export function checkForDeprecatedRuntime(runtime?: string): boolean { + if (typeof runtime !== 'string' || SUPPORTED_RUNTIMES.includes(runtime)) { + return true; + } + + const title = 'Unsupported Node.js Runtime Configured'; + const msg = stripIndent` + Your project is configured to deploy to "${runtime}", which is not supported by Twilio Serverless and will be rejected for new builds. + + Supported runtimes are ${SUPPORTED_RUNTIMES.join(' and ')}. + + Please update the "runtime" field in your .twilioserverlessrc (or pass --runtime=${ + SUPPORTED_RUNTIMES[SUPPORTED_RUNTIMES.length - 1] + }) to deploy successfully. + `; + + logger.warn(msg, title); + + return false; +} diff --git a/packages/twilio-run/src/commands/deploy.ts b/packages/twilio-run/src/commands/deploy.ts index 62034037..8e70259c 100644 --- a/packages/twilio-run/src/commands/deploy.ts +++ b/packages/twilio-run/src/commands/deploy.ts @@ -5,6 +5,7 @@ import path from 'path'; import { Argv } from 'yargs'; import { checkConfigForCredentials } from '../checks/check-credentials'; import { checkForValidRuntimeHandlerVersion } from '../checks/check-runtime-handler'; +import { checkForDeprecatedRuntime } from '../checks/deprecated-runtime'; import checkLegacyConfig from '../checks/legacy-config'; import checkProjectStructure from '../checks/project-structure'; import { @@ -111,6 +112,8 @@ export async function handler( return; } + checkForDeprecatedRuntime(config.runtime); + debug('Deploy Config %P', config); checkConfigForCredentials(config); From a8c4b2aca58dbcff67e5b72178467986ee6ec173 Mon Sep 17 00:00:00 2001 From: Andre Lars Da Cunha Date: Fri, 18 Sep 2026 14:35:06 +0200 Subject: [PATCH 4/5] refactor: use a deny list of deprecated Node.js versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FNA-1642 Review feedback: an allow list of supported runtimes baked into the CLI diverges from the platform as soon as a new runtime is added. A project pinned to an older toolkit would tell users their valid node26 config "is not supported" — the client confidently contradicting the platform. Invert both checks to deny lists of deprecated versions. A deprecation is a settled fact, so an outdated deny list stays correct: at worst it misses a newer deprecation and the platform rejects the build as it would have anyway. Unknown and future values are left alone. - `deprecated-runtime.ts` lists every even major up to 20 and no longer warns on unrecognised runtime values - `nodejs-version.ts` compares the local major against the same list instead of an allow list of supported versions - `printVersionWarning` drops its now-unused expectedVersion parameter - add the missing test coverage for `checkNodejsVersion` Co-Authored-By: Claude Opus 5 --- .../checks/deprecated-runtime.test.ts | 54 ++++++++++++++---- .../__tests__/checks/nodejs-version.test.ts | 57 +++++++++++++++++++ .../src/checks/deprecated-runtime.ts | 35 ++++++++---- .../twilio-run/src/checks/nodejs-version.ts | 25 ++++---- 4 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 packages/twilio-run/__tests__/checks/nodejs-version.test.ts diff --git a/packages/twilio-run/__tests__/checks/deprecated-runtime.test.ts b/packages/twilio-run/__tests__/checks/deprecated-runtime.test.ts index 04bc2d15..5f5ebcea 100644 --- a/packages/twilio-run/__tests__/checks/deprecated-runtime.test.ts +++ b/packages/twilio-run/__tests__/checks/deprecated-runtime.test.ts @@ -1,5 +1,8 @@ import { mocked } from 'jest-mock'; -import { checkForDeprecatedRuntime } from '../../src/checks/deprecated-runtime'; +import { + checkForDeprecatedRuntime, + DEPRECATED_RUNTIMES, +} from '../../src/checks/deprecated-runtime'; import { logger } from '../../src/utils/logger'; jest.mock('../../src/utils/logger', () => { @@ -15,28 +18,55 @@ describe('checkForDeprecatedRuntime', () => { mocked(logger.warn).mockClear(); }); - test('should not warn for supported runtimes', () => { + test('should warn for every deprecated runtime', () => { + DEPRECATED_RUNTIMES.forEach((runtime) => { + expect(checkForDeprecatedRuntime(runtime)).toEqual(false); + }); + expect(logger.warn).toHaveBeenCalledTimes(DEPRECATED_RUNTIMES.length); + }); + + test('should list every even major up to and including 20', () => { + expect(DEPRECATED_RUNTIMES).toEqual([ + 'node4', + 'node6', + 'node8', + 'node10', + 'node12', + 'node14', + 'node16', + 'node18', + 'node20', + ]); + }); + + test('should name the configured runtime in the warning', () => { + checkForDeprecatedRuntime('node20'); + expect(mocked(logger.warn).mock.calls[0][0]).toContain('node20'); + }); + + test('should not warn for currently supported runtimes', () => { ['node22', 'node24'].forEach((runtime) => { expect(checkForDeprecatedRuntime(runtime)).toEqual(true); }); expect(logger.warn).not.toHaveBeenCalled(); }); - test('should not warn when no runtime is configured', () => { - expect(checkForDeprecatedRuntime(undefined)).toEqual(true); + test('should not warn for runtimes newer than this release knows about', () => { + // An older toolkit must not claim a future runtime is invalid. + ['node26', 'node28', 'node30'].forEach((runtime) => { + expect(checkForDeprecatedRuntime(runtime)).toEqual(true); + }); expect(logger.warn).not.toHaveBeenCalled(); }); - test('should warn for deprecated runtimes', () => { - expect(checkForDeprecatedRuntime('node20')).toEqual(false); - expect(logger.warn).toHaveBeenCalledTimes(1); - expect(mocked(logger.warn).mock.calls[0][0]).toContain('node20'); + test('should not warn when no runtime is configured', () => { + expect(checkForDeprecatedRuntime(undefined)).toEqual(true); + expect(logger.warn).not.toHaveBeenCalled(); }); - test('should warn for unknown runtime values', () => { - expect(checkForDeprecatedRuntime('node18')).toEqual(false); - expect(checkForDeprecatedRuntime('nope')).toEqual(false); - expect(logger.warn).toHaveBeenCalledTimes(2); + test('should leave unrecognised values to the platform', () => { + expect(checkForDeprecatedRuntime('nope')).toEqual(true); + expect(logger.warn).not.toHaveBeenCalled(); }); test('should not exit the process', () => { diff --git a/packages/twilio-run/__tests__/checks/nodejs-version.test.ts b/packages/twilio-run/__tests__/checks/nodejs-version.test.ts new file mode 100644 index 00000000..a5d0ffa6 --- /dev/null +++ b/packages/twilio-run/__tests__/checks/nodejs-version.test.ts @@ -0,0 +1,57 @@ +import { mocked } from 'jest-mock'; +import checkNodejsVersion from '../../src/checks/nodejs-version'; +import { logger } from '../../src/utils/logger'; + +jest.mock('../../src/utils/logger', () => { + return { + logger: { + warn: jest.fn(), + }, + }; +}); + +const originalVersion = process.versions.node; + +function setNodeVersion(version: string) { + Object.defineProperty(process.versions, 'node', { + value: version, + configurable: true, + }); +} + +describe('checkNodejsVersion', () => { + beforeEach(() => { + mocked(logger.warn).mockClear(); + }); + + afterEach(() => { + setNodeVersion(originalVersion); + }); + + test('should warn on deprecated Node.js versions', () => { + ['20.11.1', '18.19.0', '16.20.2'].forEach((version) => { + mocked(logger.warn).mockClear(); + setNodeVersion(version); + checkNodejsVersion(); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(mocked(logger.warn).mock.calls[0][0]).toContain(version); + }); + }); + + test('should not warn on currently supported Node.js versions', () => { + ['22.11.0', '24.0.0'].forEach((version) => { + setNodeVersion(version); + checkNodejsVersion(); + }); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + test('should not warn on Node.js versions newer than this release knows about', () => { + // An older toolkit must not tell users a newer Node.js version is invalid. + ['26.0.0', '28.1.0'].forEach((version) => { + setNodeVersion(version); + checkNodejsVersion(); + }); + expect(logger.warn).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/twilio-run/src/checks/deprecated-runtime.ts b/packages/twilio-run/src/checks/deprecated-runtime.ts index 9c271729..e3211356 100644 --- a/packages/twilio-run/src/checks/deprecated-runtime.ts +++ b/packages/twilio-run/src/checks/deprecated-runtime.ts @@ -1,27 +1,38 @@ import { stripIndent } from 'common-tags'; import { logger } from '../utils/logger'; -export const SUPPORTED_RUNTIMES = ['node22', 'node24']; +/** + * Node.js major versions that are no longer accepted by Twilio Serverless. + * + * This is deliberately a deny list rather than an allow list of supported + * versions: a project pinned to an older version of the toolkit must not warn + * about a runtime that became valid after it was published. A deprecation is a + * settled fact, so an outdated deny list stays correct — at worst it misses a + * newer deprecation, in which case the platform still rejects the build. + */ +export const DEPRECATED_NODE_MAJORS = [4, 6, 8, 10, 12, 14, 16, 18, 20]; + +export const DEPRECATED_RUNTIMES = DEPRECATED_NODE_MAJORS.map( + (major) => `node${major}` +); /** - * Warns if the runtime a project is configured to deploy to is no longer - * supported by Twilio Serverless. Deprecated runtimes are rejected for new - * builds, so this surfaces the problem before the deploy fails. + * Warns if a project is configured to deploy to a runtime that Twilio + * Serverless no longer accepts for new builds, pointing at the field to + * change. Twilio Serverless remains the authority on which runtimes it + * accepts, so this only surfaces the problem earlier and never blocks a + * deploy. Unknown or future runtime values are left alone. */ export function checkForDeprecatedRuntime(runtime?: string): boolean { - if (typeof runtime !== 'string' || SUPPORTED_RUNTIMES.includes(runtime)) { + if (typeof runtime !== 'string' || !DEPRECATED_RUNTIMES.includes(runtime)) { return true; } - const title = 'Unsupported Node.js Runtime Configured'; + const title = 'Deprecated Node.js Runtime Configured'; const msg = stripIndent` - Your project is configured to deploy to "${runtime}", which is not supported by Twilio Serverless and will be rejected for new builds. - - Supported runtimes are ${SUPPORTED_RUNTIMES.join(' and ')}. + Your project is configured to deploy to "${runtime}", which is deprecated and is no longer accepted for new builds. - Please update the "runtime" field in your .twilioserverlessrc (or pass --runtime=${ - SUPPORTED_RUNTIMES[SUPPORTED_RUNTIMES.length - 1] - }) to deploy successfully. + Please update the "runtime" field in your .twilioserverlessrc (or pass the --runtime flag) to a version that is still supported. `; logger.warn(msg, title); diff --git a/packages/twilio-run/src/checks/nodejs-version.ts b/packages/twilio-run/src/checks/nodejs-version.ts index 6ae9a5dd..c9f943c4 100644 --- a/packages/twilio-run/src/checks/nodejs-version.ts +++ b/packages/twilio-run/src/checks/nodejs-version.ts @@ -1,15 +1,11 @@ import { stripIndent } from 'common-tags'; import { logger } from '../utils/logger'; +import { DEPRECATED_NODE_MAJORS } from './deprecated-runtime'; -const SERVERLESS_NODE_JS_VERSION = ['22.', '24.']; - -export function printVersionWarning( - nodeVersion: string, - expectedVersion: string[] -): void { - const title = 'Different Node.js Version Found'; +export function printVersionWarning(nodeVersion: string): void { + const title = 'Deprecated Node.js Version Found'; const msg = stripIndent` - You are currently running Node.js ${nodeVersion} on this local machine. The production environment for Twilio Serverless currently supports versions ${expectedVersion}x. + You are currently running Node.js ${nodeVersion} on this local machine. This version is deprecated and is no longer supported by the production environment for Twilio Serverless. When you deploy to Twilio Serverless, you may encounter differences between local development and production. @@ -22,11 +18,12 @@ export function printVersionWarning( export default function checkNodejsVersion() { const nodeVersion = process.versions.node; - if ( - !SERVERLESS_NODE_JS_VERSION.some((nodeJsVersion) => - nodeVersion.startsWith(nodeJsVersion) - ) - ) { - printVersionWarning(nodeVersion, SERVERLESS_NODE_JS_VERSION); + const major = Number.parseInt(nodeVersion.split('.')[0], 10); + + // A deny list of deprecated majors rather than an allow list of supported + // ones, so that an older toolkit release never warns about a Node.js version + // that became supported after it was published. + if (DEPRECATED_NODE_MAJORS.includes(major)) { + printVersionWarning(nodeVersion); } } From 596284092ff3a66661f7ca49323d18ed307600c1 Mon Sep 17 00:00:00 2001 From: Andre Lars Da Cunha Date: Fri, 18 Sep 2026 14:42:28 +0200 Subject: [PATCH 5/5] docs: note the deny list approach in the changeset FNA-1642 Co-Authored-By: Claude Opus 5 --- .changeset/node24-support-drop-node20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/node24-support-drop-node20.md b/.changeset/node24-support-drop-node20.md index 9a72093d..a743e59d 100644 --- a/.changeset/node24-support-drop-node20.md +++ b/.changeset/node24-support-drop-node20.md @@ -8,7 +8,7 @@ **WHAT**: Add Node.js 24 to supported versions and remove Node.js 20. -**ALSO**: `twilio-run deploy` now warns when a project is configured to deploy to an unsupported runtime such as `node20`, pointing at the `runtime` field in `.twilioserverlessrc`. It is a warning, not a failure — the deploy still proceeds and the platform has the final say. +**ALSO**: `twilio-run deploy` now warns when a project is configured to deploy to a deprecated runtime such as `node20`, pointing at the `runtime` field in `.twilioserverlessrc`. It is a warning, not a failure — the deploy still proceeds and Twilio Serverless has the final say. Both this check and the local Node.js version check use a deny list of deprecated versions rather than an allow list of supported ones, so an older release of the toolkit never claims a newer Node.js version is invalid. `printVersionWarning` drops its unused `expectedVersion` parameter as part of this. **WHY**: `node24` was added as a deploy runtime and made the default in #555, but `engines.node` was never widened, so the toolkit defaulted to deploying to node24 while refusing to declare support for running on Node 24 (#563). Node.js 20 is deprecated on Twilio Serverless and can no longer be used for new builds, so it is removed at the same time.