feat(nodejs)!: remove v18 & < 20.10 support

added support for node 24
This commit is contained in:
Todd Bluhm 2025-07-02 00:53:04 -08:00
parent a08ac87f41
commit 33d0ac3461
No known key found for this signature in database
GPG Key ID: 9CF312607477B8AB
13 changed files with 15 additions and 41 deletions

View File

@ -11,7 +11,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [18.x, 20.x, 22.x] node-version: [20.x, 22.x, 24.x]
steps: steps:
- name: Checkout Project - name: Checkout Project

View File

@ -12,7 +12,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [18.x, 20.x, 22.x] node-version: [20.x, 22.x, 24.x]
steps: steps:
- name: Checkout Project - name: Checkout Project

5
dist/parse-args.js vendored
View File

@ -1,9 +1,6 @@
import { createRequire } from 'node:module';
import { Command } from '@commander-js/extra-typings'; import { Command } from '@commander-js/extra-typings';
import { parseArgList } from './utils.js'; import { parseArgList } from './utils.js';
// TODO: once we drop support for node <v20.10, this can be converted to import packageJson from '../package.json' with { type: 'json' };
// a normal import statement
const packageJson = createRequire(import.meta.url)('../package.json');
/** /**
* Parses the arguments passed into the cli * Parses the arguments passed into the cli
*/ */

View File

@ -1,7 +1,7 @@
import { existsSync, readFileSync } from 'node:fs'; import { existsSync, readFileSync } from 'node:fs';
import { extname } from 'node:path'; import { extname } from 'node:path';
import { pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise, importAttributesKeyword } from './utils.js'; import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js';
/** /**
* Gets the environment vars from an env file * Gets the environment vars from an env file
*/ */
@ -19,7 +19,7 @@ export async function getEnvFileVars(envFilePath) {
// For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them
let attributeTypes = {}; let attributeTypes = {};
if (ext === '.json') { if (ext === '.json') {
attributeTypes = { [importAttributesKeyword]: { type: 'json' } }; attributeTypes = { with: { type: 'json' } };
} }
const res = await import(pathToFileURL(absolutePath).href, attributeTypes); const res = await import(pathToFileURL(absolutePath).href, attributeTypes);
if (typeof res === 'object' && res && 'default' in res) { if (typeof res === 'object' && res && 'default' in res) {

View File

@ -2,7 +2,7 @@ import { stat, readFile } from 'node:fs';
import { promisify } from 'node:util'; import { promisify } from 'node:util';
import { extname } from 'node:path'; import { extname } from 'node:path';
import { pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise, importAttributesKeyword } from './utils.js'; import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js';
const statAsync = promisify(stat); const statAsync = promisify(stat);
const readFileAsync = promisify(readFile); const readFileAsync = promisify(readFile);
/** /**
@ -26,7 +26,7 @@ export async function getRCFileVars({ environments, filePath }) {
// For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them
let attributeTypes = {}; let attributeTypes = {};
if (ext === '.json') { if (ext === '.json') {
attributeTypes = { [importAttributesKeyword]: { type: 'json' } }; attributeTypes = { with: { type: 'json' } };
} }
const res = await import(pathToFileURL(absolutePath).href, attributeTypes); const res = await import(pathToFileURL(absolutePath).href, attributeTypes);
if ('default' in res) { if ('default' in res) {

1
dist/utils.d.ts vendored
View File

@ -11,4 +11,3 @@ export declare function parseArgList(list: string): string[];
* A simple function to test if the value is a promise/thenable * A simple function to test if the value is a promise/thenable
*/ */
export declare function isPromise<T>(value?: T | PromiseLike<T>): value is PromiseLike<T>; export declare function isPromise<T>(value?: T | PromiseLike<T>): value is PromiseLike<T>;
export declare const importAttributesKeyword: string;

6
dist/utils.js vendored
View File

@ -29,9 +29,3 @@ export function isPromise(value) {
&& 'then' in value && 'then' in value
&& typeof value.then === 'function'; && typeof value.then === 'function';
} }
// "Import Attributes" are only supported since node v18.20 and v20.10.
// For older node versions, we have to use "Import Assertions".
// TODO: remove this check when we drop support for node v20
const [major, minor] = process.version.slice(1).split('.').map(Number);
const legacyImportAssertions = (major === 18 && minor < 20) || (major === 20 && minor < 10);
export const importAttributesKeyword = legacyImportAssertions ? 'assert' : 'with';

View File

@ -6,7 +6,7 @@
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=20.10.0"
}, },
"bin": { "bin": {
"env-cmd": "bin/env-cmd.js" "env-cmd": "bin/env-cmd.js"

View File

@ -1,13 +1,7 @@
import { createRequire } from 'node:module'
import { Command } from '@commander-js/extra-typings' import { Command } from '@commander-js/extra-typings'
import type { EnvCmdOptions, CommanderOptions, EnvFileOptions, RCFileOptions } from './types.ts' import type { EnvCmdOptions, CommanderOptions, EnvFileOptions, RCFileOptions } from './types.ts'
import { parseArgList } from './utils.js' import { parseArgList } from './utils.js'
import packageJson from '../package.json' with { type: 'json' }
// TODO: once we drop support for node <v20.10, this can be converted to
// a normal import statement
const packageJson = createRequire(import.meta.url)('../package.json') as {
version: string;
}
/** /**
* Parses the arguments passed into the cli * Parses the arguments passed into the cli

View File

@ -1,7 +1,7 @@
import { existsSync, readFileSync } from 'node:fs' import { existsSync, readFileSync } from 'node:fs'
import { extname } from 'node:path' import { extname } from 'node:path'
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise, importAttributesKeyword } from './utils.js' import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js'
import type { Environment } from './types.ts' import type { Environment } from './types.ts'
/** /**
@ -22,7 +22,7 @@ export async function getEnvFileVars(envFilePath: string): Promise<Environment>
// For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them
let attributeTypes = {} let attributeTypes = {}
if (ext === '.json') { if (ext === '.json') {
attributeTypes = { [importAttributesKeyword]: { type: 'json' } } attributeTypes = { with: { type: 'json' } }
} }
const res: unknown = await import(pathToFileURL(absolutePath).href, attributeTypes) const res: unknown = await import(pathToFileURL(absolutePath).href, attributeTypes)
if (typeof res === 'object' && res && 'default' in res) { if (typeof res === 'object' && res && 'default' in res) {

View File

@ -2,7 +2,7 @@ import { stat, readFile } from 'node:fs'
import { promisify } from 'node:util' import { promisify } from 'node:util'
import { extname } from 'node:path' import { extname } from 'node:path'
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise, importAttributesKeyword } from './utils.js' import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js'
import type { Environment, RCEnvironment } from './types.ts' import type { Environment, RCEnvironment } from './types.ts'
const statAsync = promisify(stat) const statAsync = promisify(stat)
@ -33,7 +33,7 @@ export async function getRCFileVars(
// For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them
let attributeTypes = {} let attributeTypes = {}
if (ext === '.json') { if (ext === '.json') {
attributeTypes = { [importAttributesKeyword]: { type: 'json' } } attributeTypes = { with: { type: 'json' } }
} }
const res = await import(pathToFileURL(absolutePath).href, attributeTypes) as RCEnvironment | { default: RCEnvironment } const res = await import(pathToFileURL(absolutePath).href, attributeTypes) as RCEnvironment | { default: RCEnvironment }
if ('default' in res) { if ('default' in res) {

View File

@ -32,13 +32,3 @@ export function isPromise<T>(value?: T | PromiseLike<T>): value is PromiseLike<T
&& 'then' in value && 'then' in value
&& typeof value.then === 'function' && typeof value.then === 'function'
} }
// "Import Attributes" are only supported since node v18.20 and v20.10.
// For older node versions, we have to use "Import Assertions".
// TODO: remove this check when we drop support for node v20
const [major, minor] = process.version.slice(1).split('.').map(Number)
const legacyImportAssertions =
(major === 18 && minor < 20) || (major === 20 && minor < 10)
export const importAttributesKeyword = legacyImportAssertions ? 'assert' : 'with'

View File

@ -1,9 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"esModuleInterop": false, "esModuleInterop": true,
"lib": ["es2023"], "lib": ["es2023"],
"module": "NodeNext", "module": "nodenext",
"moduleDetection": "force", "moduleDetection": "force",
"outDir": "./dist", "outDir": "./dist",
"resolveJsonModule": true, "resolveJsonModule": true,