diff --git a/bin/env-cmd.js b/bin/env-cmd.js index 6a7f73f..b133f8a 100755 --- a/bin/env-cmd.js +++ b/bin/env-cmd.js @@ -1,3 +1,3 @@ #! /usr/bin/env node -import { CLI } from '../dist' +import { CLI } from '../dist/index.js' CLI(process.argv.slice(2)) diff --git a/dist/cli.d.ts b/dist/cli.d.ts new file mode 100644 index 0000000..5012bcc --- /dev/null +++ b/dist/cli.d.ts @@ -0,0 +1,8 @@ +import type { Environment } from './types.ts'; +/** + * Executes env - cmd using command line arguments + * @export + * @param {string[]} args Command line argument to pass in ['-f', './.env'] + * @returns {Promise} + */ +export declare function CLI(args: string[]): Promise; diff --git a/dist/cli.js b/dist/cli.js new file mode 100644 index 0000000..01984d4 --- /dev/null +++ b/dist/cli.js @@ -0,0 +1,21 @@ +import * as processLib from 'node:process'; +import { EnvCmd } from './env-cmd.js'; +import { parseArgs } from './parse-args.js'; +/** + * Executes env - cmd using command line arguments + * @export + * @param {string[]} args Command line argument to pass in ['-f', './.env'] + * @returns {Promise} + */ +export async function CLI(args) { + // Parse the args from the command line + const parsedArgs = parseArgs(args); + // Run EnvCmd + try { + return await EnvCmd(parsedArgs); + } + catch (e) { + console.error(e); + return processLib.exit(1); + } +} diff --git a/dist/env-cmd.d.ts b/dist/env-cmd.d.ts index 5141864..b28c35a 100644 --- a/dist/env-cmd.d.ts +++ b/dist/env-cmd.d.ts @@ -1,17 +1,10 @@ -import { EnvCmdOptions } from './types'; -/** - * Executes env - cmd using command line arguments - * @export - * @param {string[]} args Command line argument to pass in ['-f', './.env'] - * @returns {Promise<{ [key: string]: any }>} - */ -export declare function CLI(args: string[]): Promise>; +import type { EnvCmdOptions, Environment } from './types.ts'; /** * The main env-cmd program. This will spawn a new process and run the given command using * various environment file solutions. * * @export * @param {EnvCmdOptions} { command, commandArgs, envFile, rc, options } - * @returns {Promise<{ [key: string]: any }>} Returns an object containing [environment variable name]: value + * @returns {Promise} Returns an object containing [environment variable name]: value */ -export declare function EnvCmd({ command, commandArgs, envFile, rc, options }: EnvCmdOptions): Promise>; +export declare function EnvCmd({ command, commandArgs, envFile, rc, options, }: EnvCmdOptions): Promise; diff --git a/dist/env-cmd.js b/dist/env-cmd.js index a499ed5..e02865f 100644 --- a/dist/env-cmd.js +++ b/dist/env-cmd.js @@ -1,70 +1,47 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const spawn_1 = require("./spawn"); -const signal_termination_1 = require("./signal-termination"); -const parse_args_1 = require("./parse-args"); -const get_env_vars_1 = require("./get-env-vars"); -const expand_envs_1 = require("./expand-envs"); -/** - * Executes env - cmd using command line arguments - * @export - * @param {string[]} args Command line argument to pass in ['-f', './.env'] - * @returns {Promise<{ [key: string]: any }>} - */ -async function CLI(args) { - // Parse the args from the command line - const parsedArgs = parse_args_1.parseArgs(args); - // Run EnvCmd - try { - return await exports.EnvCmd(parsedArgs); - } - catch (e) { - console.error(e); - return process.exit(1); - } -} -exports.CLI = CLI; +import { default as spawn } from 'cross-spawn'; +import { TermSignals } from './signal-termination.js'; +import { getEnvVars } from './get-env-vars.js'; +import { expandEnvs } from './expand-envs.js'; +import * as processLib from 'node:process'; /** * The main env-cmd program. This will spawn a new process and run the given command using * various environment file solutions. * * @export * @param {EnvCmdOptions} { command, commandArgs, envFile, rc, options } - * @returns {Promise<{ [key: string]: any }>} Returns an object containing [environment variable name]: value + * @returns {Promise} Returns an object containing [environment variable name]: value */ -async function EnvCmd({ command, commandArgs, envFile, rc, options = {} }) { - var _a; +export async function EnvCmd({ command, commandArgs, envFile, rc, options = {}, }) { let env = {}; try { - env = await get_env_vars_1.getEnvVars({ envFile, rc, verbose: options.verbose }); + env = await getEnvVars({ envFile, rc, verbose: options.verbose }); } catch (e) { - if (!((_a = options.silent) !== null && _a !== void 0 ? _a : false)) { + if (!(options.silent ?? false)) { throw e; } } // Override the merge order if --no-override flag set if (options.noOverride === true) { - env = Object.assign({}, env, process.env); + env = Object.assign({}, env, processLib.env); } else { // Add in the system environment variables to our environment list - env = Object.assign({}, process.env, env); + env = Object.assign({}, processLib.env, env); } if (options.expandEnvs === true) { - command = expand_envs_1.expandEnvs(command, env); - commandArgs = commandArgs.map(arg => expand_envs_1.expandEnvs(arg, env)); + command = expandEnvs(command, env); + commandArgs = commandArgs.map(arg => expandEnvs(arg, env)); } // Execute the command with the given environment variables - const proc = spawn_1.spawn(command, commandArgs, { + const proc = spawn(command, commandArgs, { stdio: 'inherit', shell: options.useShell, - env + env: env, }); // Handle any termination signals for parent and child proceses - const signals = new signal_termination_1.TermSignals({ verbose: options.verbose }); + const signals = new TermSignals({ verbose: options.verbose }); signals.handleUncaughtExceptions(); signals.handleTermSignals(proc); return env; } -exports.EnvCmd = EnvCmd; diff --git a/dist/expand-envs.d.ts b/dist/expand-envs.d.ts index 5a68b32..7706ca7 100644 --- a/dist/expand-envs.d.ts +++ b/dist/expand-envs.d.ts @@ -1,5 +1,6 @@ +import type { Environment } from './types.ts'; /** * expandEnvs Replaces $var in args and command with environment variables - * the environment variable doesn't exist, it leaves it as is. + * if the environment variable doesn't exist, it leaves it as is. */ -export declare function expandEnvs(str: string, envs: Record): string; +export declare function expandEnvs(str: string, envs: Environment): string; diff --git a/dist/expand-envs.js b/dist/expand-envs.js index b46324a..ccb4853 100644 --- a/dist/expand-envs.js +++ b/dist/expand-envs.js @@ -1,13 +1,11 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /** * expandEnvs Replaces $var in args and command with environment variables - * the environment variable doesn't exist, it leaves it as is. + * if the environment variable doesn't exist, it leaves it as is. */ -function expandEnvs(str, envs) { - return str.replace(/(? { +export function expandEnvs(str, envs) { + return str.replace(/(? { const varValue = envs[varName.slice(1)]; - return varValue === undefined ? varName : varValue; + // const test = 42; + return varValue === undefined ? varName : varValue.toString(); }); } -exports.expandEnvs = expandEnvs; diff --git a/dist/get-env-vars.d.ts b/dist/get-env-vars.d.ts index aaf968e..209e284 100644 --- a/dist/get-env-vars.d.ts +++ b/dist/get-env-vars.d.ts @@ -1,12 +1,12 @@ -import { GetEnvVarOptions } from './types'; -export declare function getEnvVars(options?: GetEnvVarOptions): Promise>; +import type { GetEnvVarOptions, Environment } from './types.ts'; +export declare function getEnvVars(options?: GetEnvVarOptions): Promise; export declare function getEnvFile({ filePath, fallback, verbose }: { filePath?: string; fallback?: boolean; verbose?: boolean; -}): Promise>; +}): Promise; export declare function getRCFile({ environments, filePath, verbose }: { environments: string[]; filePath?: string; verbose?: boolean; -}): Promise>; +}): Promise; diff --git a/dist/get-env-vars.js b/dist/get-env-vars.js index 4bd3c02..5c056ef 100644 --- a/dist/get-env-vars.js +++ b/dist/get-env-vars.js @@ -1,40 +1,38 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const parse_rc_file_1 = require("./parse-rc-file"); -const parse_env_file_1 = require("./parse-env-file"); +import { getRCFileVars } from './parse-rc-file.js'; +import { getEnvFileVars } from './parse-env-file.js'; const RC_FILE_DEFAULT_LOCATIONS = ['./.env-cmdrc', './.env-cmdrc.js', './.env-cmdrc.json']; const ENV_FILE_DEFAULT_LOCATIONS = ['./.env', './.env.js', './.env.json']; -async function getEnvVars(options = {}) { - options.envFile = options.envFile !== undefined ? options.envFile : {}; +export async function getEnvVars(options = {}) { + options.envFile = options.envFile ?? {}; // Check for rc file usage if (options.rc !== undefined) { return await getRCFile({ environments: options.rc.environments, filePath: options.rc.filePath, - verbose: options.verbose + verbose: options.verbose, }); } return await getEnvFile({ filePath: options.envFile.filePath, fallback: options.envFile.fallback, - verbose: options.verbose + verbose: options.verbose, }); } -exports.getEnvVars = getEnvVars; -async function getEnvFile({ filePath, fallback, verbose }) { +export async function getEnvFile({ filePath, fallback, verbose }) { // Use env file if (filePath !== undefined) { try { - const env = await parse_env_file_1.getEnvFileVars(filePath); + const env = await getEnvFileVars(filePath); if (verbose === true) { console.info(`Found .env file at path: ${filePath}`); } return env; } - catch (e) { + catch { if (verbose === true) { console.info(`Failed to find .env file at path: ${filePath}`); } + // Ignore error as we are just trying this location } if (fallback !== true) { throw new Error(`Failed to find .env file at path: ${filePath}`); @@ -43,13 +41,15 @@ async function getEnvFile({ filePath, fallback, verbose }) { // Use the default env file locations for (const path of ENV_FILE_DEFAULT_LOCATIONS) { try { - const env = await parse_env_file_1.getEnvFileVars(path); + const env = await getEnvFileVars(path); if (verbose === true) { console.info(`Found .env file at default path: ${path}`); } return env; } - catch (e) { } + catch { + // Ignore error because we are just trying this location + } } const error = `Failed to find .env file at default paths: [${ENV_FILE_DEFAULT_LOCATIONS.join(',')}]`; if (verbose === true) { @@ -57,26 +57,27 @@ async function getEnvFile({ filePath, fallback, verbose }) { } throw new Error(error); } -exports.getEnvFile = getEnvFile; -async function getRCFile({ environments, filePath, verbose }) { +export async function getRCFile({ environments, filePath, verbose }) { // User provided an .rc file path if (filePath !== undefined) { try { - const env = await parse_rc_file_1.getRCFileVars({ environments, filePath }); + const env = await getRCFileVars({ environments, filePath }); if (verbose === true) { console.info(`Found environments: [${environments.join(',')}] for .rc file at path: ${filePath}`); } return env; } catch (e) { - if (e.name === 'PathError') { - if (verbose === true) { - console.info(`Failed to find .rc file at path: ${filePath}`); + if (e instanceof Error) { + if (e.name === 'PathError') { + if (verbose === true) { + console.info(`Failed to find .rc file at path: ${filePath}`); + } } - } - if (e.name === 'EnvironmentError') { - if (verbose === true) { - console.info(`Failed to find environments: [${environments.join(',')}] for .rc file at path: ${filePath}`); + if (e.name === 'EnvironmentError') { + if (verbose === true) { + console.info(`Failed to find environments: [${environments.join(',')}] for .rc file at path: ${filePath}`); + } } } throw e; @@ -85,19 +86,27 @@ async function getRCFile({ environments, filePath, verbose }) { // Use the default .rc file locations for (const path of RC_FILE_DEFAULT_LOCATIONS) { try { - const env = await parse_rc_file_1.getRCFileVars({ environments, filePath: path }); + const env = await getRCFileVars({ environments, filePath: path }); if (verbose === true) { console.info(`Found environments: [${environments.join(',')}] for default .rc file at path: ${path}`); } return env; } catch (e) { - if (e.name === 'EnvironmentError') { - const errorText = `Failed to find environments: [${environments.join(',')}] for .rc file at path: ${path}`; - if (verbose === true) { - console.info(errorText); + if (e instanceof Error) { + if (e.name === 'EnvironmentError') { + const errorText = `Failed to find environments: [${environments.join(',')}] for .rc file at path: ${path}`; + if (verbose === true) { + console.info(errorText); + } + throw new Error(errorText); + } + if (e.name === 'ParseError') { + if (verbose === true) { + console.info(e.message); + } + throw new Error(e.message); } - throw new Error(errorText); } } } @@ -107,4 +116,3 @@ async function getRCFile({ environments, filePath, verbose }) { } throw new Error(errorText); } -exports.getRCFile = getRCFile; diff --git a/dist/index.d.ts b/dist/index.d.ts index 39037f2..e0c3b73 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,4 +1,5 @@ -import { getEnvVars } from './get-env-vars'; -export * from './types'; -export * from './env-cmd'; +import { getEnvVars } from './get-env-vars.js'; +export * from './types.js'; +export * from './cli.js'; +export * from './env-cmd.js'; export declare const GetEnvVars: typeof getEnvVars; diff --git a/dist/index.js b/dist/index.js index 6009b62..812f058 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,8 +1,6 @@ -"use strict"; -function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; -} -Object.defineProperty(exports, "__esModule", { value: true }); -const get_env_vars_1 = require("./get-env-vars"); -__export(require("./env-cmd")); -exports.GetEnvVars = get_env_vars_1.getEnvVars; +import { getEnvVars } from './get-env-vars.js'; +// Export the core env-cmd API +export * from './types.js'; +export * from './cli.js'; +export * from './env-cmd.js'; +export const GetEnvVars = getEnvVars; diff --git a/dist/parse-args.d.ts b/dist/parse-args.d.ts index 0c68100..15394f5 100644 --- a/dist/parse-args.d.ts +++ b/dist/parse-args.d.ts @@ -1,7 +1,6 @@ -import * as commander from 'commander'; -import { EnvCmdOptions } from './types'; +import type { EnvCmdOptions, CommanderOptions } from './types.ts'; /** * Parses the arguments passed into the cli */ export declare function parseArgs(args: string[]): EnvCmdOptions; -export declare function parseArgsUsingCommander(args: string[]): commander.Command; +export declare function parseArgsUsingCommander(args: string[]): CommanderOptions; diff --git a/dist/parse-args.js b/dist/parse-args.js index c650ea5..4494073 100644 --- a/dist/parse-args.js +++ b/dist/parse-args.js @@ -1,13 +1,10 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const commander = require("commander"); -const utils_1 = require("./utils"); -// Use commonjs require to prevent a weird folder hierarchy in dist -const packageJson = require('../package.json'); +import * as commander from 'commander'; +import { parseArgList } from './utils.js'; +import { default as packageJson } from '../package.json' with { type: 'json' }; /** * Parses the arguments passed into the cli */ -function parseArgs(args) { +export function parseArgs(args) { // Run the initial arguments through commander in order to determine // which value in the args array is the `command` to execute let program = parseArgsUsingCommander(args); @@ -39,17 +36,19 @@ function parseArgs(args) { silent = true; } let rc; - if (program.environments !== undefined && program.environments.length !== 0) { + if (program.environments !== undefined + && Array.isArray(program.environments) + && program.environments.length !== 0) { rc = { environments: program.environments, - filePath: program.rcFile + filePath: program.rcFile, }; } let envFile; if (program.file !== undefined) { envFile = { filePath: program.file, - fallback: program.fallback + fallback: program.fallback, }; } const options = { @@ -62,21 +61,20 @@ function parseArgs(args) { noOverride, silent, useShell, - verbose - } + verbose, + }, }; if (verbose) { console.info(`Options: ${JSON.stringify(options, null, 0)}`); } return options; } -exports.parseArgs = parseArgs; -function parseArgsUsingCommander(args) { +export function parseArgsUsingCommander(args) { const program = new commander.Command(); return program .version(packageJson.version, '-v, --version') .usage('[options] [...args]') - .option('-e, --environments [env1,env2,...]', 'The rc file environment(s) to use', utils_1.parseArgList) + .option('-e, --environments [env1,env2,...]', 'The rc file environment(s) to use', parseArgList) .option('-f, --file [path]', 'Custom env file path (default path: ./.env)') .option('--fallback', 'Fallback to default env file path, if custom env file path not found') .option('--no-override', 'Do not override existing environment variables') @@ -88,4 +86,3 @@ function parseArgsUsingCommander(args) { .allowUnknownOption(true) .parse(['_', '_', ...args]); } -exports.parseArgsUsingCommander = parseArgsUsingCommander; diff --git a/dist/parse-env-file.d.ts b/dist/parse-env-file.d.ts index c298868..299c2f9 100644 --- a/dist/parse-env-file.d.ts +++ b/dist/parse-env-file.d.ts @@ -1,15 +1,16 @@ +import type { Environment } from './types.ts'; /** * Gets the environment vars from an env file */ -export declare function getEnvFileVars(envFilePath: string): Promise>; +export declare function getEnvFileVars(envFilePath: string): Promise; /** * Parse out all env vars from a given env file string and return an object */ -export declare function parseEnvString(envFileString: string): Record; +export declare function parseEnvString(envFileString: string): Environment; /** * Parse out all env vars from an env file string */ -export declare function parseEnvVars(envString: string): Record; +export declare function parseEnvVars(envString: string): Environment; /** * Strips out comments from env file string */ diff --git a/dist/parse-env-file.js b/dist/parse-env-file.js index a4370ce..f7c07df 100644 --- a/dist/parse-env-file.js +++ b/dist/parse-env-file.js @@ -1,14 +1,11 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = require("fs"); -const path = require("path"); -const utils_1 = require("./utils"); -const REQUIRE_HOOK_EXTENSIONS = ['.json', '.js', '.cjs']; +import * as fs from 'fs'; +import * as path from 'path'; +import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js'; /** * Gets the environment vars from an env file */ -async function getEnvFileVars(envFilePath) { - const absolutePath = utils_1.resolveEnvFilePath(envFilePath); +export async function getEnvFileVars(envFilePath) { + const absolutePath = resolveEnvFilePath(envFilePath); if (!fs.existsSync(absolutePath)) { const pathError = new Error(`Invalid env file path (${envFilePath}).`); pathError.name = 'PathError'; @@ -17,9 +14,23 @@ async function getEnvFileVars(envFilePath) { // Get the file extension const ext = path.extname(absolutePath).toLowerCase(); let env = {}; - if (REQUIRE_HOOK_EXTENSIONS.includes(ext)) { - const possiblePromise = require(absolutePath); - env = utils_1.isPromise(possiblePromise) ? await possiblePromise : possiblePromise; + if (IMPORT_HOOK_EXTENSIONS.includes(ext)) { + // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them + let attributeTypes = {}; + if (ext === '.json') { + attributeTypes = { with: { type: 'json' } }; + } + const res = await import(absolutePath, attributeTypes); + if ('default' in res) { + env = res.default; + } + else { + env = res; + } + // Check to see if the imported value is a promise + if (isPromise(env)) { + env = await env; + } } else { const file = fs.readFileSync(absolutePath, { encoding: 'utf8' }); @@ -27,11 +38,10 @@ async function getEnvFileVars(envFilePath) { } return env; } -exports.getEnvFileVars = getEnvFileVars; /** * Parse out all env vars from a given env file string and return an object */ -function parseEnvString(envFileString) { +export function parseEnvString(envFileString) { // First thing we do is stripe out all comments envFileString = stripComments(envFileString.toString()); // Next we stripe out all the empty lines @@ -39,30 +49,41 @@ function parseEnvString(envFileString) { // Merge the file env vars with the current process env vars (the file vars overwrite process vars) return parseEnvVars(envFileString); } -exports.parseEnvString = parseEnvString; /** * Parse out all env vars from an env file string */ -function parseEnvVars(envString) { +export function parseEnvVars(envString) { const envParseRegex = /^((.+?)[=](.*))$/gim; const matches = {}; let match; while ((match = envParseRegex.exec(envString)) !== null) { // Note: match[1] is the full env=var line const key = match[2].trim(); - const value = match[3].trim(); + let value = match[3].trim(); // remove any surrounding quotes - matches[key] = value + value = value .replace(/(^['"]|['"]$)/g, '') .replace(/\\n/g, '\n'); + // Convert string to JS type if appropriate + if (value !== '' && !isNaN(+value)) { + matches[key] = +value; + } + else if (value === 'true') { + matches[key] = true; + } + else if (value === 'false') { + matches[key] = false; + } + else { + matches[key] = value; + } } - return matches; + return JSON.parse(JSON.stringify(matches)); } -exports.parseEnvVars = parseEnvVars; /** * Strips out comments from env file string */ -function stripComments(envString) { +export function stripComments(envString) { const commentsRegex = /(^#.*$)/gim; let match = commentsRegex.exec(envString); let newString = envString; @@ -72,12 +93,10 @@ function stripComments(envString) { } return newString; } -exports.stripComments = stripComments; /** * Strips out newlines from env file string */ -function stripEmptyLines(envString) { +export function stripEmptyLines(envString) { const emptyLinesRegex = /(^\n)/gim; return envString.replace(emptyLinesRegex, ''); } -exports.stripEmptyLines = stripEmptyLines; diff --git a/dist/parse-rc-file.d.ts b/dist/parse-rc-file.d.ts index bd193e1..05b37c7 100644 --- a/dist/parse-rc-file.d.ts +++ b/dist/parse-rc-file.d.ts @@ -1,7 +1,8 @@ +import type { Environment } from './types.ts'; /** * Gets the env vars from the rc file and rc environments */ export declare function getRCFileVars({ environments, filePath }: { environments: string[]; filePath: string; -}): Promise>; +}): Promise; diff --git a/dist/parse-rc-file.js b/dist/parse-rc-file.js index 07bb65e..1c80be6 100644 --- a/dist/parse-rc-file.js +++ b/dist/parse-rc-file.js @@ -1,31 +1,43 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = require("fs"); -const util_1 = require("util"); -const path_1 = require("path"); -const utils_1 = require("./utils"); -const statAsync = util_1.promisify(fs_1.stat); -const readFileAsync = util_1.promisify(fs_1.readFile); +import { stat, readFile } from 'fs'; +import { promisify } from 'util'; +import { extname } from 'path'; +import { resolveEnvFilePath, IMPORT_HOOK_EXTENSIONS, isPromise } from './utils.js'; +const statAsync = promisify(stat); +const readFileAsync = promisify(readFile); /** * Gets the env vars from the rc file and rc environments */ -async function getRCFileVars({ environments, filePath }) { - const absolutePath = utils_1.resolveEnvFilePath(filePath); +export async function getRCFileVars({ environments, filePath }) { + const absolutePath = resolveEnvFilePath(filePath); try { await statAsync(absolutePath); } - catch (e) { + catch { const pathError = new Error(`Failed to find .rc file at path: ${absolutePath}`); pathError.name = 'PathError'; throw pathError; } // Get the file extension - const ext = path_1.extname(absolutePath).toLowerCase(); - let parsedData; + const ext = extname(absolutePath).toLowerCase(); + let parsedData = {}; try { - if (ext === '.json' || ext === '.js' || ext === '.cjs') { - const possiblePromise = require(absolutePath); - parsedData = utils_1.isPromise(possiblePromise) ? await possiblePromise : possiblePromise; + if (IMPORT_HOOK_EXTENSIONS.includes(ext)) { + // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them + let attributeTypes = {}; + if (ext === '.json') { + attributeTypes = { with: { type: 'json' } }; + } + const res = await import(absolutePath, attributeTypes); + if ('default' in res) { + parsedData = res.default; + } + else { + parsedData = res; + } + // Check to see if the imported value is a promise + if (isPromise(parsedData)) { + parsedData = await parsedData; + } } else { const file = await readFileAsync(absolutePath, { encoding: 'utf8' }); @@ -33,20 +45,26 @@ async function getRCFileVars({ environments, filePath }) { } } catch (e) { - const parseError = new Error(`Failed to parse .rc file at path: ${absolutePath}`); + const errorMessage = e instanceof Error ? e.message : 'Unknown error'; + const parseError = new Error(`Failed to parse .rc file at path: ${absolutePath}.\n${errorMessage}`); parseError.name = 'ParseError'; throw parseError; } // Parse and merge multiple rc environments together let result = {}; let environmentFound = false; - environments.forEach((name) => { - const envVars = parsedData[name]; - if (envVars !== undefined) { - environmentFound = true; - result = Object.assign(Object.assign({}, result), envVars); + for (const name of environments) { + if (name in parsedData) { + const envVars = parsedData[name]; + if (envVars != null && typeof envVars === 'object') { + environmentFound = true; + result = { + ...result, + ...envVars, + }; + } } - }); + } if (!environmentFound) { const environmentError = new Error(`Failed to find environments [${environments.join(',')}] at .rc file location: ${absolutePath}`); environmentError.name = 'EnvironmentError'; @@ -54,4 +72,3 @@ async function getRCFileVars({ environments, filePath }) { } return result; } -exports.getRCFileVars = getRCFileVars; diff --git a/dist/signal-termination.d.ts b/dist/signal-termination.d.ts index 1a44663..1583776 100644 --- a/dist/signal-termination.d.ts +++ b/dist/signal-termination.d.ts @@ -1,7 +1,7 @@ -/// import { ChildProcess } from 'child_process'; export declare class TermSignals { private readonly terminateSpawnedProcessFuncHandlers; + private terminateSpawnedProcessFuncExitHandler?; private readonly verbose; _exitCalled: boolean; constructor(options?: { @@ -15,7 +15,7 @@ export declare class TermSignals { /** * Terminate parent process helper */ - _terminateProcess(code?: number, signal?: NodeJS.Signals): void; + _terminateProcess(signal?: NodeJS.Signals | number): void; /** * Exit event listener clean up helper */ diff --git a/dist/signal-termination.js b/dist/signal-termination.js index 136d7d6..230ec28 100644 --- a/dist/signal-termination.js +++ b/dist/signal-termination.js @@ -1,73 +1,73 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); const SIGNALS_TO_HANDLE = [ - 'SIGINT', 'SIGTERM', 'SIGHUP' + 'SIGINT', 'SIGTERM', 'SIGHUP', ]; -class TermSignals { +export class TermSignals { + terminateSpawnedProcessFuncHandlers = {}; + terminateSpawnedProcessFuncExitHandler; + verbose = false; + _exitCalled = false; constructor(options = {}) { - this.terminateSpawnedProcessFuncHandlers = {}; - this.verbose = false; - this._exitCalled = false; this.verbose = options.verbose === true; } handleTermSignals(proc) { // Terminate child process if parent process receives termination events - SIGNALS_TO_HANDLE.forEach((signal) => { - this.terminateSpawnedProcessFuncHandlers[signal] = - (signal, code) => { - this._removeProcessListeners(); - if (!this._exitCalled) { - if (this.verbose) { - console.info('Parent process exited with signal: ' + - signal.toString() + - '. Terminating child process...'); - } - // Mark shared state so we do not run into a signal/exit loop - this._exitCalled = true; - // Use the signal code if it is an error code - let correctSignal; - if (typeof signal === 'number') { - if (signal > (code !== null && code !== void 0 ? code : 0)) { - code = signal; - correctSignal = 'SIGINT'; - } - } - else { - correctSignal = signal; - } - // Kill the child process - proc.kill(correctSignal !== null && correctSignal !== void 0 ? correctSignal : code); - // Terminate the parent process - this._terminateProcess(code, correctSignal); + const terminationFunc = (signal) => { + this._removeProcessListeners(); + if (!this._exitCalled) { + if (this.verbose) { + console.info('Parent process exited with signal: ' + + signal.toString() + + '. Terminating child process...'); + } + // Mark shared state so we do not run into a signal/exit loop + this._exitCalled = true; + // Use the signal code if it is an error code + // let correctSignal: NodeJS.Signals | undefined + if (typeof signal === 'number') { + if (signal > 0) { + // code = signal + signal = 'SIGINT'; } - }; + } + // else { + // correctSignal = signal + // } + // Kill the child process + proc.kill(signal); + // Terminate the parent process + this._terminateProcess(signal); + } + }; + for (const signal of SIGNALS_TO_HANDLE) { + this.terminateSpawnedProcessFuncHandlers[signal] = terminationFunc; process.once(signal, this.terminateSpawnedProcessFuncHandlers[signal]); - }); - process.once('exit', this.terminateSpawnedProcessFuncHandlers.SIGTERM); + } + this.terminateSpawnedProcessFuncExitHandler = terminationFunc; + process.once('exit', this.terminateSpawnedProcessFuncExitHandler); // Terminate parent process if child process receives termination events proc.on('exit', (code, signal) => { this._removeProcessListeners(); if (!this._exitCalled) { if (this.verbose) { - console.info(`Child process exited with code: ${(code !== null && code !== void 0 ? code : '').toString()} and signal:` + - (signal !== null && signal !== void 0 ? signal : '').toString() + - '. Terminating parent process...'); + console.info(`Child process exited with code: ${(code ?? '').toString()} and signal:` + + (signal ?? '').toString() + + '. Terminating parent process...'); } // Mark shared state so we do not run into a signal/exit loop this._exitCalled = true; // Use the signal code if it is an error code let correctSignal; if (typeof signal === 'number') { - if (signal > (code !== null && code !== void 0 ? code : 0)) { + if (signal > (code ?? 0)) { code = signal; correctSignal = 'SIGINT'; } } else { - correctSignal = signal !== null && signal !== void 0 ? signal : undefined; + correctSignal = signal ?? undefined; } // Terminate the parent process - this._terminateProcess(code, correctSignal); + this._terminateProcess(correctSignal ?? code); } }); } @@ -75,17 +75,23 @@ class TermSignals { * Enables catching of unhandled exceptions */ handleUncaughtExceptions() { - process.on('uncaughtException', (e) => this._uncaughtExceptionHandler(e)); + process.on('uncaughtException', (e) => { + this._uncaughtExceptionHandler(e); + }); } /** * Terminate parent process helper */ - _terminateProcess(code, signal) { - if (signal !== undefined) { - return process.kill(process.pid, signal); - } - if (code !== undefined) { - return process.exit(code); + _terminateProcess(signal) { + if (signal != null) { + if (typeof signal === 'string') { + process.kill(process.pid, signal); + return; + } + if (typeof signal === 'number') { + process.exit(signal); + return; + } } throw new Error('Unable to terminate parent process successfully'); } @@ -96,7 +102,9 @@ class TermSignals { SIGNALS_TO_HANDLE.forEach((signal) => { process.removeListener(signal, this.terminateSpawnedProcessFuncHandlers[signal]); }); - process.removeListener('exit', this.terminateSpawnedProcessFuncHandlers.SIGTERM); + if (this.terminateSpawnedProcessFuncExitHandler != null) { + process.removeListener('exit', this.terminateSpawnedProcessFuncExitHandler); + } } /** * General exception handler @@ -106,4 +114,3 @@ class TermSignals { process.exit(1); } } -exports.TermSignals = TermSignals; diff --git a/dist/spawn.d.ts b/dist/spawn.d.ts deleted file mode 100644 index cabd0a7..0000000 --- a/dist/spawn.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import * as spawn from 'cross-spawn'; -export { spawn }; diff --git a/dist/spawn.js b/dist/spawn.js deleted file mode 100644 index e83cc2c..0000000 --- a/dist/spawn.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const spawn = require("cross-spawn"); -exports.spawn = spawn; diff --git a/dist/types.d.ts b/dist/types.d.ts index a037458..ca2795d 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -1,15 +1,31 @@ +import { Command } from 'commander'; +export type Environment = Partial>; +export type RCEnvironment = Partial>; +export interface CommanderOptions extends Command { + override?: boolean; + useShell?: boolean; + expandEnvs?: boolean; + verbose?: boolean; + silent?: boolean; + fallback?: boolean; + environments?: string[]; + rcFile?: string; + file?: string; +} +export interface RCFileOptions { + environments: string[]; + filePath?: string; +} +export interface EnvFileOptions { + filePath?: string; + fallback?: boolean; +} export interface GetEnvVarOptions { - envFile?: { - filePath?: string; - fallback?: boolean; - }; - rc?: { - environments: string[]; - filePath?: string; - }; + envFile?: EnvFileOptions; + rc?: RCFileOptions; verbose?: boolean; } -export interface EnvCmdOptions extends Pick { +export interface EnvCmdOptions extends GetEnvVarOptions { command: string; commandArgs: string[]; options?: { diff --git a/dist/types.js b/dist/types.js index c8ad2e5..cb0ff5c 100644 --- a/dist/types.js +++ b/dist/types.js @@ -1,2 +1 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; diff --git a/dist/utils.d.ts b/dist/utils.d.ts index 9a731d9..f8e85a2 100644 --- a/dist/utils.d.ts +++ b/dist/utils.d.ts @@ -1,3 +1,4 @@ +export declare const IMPORT_HOOK_EXTENSIONS: string[]; /** * A simple function for resolving the path the user entered */ @@ -7,6 +8,6 @@ export declare function resolveEnvFilePath(userPath: string): string; */ export declare function parseArgList(list: string): string[]; /** - * A simple function to test if the value is a promise + * A simple function to test if the value is a promise/thenable */ -export declare function isPromise(value: any | PromiseLike): value is Promise; +export declare function isPromise(value?: T | PromiseLike): value is PromiseLike; diff --git a/dist/utils.js b/dist/utils.js index 1c7aa4f..7535a34 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -1,30 +1,31 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const path = require("path"); -const os = require("os"); +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']; /** * A simple function for resolving the path the user entered */ -function resolveEnvFilePath(userPath) { +export function resolveEnvFilePath(userPath) { // Make sure a home directory exist - const home = os.homedir(); - if (home !== undefined) { + const home = homedir(); + if (home != null) { userPath = userPath.replace(/^~($|\/|\\)/, `${home}$1`); } - return path.resolve(process.cwd(), userPath); + return resolve(cwd(), userPath); } -exports.resolveEnvFilePath = resolveEnvFilePath; /** * A simple function that parses a comma separated string into an array of strings */ -function parseArgList(list) { +export function parseArgList(list) { return list.split(','); } -exports.parseArgList = parseArgList; /** - * A simple function to test if the value is a promise + * A simple function to test if the value is a promise/thenable */ -function isPromise(value) { - return value != null && typeof value.then === 'function'; +export function isPromise(value) { + return value != null + && typeof value === 'object' + && 'then' in value + && typeof value.then === 'function'; } -exports.isPromise = isPromise; diff --git a/src/parse-args.ts b/src/parse-args.ts index 5f56d82..4bbe764 100644 --- a/src/parse-args.ts +++ b/src/parse-args.ts @@ -1,9 +1,7 @@ import * as commander from 'commander' import type { EnvCmdOptions, CommanderOptions, EnvFileOptions, RCFileOptions } from './types.ts' import { parseArgList } from './utils.js' - -// Use commonjs require to prevent a weird folder hierarchy in dist -const packageJson = (await import('../package.json')).default +import { default as packageJson } from '../package.json' with { type: 'json' }; /** * Parses the arguments passed into the cli diff --git a/src/parse-env-file.ts b/src/parse-env-file.ts index bc803bd..41cabb8 100644 --- a/src/parse-env-file.ts +++ b/src/parse-env-file.ts @@ -18,7 +18,12 @@ export async function getEnvFileVars(envFilePath: string): Promise const ext = path.extname(absolutePath).toLowerCase() let env: Environment = {} if (IMPORT_HOOK_EXTENSIONS.includes(ext)) { - const res = await import(absolutePath) as Environment | { default: Environment } + // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them + let attributeTypes = {} + if (ext === '.json') { + attributeTypes = { with: { type: 'json' } } + } + const res = await import(absolutePath, attributeTypes) as Environment | { default: Environment } if ('default' in res) { env = res.default as Environment } else { diff --git a/src/parse-rc-file.ts b/src/parse-rc-file.ts index 8fc720a..7cc02a1 100644 --- a/src/parse-rc-file.ts +++ b/src/parse-rc-file.ts @@ -29,7 +29,12 @@ export async function getRCFileVars( let parsedData: Partial = {} try { if (IMPORT_HOOK_EXTENSIONS.includes(ext)) { - const res = await import(absolutePath) as RCEnvironment | { default: RCEnvironment } + // For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them + let attributeTypes = {} + if (ext === '.json') { + attributeTypes = { with: { type: 'json' } } + } + const res = await import(absolutePath, attributeTypes) as RCEnvironment | { default: RCEnvironment } if ('default' in res) { parsedData = res.default as RCEnvironment } else { diff --git a/tsconfig.json b/tsconfig.json index 1708f4c..8bade5d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,14 +3,15 @@ "declaration": true, "esModuleInterop": false, "lib": ["es2023"], - "module": "Node16", + "module": "NodeNext", "moduleDetection": "force", "outDir": "./dist", "resolveJsonModule": true, "strict": true, "target": "ES2022", + "rootDir": "src" }, "include": [ - "./src/**/*" + "src/**/*" ] }