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
15 changes: 6 additions & 9 deletions packages/angular/cli/bin/ng.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,19 @@ if (rawCommandName === '--get-yargs-completions' || rawCommandName === 'completi
// This node version check ensures that extremely old versions of node are not used.
// These may not support ES2015 features such as const/let/async/await/etc.
// These would then crash with a hard to diagnose error message.
const [major] = process.versions.node.split('.', 1).map((part) => Number(part));

if (major === 23 || major === 25) {
// Allow new odd numbered releases with a warning.
if (nodeUtils.isNodeVersionSupported()) {
require('./bootstrap');
} else if (nodeUtils.isNodeVersionRunnable()) {
console.warn(
'Node.js version ' +
process.version +
' detected.\n' +
'Prior to version 27, odd numbered Node.js versions will not enter LTS status and should not be used for production.' +
' For more information, please see https://nodejs.org/en/about/previous-releases/.',
'The Angular CLI has not been officially tested on this Node.js version and it is not supported for production use.\n' +
'For more information, please see https://nodejs.org/en/about/previous-releases/.',
);

require('./bootstrap');
} else if (!nodeUtils.isNodeVersionSupported()) {
} else {
// Error and exit if less than the supported versions.
console.error(
'Node.js version ' +
Expand All @@ -69,6 +68,4 @@ if (major === 23 || major === 25) {
);

process.exitCode = 3;
} else {
require('./bootstrap');
}
48 changes: 41 additions & 7 deletions packages/angular/cli/bin/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,24 @@ var supportedNodeVersions = SUPPORTED_NODE_VERSIONS.replace(/[\^~<>=]/g, '')
});

/**
* Checks if the current Node.js version is supported.
* @returns `true` if the current Node.js version is supported, `false` otherwise.
* Checks if the current Node.js version is officially supported.
* @param {string} [currentVersion] Optional Node.js version string to check. Defaults to `process.versions.node`.
* @param {string[]} [supportedVersions] Optional supported versions array. Defaults to `supportedNodeVersions`.
* @returns {boolean} `true` if the current Node.js version is officially supported, `false` otherwise.
*/
function isNodeVersionSupported() {
if (SUPPORTED_NODE_VERSIONS.charAt(0) === '0') {
function isNodeVersionSupported(currentVersion, supportedVersions) {
if (SUPPORTED_NODE_VERSIONS.charAt(0) === '0' && !supportedVersions && !currentVersion) {
return true;
}

var parts = process.versions.node.split('.', 3).map(Number);
var parts = (currentVersion || process.versions.node).split('.', 3).map(Number);
var processMajor = parts[0];
var processMinor = parts[1];
var processPatch = parts[2];

for (var i = 0; i < supportedNodeVersions.length; i++) {
var vParts = supportedNodeVersions[i].split('.', 3).map(Number);
var versions = supportedVersions || supportedNodeVersions;
for (var i = 0; i < versions.length; i++) {
var vParts = versions[i].split('.', 3).map(Number);
var major = vParts[0];
var minor = vParts[1];
var patch = vParts[2];
Expand All @@ -56,9 +59,40 @@ function isNodeVersionSupported() {
return false;
}

/**
* Checks if the current Node.js version is runnable (newer than supported LTS or an interim odd release).
* @param {string} [currentVersion] Optional Node.js version string to check. Defaults to `process.versions.node`.
* @param {string[]} [supportedVersions] Optional supported versions array. Defaults to `supportedNodeVersions`.
* @returns {boolean} `true` if the current Node.js version is runnable, `false` otherwise.
*/
function isNodeVersionRunnable(currentVersion, supportedVersions) {
if (SUPPORTED_NODE_VERSIONS.charAt(0) === '0' && !supportedVersions && !currentVersion) {
return false;
}

var processMajor = Number((currentVersion || process.versions.node).split('.', 1)[0]);
var versions = supportedVersions || supportedNodeVersions;

var minMajor = Number(versions[0].split('.', 1)[0]);
var maxMajor = Number(versions[versions.length - 1].split('.', 1)[0]);
Comment thread
clydin marked this conversation as resolved.

// Versions newer than the maximum supported major (e.g. Node 27, 28+)
if (processMajor > maxMajor) {
return true;
}

// Odd-numbered interim releases between min and max (e.g. Node 23, 25)
if (processMajor > minMajor && processMajor % 2 === 1) {
return true;
}

return false;
}

module.exports = {
VERSION: VERSION,
SUPPORTED_NODE_VERSIONS: SUPPORTED_NODE_VERSIONS,
supportedNodeVersions: supportedNodeVersions,
isNodeVersionSupported: isNodeVersionSupported,
isNodeVersionRunnable: isNodeVersionRunnable,
};
9 changes: 8 additions & 1 deletion packages/angular/cli/src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,12 @@ declare module '#version' {
export const VERSION: string;
export const SUPPORTED_NODE_VERSIONS: string;
export const supportedNodeVersions: string[];
export function isNodeVersionSupported(): boolean;
export function isNodeVersionSupported(
currentVersion?: string,
supportedVersions?: string[],
): boolean;
export function isNodeVersionRunnable(
currentVersion?: string,
supportedVersions?: string[],
): boolean;
}
20 changes: 15 additions & 5 deletions packages/angular/cli/src/utilities/node-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,32 @@

import { SUPPORTED_NODE_VERSIONS, supportedNodeVersions } from '#version';

export { SUPPORTED_NODE_VERSIONS, supportedNodeVersions, isNodeVersionSupported } from '#version';
export {
SUPPORTED_NODE_VERSIONS,
supportedNodeVersions,
isNodeVersionSupported,
isNodeVersionRunnable,
} from '#version';

/**
* Checks if the current Node.js version is the minimum supported version.
* @param currentVersion Optional Node.js version string to check. Defaults to `process.versions.node`.
* @param supportedVersions Optional supported versions array. Defaults to `supportedNodeVersions`.
* @returns `true` if the current Node.js version is the minimum supported version, `false` otherwise.
*/
export function isNodeVersionMinSupported(): boolean {
if (SUPPORTED_NODE_VERSIONS.charAt(0) === '0') {
export function isNodeVersionMinSupported(
currentVersion = process.versions.node,
supportedVersions = supportedNodeVersions,
): boolean {
if (SUPPORTED_NODE_VERSIONS.charAt(0) === '0' && currentVersion === process.versions.node) {
// Unlike `pkg_npm`, `ts_library` which is used to run unit tests does not support substitutions.
return true;
}

const [processMajor, processMinor, processPatch] = process.versions.node
const [processMajor, processMinor, processPatch] = currentVersion
.split('.', 3)
.map((part) => Number(part));
const [major, minor, patch] = supportedNodeVersions[0].split('.', 3).map((part) => Number(part));
const [major, minor, patch] = supportedVersions[0].split('.', 3).map((part) => Number(part));
Comment thread
clydin marked this conversation as resolved.

return (
processMajor > major ||
Expand Down
96 changes: 96 additions & 0 deletions packages/angular/cli/src/utilities/node-version_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {
isNodeVersionMinSupported,
isNodeVersionRunnable,
isNodeVersionSupported,
} from './node-version';

describe('node-version', () => {
const supportedVersions = ['22.22.3', '24.15.0', '26.0.0'];

describe('isNodeVersionSupported', () => {
it('should accept valid LTS versions', () => {
expect(isNodeVersionSupported('22.22.3', supportedVersions)).toBeTrue();
expect(isNodeVersionSupported('22.23.0', supportedVersions)).toBeTrue();
expect(isNodeVersionSupported('24.15.0', supportedVersions)).toBeTrue();
expect(isNodeVersionSupported('24.16.0', supportedVersions)).toBeTrue();
expect(isNodeVersionSupported('26.0.0', supportedVersions)).toBeTrue();
expect(isNodeVersionSupported('26.1.0', supportedVersions)).toBeTrue();
});

it('should reject versions below the minimum for a supported major', () => {
expect(isNodeVersionSupported('22.22.2', supportedVersions)).toBeFalse();
expect(isNodeVersionSupported('22.0.0', supportedVersions)).toBeFalse();
expect(isNodeVersionSupported('24.14.9', supportedVersions)).toBeFalse();
expect(isNodeVersionSupported('20.18.0', supportedVersions)).toBeFalse();
});

it('should reject odd-numbered major versions', () => {
expect(isNodeVersionSupported('23.0.0', supportedVersions)).toBeFalse();
expect(isNodeVersionSupported('25.0.0', supportedVersions)).toBeFalse();
});

it('should reject versions higher than the maximum supported LTS version', () => {
expect(isNodeVersionSupported('27.0.0', supportedVersions)).toBeFalse();
expect(isNodeVersionSupported('28.0.0', supportedVersions)).toBeFalse();
expect(isNodeVersionSupported('29.1.2', supportedVersions)).toBeFalse();
});

it('should return true when unstamped (0.0.0-ENGINES-NODE)', () => {
expect(isNodeVersionSupported()).toBeTrue();
});
});

describe('isNodeVersionRunnable', () => {
it('should allow odd-numbered interim releases between min and max major', () => {
expect(isNodeVersionRunnable('23.0.0', supportedVersions)).toBeTrue();
expect(isNodeVersionRunnable('25.0.0', supportedVersions)).toBeTrue();
});

it('should allow versions higher than the maximum supported major', () => {
expect(isNodeVersionRunnable('27.0.0', supportedVersions)).toBeTrue();
expect(isNodeVersionRunnable('28.0.0', supportedVersions)).toBeTrue();
expect(isNodeVersionRunnable('29.1.2', supportedVersions)).toBeTrue();
});

it('should return false for supported LTS versions', () => {
expect(isNodeVersionRunnable('22.22.3', supportedVersions)).toBeFalse();
expect(isNodeVersionRunnable('24.15.0', supportedVersions)).toBeFalse();
expect(isNodeVersionRunnable('26.0.0', supportedVersions)).toBeFalse();
});

it('should return false for versions below the minimum major', () => {
expect(isNodeVersionRunnable('18.0.0', supportedVersions)).toBeFalse();
expect(isNodeVersionRunnable('20.0.0', supportedVersions)).toBeFalse();
expect(isNodeVersionRunnable('21.0.0', supportedVersions)).toBeFalse();
});

it('should return false for outdated minors of supported majors', () => {
expect(isNodeVersionRunnable('22.0.0', supportedVersions)).toBeFalse();
expect(isNodeVersionRunnable('24.10.0', supportedVersions)).toBeFalse();
});
});

describe('isNodeVersionMinSupported', () => {
it('should accept versions at or above the minimum supported version', () => {
expect(isNodeVersionMinSupported('22.22.3', supportedVersions)).toBeTrue();
expect(isNodeVersionMinSupported('22.25.0', supportedVersions)).toBeTrue();
expect(isNodeVersionMinSupported('23.0.0', supportedVersions)).toBeTrue();
expect(isNodeVersionMinSupported('24.0.0', supportedVersions)).toBeTrue();
expect(isNodeVersionMinSupported('28.0.0', supportedVersions)).toBeTrue();
});

it('should reject versions below the minimum supported version', () => {
expect(isNodeVersionMinSupported('22.22.2', supportedVersions)).toBeFalse();
expect(isNodeVersionMinSupported('20.0.0', supportedVersions)).toBeFalse();
expect(isNodeVersionMinSupported('18.19.0', supportedVersions)).toBeFalse();
});
});
});
Loading