Skip to content

Commit 36b42d8

Browse files
authored
feat(cli): add env files support (#1022)
* feat: add support env files * feat: update size limit * chore: update test * fix: review * chore: update size limit * fix: update env path * chore: update size limit * chore: update man * chore: update size limit * fix: replace split by limit * refactor: update parseDotenv * docs: update docs * fix: add line trim * test: add test for file reading error * docs: update docs * chore: prettify test * chore: delete dot
1 parent 504a960 commit 36b42d8

File tree

7 files changed

+124
-5
lines changed

7 files changed

+124
-5
lines changed

.size-limit.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
{
33
"name": "zx/core",
44
"path": ["build/core.cjs", "build/util.cjs", "build/vendor-core.cjs"],
5-
"limit": "76 kB",
5+
"limit": "77 kB",
66
"brotli": false,
77
"gzip": false
88
},
99
{
1010
"name": "zx/index",
1111
"path": "build/*.{js,cjs}",
12-
"limit": "804 kB",
12+
"limit": "805 kB",
1313
"brotli": false,
1414
"gzip": false
1515
},
@@ -30,7 +30,7 @@
3030
{
3131
"name": "all",
3232
"path": "build/*",
33-
"limit": "841 kB",
33+
"limit": "842 kB",
3434
"brotli": false,
3535
"gzip": false
3636
}

docs/cli.md

+13
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ Set the current working directory.
118118
zx --cwd=/foo/bar script.mjs
119119
```
120120

121+
## --env
122+
Specify a env file.
123+
124+
```bash
125+
zx --env=/path/to/some.env script.mjs
126+
```
127+
128+
When cwd option is specified, it will used as base path: `--cwd='/foo/bar' --env='../.env'``/foo/.env`
129+
130+
```bash
131+
zx --cwd=/foo/bar --env=/path/to/some.env script.mjs
132+
```
133+
121134
## --ext
122135

123136
Override the default (temp) script extension. Default is `.mjs`.

man/zx.1

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ install dependencies
3131
npm registry, defaults to https://registry.npmjs.org/
3232
.SS --repl
3333
start repl
34+
.SS --env=<path>
35+
path to env file
3436
.SS --version, -v
3537
print current zx version
3638
.SS --help, -h

src/cli.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
parseArgv,
2828
} from './index.js'
2929
import { installDeps, parseDeps } from './deps.js'
30-
import { randomId } from './util.js'
30+
import { readEnvFromFile, randomId } from './util.js'
3131
import { createRequire } from './vendor.js'
3232

3333
const EXT = '.mjs'
@@ -66,6 +66,7 @@ export function printUsage() {
6666
--version, -v print current zx version
6767
--help, -h print help
6868
--repl start repl
69+
--env=<path> path to env file
6970
--experimental enables experimental features (deprecated)
7071
7172
${chalk.italic('Full documentation:')} ${chalk.underline('https://google.github.io/zx/')}
@@ -74,7 +75,7 @@ export function printUsage() {
7475

7576
// prettier-ignore
7677
export const argv = parseArgv(process.argv.slice(2), {
77-
string: ['shell', 'prefix', 'postfix', 'eval', 'cwd', 'ext', 'registry'],
78+
string: ['shell', 'prefix', 'postfix', 'eval', 'cwd', 'ext', 'registry', 'env'],
7879
boolean: ['version', 'help', 'quiet', 'verbose', 'install', 'repl', 'experimental', 'prefer-local'],
7980
alias: { e: 'eval', i: 'install', v: 'version', h: 'help', l: 'prefer-local' },
8081
stopEarly: true,
@@ -86,6 +87,10 @@ export async function main() {
8687
await import('./globals.js')
8788
argv.ext = normalizeExt(argv.ext)
8889
if (argv.cwd) $.cwd = argv.cwd
90+
if (argv.env) {
91+
const envPath = path.resolve($.cwd ?? process.cwd(), argv.env)
92+
$.env = readEnvFromFile(envPath, process.env)
93+
}
8994
if (argv.verbose) $.verbose = true
9095
if (argv.quiet) $.quiet = true
9196
if (argv.shell) $.shell = argv.shell

src/util.ts

+21
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,24 @@ export const toCamelCase = (str: string) =>
357357

358358
export const parseBool = (v: string): boolean | string =>
359359
({ true: true, false: false })[v] ?? v
360+
361+
export const parseDotenv = (content: string): NodeJS.ProcessEnv => {
362+
return content.split(/\r?\n/).reduce<NodeJS.ProcessEnv>((r, line) => {
363+
const [k] = line.trim().split('=', 1)
364+
const v = line.trim().slice(k.length + 1)
365+
if (k && v) r[k] = v
366+
return r
367+
}, {})
368+
}
369+
370+
export const readEnvFromFile = (
371+
filepath: string,
372+
env: NodeJS.ProcessEnv = process.env
373+
): NodeJS.ProcessEnv => {
374+
const content = fs.readFileSync(path.resolve(filepath), 'utf8')
375+
376+
return {
377+
...env,
378+
...parseDotenv(content),
379+
}
380+
}

test/cli.test.js

+49
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,55 @@ describe('cli', () => {
150150
assert.ok(p.stderr.endsWith(cwd + '\n'))
151151
})
152152

153+
test('supports `--env` options with file', async () => {
154+
const env = tmpfile(
155+
'.env',
156+
`FOO=BAR
157+
BAR=FOO+`
158+
)
159+
const file = `
160+
console.log((await $\`echo $FOO\`).stdout);
161+
console.log((await $\`echo $BAR\`).stdout)
162+
`
163+
164+
const out = await $`node build/cli.js --env=${env} <<< ${file}`
165+
fs.remove(env)
166+
assert.equal(out.stdout, 'BAR\n\nFOO+\n\n')
167+
})
168+
169+
test('supports `--env` and `--cwd` options with file', async () => {
170+
const env = tmpfile(
171+
'.env',
172+
`FOO=BAR
173+
BAR=FOO+`
174+
)
175+
const dir = tmpdir()
176+
const file = `
177+
console.log((await $\`echo $FOO\`).stdout);
178+
console.log((await $\`echo $BAR\`).stdout)
179+
`
180+
181+
const out =
182+
await $`node build/cli.js --cwd=${dir} --env=${env} <<< ${file}`
183+
fs.remove(env)
184+
fs.remove(dir)
185+
assert.equal(out.stdout, 'BAR\n\nFOO+\n\n')
186+
})
187+
188+
test('supports handling errors with the `--env` option', async () => {
189+
const file = `
190+
console.log((await $\`echo $FOO\`).stdout);
191+
console.log((await $\`echo $BAR\`).stdout)
192+
`
193+
try {
194+
await $`node build/cli.js --env=./env <<< ${file}`
195+
fs.remove(env)
196+
assert.throw()
197+
} catch (e) {
198+
assert.equal(e.exitCode, 1)
199+
}
200+
})
201+
153202
test('scripts from https 200', async () => {
154203
const resp = await fs.readFile(path.resolve('test/fixtures/echo.http'))
155204
const port = await getPort()

test/util.test.js

+29
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import {
2929
tempfile,
3030
preferLocalBin,
3131
toCamelCase,
32+
parseDotenv,
33+
readEnvFromFile,
3234
} from '../build/util.js'
3335

3436
describe('util', () => {
@@ -139,3 +141,30 @@ describe('util', () => {
139141
assert.equal(toCamelCase('kebab-input-str'), 'kebabInputStr')
140142
})
141143
})
144+
145+
test('parseDotenv()', () => {
146+
assert.deepEqual(parseDotenv('ENV=value1\nENV2=value24'), {
147+
ENV: 'value1',
148+
ENV2: 'value24',
149+
})
150+
assert.deepEqual(parseDotenv(''), {})
151+
})
152+
153+
describe('readEnvFromFile()', () => {
154+
test('handles correct proccess.env', () => {
155+
const file = tempfile('.env', 'ENV=value1\nENV2=value24')
156+
const env = readEnvFromFile(file)
157+
assert.equal(env.ENV, 'value1')
158+
assert.equal(env.ENV2, 'value24')
159+
assert.ok(env.NODE_VERSION !== '')
160+
})
161+
162+
test('handles correct some env', () => {
163+
const file = tempfile('.env', 'ENV=value1\nENV2=value24')
164+
const env = readEnvFromFile(file, { version: '1.0.0', name: 'zx' })
165+
assert.equal(env.ENV, 'value1')
166+
assert.equal(env.ENV2, 'value24')
167+
assert.equal(env.version, '1.0.0')
168+
assert.equal(env.name, 'zx')
169+
})
170+
})

0 commit comments

Comments
 (0)