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 +156 -212
- package/demo/config/hostname.cjs +0 -0
- package/demo/config/index.cjs +0 -0
- package/demo/secrets/config/index.cjs +3 -3
- package/index.js +27 -1
- package/package.json +6 -7
- package/skills/configre/SKILL.md +72 -98
- package/test/index.test.js +69 -3
package/README.md
CHANGED
|
@@ -1,151 +1,126 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Configre
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Define your defaults once. Let each environment describe what changes. Keep secrets alongside the settings they belong to.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
+
Requires **Node.js 22.13 or later**.
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
44
|
-
import Configre from "configre";
|
|
45
|
-
import { join } from "node:path";
|
|
15
|
+
Create `config/index.cjs` with your defaults:
|
|
46
16
|
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
29
|
+
Load it from `app.mjs`, next to the `config` directory:
|
|
52
30
|
|
|
53
|
-
|
|
31
|
+
```javascript
|
|
32
|
+
import Configre from "configre";
|
|
33
|
+
import { join } from "node:path";
|
|
54
34
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
35
|
+
const cfg = Configre(join(import.meta.dirname, "config"));
|
|
36
|
+
console.log(cfg.db.port); // 5432
|
|
37
|
+
```
|
|
58
38
|
|
|
59
|
-
|
|
60
|
-
|
|
39
|
+
```bash
|
|
40
|
+
node app.mjs
|
|
41
|
+
```
|
|
61
42
|
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
## Configuration that builds on defaults
|
|
67
48
|
|
|
68
|
-
|
|
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
|
-
|
|
51
|
+
Add `config/production.cjs`:
|
|
72
52
|
|
|
73
53
|
```javascript
|
|
74
54
|
module.exports = {
|
|
75
55
|
db: {
|
|
76
|
-
host:
|
|
77
|
-
|
|
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
|
-
|
|
68
|
+
The resulting configuration contains:
|
|
87
69
|
|
|
88
70
|
```javascript
|
|
89
|
-
|
|
71
|
+
{
|
|
90
72
|
db: {
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
### Selecting a profile
|
|
100
86
|
|
|
101
|
-
By default Configre uses the machine
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
## Secrets, organized like your configuration
|
|
114
100
|
|
|
115
|
-
|
|
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
|
-
```
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
For `--config=production`, the merge order is:
|
|
128
114
|
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
cfg.print();
|
|
115
|
+
```text
|
|
116
|
+
index.cjs → production.cjs → index.secret.cjs → production.secret.cjs
|
|
132
117
|
```
|
|
133
118
|
|
|
134
|
-
`
|
|
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
|
-
###
|
|
121
|
+
### Add your secrets
|
|
147
122
|
|
|
148
|
-
|
|
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
|
-
|
|
133
|
+
For a production API key, create `config/production.secret.cjs`:
|
|
159
134
|
|
|
160
135
|
```javascript
|
|
161
|
-
module.exports = {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
node demo/secrets/demo.js
|
|
136
|
+
module.exports = {
|
|
137
|
+
api: {
|
|
138
|
+
key: ""
|
|
139
|
+
}
|
|
140
|
+
};
|
|
168
141
|
```
|
|
169
142
|
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
+
The server needs Git push access for registration. Until the updated encrypted file arrives, Configre loads public settings only.
|
|
184
153
|
|
|
185
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
158
|
+
## Utilities
|
|
192
159
|
|
|
193
|
-
###
|
|
160
|
+
### Inspect configuration with `cfg.print()`
|
|
194
161
|
|
|
195
|
-
|
|
162
|
+
The returned configuration contains decrypted secrets. Use `cfg.print()` when inspecting it:
|
|
196
163
|
|
|
197
164
|
```javascript
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
key: ""
|
|
201
|
-
}
|
|
202
|
-
};
|
|
165
|
+
const cfg = Configre(join(import.meta.dirname, "config"));
|
|
166
|
+
cfg.print();
|
|
203
167
|
```
|
|
204
168
|
|
|
205
|
-
|
|
169
|
+
Enable its debug output with:
|
|
206
170
|
|
|
207
171
|
```bash
|
|
208
|
-
node
|
|
172
|
+
DEBUG=Configre:* node app.mjs --config=production
|
|
209
173
|
```
|
|
210
174
|
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
+
The constructor API is also available:
|
|
218
180
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
+
### Apply environment variables explicitly
|
|
230
190
|
|
|
231
|
-
|
|
191
|
+
Use `applyConfigEnv(cfg.env)` when a library reads its settings from `process.env`:
|
|
232
192
|
|
|
233
|
-
```
|
|
234
|
-
|
|
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
|
-
|
|
201
|
+
For CommonJS, use `const { applyConfigEnv } = require("configre")`.
|
|
238
202
|
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
+
## Advanced reference
|
|
247
208
|
|
|
248
|
-
|
|
209
|
+
<details>
|
|
210
|
+
<summary>Secret formats, identities, Git behavior, and recovery</summary>
|
|
249
211
|
|
|
250
|
-
###
|
|
212
|
+
### Secret files and loading
|
|
251
213
|
|
|
252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
264
|
-
git pull --ff-only
|
|
265
|
-
node demo/secrets/demo.js --config=truco
|
|
266
|
-
```
|
|
222
|
+
### Identities and registration
|
|
267
223
|
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
+
### Updates, revocation, and recovery
|
|
283
237
|
|
|
284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
##
|
|
258
|
+
## Resources and contributions
|
|
315
259
|
|
|
316
|
-
|
|
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
|
-
|
|
262
|
+
To add the Configre skill to your coding agent:
|
|
321
263
|
|
|
322
|
-
|
|
264
|
+
```bash
|
|
265
|
+
npx skills add https://github.com/clasen/Configre --skill configre
|
|
266
|
+
```
|
|
323
267
|
|
|
324
|
-
|
|
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
|
-
##
|
|
270
|
+
## License
|
|
327
271
|
|
|
328
272
|
The MIT License (MIT)
|
|
329
273
|
|
package/demo/config/hostname.cjs
CHANGED
|
File without changes
|
package/demo/config/index.cjs
CHANGED
|
File without changes
|
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
|
-
|
|
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.
|
|
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
|
+
}
|
package/skills/configre/SKILL.md
CHANGED
|
@@ -1,126 +1,100 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: configre
|
|
3
|
-
description: Set up
|
|
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,
|
|
6
|
+
tags: [nodejs, config, environment, settings, secrets]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Configre
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Load public defaults, extend or overwrite them by profile, and merge optional secrets synchronously.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Setup and loading
|
|
14
14
|
|
|
15
|
-
|
|
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
|
-
|
|
19
|
+
```javascript
|
|
20
|
+
import Configre from "configre";
|
|
21
|
+
import { join } from "node:path";
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
npm install configre --save
|
|
23
|
+
const cfg = Configre(join(import.meta.dirname, "config"));
|
|
21
24
|
```
|
|
22
25
|
|
|
23
|
-
|
|
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
|
-
|
|
31
|
+
## Profiles and merge behavior
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
41
|
+
## Secrets
|
|
53
42
|
|
|
54
|
-
Create `
|
|
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
|
-
|
|
60
|
-
password: 'prod-secret'
|
|
61
|
-
}
|
|
47
|
+
db: { password: "" },
|
|
48
|
+
api: { key: "" }
|
|
62
49
|
};
|
|
63
50
|
```
|
|
64
51
|
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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).
|
package/test/index.test.js
CHANGED
|
@@ -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
|
-
|
|
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);
|