diff --git a/dist/get-env-vars.js b/dist/get-env-vars.js index 5c056ef..763b9da 100644 --- a/dist/get-env-vars.js +++ b/dist/get-env-vars.js @@ -1,5 +1,6 @@ import { getRCFileVars } from './parse-rc-file.js'; import { getEnvFileVars } from './parse-env-file.js'; +import { isLoaderError } from './utils.js'; const RC_FILE_DEFAULT_LOCATIONS = ['./.env-cmdrc', './.env-cmdrc.js', './.env-cmdrc.json']; const ENV_FILE_DEFAULT_LOCATIONS = ['./.env', './.env.js', './.env.json']; export async function getEnvVars(options = {}) { @@ -28,7 +29,10 @@ export async function getEnvFile({ filePath, fallback, verbose }) { } return env; } - catch { + catch (error) { + if (isLoaderError(error)) { + throw error; + } if (verbose === true) { console.info(`Failed to find .env file at path: ${filePath}`); } diff --git a/dist/loaders/typescript.d.ts b/dist/loaders/typescript.d.ts new file mode 100644 index 0000000..78827a6 --- /dev/null +++ b/dist/loaders/typescript.d.ts @@ -0,0 +1 @@ +export declare function checkIfTypescriptSupported(): void; diff --git a/dist/loaders/typescript.js b/dist/loaders/typescript.js new file mode 100644 index 0000000..7d6d31d --- /dev/null +++ b/dist/loaders/typescript.js @@ -0,0 +1,8 @@ +export function checkIfTypescriptSupported() { + if (!process.features.typescript) { + const error = new Error('To load typescript files with env-cmd, you need to upgrade to node v23.6' + + ' or later. See https://nodejs.org/en/learn/typescript/run-natively'); + Object.assign(error, { code: 'ERR_UNKNOWN_FILE_EXTENSION' }); + throw error; + } +} diff --git a/dist/parse-env-file.js b/dist/parse-env-file.js index d2f7eed..81f6c3e 100644 --- a/dist/parse-env-file.js +++ b/dist/parse-env-file.js @@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'node:fs'; import { extname } from 'node:path'; import { pathToFileURL } from 'node:url'; import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js'; +import { checkIfTypescriptSupported } from './loaders/typescript.js'; /** * Gets the environment vars from an env file */ @@ -16,6 +17,8 @@ export async function getEnvFileVars(envFilePath) { const ext = extname(absolutePath).toLowerCase(); let env; if (IMPORT_HOOK_EXTENSIONS.includes(ext)) { + if (/tsx?$/.test(ext)) + checkIfTypescriptSupported(); // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them let attributeTypes = {}; if (ext === '.json') { diff --git a/dist/parse-rc-file.js b/dist/parse-rc-file.js index fd63997..3c650c6 100644 --- a/dist/parse-rc-file.js +++ b/dist/parse-rc-file.js @@ -3,6 +3,7 @@ import { promisify } from 'node:util'; import { extname } from 'node:path'; import { pathToFileURL } from 'node:url'; import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js'; +import { checkIfTypescriptSupported } from './loaders/typescript.js'; const statAsync = promisify(stat); const readFileAsync = promisify(readFile); /** @@ -23,6 +24,8 @@ export async function getRCFileVars({ environments, filePath }) { let parsedData = {}; try { if (IMPORT_HOOK_EXTENSIONS.includes(ext)) { + if (/tsx?$/.test(ext)) + checkIfTypescriptSupported(); // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them let attributeTypes = {}; if (ext === '.json') { diff --git a/dist/utils.d.ts b/dist/utils.d.ts index f8e85a2..4459183 100644 --- a/dist/utils.d.ts +++ b/dist/utils.d.ts @@ -11,3 +11,5 @@ export declare function parseArgList(list: string): string[]; * A simple function to test if the value is a promise/thenable */ export declare function isPromise(value?: T | PromiseLike): value is PromiseLike; +/** @returns true if the error is `ERR_UNKNOWN_FILE_EXTENSION` */ +export declare function isLoaderError(error: unknown): error is Error; diff --git a/dist/utils.js b/dist/utils.js index 7535a34..9f9bf76 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -2,7 +2,16 @@ import { resolve } from 'node:path'; import { homedir } from 'node:os'; import { cwd } from 'node:process'; // Special file extensions that node can natively import -export const IMPORT_HOOK_EXTENSIONS = ['.json', '.js', '.cjs', '.mjs']; +export const IMPORT_HOOK_EXTENSIONS = [ + '.json', + '.js', + '.cjs', + '.mjs', + '.ts', + '.mts', + '.cts', + '.tsx', +]; /** * A simple function for resolving the path the user entered */ @@ -29,3 +38,9 @@ export function isPromise(value) { && 'then' in value && typeof value.then === 'function'; } +/** @returns true if the error is `ERR_UNKNOWN_FILE_EXTENSION` */ +export function isLoaderError(error) { + return (error instanceof Error && + 'code' in error && + error.code === 'ERR_UNKNOWN_FILE_EXTENSION'); +} diff --git a/package.json b/package.json index 4e203b2..bf0a28e 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "Eric Lanehart ", "Jon Scheiding ", "serapath (Alexander Praetorius) ", + "Kyle Hensel ", "Anton Versal " ], "license": "MIT", diff --git a/src/get-env-vars.ts b/src/get-env-vars.ts index 89f2f47..ac5cf4f 100644 --- a/src/get-env-vars.ts +++ b/src/get-env-vars.ts @@ -1,6 +1,7 @@ import type { GetEnvVarOptions, Environment } from './types.ts' import { getRCFileVars } from './parse-rc-file.js' import { getEnvFileVars } from './parse-env-file.js' +import { isLoaderError } from './utils.js' const RC_FILE_DEFAULT_LOCATIONS = ['./.env-cmdrc', './.env-cmdrc.js', './.env-cmdrc.json'] const ENV_FILE_DEFAULT_LOCATIONS = ['./.env', './.env.js', './.env.json'] @@ -34,7 +35,11 @@ export async function getEnvFile( } return env } - catch { + catch (error) { + if (isLoaderError(error)) { + throw error + } + if (verbose === true) { console.info(`Failed to find .env file at path: ${filePath}`) } diff --git a/src/loaders/typescript.ts b/src/loaders/typescript.ts new file mode 100644 index 0000000..0f7e1fa --- /dev/null +++ b/src/loaders/typescript.ts @@ -0,0 +1,10 @@ +export function checkIfTypescriptSupported() { + if (!process.features.typescript) { + const error = new Error( + 'To load typescript files with env-cmd, you need to upgrade to node v23.6' + + ' or later. See https://nodejs.org/en/learn/typescript/run-natively', + ); + Object.assign(error, { code: 'ERR_UNKNOWN_FILE_EXTENSION' }); + throw error; + } +} diff --git a/src/parse-env-file.ts b/src/parse-env-file.ts index e6aa8eb..a646cf8 100644 --- a/src/parse-env-file.ts +++ b/src/parse-env-file.ts @@ -3,6 +3,7 @@ import { extname } from 'node:path' import { pathToFileURL } from 'node:url' import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js' import type { Environment } from './types.ts' +import { checkIfTypescriptSupported } from './loaders/typescript.js' /** * Gets the environment vars from an env file @@ -19,6 +20,8 @@ export async function getEnvFileVars(envFilePath: string): Promise const ext = extname(absolutePath).toLowerCase() let env: unknown; if (IMPORT_HOOK_EXTENSIONS.includes(ext)) { + if (/tsx?$/.test(ext)) checkIfTypescriptSupported(); + // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them let attributeTypes = {} if (ext === '.json') { diff --git a/src/parse-rc-file.ts b/src/parse-rc-file.ts index 09ea9dd..75aa0a3 100644 --- a/src/parse-rc-file.ts +++ b/src/parse-rc-file.ts @@ -4,6 +4,7 @@ import { extname } from 'node:path' import { pathToFileURL } from 'node:url' import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js' import type { Environment, RCEnvironment } from './types.ts' +import { checkIfTypescriptSupported } from './loaders/typescript.js' const statAsync = promisify(stat) const readFileAsync = promisify(readFile) @@ -30,6 +31,8 @@ export async function getRCFileVars( let parsedData: Partial = {} try { if (IMPORT_HOOK_EXTENSIONS.includes(ext)) { + if (/tsx?$/.test(ext)) checkIfTypescriptSupported() + // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them let attributeTypes = {} if (ext === '.json') { diff --git a/src/utils.ts b/src/utils.ts index 466c483..59c62ef 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,7 +3,16 @@ import { homedir } from 'node:os' import { cwd } from 'node:process' // Special file extensions that node can natively import -export const IMPORT_HOOK_EXTENSIONS = ['.json', '.js', '.cjs', '.mjs'] +export const IMPORT_HOOK_EXTENSIONS = [ + '.json', + '.js', + '.cjs', + '.mjs', + '.ts', + '.mts', + '.cts', + '.tsx', +]; /** * A simple function for resolving the path the user entered @@ -32,3 +41,12 @@ export function isPromise(value?: T | PromiseLike): value is PromiseLike { THANKS: 'FOR ALL THE FISH', ANSWER: '0', }) + }); + + (process.features.typescript ? describe : describe.skip)('TS', () => { + it('should parse a .ts file', async () => { + const env = await getEnvFileVars('./test/test-files/ts-test.ts'); + assert.deepEqual(env, { + THANKS: 'FOR ALL THE FISH', + ANSWER: '1', + }); + }); + + it('should parse a .cts file', async () => { + const env = await getEnvFileVars('./test/test-files/cts-test.cts'); + assert.deepEqual(env, { + THANKS: 'FOR ALL THE FISH', + ANSWER: '0', + }); + }); + + it('should parse a .tsx file', async () => { + const env = await getEnvFileVars('./test/test-files/tsx-test.tsx'); + assert.deepEqual(env, { + THANKS: 'FOR ALL THE FISH', + ANSWER: '2', + }); + }); }) it('should parse an env file', async (): Promise => { diff --git a/test/test-files/cts-test.cts b/test/test-files/cts-test.cts new file mode 100644 index 0000000..32b57be --- /dev/null +++ b/test/test-files/cts-test.cts @@ -0,0 +1,5 @@ +const env: unknown = { + THANKS: 'FOR ALL THE FISH', + ANSWER: 0, +}; +export default env; diff --git a/test/test-files/ts-test.ts b/test/test-files/ts-test.ts new file mode 100644 index 0000000..5d8e40c --- /dev/null +++ b/test/test-files/ts-test.ts @@ -0,0 +1,7 @@ +import type { Environment } from '../../src/types.js'; + +const env: Environment = { + THANKS: 'FOR ALL THE FISH', + ANSWER: 1, +}; +export default env; diff --git a/test/test-files/tsx-test.tsx b/test/test-files/tsx-test.tsx new file mode 100644 index 0000000..ecb6549 --- /dev/null +++ b/test/test-files/tsx-test.tsx @@ -0,0 +1,8 @@ +import type { Environment } from '../../src/types.js'; + +const env: Environment = { + THANKS: 'FOR ALL THE FISH', + ANSWER: 2, +}; + +export default env;