Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle multilines in env files #1032

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "zx/index",
"path": "build/*.{js,cjs}",
"limit": "805 kB",
"limit": "805.5 kB",
"brotli": false,
"gzip": false
},
Expand All @@ -30,7 +30,7 @@
{
"name": "all",
"path": "build/*",
"limit": "842 kB",
"limit": "842.5 kB",
"brotli": false,
"gzip": false
}
Expand Down
61 changes: 52 additions & 9 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,58 @@ export const toCamelCase = (str: string) =>
export const parseBool = (v: string): boolean | string =>
({ true: true, false: false })[v] ?? v

export const parseDotenv = (content: string): NodeJS.ProcessEnv =>
content.split(/\r?\n/).reduce<NodeJS.ProcessEnv>((r, line) => {
if (line.startsWith('export ')) line = line.slice(7)
const i = line.indexOf('=')
const k = line.slice(0, i).trim()
const v = line.slice(i + 1).trim()
if (k && v) r[k] = v
return r
}, {})
// prettier-ignore
export const parseDotenv = (content: string): NodeJS.ProcessEnv => {
const kr = /^[a-zA-Z_]+\w*$/
const sr = /\s/
const e: Record<string, string> = {}
let k = ''
let b = ''
let q = ''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use more readable variable names?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a minification challenge without any special reason.

(s)pace(r)egex
(k)ey(r)egex

(e)nv
(k)ey
(b)uffer
(q)oute
(i)gnore
(c)har

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets make it contributor-friendly and leave minification for Vite plugin!

let i = 0
const cap = () => { if (b && k) {
if (!kr.test(k)) throw new Error(`Invalid identifier: ${k}`)
e[k] = b; b = k = ''
}}

for (const c of content.replace(/\r\n?/mg, '\n')) {
if (i) {
if (c === '\n') i = 0
continue
}
if (!q) {
if (c === '#') {
i = 1
continue
}
if (c === '\n') {
cap()
continue
}
if (sr.test(c)) {
if (!k && b === 'export') b = ''
continue
}
if (c === '=') {
if (!k) { k = b; b = ''; continue }
}
}

if (c === '"' || c === "'" || c === '`') {
if (q === c) {
q = ''
cap()
continue
}
q = c
continue
}
b += c
}
cap()

return e
}

export const readEnvFromFile = (
filepath: string,
Expand Down
2 changes: 1 addition & 1 deletion test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ describe('cli', () => {
assert.ok(p.stderr.endsWith(cwd + '\n'))
})

test('supports `--env` options with file', async () => {
test('supports `--env` option', async () => {
const env = tmpfile(
'.env',
`FOO=BAR
Expand Down
71 changes: 40 additions & 31 deletions test/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,44 +140,53 @@ describe('util', () => {
assert.equal(toCamelCase('SOME_MORE_BIG_STR'), 'someMoreBigStr')
assert.equal(toCamelCase('kebab-input-str'), 'kebabInputStr')
})
})

test('parseDotenv()', () => {
assert.deepEqual(
parseDotenv('ENV=v1\nENV2=v2\n\n\n ENV3 = v3 \nexport ENV4=v4'),
{
test.only('parseDotenv()', () => {
const multiline = `SIMPLE=xyz123
# comment ###
NON_INTERPOLATED='raw text without variable interpolation'
MULTILINE = """
long text here, # not-comment
e.g. a private SSH key
"""
ENV=v1\nENV2=v2\n\n\n\t\t ENV3 = v3 \n export ENV4=v4
ENV5=v5 # comment
`

assert.deepEqual(parseDotenv(multiline), {
SIMPLE: 'xyz123',
NON_INTERPOLATED: 'raw text without variable interpolation',
MULTILINE: '\nlong text here, # not-comment\ne.g. a private SSH key\n',
ENV: 'v1',
ENV2: 'v2',
ENV3: 'v3',
ENV4: 'v4',
}
)
assert.deepEqual(parseDotenv(''), {})
ENV5: 'v5',
})

// TBD: multiline
const multiline = `SIMPLE=xyz123
NON_INTERPOLATED='raw text without variable interpolation'
MULTILINE = """
long text here,
e.g. a private SSH key
"""`
})

describe('readEnvFromFile()', () => {
test('handles correct proccess.env', () => {
const file = tempfile('.env', 'ENV=value1\nENV2=value24')
const env = readEnvFromFile(file)
assert.equal(env.ENV, 'value1')
assert.equal(env.ENV2, 'value24')
assert.ok(env.NODE_VERSION !== '')
assert.deepEqual(
parseDotenv(`FOO=BAR
BAR=FOO+`),
{ FOO: 'BAR', BAR: 'FOO+' }
)
})

test('handles correct some env', () => {
const file = tempfile('.env', 'ENV=value1\nENV2=value24')
const env = readEnvFromFile(file, { version: '1.0.0', name: 'zx' })
assert.equal(env.ENV, 'value1')
assert.equal(env.ENV2, 'value24')
assert.equal(env.version, '1.0.0')
assert.equal(env.name, 'zx')
describe('readEnvFromFile()', () => {
test('handles correct proccess.env', () => {
const file = tempfile('.env', 'ENV=value1\nENV2=value24')
const env = readEnvFromFile(file)
assert.equal(env.ENV, 'value1')
assert.equal(env.ENV2, 'value24')
assert.ok(env.NODE_VERSION !== '')
})

test('handles correct some env', () => {
const file = tempfile('.env', 'ENV=value1\nENV2=value24')
const env = readEnvFromFile(file, { version: '1.0.0', name: 'zx' })
assert.equal(env.ENV, 'value1')
assert.equal(env.ENV2, 'value24')
assert.equal(env.version, '1.0.0')
assert.equal(env.name, 'zx')
})
})
})
Loading