diff --git a/docs/v7/api.md b/docs/v7/api.md index b7fd77d4dc..1601009c8b 100644 --- a/docs/v7/api.md +++ b/docs/v7/api.md @@ -204,3 +204,17 @@ The [yaml](https://www.npmjs.com/package/yaml) package. ```js console.log(YAML.parse('foo: bar').foo) ``` + + +## loadDotenv + +Read env files and collects it into environment variables. + +```js +const env = loadDotenv(env1, env2) +console.log((await $({ env })`echo $FOO`).stdout) +--- +const env = loadDotenv(env1) +$.env = env +console.log((await $`echo $FOO`).stdout) +``` \ No newline at end of file diff --git a/src/goods.ts b/src/goods.ts index b2b1c1bf8a..88ae1dd4fb 100644 --- a/src/goods.ts +++ b/src/goods.ts @@ -21,6 +21,7 @@ import { isStringLiteral, parseBool, parseDuration, + readEnvFromFile, toCamelCase, } from './util.js' import { @@ -217,3 +218,10 @@ export async function spinner( } }) } + +/** + * + * Read env files and collects it into environment variables + */ +export const loadDotenv = (...files: string[]): NodeJS.ProcessEnv => + files.reduce((m, f) => readEnvFromFile(f, m), {}) diff --git a/test/cli.test.js b/test/cli.test.js index 86f6565a3c..a15170f6ba 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -17,21 +17,8 @@ import { test, describe, before, after } from 'node:test' import { fileURLToPath } from 'node:url' import net from 'node:net' import getPort from 'get-port' -import { - argv, - importPath, - injectGlobalRequire, - isMain, - main, - normalizeExt, - runScript, - printUsage, - scriptFromStdin, - scriptFromHttp, - transformMarkdown, - writeAndImport, -} from '../build/cli.js' -import { $, path, fs, tmpfile, tmpdir } from '../build/index.js' +import { $, path, tmpfile, tmpdir, fs } from '../build/index.js' +import { isMain, normalizeExt, transformMarkdown } from '../build/cli.js' const __filename = fileURLToPath(import.meta.url) const spawn = $.spawn diff --git a/test/core.test.js b/test/core.test.js index 02feb5828a..8c4d7cc49b 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -26,24 +26,20 @@ import { resolveDefaults, cd, syncProcessCwd, - log, - kill, - defaults, within, usePowerShell, usePwsh, useBash, } from '../build/core.js' import { + tempfile, fs, - nothrow, - quiet, + quote, + quotePowerShell, sleep, - tempfile, - tempdir, + quiet, which, } from '../build/index.js' -import { quote, quotePowerShell } from '../build/util.js' describe('core', () => { describe('resolveDefaults()', () => { diff --git a/test/export.test.js b/test/export.test.js index 8b714d11d6..fc616c1043 100644 --- a/test/export.test.js +++ b/test/export.test.js @@ -331,6 +331,7 @@ describe('index', () => { assert.equal(typeof index.globby.isGitIgnored, 'function', 'index.globby.isGitIgnored') assert.equal(typeof index.globby.isGitIgnoredSync, 'function', 'index.globby.isGitIgnoredSync') assert.equal(typeof index.kill, 'function', 'index.kill') + assert.equal(typeof index.loadDotenv, 'function', 'index.loadDotenv') assert.equal(typeof index.log, 'function', 'index.log') assert.equal(typeof index.minimist, 'function', 'index.minimist') assert.equal(typeof index.nothrow, 'function', 'index.nothrow') diff --git a/test/goods.test.js b/test/goods.test.js index 6aed154606..04882e865b 100644 --- a/test/goods.test.js +++ b/test/goods.test.js @@ -13,9 +13,9 @@ // limitations under the License. import assert from 'node:assert' -import { test, describe } from 'node:test' -import { $, chalk } from '../build/index.js' -import { echo, sleep, parseArgv } from '../build/goods.js' +import { test, describe, after } from 'node:test' +import { $, chalk, fs, tempfile } from '../build/index.js' +import { echo, sleep, parseArgv, loadDotenv } from '../build/goods.js' describe('goods', () => { function zx(script) { @@ -173,4 +173,45 @@ describe('goods', () => { } ) }) + + describe('loadDotenv()', () => { + const env1 = tempfile( + '.env', + `FOO=BAR + BAR=FOO+` + ) + const env2 = tempfile('.env.default', `BAR2=FOO2`) + + after(() => { + fs.remove(env1) + fs.remove(env2) + }) + + test('handles multiple dotenv files', async () => { + const env = loadDotenv(env1, env2) + + assert.equal((await $({ env })`echo $FOO`).stdout, 'BAR\n') + assert.equal((await $({ env })`echo $BAR`).stdout, 'FOO+\n') + assert.equal((await $({ env })`echo $BAR2`).stdout, 'FOO2\n') + }) + + test('handles replace evn', async () => { + const env = loadDotenv(env1) + $.env = env + assert.equal((await $`echo $FOO`).stdout, 'BAR\n') + assert.equal((await $`echo $BAR`).stdout, 'FOO+\n') + $.env = process.env + }) + + test('handle error', async () => { + try { + loadDotenv('./.env') + + assert.throw() + } catch (e) { + assert.equal(e.code, 'ENOENT') + assert.equal(e.errno, -2) + } + }) + }) }) diff --git a/test/util.test.js b/test/util.test.js index 6a98189c62..3daf96b7f3 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -14,7 +14,8 @@ import assert from 'node:assert' import fs from 'node:fs' -import { test, describe } from 'node:test' +import { test, describe, after } from 'node:test' +import { fs as fsCore } from '../build/index.js' import { formatCmd, isString, @@ -164,8 +165,10 @@ e.g. a private SSH key }) describe('readEnvFromFile()', () => { + const file = tempfile('.env', 'ENV=value1\nENV2=value24') + after(() => fsCore.remove(file)) + 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') @@ -173,7 +176,6 @@ describe('readEnvFromFile()', () => { }) 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')