Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/test-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Node.js versions tested on Linux in every PR's test matrix.
# The minimum version should stay aligned with package.json `engines.node`.
# Used by: unit-tests.yml, integration-tests.yml, e2e-tests.yml
node_versions: ['20.12.2', '22', '24']
node_versions: ['22.13.0', '24']

# Pin specific install versions for entries in `node_versions` / `primary_node_version`.
node_install_overrides:
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ The CLI integrates with Netlify's build plugin system, allowing plugins to:
- Provide additional CLI commands

### Development Setup Requirements
- Node.js 20.12.2+ required
- Node.js 22.13.0+ required
- Git LFS must be installed for full test suite
- Some integration tests require Netlify Auth Token (`NETLIFY_AUTH_TOKEN`) or login via `./bin/run.js login`

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ See the [CLI command line reference](https://cli.netlify.com/#commands) to get s

## Installation

Netlify CLI requires [Node.js](https://nodejs.org) version 20.12.2 or above. To install, run the following command from
Netlify CLI requires [Node.js](https://nodejs.org) version 22.13.0 or above. To install, run the following command from
any directory in your terminal:

```bash
Expand Down
10 changes: 3 additions & 7 deletions e2e/install.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import { platform } from 'node:os'
import path from 'node:path'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'

import process from 'node:process'
Expand All @@ -15,10 +16,9 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import createDebug from 'debug'
import picomatch from 'picomatch'

import pkg from '../package.json'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const projectRoot = path.resolve(__dirname, '..')
const pkg = createRequire(import.meta.url)('../package.json') as { version: string }
const distDir = path.join(projectRoot, 'dist')
const tempdirPrefix = 'netlify-cli-e2e-test--'

Expand Down Expand Up @@ -215,11 +215,7 @@ const installTests: [packageManager: string, config: InstallTest][] = [
]

describe.each(installTests)('%s → installs the cli and runs commands without errors', (packageManager, config) => {
// Yarn v1 enforces engine constraints strictly. A transitive dep (chokidar@5) requires node >=20.19.0,
// breaking yarn installs on older Node 20.x. Node 20 EOL is April 2026, so we skip rather than override.
const yarnOnOldNode20 = packageManager === 'yarn' && process.versions.node === '20.12.2'

it.skipIf(yarnOnOldNode20)('runs the commands without errors', async () => {
it('runs the commands without errors', async () => {
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), tempdirPrefix))

try {
Expand Down
13 changes: 6 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "Netlify Inc.",
"type": "module",
"engines": {
"node": ">=20.12.2"
"node": ">=22.13.0"
},
"files": [
"/bin",
Expand Down Expand Up @@ -160,7 +160,7 @@
"@netlify/functions": "^5.3.0",
"@netlify/types": "^2.8.0",
"@sindresorhus/slugify": "^3.0.0",
"@tsconfig/node20": "20.1.0",
"@tsconfig/node22": "22.0.5",
"@tsconfig/recommended": "^1.0.13",
"@types/backoff": "^2.5.5",
"@types/content-type": "^1.1.9",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/blobs/blobs-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const blobsSet = async (
if (input) {
const inputPath = resolve(input)
try {
value = await fs.readFile(inputPath)
value = new Uint8Array(await fs.readFile(inputPath)).buffer
} catch (error) {
if (isNodeError(error) && error.code === 'ENOENT') {
return logAndThrowError(
Expand Down
6 changes: 4 additions & 2 deletions src/utils/nodejs-compile-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ export let didEnableCompileCache = false
* Keep in mind that the cache is specific to a version of netlify-cli and a version of node.js and it is stored on the
* user's disk in a temp dir. If any of these changes or the temp dir is cleared, the next run is a cold run.
*
* The programmatic API to enable this (`enableCompileCache()`) was added in node 22.8.0, but we currently support
* >=20.12.2, hence the conditional below. (For completeness, note that support via the env var was added in 22.1.0.)
* The programmatic API to enable this (`enableCompileCache()`) was added in node 22.8.0, below our minimum supported
* version, so the conditional below is defensive rather than strictly necessary. (For completeness, note that support
* via the env var was added in 22.1.0.)
*
* The Netlify CLI is often used in CI workflows. In this context, we wouldn't want the overhead of the first run
* because we almost certainly would not get any benefits on "subsequent runs". Even if the user has configured caching
Expand All @@ -30,6 +31,7 @@ export const maybeEnableCompileCache = (): void => {
// The docs recommend turning this off when running tests to generate precise coverage
if (process.env.NODE_ENV === 'test') return

// TODO(serhalp): Consider enabling unconditionally now that we require Node.js 22.13.0+.
// eslint-disable-next-line n/no-unsupported-features/node-builtins
if ('enableCompileCache' in module && typeof module.enableCompileCache === 'function') {
// eslint-disable-next-line n/no-unsupported-features/node-builtins
Expand Down
2 changes: 1 addition & 1 deletion src/utils/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ const initializeProxy = async function ({

proxyRes.on('end', async function onEnd() {
// @ts-expect-error TS(7005) FIXME: Variable 'responseData' implicitly has an 'any[]' ... Remove this comment to see the full error message
let responseBody = Buffer.concat(responseData)
let responseBody: Buffer = Buffer.concat(responseData)

let responseStatus = options.status || proxyRes.statusCode

Expand Down
4 changes: 1 addition & 3 deletions tests/integration/commands/dev/dev.config.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Buffer } from 'node:buffer'
import { version } from 'node:process'
import events from 'node:events'

import type { HandlerEvent } from '@netlify/functions'
import FormData from 'form-data'
import getPort from 'get-port'
import fetch from 'node-fetch'
import { gte } from 'semver'
import { describe, test } from 'vitest'

import { withDevServer } from '../../utils/dev-server.js'
Expand Down Expand Up @@ -459,7 +457,7 @@ describe.concurrent('commands/dev/config', () => {
})
})

test.runIf(gte(version, '20.12.2'))('should support functions with streaming responses', async (t) => {
test('should support functions with streaming responses', async (t) => {
await withSiteBuilder(t, async (builder) => {
builder
.withPackageJson({ packageJson: { dependencies: { '@netlify/functions': 'latest' } } })
Expand Down
5 changes: 1 addition & 4 deletions tests/integration/commands/dev/v2-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { version } from 'process'

import execa from 'execa'
import { gte } from 'semver'
import { describe, test } from 'vitest'

import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.js'
Expand All @@ -26,7 +23,7 @@ const setup = async ({ fixture }: { fixture: { directory: string } }) => {
await execa('npm', ['install'], { cwd: fixture.directory })
}

describe.runIf(gte(version, '20.12.2')).concurrent('v2 api', async () => {
describe.concurrent('v2 api', async () => {
await setupFixtureTests('dev-server-with-v2-functions', { devServer: true, mockApi: { routes }, setup }, () => {
test<FixtureTestContext>('should successfully be able to run v2 functions', async ({ devServer, expect }) => {
const response = await fetch(`http://localhost:${devServer!.port}/.netlify/functions/ping`)
Expand Down
5 changes: 3 additions & 2 deletions tests/integration/telemetry.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { createRequire } from 'node:module'
import { env as _env, version as nodejsVersion } from 'process'

import type { Options } from 'execa'
import execa from 'execa'
import { expect, test } from 'vitest'

import pkg from '../../package.json'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can switch these to with {type: 'json'} now in theory, but we need to upgrade prettier first.


import { callCli } from './utils/call-cli.js'
import { cliPath } from './utils/cli-path.js'
import { MockApiTestContext, withMockApi } from './utils/mock-api-vitest.js'
import { withSiteBuilder } from './utils/site-builder.js'

const pkg = createRequire(import.meta.url)('../../package.json') as { name: string; version: string }

const getCLIOptions = (apiUrl: string): Options => ({
env: {
NETLIFY_TEST_TRACK_URL: `${apiUrl}/track`,
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": ["@tsconfig/recommended", "@tsconfig/node20"],
"extends": ["@tsconfig/recommended", "@tsconfig/node22"],
"compilerOptions": {
"allowJs": true,
"downlevelIteration": true,
Expand Down
Loading