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

soon-env

v0.2.0

Published

Encrypt and decrypt .soonenv files: ciphertext on disk, plaintext when you need it. CLI + Node API, AES-256-GCM + scrypt.

Readme

soon-env

.soonenv 加密环境变量文件的读写工具:磁盘上永远是密文,需要时才变成明文。 命令行 + Node API 双入口,零运行时依赖。

这是 .soonenv 格式的唯一实现:VS Code 扩展 soon-env-edit 在运行时用的也是它(通过 workspace 依赖打进 vsix),所以格式永远不会两边漂移。

  • 加密:scrypt(N=32768, r=8, p=1) 派生 32 字节密钥 → AES-256-GCM
  • 一个文件 = 一行密文,salt / IV / 认证标签都在文件里,自包含
  • 密码来源:函数参数 > SOON_ENV_PW 环境变量
  • 零依赖,同时提供 CJS / ESM / TypeScript 类型

安装

pnpm add soon-env      # 或 npm install soon-env

要求 Node.js >= 18.18。

命令行

# 密码只从环境变量读,不提供命令行参数(避免泄漏到 shell history / 进程列表)
export SOON_ENV_PW='your-password'

soon-env encrypt .env                 # 生成同目录的 .env.soonenv
soon-env decrypt .env.soonenv         # 明文打到 stdout
soon-env decrypt .env.soonenv -o .env.tmp --force
pnpx soon-env decrypt .env.soonenv    # 不想全局安装也可以直接用

| 命令 | 默认行为 | | --- | --- | | soon-env encrypt <file> | 生成 <file>.soonenv(同目录),进度信息写到 stderr | | soon-env decrypt <file> | 明文写到 stdout(方便 \| grep / \| xargs) |

| 选项 | 说明 | | --- | --- | | -o, --output <file> | 指定输出文件;已存在时默认拒绝 | | --stdout | encrypt 时把密文打到 stdout,不落盘 | | -f, --force | 覆盖已存在的输出文件 | | --password-env <NAME> | 换个环境变量名读密码(默认 SOON_ENV_PW) | | -q, --quiet | 不输出进度行 | | -h, --help / -v, --version | 帮助 / 版本 |

| 退出码 | 含义 | | --- | --- | | 0 | 成功 | | 1 | 运行时错误(密码错误、格式损坏、文件不存在……) | | 2 | 用法错误(命令/参数写错) |

常见用法:

# 加密项目里所有的 .env* 文件
for f in .env .env.local .env.production; do soon-env encrypt "$f"; done

# 临时用一次密码,不导出到整个 shell
SOON_ENV_PW="$(pass show soon)" soon-env decrypt .env.soonenv | grep DB_HOST

# 在 CI 里生成密文(密码来自 secret)
SOON_ENV_PW="$SOON_ENV_SECRET" soon-env encrypt .env --force

Node API

import {
  encryptEnvContent,
  decryptEnvContent,
  encryptEnvFile,
  decryptEnvFile,
} from 'soon-env';

// 内容:传入 env 文本,返回密文文本
const cipher = encryptEnvContent('A=1\n', 'pw');   // 显式传密码
const cipher2 = encryptEnvContent('A=1\n');        // 从 SOON_ENV_PW 取
const plain = decryptEnvContent(cipher, 'pw');

// 文件:默认在源文件旁边生成 <文件名>.soonenv
const outputPath = await encryptEnvFile('.env');                  // -> .env.soonenv
await encryptEnvFile('.env', { output: 'dist/.env.soonenv' });
await encryptEnvFile('.env', { force: true });                    // 覆盖已存在
const plain2 = await decryptEnvFile('.env.soonenv');

密码解析优先级对所有 API 一致:

  1. 显式传入的 password(按原样使用,不做任何加工)
  2. options.envVar 指定的环境变量(默认 SOON_ENV_PW)
  3. 都没有 → 抛 SoonEnvPasswordMissingError

从环境变量取到的密码会剥掉结尾的换行符 —— SOON_ENV_PW=$(cat pw.txt) 或 CI 里 echo 出来的值不会因为多个 \n 而解不开。

错误类型

| 错误 | 什么时候抛 | | --- | --- | | SoonEnvPasswordMissingError | 没有可用的密码 | | SoonEnvPasswordError | 密码错误(GCM 认证失败) | | SoonEnvFormatError | 内容不是合法的 SoonEnv 密文 | | SoonEnvFileError | 文件不存在(code: 'ENOENT')或输出已存在(code: 'EEXIST') |

都继承自 SoonEnvError,可以一次性 catch。

脱敏模板与路径规则

生成「给别人和 AI 看的结构文件」用的规则也在这里,和扩展完全同源:

import { maskEnvContent, valueFingerprint, maskedPathFor, soonEnvPathFor } from 'soon-env';

maskEnvContent('# 数据库\nDB_PASSWORD=real\n');
// '# 数据库\nDB_PASSWORD=soon-env-encrypted\n'

// 传入文件密钥,就在每个值后面附上该值的不可逆指纹
maskEnvContent('# 数据库\nDB_PASSWORD=real\n', { fingerprintKey: material.key });
// '# 数据库\nDB_PASSWORD=soon-env-encrypted:8b45d713\n'

soonEnvPathFor('/p/.env');            // '/p/.env.soonenv'
maskedPathFor('/p/.env.soonenv');     // '/p/.env'

key、注释、空行、缩进、换行符(CRLF/LF)、行尾注释都会原样保留,只有值被替换成占位符。

MaskOptions:

| 选项 | 说明 | | --- | --- | | maskValue | 占位符文本,默认 soon-env-encrypted | | fingerprintKey | 用于算指纹的密钥(通常是该 .soonenv 的加密密钥);不传就不加指纹 | | fingerprintLength | 指纹长度(hex 字符数),默认 8 |

值指纹(valueFingerprint(key, value))解决的是「密文没法 review」:

  • 同一个值永远得到同一个指纹 → 内容没变时生成的模板逐字节一致,没改的行不会出现在 diff 里;
  • 值一改指纹就变 → 提交前能从生成的 .env 看出到底改了哪些变量;
  • 它是 HMAC-SHA256(从密钥派生的子密钥, 值) 的截断 hex(子密钥用 HKDF 做域分隔), 没有密钥就算不出来、也无法拿字典离线比对,所以不会削弱安全性;
  • 空值不附指纹;改注释不影响指纹(注释本来就在 diff 里看得见)。

底层 API

需要自己控制交互流程的宿主(例如 VS Code 扩展要自己弹密码框、自己做会话级密钥缓存) 可以直接用底层原语:parseEnvelope / createKeyMaterial / keyMaterialFor / deriveKey / encryptText / decryptText / tryDecrypt / matchesEnvelope / isEncryptedPayload。普通调用方用上面的内容级 / 文件级 API 就够了。

密文格式(v1)

磁盘上的文件永远只有一行:

SOONENV1.<base64url(JSON)>

JSON 信封:

| 字段 | 说明 | | --- | --- | | v | 格式版本,当前 1 | | kdf | "scrypt" | | n / r / p | scrypt 参数,当前 32768 / 8 / 1 | | salt | 16 字节随机盐(base64url) | | iv | 12 字节随机 IV(base64url) | | tag | 16 字节 GCM 认证标签(base64url) | | ct | 密文本身(base64url,空文件的密文长度为 0) |

  • 密钥:scrypt(password.normalize('NFC'), salt, 32)
  • 附加认证数据(AAD):SOONENV1.v1|scrypt|<n>|<r>|<p>|<salt>, 也就是说改 KDF 参数或 salt 都会导致解密失败;
  • 解析时会校验 KDF 参数上下限,恶意文件无法用超大 n 拖垮内存;
  • 向后兼容是硬承诺:src/test/fixtures/ 里冻结了一个旧实现的编译副本 和一份由旧版 VS Code 扩展真实生成的密文,compat.test.ts 会双向验证 「老文件打得开、新文件旧版也能打开」。想改格式,先想清楚历史密文怎么办。

与 soon-env-edit 的关系

| | soon-env(本包) | soon-env-edit(VS Code 扩展) | | --- | --- | --- | | 角色 | 格式与算法的唯一实现 | VS Code 集成层(虚拟文件系统、自定义编辑器、密码缓存) | | 形态 | npm 包:CLI + Node API | .vsix 扩展,打包时把本包内联进单文件 bundle | | 加解密 | 这里 | 调用这里,不重复实现 |

所以:改算法、改脱敏规则、改路径推导,都只改本包;扩展那边会自动跟着变。

开发

pnpm install
pnpm --filter soon-env run build      # 产出 dist/cjs + dist/esm + .d.ts
pnpm --filter soon-env run test       # 构建 + 62 个测试
pnpm --filter soon-env run typecheck

目录:

src/
  crypto.ts      密文格式与算法(改动等于改磁盘格式,慎之又慎)
  envContent.ts  内容级 API
  envFile.ts     文件级 API
  password.ts    SOON_ENV_PW 解析规则
  mask.ts        脱敏模板规则
  paths.ts       路径推导规则
  cli.ts         参数解析 + 命令实现(暴露 run(),可被直接调用测试)
  bin.ts         可执行入口(只负责调 run 并设置退出码)
  index.ts       公开 API 汇总
  test/          测试(含 fixtures/ 里的兼容性见证文件)

测试直接调用 run(argv, io) 而不是 fork 子进程,所以既能断言退出码,也能在禁止 子进程的受限环境里跑。

发布

cd packages/soon-env
pnpm run build
pnpm run test          # prepublishOnly 也会自动跑一遍
npm publish --dry-run  # 先看看到底会发布哪些文件
npm publish

files 只放行 dist/cjs、dist/esm、README.md、LICENSE,测试、源码、 临时目录都不会进包。改了 package.json 的 version 记得同步 src/version.ts (version.test.ts 会校验,漏改就红)。

许可

MIT