{"id":13474499,"url":"https://github.com/lostfictions/znv","last_synced_at":"2025-05-14T21:05:56.433Z","repository":{"id":50548321,"uuid":"445959490","full_name":"lostfictions/znv","owner":"lostfictions","description":"Type-safe environment parsing and validation for Node.js with Zod schemas","archived":false,"fork":false,"pushed_at":"2025-03-24T09:02:23.000Z","size":447,"stargazers_count":376,"open_issues_count":2,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-12T18:58:17.218Z","etag":null,"topics":["environment-variables","nodejs","typescript","zod"],"latest_commit_sha":null,"homepage":"https://github.com/lostfictions/znv","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lostfictions.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-09T00:29:41.000Z","updated_at":"2025-05-12T07:32:04.000Z","dependencies_parsed_at":"2024-01-07T06:22:05.335Z","dependency_job_id":"dc62377c-0ccc-4276-9a69-139b787ae270","html_url":"https://github.com/lostfictions/znv","commit_stats":{"total_commits":74,"total_committers":4,"mean_commits":18.5,"dds":0.1216216216216216,"last_synced_commit":"1cc9558c1218d263bca8484f4d8862c8eb2074e0"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostfictions%2Fznv","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostfictions%2Fznv/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostfictions%2Fznv/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostfictions%2Fznv/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lostfictions","download_url":"https://codeload.github.com/lostfictions/znv/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253805844,"owners_count":21967054,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["environment-variables","nodejs","typescript","zod"],"created_at":"2024-07-31T16:01:12.773Z","updated_at":"2025-05-14T21:05:56.401Z","avatar_url":"https://github.com/lostfictions.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","typescript"],"sub_categories":[],"readme":"# znv\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"logo.svg\" height=\"90\" alt=\"znv logo\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://www.npmjs.com/package/znv\"\u003e\n\u003cimg src=\"https://img.shields.io/npm/v/znv.svg?logo=npm\" alt=\"NPM version\" /\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\nParse your environment with [Zod](https://github.com/colinhacks/zod).\n\nPass in a schema and your `process.env`. Get back a validated, type-safe,\nread-only environment object that you can export for use in your app. You can\noptionally provide defaults (which can be matched against `NODE_ENV` values like\n`production` or `development`), as well as help strings that will be included in\nthe error thrown when an env var is missing.\n\n## Features\n\n- No dependencies\n- Fully type-safe\n- Compatible with serverless environments (import `znv/compat` instead of `znv`)\n\n## Status\n\nUnstable: znv has not yet hit v1.0.0, and per semver there may be breaking\nchanges in minor versions before the v1.0.0 release. Any (known) breaking\nchanges will be documented in release notes. znv is used in production in\nseveral services at the primary author's workplace. Feedback and suggestions\nabout final API design are welcome.\n\n## Contents\n\n- [Quickstart](#quickstart)\n- [Motivation](#motivation)\n- [Usage](#usage)\n  - [`parseEnv`](#parseenvenvironment-schemas-reporterOrFormatters)\n  - [Extra schemas](#extra-schemas)\n- [Coercion rules](#coercion-rules)\n- [Comparison to other libraries](#comparison-to-other-libraries)\n- [Complementary tooling](#complementary-tooling)\n- [How do I pronounce znv?](#how-do-i-pronounce-znv)\n\n## Quickstart\n\n```bash\nnpm i znv zod\n# or\npnpm add znv zod\n# or\nyarn add znv zod\n```\n\nCreate a file named something like `env.ts`:\n\n```ts\nimport { parseEnv } from \"znv\";\nimport { z } from \"zod\";\n\nexport const { NICKNAME, LLAMA_COUNT, COLOR, SHINY } = parseEnv(process.env, {\n  NICKNAME: z.string().min(1),\n  LLAMA_COUNT: z.number().int().positive(),\n  COLOR: z.enum([\"red\", \"blue\"]),\n  SHINY: z.boolean().default(true),\n});\n\nconsole.log([NICKNAME, LLAMA_COUNT, COLOR, SHINY].join(\", \"));\n```\n\nLet's run this with [ts-node](https://github.com/TypeStrong/ts-node):\n\n```\n$ LLAMA_COUNT=huge COLOR=cyan ts-node env.ts\n```\n\n\u003cimg src=\"example.png\" width=\"658\" alt=\"A screenshot showing error output, with parsing errors aggregated and grouped by env var.\"\u003e\n\nOops! Let's fix those issues:\n\n```\n$ LLAMA_COUNT=24 COLOR=red NICKNAME=coolguy ts-node env.ts\n```\n\nNow we see the expected output:\n\n```\ncoolguy, 24, red, true\n```\n\nSince `parseEnv` didn't throw, our exported values are guaranteed to be defined.\nTheir TypeScript types will be inferred based on the schemas we used — `COLOR`\nwill be even be typed to the union of literal strings `'red' | 'blue'` rather\nthan just `string`.\n\n---\n\nA more elaborate example:\n\n```ts\n// znv re-exports zod as 'z' to save a few keystrokes.\nimport { parseEnv, z, port } from \"znv\";\n\nexport const { API_SERVER, HOST, PORT, EDITORS, POST_LIMIT, AUTH_SERVER } =\n  parseEnv(process.env, {\n    // you can provide defaults with `.default()`. these will be validated\n    // against the schema.\n    API_SERVER: z.string().url().default(\"https://api.llamafy.biz\"),\n\n    // specs can also be more detailed.\n    HOST: {\n      schema: z.string().min(1),\n\n      // the description is handy as in-code documentation, but is also printed\n      // to the console if validation for this env var fails.\n      description: \"The hostname for this service.\",\n\n      // instead of specifying defaults as part of the zod schema, you can pass\n      // them in the `defaults` object. a default will be matched based on the\n      // value of `NODE_ENV`.\n      defaults: {\n        production: \"my-cool-llama.website\",\n        test: \"cool-llama-staging.cloud-provider.zone\",\n\n        // \"_\" is a special token that can be used in `defaults`. its value will\n        // be used if `NODE_ENV` doesn't match any other provided key.\n        _: \"localhost\",\n      },\n    },\n\n    // znv provides helpers for a few very common environment var types not\n    // covered by zod. these can have further refinements chained to them:\n    PORT: port().default(8080),\n\n    // using a zod `array()` or `object()` as a spec will make znv attempt to\n    // `JSON.parse` the env var if it's present.\n    EDITORS: z.array(z.string().min(1)),\n\n    // optional values are also supported and provide a way to benefit from the\n    // validation and static typing provided by zod even if you don't want to\n    // error out on a missing value.\n    POST_LIMIT: z.number().optional(),\n\n    // use all of the expressiveness of zod, including enums and post-processing.\n    AUTH_SERVER: z\n      .enum([\"prod\", \"staging\"])\n      .optional()\n      .transform((prefix) =\u003e\n        prefix ? `http://auth-${prefix}.cool-llama.app` : \"http://localhost:91\",\n      ),\n  });\n```\n\nIf any env var fails validation, `parseEnv()` will throw. All failing specs will\nbe aggregated in the error message, with each showing the received value, the\nreason for the failure, and a hint about the var's purpose (if `description` was\nprovided in the spec).\n\n## Motivation\n\nEnvironment variables are one way to pass runtime configuration into your\napplication. As [promoted by the Twelve-Factor App\nmethodology](https://12factor.net/config), this helps keep config (which can\nvary by deployment) cleanly separated from code, encouraging maintainable\npractices and better security hygiene. But passing in configuration via env vars\ncan often turn into an ad-hoc affair, with access and validation scattered\nacross your codebase. At worst, a misconfigured environment will launch and run\nwithout apparent error, with issues only making themselves apparent later when a\ncertain code path is hit. A good way to avoid this is to **declare and validate\nenvironment variables in one place** and export the validated result, so that\nother parts of your code can make their dependencies on these vars explicit.\n\nEnv vars represent one of the _boundaries_ of your application, just like file\nI/O or a server request. In TypeScript, as in many other typed languages, these\nboundaries present a challenge to maintaining a well-typed app.\n[Zod](https://github.com/colinhacks/zod) does an excellent job at parsing and\nvalidating poorly-typed data at boundaries into clean, well-typed values. znv\nfacilitates its use for environment validation.\n\n### What does znv actually do?\n\nznv is a small module that works hand-in-hand with Zod. Since env vars, when\ndefined, are _always strings_, Zod schemas like `z.number()` will fail to parse\nthem out-of-the-box. Zod allows you to use a [`preprocess`\nschema](https://github.com/colinhacks/zod#preprocess) to handle coercions, but\npeppering your schemas with preprocessors to this end is verbose, error-prone,\nand clunky. znv wraps each of the Zod schemas you pass to `parseEnv` in a\npreprocessor that tries to coerce a string to a type the schema expects.\n\nThese preprocessors don't do any validation of their own — in fact, they try to\ndo as little work as possible and defer to your schema to handle the validation.\nIn practice, this should be pretty much transparent to you, but you can check\nout the [coercion rules](#coercion-rules) if you'd like more info.\n\n\u003e Since `v3.20`, Zod provides\n\u003e [`z.coerce`](https://github.com/colinhacks/zod#coercion-for-primitives) for\n\u003e primitive coercion, but this is often too naive to be useful. For example,\n\u003e `z.coerce.boolean()` will parse \"false\" into `true`, since the string \"false\"\n\u003e is _truthy_ in JavaScript. znv will coerce \"false\" into `false`, which is\n\u003e probably what you expect.\n\nznv also makes it easy to define defaults for env vars based on your\nenvironment. Zod allows you to add a default value for a schema, but making a\ngiven default vary by environment or only act as a fallback in certain\nenvironments is not straightforward.\n\n## Usage\n\n### `parseEnv(environment, schemas, reporterOrFormatters?)`\n\nParse the given `environment` using the given `schemas`. Returns a read-only\nobject that maps the keys of the `schemas` object to their respective parsed\nvalues.\n\nThrows if any schema fails to parse its respective env var. The error aggregates\nall parsing failures for the schemas.\n\nOptionally, you can pass a custom error reporter as the third parameter to\n`parseEnv` to customize how errors are displayed. The reporter is a function\nthat receives error details and returns a `string`. Alternately, you can pass an\nobject of _token formatters_ as the third parameter to `parseEnv`; this can be\nuseful if you want to retain the default error reporting format but want to\ncustomize some aspects of it (for example, by redacting secrets).\n\n#### `environment: Record\u003cstring, string | undefined\u003e`\n\nYou usually want to pass in `process.env` as the first argument.\n\n\u003e **It is not recommended** to use znv for general-purpose schema validation —\n\u003e just use Zod (with\n\u003e [preprocessors](https://github.com/colinhacks/zod#preprocess) to handle\n\u003e coercion, if necessary).\n\n#### `schemas: Record\u003cstring, ZodType | DetailedSpec\u003e`\n\nMaps env var names to validators. You can either use a Zod schema directly, or\npass a `DetailedSpec` object that has the following fields:\n\n- `schema: ZodType`\n\n  The Zod validator schema.\n\n- `description?: string`\n\n  Optional help text that will be displayed when this env var is missing or\n  fails to validate.\n\n- `defaults?: Record\u003cstring, SchemaInput | undefined\u003e`\n\n  An object that maps from `NODE_ENV` values to values that will be passed as\n  input to the schema if this var isn't present in the environment. For example:\n\n  ```ts\n  const schemas = {\n    FRUIT: {\n      schema: z.string().min(1),\n      defaults: {\n        production: \"orange\",\n        development: \"banana\",\n      },\n    },\n  };\n\n  // FRUIT wll have value \"banana\".\n  const { FRUIT } = parseEnv({ NODE_ENV: \"development\" }, schemas);\n\n  // FRUIT wll have value \"orange\".\n  const { FRUIT } = parseEnv({ NODE_ENV: \"production\" }, schemas);\n\n  // FRUIT wll have value \"fig\".\n  const { FRUIT } = parseEnv({ NODE_ENV: \"production\", FRUIT: \"fig\" }, schemas);\n\n  // FRUIT wll have value \"apple\".\n  const { FRUIT } = parseEnv({ FRUIT: \"apple\" }, schemas);\n\n  // this will throw, since NODE_ENV doesn't match \"production\" or \"development\".\n  const { FRUIT } = parseEnv({}, schemas);\n  ```\n\n  `defaults` accepts a special token as a key: `_`. This is like the `default`\n  clause in a `switch` case — its value will be used if `NODE_ENV` doesn't match\n  any other key in `defaults`.\n\n  \u003e (As an aside, it is **not recommended** to use `staging` as a possible value\n  \u003e for `NODE_ENV`. Your staging environment should be as similar to your\n  \u003e production environment as possible, and `NODE_ENV=production` has special\n  \u003e meaning for several tools and libraries. For example,\n  \u003e [`npm install`](https://docs.npmjs.com/cli/v8/commands/npm-install) and\n  \u003e [`yarn install`](https://classic.yarnpkg.com/en/docs/cli/install#toc-yarn-install-production-true-false)\n  \u003e by default won't install `devDependencies` if `NODE_ENV=production`;\n  \u003e [Express](https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production)\n  \u003e and [React](https://reactjs.org/docs/optimizing-performance.html) will also\n  \u003e behave differently depending on whether `NODE_ENV` is `production` or not.\n  \u003e Instead, your staging environment should also set `NODE_ENV=production`, and\n  \u003e you should define your own env var(s) for any special configuration that's\n  \u003e necessary for your staging environment.)\n\n  Caveats aside, `_` lets you express a few interesting scenarios:\n\n  ```ts\n  // one default for production, and one for all other environments, including\n  // development and testing.\n  { production: \"prod default\", _: \"dev default\" }\n\n  // default for all non-production environments, but require the var to be\n  // passed in for production.\n  { production: undefined, _: \"dev default\" }\n\n  // unconditional default. equivalent to adding `.default(\"some default\")`\n  // to the zod schema, but this might be more stylistically consistent with\n  // your other specs if they use the `defaults` field.\n  { _: \"unconditional default\" }\n  ```\n\n  Some testing tools like [Jest](https://jestjs.io/) set `NODE_ENV` to `test`,\n  so you can also use `defaults` to provide default env vars for testing.\n\n  `parseEnv` doesn't restrict or validate `NODE_ENV` to any particular values,\n  but you can add `NODE_ENV` to your schemas like any other env var. For\n  example, you could use\n  `NODE_ENV: z.enum([\"production\", \"development\", \"test\", \"ci\"])` to enforce\n  that `NODE_ENV` is always defined and is one of those four expected values.\n\n#### `reporterOrFormatters?: Reporter | TokenFormatters`\n\nAn optional error reporter or object of error token formatters, for customizing\nthe displayed output when a validation error occurs.\n\n- `Reporter: (errors: ErrorWithContext[], schemas: Schemas) =\u003e string`\n\n  A reporter is a function that takes a list of errors and the schemas you\n  passed to `parseEnv` and returns a `string`. Each error has the following\n  format:\n\n  ```ts\n  {\n    /** The env var name. */\n    key: string;\n    /** The actual value present in `process.env[key]`, or undefined. */\n    receivedValue: unknown;\n    /** `ZodError` if Zod parsing failed, or `Error` if a preprocessor threw. */\n    error: unknown;\n    /** If a default was provided, whether the default value was used. */\n    defaultUsed: boolean;\n    /** If a default was provided, the given default value. */\n    defaultValue: unknown;\n  }\n  ```\n\n- `TokenFormatters`\n\n  An object with the following structure:\n\n  ```ts\n    {\n    /** Formatter for the env var name. */\n    formatVarName?: (key: string) =\u003e string;\n\n    /** For parsed objects with errors, formatter for object keys. */\n    formatObjKey?: (key: string) =\u003e string;\n\n    /** Formatter for the actual value we received for the env var. */\n    formatReceivedValue?: (val: unknown) =\u003e string;\n\n    /** Formatter for the default value provided for the schema. */\n    formatDefaultValue?: (val: unknown) =\u003e string;\n\n    /** Formatter for the error summary header. */\n    formatHeader?: (header: string) =\u003e string;\n  }\n  ```\n\n  For example, if you want to redact value names, you can invoke `parseEnv` like\n  this:\n\n  ```ts\n  export const { SOME_VAL } = parseEnv(\n    process.env,\n    { SOME_VAL: z.number().nonnegative() },\n    { formatReceivedValue: () =\u003e \"\u003credacted\u003e\" },\n  );\n  ```\n\n### Extra schemas\n\nznv exports a very small number of extra schemas for common env var types.\n\n#### `port()`\n\n`port()` is an alias for `z.number().int().nonnegative().lte(65535)`.\n\n#### `deprecate()`\n\n`deprecate()` is an alias for\n`z.undefined().transform(() =\u003e undefined as never)`. `parseEnv` will throw if a\nvar using the `deprecate()` schema is passed in from the environment.\n\n## Coercion rules\n\nznv tries to do as little work as possible to coerce env vars (which are always\nstrings when they're present) to the [input\ntypes](https://github.com/colinhacks/zod#what-about-transforms) of your schemas.\nIf the env var doesn't look like the input type, znv will pass it to the\nvalidator as-is with the assumption that the validator will throw. For example,\nif your schema is `z.number()`, znv will test it against a numeric regex first,\nrather than unconditionally wrap it in `Number()` or `parseFloat()` (and thus\ncoerce it to `NaN`).\n\nBy modifying as little as possible, znv tries to get out of Zod's way and let it\ndo the heavy lifting of validation. This also lets us produce less confusing\nerror messages: if you pass the string \"banana\" to your number schema, it should\nbe able to say \"you gave me 'banana' instead of a number!\" rather than \"you gave\nme NaN instead of a number!\"\n\n**Coercions only happen at the top level of a schema**. If you define an object\nwith nested schemas, no coercions will be applied to the keys.\n\nSome notable coercion mechanics:\n\n- If your schema's input is a boolean, znv will coerce `\"true\"`, `\"yes\"` and\n  `\"1\"` to `true`, and `\"false\"`, `\"no\"` and `\"0\"` to `false`. All other values\n  will be passed through.\n\n  \u003e Some CLI tool conventions dictate that a variable simply being present in\n  \u003e the environment (even with no value, eg. setting `MY_VALUE=` with no\n  \u003e right-hand side) should be interpreted as `true`. However, this convention\n  \u003e doesn't seem to be in widespread use in Node, probably because it causes the\n  \u003e var to evaluate to the empty string (which is falsy). znv demands a little\n  \u003e more specificity by default, while still hedging a bit for some common\n  \u003e true/false equivalents. If you want the \"any defined value\" behaviour, you\n  \u003e can use\n  \u003e `z.string().optional().transform(v =\u003e v === undefined ? false : true)`.\n\n- If your schema's input is an object or array (or record or tuple), znv will\n  attempt to `JSON.parse` the input value if it's not `undefined` or the empty\n  string.\n\n  \u003e **Remember, with great power comes great responsibility!** If you're using\n  \u003e an object or array schema to pass in dozens or hundreds of kilobytes of data\n  \u003e as an env var, you may be doing something wrong. (Certain platforms also\n  \u003e [impose limits on environment variable\n  \u003e length](https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083).)\n\n- If your schema's input is a Date, znv will call `new Date()` with the input\n  value. This has a number of pitfalls, since the `Date()` constructor is\n  excessively forgiving. The value is passed in as a string, which means\n  **trying to pass a Unix epoch will yield unexpected results**. (Epochs need to\n  be passed in as `number`: `new Date()` with an epoch as a string will either\n  give you `invalid date` or a completely nonsensical date.) _You should only\n  pass in ISO 8601 date strings_, such as those returned by\n  [`Date.prototype.toISOString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString).\n  Improved validation for Date schemas could be added in a future version.\n\n- Zod defines \"nullable\" as distinct from \"optional\". If your schema is\n  `nullable`, znv will coerce `undefined` to `null`. Generally it's preferred to\n  simply use `optional`.\n\n## Comparison to other libraries\n\n### [Envalid](https://github.com/af/envalid)\n\nEnvalid is a nice library that inspired znv's API design. Envalid is written in\nTypeScript and performs some inference of the return value based on the\nvalidator schema you pass in, but won't do things like narrow enumerated types\n(`str({ choices: ['a', 'b'] })`) to a union of literals. Expressing defaults is\nmore limited (you can't have different defaults for `test` and `development`\nenvironments, for example). Defaults are not passed through validators.\n\nEnvalid's validators are built-in and express a handful of types with limited\noptions and no ability to perform postprocessing. For other use cases you have\nto write your own [custom\nvalidators](https://github.com/af/envalid#custom-validators).\n\nEnvalid wraps its return value in a proxy, which can't be opted out of and has\nsome [surprising effects](https://github.com/af/envalid/issues/177).\n\n### [Joi](https://joi.dev/)\n\nJoi is the Cadillac of schema validation libraries. Its default of coercing\nstrings to the target type makes it easy to adopt for environment validation.\nUnfortunately, Joi is written in JavaScript and its type definitions support a\nvery limited form of inference when they work at all.\n\n### [Zod](https://github.com/colinhacks/zod)\n\nHey, what's Zod doing here? Doesn't znv use Zod?\n\nIf you just want to parse some values against a certain schema, **you might not\nneed znv**. Just use Zod directly.\n\nznv is best-suited for _environment validation_: it automatically wraps your Zod\nschemas in preprocessors that coerce env vars, which are always strings, into\nthe appropriate type. This is different from Zod's built-in `z.coerce`, which is\noften too naive to be useful. For example, `z.coerce.boolean()` will parse\n\"false\" into `true`, since the string \"false\" is _truthy_ in JavaScript. znv\nwill coerce \"false\" into `false`, which is probably what you expect. Check the\nsection on [coercion rules](#coercion-rules) for more information.\n\n## Complementary tooling\n\nThe [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) rule\n[`no-process-env`](https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-process-env.md)\nis recommended to restrict usage of `process.env` outside of the module that\nparses your schema.\n\nznv also works great with [dotenv](https://github.com/motdotla/dotenv).\n\n## How do I pronounce znv?\n\nIf you usually pronounce \"z\" as \"zed,\" then you could say \"zenv.\" If you usually\npronounce \"z\" as \"zee,\" you could say \"zee en vee.\"\n\nOr do your own thing. I'm not the boss of you.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flostfictions%2Fznv","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flostfictions%2Fznv","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flostfictions%2Fznv/lists"}