Commit d9c4f9a

pafik13 <re1ax_88@mail.ru>
2024-12-17 15:39:40
feat: provide configuration via env vars (#988)
* fear(util): add extractFromEnv * chore(config): increase size limits * test(core): clean up env var some tests * test(util): cover extractFromEnv * feat(util): remove extractFromEnv and snakeToCamel, add camelToSnake * feat(core): implement getZxDefaults * test: add tests for getZxDefaults and camelToSnake * chore: up size limit * feat(util): add snakeToCamel * refactor(core): remove extra param from getZxDefaults * test(util): cover snakeToCamel * test(core): actualize getZxDefaults * feat(core): extend types in getZxDefaults and use it as allowed list * test(core): update suites * chore(core): swap getZxDefaults definition and usage for better git blame * chore(core): remove one tab for defaults * chore: update .size-limit.json --------- Co-authored-by: Anton Golub <antongolub@antongolub.com> Co-authored-by: Anton Golub <golub.anton@gmail.com>
1 parent 3e5bcb0
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<PropertyKey, Array<'string' | 'boolean'>> = {
+    preferLocal: ['string', 'boolean'],
+    detached: ['boolean'],
+    verbose: ['boolean'],
+    quiet: ['boolean'],
+    timeout: ['string'],
+    timeoutSignal: ['string'],
+    prefix: ['string'],
+    postfix: ['string'],
+  }
+
+  const o = Object.entries(env).reduce<Record<string, string | boolean>>(
+    (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
src/util.ts
@@ -462,3 +462,18 @@ export const proxyOverride = <T extends object>(
       )
     },
   }) 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('_', '')
+    )
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 () => {
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')
+})
.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
   },