Skip to content

Commit b99ac60

Browse files
committed
feat: provide load and config API
1 parent 49a1458 commit b99ac60

File tree

2 files changed

+100
-10
lines changed

2 files changed

+100
-10
lines changed

src/main/ts/envapi.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import fs from 'node:fs'
2+
import path from 'node:path'
3+
4+
const Q1 = '"' // double quote
5+
const Q2 = "'" // single quote
6+
const Q3 = '`' // backtick
7+
18
export const parse = (content: string): NodeJS.ProcessEnv => {
29
const kr = /^[a-zA-Z_]+\w*$/
310
const sr = /\s/
@@ -37,7 +44,7 @@ export const parse = (content: string): NodeJS.ProcessEnv => {
3744
}
3845
}
3946

40-
if (c === '"' || c === "'" || c === '`') {
47+
if (c === Q1 || c === Q2 || c === Q3) {
4148
if (!q && !b) {
4249
q = c
4350
continue
@@ -55,10 +62,6 @@ export const parse = (content: string): NodeJS.ProcessEnv => {
5562
return e
5663
}
5764

58-
const Q1 = '"' // double quote
59-
const Q2 = "'" // single quote
60-
const Q3 = '`' // backtick
61-
6265
const formatValue = (v: string): string => {
6366
const q1 = v.includes(Q1)
6467
const q2 = v.includes(Q2)
@@ -74,4 +77,23 @@ const formatValue = (v: string): string => {
7477
export const stringify = (env: NodeJS.ProcessEnv): string =>
7578
Object.entries(env).map(([k, v]) => `${k}=${formatValue(v || '')}`).join('\n')
7679

77-
export default { parse, stringify }
80+
const _load = (
81+
read: (file: string) => string,
82+
...files: string[]
83+
): NodeJS.ProcessEnv =>
84+
files
85+
.reverse()
86+
.reduce((m, f) => Object.assign(m, parse(read(path.resolve(f)))), {})
87+
export const load = (...files: string[]): NodeJS.ProcessEnv =>
88+
_load((file) => fs.readFileSync(file, 'utf8'), ...files)
89+
export const loadSafe = (...files: string[]): NodeJS.ProcessEnv =>
90+
_load(
91+
(file: string): string =>
92+
fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '',
93+
...files
94+
)
95+
96+
export const config = (def = '.env', ...files: string[]): NodeJS.ProcessEnv =>
97+
Object.assign(process.env, loadSafe(def, ...files))
98+
99+
export default { parse, stringify, load, loadSafe, config }

src/test/ts/index.test.ts

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
import * as assert from 'node:assert'
2-
import { describe, it } from 'vitest'
3-
import { parse, stringify } from '../../main/ts/index.js'
2+
import fs from 'node:fs'
3+
import os from 'node:os'
4+
import path from 'node:path'
5+
import { describe, test, afterAll } from 'vitest'
6+
import { parse, stringify, load, loadSafe, config } from '../../main/ts/index.js'
7+
8+
const randomId= () => Math.random().toString(36).slice(2)
9+
const tempdir = (prefix: string = `temp-${randomId()}`): string => {
10+
const dirpath = path.join(os.tmpdir(), prefix)
11+
fs.mkdirSync(dirpath, { recursive: true })
12+
return dirpath
13+
}
14+
const tempfile = (name?: string, data?: string | Buffer): string => {
15+
const filepath = name
16+
? path.join(tempdir(), name)
17+
: path.join(os.tmpdir(), `temp-${randomId()}`)
18+
if (data === undefined) fs.closeSync(fs.openSync(filepath, 'w'))
19+
else fs.writeFileSync(filepath, data)
20+
return filepath
21+
}
422

523
describe('parse/stringify', () => {
6-
it('works', () => {
24+
test('works', () => {
725
const str = `SIMPLE=xyz123
826
# comment ###
927
NON_INTERPOLATED='raw text without variable interpolation'
@@ -53,9 +71,59 @@ JSONSTR='{"foo": "b a r"}'`
5371
)
5472
})
5573

56-
it('throws on invalid input', () => {
74+
test('throws on invalid input', () => {
5775
assert.throws(() => parse('BRO-KEN=xyz123'))
5876
assert.throws(() => parse('BRO KEN=xyz123'))
5977
assert.throws(() => parse('1BROKEN=xyz123'))
6078
})
79+
80+
describe('load()', () => {
81+
const file1 = tempfile('.env.1', 'ENV1=value1\nENV2=value2')
82+
const file2 = tempfile('.env.2', 'ENV2=value222\nENV3=value3')
83+
afterAll(() => {
84+
fs.unlinkSync(file1)
85+
fs.unlinkSync(file2)
86+
})
87+
88+
test('loads env from files', () => {
89+
const env = load(file1, file2)
90+
assert.equal(env.ENV1, 'value1')
91+
assert.equal(env.ENV2, 'value2')
92+
assert.equal(env.ENV3, 'value3')
93+
})
94+
95+
test('throws error on ENOENT', () => {
96+
try {
97+
load('./.env')
98+
throw new Error('shouldnt have thrown')
99+
} catch (e: any) {
100+
assert.equal(e.code, 'ENOENT')
101+
assert.equal(e.errno, -2)
102+
}
103+
})
104+
})
105+
106+
describe('loadSafe()', () => {
107+
const file1 = tempfile('.env.1', 'ENV1=value1\nENV2=value2')
108+
const file2 = '.env.notexists'
109+
110+
afterAll(() => fs.unlinkSync(file1))
111+
112+
test('loads env from files', () => {
113+
const env = loadSafe(file1, file2)
114+
assert.equal(env.ENV1, 'value1')
115+
assert.equal(env.ENV2, 'value2')
116+
})
117+
})
118+
119+
describe('config()', () => {
120+
test('updates process.env', () => {
121+
const file1 = tempfile('.env.1', 'ENV1=value1')
122+
123+
assert.equal(process.env.ENV1, undefined)
124+
config(file1)
125+
assert.equal(process.env.ENV1, 'value1')
126+
delete process.env.ENV1
127+
})
128+
})
61129
})

0 commit comments

Comments
 (0)