@ciqol/resenv 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +103 -161
  2. package/package.json +7 -2
package/README.md CHANGED
@@ -1,33 +1,32 @@
1
1
  # @ciqol/resenv
2
2
 
3
- **Replace references already in `process.env` with their resolved values at
4
- application startup.** Your existing loader, such as Nx, chooses the dotenv
5
- files. resenv resolves the resulting environment before application code runs.
6
-
7
- **Doppler is the only built-in cloud provider.** A value such as
8
- `doppler://my-project/dev/API_TOKEN` tells resenv which secret to fetch. The
9
- application then reads the actual value from `process.env.API_TOKEN`.
10
-
11
- ```text
12
- Nx or another loader sets process.env
13
- |
14
- resenv fetches the referenced values and replaces them in memory
15
- |
16
- Application starts with ordinary environment values
17
- ```
3
+ [![🏗️ CI](https://github.com/ciqol/resenv/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ciqol/resenv/actions/workflows/ci.yml)
4
+ [![NPM Version](https://img.shields.io/npm/v/@ciqol/resenv)](https://www.npmjs.com/package/@ciqol/resenv)
5
+ [![NPM Downloads](https://img.shields.io/npm/dm/@ciqol/resenv)](https://www.npmjs.com/package/@ciqol/resenv)
18
6
 
19
- Resolution happens once at startup. It does not watch the provider for changes;
20
- restart the application to fetch updated values. No filename is passed to
21
- resenv, and dotenv files are never rewritten.
7
+ **Resolve secret references already in `process.env` before application code
8
+ runs.** Your existing loader, such as Nx, chooses and loads dotenv files;
9
+ resenv replaces references in memory. It never rewrites those files.
22
10
 
23
- Requires Node.js 20 or later. The package is experimental and remains in initial
24
- development; its API may change between 0.x releases.
11
+ Doppler is the only built-in cloud provider. For example,
12
+ `API_TOKEN=doppler://my-project/dev/API_TOKEN` becomes the fetched secret in
13
+ `process.env.API_TOKEN`. Resolution happens once at startup; restart the
14
+ application to fetch updated values.
25
15
 
26
- Each npm package includes this README for the version you installed. The
27
- [repository README](https://github.com/ciqol/resenv#readme) and linked online
28
- guides follow current development and may describe newer versions.
16
+ Requires Node.js 20 or later. The package has no runtime npm dependencies and
17
+ is experimental; its API may change between 0.x releases.
18
+
19
+ ## When to choose resenv
29
20
 
30
- Background: [A new Git worktree shouldn't mean another copy of your secrets](https://dev.to/titanron/a-new-git-worktree-shouldnt-mean-another-copy-of-your-secrets-424).
21
+ Choose resenv when your Node.js application already loads its environment through
22
+ Nx, dotenv or another loader, and you want to resolve individual Doppler secret
23
+ references before application code runs. Each reference selects its own project
24
+ and config, so one application can use secrets from multiple Doppler configs.
25
+
26
+ For Git worktrees, keep reviewed reference configuration and the startup command
27
+ with the project so each checkout receives them without copying plaintext secrets.
28
+ Each worktree still needs the application's usual setup and configured Doppler
29
+ access. Your existing loader remains responsible for selecting and loading files.
31
30
 
32
31
  ## Install
33
32
 
@@ -37,84 +36,60 @@ Install in your application project:
37
36
  npm install --save-exact @ciqol/resenv
38
37
  ```
39
38
 
40
- This records the version selected by npm's `latest` tag in your project's
41
- manifest and lockfile.
42
-
43
- The CLI examples below use `npx --no-install` to run the copy installed in your
44
- project. In an npm script, use `resenv` directly.
39
+ The CLI examples use `npx --no-install` to run that installed copy. In an npm
40
+ script, use `resenv` directly.
45
41
 
46
42
  ## Use with Doppler
47
43
 
48
- Doppler manages application secrets. If you're new to it, start with
49
- [Doppler's introduction](https://docs.doppler.com/docs/start).
50
-
51
- ### 1. Authenticate locally
44
+ ### 1. Authenticate
52
45
 
53
- Follow the [official Doppler CLI installation guide](https://docs.doppler.com/docs/cli#installation)
54
- for your operating system, then log in from your application directory:
46
+ Install the [official Doppler CLI](https://docs.doppler.com/docs/cli#installation),
47
+ then log in from your application directory:
55
48
 
56
49
  ```sh
57
50
  doppler login
58
51
  ```
59
52
 
60
- Each developer authenticates with their own Doppler access.
53
+ resenv uses `DOPPLER_TOKEN` when supplied; otherwise it reuses the CLI login
54
+ selected for the application's working directory. Doppler owns login, logout
55
+ and credential storage. Missing credentials or access stop startup without
56
+ prompting for login.
61
57
 
62
- > [!WARNING]
63
- > Authentication and credential storage depend on the Doppler CLI's implementation
64
- > and may vary by platform or CLI version. resenv only reuses its existing token;
65
- > it does not manage login, logout or credential storage, or access the OS
66
- > credential store directly.
58
+ For CI and other unattended environments, supply a suitably scoped
59
+ `DOPPLER_TOKEN` through your runner's secret facility; no CLI is needed.
60
+ See [authentication and platform details](https://github.com/ciqol/resenv/blob/main/docs/doppler.md)
61
+ for supported CLI locations and lookup rules.
67
62
 
68
- At startup, resenv uses `DOPPLER_TOKEN` when supplied; otherwise it reads the
69
- existing Doppler CLI token for the application's working directory. It then
70
- fetches only referenced secrets through the HTTPS API. Missing credentials or
71
- access stop startup without opening a browser or prompting for login.
63
+ ### 2. Declare references
72
64
 
73
- For CI and other unattended environments, supply `DOPPLER_TOKEN` through your
74
- runner's secret facility; the CLI is not needed in that mode. See
75
- [Doppler authentication and platform details](https://github.com/ciqol/resenv/blob/main/docs/doppler.md), including
76
- migration from the earlier resenv-managed login.
77
-
78
- Need another secrets provider, operating system or CLI installation method?
79
- [Request support](https://github.com/ciqol/resenv/issues/new) with your use case
80
- and platform details.
81
-
82
- ### 2. Declare references in the environment your app already loads
83
-
84
- For example, in an Nx application's `.env.serve`:
65
+ In the environment your application already loads, such as Nx's `.env.serve`:
85
66
 
86
67
  ```dotenv
87
68
  API_TOKEN=doppler://my-project/dev/API_TOKEN
88
69
  PORT=3000
89
70
  ```
90
71
 
91
- The reference contains the Doppler project, config and secret name. This is
92
- resenv's reference syntax. Plain values such as `PORT` pass through unchanged.
93
- The reference itself grants no access: the authenticated identity needs
94
- permission to read that secret. Doppler references need no dotenvx private key.
72
+ Each reference names a project, config and secret. The authenticated identity
73
+ must have access to that secret. Plain values such as `PORT` pass through.
95
74
 
96
- ### 3. Preload resenv on the application process
75
+ ### 3. Preload on the application process
97
76
 
98
- Once the environment has been loaded:
77
+ After your loader has populated the environment:
99
78
 
100
79
  ```sh
101
80
  node --require @ciqol/resenv/resolve app.cjs
102
81
  node --import @ciqol/resenv/resolve app.mjs
103
82
  ```
104
83
 
105
- The preload replaces `process.env.API_TOKEN` before the application and its
106
- imports execute. It infers Doppler from the value, so no provider flag is needed.
107
-
84
+ The preload replaces references before the application and its imports execute.
108
85
  Alternatively, wrap a command whose environment is already populated:
109
86
 
110
87
  ```sh
111
88
  npx --no-install resenv run -- node app.mjs
112
89
  ```
113
90
 
114
- The wrapper supplies resolved values to the child process. It inherits
115
- stdin/stdout/stderr, preserves exit status and forwards SIGINT, SIGTERM and
116
- SIGHUP to that child. On POSIX, the child runs in its own session so terminal
117
- signals reach it only through the wrapper.
91
+ The wrapper passes the resolved environment to its child, inherits terminal I/O,
92
+ preserves exit status and forwards termination signals.
118
93
 
119
94
  ## Nx integration
120
95
 
@@ -137,70 +112,70 @@ Put the preload on the application's target, after Nx loads its dotenv files:
137
112
  }
138
113
  ```
139
114
 
140
- Run `nx serve app`. Nx selects `.env.serve` and any applicable overrides,
141
- then starts the application with resenv. Use your application's existing path
142
- and target options. For an existing `@nx/js:node` target, add:
115
+ Run `nx serve app`, using your application's existing path and target options.
116
+ For an existing `@nx/js:node` target, add:
143
117
 
144
118
  ```json
145
119
  "runtimeArgs": ["--require", "@ciqol/resenv/resolve"]
146
120
  ```
147
121
 
148
- Preloading the outer `nx` process runs too early. A shell-wide `NODE_OPTIONS`
149
- setting also affects unrelated processes. Keep the preload on the application
150
- target. If a build needs secrets, its process needs its own integration after
151
- its environment is loaded; values embedded in build output remain in that output.
152
-
153
- ## Resolution behavior
154
-
155
- - Only the enabled reference forms are resolved. Ordinary values pass through.
156
- - Doppler fetches only the named secrets; repeated references share one request
157
- within a resolution pass. Values are not cached on disk.
158
- - All lookups must succeed before the environment is changed or a child starts.
159
- Missing access, malformed references and provider failures stop startup.
160
- - Fetched values stay literal, even if they contain `$()` or another reference.
161
- - The canonical preload leaves shell expressions literal. Executing commands
162
- requires the separate opt-in described below.
163
- - Application code can read its resolved environment. Access to the same
164
- credentials also permits other code or sessions to retrieve those secrets.
165
-
166
- See the current [Doppler limits](https://github.com/ciqol/resenv/blob/main/docs/doppler.md#resolution-and-security-boundaries)
167
- and [security guidance](https://github.com/ciqol/resenv/blob/main/SECURITY.md).
122
+ Preloading the outer `nx` process runs too early. Keep the preload on each
123
+ application or build process that needs secrets, after its environment is loaded.
124
+ Secrets embedded in build output remain in that output.
168
125
 
169
126
  ## Library API
170
127
 
171
- Call the library after your loader has populated the environment, before
172
- importing modules that read configuration:
128
+ Call the synchronous resolver after loading the environment, before importing
129
+ modules that read configuration:
173
130
 
174
131
  ```js
175
132
  import { resolve } from '@ciqol/resenv'
176
133
 
177
134
  const childEnv = resolve()
178
-
179
- // Also replace values in process.env; the returned copy can be ignored.
180
135
  resolve(process.env, { replace: true })
181
136
  ```
182
137
 
183
- `resolve(env = process.env, options = {})` always returns a new resolved
184
- environment. `replace` defaults to `false`, leaving the input unchanged. With
185
- `replace: true`, it also replaces values in the input after every lookup succeeds;
186
- the return value is still an independent copy.
138
+ `resolve(env = process.env, options = {})` always returns an independent
139
+ resolved copy. CommonJS uses `require('@ciqol/resenv')`.
140
+
141
+ | Option | Default | Behavior |
142
+ | --- | --- | --- |
143
+ | `replace` | `false` | Also replace input values after all lookups succeed. Requires `process.env` or writable data properties. |
144
+ | `commands` | `false` | Execute whole-value `$(command)` expressions through POSIX `/bin/sh`. |
145
+ | `commandTimeoutMs` | `30000` | Per-command timeout in milliseconds; a positive integer up to `2147483647`. |
146
+
147
+ Doppler is inferred from original values. Without references, the result is an
148
+ unchanged copy and no credentials are accessed. Library failures throw
149
+ `ResenvError`; its `code` identifies the failure. CLI and preload failures exit
150
+ with a safe message.
151
+
152
+ ## Resolution and safety
153
+
154
+ - All lookups must succeed before the input environment changes or a child starts.
155
+ - Only named secrets are fetched; repeated references share a request within a
156
+ resolution pass. Values are not cached on disk.
157
+ - Fetched values stay literal, even if they contain another reference or `$()`.
158
+ - Shell commands require explicit opt-in and run with your permissions. Failed
159
+ resolution cannot undo their file or network effects.
160
+ - Use one preload mode per process. Combining modes or a preload with the CLI
161
+ fails with `CONFLICTING_PRELOADS`. Avoid additional resolvers in `NODE_OPTIONS`.
162
+ - Application code can read its secrets and any credentials supplied to it.
163
+
164
+ See [Doppler limits and compatibility](https://github.com/ciqol/resenv/blob/main/docs/doppler.md)
165
+ and [security guidance](https://github.com/ciqol/resenv/blob/main/SECURITY.md).
166
+
167
+ ## Optional commands
187
168
 
188
- Providers are inferred from each original value, just as with `/resolve` and
189
- `run`. Doppler is the only supported cloud provider today. No options are
190
- required: without references, the result is an unchanged copy and no credentials
191
- are accessed. An environment can contain several references, including different
192
- Doppler projects/configs. Each value selects its own source; a command can supply
193
- a Doppler token. Commands still require
194
- `commands: true`. Returned secret values are never interpreted again.
169
+ Enable commands alongside Doppler references through the library:
195
170
 
196
- CommonJS uses the same API through
197
- `require('@ciqol/resenv')`. Both formats share the `ResenvError` class, whose
198
- `code` identifies the failure. CLI and preload failures exit with a safe message.
171
+ ```js
172
+ const env = resolve({ BUILD_LABEL: '$(printf local)' }, { commands: true })
173
+ ```
199
174
 
200
- Using `replace: true` requires `process.env` or writable data properties. A read-only
201
- property, setter or Proxy target fails before any replacement is committed.
202
- Keep the default copy mode for inputs that should remain unchanged. The Node
203
- preloads enable in-place replacement so application imports see resolved values.
175
+ Or use `resenv run --commands -- ...`, or preload
176
+ `@ciqol/resenv/resolve-commands`. The canonical `/resolve` preload leaves shell
177
+ expressions literal. Only use command mode with trusted configuration; see
178
+ [command limits](https://github.com/ciqol/resenv/blob/main/SECURITY.md#command-execution).
204
179
 
205
180
  ## Debug startup
206
181
 
@@ -208,55 +183,22 @@ preloads enable in-place replacement so application imports see resolved values.
208
183
  NODE_DEBUG=ciqol:resenv node --require @ciqol/resenv/resolve app.mjs
209
184
  ```
210
185
 
211
- For the Nx target above, use `NODE_DEBUG=ciqol:resenv nx serve app`. Set `NODE_DEBUG`
212
- when launching Node, before dotenv loading. Diagnostics use `node:util`'s
213
- `debuglog` and are off by default:
186
+ For Nx, use `NODE_DEBUG=ciqol:resenv nx serve app`. Enable it when launching Node,
187
+ before dotenv loading. Diagnostics omit secrets, tokens, commands and provider
188
+ responses. `NODE_DEBUG=child_process` or `NODE_DEBUG=*` can expose credentials;
189
+ resenv blocks its subprocess operations in that mode. See
190
+ [diagnostic details](https://github.com/ciqol/resenv/blob/main/SECURITY.md#diagnostics).
214
191
 
215
- ```text
216
- CIQOL:RESENV 12345: Replaced secret value for "API_TOKEN".
217
- CIQOL:RESENV 12345: Done: replaced 1 secret value(s).
218
- ```
219
-
220
- With no matching references, the only message is `Nothing to handle.` Failures
221
- include a safe error and completion summary. Logs omit values, tokens, command
222
- text and provider responses; no log files are created.
223
-
224
- `NODE_DEBUG=child_process` (including `NODE_DEBUG=*`) makes Node print spawn
225
- options containing credentials. resenv blocks its subprocess operations with
226
- `UNSAFE_DEBUG_LOGGING` in that mode. Use `NODE_DEBUG=ciqol:resenv` instead.
192
+ ## Further reading
227
193
 
228
- ## Optional commands
194
+ Each npm package includes this README for its installed version. The
195
+ [repository README](https://github.com/ciqol/resenv#readme) and linked online
196
+ guides follow current development and may describe newer versions. GitHub
197
+ [releases](https://github.com/ciqol/resenv/releases) contain version change notes.
229
198
 
230
- - **Opt-in shell commands:** `/resolve-commands` resolves complete `$(command)`
231
- values through `/bin/sh`. The canonical `/resolve` preload leaves these values
232
- literal for both `--require` and `--import`, including when `RESENV_COMMANDS`
233
- is set in the environment. `resenv run --commands -- ...` enables commands
234
- alongside inferred Doppler references, as does the command preload. Use only
235
- trusted configuration; commands run with your permissions. A timeout limits
236
- execution time, but cannot undo file changes, network requests or other effects.
237
- Leave this option disabled when the environment can contain untrusted input.
238
- Commands have closed stdin, a 30-second timeout and a 64 KiB combined output
239
- limit. Trailing LF characters are removed; fetched output stays literal.
240
- Embedded forms such as `prefix$(command)suffix` are unsupported.
241
-
242
- Values beginning with `encrypted:` are left unchanged. resenv resolves cloud
243
- references; dotenvx decryption is outside its scope. The package has no runtime
244
- npm dependencies.
245
-
246
- `/resolve-doppler` is an equivalent name for `/resolve`; `run --doppler` remains
247
- accepted. New integrations should use the canonical entry points above.
248
-
249
- Use one preload mode per process. Combining `/resolve` and `/resolve-commands`
250
- or combining a preload with `resenv run` fails with `CONFLICTING_PRELOADS` to
251
- prevent reinterpreting fetched values. Do not configure a second resolver in
252
- the child process through `NODE_OPTIONS` or other launch hooks.
253
- For both modes together, use `resenv run --commands -- ...` or one library call
254
- with `{ commands: true }`.
255
-
256
- ## Contributing
257
-
258
- For checkout setup, building, testing, trying local changes in another project
259
- and submitting fixes, see [CONTRIBUTING.md](https://github.com/ciqol/resenv/blob/main/CONTRIBUTING.md).
199
+ - [Contributing, development and tests](https://github.com/ciqol/resenv/blob/main/CONTRIBUTING.md)
200
+ - [Request provider or platform support](https://github.com/ciqol/resenv/issues/new)
201
+ - [Background: A new Git worktree shouldn't mean another copy of your secrets](https://dev.to/titanron/a-new-git-worktree-shouldnt-mean-another-copy-of-your-secrets-424)
260
202
 
261
203
  ## License
262
204
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciqol/resenv",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Replace environment references with their resolved values at application startup",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -20,7 +20,12 @@
20
20
  "environment",
21
21
  "dotenv",
22
22
  "secrets",
23
- "nx"
23
+ "nx",
24
+ "doppler",
25
+ "secret-references",
26
+ "environment-variables",
27
+ "nodejs",
28
+ "git-worktree"
24
29
  ],
25
30
  "main": "./index.cjs",
26
31
  "module": "./index.mjs",