diff --git a/.changeset/great-bags-smoke.md b/.changeset/great-bags-smoke.md new file mode 100644 index 00000000..1ad40d6b --- /dev/null +++ b/.changeset/great-bags-smoke.md @@ -0,0 +1,5 @@ +--- +'sv': patch +--- + +feat: print warning if using Node.js version below 18.3 diff --git a/packages/cli/utils/common.ts b/packages/cli/utils/common.ts index b2282f97..c8d86a23 100644 --- a/packages/cli/utils/common.ts +++ b/packages/cli/utils/common.ts @@ -4,6 +4,7 @@ import * as p from '@clack/prompts'; import type { Argument, HelpConfiguration, Option } from 'commander'; import { UnsupportedError } from './errors.ts'; import process from 'node:process'; +import { isVersionUnsupportedBelow } from '@sveltejs/cli-core'; const NO_PREFIX = '--no-'; let options: readonly Option[] = []; @@ -75,6 +76,15 @@ type MaybePromise = () => Promise | void; export async function runCommand(action: MaybePromise): Promise { try { p.intro(`Welcome to the Svelte CLI! ${pc.gray(`(v${pkg.version})`)}`); + + const minimumVersion = '18.3.0'; + const unsupported = isVersionUnsupportedBelow(process.versions.node, minimumVersion); + if (unsupported) { + p.log.warn( + `You are using Node.js ${pc.red(process.versions.node)}, please upgrade to Node.js ${pc.green(minimumVersion)} or higher.` + ); + } + await action(); p.outro("You're all set!"); } catch (e) { diff --git a/packages/core/common.ts b/packages/core/common.ts new file mode 100644 index 00000000..dfdd50fc --- /dev/null +++ b/packages/core/common.ts @@ -0,0 +1,48 @@ +type Version = { + major?: number; + minor?: number; + patch?: number; +}; + +export function splitVersion(str: string): Version { + const [major, minor, patch] = str?.split('.') ?? []; + + function toVersionNumber(val: string | undefined): number | undefined { + return val !== undefined && val !== '' && !isNaN(Number(val)) ? Number(val) : undefined; + } + + return { + major: toVersionNumber(major), + minor: toVersionNumber(minor), + patch: toVersionNumber(patch) + }; +} + +export function isVersionUnsupportedBelow( + versionStr: string, + belowStr: string +): boolean | undefined { + const version = splitVersion(versionStr); + const below = splitVersion(belowStr); + + if (version.major === undefined || below.major === undefined) return undefined; + if (version.major < below.major) return true; + if (version.major > below.major) return false; + + if (version.minor === undefined || below.minor === undefined) { + if (version.major === below.major) return false; + else return true; + } + if (version.minor < below.minor) return true; + if (version.minor > below.minor) return false; + + if (version.patch === undefined || below.patch === undefined) { + if (version.minor === below.minor) return false; + else return true; + } + if (version.patch < below.patch) return true; + if (version.patch > below.patch) return false; + if (version.patch === below.patch) return false; + + return undefined; +} diff --git a/packages/core/index.ts b/packages/core/index.ts index e22448cb..92fed5cf 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -3,6 +3,7 @@ export { log } from '@clack/prompts'; export { default as colors } from 'picocolors'; export { default as dedent } from 'dedent'; export * as utils from './utils.ts'; +export { isVersionUnsupportedBelow, splitVersion } from './common.ts'; export type * from './addon/processors.ts'; export type * from './addon/options.ts'; diff --git a/packages/core/tests/common.ts b/packages/core/tests/common.ts new file mode 100644 index 00000000..100f7ad9 --- /dev/null +++ b/packages/core/tests/common.ts @@ -0,0 +1,48 @@ +import { expect, describe, it } from 'vitest'; +import { splitVersion, isVersionUnsupportedBelow } from '../common.ts'; + +describe('versionSplit', () => { + const combinationsVersionSplit = [ + { version: '18.13.0', expected: { major: 18, minor: 13, patch: 0 } }, + { version: 'x.13.0', expected: { major: undefined, minor: 13, patch: 0 } }, + { version: '18.y.0', expected: { major: 18, minor: undefined, patch: 0 } }, + { version: '18.13.z', expected: { major: 18, minor: 13, patch: undefined } }, + { version: '18', expected: { major: 18, minor: undefined, patch: undefined } }, + { version: '18.13', expected: { major: 18, minor: 13, patch: undefined } }, + { version: 'invalid', expected: { major: undefined, minor: undefined, patch: undefined } } + ]; + it.each(combinationsVersionSplit)( + 'should return the correct version for $version', + ({ version, expected }) => { + expect(splitVersion(version)).toEqual(expected); + } + ); +}); + +describe('minimumRequirement', () => { + const combinationsMinimumRequirement = [ + { version: '17', below: '18.3.0', expected: true }, + { version: '18.2', below: '18.3.0', expected: true }, + { version: '18.3.0', below: '18.3.1', expected: true }, + { version: '18.3.1', below: '18.3.0', expected: false }, + { version: '18.3.0', below: '18.3.0', expected: false }, + { version: '18.3.0', below: '18.3', expected: false }, + { version: '18.3.1', below: '18.3', expected: false }, + { version: '18.3.1', below: '18', expected: false }, + { version: '18', below: '18', expected: false }, + { version: 'a', below: 'b', expected: undefined }, + { version: '18.3', below: '18.3', expected: false }, + { version: '18.4', below: '18.3', expected: false }, + { version: '18.2', below: '18.3', expected: true }, + + // if it's undefined, we can't say anything... + { version: undefined!, below: '18.3', expected: undefined }, + { version: '', below: '18.3', expected: undefined } + ] as const; + it.each(combinationsMinimumRequirement)( + '($version below $below) should be $expected', + ({ version, below, expected }) => { + expect(isVersionUnsupportedBelow(version, below)).toEqual(expected); + } + ); +});