diff --git a/.gitignore b/.gitignore index bdf5a57..903b0be 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules coverage npm-debug.log yarn-error.log -dist \ No newline at end of file +dist +.vscode \ No newline at end of file diff --git a/README.md b/README.md index e72b1fa..2aabfee 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,8 @@ Each validation function accepts an (optional) object with the following attribu ## Custom validators +### Basic usage + You can easily create your own validator functions with `envalid.makeValidator()`. It takes a function as its only parameter, and should either return a cleaned value, or throw if the input is unacceptable: @@ -149,7 +151,43 @@ const env = cleanEnv(process.env, { INITIALS: twochars() }); ``` +### TypeScript users + +You can use either one of `makeValidator`, `makeExactValidator` and `makeStructuredValidator` +depending on your use case. + +#### `makeValidator` + +This validator has the output narrowed-down to a subtype of `BaseT` (e.g. `str`). +Example of a custom integer validator: + +```ts +const int = makeValidator((input: string) => { + const coerced = parseInt(input, 10) + if (Number.isNaN(coerced)) throw new EnvError(`Invalid integer input: "${input}"`) + return coerced +}) +const MAX_RETRIES = int({ choices: [1, 2, 3, 4] }) +// Narrows down output type to '1 | 2 | 3 | 4' witch is a subtype of 'number' +``` + +#### `makeExactValidator` + +This validator has the output widened to `T` (e.g. `bool`). To understand the difference +with `makeValidator`, let's use it in the same scenario: + +```ts +const int = makeExactValidator((input: string) => { + const coerced = parseInt(input, 10) + if (Number.isNaN(coerced)) throw new EnvError(`Invalid integer input: "${input}"`) + return coerced +}) +const MAX_RETRIES = int({ choices: [1, 2, 3, 4] }) +// Output type is 'number' +``` +As you can see in this instance, _the output type is exactly `number`, the parameter type of +`makeExactValidator`_. Also note that here, `int` is not parametrizable. ## Error Reporting diff --git a/package.json b/package.json index d584e56..9adca4b 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "devDependencies": { "@types/jest": "28.1.8", "@types/node": "17.0.21", + "expect-type": "^0.15.0", "husky": "7.0.4", "jest": "28.1.3", "prettier": "2.5.1", diff --git a/src/core.ts b/src/core.ts index bab2a13..0e29131 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,5 +1,5 @@ import { EnvError, EnvMissingError } from './errors' -import { CleanOptions, Spec, ValidatorSpec } from './types' +import { CleanOptions, SpecsOutput, Spec, ValidatorSpec } from './types' import { defaultReporter } from './reporter' export const testOnlySymbol = Symbol('envalid - test only') @@ -51,18 +51,19 @@ const isTestOnlySymbol = (value: any): value is symbol => value === testOnlySymb /** * Perform the central validation/sanitization logic on the full environment object */ -export function getSanitizedEnv( +export function getSanitizedEnv( environment: unknown, - specs: { [K in keyof T]: ValidatorSpec }, - options: CleanOptions = {}, -): T { - let cleanedEnv = {} as T - const errors: Partial> = {} - const varKeys = Object.keys(specs) as Array + specs: S, + options: CleanOptions> = {}, +): SpecsOutput { + let cleanedEnv = {} as SpecsOutput + const castedSpecs = specs as unknown as Record> + const errors = {} as Record + const varKeys = Object.keys(castedSpecs) as Array const rawNodeEnv = readRawEnvValue(environment, 'NODE_ENV') for (const k of varKeys) { - const spec = specs[k] + const spec = castedSpecs[k] const rawValue = readRawEnvValue(environment, k) // If no value was given and default/devDefault were provided, return the appropriate default @@ -72,12 +73,10 @@ export function getSanitizedEnv( const usingDevDefault = rawNodeEnv && rawNodeEnv !== 'production' && spec.hasOwnProperty('devDefault') if (usingDevDefault) { - // @ts-expect-error default values can break the rules slightly by being explicitly set to undefined cleanedEnv[k] = spec.devDefault continue } - if (spec.hasOwnProperty('default')) { - // @ts-expect-error default values can break the rules slightly by being explicitly set to undefined + if ('default' in spec) { cleanedEnv[k] = spec.default continue } @@ -89,7 +88,6 @@ export function getSanitizedEnv( } if (rawValue === undefined) { - // @ts-ignore (fixes #138) Need to figure out why explicitly undefined default/devDefault breaks inference cleanedEnv[k] = undefined throw new EnvMissingError(formatSpecDescription(spec)) } else { diff --git a/src/envalid.ts b/src/envalid.ts index d70d418..c0c453c 100644 --- a/src/envalid.ts +++ b/src/envalid.ts @@ -1,4 +1,4 @@ -import { CleanedEnvAccessors, CleanOptions, ValidatorSpec } from './types' +import { CleanedEnv, CleanOptions } from './types' import { getSanitizedEnv, testOnlySymbol } from './core' import { applyDefaultMiddleware } from './middleware' @@ -10,13 +10,13 @@ import { applyDefaultMiddleware } from './middleware' * @param specs An object that specifies the format of required vars. * @param options An object that specifies options for cleanEnv. */ -export function cleanEnv( +export function cleanEnv( environment: unknown, - specs: { [K in keyof T]: ValidatorSpec }, - options: CleanOptions = {}, -): Readonly { + specs: S, + options: CleanOptions = {}, +): CleanedEnv { const cleaned = getSanitizedEnv(environment, specs, options) - return Object.freeze(applyDefaultMiddleware(cleaned, environment)) + return Object.freeze(applyDefaultMiddleware(cleaned, environment)) as CleanedEnv } /** @@ -29,14 +29,14 @@ export function cleanEnv( * @param applyMiddleware A function that applies transformations to the cleaned env object * @param options An object that specifies options for cleanEnv. */ -export function customCleanEnv( +export function customCleanEnv( environment: unknown, - specs: { [K in keyof T]: ValidatorSpec }, - applyMiddleware: (cleaned: T, rawEnv: unknown) => MW, - options: CleanOptions = {}, + specs: S, + applyMiddleware: (cleaned: CleanedEnv, rawEnv: unknown) => MW, + options: CleanOptions = {}, ): Readonly { const cleaned = getSanitizedEnv(environment, specs, options) - return Object.freeze(applyMiddleware(cleaned, environment)) + return Object.freeze(applyMiddleware(cleaned as CleanedEnv, environment)) } /** diff --git a/src/index.ts b/src/index.ts index 782370b..f17ad33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,3 +4,4 @@ export * from './middleware' export * from './types' export * from './validators' export * from './reporter' +export { makeExactValidator, makeValidator } from './makers' diff --git a/src/makers.ts b/src/makers.ts new file mode 100644 index 0000000..59ea76e --- /dev/null +++ b/src/makers.ts @@ -0,0 +1,77 @@ +import { Spec, BaseValidator, StructuredValidator, ExactValidator } from './types' + +const internalMakeValidator = (parseFn: (input: string) => T) => { + return (spec?: Spec) => ({ ...spec, _parse: parseFn }) +} + +/** + * Creates a validator which can output subtypes of `BaseT`. E.g.: + * + * ```ts + * const int = makeValidator((input: string) => { + * // Implementation details + * }) + * const MAX_RETRIES = int({ choices: [1, 2, 3, 4] }) + * // Narrows down output type to 1 | 2 | 3 | 4 + * ``` + * + * @param parseFn - A function to parse and validate input. + * @returns A validator which output type is narrowed-down to a subtype of `BaseT` + */ +export const makeValidator = (parseFn: (input: string) => BaseT): BaseValidator => { + return internalMakeValidator(parseFn) as BaseValidator +} + +/** + * Creates a validator which output type is exactly T: + * + * ```ts + * const int = makeExactValidator((input: string) => { + * // Implementation details + * }) + * const MAX_RETRIES = int({ choices: [1, 2, 3, 4] }) + * // Output type 'number' + * ``` + * + * @param parseFn - A function to parse and validate input. + * @returns A validator which output type is exactly `T` + */ +export const makeExactValidator = (parseFn: (input: string) => T): ExactValidator => { + return internalMakeValidator(parseFn) as ExactValidator +} + +/** + * This validator is meant for inputs which can produce arbitrary output types (e.g. json). + * The typing logic behaves differently from other makers: + * + * - makeStructuredValidator has no type parameter. + * - When no types can be inferred from context, output type defaults to any. + * - Otherwise, infers type from `default` or `devDefault`. + * - Also generated validators have an output type parameter. + * - Finally, the generated validators disallow `choices` parameter. + * + * Below is an example of a validator for query parameters (e.g. `option1=foo&option2=bar`): + * + * ```ts + * const queryParams = makeStructuredValidator((input: string) => { + * const params = new URLSearchParams(input) + * return Object.fromEntries(params.entries()) + * }) + * const OPTIONS1 = queryParams() + * // Output type 'any' + * const OPTIONS2 = queryParams({ default: { option1: 'foo', option2: 'bar' } }) + * // Output type '{ option1: string, option2: string }' + * const OPTIONS3 = queryParams<{ option1?: string; option2?: string }>({ + * default: { option1: 'foo', option2: 'bar' }, + * }) + * // Output type '{ option1?: string, option2?: string }' + * ``` + * + * @param parseFn - A function to parse and validate input. + * @returns A validator which output type is exactly `T` + */ +export const makeStructuredValidator = ( + parseFn: (input: string) => unknown, +): StructuredValidator => { + return internalMakeValidator(parseFn) as StructuredValidator +} diff --git a/src/types.ts b/src/types.ts index 365f43e..0341bb8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,30 +1,8 @@ -// Hacky conditional type to prevent default/devDefault from narrowing type T to a single value. -// Ideally this could be replaced by something that would enforce the default value being a subset -// of T, without affecting the definition of T itself -type DefaultType = T extends string - ? string - : T extends number - ? number - : T extends boolean - ? boolean - : T extends object - ? object - : any - export interface Spec { /** * An Array that lists the admissable parsed values for the env var. */ choices?: ReadonlyArray - /** - * A fallback value, which will be used if the env var wasn't specified. Providing a default effectively makes the env var optional. - */ - default?: DefaultType - /** - * A fallback value to use only when NODE_ENV is not 'production'. - * This is handy for env vars that are required for production environments, but optional for development and testing. - */ - devDefault?: DefaultType /** * A string that describes the env var. */ @@ -37,12 +15,105 @@ export interface Spec { * A url that leads to more detailed documentation about the env var. */ docs?: string + /** + * A fallback value, which will be used if the env var wasn't specified. Providing a default effectively makes the env var optional. + */ + default?: NonNullable | undefined + /** + * A fallback value to use only when NODE_ENV is not 'production'. + * This is handy for env vars that are required for production environments, but optional for development and testing. + */ + devDefault?: NonNullable | undefined +} + +type OptionalSpec = Omit, 'default'> & { default: undefined } +type OptionalTypelessSpec = Omit, 'choices'> + +type RequiredSpec = (Spec & { default: NonNullable }) | Omit, 'default'> +type RequiredTypelessSpec = Omit, 'choices' | 'default'> & { + devDefault?: undefined +} + +type ChoicelessOptionalSpec = Omit, 'default' | 'choices'> & { + default: undefined } -export interface ValidatorSpec extends Spec { +type ChoicelessRequiredSpec = + | (Omit, 'choices'> & { default: NonNullable }) + | Omit, 'default' | 'choices'> + +type ChoicelessRequiredSpecWithType = ChoicelessRequiredSpec & + ( + | { + default: NonNullable + } + | { + devDefault: NonNullable + } + ) + +type WithParser = { _parse: (input: string) => T } +export type RequiredValidatorSpec = RequiredSpec & WithParser + +export type OptionalValidatorSpec = OptionalSpec & WithParser + +export type ValidatorSpec = RequiredValidatorSpec | OptionalValidatorSpec + +// Such validator works for exactly one type. You can't parametrize +// the output type at invocation site (e.g.: boolean). +export interface ExactValidator { + (spec?: RequiredSpec): RequiredValidatorSpec + (spec: OptionalSpec): OptionalValidatorSpec +} + +// Such validator only works for subtypes of BaseT. +export interface BaseValidator { + // These function overloads enable nuanced type inferences for optimal DX + // This will prevent specifying "default" alone from narrowing down output type. + // https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads + (spec: ChoicelessRequiredSpecWithType): RequiredValidatorSpec + (spec?: RequiredSpec): RequiredValidatorSpec + (spec: OptionalSpec): OptionalValidatorSpec +} + +// Such validator inputs a structured input format such as JSON. +// Because it can output complex types, including objects: +// - it has no supertype +// - it fallbacks to 'any' when no type information can be inferred +// from the spec object. +// - One can't pass "choices" since choices uses reference equality. +export interface StructuredValidator { + // Defaults to any when no argument (prevents 'unknown') + (): RequiredValidatorSpec + // Allow overriding output type with type parameter + (): RequiredValidatorSpec + // Make sure we grab 'any' when no type inference can be made + // otherwise it would resolve to 'unknown' + (spec: RequiredTypelessSpec): RequiredValidatorSpec + (spec: OptionalTypelessSpec): OptionalValidatorSpec + (spec: ChoicelessOptionalSpec): OptionalValidatorSpec + (spec: ChoicelessRequiredSpec): RequiredValidatorSpec +} + +export type SpecsOutput = { + [K in keyof S]: unknown +} + +export type CleanedEnv = S extends Record> + ? Readonly< + { + [K in keyof S]: S[K] extends OptionalValidatorSpec + ? U | undefined + : S[K] extends RequiredValidatorSpec + ? U + : never + } & CleanedEnvAccessors + > + : never + export interface CleanedEnvAccessors { /** true if NODE_ENV === 'development' */ readonly isDevelopment: boolean diff --git a/src/validators.ts b/src/validators.ts index 4103fad..2d25487 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -1,5 +1,5 @@ -import { Spec, ValidatorSpec } from './types' import { EnvError } from './errors' +import { makeExactValidator, makeStructuredValidator, makeValidator } from './makers' // Simplified adaptation of https://github.com/validatorjs/validator.js/blob/master/src/lib/isFQDN.js const isFQDN = (input: string) => { @@ -27,104 +27,87 @@ const isIP = (input: string) => { const EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/ // intentionally non-exhaustive -export const makeValidator = (parseFn: (input: string) => T) => { - return function (spec?: Spec): ValidatorSpec { - return { ...spec, _parse: parseFn } - } -} -// The reason for the function wrapper is to enable the type parameter -// that enables better type inference. For more context, check out the following PR: -// https://github.com/af/envalid/pull/118 -export function bool(spec?: Spec) { - return makeValidator((input: string | boolean) => { - switch (input) { - case true: - case 'true': - case 't': - case '1': - return true as T - case false: - case 'false': - case 'f': - case '0': - return false as T - default: - throw new EnvError(`Invalid bool input: "${input}"`) - } - })(spec) -} +// We use exact validator here because narrowing down to either 'true' or 'false' +// makes no sense. +export const bool = makeExactValidator((input: string | boolean) => { + switch (input) { + case true: + case 'true': + case 't': + case '1': + return true + case false: + case 'false': + case 'f': + case '0': + return false + default: + throw new EnvError(`Invalid bool input: "${input}"`) + } +}) -export function num(spec?: Spec) { - return makeValidator((input: string) => { - const coerced = parseFloat(input) - if (Number.isNaN(coerced)) throw new EnvError(`Invalid number input: "${input}"`) - return coerced as T - })(spec) -} +export const num = makeValidator((input: string) => { + const coerced = parseFloat(input) + if (Number.isNaN(coerced)) throw new EnvError(`Invalid number input: "${input}"`) + return coerced +}) -export function str(spec?: Spec) { - return makeValidator((input: string) => { - if (typeof input === 'string') return input as T - throw new EnvError(`Not a string: "${input}"`) - })(spec) -} +export const str = makeValidator((input: string) => { + if (typeof input === 'string') return input + throw new EnvError(`Not a string: "${input}"`) +}) -export function email(spec?: Spec) { - return makeValidator((x: string) => { - if (EMAIL_REGEX.test(x)) return x as T - throw new EnvError(`Invalid email address: "${x}"`) - })(spec) -} +export const email = makeValidator((x: string) => { + if (EMAIL_REGEX.test(x)) return x + throw new EnvError(`Invalid email address: "${x}"`) +}) -export function host(spec?: Spec) { - return makeValidator((input: string) => { - if (!isFQDN(input) && !isIP(input)) { - throw new EnvError(`Invalid host (domain or ip): "${input}"`) - } - return input as T - })(spec) -} +export const host = makeValidator((input: string) => { + if (!isFQDN(input) && !isIP(input)) { + throw new EnvError(`Invalid host (domain or ip): "${input}"`) + } + return input +}) -export function port(spec?: Spec) { - return makeValidator((input: string) => { - const coerced = +input - if ( - Number.isNaN(coerced) || - `${coerced}` !== `${input}` || - coerced % 1 !== 0 || - coerced < 1 || - coerced > 65535 - ) { - throw new EnvError(`Invalid port input: "${input}"`) - } - return coerced as T - })(spec) -} +export const port = makeValidator((input: string) => { + const coerced = +input + if ( + Number.isNaN(coerced) || + `${coerced}` !== `${input}` || + coerced % 1 !== 0 || + coerced < 1 || + coerced > 65535 + ) { + throw new EnvError(`Invalid port input: "${input}"`) + } + return coerced +}) -export function url(spec?: Spec) { - return makeValidator((x: string) => { - try { - new URL(x) - return x as T - } catch (e) { - throw new EnvError(`Invalid url: "${x}"`) - } - })(spec) -} +export const url = makeValidator((x: string) => { + try { + new URL(x) + return x + } catch (e) { + throw new EnvError(`Invalid url: "${x}"`) + } +}) -// It's recommended that you provide an explicit type parameter for json validation -// if you're using TypeScript. Otherwise the output will be typed as `any`. For example: -// -// cleanEnv({ -// MY_VAR: json<{ foo: number }>({ default: { foo: 123 } }), -// }) -export function json(spec?: Spec) { - return makeValidator((x: string) => { - try { - return JSON.parse(x) as T - } catch (e) { - throw new EnvError(`Invalid json: "${x}"`) - } - })(spec) -} +/** + * Unless passing a default property, it's recommended that you provide an explicit type parameter + * for json validation if you're using TypeScript. Otherwise the output will be typed as `any`. + * For example: + * + * ```ts + * cleanEnv({ + * MY_VAR: json<{ foo: number }>(), + * }) + * ``` + */ +export const json = makeStructuredValidator((x: string) => { + try { + return JSON.parse(x) + } catch (e) { + throw new EnvError(`Invalid json: "${x}"`) + } +}) diff --git a/tests/basics.test.ts b/tests/basics.test.ts index 6d1d50e..c0d6b47 100644 --- a/tests/basics.test.ts +++ b/tests/basics.test.ts @@ -1,5 +1,6 @@ import { cleanEnv, str, num, testOnly } from '../src' import { assertPassthrough } from './utils' +import { expectTypeOf } from 'expect-type' const makeSilent = { reporter: null } @@ -157,8 +158,9 @@ test('choices should refine the type of the field to a union', () => { test('misconfigured spec', () => { // Validation throws with different error if spec is invalid - // @ts-expect-error This misuse should be a type error - expect(() => cleanEnv({ FOO: 'asdf' }, { FOO: {} }, makeSilent)).toThrow() + expect(() => { + expectTypeOf(cleanEnv({ FOO: 'asdf' }, { FOO: {} }, makeSilent)).toBeNever() + }).toThrow() }) describe('NODE_ENV built-in support', () => { diff --git a/tests/types.test.ts b/tests/types.test.ts new file mode 100644 index 0000000..86dd6d0 --- /dev/null +++ b/tests/types.test.ts @@ -0,0 +1,271 @@ +import { + cleanEnv, + str, + bool, + num, + RequiredValidatorSpec, + OptionalValidatorSpec, + json, +} from '../src' +import { expectTypeOf } from 'expect-type' +import { makeStructuredValidator, makeValidator } from '../src/makers' + +describe('validators types', () => { + test('boolean validator', () => { + const validator = bool + expectTypeOf(validator()).toEqualTypeOf>() + expectTypeOf( + validator({ + default: false, + }), + ).toEqualTypeOf>() + expectTypeOf( + validator({ + choices: [true, false], + default: true, + }), + ).toEqualTypeOf>() + expectTypeOf(validator({ default: undefined })).toEqualTypeOf>() + expectTypeOf(validator({ devDefault: undefined })).toEqualTypeOf< + RequiredValidatorSpec + >() + expectTypeOf(validator({ devDefault: false })).toEqualTypeOf>() + }) + + test('number-based validators', () => { + const validator = makeValidator(() => 1) + // Specifying default or devDefault value should cause validator spec type param to widen + expectTypeOf( + validator({ + default: 0, + }), + ).toEqualTypeOf>() + expectTypeOf( + validator({ + devDefault: 0, + }), + ).toEqualTypeOf>() + // But this inference can be overridden by specifying a type parameter + expectTypeOf( + validator<0>({ + default: 0, + }), + ).toEqualTypeOf>() + expectTypeOf( + validator<0>({ + devDefault: 0, + }), + ).toEqualTypeOf>() + + // Choices + expectTypeOf( + validator({ + choices: [1, 2], + }), + ).toEqualTypeOf>() + expectTypeOf( + validator({ + choices: [1, 2], + default: 1, + }), + ).toEqualTypeOf>() + + // @ts-expect-error - 3 is not assignable to 1 | 2 + validator({ choices: [1, 2], default: 3 }) + // @ts-expect-error - 3 is not assignable to 1 | 2 + validator({ choices: [1, 2], devDefault: 3 }) + // Basic + expectTypeOf(validator()).toEqualTypeOf>() + expectTypeOf(validator<1>()).toEqualTypeOf>() + expectTypeOf(validator({ default: undefined })).toEqualTypeOf>() + expectTypeOf(validator({ devDefault: undefined })).toEqualTypeOf< + RequiredValidatorSpec + >() + expectTypeOf(validator<2>({ devDefault: 2 })).toEqualTypeOf>() + }) + test('string-based validators', () => { + const validator = makeValidator(() => '') + // Specifying default or devDefault value should cause validator spec type param to widen + expectTypeOf( + validator({ + default: 'foo', + }), + ).toEqualTypeOf>() + expectTypeOf( + validator({ + devDefault: 'foo', + }), + ).toEqualTypeOf>() + // But this inference can be overridden by specifying a type parameter + expectTypeOf( + validator<'foo'>({ + default: 'foo', + }), + ).toEqualTypeOf>() + expectTypeOf( + validator<'foo'>({ + devDefault: 'foo', + }), + ).toEqualTypeOf>() + expectTypeOf( + validator({ + choices: ['foo', 'bar'], + }), + ).toEqualTypeOf>() + expectTypeOf( + validator({ + choices: ['foo', 'bar'], + default: 'foo', + }), + ).toEqualTypeOf>() + //@ts-expect-error - baz is not assignable to 'foo' | 'bar' + validator({ choices: ['foo', 'bar'], default: 'baz' }) + // Basic + expectTypeOf(validator()).toEqualTypeOf>() + expectTypeOf(validator<'foo'>()).toEqualTypeOf>() + expectTypeOf(validator({ default: undefined })).toEqualTypeOf>() + expectTypeOf(validator({ devDefault: undefined })).toEqualTypeOf< + RequiredValidatorSpec + >() + + expectTypeOf(validator({ default: undefined })).toEqualTypeOf>() + expectTypeOf(validator({ devDefault: undefined })).toEqualTypeOf< + RequiredValidatorSpec + >() + + expectTypeOf(validator({ devDefault: 'foo' })).toEqualTypeOf>() + expectTypeOf(validator<'foo'>({ devDefault: 'foo' })).toEqualTypeOf< + RequiredValidatorSpec<'foo'> + >() + expectTypeOf(validator({ default: 'foo', devDefault: 'foo' })).toEqualTypeOf< + RequiredValidatorSpec + >() + expectTypeOf(validator<'foo' | 'bar'>({ default: 'foo', devDefault: 'bar' })).toEqualTypeOf< + RequiredValidatorSpec<'foo' | 'bar'> + >() + expectTypeOf( + validator<'foo' | 'bar'>({ choices: ['foo', 'bar'], devDefault: 'bar' }), + ).toEqualTypeOf>() + }) + test('structured data validator', () => { + const validator = makeStructuredValidator(() => ({})) + expectTypeOf(validator()).toEqualTypeOf>() + expectTypeOf(validator({ default: {} as any })).toEqualTypeOf>() + expectTypeOf(validator({ default: undefined })).toEqualTypeOf>() + //@ts-expect-error - Choices not available for structured data + validator({ choices: [{ foo: 'bar' }] }) + expectTypeOf(validator({ devDefault: undefined })).toEqualTypeOf>() + expectTypeOf(validator({ devDefault: { foo: 'bar' } })).toEqualTypeOf< + RequiredValidatorSpec<{ foo: string }> + >() + expectTypeOf(validator<{ foo: 'bar' }>()).toEqualTypeOf>() + expectTypeOf( + validator({ + default: { + hello: 'world', + }, + }), + ).toEqualTypeOf< + RequiredValidatorSpec<{ + hello: string + }> + >() + expectTypeOf( + validator<{ hello: 'world' }>({ + default: { + hello: 'world', + }, + }), + ).toEqualTypeOf< + RequiredValidatorSpec<{ + hello: 'world' + }> + >() + expectTypeOf(validator<{ hello: string }>()).toEqualTypeOf< + RequiredValidatorSpec<{ + hello: string + }> + >() + }) +}) + +test('cleanEnv', () => { + const env = { + STR: 'FOO', + STR_OPT: undefined, + STR_CHOICES: 'foo', + STR_REQ: 'BAR', + STR_DEFAULT_CHOICES: 'bar', + BOOL: 'false', + BOOL_OPT: undefined, + BOOL_DEFAULT: undefined, + NUM: '34', + NUM_DEFAULT_CHOICES: '3', + JSON_ANY: JSON.stringify(true), + JSON_REQ_ANY: JSON.stringify('Foo bar'), + JSON_DEV_DEFAULT: JSON.stringify('Foo bar'), + JSON_DEFAULT: JSON.stringify({ foo: 'bar' }), + JSON_DEFAULT_OPT: undefined, + } + const specs = { + STR: str(), + STR_OPT: str({ default: undefined }), + STR_CHOICES: str({ choices: ['foo', 'bar'] }), + STR_REQ: str({ default: 'foo' }), + STR_DEFAULT_CHOICES: str({ default: 'foo', choices: ['foo', 'bar'] }), + BOOL: bool(), + BOOL_OPT: bool({ default: undefined }), + BOOL_DEFAULT: bool({ + default: false, + }), + NUM: num(), + NUM_DEFAULT_CHOICES: num({ default: 1, choices: [1, 2, 3] }), + JSON_ANY: json(), + JSON_REQ_ANY: json({ default: {} as any }), + JSON_DEFAULT: json({ default: { foo: 'bar' } }), + JSON_DEV_DEFAULT: json({ devDefault: { foo: 'bar' } }), + JSON_DEFAULT_OPT: json<{ foo: 'bar' }>({ default: undefined }), + } + interface TestedCleanedEnv { + readonly STR: string + readonly STR_OPT?: string + readonly STR_CHOICES: 'foo' | 'bar' + readonly STR_REQ: string + readonly STR_DEFAULT_CHOICES: 'foo' | 'bar' + readonly BOOL: boolean + readonly BOOL_OPT?: boolean + readonly BOOL_DEFAULT: boolean + readonly NUM: number + readonly NUM_DEFAULT_CHOICES: 1 | 2 | 3 + readonly JSON_ANY: any + readonly JSON_REQ_ANY: any + readonly JSON_DEFAULT: { foo: string } + readonly JSON_DEV_DEFAULT: { foo: string } + readonly JSON_DEFAULT_OPT?: { foo: string } + } + + expectTypeOf(cleanEnv(env, specs)).toMatchTypeOf() + + // Should also work when specs inlined + expectTypeOf( + cleanEnv(env, { + STR: str(), + STR_OPT: str({ default: undefined }), + STR_CHOICES: str({ choices: ['foo', 'bar'] }), + STR_REQ: str({ default: 'foo' }), + STR_DEFAULT_CHOICES: str({ default: 'foo', choices: ['foo', 'bar'] }), + BOOL: bool(), + BOOL_OPT: bool({ default: undefined }), + BOOL_DEFAULT: bool({ + default: false, + }), + NUM: num(), + NUM_DEFAULT_CHOICES: num({ default: 1, choices: [1, 2, 3] }), + JSON_ANY: json(), + JSON_REQ_ANY: json({ default: {} as any }), + JSON_DEFAULT: json({ default: { foo: 'bar' } }), + JSON_DEV_DEFAULT: json({ devDefault: { foo: 'bar' } }), + JSON_DEFAULT_OPT: json<{ foo: 'bar' }>({ default: undefined }), + }), + ).toMatchTypeOf() +}) diff --git a/yarn.lock b/yarn.lock index 0a560d0..acfc647 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.0.0", "@ampproject/remapping@^2.1.0": +"@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== @@ -10,14 +10,14 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.16.4", "@babel/compat-data@^7.19.0": +"@babel/compat-data@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== @@ -43,7 +43,7 @@ json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.14.5", "@babel/generator@^7.17.0", "@babel/generator@^7.19.0", "@babel/generator@^7.7.2": +"@babel/generator@^7.19.0", "@babel/generator@^7.7.2": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== @@ -52,7 +52,7 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.19.0": +"@babel/helper-compilation-targets@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== @@ -62,12 +62,12 @@ browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.9": +"@babel/helper-environment-visitor@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@babel/helper-function-name@^7.14.5", "@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.19.0": +"@babel/helper-function-name@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== @@ -75,28 +75,21 @@ "@babel/template" "^7.18.10" "@babel/types" "^7.19.0" -"@babel/helper-get-function-arity@^7.14.5", "@babel/helper-get-function-arity@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" - integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-hoist-variables@^7.14.5", "@babel/helper-hoist-variables@^7.16.7", "@babel/helper-hoist-variables@^7.18.6": +"@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6": +"@babel/helper-module-imports@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.19.0": +"@babel/helper-module-transforms@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== @@ -115,14 +108,14 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== -"@babel/helper-simple-access@^7.16.7", "@babel/helper-simple-access@^7.18.6": +"@babel/helper-simple-access@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== dependencies: "@babel/types" "^7.18.6" -"@babel/helper-split-export-declaration@^7.14.5", "@babel/helper-split-export-declaration@^7.16.7", "@babel/helper-split-export-declaration@^7.18.6": +"@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== @@ -134,17 +127,17 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6": +"@babel/helper-validator-identifier@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== -"@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.18.6": +"@babel/helper-validator-option@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== -"@babel/helpers@^7.17.0", "@babel/helpers@^7.19.0": +"@babel/helpers@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== @@ -153,7 +146,7 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" -"@babel/highlight@^7.14.5", "@babel/highlight@^7.16.7", "@babel/highlight@^7.18.6": +"@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== @@ -162,7 +155,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.5", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.0", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== @@ -258,7 +251,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/template@^7.14.5", "@babel/template@^7.16.7", "@babel/template@^7.18.10", "@babel/template@^7.3.3": +"@babel/template@^7.18.10", "@babel/template@^7.3.3": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== @@ -267,7 +260,7 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.16.7", "@babel/traverse@^7.17.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.19.0", "@babel/traverse@^7.7.2": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== @@ -283,7 +276,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.14.5", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== @@ -538,7 +531,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.10.tgz#baf57b4e2a690d4f38560171f91783656b7f8186" integrity sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg== -"@jridgewell/trace-mapping@^0.3.0", "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.9": version "0.3.15" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== @@ -666,7 +659,7 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" -ansi-regex@^5.0.0, ansi-regex@^5.0.1: +ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -785,7 +778,7 @@ braces@^3.0.1: dependencies: fill-range "^7.0.1" -browserslist@^4.17.5, browserslist@^4.20.2: +browserslist@^4.20.2: version "4.21.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== @@ -829,7 +822,7 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001370: +caniuse-lite@^1.0.30001370: version "1.0.30001393" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356" integrity sha512-N/od11RX+Gsk+1qY/jbPa0R6zJupEa0lxeBG598EbrtblxVCTJsQwbRBm6+V+rxpc5lHKdsXb9RY83cZIPLseA== @@ -957,7 +950,7 @@ diff-sequences@^28.1.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== -electron-to-chromium@^1.4.17, electron-to-chromium@^1.4.202: +electron-to-chromium@^1.4.202: version "1.4.247" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz#cc93859bc5fc521f611656e65ce17eae26a0fd3d" integrity sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw== @@ -1019,6 +1012,11 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= +expect-type@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-0.15.0.tgz#89f75e22c88554844ea2b2faf4ef5fc2e579d3b5" + integrity sha512-yWnriYB4e8G54M5/fAFj7rCIBiKs1HAACaY13kCz6Ku0dezjS9aMcfcdVK2X8Tv2tEV1BPz/wKfQ7WA4S/d8aA== + expect@^28.0.0, expect@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.3.tgz#90a7c1a124f1824133dd4533cce2d2bdcb6603ec" @@ -1632,7 +1630,7 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json5@^2.1.2, json5@^2.2.1: +json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -1715,11 +1713,6 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -1735,7 +1728,7 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= -node-releases@^2.0.1, node-releases@^2.0.6: +node-releases@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== @@ -1961,11 +1954,6 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"