@asafarim/envage

中文文档
0.5.0发布于 2026/11/04MITTypeScriptES Module近 30 天下载 1967

Secure, age-based encryption/decryption for .env files across multiple environments and monorepo app folders

原始使用文档
文档目录30 节
  1. @asafarim/envage TS Package
  2. Features
  3. Compatibility
  4. Installation
  5. Quick Start
  6. 1. Generate a keypair
  7. 2. Create your config
  8. 3. Encrypt your env files
  9. 4. Check status
  10. 5. Decrypt on a new machine
  11. CLI Reference
  12. envage init-key
  13. envage encrypt
  14. envage decrypt
  15. envage recipient add/list/remove
  16. envage status
  17. Configuration — envage.config.json
  18. Root env — plain .env files
  19. Glob patterns in apps
  20. Config fields
  21. Programmatic API
  22. Team encryption (v0.5.0)
  23. Git Integration
  24. .gitignore rules
  25. Staged file warning
  26. Optional: pre-commit hook
  27. Monorepo Structure
  28. Security Notes
  29. Development
  30. License

Commit encrypted .env files with confidence

@asafarim/envage TS Package

Secure, age-based encryption for .env files across monorepo environments.

envage lets you safely commit encrypted .env files to Git while keeping the decrypted secrets out of version control. It uses the battle-tested age encryption format — the same tool used by Go, Rust, and DevSecOps toolchains worldwide.


Features

  • 🔐 age encryption — uses age-encryption (pure TypeScript, no native deps)
  • 🌍 Multi-environment — dev, staging, prod, any custom names, or root for a plain .env file
  • 📁 Monorepo-aware — encrypt/decrypt all apps at once with --all
  • 👥 Team recipients — independent identities decrypt shared ciphertext; no private-key sharing
  • 🔑 Key or passphrase — supports X25519 keypairs and passphrase-based encryption
  • 🛡 Git-safe — auto-updates .gitignore; warns if you stage a decrypted file
  • ✅ Production guard — interactive confirmation required to decrypt prod files
  • 🧩 Programmatic API — use as a library in your own scripts
  • 📦 pnpm / monorepo friendly — works seamlessly with pnpm workspaces

Compatibility

Requires Node.js 20 or newer. Envage has no React dependency or peer-version restriction and works in React 18 and React 19 project tooling. Its CLI and filesystem API run in Node.js, outside browser-rendered components. The demo uses React 18, with a separate React 19 build in CI. The demo defaults to dark mode, remembers your theme choice, and includes CLI/API team-recipient examples and a Roadmap linked to the current milestone.

Installation

# In your monorepo root
pnpm add -D @asafarim/envage

# Or globally
pnpm add -g @asafarim/envage

Quick Start

1. Generate a keypair

npx envage init-key

This creates:

.age/key.txt   ← private key (mode 0600, gitignored automatically)
.age/key.pub   ← public key  (safe to share with teammates)

2. Create your config

# envage.config.json (add it to the monorepo root)
# use relative paths from the monorepo root
# use 'root' for the .env files regardless of environment, 'dev' for development environment .env.dev, 'staging' for staging environment .env.staging, 'prod' for production environment .env.prod
# use '.' for the current directory (monorepo root): "apps": [".", "apps/web"],
{
  "apps": [".", "apps/web", "apps/admin", "packages/api"],
  "envs": ["root", "dev", "staging", "prod"],
  "keyFile": ".age/key.txt"
}

3. Encrypt your env files

# Encrypt a single app/env
npx envage encrypt apps/web --env dev

# Encrypt all apps at once
npx envage encrypt --all --env prod

# Encrypt plain .env files across all apps (one environment per invocation)
npx envage encrypt --all --env root

4. Check status

npx envage status
apps/web
  dev        🔒 encrypted
  staging    🔒 encrypted
  prod       🔒 encrypted

apps/admin
  dev        🔓 decrypted (not yet encrypted)
  prod       🔒 encrypted

packages/api
  dev        🔒 encrypted
  prod       🔒 encrypted

9 entries — 8 encrypted  1 decrypted

5. Decrypt on a new machine

# Restore your own private identity from secure backup, or have an authorized
# teammate add your new public recipient (see Team encryption below).
npx envage decrypt apps/web --env dev

# Decrypt all at once
npx envage decrypt --all --env dev

CLI Reference

envage init-key

Generate a new age X25519 keypair.

envage init-key [--output <folder>]
OptionDefaultDescription
--output.ageOutput folder for key.txt and key.pub

envage encrypt

Encrypt .env.<env> → .env.<env>.age (or .env → .env.age when --env root).

envage encrypt [path] [options]
OptionDefaultDescription
--envdevEnvironment name
--keyauto-detectedPath to age key file
--passphrase—Passphrase (prompted if omitted)
--all—Encrypt all apps in envage.config.json

Examples:

envage encrypt apps/web --env dev
envage encrypt apps/web --env prod --key .age/key.txt
envage encrypt --all --env staging
envage encrypt apps/web --env dev --passphrase "my secret"

# Root env — targets the plain .env file (no suffix)
envage encrypt apps/portal --env root
envage encrypt --all --env root

envage decrypt

Decrypt .env.<env>.age → .env.<env> (or .env.age → .env when --env root).

envage decrypt [path] [options]
OptionDefaultDescription
--envdevEnvironment name
--keyauto-detectedPath to age private key file
--passphrase—Passphrase
--all—Decrypt all apps in envage.config.json

⚠️ Decrypting prod or production environments always prompts for manual confirmation.

Examples:

envage decrypt apps/web --env dev
envage decrypt --all --env prod

# Root env — targets .env.age → .env
envage decrypt apps/portal --env root
envage decrypt --all --env root

envage recipient add/list/remove

Manage team membership and re-encrypt every existing configured ciphertext. add and remove take one complete public recipient plus optional --key <identity-file>. See Team encryption for configuration, recovery, and revocation limits.

envage status

Show encryption status for all configured apps and environments.

envage status [path]
OptionDescription
--envFilter output to a single environment

Configuration — envage.config.json

Place this file at the root of your monorepo:

{
  "apps": [
    "apps/web",
    "apps/admin",
    "packages/api"
  ],
  "envs": ["dev", "staging", "prod"],
  "keyFile": ".age/key.txt",
  "keyPubFile": ".age/key.pub"
}

Root env — plain .env files

Use "root" as an env name to target the bare .env file (no suffix) in each app folder. This is useful for projects that already use a single .env and don't want to rename it.

{
  "apps": [".", "apps/web", "apps/admin", "packages/api"],
  "envs": ["root", "dev", "staging", "prod"],
  "keyFile": ".age/key.txt"
}
env nameplaintext fileencrypted file
root.env.env.age
dev.env.dev.env.dev.age
staging.env.staging.env.staging.age
prod.env.prod.env.prod.age

Glob patterns in apps

App entries may use glob patterns, which are expanded against the filesystem to the matching directories that exist. This is handy for monorepos where you don't want to list every package by hand.

{
  "apps": [".", "apps/*", "packages/*"],
  "envs": ["root", "production"],
  "keyFile": ".age/key.txt",
  "keyPubFile": ".age/key.pub"
}
  • * matches a single path segment (e.g. apps/* → apps/web, apps/admin).
  • ** matches nested directories at any depth.
  • Only existing directories are matched; files are ignored.
  • Non-glob entries (like . or apps/web) are kept as-is, even if the folder does not exist yet.
  • Results are de-duplicated, so overlapping patterns are safe.

Config fields

FieldTypeDefaultDescription
appsstring[][]App folder paths (relative to cwd). Supports glob patterns (see below)
envsstring[]["dev","staging","prod"]Environment names to manage
keyFilestring.age/key.txtPath to private key
keyPubFilestring.age/key.pubPath to public key
recipientsstring[]omittedPublic age recipients; omission keeps legacy single-key mode

Programmatic API

import {
  encryptEnv,
  decryptEnv,
  generateKeyPair,
  generateKeyPairToFolder,
  getEnvStatus,
  loadConfig,
  resolveApps,
  ensureGitignore,
  updateRecipient,
} from "@asafarim/envage";

// Generate a keypair
const { identity, recipient } = await generateKeyPair();

// Write keypair to disk
await generateKeyPairToFolder(".age");

// Encrypt a named env file (.env.dev → .env.dev.age)
await encryptEnv({
  folder: "apps/web",
  env: "dev",
  keyFile: ".age/key.txt",
});

// Encrypt the root env (.env → .env.age)
await encryptEnv({
  folder: "apps/portal",
  env: "root",
  keyFile: ".age/key.txt",
});

// Passphrase-based encryption
await encryptEnv({
  folder: "apps/web",
  env: "dev",
  passphrase: "my-secret-passphrase",
});

// Decrypt a named env file (.env.dev.age → .env.dev)
await decryptEnv({
  folder: "apps/web",
  env: "dev",
  keyFile: ".age/key.txt",
});

// Decrypt the root env (.env.age → .env)
await decryptEnv({
  folder: "apps/portal",
  env: "root",
  keyFile: ".age/key.txt",
});

// Expand app globs ("apps/*") to actual folder paths
const config = await loadConfig();
const folders = await resolveApps(config);
// → ["/repo/apps/web", "/repo/apps/admin", ...]

// Check status (globs in config are expanded automatically)
const statuses = await getEnvStatus(config);
statuses.forEach((s) => {
  console.log(`${s.folder} [${s.env}]: encrypted=${s.encrypted} decrypted=${s.decrypted}`);
});

// Ensure .gitignore rules are present
await ensureGitignore();

Team encryption (v0.5.0)

Each teammate runs envage init-key locally and shares only their public key.pub. Keep private identities on their owner's machine or in a secure backup. An existing authorized member can onboard another public recipient:

envage recipient add age1... --key .age/key.txt
envage recipient list
envage recipient remove age1... --key .age/key.txt

Replace age1... with a complete age X25519 public key. Commands run from the project root and require an existing envage.config.json with nonempty apps and envs. Add/remove re-encrypt all existing ciphertext in those configured slots, including glob-expanded apps and root environments. Unconfigured files are outside their scope. Missing ciphertext is skipped. Passphrase-encrypted or undecryptable files abort the entire operation before replacements begin.

Public recipients are saved in the versioned config:

{
  "apps": [".", "apps/*"],
  "envs": ["root", "dev", "prod"],
  "keyFile": ".age/key.txt",
  "recipients": ["age1...", "age1..."]
}

Commit the updated config and ciphertext together. Ordinary CLI encryption uses configured recipients, even when --key is supplied, and needs no private key. An explicit --passphrase selects passphrase encryption instead. Configs without recipients keep the legacy single-key behavior. The first recipient add retains the supplied identity's public key as the initial authorized member. Duplicate, invalid, unknown, or last-recipient removal requests fail safely.

The programmatic API accepts explicit public recipients:

await encryptEnv({ folder: "apps/web", env: "dev", recipients: [alicePublic, bobPublic] });
await updateRecipient("add", bobPublic, { cwd: projectRoot, keyFile: ".age/key.txt" });

encryptEnv does not load config automatically; pass config.recipients yourself. Recipient changes decrypt only in memory, never to plaintext files. Replacements use same-directory temporary files and atomic renames. On a write failure, completed replacements are rolled back. Multi-file replacement is not one filesystem transaction: do not run concurrent encryption/config writers. A lock prevents concurrent recipient updates. Originals and a manifest are saved in a private .envage-recovery-* directory until success or successful rollback. After a process interruption or failed rollback, stop all writers, restore every original listed in that directory's manifest.json, verify decryption, then remove the recovery directory and envage.config.json.lock. Never commit these recovery artifacts. Keep a secure backup or clean Git checkpoint before changes.

Removing a recipient blocks access to the newly encrypted files only. Old Git history, ciphertext copies, and previously learned secrets remain accessible. Rotate underlying API credentials separately if access must be revoked.


Git Integration

.gitignore rules

Running envage init-key automatically adds these rules to your .gitignore:

# --- envage managed ---
# Decrypted env files — never commit
.env*
!.env*.age
!.env.example
!.env.template
# Age private key
.age/key.txt
# --- end envage ---

Staged file warning

If you accidentally git add a decrypted env file before encrypting, the encrypt command will catch it and exit with:

✖ You are trying to commit a decrypted env file.
✖ Only encrypted .age files are allowed.
  apps/web/.env.prod

Optional: pre-commit hook

Add this to .git/hooks/pre-commit to prevent accidental commits entirely:

#!/usr/bin/env bash
npx envage status 2>/dev/null | grep -q "decrypted" && \
  echo "❌ You have decrypted env files. Run: npx envage encrypt --all" && \
  exit 1
exit 0

Monorepo Structure

monorepo/
├── .age/
│   ├── key.txt              ← private key (gitignored)
│   └── key.pub              ← public key  (commit this)
├── apps/
│   ├── web/
│   │   ├── .env.dev         ← decrypted (gitignored)
│   │   ├── .env.dev.age     ← encrypted (committed ✅)
│   │   ├── .env.prod        ← decrypted (gitignored)
│   │   └── .env.prod.age    ← encrypted (committed ✅)
│   ├── admin/
│   │   ├── .env.dev.age
│   │   └── .env.prod.age
│   └── portal/              ← app using root env (plain .env)
│       ├── .env             ← decrypted (gitignored)
│       ├── .env.age         ← encrypted (committed ✅)
│       └── .env.staging.age ← encrypted (committed ✅)
├── packages/
│   └── api/
│       ├── .env.dev.age
│       └── .env.prod.age
├── envage.config.json       ← committed ✅
└── .gitignore               ← auto-updated by envage

Security Notes

  • Never commit or share .age/key.txt — it is your private identity. Give each teammate their own identity and add only their public recipient.
  • Decrypted values are never logged — not in verbose mode, not in errors
  • Production decryption is gated — always requires explicit y confirmation
  • Encrypted files are binary — they cannot be accidentally read as text
  • The age format provides authenticated encryption; tampering is detectable

Development

# Install dependencies
pnpm install

# Build
pnpm build

# Run tests
pnpm test

# Type-check only
pnpm lint

License

MIT © Ali Safarim

版本与发布记录

@asafarim/envage 共发布 10 个版本,首次发布于 2026/05/22;过去 12 个月发布了 10 个版本。

版本发布日期距今
0.5.0latest2026/11/043 天前
0.4.22026/07/064 个月前
0.4.12026/07/064 个月前
0.4.02026/07/064 个月前
0.3.32026/06/015 个月前
0.3.22026/05/225 个月前

关于 @asafarim/envage 的常见问题

@asafarim/envage 是什么?

Secure, age-based encryption/decryption for .env files across multiple environments and monorepo app folders

如何安装 @asafarim/envage?

在项目目录运行 npm install @asafarim/envage,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 envage,可以用 npx envage 直接运行。

@asafarim/envage 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@asafarim/envage 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@asafarim/envage 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@asafarim/envage 可以商用吗?

@asafarim/envage 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@asafarim/envage 的下载量有多少?

根据 npm 官方下载统计,@asafarim/envage 在 2026/10/07 – 2026/11/05 这 30 天内被下载 1,967 次,日均约 66 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@asafarim/envage 还在维护吗?

最新版本 0.5.0 发布于 2026/11/04(3 天前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/05/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。