{"_id":"@1dot5/smonoenv","_rev":"6-ae64b724458d8fd974fb329ec2066562","name":"@1dot5/smonoenv","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@1dot5/smonoenv","version":"0.1.0","license":"MIT","_id":"@1dot5/smonoenv@0.1.0","maintainers":[{"name":"mtyk15","email":"kondo@1dot5.jp"}],"homepage":"https://github.com/1dot5/smonoenv#readme","bugs":{"url":"https://github.com/1dot5/smonoenv/issues"},"bin":{"smonoenv":"dist/cli.js"},"dist":{"shasum":"092b76a40917318cf3f712b07004cd3d279fc602","tarball":"https://registry.npmjs.org/@1dot5/smonoenv/-/smonoenv-0.1.0.tgz","fileCount":55,"integrity":"sha512-mbdxuwYPdKX93KoU8a3GhrFqa+2/BDbbmarNtMK0scmaFyfO5+Ek6M/QUAVeiyy0ljr880cvQFsuFu13mvOkkw==","signatures":[{"sig":"MEUCIQDP/go95vrGOYpZlaJMgYUcO2xI6/bVLPOTIA4J8vHpWAIgR8Hk+dshucZ27k8prIFFPNcUEmLc35U2nKUYY5q/6Lw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51457},"main":"./dist/index.js","type":"module","_from":"file:1dot5-smonoenv-0.1.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"scripts":{"dev":"tsc --watch","build":"tsc"},"_npmUser":{"name":"mtyk15","email":"kondo@1dot5.jp"},"_resolved":"/tmp/7f2f12ad498e210df103a6f1282b0ee8/1dot5-smonoenv-0.1.0.tgz","_integrity":"sha512-mbdxuwYPdKX93KoU8a3GhrFqa+2/BDbbmarNtMK0scmaFyfO5+Ek6M/QUAVeiyy0ljr880cvQFsuFu13mvOkkw==","repository":{"url":"git+https://github.com/1dot5/smonoenv.git","type":"git"},"_npmVersion":"10.8.2","description":"SOPS + age secret management CLI for monorepo environment files","directories":{},"_nodeVersion":"20.20.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.4.0","@types/node":"^25.5.0"},"_npmOperationalInternal":{"tmp":"tmp/smonoenv_0.1.0_1774336127591_0.2044061905958816","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@1dot5/smonoenv","version":"0.2.0","author":"","license":"MIT","_id":"@1dot5/smonoenv@0.2.0","maintainers":[{"name":"mtyk15","email":"kondo@1dot5.jp"}],"homepage":"https://github.com/1dot5/smonoenv#readme","bugs":{"url":"https://github.com/1dot5/smonoenv/issues"},"bin":{"smonoenv":"dist/cli.js"},"dist":{"shasum":"cf35c84c20df64592c93d8ef746fe4a9836d0946","tarball":"https://registry.npmjs.org/@1dot5/smonoenv/-/smonoenv-0.2.0.tgz","fileCount":55,"integrity":"sha512-2LDvjC2W/GVBV33CAPHW6Aww4RYly2lAv/XuRtWtniLtdaCbMNmZJ+op0MChrSViurLr++MvEfgJYXFLdeOLCQ==","signatures":[{"sig":"MEQCIB7F1lNDkUw/xeOE2cHNhfOlftfaIuYQWKt27XRA6XgbAiBglruHVIyK+CQ3GAkch9amPZ8yIlSrnA/IqhfxCUy1kQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51634},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"gitHead":"b1c9690a7210745a51b9dcc7ae5ee04b062b56d4","scripts":{"dev":"tsc --watch","build":"tsc","prepublishOnly":"tsc"},"_npmUser":{"name":"mtyk15","email":"kondo@1dot5.jp"},"repository":{"url":"git+https://github.com/1dot5/smonoenv.git","type":"git"},"_npmVersion":"11.6.0","description":"SOPS + age secret management CLI for monorepo environment files","directories":{},"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.4.0","@types/node":"^25.5.0"},"_npmOperationalInternal":{"tmp":"tmp/smonoenv_0.2.0_1774362321293_0.07499754865959174","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@1dot5/smonoenv","version":"0.2.1","author":"","license":"MIT","_id":"@1dot5/smonoenv@0.2.1","maintainers":[{"name":"mtyk15","email":"kondo@1dot5.jp"}],"homepage":"https://github.com/1dot5/smonoenv#readme","bugs":{"url":"https://github.com/1dot5/smonoenv/issues"},"bin":{"smonoenv":"dist/cli.js"},"dist":{"shasum":"4a65d57fc4b15f9e3378b6de4064935cea024609","tarball":"https://registry.npmjs.org/@1dot5/smonoenv/-/smonoenv-0.2.1.tgz","fileCount":51,"integrity":"sha512-4VEnIOBCgL7W4AaJuL7SS+JGn6uhiOMIlymIuqbl62C5U7EWLFfSIFiILqXrVeTFckWU2S49Ab7Z/8DFmWG9nQ==","signatures":[{"sig":"MEUCICdjx2d3mOUC2ecCGkbZGZTaVCWb1GFbxFzitkLD6PnNAiEAxq+AAPw5eBkQn66rHRT27ASAQYfJYcb024KJLmTO2lE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1dot5%2fsmonoenv@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":52177},"main":"./dist/index.js","type":"module","_from":"file:1dot5-smonoenv-0.2.1.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"scripts":{"dev":"tsc --watch","build":"tsc"},"_npmUser":{"name":"mtyk15","email":"kondo@1dot5.jp"},"_resolved":"/tmp/626e789ab21a231468825f6222374775/1dot5-smonoenv-0.2.1.tgz","_integrity":"sha512-4VEnIOBCgL7W4AaJuL7SS+JGn6uhiOMIlymIuqbl62C5U7EWLFfSIFiILqXrVeTFckWU2S49Ab7Z/8DFmWG9nQ==","repository":{"url":"git+https://github.com/1dot5/smonoenv.git","type":"git"},"_npmVersion":"10.8.2","description":"SOPS + age secret management CLI for monorepo environment files","directories":{},"_nodeVersion":"20.20.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.4.0","@types/node":"^25.5.0"},"_npmOperationalInternal":{"tmp":"tmp/smonoenv_0.2.1_1774364957648_0.3337726571742119","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@1dot5/smonoenv","version":"0.3.0","author":"","license":"MIT","_id":"@1dot5/smonoenv@0.3.0","maintainers":[{"name":"mtyk15","email":"kondo@1dot5.jp"}],"homepage":"https://github.com/1dot5/smonoenv#readme","bugs":{"url":"https://github.com/1dot5/smonoenv/issues"},"bin":{"smonoenv":"dist/cli.js"},"dist":{"shasum":"0839cd82c5569647194aa171e1d938e8c0030b36","tarball":"https://registry.npmjs.org/@1dot5/smonoenv/-/smonoenv-0.3.0.tgz","fileCount":55,"integrity":"sha512-BQg+ijLJpOOZC/8j7IaC1g/qKFk1DVN594UJJcsphWtzxCZep4fNT93cNpZgtoogx1CqV//kCqJfNGsJePlTuw==","signatures":[{"sig":"MEUCICb4dHUtJY89k1rf1Gqx1ugZMle6mk/5Y7d192yH+FYBAiEAhsAMDFvPeS2jDfC8D7JwdRkorF87E/mKWYZ9AwWLgD0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1dot5%2fsmonoenv@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":54801},"main":"./dist/index.js","type":"module","_from":"file:1dot5-smonoenv-0.3.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"scripts":{"dev":"tsc --watch","build":"tsc"},"_npmUser":{"name":"mtyk15","email":"kondo@1dot5.jp"},"_resolved":"/tmp/a795ecdd0d6f80f67c437c29766df0d8/1dot5-smonoenv-0.3.0.tgz","_integrity":"sha512-BQg+ijLJpOOZC/8j7IaC1g/qKFk1DVN594UJJcsphWtzxCZep4fNT93cNpZgtoogx1CqV//kCqJfNGsJePlTuw==","repository":{"url":"git+https://github.com/1dot5/smonoenv.git","type":"git"},"_npmVersion":"10.8.2","description":"SOPS + age secret management CLI for monorepo environment files","directories":{},"_nodeVersion":"20.20.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.4.0","@types/node":"^25.5.0"},"_npmOperationalInternal":{"tmp":"tmp/smonoenv_0.3.0_1774408151180_0.7781149387763922","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@1dot5/smonoenv","version":"0.3.1","author":"","license":"MIT","_id":"@1dot5/smonoenv@0.3.1","maintainers":[{"name":"mtyk15","email":"kondo@1dot5.jp"}],"homepage":"https://github.com/1dot5/smonoenv#readme","bugs":{"url":"https://github.com/1dot5/smonoenv/issues"},"bin":{"smonoenv":"dist/cli.js"},"dist":{"shasum":"0dc98da97316d954357d87dbf442470e972dcece","tarball":"https://registry.npmjs.org/@1dot5/smonoenv/-/smonoenv-0.3.1.tgz","fileCount":79,"integrity":"sha512-oUD7IGy1vTDQ5Tx12zJqQxqALORN0kpqwRXmPpISQ4ZBvxPWL1BwNIvAJWWUEZOQDf8XjEu8GelLTXDJ4Ms4jg==","signatures":[{"sig":"MEYCIQC05+cL3b2w2Kge6oFprWiJBWh5NawsWS3RIpaDMgFFHAIhAO7iDWdcbfLUg9a4Hh6WIAYNhGLFpDYlB+ZZN9N+hx3F","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1dot5%2fsmonoenv@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":99594},"main":"./dist/index.js","type":"module","_from":"file:1dot5-smonoenv-0.3.1.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"mtyk15","email":"kondo@1dot5.jp"},"_resolved":"/tmp/95e6329508eeed9046a8fae901781607/1dot5-smonoenv-0.3.1.tgz","_integrity":"sha512-oUD7IGy1vTDQ5Tx12zJqQxqALORN0kpqwRXmPpISQ4ZBvxPWL1BwNIvAJWWUEZOQDf8XjEu8GelLTXDJ4Ms4jg==","repository":{"url":"git+https://github.com/1dot5/smonoenv.git","type":"git"},"_npmVersion":"10.8.2","description":"SOPS + age secret management CLI for monorepo environment files","directories":{},"_nodeVersion":"20.20.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.1","typescript":"^5.4.0","@types/node":"^25.5.0"},"_npmOperationalInternal":{"tmp":"tmp/smonoenv_0.3.1_1774450518029_0.12184955584862722","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@1dot5/smonoenv","version":"0.4.0","description":"SOPS + age secret management CLI for monorepo environment files","homepage":"https://github.com/1dot5/smonoenv#readme","bugs":{"url":"https://github.com/1dot5/smonoenv/issues"},"repository":{"type":"git","url":"git+https://github.com/1dot5/smonoenv.git"},"license":"MIT","author":"","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","bin":{"smonoenv":"dist/cli.js"},"devDependencies":{"@types/node":"^25.5.0","typescript":"^5.4.0","vitest":"^4.1.1"},"engines":{"node":">=18"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc","dev":"tsc --watch","pretest":"tsc","test":"vitest run","test:watch":"vitest"},"_id":"@1dot5/smonoenv@0.4.0","_integrity":"sha512-EWPQV7gSZYgptpZ4TL5J50IPPMy9LHUY6+2HKfpOmeeh3GsavEq/arKgDeiXp8afoYF6/WIkYD5n/8BDkcQqPQ==","_resolved":"/tmp/d5fdc607dea7f21895c4c208ff5b4fe4/1dot5-smonoenv-0.4.0.tgz","_from":"file:1dot5-smonoenv-0.4.0.tgz","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-EWPQV7gSZYgptpZ4TL5J50IPPMy9LHUY6+2HKfpOmeeh3GsavEq/arKgDeiXp8afoYF6/WIkYD5n/8BDkcQqPQ==","shasum":"f242fbce79cab1cda3bdd2b5443441689cac6b6a","tarball":"https://registry.npmjs.org/@1dot5/smonoenv/-/smonoenv-0.4.0.tgz","fileCount":99,"unpackedSize":181490,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1dot5%2fsmonoenv@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDseNcL2TxBWgtEZzIYkUwzkuCv32CHKroRbQ4x55Up2gIgFPoFdyXwU1AK0tcMQ7N0c/8vnfSfAy6CQy7XD1A5YNw="}]},"_npmUser":{"name":"mtyk15","email":"kondo@1dot5.jp"},"directories":{},"maintainers":[{"name":"mtyk15","email":"kondo@1dot5.jp"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/smonoenv_0.4.0_1776866352174_0.8487877206742434"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-24T07:08:47.489Z","modified":"2026-04-22T13:59:12.628Z","0.1.0":"2026-03-24T07:08:47.746Z","0.2.0":"2026-03-24T14:25:21.451Z","0.2.1":"2026-03-24T15:09:17.794Z","0.3.0":"2026-03-25T03:09:11.342Z","0.3.1":"2026-03-25T14:55:18.222Z","0.4.0":"2026-04-22T13:59:12.315Z"},"bugs":{"url":"https://github.com/1dot5/smonoenv/issues"},"license":"MIT","homepage":"https://github.com/1dot5/smonoenv#readme","repository":{"type":"git","url":"git+https://github.com/1dot5/smonoenv.git"},"description":"SOPS + age secret management CLI for monorepo environment files","maintainers":[{"name":"mtyk15","email":"kondo@1dot5.jp"}],"readme":"# smonoenv\n\nSecret management CLI for monorepos using SOPS + age.\n\nManage all app environment variables in a single `.env.monorepo.<env>` file and automatically distribute them to each app's `.env`. Encryption is handled by [SOPS](https://github.com/getsops/sops) + [age](https://github.com/FiloSottile/age).\n\n## Installation\n\n```bash\nnpm install -g @1dot5/smonoenv\n\n# or as a devDependency\nnpm install -D @1dot5/smonoenv\n\n# use npx\nnpx @1dot5/smonoenv\n```\n\n### Prerequisites\n\n```bash\nbrew install sops age\n```\n\n## Setup\n\n```bash\nsmonoenv setup\n```\n\nOn first run, this will:\n- Verify that `sops` / `age` are installed\n- Check that an age secret key is resolvable (project-local `.smonoenv/` or the global default path)\n- Print which key source will be used\n\n### age key resolution order\n\nsmonoenv looks up the age key in this priority (first match wins):\n\n1. `SOPS_AGE_KEY_<ENV>` process env (inline value) — env-scoped\n2. `SOPS_AGE_KEY_FILE_<ENV>` process env (path to file) — env-scoped\n3. `<project>/.smonoenv/keys.<env>.txt` — project-local, env-scoped\n4. `<project>/.smonoenv/keys.txt` — project-local, common\n5. `SOPS_AGE_KEY` process env (inline value)\n6. `SOPS_AGE_KEY_FILE` process env (path to file)\n7. `~/.config/sops/age/keys.txt` — global legacy default\n\n`<project>` is the nearest ancestor directory that contains a `.smonoenv/` folder, so any subdirectory inside the repo works.\n\n### Recommended: project-local keys (`.smonoenv/`)\n\nUsing a project-local key keeps each repository's age identity isolated, so a leak in one project doesn't grant access to others.\n\n```bash\n# First time, in the repo root:\nsmonoenv setup --project --create-key\n# → creates .smonoenv/keys.txt and adds it to .gitignore\n```\n\nFor environment-scoped keys (recommended for staging / production):\n\n```bash\nsmonoenv setup --project --env production --create-key\n# → creates .smonoenv/keys.production.txt\n```\n\nResulting layout:\n\n```\n<repo>/.smonoenv/\n  keys.txt                  # project common (gitignored)\n  keys.production.txt       # production-only (gitignored)\n  keys.staging.txt          # staging-only (gitignored)\n```\n\nNew team members receive the appropriate `keys.<env>.txt` from 1Password / your secret vault and drop it in `.smonoenv/`. `smonoenv setup --project` will acknowledge the file and confirm the public key.\n\n### Legacy / global key (single identity for all projects)\n\nStill supported for backwards compatibility. Drop a shared team key at:\n\n```\n~/.config/sops/age/keys.txt\n```\n\nOr generate a new one:\n\n```bash\nage-keygen -o ~/.config/sops/age/keys.txt\n```\n\nNote: the global key is used by every project on this machine. Prefer the project-local form above when working on more than one repository.\n\n### CI / Docker: env vars\n\nFor CI runners and containers, inject the key via env var. smonoenv will materialize inline values to a `mode 0600` tmpfile that is cleaned up after use.\n\n```bash\n# global (used for all envs)\nexport SOPS_AGE_KEY=\"$(cat keys.txt)\"\n\n# env-scoped (wins over the global ones)\nexport SOPS_AGE_KEY_PRODUCTION=\"$(cat keys.production.txt)\"\nexport SOPS_AGE_KEY_FILE_STAGING=/etc/secrets/keys.staging.txt\n```\n\nSee the Docker / Fargate example further below.\n\n## Monorepo env file format\n\n`.env.monorepo.<env>` files use section delimiters to define environment variables for each app:\n\n```dotenv\n#<<< ENV BEGIN PATH=apps/web\nDATABASE_URL=postgres://localhost:5432/mydb\nNEXT_PUBLIC_API_URL=http://localhost:3000\n#>>> ENV END\n\n#<<< ENV BEGIN PATH=apps/api\nDATABASE_URL=postgres://localhost:5432/mydb\nPORT=3001\nJWT_SECRET=dev-secret\n#>>> ENV END\n\n#<<< ENV BEGIN PATH=packages/shared\nAPI_KEY=test-key\n#>>> ENV END\n```\n\n`PATH=` takes a relative path from the repository root. Running sync generates a `.env` file at each path.\n\n### Environment-suffixed output\n\nAppend `:<env-name>` to the path (e.g. `PATH=apps/web:production`) to output as `.env.production`:\n\n```dotenv\n#<<< ENV BEGIN PATH=apps/web:production\nNEXT_PUBLIC_API_URL=https://api.example.com\n#>>> ENV END\n```\n\nOutputs to `apps/web/.env.production`\n\n## Commands\n\n### `smonoenv setup`\n\nVerify tooling and age key availability, and (optionally) generate a new key.\n\n```bash\nsmonoenv setup                              # Verify existing setup\nsmonoenv setup --create-key                 # Generate a new age key at the default path\nsmonoenv setup --project --create-key       # Generate .smonoenv/keys.txt (project-local)\nsmonoenv setup --project --env production --create-key\n                                            # Generate .smonoenv/keys.production.txt (env-scoped)\n```\n\n#### Options\n\n| Flag | Description |\n|------|-------------|\n| `--create-key` | Generate a new age key |\n| `--project` | Use project-local `.smonoenv/` instead of `~/.config/sops/age/` |\n| `--env <env>` | Scope the age key to a specific env (`.smonoenv/keys.<env>.txt`) |\n\n### `smonoenv local`\n\nOne-command local development setup. Internally runs `decrypt` then `sync`.\n\n```bash\nsmonoenv local\n```\n\nBehavior:\n1. Decrypts `.env.monorepo.local.sops` if it exists\n2. Distributes `.env.monorepo.local` to each app's `.env`\n3. If no encrypted file exists but `.env.monorepo.local.example` is found, copies it and provides guidance\n\n### `smonoenv encrypt <env>`\n\nEncrypt a plaintext file with SOPS.\n\n```bash\nsmonoenv encrypt local\nsmonoenv encrypt staging\nsmonoenv encrypt production\n```\n\n`.env.monorepo.<env>` → `.env.monorepo.<env>.sops`\n\nEncrypted files (`.sops`) can be safely committed to Git.\n\n### `smonoenv decrypt <env>`\n\nDecrypt a SOPS-encrypted file.\n\n```bash\nsmonoenv decrypt staging\nsmonoenv decrypt production\n```\n\n`.env.monorepo.<env>.sops` → `.env.monorepo.<env>`\n\n### `smonoenv edit <env>`\n\nEdit an encrypted file directly with `$EDITOR`. Automatically handles decryption before editing and re-encryption after.\n\n```bash\nsmonoenv edit staging\n```\n\n### `smonoenv run <env> -- <cmd> [args...]`\n\nContainer-friendly entrypoint. Decrypts `.env.monorepo.<env>.sops`, syncs app\n`.env` files, then `exec`s the given command. Use this as the Dockerfile\n`ENTRYPOINT` so your image never stores plaintext secrets.\n\n```bash\n# Fargate / K8s container startup\nsmonoenv run production -- node dist/main.js\n\n# Scope to a single app\nsmonoenv run production --app apps/slack-bot -- node dist/main.js\n\n# Load env into the current shell\neval \"$(smonoenv run staging --print-env --format shell)\"\n```\n\n#### Options\n\n| Flag | Description |\n|------|-------------|\n| `--app <path>` | Only sync this app path. Repeatable. |\n| `--clean` | Delete target `.env` files before syncing |\n| `--keep-artifacts` | Keep decrypted plaintext + age key tmpfile (debug only) |\n| `--no-sync` | Decrypt only (useful with `--print-env`) |\n| `--print-env` | Print env to stdout instead of exec |\n| `--format <fmt>` | `--print-env` format: `dotenv` (default), `shell`, `json` |\n| `--quiet` | Suppress informational output |\n\n#### age key resolution\n\n`run` uses the same 7-step resolution order as the rest of smonoenv (see\n[age key resolution order](#age-key-resolution-order) above), with `<ENV>`\nbound to the `run` argument:\n\n1. `SOPS_AGE_KEY_<ENV>` (inline) — env-scoped\n2. `SOPS_AGE_KEY_FILE_<ENV>` (path) — env-scoped\n3. `<project>/.smonoenv/keys.<env>.txt` — project-local, env-scoped\n4. `<project>/.smonoenv/keys.txt` — project-local, common\n5. `SOPS_AGE_KEY` (inline)\n6. `SOPS_AGE_KEY_FILE` (path)\n7. `~/.config/sops/age/keys.txt` — global legacy default\n\nInline values (`SOPS_AGE_KEY` / `SOPS_AGE_KEY_<ENV>`) are materialized to a\n`mode 0600` tmpfile and deleted on exit. If none resolve, `run` exits with\ncode `2`.\n\n#### Exit codes\n\n| Code | Meaning |\n|------|---------|\n| `0` | Success (or child exited `0`) |\n| `1` | decrypt / sync failure |\n| `2` | age key not found |\n| `127` | exec target not found |\n| `≥128` | child terminated by signal |\n\n### `smonoenv export <file> [--format <fmt>]`\n\nRead a `.env` file and print its variables to stdout in the requested format. Useful for piping env values into other tools or CI steps that expect a single-line string.\n\n```bash\nsmonoenv export apps/web/.env\nsmonoenv export apps/web/.env --format key-value\n```\n\n#### Options\n\n| Flag | Description |\n|------|-------------|\n| `--format <fmt>` | Output format. Currently only `key-value` (default), which prints `KEY=val,KEY2=val2` on a single line |\n\n### `smonoenv sync [env] [options]`\n\nDistribute monorepo env variables to each app's `.env`. Defaults to `local` if env is omitted.\n\n```bash\nsmonoenv sync              # distribute local\nsmonoenv sync staging      # distribute staging\n```\n\n#### Options\n\n| Flag | Description |\n|------|-------------|\n| `--check` | Check sync status only. Exits with code 1 if drift is detected (for CI) |\n| `--dry` | Dry run. No files are written |\n| `--clean` | Delete existing .env files before syncing |\n| `--quiet` | Suppress informational output |\n\n## Common workflows\n\n### New team member onboarding\n\n```bash\n# 1. Install tools\nbrew install sops age\n\n# 2. Grab the project's age key(s) from 1Password and drop them in\n#    .smonoenv/. For local dev you only need the common / local key.\nmkdir -p .smonoenv\n# cp ~/Downloads/keys.txt .smonoenv/keys.txt           # common\n# cp ~/Downloads/keys.production.txt .smonoenv/keys.production.txt\n\n# 3. Verify setup (will print which key source it picked up)\nsmonoenv setup --project\n\n# 4. Set up local environment\nsmonoenv local\n```\n\n### Adding or changing environment variables\n\n```bash\n# 1. Edit directly\nsmonoenv edit local\n\n# 2. Or edit the plaintext file and re-encrypt\nsmonoenv decrypt local\nvi .env.monorepo.local\nsmonoenv encrypt local\n\n# 3. Distribute to apps\nsmonoenv sync\n```\n\n### CI sync check\n\n```yaml\n# GitHub Actions\n- run: smonoenv decrypt local\n- run: smonoenv sync --check\n```\n\n### Generating .env files in GitHub Actions\n\nWhen your CI/CD pipeline needs actual `.env` files (e.g. Next.js builds, E2E tests, Docker builds), register the age secret key as a GitHub Actions Secret and use `decrypt` → `sync` to generate them.\n\n#### 1. Register the age secret key\n\n```bash\n# Project-local, env-scoped key (recommended)\ncat .smonoenv/keys.production.txt\n\n# Legacy global key\ncat ~/.config/sops/age/keys.txt\n```\n\nGo to your GitHub repository's **Settings → Secrets and variables → Actions** and add it as `SOPS_AGE_KEY_PRODUCTION` (or `SOPS_AGE_KEY` for a single shared identity). Paste the entire key file contents.\n\n#### 2. Workflow example\n\n```yaml\nname: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: pnpm/action-setup@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n\n      - name: Install sops and age\n        run: |\n          curl -Lo /usr/local/bin/sops https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64\n          chmod +x /usr/local/bin/sops\n          curl -Lo age.tar.gz https://github.com/FiloSottile/age/releases/download/v1.2.1/age-v1.2.1-linux-amd64.tar.gz\n          tar xf age.tar.gz\n          mv age/age /usr/local/bin/\n          mv age/age-keygen /usr/local/bin/\n\n      - name: Inject age key (env-scoped)\n        run: |\n          set -euo pipefail\n          test -n \"${SOPS_AGE_KEY_STAGING:-}\" || (echo \"SOPS_AGE_KEY_STAGING is empty\"; exit 1)\n        env:\n          SOPS_AGE_KEY_STAGING: ${{ secrets.SOPS_AGE_KEY_STAGING }}\n\n      - run: pnpm install --frozen-lockfile\n\n      - name: Generate .env files\n        env:\n          SOPS_AGE_KEY_STAGING: ${{ secrets.SOPS_AGE_KEY_STAGING }}\n        run: |\n          npx smonoenv decrypt staging\n          npx smonoenv sync staging\n\n      - run: pnpm build\n      - run: pnpm test\n```\n\n#### 3. Self-hosted runners\n\nIf `sops` / `age` are pre-installed on self-hosted runners, only the key setup step is needed:\n\n```yaml\nsteps:\n  - uses: actions/checkout@v4\n\n  - name: Generate .env files\n    env:\n      SOPS_AGE_KEY_PRODUCTION: ${{ secrets.SOPS_AGE_KEY_PRODUCTION }}\n    run: |\n      npx smonoenv decrypt production\n      npx smonoenv sync production\n\n  - run: pnpm build\n```\n\nThe inline value in `SOPS_AGE_KEY_PRODUCTION` is materialized to a `mode 0600` tmpfile and cleaned up when the command exits; no file setup step is required.\n\n#### 4. Per-environment configuration\n\nCombine with GitHub Actions `environment` to inject only the key scoped to that deploy target. Each environment holds a different `SOPS_AGE_KEY_*` secret:\n\n```yaml\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}\n    steps:\n      # ... checkout, setup omitted ...\n\n      - name: Generate .env files\n        env:\n          # Each GitHub Environment provides its own *_<ENV> secret.\n          SOPS_AGE_KEY_PRODUCTION: ${{ secrets.SOPS_AGE_KEY_PRODUCTION }}\n          SOPS_AGE_KEY_STAGING: ${{ secrets.SOPS_AGE_KEY_STAGING }}\n        run: |\n          ENV_NAME=${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}\n          npx smonoenv decrypt $ENV_NAME\n          npx smonoenv sync $ENV_NAME\n```\n\nBecause smonoenv picks the `SOPS_AGE_KEY_<ENV>` matching `$ENV_NAME`, the staging job cannot accidentally decrypt production secrets even if both env vars are set.\n\n#### 5. Security: Cleanup on self-hosted runners\n\nGitHub-hosted runners are destroyed after each job, but files persist on self-hosted runners. Use `always()` to clean up regardless of success or failure:\n\n```yaml\njobs:\n  build:\n    runs-on: [self-hosted]\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Generate .env files\n        env:\n          SOPS_AGE_KEY_PRODUCTION: ${{ secrets.SOPS_AGE_KEY_PRODUCTION }}\n        run: |\n          npx smonoenv decrypt production\n          npx smonoenv sync production\n\n      - run: pnpm build\n      - run: pnpm test\n\n      - name: Cleanup secrets\n        if: always()\n        run: |\n          rm -f .env.monorepo.*\n          find . -name '.env' -not -path './node_modules/*' -delete\n          find . -name '.env.*' -not -name '.env.example' -not -path './node_modules/*' -delete\n```\n\nInline `SOPS_AGE_KEY_<ENV>` values are materialized to a tmpfile under `$TMPDIR` and unlinked when smonoenv exits, so no explicit key cleanup is required. Only the decrypted mono file and generated `.env` files need post-run cleanup on self-hosted runners.\n\n## Running in Docker / Fargate / Kubernetes\n\nFor runtimes that start a container and expect a single secret source, use\n`smonoenv run` as the entrypoint. It decrypts at container start, writes the\n`.env` files, deletes the plaintext mono file, and `exec`s your app.\n\nThe only secret the platform needs to inject is the **age private key**.\nAll individual values (DB URL, API keys, tokens) stay in\n`.env.monorepo.<env>.sops`, which is shipped inside the image.\n\n### Dockerfile template\n\n```dockerfile\nFROM node:24-bookworm-slim AS base\nWORKDIR /app\n\nRUN apt-get update \\\n && apt-get install -y --no-install-recommends ca-certificates curl age \\\n && SOPS_VER=3.9.4 && ARCH=\"$(dpkg --print-architecture)\" \\\n && curl -sSL -o /usr/local/bin/sops \\\n      \"https://github.com/getsops/sops/releases/download/v${SOPS_VER}/sops-v${SOPS_VER}.linux.${ARCH}\" \\\n && chmod +x /usr/local/bin/sops \\\n && rm -rf /var/lib/apt/lists/*\n\n# Install smonoenv and build your app as usual\n# ...\n\n# Ship encrypted sops files in the image (.dockerignore excludes plaintext)\nCOPY .env.monorepo.staging.sops .env.monorepo.production.sops ./\n\nENTRYPOINT [\"npx\", \"-y\", \"@1dot5/smonoenv\", \"run\", \"production\", \"--\"]\nCMD [\"node\", \"dist/main.js\"]\n```\n\n### ECS / Fargate Task Definition\n\n```json\n{\n  \"environment\": [\n    { \"name\": \"APP_ENV\", \"value\": \"production\" },\n    { \"name\": \"NODE_ENV\", \"value\": \"production\" }\n  ],\n  \"secrets\": [\n    {\n      \"name\": \"SOPS_AGE_KEY_PRODUCTION\",\n      \"valueFrom\": \"arn:aws:secretsmanager:...:SOPS_AGE_KEY_PRODUCTION::\"\n    }\n  ]\n}\n```\n\nUse `SOPS_AGE_KEY_<ENV>` (env-scoped) so a production task can never load a\nstaging key by accident. If you only have a single identity for the project,\n`SOPS_AGE_KEY` works as a fallback.\n\nOnly the age secret lives in Secrets Manager. Individual app env vars are\nrecovered by `smonoenv run` at container start from the `.sops` file.\n\n### Kubernetes\n\n```yaml\nenv:\n  - name: SOPS_AGE_KEY_PRODUCTION\n    valueFrom:\n      secretKeyRef:\n        name: smonoenv-age-keys\n        key: keys.production.txt\n```\n\n### `.dockerignore`\n\nExclude plaintext env files and the age key from the build context:\n\n```\n.env\n.env.local\n**/.env\n**/.env.local\n.env.monorepo.local\n.env.monorepo.staging\n.env.monorepo.production\n.smonoenv/keys.txt\n.smonoenv/keys.*.txt\nkeys.txt\n*.age-key\n```\n\nThe `.sops` files are safe to include — they are encrypted.\n\n### Security notes\n\n- `.env.monorepo.<env>.sops` lives inside your image. Keep the registry\n  private and restrict pull permissions.\n- `smonoenv run` deletes the decrypted mono file and the age key tmpfile\n  before `exec`. The `.env` files it wrote remain inside the container.\n- Use `--keep-artifacts` only for debugging — it leaves plaintext on disk.\n- `smonoenv run` forwards `SIGINT` / `SIGTERM` / `SIGHUP` to the child.\n  For PID 1 zombie reaping, combine with `tini` (`docker run --init`).\n\n## Environments\n\n| Name | Purpose |\n|------|---------|\n| `local` | Local development |\n| `staging` | Staging |\n| `production` | Production |\n\n## File structure\n\n```\n.env.monorepo.local           # Plaintext (recommended in .gitignore)\n.env.monorepo.local.sops      # Encrypted (tracked in Git)\n.env.monorepo.staging         # Plaintext\n.env.monorepo.staging.sops    # Encrypted\n.env.monorepo.production      # Plaintext\n.env.monorepo.production.sops # Encrypted\n.sops.yaml                    # SOPS encryption config\n.smonoenv/keys.txt            # Project-local age key (optional, gitignored)\n.smonoenv/keys.<env>.txt      # Env-scoped age key (optional, gitignored)\n```\n\n## Library usage\n\nCan also be imported directly from Node.js:\n\n```typescript\nimport { parseMono, normalize, parseEnvFile } from \"@1dot5/smonoenv\";\nimport { sync, decrypt, encrypt } from \"@1dot5/smonoenv\";\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}