configre 2.1.4 → 2.1.6

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.
package/README.md CHANGED
@@ -1,151 +1,126 @@
1
- # 🔧 Configre
1
+ # Configre
2
2
 
3
- Welcome to Configre, the coolest way to manage your project's configuration with a twist of personality based on your environment! Whether you're in development, testing, or production, Configre seamlessly adjusts to your project's needs by loading specific configurations tailored to each environment. Say goodbye to manual config tweaks and hello to automatic, hassle-free setup! 🚀
3
+ Define your defaults once. Let each environment describe what changes. Keep secrets alongside the settings they belong to.
4
4
 
5
- ## ✨ Features
5
+ Configre loads a base configuration, merges in a host or profile, and returns a plain JavaScript object. Configuration lives in `.cjs` files, so you get nested objects, comments, and ordinary JavaScript. Optional secret files follow the same structure and can be shared with your servers through encrypted files in Git.
6
6
 
7
- - **Environment-Specific Configurations**: Automatically loads configurations based on the hostname or a custom profile forced via `--config=<profile>` CLI argument.
8
- - **Fallback to Defaults**: Uses a default configuration as a baseline, ensuring your application always has the necessary settings.
9
- - **Easy Integration**: A simple setup process that integrates effortlessly into any project.
10
- - **Support for `.cjs` Config Files Only**: Config files must use the `.cjs` extension. This allows dynamic configuration values and comments, and works the same in both CommonJS and ESM projects.
7
+ ## Quick start
11
8
 
12
- ## 🌟 Getting Started
9
+ Requires **Node.js 22.13 or later**.
13
10
 
14
- To get started with Configre, follow these steps:
15
-
16
- 1. **Install the Library**
17
-
18
- Configre requires **Node.js 22.13 or later**. Add it to your project:
19
-
20
- ```bash
21
- npm install configre --save
22
- ```
23
-
24
- > You can also add Configre as a skill for AI agentic development:
25
- > ```bash
26
- > npx skills add https://github.com/clasen/Configre --skill configre
27
- > ```
28
-
29
- 2. **Setup Your Configuration Files**
30
-
31
- Organize your configuration files within a directory (e.g., `config`). Create a default configuration file and environment-specific files as needed.
32
-
33
- - `index.cjs`: Your default configuration.
34
- - `[hostname].cjs`: Override configurations for specific hosts.
35
- - `[hostname].dev.cjs`: Development-specific configurations.
36
-
37
- > **Note:** Config files must always use the `.cjs` extension; `.js` config files are not accepted. That way they work in both CommonJS and ESM projects and you can use `module.exports` in them.
38
-
39
- 3. **Use Configre in Your Project**
40
-
41
- Import and use Configre to load your configurations. The configuration path is required:
11
+ ```bash
12
+ npm install configre
13
+ ```
42
14
 
43
- ```javascript
44
- import Configre from "configre";
45
- import { join } from "node:path";
15
+ Create `config/index.cjs` with your defaults:
46
16
 
47
- const cfg = Configre(join(import.meta.dirname, "config"));
48
- console.log(cfg.db); // Access your db configuration
49
- ```
17
+ ```javascript
18
+ module.exports = {
19
+ db: {
20
+ host: "localhost",
21
+ port: 5432
22
+ },
23
+ api: {
24
+ url: "https://api.example.com"
25
+ }
26
+ };
27
+ ```
50
28
 
51
- Prefer an absolute path anchored to the module, as above. Avoid deriving it from `process.cwd()` (for example, `path.join(process.cwd(), "config")`), because that can point to a different location depending on where the process is started.
29
+ Load it from `app.mjs`, next to the `config` directory:
52
30
 
53
- Configre is a native ES module. CommonJS consumers keep the same synchronous API, without accessing `.default`:
31
+ ```javascript
32
+ import Configre from "configre";
33
+ import { join } from "node:path";
54
34
 
55
- ```javascript
56
- const Configre = require("configre");
57
- const { join } = require("node:path");
35
+ const cfg = Configre(join(import.meta.dirname, "config"));
36
+ console.log(cfg.db.port); // 5432
37
+ ```
58
38
 
59
- const cfg = Configre(join(__dirname, "config"));
60
- ```
39
+ ```bash
40
+ node app.mjs
41
+ ```
61
42
 
62
- Both module systems share the same implementation. Existing configuration files stay in `.cjs` format; Configre uses Node's `createRequire` only to load these files synchronously.
43
+ The configuration path is required. Anchor it to your module so starting the process from another directory does not change which configuration it loads. Avoid building this path from `process.cwd()`.
63
44
 
64
- ## 📚 Example
45
+ Configre is a native ES module. CommonJS consumers can also use the synchronous API: `require("configre")` returns the function directly. Configuration files use `.cjs` and `module.exports` in either kind of project.
65
46
 
66
- Imagine you have the following structure in your `config` directory:
47
+ ## Configuration that builds on defaults
67
48
 
68
- - `index.cjs`: Contains default settings.
69
- - `myhostname.cjs`: Contains overrides for the host named `myhostname`.
49
+ An environment should describe what changes. Everything else comes from the base.
70
50
 
71
- Your `index.cjs` might look like this:
51
+ Add `config/production.cjs`:
72
52
 
73
53
  ```javascript
74
54
  module.exports = {
75
55
  db: {
76
- host: 'localhost',
77
- user: 'myuser',
78
- password: 'mypassword'
79
- },
80
- api: {
81
- key: 'development-api-key'
56
+ host: "db.internal",
57
+ ssl: true
82
58
  }
83
- }
59
+ };
60
+ ```
61
+
62
+ Select that profile:
63
+
64
+ ```bash
65
+ node app.mjs --config=production
84
66
  ```
85
67
 
86
- And your `myhostname.cjs`:
68
+ The resulting configuration contains:
87
69
 
88
70
  ```javascript
89
- module.exports = {
71
+ {
90
72
  db: {
91
- user: 'john',
92
- password: 'johns-password'
73
+ host: "db.internal",
74
+ port: 5432,
75
+ ssl: true
76
+ },
77
+ api: {
78
+ url: "https://api.example.com"
93
79
  }
94
80
  }
95
81
  ```
96
82
 
97
- Configre merges these configurations based on your environment, making your app adaptable and easier to manage.
83
+ `host` is overwritten, `port` is inherited, and `ssl` is added. The `api` object stays intact. A profile can extend nested objects without repeating their other fields.
98
84
 
99
- ## 🎯 Forcing a hostname / config
85
+ ### Selecting a profile
100
86
 
101
- By default Configre uses the machine’s hostname to choose the config file (e.g. `config/<hostname>.cjs`). You can force which hostname or profile to use with the `--config=<hostname>` CLI argument:
87
+ By default, Configre uses the machine's hostname. A machine named `web-01` loads `config/web-01.cjs`. Pass `--config=<profile>` to choose a name explicitly; the flag can appear alongside other application arguments.
102
88
 
103
- ```bash
104
- node demo.js --config=staging
105
- node demo.js --config=production
106
- node demo.js --port=3000 --config=myhost --debug
107
- ```
89
+ | File | Role |
90
+ | --- | --- |
91
+ | `index.cjs` | Base configuration |
92
+ | `<profile>.cjs` | Additions and overrides for the selected profile |
93
+ | `<profile>.dev.cjs` | Preferred over `<profile>.cjs` when present |
108
94
 
109
- This loads `config/staging.cjs` (or `config/staging.dev.cjs`) instead of the one for the actual hostname. The `--config=` flag can appear anywhere in the command and works alongside other arguments.
95
+ If no profile file exists, Configre uses the base configuration. When `production.dev.cjs` exists, it is selected **instead of** `production.cjs`; those two files do not merge. This selection depends on the files present, not on `NODE_ENV`.
110
96
 
111
- ## 🔐 Shared secrets: follow the demo
97
+ Objects merge recursively. Later values override matching fields. Arrays merge **by index**, rather than being replaced as a whole: `["a", "b"]` followed by `["c"]` becomes `["c", "b"]`.
112
98
 
113
- The [secrets demo](demo/secrets/) shows how to keep API keys out of your public configuration and share them with a server through Git. Use one checkout as the **administrator**, where you edit secrets, and another as the **server**, which reads the encrypted values.
99
+ ## Secrets, organized like your configuration
114
100
 
115
- Run the commands below from the repository root. In [demo/secrets/demo.js](demo/secrets/demo.js), the configuration is loaded with:
101
+ Database credentials belong under `db`. API keys belong under `api`. Secret files keep that organization while separating sensitive values from public settings.
116
102
 
117
- ```javascript
118
- import Configre from "../../index.js";
119
- import { join } from "node:path";
120
-
121
- const configPath = join(import.meta.dirname, "config");
122
- const cfg = Configre(configPath);
103
+ ```text
104
+ config/
105
+ index.cjs # Shared public settings
106
+ production.cjs # Production additions and overrides
107
+ index.secret.cjs # Shared secrets, edited locally
108
+ production.secret.cjs # Production secrets, edited locally
123
109
  ```
124
110
 
125
- Secrets activate automatically when the base configuration or selected profile has a corresponding `.secret.cjs` file, or when `secrets.enc.json` already exists. No options are needed. Loading remains synchronous, and your application reads the result through ordinary properties such as `cfg.api.key`.
111
+ A secret file exports only the fields it needs to supply. Your application reads them through ordinary properties such as `cfg.api.key`; there is no separate secrets API. Define sensitive fields in secret files; public configuration does not need placeholders for them.
126
112
 
127
- > The demo reports `API key configured:` without printing the key. Avoid logging `cfg` in your application: it contains the decrypted secrets. Use `cfg.print()` to log configuration with secret fields omitted.
113
+ For `--config=production`, the merge order is:
128
114
 
129
- ```javascript
130
- const cfg = Configre(configPath);
131
- cfg.print();
115
+ ```text
116
+ index.cjs → production.cjs → index.secret.cjs → production.secret.cjs
132
117
  ```
133
118
 
134
- `print()` calls Configre's `log.debug`; enable its output with `DEBUG=Configre:*`.
135
- It omits fields supplied by the base and selected profile's `.secret.cjs` files,
136
- including when loaded from the encrypted file. Public siblings in nested objects
137
- remain visible; arrays supplied by secrets are omitted entirely. It does not
138
- modify the configuration or log automatically on load. `cfg.print()` logs its
139
- current values, including changes made after loading. The method is non-enumerable
140
- and does not appear in `Object.keys(cfg)`, object spreads or JSON output.
141
- `new Configre(configPath).print()` is also supported. If your configuration already
142
- has a field named `print`, that value is preserved; use the constructor API to print it.
143
- Sensitivity is determined by secret-file fields, not by names such as `password`
144
- or `token`: keep sensitive values in `.secret.cjs` files, not in public settings.
119
+ Later layers win. Shared secrets can be extended or overwritten by profile secrets, just like public settings. Secret selection independently prefers `production.dev.secret.cjs` over `production.secret.cjs`; it does not merge the two variants.
145
120
 
146
- ### 1. Start the demo on the administrator machine
121
+ ### Add your secrets
147
122
 
148
- For this walkthrough, use an empty public placeholder in [demo/secrets/config/index.cjs](demo/secrets/config/index.cjs):
123
+ Create `config/index.secret.cjs` for shared values:
149
124
 
150
125
  ```javascript
151
126
  module.exports = {
@@ -155,175 +130,144 @@ module.exports = {
155
130
  };
156
131
  ```
157
132
 
158
- Create `demo/secrets/config/index.secret.cjs` yourself to enable secrets, initially with:
133
+ For a production API key, create `config/production.secret.cjs`:
159
134
 
160
135
  ```javascript
161
- module.exports = {};
162
- ```
163
-
164
- Then run:
165
-
166
- ```bash
167
- node demo/secrets/demo.js
136
+ module.exports = {
137
+ api: {
138
+ key: ""
139
+ }
140
+ };
168
141
  ```
169
142
 
170
- For a new setup, Configre generates the encrypted file, recipients directory and Git exclusions alongside your existing configuration files:
143
+ Fill in the values in these local files. The production key overrides the shared key when that profile is selected, while `api.url` still comes from the public configuration. Configre generates `secrets.enc.json` and adds Git exclusions for the editable secret files when your application runs.
171
144
 
172
- ```text
173
- demo/secrets/config/
174
- index.cjs # Public configuration
175
- index.secret.cjs # Editable secrets you create yourself
176
- recipients/ # Public keys of servers that register
177
- secrets.enc.json # Generated encrypted values
178
- .gitignore # Keeps .secret.cjs files out of Git
179
- ```
145
+ ### Share with a server
180
146
 
181
- It also creates this OS user's identity outside the repository, at `~/.config/configre/identity.pem` and `identity.pub`.
147
+ 1. **Local:** run your application and push the generated encrypted file.
148
+ 2. **Server:** pull and run. On the first run, Configre automatically commits and pushes the server's public key as `recipients/<profile>.pub` (or `<hostname>.pub` when no profile is specified).
149
+ 3. **Local:** pull, run again to include that public key in the encrypted file, and push.
150
+ 4. **Server:** pull and restart. The secrets are now available through `cfg`.
182
151
 
183
- With the public placeholder empty and no secrets added, look for:
152
+ The server needs Git push access for registration. Until the updated encrypted file arrives, Configre loads public settings only.
184
153
 
185
- ```text
186
- API key configured: false
187
- ```
154
+ To update secrets later, edit them locally, run, and push. Pull and restart on the server. Registration happens only once per identity.
188
155
 
189
- **Configre never creates `.secret.cjs` files.** Without a secret counterpart for the base configuration or selected profile, and without an encrypted file, it loads only public settings and creates no identity or secret artifacts. A secret module contains only the fields you choose to override; public settings continue to come from `index.cjs`.
156
+ **Profiles organize secrets; they do not isolate access.** Authorized identities can decrypt all profiles in the encrypted file. Pulling a new registration and publishing the updated encrypted file grants that identity access.
190
157
 
191
- These initialization steps describe a new setup. If `secrets.enc.json` already exists, Configre uses that encrypted file instead of resetting it. A checkout containing the encrypted file but no `.secret.cjs` files is treated as a server checkout.
158
+ ## Utilities
192
159
 
193
- ### 2. Add an API key and publish the encrypted file
160
+ ### Inspect configuration with `cfg.print()`
194
161
 
195
- On the administrator machine, edit `demo/secrets/config/index.secret.cjs`:
162
+ The returned configuration contains decrypted secrets. Use `cfg.print()` when inspecting it:
196
163
 
197
164
  ```javascript
198
- module.exports = {
199
- api: {
200
- key: ""
201
- }
202
- };
165
+ const cfg = Configre(join(import.meta.dirname, "config"));
166
+ cfg.print();
203
167
  ```
204
168
 
205
- Replace the empty string with your actual key **in this local file**, then run the demo again:
169
+ Enable its debug output with:
206
170
 
207
171
  ```bash
208
- node demo/secrets/demo.js
172
+ DEBUG=Configre:* node app.mjs --config=production
209
173
  ```
210
174
 
211
- Configre updates `demo/secrets/config/secrets.enc.json`, merges the secret into the configuration, and the demo reports:
175
+ `print()` omits fields supplied by the base and selected profile's secret files, including when those values come from the encrypted file. Public siblings remain visible; arrays supplied by secrets are omitted entirely. Omission is based on secret-file fields, not names such as `password` or `token`. Keep sensitive values in secret files.
212
176
 
213
- ```text
214
- API key configured: true
215
- ```
177
+ It logs current values, including edits made after loading, without changing the configuration. It does not log automatically on load. The method is non-enumerable, so it stays out of `Object.keys(cfg)`, spreads, and JSON output. Spreads and JSON output still include the configuration's secret values.
216
178
 
217
- Commit and push the demo's public files, including `config/index.cjs`, the generated `config/.gitignore`, and `config/secrets.enc.json`. Keep `config/index.secret.cjs` on the administrator machine.
179
+ The constructor API is also available:
218
180
 
219
- | File in `demo/secrets/config/` | Purpose | Share through Git? |
220
- | --- | --- | --- |
221
- | `index.cjs` | Public settings and empty placeholders | Yes |
222
- | `index.secret.cjs` | Values you edit on the administrator | No |
223
- | `secrets.enc.json` | Encrypted values generated by Configre | Yes |
224
- | `recipients/truco.pub` | Public identity of the server named `truco` | Yes; the server publishes it |
225
- | `.gitignore` | Excludes editable secret files | Yes |
181
+ ```javascript
182
+ const config = new Configre(join(import.meta.dirname, "config"));
183
+ const cfg = config.get();
184
+ config.print();
185
+ ```
226
186
 
227
- ### 3. Register the server as `truco`
187
+ If your configuration already has a field named `print`, Configre preserves it. Use the constructor API to print in that case.
228
188
 
229
- On the server, clone or pull the repository **including `demo/secrets/config/secrets.enc.json`**. Use a branch with a configured upstream, Git author name/email, and credentials that allow a push without an interactive prompt. Before a new registration, the local branch must match its upstream.
189
+ ### Apply environment variables explicitly
230
190
 
231
- Run:
191
+ Use `applyConfigEnv(cfg.env)` when a library reads its settings from `process.env`:
232
192
 
233
- ```bash
234
- node demo/secrets/demo.js --config=truco
193
+ ```javascript
194
+ import Configre, { applyConfigEnv } from "configre";
195
+ import { join } from "node:path";
196
+
197
+ const cfg = Configre(join(import.meta.dirname, "config"));
198
+ applyConfigEnv(cfg.env);
235
199
  ```
236
200
 
237
- `--config=truco` selects the profile and names the public-key file. Without this flag, Configre uses the hostname.
201
+ For CommonJS, use `const { applyConfigEnv } = require("configre")`.
238
202
 
239
- If this server is not authorized yet, Configre automatically:
203
+ The helper converts values with `String(value)` and sets only variables that are undefined in `process.env`. Existing values, including empty strings, are preserved. An undefined `cfg.env` does nothing; null or undefined entries are skipped.
240
204
 
241
- 1. Creates its local identity, if needed.
242
- 2. Writes `demo/secrets/config/recipients/truco.pub`.
243
- 3. Creates and pushes a commit containing only that public-key file.
244
- 4. Logs a warning and continues with public configuration only until the administrator publishes an updated encrypted file.
205
+ It rejects null, arrays and non-objects as `cfg.env`, empty keys, and object-valued entries. Entries are processed in order; an invalid entry throws without undoing previous assignments. Loading Configre does not call this helper automatically, and empty placeholders are never filled from environment variables automatically.
245
206
 
246
- Publishing a public key does not let the server decrypt the existing ciphertext. While authorization is pending, `cfg` preserves the public defaults and selected public profile; fields that exist only in the encrypted file are absent. Repeating the command does not publish another registration commit for the same key. If Git registration fails, Configre logs a warning and still returns public settings; the next Configre load retries registration.
207
+ ## Advanced reference
247
208
 
248
- Configre uses `log.info` when it creates identity files, writes Git exclusions, creates the recipients directory or a public-key file, updates the encrypted file, and successfully pushes the public key. Messages contain operation names and file paths, never secret values or key contents. These messages use the existing `Configre` LemonLog namespace; use `DEBUG=Configre:*` to display them. Unchanged files and previously published public keys do not generate another creation or publication log.
209
+ <details>
210
+ <summary>Secret formats, identities, Git behavior, and recovery</summary>
249
211
 
250
- ### 4. Include the server in the encrypted file
212
+ ### Secret files and loading
251
213
 
252
- Back on the administrator machine:
214
+ Secret `.cjs` modules execute locally on the machine where you edit them and are reloaded on each Configre call. They must export plain objects containing JSON-compatible objects, arrays, strings, finite numbers, booleans, and `null`. Functions, `undefined`, accessors, symbols, custom objects, circular references, and properties named `__proto__`, `constructor`, or `prototype` are rejected. Servers decrypt data without executing these modules.
253
215
 
254
- ```bash
255
- git pull --ff-only
256
- node demo/secrets/demo.js
257
- ```
216
+ Secrets activate when the base or selected profile has a secret counterpart, or when the encrypted file already exists. Without either, loading public configuration creates no secret artifacts. Once active, all local profile secret files are encrypted together, including inactive profiles.
258
217
 
259
- The pull brings in `demo/secrets/config/recipients/truco.pub`. When the demo runs, Configre automatically includes that public key and regenerates `demo/secrets/config/secrets.enc.json`. There is no separate approval step.
218
+ A selected profile's secret file can activate secrets without `index.secret.cjs`. A public profile alone never creates a secret counterpart. A missing profile uses the available base settings and base secrets. A checkout with an encrypted file and no editable secret files acts as a consumer. Existing ciphertext is read and validated before updates; it is not reset on startup. There is no `secrets` option, and function, constructor, ESM, and CommonJS usage share the same behavior.
260
219
 
261
- Commit and push the updated encrypted file. Then, on the server:
220
+ For an individual configuration file, sidecars live beside it: `settings.secret.cjs`, `settings.cjs.recipients/`, and `settings.cjs.secrets.enc.json`. An extensionless path resolving to `settings.cjs` uses the same sidecars.
262
221
 
263
- ```bash
264
- git pull --ff-only
265
- node demo/secrets/demo.js --config=truco
266
- ```
222
+ ### Identities and registration
267
223
 
268
- The server can now decrypt the key, and the demo reports `API key configured: true`. It does not need `index.secret.cjs`; the value reaches `cfg.api.key` through the encrypted file.
224
+ Automatic registration requires a Git checkout with a configured upstream, Git author name/email, and credentials that allow a non-interactive push. The branch must match its upstream before a new registration. Failed registrations are retried on the next load.
269
225
 
270
- **Repository write access allows a machine to register for all project secrets.** Configre publishes the server's public key automatically. Pulling changes and publishing the administrator's updated encrypted file remain part of your Git/deployment workflow.
226
+ Each OS user has one identity, stored at `~/.config/configre/identity.pem` and `identity.pub`, reused across projects. Authorization is per project. A service running under another OS user needs its own registration.
271
227
 
272
- ### 5. Update keys or use profile-specific secrets
228
+ Recipient files must contain a single RSA-3072 public key in PEM format, with exponent 65537, as generated by Configre. Other extensions are ignored, and duplicate keys do not add recipients. The identity encrypting the file is always included.
273
229
 
274
- To change the shared API key, edit `demo/secrets/config/index.secret.cjs` on the administrator and run:
230
+ Registration names must start with a letter or digit and contain only letters, digits, dots, underscores, or hyphens. If another key already occupies `production.pub`, registration stops without overwriting it, and configuration loading continues with a warning and public settings only. Use a distinct profile or resolve the key replacement explicitly.
275
231
 
276
- ```bash
277
- node demo/secrets/demo.js
278
- ```
232
+ Registration uses an isolated Git index and publishes only the public-key file. It preserves unrelated staged and unstaged changes, runs no commit or pre-push hooks, and does not push local tags, merge, rebase, or force-push. Repeated loads do not publish another registration commit for the same key. Each Git command has a 30-second timeout. An authorized server loads secrets without Git commands or project writes.
279
233
 
280
- Commit and push the updated `secrets.enc.json`, then pull and restart the demo on the server. **You do not need to copy or register `truco.pub` again.** Its existing authorization also covers new keys you add later.
234
+ Configre appends Git exclusions without removing existing rules, including when no repository exists yet. It refuses to proceed on the machine holding editable secrets if those modules are already tracked. It does not untrack files or rewrite history.
281
235
 
282
- For settings specific to `truco`, add `demo/secrets/config/truco.cjs` and create `truco.secret.cjs` yourself with that profile's secrets. The secret file activates secrets when `truco` is selected, even without `index.secret.cjs`. A public profile alone never creates a secret counterpart. Once secrets are active, all profile secret files, including inactive ones, are encrypted together when the administrator runs the demo.
236
+ ### Updates, revocation, and recovery
283
237
 
284
- For `--config=truco`, the merge order is:
238
+ The editable `.secret.cjs` files are the source of truth for values; `recipients/` is the source of truth for authorization. To revoke a server, remove its public-key file, run Configre locally, and commit and push both the removal and the updated encrypted file. Remove the server's repository write access too if it must not register again. Revocation protects new encrypted versions. Rotate credentials at their providers to invalidate values a server already received.
285
239
 
286
- ```text
287
- index.cjs → truco.cjs → index.secret.cjs → truco.secret.cjs
288
- ```
240
+ Removing all recipient files revokes them on the next local load; old ciphertext does not restore them. Recover accidentally deleted public-key files from Git before reloading. Missing Git exclusions and ciphertext are regenerated on the machine holding editable secrets, and existing secret modules are never overwritten.
289
241
 
290
- Later files override matching fields while preserving other fields. If `truco.dev.cjs` exists, it is selected instead of `truco.cjs`. Secret selection independently prefers `truco.dev.secret.cjs` over `truco.secret.cjs`; those two secret variants do not merge with each other. A missing profile uses the available base configuration and base secrets.
242
+ Back up editable secrets and the private identity securely. Never share or commit `identity.pem`. On POSIX, the identity directory must have owner-only permissions (`0700`), and the private file must have owner-only permissions (`0600`). On Windows, protection depends on the user profile's filesystem permissions. Private identities and secret input files cannot be symlinks.
291
243
 
292
- To revoke this server, remove `demo/secrets/config/recipients/truco.pub`, run the demo on the administrator, and commit and push both the removal and the updated encrypted file. Remove the server's repository write access too if it must not register itself again. Revocation protects new encrypted versions; rotate the key at its provider to invalidate a credential the server already received.
244
+ A corrupt or missing private identity is never silently replaced. Restore it from backup, or register a new identity on a consumer. A missing public file can be regenerated from the valid private key. Losing every authorized private key makes existing ciphertext unrecoverable; surviving editable secrets can be used to create a fresh encrypted file in a new setup.
293
245
 
294
- <details>
295
- <summary>File formats, Git behavior and recovery</summary>
296
-
297
- - Secret `.cjs` modules execute on the administrator and are reloaded on each Configre call. They must export plain objects containing JSON-compatible objects, arrays, strings, finite numbers, booleans and `null`. Functions, `undefined`, accessors, symbols, custom objects, circular references, and properties named `__proto__`, `constructor` or `prototype` are rejected. Consumers decrypt data without executing these modules.
298
- - Empty strings stay empty strings. Configre does not fill them from environment variables or populate `process.env`. The existing deep-merge behavior, including array merging, also applies to secrets.
299
- - Activation depends only on the existing files; there is no `secrets` option. The same behavior applies to `new Configre(configPath).get()` and CommonJS consumers.
300
- - Each OS user has one identity reused across projects; authorization is per project. A service running under another OS user needs its own registration. All authorized identities can decrypt all profiles in the project's encrypted file.
301
- - Public recipient files must contain a single RSA-3072 public key in PEM format, with exponent 65537, as generated by Configre. Other file extensions are ignored, and duplicate keys do not add recipients. The administrator is always included.
302
- - Registration uses an isolated Git index and publishes only the public-key file. It preserves unrelated staged and unstaged changes, runs no commit or pre-push hooks, and does not push local tags, merge, rebase or force-push. Failed registrations can be retried at the next startup; each Git command has a 30-second timeout. An authorized server loads secrets without Git commands or project writes.
303
- - Registration names must start with a letter or digit and contain only letters, digits, dots, underscores or hyphens. If another key already occupies `truco.pub`, registration stops without overwriting it, and configuration loading continues with a warning and public settings only; use a distinct profile or resolve the key replacement explicitly.
304
- - Configre appends Git exclusions without removing existing rules and refuses to proceed on the administrator if editable secret modules are already tracked. It does not untrack files or rewrite history. It also prepares `.gitignore` when no repository exists yet.
305
- - The administrator's `.secret.cjs` files are the source of truth for values, and `recipients/` is the source of truth for authorization. Removing all recipient files revokes them on the next administrator reload; old ciphertext does not restore them. Recover accidentally deleted public-key files from Git before reloading. Missing Git exclusions and ciphertext are regenerated on the administrator, and existing secret modules are never overwritten.
306
- - Back up the administrator's editable secrets and private identity securely. Never share or commit `identity.pem`. On POSIX, its directory must have owner-only permissions (`0700`) and the private file must have owner-only permissions (`0600`); on Windows, protection depends on the user profile's filesystem permissions. Private identities and secret input files cannot be symlinks.
307
- - A corrupt or missing private identity is never silently replaced. Restore it from backup, or register a new identity on a consumer. A missing public file can be regenerated from the valid private key. Losing every authorized private key makes existing ciphertext unrecoverable; an administrator with surviving editable secret files can create fresh ciphertext.
308
- - The encrypted JSON envelope uses AES-256-GCM with a fresh content key and nonce for each update, wrapping that key for each recipient with RSA-3072 / OAEP-SHA-256. Version and recipient metadata are authenticated. Invalid modules, malformed ciphertext and authentication failures stop loading without replacing the existing encrypted file.
309
- - Ciphertext is replaced atomically under an exclusive writer lock. Temporary vault files contain only encrypted data and public metadata. After a crash, remove a reported stale `.lock` only after confirming its writer has stopped. Unchanged values and recipient keys do not cause a rewrite.
310
- - For an individual configuration file, the same workflow uses sidecars beside that file: `settings.secret.cjs`, `settings.cjs.recipients/` and `settings.cjs.secrets.enc.json`. An extensionless path resolving to `settings.cjs` uses the same sidecars.
246
+ ### Encryption and writes
247
+
248
+ The encrypted JSON envelope uses AES-256-GCM with a fresh content key and nonce for each update. That key is wrapped for each recipient with RSA-3072 / OAEP-SHA-256. Version and recipient metadata are authenticated. Invalid modules, malformed ciphertext, and authentication failures stop loading without replacing the existing encrypted file.
249
+
250
+ Ciphertext is replaced atomically under an exclusive writer lock. Temporary files contain only encrypted data and public metadata. After a crash, remove a reported stale `.lock` only after confirming its writer has stopped. Unchanged values and recipient keys do not cause a rewrite.
251
+
252
+ ### Operational logs
253
+
254
+ With `DEBUG=Configre:*`, Configre reports identity creation, Git exclusions, recipient registration, encrypted-file updates, and successful public-key publication. These operation messages contain names and paths, never secret values or key contents. Unchanged files and previously published keys do not generate another creation or publication log.
311
255
 
312
256
  </details>
313
257
 
314
- ## 🤔 Why Configre?
258
+ ## Resources and contributions
315
259
 
316
- - **No More Manual Switching**: Automatically adjusts your configuration based on the environment.
317
- - **Clarity and Convenience**: Keep your configuration organized and easy to understand.
318
- - **Flexibility**: Supports dynamic configuration values for complex setups.
260
+ Explore the [basic demo](https://github.com/clasen/Configre/blob/main/demo/demo.js), [ESM project demo](https://github.com/clasen/Configre/blob/main/demo/module/demo.js), or [secrets demo](https://github.com/clasen/Configre/blob/main/demo/secrets/demo.js).
319
261
 
320
- ## 🤝 Contribute
262
+ To add the Configre skill to your coding agent:
321
263
 
322
- Found a bug or have a feature request? Contributions are welcome! Feel free to open an issue or submit a pull request.
264
+ ```bash
265
+ npx skills add https://github.com/clasen/Configre --skill configre
266
+ ```
323
267
 
324
- Let's make Configre even better, together! 🎉
268
+ Found a bug or a useful improvement? [Open an issue](https://github.com/clasen/Configre/issues) or submit a pull request. A small, reproducible example helps.
325
269
 
326
- ## 📄 License
270
+ ## License
327
271
 
328
272
  The MIT License (MIT)
329
273
 
File without changes
File without changes
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- api: {
3
- key: ""
2
+ "api": {
3
+ "url": "https://api.example.com"
4
4
  }
5
- };
5
+ }
package/index.js CHANGED
@@ -9,6 +9,30 @@ import loadSecrets from "./secrets/index.js";
9
9
  const requireConfig = createRequire(import.meta.url);
10
10
  const log = lemonlog("Configre");
11
11
 
12
+ function applyConfigEnv(env) {
13
+ if (env === undefined) {
14
+ return;
15
+ }
16
+ if (env === null || typeof env !== "object" || Array.isArray(env)) {
17
+ throw new TypeError("config.env must be a plain object");
18
+ }
19
+
20
+ for (const [key, value] of Object.entries(env)) {
21
+ if (key.length === 0) {
22
+ throw new TypeError("config.env keys must be non-empty strings");
23
+ }
24
+ if (value == null) {
25
+ continue;
26
+ }
27
+ if (typeof value === "object") {
28
+ throw new TypeError(`config.env.${key} must be a scalar`);
29
+ }
30
+ if (process.env[key] === undefined) {
31
+ process.env[key] = String(value);
32
+ }
33
+ }
34
+ }
35
+
12
36
  function omitSecrets(settings, secrets) {
13
37
  for (const [key, value] of Object.entries(secrets)) {
14
38
  if (!Object.hasOwn(settings, key)) continue;
@@ -127,4 +151,6 @@ function Configre(path) {
127
151
  }
128
152
  }
129
153
 
130
- export { Configre as default, Configre as "module.exports" };
154
+ Configre.applyConfigEnv = applyConfigEnv;
155
+
156
+ export { Configre as default, Configre as "module.exports", applyConfigEnv };
package/package.json CHANGED
@@ -1,14 +1,11 @@
1
1
  {
2
2
  "name": "configre",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "🔧 Effortlessly Tailor Your Settings",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=22.13.0"
8
8
  },
9
- "scripts": {
10
- "test": "node --test"
11
- },
12
9
  "dependencies": {
13
10
  "lemonlog": "^1.2.2"
14
11
  },
@@ -50,7 +47,6 @@
50
47
  "url": "https://github.com/clasen/Configre/issues"
51
48
  },
52
49
  "homepage": "https://github.com/clasen/Configre#readme",
53
- "packageManager": "pnpm@11.9.0",
54
50
  "files": [
55
51
  "index.js",
56
52
  "merge.js",
@@ -66,5 +62,8 @@
66
62
  "demo/module/config/hostname.cjs",
67
63
  "demo/secrets/demo.js",
68
64
  "demo/secrets/config/index.cjs"
69
- ]
70
- }
65
+ ],
66
+ "scripts": {
67
+ "test": "node --test"
68
+ }
69
+ }
@@ -1,126 +1,100 @@
1
1
  ---
2
2
  name: configre
3
- description: Set up and manage environment-specific configuration in Node.js projects using the Configre library. Use when the user wants to add configuration management, create environment configs, set up hostname-based settings, or mentions "configre", "config files", "environment config", or "per-host settings".
3
+ description: Set up or edit Configre configuration in Node.js projects, including profile overrides, encrypted secrets, safe printing, and explicit environment-variable application.
4
4
  metadata:
5
5
  category: configuration
6
- tags: [nodejs, config, environment, settings, env]
6
+ tags: [nodejs, config, environment, settings, secrets]
7
7
  ---
8
8
 
9
9
  # Configre
10
10
 
11
- Environment-specific configuration manager for Node.js. Merges a default config with hostname or profile-based overrides using deep merge (lodash).
11
+ Load public defaults, extend or overwrite them by profile, and merge optional secrets synchronously.
12
12
 
13
- ## Instructions
13
+ ## Setup and loading
14
14
 
15
- ### Step 1: Install the package
15
+ Install with `npm install configre`; requires Node.js 22.13 or later.
16
+ Configuration files use `.cjs` and `module.exports`, including in ESM projects.
17
+ The path is required. Anchor it to the module; avoid deriving it from `process.cwd()`.
16
18
 
17
- Requires Node.js 22.13 or later. Configre is native ESM and supports synchronous CommonJS consumers through the same implementation.
19
+ ```javascript
20
+ import Configre from "configre";
21
+ import { join } from "node:path";
18
22
 
19
- ```bash
20
- npm install configre --save
23
+ const cfg = Configre(join(import.meta.dirname, "config"));
21
24
  ```
22
25
 
23
- ### Step 2: Create the config directory
26
+ Create `config/index.cjs` for defaults and `config/production.cjs` for additions or overrides.
27
+ Keep sensitive public placeholders as `""`; put actual values in secret files.
28
+ CommonJS supports `require("configre")` directly, without `.default` or `await`.
29
+ `Configre(path)` returns configuration; `new Configre(path)` exposes `.get()` and `.print()`.
24
30
 
25
- Config files **must always use the `.cjs` extension**. This works in both CommonJS and ESM projects and allows using `module.exports`.
31
+ ## Profiles and merge behavior
26
32
 
27
- ```
28
- project/
29
- ├── config/
30
- │ ├── index.cjs # Default settings (required)
31
- │ ├── myhostname.cjs # Host-specific overrides (optional)
32
- │ └── myhostname.dev.cjs # Dev override for host (optional)
33
- ```
34
-
35
- ### Step 3: Write the default config (`config/index.cjs`)
36
-
37
- ```javascript
38
- module.exports = {
39
- db: {
40
- host: 'localhost',
41
- port: 5432,
42
- user: 'dev',
43
- password: 'dev-pass'
44
- },
45
- api: {
46
- key: 'default-key',
47
- url: 'http://localhost:3000'
48
- }
49
- };
50
- ```
33
+ - Select with `--config=production` anywhere in the application arguments; otherwise use `os.hostname()`.
34
+ - Prefer `<profile>.dev.cjs` over `<profile>.cjs` when present, independently of `NODE_ENV`.
35
+ - Select one public profile; dev and regular variants do not merge. Missing profiles use defaults.
36
+ - Objects merge recursively: preserve unspecified fields, override matching values, and add new fields.
37
+ - Arrays merge by index and retain trailing entries; they are not replaced wholesale.
38
+ - Merge order: public base → public profile → secret base → secret profile.
39
+ - Independently prefer `<profile>.dev.secret.cjs` over `<profile>.secret.cjs`.
51
40
 
52
- ### Step 4: Write host/profile overrides
41
+ ## Secrets
53
42
 
54
- Create `config/<hostname>.cjs` with only the keys that differ — they are deep-merged over defaults:
43
+ Create `index.secret.cjs` for shared secrets and `<profile>.secret.cjs` for profile secrets:
55
44
 
56
45
  ```javascript
57
46
  module.exports = {
58
- db: {
59
- user: 'prod-user',
60
- password: 'prod-secret'
61
- }
47
+ db: { password: "" },
48
+ api: { key: "" }
62
49
  };
63
50
  ```
64
51
 
65
- ### Step 5: Load the configuration
52
+ - Secret modules supply only the needed fields; consumers read ordinary properties such as `cfg.api.key`.
53
+ - Activate when the base or selected profile has a secret counterpart, or ciphertext already exists.
54
+ - Without either condition, load public settings without identity access or secret artifacts.
55
+ - Configre never creates editable secret modules. There is no `secrets` option.
56
+ - Export plain objects containing JSON-compatible values; empty strings remain empty.
57
+ - Reject functions, undefined, accessors, symbols, custom objects, cycles, sparse arrays, non-finite numbers, and keys named `__proto__`, `constructor`, or `prototype`.
58
+ - Once active, encrypt all local profile secrets together; merge only the base and selected profile.
59
+ - Every authorized identity can decrypt every profile. Profiles organize values, not access.
60
+ - Editable secrets are authoritative for values; `recipients/` is authoritative for additional recipients.
61
+ - With ciphertext and no editable secret modules, load as a consumer; deleting local modules does not clear secrets.
62
+ - Individual `settings.cjs` files use `settings.secret.cjs`, `settings.cjs.secrets.enc.json`, and `settings.cjs.recipients/`; file mode has no secret profiles.
63
+
64
+ ### Sharing and updates
65
+
66
+ 1. Local: create secret files and run; Configre generates ciphertext and Git exclusions. Push the encrypted file.
67
+ 2. Server: pull and run; Configre automatically commits and pushes `recipients/<profile>.pub`.
68
+ 3. Local: pull, run to incorporate the public key, and push the updated ciphertext.
69
+ 4. Server: pull and restart. Later secret updates repeat the local run/push and server pull/restart.
70
+
71
+ Registration needs a branch matching its upstream, Git author identity, and non-interactive push access.
72
+ Pending authorization or failed registration returns public settings only; malformed secrets, identities, and ciphertext stop loading.
73
+ Use isolated fixtures for verification unless live registration is authorized: loading can fetch, commit, and push.
74
+ Registration preserves unrelated changes, skips hooks, rejects key-name collisions, and retries failures on later loads.
75
+ Never commit editable secret modules or private identities; Configre rejects already tracked secret inputs.
76
+ Identity files live under `~/.config/configre/`: `identity.pem` is private and `identity.pub` is public.
77
+ Back up editable secrets and private identities; never reset damaged identities or ciphertext as a retry.
78
+ To revoke access, remove the recipient key, reload locally, and publish; prevent re-registration and rotate previously shared credentials when required.
79
+
80
+ ## Print without secret fields
81
+
82
+ Use `cfg.print()` with `DEBUG=Configre:*`, rather than logging the merged configuration.
83
+ It logs current values without mutation or automatic printing on load; public siblings remain visible.
84
+ Omit loaded secret-file fields recursively, including encrypted inputs; omit secret arrays entirely.
85
+ Classification follows secret-file fields, not names: secrets copied elsewhere are not automatically detected.
86
+ The method is non-enumerable; spreads and JSON still contain secrets and do not preserve the method.
87
+ An existing `print` field is preserved; use `new Configre(configPath).print()` in that case.
88
+
89
+ ## Apply environment variables
66
90
 
67
91
  ```javascript
68
- import Configre from "configre";
69
- import { join } from "node:path";
70
-
71
- const cfg = Configre(join(import.meta.dirname, "config"));
72
-
73
- console.log(cfg.db.host); // from default
74
- console.log(cfg.db.user); // from host override
92
+ import Configre, { applyConfigEnv } from "configre";
93
+ const cfg = Configre(configPath);
94
+ applyConfigEnv(cfg.env);
75
95
  ```
76
96
 
77
- CommonJS consumers can still use `const Configre = require("configre")` and `Configre(path.join(__dirname, "config"))`, without `.default` or `await`. Configuration files remain `.cjs` in either module system.
78
-
79
- The path argument is required. Prefer an absolute path anchored to the module. Do not recommend paths derived from `process.cwd()`, such as `path.join(process.cwd(), "config")`: they can point somewhere else when the process is launched from a different directory.
80
-
81
- To use a different config directory:
82
-
83
- ```javascript
84
- const cfg = Configre(join(import.meta.dirname, "settings"));
85
- ```
86
-
87
- ## Profile resolution
88
-
89
- Configre determines the active profile by looking for a `--config=<profile>` argument anywhere in `process.argv`, or falls back to `os.hostname()`. It then checks for overrides in this order (first match wins):
90
-
91
- 1. `config/<profile>.dev.cjs` — dev override
92
- 2. `config/<profile>.cjs` — production override
93
- 3. No match — uses defaults only
94
-
95
- To force a specific profile at runtime:
96
-
97
- ```bash
98
- node app.js --config=staging
99
- node app.js --port=3000 --config=production --debug
100
- ```
101
-
102
- Using `--config=` (instead of a positional argument) avoids conflicts with other CLI flags.
103
-
104
- ## Examples
105
-
106
- **Example 1: Basic setup**
107
- User says: "Add configuration management to my Node.js project"
108
- Actions: install configre, create `config/index.cjs` with project defaults, import Configre and load with `Configre(join(import.meta.dirname, "config"))`
109
- Result: merged config object ready to use
110
-
111
- **Example 2: Multi-environment**
112
- User says: "I need different database settings per server"
113
- Actions: create `config/index.cjs` with defaults, create `config/<hostname>.cjs` per server with db overrides
114
- Result: each server automatically loads its own config based on hostname
115
-
116
- **Example 3: Custom profile via CLI**
117
- User says: "I want to run my app with a staging config"
118
- Actions: create `config/staging.cjs`, run app with `node app.js --config=staging`
119
- Result: staging overrides are merged over defaults, without conflicting with other CLI arguments
120
-
121
- ## Key behaviors
122
-
123
- - **Deep merge**: nested objects merge recursively via lodash `_.merge`
124
- - **Config files must use the `.cjs` extension** (`.js` is not accepted; works in both CommonJS and ESM projects)
125
- - **Required path**: always pass the config directory or file path; prefer an absolute module-relative path over a process-relative path
126
- - **Function vs constructor**: `Configre(path)` returns the merged config directly; `new Configre(path)` returns the instance (use `.get()` to retrieve config)
97
+ Call explicitly after loading. Set only undefined `process.env` variables using `String(value)`; preserve existing values, including `""`.
98
+ Omitted `cfg.env` does nothing; skip null/undefined entries. Reject null, arrays, non-objects, empty keys, and object values.
99
+ Invalid entries throw without rolling back earlier assignments. CommonJS: `const { applyConfigEnv } = require("configre")`.
100
+ For encryption, identity permissions, and recovery details, consult the [advanced reference](https://github.com/clasen/Configre#advanced-reference).
@@ -4,7 +4,65 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import test from "node:test";
7
- import Configre from "../index.js";
7
+ import Configre, { applyConfigEnv } from "../index.js";
8
+
9
+ test("applyConfigEnv fills missing variables without overwriting existing values", t => {
10
+ const env = {
11
+ CONFIGRE_TEST_ENV_TEXT: "value",
12
+ CONFIGRE_TEST_ENV_NUMBER: 0,
13
+ CONFIGRE_TEST_ENV_BOOLEAN: false,
14
+ CONFIGRE_TEST_ENV_EMPTY: "",
15
+ CONFIGRE_TEST_ENV_NULL: null,
16
+ CONFIGRE_TEST_ENV_UNDEFINED: undefined,
17
+ CONFIGRE_TEST_ENV_EXISTING: "replacement",
18
+ CONFIGRE_TEST_ENV_EXISTING_EMPTY: "replacement"
19
+ };
20
+ const original = Object.fromEntries(Object.keys(env).map(key => [key, process.env[key]]));
21
+ t.after(() => {
22
+ for (const [key, value] of Object.entries(original)) {
23
+ if (value === undefined) delete process.env[key];
24
+ else process.env[key] = value;
25
+ }
26
+ });
27
+ for (const key of Object.keys(env)) delete process.env[key];
28
+ process.env.CONFIGRE_TEST_ENV_EXISTING = "original";
29
+ process.env.CONFIGRE_TEST_ENV_EXISTING_EMPTY = "";
30
+
31
+ assert.equal(applyConfigEnv(env), undefined);
32
+ assert.equal(process.env.CONFIGRE_TEST_ENV_TEXT, "value");
33
+ assert.equal(process.env.CONFIGRE_TEST_ENV_NUMBER, "0");
34
+ assert.equal(process.env.CONFIGRE_TEST_ENV_BOOLEAN, "false");
35
+ assert.equal(process.env.CONFIGRE_TEST_ENV_EMPTY, "");
36
+ assert.equal(process.env.CONFIGRE_TEST_ENV_NULL, undefined);
37
+ assert.equal(process.env.CONFIGRE_TEST_ENV_UNDEFINED, undefined);
38
+ assert.equal(process.env.CONFIGRE_TEST_ENV_EXISTING, "original");
39
+ assert.equal(process.env.CONFIGRE_TEST_ENV_EXISTING_EMPTY, "");
40
+ applyConfigEnv({ CONFIGRE_TEST_ENV_TEXT: "replacement" });
41
+ assert.equal(process.env.CONFIGRE_TEST_ENV_TEXT, "value");
42
+ assert.equal(env.CONFIGRE_TEST_ENV_NUMBER, 0);
43
+ assert.equal(env.CONFIGRE_TEST_ENV_BOOLEAN, false);
44
+ });
45
+
46
+ test("applyConfigEnv accepts omitted configuration and rejects invalid inputs", () => {
47
+ assert.equal(applyConfigEnv(), undefined);
48
+ assert.equal(applyConfigEnv({}), undefined);
49
+ for (const env of [null, [], "text", 1, true, () => {}]) {
50
+ assert.throws(() => applyConfigEnv(env), {
51
+ name: "TypeError",
52
+ message: "config.env must be a plain object"
53
+ });
54
+ }
55
+ assert.throws(() => applyConfigEnv({ "": null }), {
56
+ name: "TypeError",
57
+ message: "config.env keys must be non-empty strings"
58
+ });
59
+ for (const value of [{ nested: true }, [], new Date()]) {
60
+ assert.throws(() => applyConfigEnv({ CONFIGRE_TEST_ENV_INVALID: value }), {
61
+ name: "TypeError",
62
+ message: "config.env.CONFIGRE_TEST_ENV_INVALID must be a scalar"
63
+ });
64
+ }
65
+ });
8
66
 
9
67
  test("requires a config path", () => {
10
68
  assert.throws(
@@ -44,17 +102,25 @@ for (const format of ["commonjs", "module"]) {
44
102
  const entry = format === "commonjs" ? `
45
103
  const assert = require('node:assert/strict');
46
104
  const Configre = require('configre');
47
- import('configre').then(module => assert.equal(module.default, Configre));
105
+ const { applyConfigEnv } = Configre;
106
+ import('configre').then(module => {
107
+ assert.equal(module.default, Configre);
108
+ assert.equal(module.applyConfigEnv, applyConfigEnv);
109
+ });
48
110
  ` : `
49
111
  import assert from 'node:assert/strict';
50
112
  import { createRequire } from 'node:module';
51
- import Configre from 'configre';
113
+ import Configre, { applyConfigEnv } from 'configre';
52
114
  const require = createRequire(import.meta.url);
53
115
  assert.equal(require('configre'), Configre);
116
+ assert.equal(require('configre').applyConfigEnv, applyConfigEnv);
54
117
  `;
55
118
  const filename = path.join(directory, format === "commonjs" ? "consumer.cjs" : "consumer.mjs");
56
119
  fs.writeFileSync(filename, entry + `
57
120
  assert.equal(typeof Configre, 'function');
121
+ delete process.env.CONFIGRE_TEST_ENV_INTEROP;
122
+ applyConfigEnv({ CONFIGRE_TEST_ENV_INTEROP: 123 });
123
+ assert.equal(process.env.CONFIGRE_TEST_ENV_INTEROP, '123');
58
124
  const configPath = ${JSON.stringify(config)};
59
125
  const expected = { db: { host: 'localhost', port: 456 } };
60
126
  assert.deepEqual(Configre(configPath), expected);