Skip to content

Commit

Permalink
feat(cli): add env files support (#1022)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
easymikey authored Dec 24, 2024
1 parent 504a960 commit 36b42d8
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 5 deletions.
6 changes: 3 additions & 3 deletions .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand All @@ -30,7 +30,7 @@
{
"name": "all",
"path": "build/*",
"limit": "841 kB",
"limit": "842 kB",
"brotli": false,
"gzip": false
}
Expand Down
13 changes: 13 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
2 changes: 2 additions & 0 deletions man/zx.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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/')}
Expand All @@ -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,
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
49 changes: 49 additions & 0 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
29 changes: 29 additions & 0 deletions test/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
tempfile,
preferLocalBin,
toCamelCase,
parseDotenv,
readEnvFromFile,
} from '../build/util.js'

describe('util', () => {
Expand Down Expand Up @@ -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')
})
})

0 comments on commit 36b42d8

Please sign in to comment.