npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

envwire

v0.0.1

Published

Declare env vars once in a root .env.template and compile per-app .env files from it

Downloads

117

Readme

envwire

Declare every environment variable once in a root .env.template. Each app has its own small .env.template that picks the variables it needs, under the names it needs. envwire turns all of them into real .env files.

# .env.template (root, committed)
HOST=127.0.0.1
SUPABASE_URL=http://${HOST}:54321
SUPABASE_ANON_KEY=local-anon-key
STRIPE_SECRET_KEY=
# apps/web/.env.template (committed)
NEXT_PUBLIC_SUPABASE_URL=${SUPABASE_URL}
NEXT_PUBLIC_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
# apps/mobile/.env.template (committed)
EXPO_PUBLIC_SUPABASE_URL=${SUPABASE_URL}
EXPO_PUBLIC_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
# .env.local (root, gitignored)
HOST=192.168.1.20
$ envwire
compiled .env
compiled apps/web/.env
compiled apps/mobile/.env

The one line in .env.local reaches both apps: apps/web/.env gets NEXT_PUBLIC_SUPABASE_URL=http://192.168.1.20:54321 and apps/mobile/.env gets EXPO_PUBLIC_SUPABASE_URL=http://192.168.1.20:54321. Next and Expo read their own .env from disk as usual; nothing changes at runtime.

Install

npm i -D envwire
{
  "scripts": {
    "env": "envwire",
    "dev": "npm run env && turbo dev"
  }
}
.env
.env.*
!.env.template

Files

| File | Committed | Role | | --- | --- | --- | | .env.template | yes | Root schema. Every variable, declared once, with a safe local default or empty. | | .env.local | no | Root overrides for your machine. | | .env.<name> | no | Extra root overrides, applied with --env <name>. | | <app>/.env.template | yes | Which root variables the app gets, under what name. | | .env, <app>/.env | no | Generated. |

Every .env.template below the root is an app template. node_modules, .git, dist, build, .next, coverage, .turbo, and dot-directories are skipped. There is no config file.

How values are resolved

  1. Root layers are merged: .env.template, then .env.local, then .env.<name>. Later wins. Keys that are not in .env.template are ignored, so the template stays the full list.
  2. ${VAR} references between root values are expanded after the merge. Overriding HOST rewrites every value built from it.
  3. Each app template is compiled line by line. ${VAR} becomes the merged root value; the left-hand side is the name the app reads. Comments and blank lines are kept.
  4. A .env is written beside every template with a header marking it as generated. An existing .env without that header is left alone with a warning.

An empty root value is written as KEY=, so the app sees "", not undefined. Put local defaults in the root template rather than in process.env.X ?? "fallback".

Values are quoted only when dotenv would otherwise misparse them. A ${VAR} that the root does not declare, or a reference cycle, expands to "" with a warning. Compilation never exits non-zero for warnings.

CLI

envwire                 compile every template
envwire --env staging   also apply the root .env.staging layer
envwire --cwd ../..     use another directory as the root
envwire --check         gap check only, exit 1 on findings (see below)

Gap check

The app template is the only thing that decides what an app gets, so an app can read a variable in code that its template never wires in. It works in production, where the platform injects it, and is silently undefined locally.

envwire --check scans each app (and its first-party workspace dependencies) for process.env.X reads and reports any X that the root declares but the app template does not reference. It is meant for CI:

{ "scripts": { "check:env": "envwire --check" } }

Two directives in the comment directly above a root declaration suppress a finding. Both require a reason, and both are reported as stale when they stop being true.

# envwire: deployment-only Consumed by the hosting platform, never read by application code.
VERCEL_TOKEN=

# envwire: known-gap apps/worker The worker resolves the URL from its queue config instead.
API_URL=http://${HOST}:3000

Reads recognized: process.env.X, process.env["X"], process.env['X'], const { X } = process.env. Computed access cannot be checked and is listed as a note. Test files and .d.ts files are skipped.