Commit 36b42d8
Changed files (7)
docs/cli.md
@@ -118,6 +118,19 @@ Set the current working directory.
zx --cwd=/foo/bar script.mjs
```
+## --env
+Specify a env file.
+
+```bash
+zx --env=/path/to/some.env script.mjs
+```
+
+When cwd option is specified, it will used as base path: `--cwd='/foo/bar' --env='../.env'` → `/foo/.env`
+
+```bash
+zx --cwd=/foo/bar --env=/path/to/some.env script.mjs
+```
+
## --ext
Override the default (temp) script extension. Default is `.mjs`.
man/zx.1
@@ -31,6 +31,8 @@ install dependencies
npm registry, defaults to https://registry.npmjs.org/
.SS --repl
start repl
+.SS --env=<path>
+path to env file
.SS --version, -v
print current zx version
.SS --help, -h
src/cli.ts
@@ -27,7 +27,7 @@ import {
parseArgv,
} from './index.js'
import { installDeps, parseDeps } from './deps.js'
-import { randomId } from './util.js'
+import { readEnvFromFile, randomId } from './util.js'
import { createRequire } from './vendor.js'
const EXT = '.mjs'
@@ -66,6 +66,7 @@ export function printUsage() {
--version, -v print current zx version
--help, -h print help
--repl start repl
+ --env=<path> path to env file
--experimental enables experimental features (deprecated)
${chalk.italic('Full documentation:')} ${chalk.underline('https://google.github.io/zx/')}
@@ -74,7 +75,7 @@ export function printUsage() {
// prettier-ignore
export const argv = parseArgv(process.argv.slice(2), {
- string: ['shell', 'prefix', 'postfix', 'eval', 'cwd', 'ext', 'registry'],
+ string: ['shell', 'prefix', 'postfix', 'eval', 'cwd', 'ext', 'registry', 'env'],
boolean: ['version', 'help', 'quiet', 'verbose', 'install', 'repl', 'experimental', 'prefer-local'],
alias: { e: 'eval', i: 'install', v: 'version', h: 'help', l: 'prefer-local' },
stopEarly: true,
@@ -86,6 +87,10 @@ export async function main() {
await import('./globals.js')
argv.ext = normalizeExt(argv.ext)
if (argv.cwd) $.cwd = argv.cwd
+ if (argv.env) {
+ const envPath = path.resolve($.cwd ?? process.cwd(), argv.env)
+ $.env = readEnvFromFile(envPath, process.env)
+ }
if (argv.verbose) $.verbose = true
if (argv.quiet) $.quiet = true
if (argv.shell) $.shell = argv.shell
src/util.ts
@@ -357,3 +357,24 @@ 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 => {
+ return content.split(/\r?\n/).reduce<NodeJS.ProcessEnv>((r, line) => {
+ const [k] = line.trim().split('=', 1)
+ const v = line.trim().slice(k.length + 1)
+ if (k && v) r[k] = v
+ return r
+ }, {})
+}
+
+export const readEnvFromFile = (
+ filepath: string,
+ env: NodeJS.ProcessEnv = process.env
+): NodeJS.ProcessEnv => {
+ const content = fs.readFileSync(path.resolve(filepath), 'utf8')
+
+ return {
+ ...env,
+ ...parseDotenv(content),
+ }
+}
test/cli.test.js
@@ -150,6 +150,55 @@ describe('cli', () => {
assert.ok(p.stderr.endsWith(cwd + '\n'))
})
+ test('supports `--env` options with file', async () => {
+ const env = tmpfile(
+ '.env',
+ `FOO=BAR
+ BAR=FOO+`
+ )
+ const file = `
+ console.log((await $\`echo $FOO\`).stdout);
+ console.log((await $\`echo $BAR\`).stdout)
+ `
+
+ const out = await $`node build/cli.js --env=${env} <<< ${file}`
+ fs.remove(env)
+ assert.equal(out.stdout, 'BAR\n\nFOO+\n\n')
+ })
+
+ test('supports `--env` and `--cwd` options with file', async () => {
+ const env = tmpfile(
+ '.env',
+ `FOO=BAR
+ BAR=FOO+`
+ )
+ const dir = tmpdir()
+ const file = `
+ console.log((await $\`echo $FOO\`).stdout);
+ console.log((await $\`echo $BAR\`).stdout)
+ `
+
+ const out =
+ await $`node build/cli.js --cwd=${dir} --env=${env} <<< ${file}`
+ fs.remove(env)
+ fs.remove(dir)
+ assert.equal(out.stdout, 'BAR\n\nFOO+\n\n')
+ })
+
+ test('supports handling errors with the `--env` option', async () => {
+ const file = `
+ console.log((await $\`echo $FOO\`).stdout);
+ console.log((await $\`echo $BAR\`).stdout)
+ `
+ try {
+ await $`node build/cli.js --env=./env <<< ${file}`
+ fs.remove(env)
+ assert.throw()
+ } catch (e) {
+ assert.equal(e.exitCode, 1)
+ }
+ })
+
test('scripts from https 200', async () => {
const resp = await fs.readFile(path.resolve('test/fixtures/echo.http'))
const port = await getPort()
test/util.test.js
@@ -29,6 +29,8 @@ import {
tempfile,
preferLocalBin,
toCamelCase,
+ parseDotenv,
+ readEnvFromFile,
} from '../build/util.js'
describe('util', () => {
@@ -139,3 +141,30 @@ describe('util', () => {
assert.equal(toCamelCase('kebab-input-str'), 'kebabInputStr')
})
})
+
+test('parseDotenv()', () => {
+ assert.deepEqual(parseDotenv('ENV=value1\nENV2=value24'), {
+ ENV: 'value1',
+ ENV2: 'value24',
+ })
+ assert.deepEqual(parseDotenv(''), {})
+})
+
+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')
+ })
+})
.size-limit.json
@@ -2,14 +2,14 @@
{
"name": "zx/core",
"path": ["build/core.cjs", "build/util.cjs", "build/vendor-core.cjs"],
- "limit": "76 kB",
+ "limit": "77 kB",
"brotli": false,
"gzip": false
},
{
"name": "zx/index",
"path": "build/*.{js,cjs}",
- "limit": "804 kB",
+ "limit": "805 kB",
"brotli": false,
"gzip": false
},
@@ -30,7 +30,7 @@
{
"name": "all",
"path": "build/*",
- "limit": "841 kB",
+ "limit": "842 kB",
"brotli": false,
"gzip": false
}