Skip to content

Commit cb1b2a5

Browse files
committed
feat: support loading TypeScript files
1 parent 42b95c0 commit cb1b2a5

File tree

9 files changed

+87
-2
lines changed

9 files changed

+87
-2
lines changed

src/get-env-vars.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { GetEnvVarOptions, Environment } from './types.ts'
22
import { getRCFileVars } from './parse-rc-file.js'
33
import { getEnvFileVars } from './parse-env-file.js'
4+
import { isLoaderError } from './utils.js'
45

56
const RC_FILE_DEFAULT_LOCATIONS = ['./.env-cmdrc', './.env-cmdrc.js', './.env-cmdrc.json']
67
const ENV_FILE_DEFAULT_LOCATIONS = ['./.env', './.env.js', './.env.json']
@@ -34,7 +35,11 @@ export async function getEnvFile(
3435
}
3536
return env
3637
}
37-
catch {
38+
catch (error) {
39+
if (isLoaderError(error)) {
40+
throw error
41+
}
42+
3843
if (verbose === true) {
3944
console.info(`Failed to find .env file at path: ${filePath}`)
4045
}

src/loaders/typescript.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function checkIfTypescriptSupported() {
2+
if (!process.features.typescript) {
3+
const error = new Error(
4+
'To load typescript files with env-cmd, you need to upgrade to node v23.6' +
5+
' or later. See https://nodejs.org/en/learn/typescript/run-natively',
6+
);
7+
Object.assign(error, { code: 'ERR_UNKNOWN_FILE_EXTENSION' });
8+
throw error;
9+
}
10+
}

src/parse-env-file.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { extname } from 'node:path'
33
import { pathToFileURL } from 'node:url'
44
import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise, importAttributesKeyword } from './utils.js'
55
import type { Environment } from './types.ts'
6+
import { checkIfTypescriptSupported } from './loaders/typescript.js'
67

78
/**
89
* Gets the environment vars from an env file
@@ -19,6 +20,8 @@ export async function getEnvFileVars(envFilePath: string): Promise<Environment>
1920
const ext = extname(absolutePath).toLowerCase()
2021
let env: unknown;
2122
if (IMPORT_HOOK_EXTENSIONS.includes(ext)) {
23+
if (/tsx?$/.test(ext)) checkIfTypescriptSupported();
24+
2225
// For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them
2326
let attributeTypes = {}
2427
if (ext === '.json') {

src/parse-rc-file.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { extname } from 'node:path'
44
import { pathToFileURL } from 'node:url'
55
import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise, importAttributesKeyword } from './utils.js'
66
import type { Environment, RCEnvironment } from './types.ts'
7+
import { checkIfTypescriptSupported } from './loaders/typescript.js'
78

89
const statAsync = promisify(stat)
910
const readFileAsync = promisify(readFile)
@@ -30,6 +31,8 @@ export async function getRCFileVars(
3031
let parsedData: Partial<RCEnvironment> = {}
3132
try {
3233
if (IMPORT_HOOK_EXTENSIONS.includes(ext)) {
34+
if (/tsx?$/.test(ext)) checkIfTypescriptSupported()
35+
3336
// For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them
3437
let attributeTypes = {}
3538
if (ext === '.json') {

src/utils.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ import { homedir } from 'node:os'
33
import { cwd } from 'node:process'
44

55
// Special file extensions that node can natively import
6-
export const IMPORT_HOOK_EXTENSIONS = ['.json', '.js', '.cjs', '.mjs']
6+
export const IMPORT_HOOK_EXTENSIONS = [
7+
'.json',
8+
'.js',
9+
'.cjs',
10+
'.mjs',
11+
'.ts',
12+
'.mts',
13+
'.cts',
14+
'.tsx',
15+
];
716

817
/**
918
* A simple function for resolving the path the user entered
@@ -33,6 +42,15 @@ export function isPromise<T>(value?: T | PromiseLike<T>): value is PromiseLike<T
3342
&& typeof value.then === 'function'
3443
}
3544

45+
/** @returns true if the error is `ERR_UNKNOWN_FILE_EXTENSION` */
46+
export function isLoaderError(error: unknown): error is Error {
47+
return (
48+
error instanceof Error &&
49+
'code' in error &&
50+
error.code === 'ERR_UNKNOWN_FILE_EXTENSION'
51+
);
52+
}
53+
3654

3755
// "Import Attributes" are only supported since node v18.20 and v20.10.
3856
// For older node versions, we have to use "Import Assertions".

test/parse-env-file.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,32 @@ describe('getEnvFileVars', (): void => {
192192
THANKS: 'FOR ALL THE FISH',
193193
ANSWER: '0',
194194
})
195+
});
196+
197+
(process.features.typescript ? describe : describe.skip)('TS', () => {
198+
it('should parse a .ts file', async () => {
199+
const env = await getEnvFileVars('./test/test-files/ts-test.ts');
200+
assert.deepEqual(env, {
201+
THANKS: 'FOR ALL THE FISH',
202+
ANSWER: 1,
203+
});
204+
});
205+
206+
it('should parse a .cts file', async () => {
207+
const env = await getEnvFileVars('./test/test-files/cts-test.cts');
208+
assert.deepEqual(env, {
209+
THANKS: 'FOR ALL THE FISH',
210+
ANSWER: 0,
211+
});
212+
});
213+
214+
it('should parse a .tsx file', async () => {
215+
const env = await getEnvFileVars('./test/test-files/tsx-test.tsx');
216+
assert.deepEqual(env, {
217+
THANKS: 'FOR ALL THE FISH',
218+
ANSWER: 2,
219+
});
220+
});
195221
})
196222

197223
it('should parse an env file', async (): Promise<void> => {

test/test-files/cts-test.cts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const env: unknown = {
2+
THANKS: 'FOR ALL THE FISH',
3+
ANSWER: 0,
4+
};
5+
export default env;

test/test-files/ts-test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Environment } from '../../src/types.js';
2+
3+
const env: Environment = {
4+
THANKS: 'FOR ALL THE FISH',
5+
ANSWER: 1,
6+
};
7+
export default env;

test/test-files/tsx-test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Environment } from '../../src/types.js';
2+
3+
const env: Environment = {
4+
THANKS: 'FOR ALL THE FISH',
5+
ANSWER: 2,
6+
};
7+
8+
export default env;

0 commit comments

Comments
 (0)