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

@hubble-ventures/env-source

v0.4.0

Published

Populate .env files from orchestrated secret providers, driven by decorated .env.source manifests — one format for local dev and CI.

Downloads

1,438

Readme

@hubble-ventures/env-source

Populate .env files from orchestrated secret providers, driven by decorated .env.source manifests — one format for local dev and CI.

You declare where each variable comes from right next to the variable, in a file that reads like a .env. env-source discovers those manifests across a monorepo, resolves each variable through the right provider (Infisical today; the provider layer is pluggable), and writes a plain .env next to each manifest — or, in CI, loads the values straight into the job environment.

.env.source            ← the manifest: variables + where they come from (committed)
env-source.toml        ← root, non-secret provider context (committed)
        │
        ▼  env-source pull
.env                   ← resolved values (gitignored)

The .env.source format

It's dotenv with sticky decorators (think frontmatter). A comment block says which provider sources the variables below it, from which container, in which environments — and that context applies to every assignment until the next decorator changes it. You write the provider and path once, not per key:

# infisical                          ← provider id (opens a group)
# (development,preview,production)    ← environments the provider is consulted in
#     /clerk                         ← provider container path
CLERK_SECRET_KEY=                    ← sourced from /clerk
GOOGLE_IOS_CLIENT_ID=                ← same group — no need to repeat the decorator
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=

What is sticky vs. one-shot

  • Provider, container path, and environments are sticky — they carry down to every assignment until a new decorator replaces them.
  • A source-key alias is one-shot — a bare # SOURCE_KEY line aliases only the immediately following key (source keys are inherently per-variable):
    # infisical
    #     /clerk
    #     CLERK_PUBLISHABLE_KEY        ← one-shot: applies to the next key only
    VITE_CLERK_PUBLISHABLE_KEY=
    CLERK_SECRET_KEY=                  ← reverts to its own name
  • Blank lines are cosmetic — they never end a group. Format for readability freely.
  • # literal clears the provider context, so the keys below are literals (their value is the right-hand side). Literals also work at the top of a file, before any decorator:
    # literal
    NODE_ENV=production
    PORT=8080

Other rules

  • No environments line → the provider is consulted in all environments.
  • The right of = is an optional fallback default. An empty RHS or a <placeholder> means "no concrete default". When no source yields a value, the variable falls back to this default (and is dropped if there is none).
  • Several provider blocks with no key between them stack into a fallback chain — each is tried in order until one has the key, then the default:
    # infisical
    #     /shared
    # onepassword
    #     /vault/item
    API_TOKEN=<optional default>
  • Provider ids come from env-source.toml, so a container key that happens to be a lowercase word (token, secret) is never mistaken for a provider.

Putting it together:

# infisical
# (development,preview,production)
#     /payments/stripe
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
#     STRIPE_WEBHOOK_SIGNING_SECRET   ← one-shot alias for the next key
WEBHOOK_SECRET=

# Sourced only in dev; every other environment falls back to the default.
# infisical
# (development)
#     /shared
DEBUG_TOKEN=off

# literal
NODE_ENV=production

Root config — env-source.toml

Non-secret provider context, committed at (or above) the workspace root. Credentials never live here — they come from the environment at resolve time.

default_environment = "development"
output = ".env"

[providers.infisical]
project = "acme-payments"                          # project slug — CI (REST/OIDC) lane
# project_id = "…uuid…"                             # project id — local CLI lane (or $INFISICAL_PROJECT_ID)
oidc_audience = "https://github.com/your-org"      # CI OIDC audience (optional)

CLI

npx @hubble-ventures/env-source pull                 # write .env (0600) next to each manifest
npx @hubble-ventures/env-source pull --env preview   # resolve a specific environment
npx @hubble-ventures/env-source run -- npm run dev   # inject into a process; nothing hits disk
npx @hubble-ventures/env-source validate             # structural checks
npx @hubble-ventures/env-source validate --against-providers  # + peek the live vault
npx @hubble-ventures/env-source validate --check-values       # + flag present-but-empty values
npx @hubble-ventures/env-source diff --base origin/main  # what changed in the manifest surface
npx @hubble-ventures/env-source migrate secrets.json     # convert a legacy infisicml manifest
npx @hubble-ventures/env-source list                 # summarize discovered manifests

Add --profile <name> to any resolving command to load a sibling .env.<name>.source where it exists (e.g. a deploy profile).

Infisical auth has exactly two lanes:

  • local — the Infisical CLI, using your own infisical login session. env-source shells out to it and never handles a token.
  • CI — GitHub OIDC (INFISICAL_IDENTITY_ID + permissions: id-token: write).

diff — review the manifest surface on a PR

diff compares each manifest against a git ref and reports what a reviewer cares about — variables added/removed and any variable whose source moved (provider, container path, source key, or environment scope). It reads no secret values, so it's safe to run on every PR. It exits non-zero when anything changed (use --exit-zero to report without failing).

apps/api
+ B         ← infisical /shared:B
+ NODE_ENV  ← literal
~ A         infisical /shared:A → infisical /other:A

migrate — from legacy infisicml secrets.json

migrate secrets.json converts a legacy manifest into .env.source (plus a sibling .env.<profile>.source per profile). It prints a dry run by default; add --write to create the files. Concepts env-source doesn't model (environments.*.optionalKeys, ci) are reported as warnings, not dropped silently.

GitHub Action

Load resolved secrets into the job environment for later steps. Authentication is GitHub OIDC — no long-lived credential.

permissions:
  id-token: write            # required — mints the OIDC token
steps:
  - uses: actions/checkout@v4
  - uses: hubble-ventures/env-source@v1
    with:
      command: pull
      environment: production
      identity-id: ${{ vars.INFISICAL_IDENTITY_ID }}
  - run: node server.js      # secrets are now in the environment (masked in logs)

Gate manifest changes on a PR (no secrets read; posts a sticky comment + job summary):

permissions:
  contents: read
  pull-requests: write       # for the sticky comment
steps:
  - uses: actions/checkout@v4
    with: { fetch-depth: 0 } # diff needs the base ref
  - uses: hubble-ventures/env-source@v1
    with:
      command: diff
      base: ${{ github.event.pull_request.base.sha }}
      comment: 'true'
      fail-on-change: 'false'

Providers

A provider defines three capabilities the core orchestrates over:

| Capability | Purpose | | ---------- | ------- | | auth | Establish access (Infisical: local CLI session, or GitHub OIDC in CI). | | read | Resolve values for the declared keys at a container path. | | peek | Assert a key exists without surfacing its value (used by validate). |

Least privilege, as far as the lane allows. Nothing you didn't declare is ever written to a .env — a provider returns the declared keys and drops the rest. How little crosses the wire depends on what the backend actually offers:

  • Infisical over REST (CI). /secrets/raw/{key} is a genuine single-secret endpoint, so each declared key is fetched by name, one request per key, and nothing else leaves the vault. peek asserts existence from the response status without reading the value.
  • Infisical over the CLI (local). infisical secrets get <KEY> resolves server-side to GET /secrets/raw?secretPath=P — the folder endpoint, with no key filter — and filters client-side. The whole folder crosses the wire whichever way you ask, so asking per key buys no privacy and multiplies the requests by the number of declared keys (74 keys once cost 74 full-folder reads and a 429). This lane therefore reads each folder once via infisical export, memoised per environment and path, and selects the declared keys from it — strictly less data over the wire than per-key. The trade is that the folder's other values pass through memory for the life of the command, and peek here is membership in that folder rather than a value-free status check.

Infisical ships in the box. The Provider interface (src/core/types.ts) is the extension point for 1Password, the process environment, and password managers.

Library

import {
  loadConfig,
  parseEnvSource,
  compile,
  materialize,
  resolveProviders,
} from "@hubble-ventures/env-source";

parse → compile → materialize is a pure pipeline over a Provider; the adapters (workspace, gha) and the concrete providers sit around that core.

License

MIT