diff --git a/CHANGELOG.md b/CHANGELOG.md index 6469de2..8f5f54d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Additions - [Issue #12] Add option to prevent reading from process.env +- [Issue #11] Add support for relative & absolute filepaths ## [1.1.1] diff --git a/src/configure.ts b/src/configure.ts index e2cdf1c..9777770 100644 --- a/src/configure.ts +++ b/src/configure.ts @@ -11,6 +11,7 @@ import { parseLines } from './parser/parse'; import { readFileLines } from './parser/read-file'; import { castValue } from './parser/cast-value'; import { variableExpansion } from './parser/variable-expansion'; +import { resolveOptionsFilepath } from './options/resolve-options-filepath'; /** * Create and hydrate a Configuration instance @@ -32,7 +33,8 @@ export function configure( } // Read vars - const lines: string[] = readFileLines(options.relativePath); + const file = resolveOptionsFilepath(options); + const lines: string[] = readFileLines(file); const envFileVars: RawKeyValues = parseLines(lines, options); // Create instance diff --git a/src/options/options.ts b/src/options/options.ts index 0c99353..a7ae296 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -2,9 +2,15 @@ import { ParserOptions } from './parser-options'; export interface ConfigurationOptions extends ParserOptions { /** - * Relative filename to load (relative to cwd) + * Absolute filename to load */ - relativePath: string; + absolutePath?: string; + + /** + * Relative filename to load (relative to cwd). Disabled if absolutePath + * is provided + */ + relativePath?: string; /** * This check throws an exception if there is a key in the .env file diff --git a/src/options/resolve-options-filepath.ts b/src/options/resolve-options-filepath.ts new file mode 100644 index 0000000..a461bd6 --- /dev/null +++ b/src/options/resolve-options-filepath.ts @@ -0,0 +1,36 @@ +import { PathLike } from 'fs'; +import * as path from 'path'; +import { ConfigurationOptions } from './options'; + +/** + * Resolve the target filepath from options + * + * @param options Configuration options + * @returns Returns the target filepath + */ +export function resolveOptionsFilepath( + options: ConfigurationOptions +): PathLike { + let file: PathLike; + + if (options.absolutePath) { + file = options.absolutePath; + } + else if (options.relativePath) { + file = path.join(process.cwd(), options.relativePath); + } + else { + file = process.cwd(); + } + + const filename = path.basename(file); + + if (!filename.includes('.')) { + file = path.join(file, '.env'); + } + else { + file = path.join(file); + } + + return file; +} diff --git a/src/parser/read-file.ts b/src/parser/read-file.ts index a9a50e4..811ab7e 100644 --- a/src/parser/read-file.ts +++ b/src/parser/read-file.ts @@ -1,5 +1,4 @@ -import { existsSync, readFileSync } from 'fs'; -import { join as joinPath } from 'path'; +import { existsSync, PathLike, readFileSync } from 'fs'; import { newLineRegex } from '../regex/new-line'; @@ -7,13 +6,11 @@ import { newLineRegex } from '../regex/new-line'; * Read a file into an array of non-empty lines. If no file is found, an empty * array is returned. * - * @param filename Input filename + * @param file Input filename * @returns Returns an array of strings, where each non-empty line is an * element */ -export function readFileLines(filename: string): string[] { - const file = joinPath(process.cwd(), filename); - +export function readFileLines(file: PathLike): string[] { if (existsSync(file)) { return readFileSync(file) .toString() diff --git a/test/envs/absolute.env b/test/envs/absolute.env new file mode 100644 index 0000000..6682a5b --- /dev/null +++ b/test/envs/absolute.env @@ -0,0 +1 @@ +APP_TITLE="Absolute Path" \ No newline at end of file diff --git a/test/envs/relative.env b/test/envs/relative.env new file mode 100644 index 0000000..a162453 --- /dev/null +++ b/test/envs/relative.env @@ -0,0 +1 @@ +APP_TITLE="Relative Path" diff --git a/test/spec/configure.spec.ts b/test/spec/configure.spec.ts index 9315311..f892e9c 100644 --- a/test/spec/configure.spec.ts +++ b/test/spec/configure.spec.ts @@ -31,6 +31,49 @@ describe('configure', () => { })).toThrow(new UndeclaredKeyError(expectedErrorMessage)); }); + it('can accept an absolute path without a filename', () => { + const env: Environment = configure(Environment, { + absolutePath: path.join(process.cwd(), 'test', 'envs'), + overwriteProcessEnv: false, + fromProcessEnv: false, + }); + + expect(env.APP_TITLE).toBe('Default filename'); + }); + + it('can accept an absolute path with a filename', () => { + const env: Environment = configure(Environment, { + absolutePath: path.join( + process.cwd(), + 'test/envs/absolute.env' + ), + overwriteProcessEnv: false, + fromProcessEnv: false, + }); + + expect(env.APP_TITLE).toBe('Absolute Path'); + }); + + it('can accept a relative path without a filename', () => { + const env: Environment = configure(Environment, { + relativePath: 'test/envs/', + overwriteProcessEnv: false, + fromProcessEnv: false, + }); + + expect(env.APP_TITLE).toBe('Default filename'); + }); + + it('can accept a relative path with a filename', () => { + const env: Environment = configure(Environment, { + relativePath: 'test/envs/relative.env', + overwriteProcessEnv: false, + fromProcessEnv: false, + }); + + expect(env.APP_TITLE).toBe('Relative Path'); + }); + it('reads from process.env', () => { process.env.TEST_READ_PROCESS_ENV = 'read'; @@ -41,7 +84,10 @@ describe('configure', () => { it('doesn\'t read from process.env when disabled', () => { process.env.TEST_DISABLE_READ_PROCESS_ENV = 'read'; - const env = configure(ProcessEnvTestEnvironment, { fromProcessEnv: false }); + const env = configure( + ProcessEnvTestEnvironment, + { fromProcessEnv: false } + ); expect(env.TEST_DISABLE_READ_PROCESS_ENV).not.toBe('read'); }); diff --git a/test/spec/options/resolve-options-filepath.spec.ts b/test/spec/options/resolve-options-filepath.spec.ts new file mode 100644 index 0000000..f351140 --- /dev/null +++ b/test/spec/options/resolve-options-filepath.spec.ts @@ -0,0 +1,43 @@ +import { + resolveOptionsFilepath +} from '../../../src/options/resolve-options-filepath'; +import * as path from 'path'; + +describe('options/resolve-options-filepath', () => { + it('can use a default file', () => { + const file = resolveOptionsFilepath({ }); + + expect(file).toEqual(path.join(process.cwd(), '.env')); + }); + + it('can accept a relative file', () => { + const file = resolveOptionsFilepath( + { relativePath: 'test/relative.env' } + ); + + expect(file).toEqual(path.join( + process.cwd(), + 'test/relative.env' + )); + }); + + it('can accept a relative directory', () => { + const file = resolveOptionsFilepath({ relativePath: 'test/' }); + + expect(file).toEqual(path.join(process.cwd(), 'test', '.env')); + }); + + it('can accept an absolute file', () => { + const file = resolveOptionsFilepath( + { absolutePath: 'C:/test/absolute.env' } + ); + + expect(file).toEqual(path.join('C:/test/absolute.env')); + }); + + it('can accept an absolute directory', () => { + const file = resolveOptionsFilepath({ absolutePath: 'C:/test/' }); + + expect(file).toEqual(path.join('C:/test/.env')); + }); +});