diff --git a/.size-limit.json b/.size-limit.json index 3cb3b241ac..1be91f16d0 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -2,14 +2,14 @@ { "name": "zx/core", "path": ["build/core.cjs", "build/util.cjs", "build/vendor-core.cjs"], - "limit": "74 kB", + "limit": "76 kB", "brotli": false, "gzip": false }, { "name": "zx/index", "path": "build/*.{js,cjs}", - "limit": "801 kB", + "limit": "803 kB", "brotli": false, "gzip": false }, diff --git a/src/core.ts b/src/core.ts index 552a89dc4d..68d2e34b7a 100644 --- a/src/core.ts +++ b/src/core.ts @@ -52,6 +52,7 @@ import { proxyOverride, quote, quotePowerShell, + snakeToCamel, } from './util.js' const CWD = Symbol('processCwd') @@ -81,7 +82,7 @@ export interface Options { verbose: boolean sync: boolean env: NodeJS.ProcessEnv - shell: string | boolean + shell: string | true nothrow: boolean prefix: string postfix: string @@ -97,8 +98,9 @@ export interface Options { killSignal?: NodeJS.Signals halt?: boolean } + // prettier-ignore -export const defaults: Options = { +export const defaults: Options = getZxDefaults({ [CWD]: process.cwd(), [SYNC]: false, verbose: false, @@ -118,6 +120,38 @@ export const defaults: Options = { kill, killSignal: SIGTERM, timeoutSignal: SIGTERM, +}) + +export function getZxDefaults( + defs: Options, + prefix: string = 'ZX_', + env = process.env +) { + const types: Record> = { + preferLocal: ['string', 'boolean'], + detached: ['boolean'], + verbose: ['boolean'], + quiet: ['boolean'], + timeout: ['string'], + timeoutSignal: ['string'], + prefix: ['string'], + postfix: ['string'], + } + + const o = Object.entries(env).reduce>( + (m, [k, v]) => { + if (v && k.startsWith(prefix)) { + const _k = snakeToCamel(k.slice(prefix.length)) + const _v = { true: true, false: false }[v.toLowerCase()] ?? v + if (_k in types && types[_k].some((type) => type === typeof _v)) { + m[_k] = _v + } + } + return m + }, + {} + ) + return Object.assign(defs, o) } // prettier-ignore diff --git a/src/util.ts b/src/util.ts index 233bf52cae..cc26c88e67 100644 --- a/src/util.ts +++ b/src/util.ts @@ -462,3 +462,18 @@ export const proxyOverride = ( ) }, }) as T + +// https://stackoverflow.com/a/7888303 +export const camelToSnake = (str: string) => + str + .split(/(?=[A-Z])/) + .map((s) => s.toUpperCase()) + .join('_') + +// https://stackoverflow.com/a/61375162 +export const snakeToCamel = (str: string) => + str + .toLowerCase() + .replace(/([-_][a-z])/g, (group) => + group.toUpperCase().replace('-', '').replace('_', '') + ) diff --git a/test/core.test.js b/test/core.test.js index 617127cdf3..a7ed3b6baa 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -19,10 +19,47 @@ import { basename } from 'node:path' import { WriteStream } from 'node:fs' import { Readable, Transform, Writable } from 'node:stream' import { Socket } from 'node:net' -import { ProcessPromise, ProcessOutput } from '../build/index.js' +import { ProcessPromise, ProcessOutput, getZxDefaults } from '../build/index.js' import '../build/globals.js' describe('core', () => { + describe('getZxDefaults', () => { + test('verbose rewrite', async () => { + const defaults = getZxDefaults({ verbose: false }, 'ZX_', { + ZX_VERBOSE: 'true', + }) + assert.equal(defaults.verbose, true) + }) + + test('verbose ignore', async () => { + const defaults = getZxDefaults({ verbose: false }, 'ZX_', { + ZX_VERBOSE: 'true123', + }) + assert.equal(defaults.verbose, false) + }) + + test('input ignored', async () => { + const defaults = getZxDefaults({}, 'ZX_', { + ZX_INPUT: 'input', + }) + assert.equal(defaults.input, undefined) + }) + + test('preferLocal rewrite boolean', async () => { + const defaults = getZxDefaults({ preferLocal: false }, 'ZX_', { + ZX_PREFER_LOCAL: 'true', + }) + assert.equal(defaults.preferLocal, true) + }) + + test('preferLocal rewrite string', async () => { + const defaults = getZxDefaults({ preferLocal: false }, 'ZX_', { + ZX_PREFER_LOCAL: 'true123', + }) + assert.equal(defaults.preferLocal, 'true123') + }) + }) + describe('$', () => { test('is a regular function', async () => { const _$ = $.bind(null) @@ -42,12 +79,14 @@ describe('core', () => { process.env.ZX_TEST_FOO = 'foo' const foo = await $`echo $ZX_TEST_FOO` assert.equal(foo.stdout, 'foo\n') + delete process.env.ZX_TEST_FOO }) test('env vars are safe to pass', async () => { process.env.ZX_TEST_BAR = 'hi; exit 1' const bar = await $`echo $ZX_TEST_BAR` assert.equal(bar.stdout, 'hi; exit 1\n') + delete process.env.ZX_TEST_BAR }) test('arguments are quoted', async () => { diff --git a/test/util.test.js b/test/util.test.js index 1176447634..893e8cdf9b 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -31,6 +31,8 @@ import { tempdir, tempfile, preferLocalBin, + camelToSnake, + snakeToCamel, } from '../build/util.js' describe('util', () => { @@ -191,3 +193,17 @@ test('preferLocalBin()', () => { `${process.cwd()}/node_modules/.bin:${process.cwd()}:${env.PATH}` ) }) + +test('camelToSnake()', () => { + assert.equal(camelToSnake('verbose'), 'VERBOSE') + assert.equal(camelToSnake('nothrow'), 'NOTHROW') + assert.equal(camelToSnake('preferLocal'), 'PREFER_LOCAL') + assert.equal(camelToSnake('someMoreBigStr'), 'SOME_MORE_BIG_STR') +}) + +test('snakeToCamel()', () => { + assert.equal(snakeToCamel('VERBOSE'), 'verbose') + assert.equal(snakeToCamel('NOTHROW'), 'nothrow') + assert.equal(snakeToCamel('PREFER_LOCAL'), 'preferLocal') + assert.equal(snakeToCamel('SOME_MORE_BIG_STR'), 'someMoreBigStr') +})