mirror of
https://github.com/toddbluhm/env-cmd.git
synced 2025-12-08 18:23:33 +00:00
265 lines
9.0 KiB
TypeScript
265 lines
9.0 KiB
TypeScript
import { assert } from 'chai'
|
|
import {
|
|
stripEmptyLines, stripComments, parseEnvVars,
|
|
parseEnvString, getEnvFileVars,
|
|
} from '../src/parse-env-file.js'
|
|
|
|
describe('stripEmptyLines', (): void => {
|
|
it('should strip out all empty lines', (): void => {
|
|
const envString = stripEmptyLines('\nBOB=COOL\n\nNODE_ENV=dev\n\nANSWER=42 AND COUNTING\n\n')
|
|
assert(envString === 'BOB=COOL\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n')
|
|
})
|
|
})
|
|
|
|
describe('stripComments', (): void => {
|
|
it('should strip out all full line comments', (): void => {
|
|
const envString = stripComments('#BOB=COOL\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n#AnotherComment\n')
|
|
assert(envString === '\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n\n')
|
|
})
|
|
|
|
it('should not strip out #s from values', (): void => {
|
|
const envString = stripComments('#\nBOB=COMMENT#ELL\n#\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n#AnotherComment\n')
|
|
assert(envString === '\nBOB=COMMENT#ELL\n\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n\n', envString)
|
|
})
|
|
})
|
|
|
|
describe('parseEnvVars', (): void => {
|
|
it('should parse out all env vars in string when not ending with \'\\n\'', (): void => {
|
|
const envVars = parseEnvVars('BOB=COOL\nNODE_ENV=dev\nANSWER=42 AND COUNTING\nNUMBER=42\nBOOLEAN=true')
|
|
assert(envVars.BOB === 'COOL')
|
|
assert(envVars.NODE_ENV === 'dev')
|
|
assert(envVars.ANSWER === '42 AND COUNTING')
|
|
assert(envVars.NUMBER === '42')
|
|
assert(envVars.BOOLEAN === 'true')
|
|
})
|
|
|
|
it('should parse out all env vars in string with format \'key=value\'', (): void => {
|
|
const envVars = parseEnvVars('BOB=COOL\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n')
|
|
assert(envVars.BOB === 'COOL')
|
|
assert(envVars.NODE_ENV === 'dev')
|
|
assert(envVars.ANSWER === '42 AND COUNTING')
|
|
})
|
|
|
|
it('should ignore invalid lines', (): void => {
|
|
const envVars = parseEnvVars('BOB=COOL\nTHISIS$ANDINVALIDLINE\nANSWER=42 AND COUNTING\n')
|
|
assert(Object.keys(envVars).length === 2)
|
|
assert(envVars.BOB === 'COOL')
|
|
assert(envVars.ANSWER === '42 AND COUNTING')
|
|
})
|
|
|
|
it('should default an empty value to an empty string', (): void => {
|
|
const envVars = parseEnvVars('EMPTY=\n')
|
|
assert(envVars.EMPTY === '')
|
|
})
|
|
|
|
it('should escape double quoted values', (): void => {
|
|
const envVars = parseEnvVars('DOUBLE_QUOTES="double_quotes"\n')
|
|
assert(envVars.DOUBLE_QUOTES === 'double_quotes')
|
|
})
|
|
|
|
it('should escape single quoted values', (): void => {
|
|
const envVars = parseEnvVars('SINGLE_QUOTES=\'single_quotes\'\n')
|
|
assert(envVars.SINGLE_QUOTES === 'single_quotes')
|
|
})
|
|
|
|
it('should preserve embedded double quotes', (): void => {
|
|
const envVars = parseEnvVars('DOUBLE=""""\nDOUBLE_ONE=\'"double_one"\'\nDOUBLE_TWO=""double_two""\n')
|
|
assert(envVars.DOUBLE === '""')
|
|
assert(envVars.DOUBLE_ONE === '"double_one"')
|
|
assert(envVars.DOUBLE_TWO === '"double_two"')
|
|
})
|
|
|
|
it('should preserve newlines when surrounded in quotes', (): void => {
|
|
const envVars = parseEnvVars('ONE_NEWLINE="ONE\\n"\nTWO_NEWLINES="HELLO\\nWORLD\\n"\nTHREE_NEWLINES="HELLO\\n\\nWOR\\nLD"\n')
|
|
assert(envVars.ONE_NEWLINE === 'ONE\n')
|
|
assert(envVars.TWO_NEWLINES === 'HELLO\nWORLD\n')
|
|
assert(envVars.THREE_NEWLINES === 'HELLO\n\nWOR\nLD')
|
|
})
|
|
|
|
it('should preserve embedded single quotes', (): void => {
|
|
const envVars = parseEnvVars('SINGLE=\'\'\'\'\nSINGLE_ONE=\'\'single_one\'\'\nSINGLE_TWO="\'single_two\'"\n')
|
|
assert(envVars.SINGLE === '\'\'')
|
|
assert(envVars.SINGLE_ONE === '\'single_one\'')
|
|
assert(envVars.SINGLE_TWO === '\'single_two\'')
|
|
})
|
|
|
|
it('should parse out all env vars ignoring spaces around = sign', (): void => {
|
|
const envVars = parseEnvVars('BOB = COOL\nNODE_ENV =dev\nANSWER= 42 AND COUNTING')
|
|
assert(envVars.BOB === 'COOL')
|
|
assert(envVars.NODE_ENV === 'dev')
|
|
assert(envVars.ANSWER === '42 AND COUNTING')
|
|
})
|
|
|
|
it('should parse out all env vars ignoring spaces around = sign', (): void => {
|
|
const envVars = parseEnvVars('BOB = "COOL "\nNODE_ENV = dev\nANSWER= \' 42 AND COUNTING\'')
|
|
assert(envVars.BOB === 'COOL ')
|
|
assert(envVars.NODE_ENV === 'dev')
|
|
assert(envVars.ANSWER === ' 42 AND COUNTING')
|
|
})
|
|
|
|
describe('value', () => {
|
|
const testCases = [
|
|
[`a`, `a`],
|
|
[`'a'`, `a`],
|
|
[`"a"`, `a`],
|
|
[`"a"`, `a`],
|
|
[`"single 'quotes' inside double"`, `single 'quotes' inside double`],
|
|
[`'double "quotes" inside single'`, `double "quotes" inside single`],
|
|
[`a # comment without quotes`, `a`],
|
|
[`'a' # comment with single quotes`, `a`],
|
|
[`"a" # comment with double quotes`, `a`],
|
|
[`"a"garbage after final quote`, `a`],
|
|
[
|
|
`"double quotes " within double quotes"`,
|
|
`double quotes " within double quotes`,
|
|
],
|
|
[
|
|
`'single quotes ' within single quotes'`,
|
|
`single quotes ' within single quotes`,
|
|
],
|
|
[`line\\nbreaks`, `line\nbreaks`],
|
|
[`"line\\n\\nbreaks in quotes"`, `line\n\nbreaks in quotes`],
|
|
];
|
|
for (const [input, output] of testCases) {
|
|
it(`should parse ${input}`, () => {
|
|
assert.equal(parseEnvVars(`KEY=${input}`).KEY, output)
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('parseEnvString', (): void => {
|
|
it('should parse env vars and merge (overwrite) with process.env vars', (): void => {
|
|
const env = parseEnvString('BOB=COOL\nNODE_ENV=dev\nANSWER=42\n')
|
|
assert(env.BOB === 'COOL')
|
|
assert(env.NODE_ENV === 'dev')
|
|
assert(env.ANSWER === '42')
|
|
})
|
|
})
|
|
|
|
describe('getEnvFileVars', (): void => {
|
|
it('should parse a json file', async (): Promise<void> => {
|
|
const env = await getEnvFileVars('./test/test-files/test.json')
|
|
assert.deepEqual(env, {
|
|
THANKS: 'FOR WHAT?!',
|
|
ANSWER: '42',
|
|
ONLY: 'IN PRODUCTION',
|
|
GALAXY: 'hitch\nhiking',
|
|
BRINGATOWEL: 'true',
|
|
})
|
|
})
|
|
|
|
it('should parse a json file keeping all newlines intact', async (): Promise<void> => {
|
|
const env = await getEnvFileVars('./test/test-files/test-newlines.json')
|
|
assert.deepEqual(env, {
|
|
THANKS: 'FOR WHAT?!',
|
|
ANSWER: '42',
|
|
ONLY: 'IN\n PRODUCTION',
|
|
GALAXY: 'hitch\nhiking\n\n',
|
|
BRINGATOWEL: 'true',
|
|
})
|
|
})
|
|
|
|
it('should parse a js/cjs file', async (): Promise<void> => {
|
|
const env = await getEnvFileVars('./test/test-files/test.cjs')
|
|
assert.deepEqual(env, {
|
|
THANKS: 'FOR ALL THE FISH',
|
|
ANSWER: '0',
|
|
GALAXY: 'hitch\nhiking',
|
|
})
|
|
})
|
|
|
|
it('should parse an async js/cjs file', async (): Promise<void> => {
|
|
const env = await getEnvFileVars('./test/test-files/test-async.cjs')
|
|
assert.deepEqual(env, {
|
|
THANKS: 'FOR ALL THE FISH',
|
|
ANSWER: '0',
|
|
})
|
|
})
|
|
|
|
it('should parse a mjs file', async (): Promise<void> => {
|
|
const env = await getEnvFileVars('./test/test-files/test.mjs')
|
|
assert.deepEqual(env, {
|
|
THANKS: 'FOR ALL THE FISH',
|
|
ANSWER: '0',
|
|
GALAXY: 'hitch\nhiking',
|
|
})
|
|
})
|
|
|
|
it('should parse an async mjs file', async (): Promise<void> => {
|
|
const env = await getEnvFileVars('./test/test-files/test-async.mjs')
|
|
assert.deepEqual(env, {
|
|
THANKS: 'FOR ALL THE FISH',
|
|
ANSWER: '0',
|
|
})
|
|
});
|
|
|
|
(process.features.typescript === 'transform' ? 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<void> => {
|
|
const env = await getEnvFileVars('./test/test-files/test')
|
|
assert.deepEqual(env, {
|
|
THANKS: 'FOR WHAT?!',
|
|
ANSWER: '42',
|
|
ONLY: 'IN=PRODUCTION',
|
|
GALAXY: 'hitch\nhiking',
|
|
BRINGATOWEL: 'true',
|
|
a: '1',
|
|
b: '2',
|
|
c: '3',
|
|
d: "=",
|
|
e: "equals symbol = = ",
|
|
json_no_quotes: "{\"foo\": \"bar\"}",
|
|
json_with_quotes: "{\"foo\": \"bar\"}",
|
|
quotes: "hello \n\n \" 'escaped \\tsingle quote' \" #cool #fun ",
|
|
})
|
|
})
|
|
|
|
it('should throw error if invalid path provided', async (): Promise<void> => {
|
|
try {
|
|
await getEnvFileVars('./test/test-files/non-existent-file')
|
|
assert.fail('Should not get here!')
|
|
}
|
|
catch (e) {
|
|
assert.instanceOf(e, Error)
|
|
assert.match(e.message, /file path/gi)
|
|
}
|
|
})
|
|
|
|
for (const fileExt of ['cjs', 'mjs', 'json']) {
|
|
it(`should throw an error when importing a ${fileExt} file with an invalid export`, async () => {
|
|
try {
|
|
await getEnvFileVars(`./test/test-files/invalid.${fileExt}`)
|
|
assert.fail('Should not get here!')
|
|
} catch (e) {
|
|
assert.instanceOf(e, Error)
|
|
assert.match(e.message, /does not export an object/gi)
|
|
}
|
|
})
|
|
}
|
|
})
|