Comparing version 0.2.0 to 0.3.0
@@ -24,4 +24,4 @@ "use strict"; | ||
const z = __importStar(require("zod")); | ||
const colorette_1 = require("colorette"); | ||
const preprocessors_1 = require("./preprocessors"); | ||
const reporter_1 = require("./reporter"); | ||
/** | ||
@@ -58,10 +58,27 @@ * Since there might be a provided default value of `null` or `undefined`, we | ||
const envValue = env[key]; | ||
let defaultUsed = false; | ||
let defaultValue; | ||
try { | ||
if (schemaOrSpec instanceof z.ZodType) { | ||
parsed[key] = (0, preprocessors_1.getSchemaWithPreprocessor)(schemaOrSpec).parse(envValue); | ||
if (envValue == null && schemaOrSpec instanceof z.ZodDefault) { | ||
defaultUsed = true; | ||
defaultValue = schemaOrSpec._def.defaultValue(); | ||
// we "unwrap" the default value ourselves and pass it to the schema. | ||
// in the very unlikely case that the value isn't stable AND | ||
// validation fails, this ensures the default value we report is the | ||
// one that was actually used. | ||
// (consider `z.number().gte(0.5).default(() => Math.random())` -- if | ||
// we invoked the default getter and got 0.7, and then ran the parser | ||
// against a missing env var and it generated another default of 0.4, | ||
// we'd report a default value that _should_ have passed.) | ||
parsed[key] = schemaOrSpec.parse(defaultValue, { errorMap: reporter_1.errorMap }); | ||
} | ||
else { | ||
parsed[key] = (0, preprocessors_1.getSchemaWithPreprocessor)(schemaOrSpec).parse(envValue, { errorMap: reporter_1.errorMap }); | ||
} | ||
} | ||
else if (envValue == null) { | ||
const [hasDefault, defaultValue] = resolveDefaultValueForSpec(schemaOrSpec.defaults, env["NODE_ENV"]); | ||
if (hasDefault) { | ||
parsed[key] = schemaOrSpec.schema.parse(defaultValue); | ||
[defaultUsed, defaultValue] = resolveDefaultValueForSpec(schemaOrSpec.defaults, env["NODE_ENV"]); | ||
if (defaultUsed) { | ||
parsed[key] = schemaOrSpec.schema.parse(defaultValue, { errorMap: reporter_1.errorMap }); | ||
} | ||
@@ -73,15 +90,21 @@ else { | ||
// `null` for us). | ||
parsed[key] = (0, preprocessors_1.getSchemaWithPreprocessor)(schemaOrSpec.schema).parse(envValue); | ||
parsed[key] = (0, preprocessors_1.getSchemaWithPreprocessor)(schemaOrSpec.schema).parse(envValue, { errorMap: reporter_1.errorMap }); | ||
} | ||
} | ||
else { | ||
parsed[key] = (0, preprocessors_1.getSchemaWithPreprocessor)(schemaOrSpec.schema).parse(envValue); | ||
parsed[key] = (0, preprocessors_1.getSchemaWithPreprocessor)(schemaOrSpec.schema).parse(envValue, { errorMap: reporter_1.errorMap }); | ||
} | ||
} | ||
catch (e) { | ||
errors.push([key, envValue, e]); | ||
errors.push({ | ||
key, | ||
receivedValue: envValue, | ||
error: e, | ||
defaultUsed, | ||
defaultValue, | ||
}); | ||
} | ||
} | ||
if (errors.length > 0) { | ||
throw new Error(reportErrors(errors, schemas)); | ||
throw new Error((0, reporter_1.reportErrors)(errors, schemas)); | ||
} | ||
@@ -91,12 +114,2 @@ return parsed; | ||
exports.parseEnv = parseEnv; | ||
const indent = (msg) => msg | ||
.split("\n") | ||
.map((line) => ` ${line}`) | ||
.join("\n"); | ||
function reportErrors(errors, schemas) { | ||
const errorMap = errors.map(([k, v, e]) => ` [${(0, colorette_1.yellow)(k)}]:\n${indent(e instanceof Error ? e.message : JSON.stringify(e))}\n (received ${typeof v === "undefined" ? (0, colorette_1.cyan)("undefined") : `\`${(0, colorette_1.cyan)(v)}\``})${schemas[k]?.description | ||
? `\n\n Description of [${(0, colorette_1.yellow)(k)}]: ${schemas[k].description}` | ||
: ""}`); | ||
return `${(0, colorette_1.red)("Errors found!")}\n${errorMap.join("\n\n")}\n`; | ||
} | ||
//# sourceMappingURL=parse-env.js.map |
{ | ||
"name": "znv", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Parse your environment with Zod schemas", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
137
README.md
# znv | ||
<p align="center"> | ||
<img src="logo.svg" height="90"> | ||
</p> | ||
<p align="center"> | ||
<a href="https://www.npmjs.com/package/znv"> | ||
<img src="https://img.shields.io/npm/v/znv.svg?logo=npm" alt="NPM version" /> | ||
</a> | ||
</p> | ||
@@ -15,2 +21,12 @@ Parse your environment with [Zod](https://github.com/colinhacks/zod). | ||
- [Quickstart](#quickstart) | ||
- [Motivation](#motivation) | ||
- [Usage](#usage) | ||
- [`parseEnv`](#parseenvenvironment-schemas) | ||
- [Extra schemas](#extra-schemas) | ||
- [Coercion rules](#coercion-rules) | ||
- [Comparison to other libraries](#comparison-to-other-libraries) | ||
- [Complementary tooling](#complementary-tooling) | ||
- [How do I pronounce znv?](#how-do-i-pronounce-znv) | ||
## Quickstart | ||
@@ -36,5 +52,5 @@ | ||
In the above example, the exported values `API_SERVER`, `LLAMA_COUNT`, and | ||
`COLOR` are all guaranteed to be defined and will be typed as `string`, | ||
`number`, and `'red' | 'blue'`, respectively. | ||
In the above example, the exported values `LLAMA_COUNT` and `COLOR` are | ||
guaranteed to be defined and will be typed as `number` and `'red' | 'blue'` | ||
respectively. | ||
@@ -66,3 +82,3 @@ A more elaborate example: | ||
production: "my-cool-llama.website", | ||
staging: "cool-llama-staging.cloud-provider.zone", | ||
test: "cool-llama-staging.cloud-provider.zone", | ||
@@ -116,10 +132,11 @@ // "_" is a special token that can be used in `defaults`. its value will | ||
certain code path is hit. A good way to avoid this is to **declare and validate | ||
environment variables in one place** and to export the validated result, so that | ||
environment variables in one place** and export the validated result, so that | ||
other parts of your code can make their dependencies on these vars explicit. | ||
Additionally, env vars represent one of the _boundaries_ of your application, | ||
similar to an FFI call or a server request. In TypeScript, as in many other | ||
typed languages, these boundaries present a challenge to maintaining a | ||
well-typed app. [Zod](https://github.com/colinhacks/zod) does an excellent job | ||
at parsing and validating poorly-typed data into clean, well-typed values. | ||
Env vars represent one of the _boundaries_ of your application, just like file | ||
I/O or a server request. In TypeScript, as in many other typed languages, these | ||
boundaries present a challenge to maintaining a well-typed app. | ||
[Zod](https://github.com/colinhacks/zod) does an excellent job at parsing and | ||
validating poorly-typed data at boundaries into clean, well-typed values. znv | ||
facilitates its use for environment validation. | ||
@@ -141,2 +158,7 @@ ### What does znv actually do? | ||
znv also makes it easy to define defaults for env vars based on your | ||
environment. Zod allows you to define schema defaults, but making a given | ||
default vary by environment or only act as a fallback in certain environments is | ||
not straightforward. | ||
## Usage | ||
@@ -155,6 +177,9 @@ | ||
You usually want to pass in `process.env` as the first argument. **It is not | ||
recommended** to use znv for general-purpose schema validation — just use Zod | ||
for that. | ||
You usually want to pass in `process.env` as the first argument. | ||
> **It is not recommended** to use znv for general-purpose schema validation — | ||
> just use Zod (with | ||
> [preprocessors](https://github.com/colinhacks/zod#preprocess) to handle | ||
> coercion, if necessary). | ||
#### `schemas: Record<string, ZodType | DetailedSpec>` | ||
@@ -210,15 +235,15 @@ | ||
Note that it is **not recommended** to use `staging` as a possible value for | ||
`NODE_ENV`. Your staging environment should be as similar to your production | ||
environment as possible, and `NODE_ENV=production` has special meaning for | ||
several tools and libraries. For example, | ||
[`npm install`](https://docs.npmjs.com/cli/v8/commands/npm-install) and | ||
[`yarn install`](https://classic.yarnpkg.com/en/docs/cli/install#toc-yarn-install-production-true-false) | ||
won't install `devDependencies` if `NODE_ENV=production`; | ||
[Express](https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production) | ||
and [React](https://reactjs.org/docs/optimizing-performance.html) will also | ||
behave differently depending on whether `NODE_ENV` is `production` or not. | ||
Instead, your staging environment should also set `NODE_ENV=production`, and | ||
you should define your own env var(s) for any special configuration that's | ||
necessary for your staging environment. | ||
> (As an aside, it is **not recommended** to use `staging` as a possible value | ||
> for `NODE_ENV`. Your staging environment should be as similar to your | ||
> production environment as possible, and `NODE_ENV=production` has special | ||
> meaning for several tools and libraries. For example, | ||
> [`npm install`](https://docs.npmjs.com/cli/v8/commands/npm-install) and | ||
> [`yarn install`](https://classic.yarnpkg.com/en/docs/cli/install#toc-yarn-install-production-true-false) | ||
> by default won't install `devDependencies` if `NODE_ENV=production`; | ||
> [Express](https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production) | ||
> and [React](https://reactjs.org/docs/optimizing-performance.html) will also | ||
> behave differently depending on whether `NODE_ENV` is `production` or not. | ||
> Instead, your staging environment should also set `NODE_ENV=production`, and | ||
> you should define your own env var(s) for any special configuration that's | ||
> necessary for your staging environment.) | ||
@@ -232,3 +257,3 @@ However, `_` still lets you express a few interesting scenarios: | ||
// default for all environments, but require the var to be defined in prod. | ||
// default for all environments, but require the var to be passed in in prod. | ||
{ production: undefined, _: "dev default" } | ||
@@ -244,10 +269,11 @@ | ||
`parseEnv` doesn't restrict or validate `NODE_ENV`, but you can add it to your | ||
schemas like any other env var. For example, you could use | ||
`parseEnv` doesn't restrict or validate `NODE_ENV` to any particular values, | ||
but you can add `NODE_ENV` to your schemas like any other env var. For | ||
example, you could use | ||
`NODE_ENV: z.enum(["production", "development", "test", "ci"])` to enforce | ||
that `NODE_ENV` is always defined and is one of the expected values. | ||
that `NODE_ENV` is always defined and is one of those four expected values. | ||
### Extra schemas | ||
znv exports a very small number of extra schema for common env var types. | ||
znv exports a very small number of extra schemas for common env var types. | ||
@@ -260,9 +286,6 @@ #### `port()` | ||
`deprecate()` is an alias for `z.undefined().transform(() => undefined as never)`. | ||
This will do two things: | ||
`deprecate()` is an alias for | ||
`z.undefined().transform(() => undefined as never)`. `parseEnv` will throw if a | ||
var using the `deprecate()` schema is passed in from the environment. | ||
- throw if the var is defined in the environment | ||
- yield a TypeScript error if the value returned from `parseEnv` is used | ||
anywhere in your code. | ||
## Coercion rules | ||
@@ -287,9 +310,8 @@ | ||
- If your schema's input is a Date, znv will call `new Date()` with the input | ||
value. This has a number of pitfalls, and `Date()` constructor is excessively | ||
forgiving. The value is passed in as a string, which means trying to pass a | ||
Unix epoch will yield unexpected results. (Epochs need to be passed in as | ||
`number`; `new Date()` with an epoch as a string might give you an | ||
`invalid date` or might equally give you a well-formed but utterly incorrect | ||
date.) _You should only pass in ISO 8601 date strings_, such as those returned | ||
by `Date.prototype.toISOString()`. | ||
value. This has a number of pitfalls, since the `Date()` constructor is | ||
excessively forgiving. The value is passed in as a string, which means trying | ||
to pass a Unix epoch will yield unexpected results. (Epochs need to be passed | ||
in as `number`: `new Date()` with an epoch as a string will either give you | ||
`invalid date` or a completely nonsensical date.) _You should only pass in ISO | ||
8601 date strings_, such as those returned by `Date.prototype.toISOString()`. | ||
- Zod defines "nullable" as distinct from "optional". If your schema is | ||
@@ -303,9 +325,8 @@ nullable, znv will coerce `undefined` to `null`. Generally it's preferred to | ||
Envalid is a nice little library, and znv's design was inspired by it. Envalid | ||
has a similar API and is written in TypeScript. It performs some inference of | ||
the return value based on the validator schema you pass in, but won't do things | ||
like narrow enumerated types (`str({ choices: ['a', 'b'] })`) to a union of | ||
literals. Expressing defaults is more limited (you can't have different defaults | ||
for `test` and `development` environments, for example). Defaults are not passed | ||
through validators. | ||
Envalid is a nice library that inspired znv's API design. Envalid is written in | ||
TypeScript and performs some inference of the return value based on the | ||
validator schema you pass in, but won't do things like narrow enumerated types | ||
(`str({ choices: ['a', 'b'] })`) to a union of literals. Expressing defaults is | ||
more limited (you can't have different defaults for `test` and `development` | ||
environments, for example). Defaults are not passed through validators. | ||
@@ -322,5 +343,6 @@ Envalid's validators are built-in and express a handful of types with limited | ||
Joi is the "classic" schema validation library. Joi is written in JavaScript, | ||
and its type definitions support a very limited form of inference when they work | ||
at all. | ||
Joi is the Cadillac of schema validation libraries. Its default of coercing | ||
strings to the target type makes it easy to adopt for environment validation. | ||
Unfortunately, Joi is written in JavaScript and its type definitions support a | ||
very limited form of inference when they work at all. | ||
@@ -335,1 +357,8 @@ ## Complementary tooling | ||
znv also works great with [dotenv](https://github.com/motdotla/dotenv). | ||
## How do I pronounce znv? | ||
If you usually pronounce "z" as "zed," then you could say "zenv." If you usually | ||
pronounce "z" as "zee," you could say "zee en vee." | ||
Or do your own thing. I'm not the boss of you. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50239
21
515
351