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

@structure-ai/dotenv

v0.4.1

Published

First-class .env support on Effect: dotenv-compatible parsing, variable expansion, environment cascade (.env, .env.local, .env.<env>, .env.<env>.local), lossless file edits, required-key checks, and a CLI command group.

Readme

@structure-ai/dotenv

First-class .env support on Effect. Parsing follows the dotenv package byte for byte, expansion follows dotenv-expand, the file cascade follows the .env / .env.local / .env.<env> / .env.<env>.local convention, and files can be edited in place without losing comments or ordering. The loaded values feed @structure-ai/config or Effect Config without touching process.env, or are applied to process.env the classic way. Bun only.

Usage

import { load as loadSettings, Settings } from "@structure-ai/config";
import * as Dotenv from "@structure-ai/dotenv";
import { Effect } from "effect";

const settings = Settings.struct({
  port: Settings.port("PORT", { default: 3000 }),
  databaseUrl: Settings.secret("DATABASE_URL"),
});

// Local development: .env files below the real environment, no global mutation.
const program = Effect.gen(function* () {
  const env = yield* Dotenv.environment(); // process.env merged with .env, .env.local, .env.$NODE_ENV, .env.$NODE_ENV.local
  const config = yield* loadSettings(settings, { env });
  // config.port: number, config.databaseUrl: Redacted<string>
});

// Or the classic way, at the top of the layer stack: writes into process.env.
const app = Layer.provideMerge(AppLive, Dotenv.layer());

Rules the loader applies:

  • Precedence. Later files in the cascade override earlier ones. A variable already present in the environment keeps its value and the file's value is reported as shadowed — unless override: true. This is the classic dotenv rule, and it makes Bun's own preloading of .env, .env.local and .env.$NODE_ENV harmless (same values, already there). Only when a .env.<env>.local value must beat one Bun already loaded from .env do you need override: true (development only) or bun --env-file=/dev/null.
  • Cascade. environment defaults to NODE_ENV; an empty name loads only .env and .env.local. .env.local is skipped when the environment is test so test runs stay reproducible. Missing cascade files are skipped; explicit files must exist.
  • Expansion. $VAR, ${VAR}, ${VAR:-default}, ${VAR-default}, ${VAR:+alt}, ${VAR+alt}; \$ is a literal dollar. References resolve from the environment first, then from the files (files first with override). Single-quoted values are literal. Undefined names expand to ""; a cycle fails with DotenvError of kind expand.
  • Secrets. Parsed values are plain strings — a dotenv parser cannot know which keys are secrets; route them through Settings.secret. Errors and the CLI never print values unless asked (print --reveal).

CLI

import { dotenvCommand } from "@structure-ai/dotenv/cli";

const root = withSubcommands(app, [dotenvCommand({ settings, example: ".env.example" })]);

| Command | Does | | --- | --- | | dotenv check [--example FILE] [--allow-empty] | Lists missing, empty and undeclared variables against the settings definition and/or example file; exits 78 (ConfigLoadError, one issue per key) when a required one is missing or empty. | | dotenv print [--reveal] [--json] | What the files contribute and which keys the environment shadows; values <redacted> unless --reveal. | | dotenv run -- <command...> | Runs a command with the loaded environment. A non-zero child exit fails the command (exit 1) with the child's code in the message. | | dotenv set KEY VALUE [--file .env] / dotenv unset KEY... [--file .env] | Edit a file in place: existing lines rewritten (export prefix and inline comment kept), other bytes untouched, file created when absent. |

Exports

| Export | What it is | | --- | --- | | parse(content) | dotenv-compatible parsing into a Map; never fails, last duplicate wins. | | load(options?) | Effect<Loaded, DotenvError>: { values, files, shadowed, sources } after precedence — nothing written anywhere. | | environment(options?) | The merged environment as a Record, for @structure-ai/config's load(settings, { env }). | | configProvider(options?) | A ConfigProvider over that environment (_ nesting) for Effect.withConfigProvider / Layer.setConfigProvider. | | apply(options?) / layer(options?) | Load and write the values into process.env; the layer form for the top of a layer stack. | | LoadOptions | cwd, files, environment, env, override, expand. | | cascade(environment) | The conventional file list for an environment name. | | check(options) / toConfigIssues(report) | Compare the merged environment against a Setting (required = no default, not optional) and/or an example file (every key required); report { required, missing, empty, unknown }, convertible to ConfigLoadError issues. | | setValues(path, entries) / unsetKeys(path, keys) | Edit a file in place. | | Document.parse/assignments/values/set/unset/render | The lossless document model behind the writers: quote style, export prefix, inline comments, line numbers, line endings. | | formatValue(value) / stringify(entries) | Quote a value so it reads back unchanged (bare → single → double/backtick with $ escaped; a value mixing all three quotes is a write error). | | expand(entries, options?) | The expansion step on its own. | | DotenvError | { kind: read \| missing \| expand \| write \| run \| invalid, reason, path?, key?, exitCode? }, classification: "permanent"; messages never carry values. |

Out of scope by decision (ADR-0017): encrypted .env.vault files and key management.