Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/973 provide configuration via env vars #988

Merged

Conversation

pafik13
Copy link
Contributor

@pafik13 pafik13 commented Dec 14, 2024

Fixes #973

  process.env.ZX_VERBOSE = 'true'
  const envVariables = extractFromEnv()
  envVariables.verbose /// <- here should be Boolean
  • Tests pass
  • Appropriate changes to README are included in PR

Copy link

google-cla bot commented Dec 14, 2024

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

src/util.ts Outdated
group.toUpperCase().replace('-', '').replace('_', '')
)

export function extractFromEnv<T>(prefix: string = 'ZX_') {
Copy link
Collaborator

@antongolub antongolub Dec 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I'd suggest getZxDefaults
  2. Let's extend signature for more convenient testing: (defs: Options, prefix: string = 'ZX_', env = process.env)
  3. The reduce API might be better here:
return Object.entries(env).reduce((m, [k, v]) => {
  if (k.startsWith(prefix)) {
    const _k = snakeToCamel(key.slice(prefix.length))
    const _v = ({true: true, false: false})[v.toLowerCase()] ?? v
    if (typeof _v === typeof defs[_k])
      m[_k] =  _v
  }
  return m
}, defs)
// core.ts
const defaults = getZxDefaults({
  [CWD]:          process.cwd(),
  [SYNC]:         false,
  verbose:        false,
  //..
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty nice. Thank you. I will rewrite it ASAP)

There is only one thing that worries me in default value type checking: mixed types. For example, shell and preferLocal. In defaults it a boolean, but we can pass a string programmatically. This condition rejects such values.

What do you think about this? @antongolub

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated code with some potentially improvements:

  1. I iterate through defaults cause it contains less keys then env (i hope)))
  2. I use parameter extra to determine optional fields types and additional type for localPrefer

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k.startsWith(prefix) is cheap, so its fine to traverse the whole env

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can avoid extra in arguments. Fn internal constant is fine here:

const types = {
   shell: ['string', 'boolean']
}

if ([typeof defs[_k], ...types[_k]].includes(typeof _v))

@antongolub
Copy link
Collaborator

Hey, @pafik13,

  1. I'm afraid, no chance to land w/o CLA sign
  2. type assertions against the default map values are necessary.

@antongolub antongolub added the ossln24 OSS Library Night 2024 label Dec 15, 2024
@pafik13 pafik13 requested a review from antongolub December 16, 2024 14:03
src/core.ts Outdated
}

// prettier-ignore
export const defaults: Options = getZxDefaults(
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the original formatting here for git blame

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// prettier-ignore
export const defaults: Options = getZxDefaults({
  // ... to keep git blame
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I restored formatting, but it didn't help (:

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prettier-ignore should go above the export const defaults declaration

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was there and extended only to the closest expression - the function call...
JSON declaration was out of scope)
Now, JSON isn't formatted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was hard, but I did it.
Thank you)

@pafik13 pafik13 requested a review from antongolub December 16, 2024 19:15
@pafik13
Copy link
Contributor Author

pafik13 commented Dec 16, 2024

image

src/core.ts Outdated
}

// prettier-ignore
export const defaults: Options = getZxDefaults(
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// prettier-ignore
export const defaults: Options = getZxDefaults({
  // ... to keep git blame
})

src/core.ts Outdated
const _k = snakeToCamel(k.slice(prefix.length))
const _v = { true: true, false: false }[v.toLowerCase()] ?? v
if (
[typeof defs[_k as keyof Options], ...(types[_k] ?? [])].includes(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{...undefined}
undefined is spreadable, afair, isnt it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the same, but output from my Browser console

[...undefined]
VM153:1 Uncaught TypeError: undefined is not iterable
    at <anonymous>:1:5

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but in your case it works)

> {...undefined}
< {}

src/core.ts Outdated

const o = Object.entries(env).reduce<Record<string, string | boolean>>(
(m, [k, v]) => {
if (k.startsWith(prefix) && v) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor optimization: v && k.startsWith(prefix)

src/core.ts Outdated
shell: ['string', 'boolean'],
halt: ['boolean'],
preferLocal: ['string', 'boolean'],
input: ['string'],
Copy link
Collaborator

@antongolub antongolub Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should restrict the accepted env opts: sync, nothrow, input, halt, stdio can break the script logic, while verbose, shell, quiet, preferLocal just change its behaviour. Moreover, timeout does not belong to defaults, but it looks reasonable to set externally.

const allowed = new Set(['timeout', 'shell', ...])
// ...
if (allowed.has(_k)) {
}

And if somebody is setting ZX_CWD=true, wrong typecast to bool is the least of problems. So maybe we can skip the type check at all

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plz clarify what you suggest:

  • determine only one list of acceptable properties (verbose, shell, quiet, preferLocal) and try it extract from env with type-checking

  • determine two lists:

    • acceptable - extract w/o type check
    • unacceptable - extract with strict type check

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this?

(defs: Options, prefix = 'ZX_', env = process.env) => Object.entries(env).reduce<Options>((m, [k, v]) => {
  const allowed = new Set(['preferLocal', 'detached', 'verbose', 'quiet', 'timeout', 'timeoutSignal', 'prefix', 'postfix'])
  if (v && k.startsWith(prefix)) {
    const _k = snakeToCamel(key.slice(prefix.length))
    const _v = ({true: true, false: false})[v.toLowerCase()] ?? v
    if (allowed.has(_k))
      m[_k] =  _v
  }
  return m
}, defs)

Copy link
Contributor Author

@pafik13 pafik13 Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You insistently suggest pass Options to reduce method - it isn't work in this place m[_k] = _v. Since _k is random key of Options, m[_k] has type never as union of string, boolean, StdioInput and other values of Options.
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this?

(defs: Options, prefix = 'ZX_', env = process.env) => Object.entries(env).reduce<Options>((m, [k, v]) => {
  const allowed = new Set(['preferLocal', 'detached', 'verbose', 'quiet', 'timeout', 'timeoutSignal', 'prefix', 'postfix'])
  if (v && k.startsWith(prefix)) {
    const _k = snakeToCamel(key.slice(prefix.length))
    const _v = ({true: true, false: false})[v.toLowerCase()] ?? v
    if (allowed.has(_k))
      m[_k] =  _v
  }
  return m
}, defs)

In that case we can assign to verbose and quiet some string value. It's not good, in my opinion.
So, I combined 'allowed' and type-check together into types and use only it.

@pafik13 pafik13 requested a review from antongolub December 16, 2024 21:51
Copy link
Collaborator

@antongolub antongolub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Thanks for the contribution.

@antongolub antongolub merged commit d9c4f9a into google:main Dec 17, 2024
21 checks passed
antongolub added a commit to antongolub/zx that referenced this pull request Dec 17, 2024
@antongolub antongolub mentioned this pull request Dec 17, 2024
2 tasks
antongolub added a commit to antongolub/zx that referenced this pull request Dec 17, 2024
antongolub added a commit that referenced this pull request Dec 17, 2024
@pafik13
Copy link
Contributor Author

pafik13 commented Dec 17, 2024

Nice! Thanks for the contribution.

Thanks for the mentorship)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ossln24 OSS Library Night 2024
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: provide configuration via env vars
2 participants