@lindorm/config 0.2.9 → 0.3.0
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/CHANGELOG.md +11 -0
- package/README.md +96 -301
- package/dist/cli.js +17 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -17
- package/dist/index.js.map +1 -1
- package/dist/{utils/private → internal}/coerce-all.d.ts +1 -1
- package/dist/internal/coerce-all.d.ts.map +1 -0
- package/dist/internal/coerce-all.js +35 -0
- package/dist/internal/coerce-all.js.map +1 -0
- package/dist/{utils/private → internal}/find-process-env-value.d.ts +1 -1
- package/dist/internal/find-process-env-value.d.ts.map +1 -0
- package/dist/internal/find-process-env-value.js +10 -0
- package/dist/internal/find-process-env-value.js.map +1 -0
- package/dist/internal/index.d.ts +5 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +5 -0
- package/dist/internal/index.js.map +1 -0
- package/dist/internal/load-config.d.ts +4 -0
- package/dist/internal/load-config.d.ts.map +1 -0
- package/dist/internal/load-config.js +9 -0
- package/dist/internal/load-config.js.map +1 -0
- package/dist/internal/load-node-config.d.ts +4 -0
- package/dist/internal/load-node-config.d.ts.map +1 -0
- package/dist/internal/load-node-config.js +13 -0
- package/dist/internal/load-node-config.js.map +1 -0
- package/dist/internal/load-npm-info.d.ts +6 -0
- package/dist/internal/load-npm-info.d.ts.map +1 -0
- package/dist/internal/load-npm-info.js +47 -0
- package/dist/internal/load-npm-info.js.map +1 -0
- package/dist/internal/merge-object-with-process-env.d.ts +4 -0
- package/dist/internal/merge-object-with-process-env.d.ts.map +1 -0
- package/dist/internal/merge-object-with-process-env.js +20 -0
- package/dist/internal/merge-object-with-process-env.js.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -17
- package/dist/types/index.js.map +1 -1
- package/dist/types/types.d.ts +1 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +1 -2
- package/dist/utils/configuration.d.ts +6 -3
- package/dist/utils/configuration.d.ts.map +1 -1
- package/dist/utils/configuration.js +12 -22
- package/dist/utils/configuration.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -17
- package/dist/utils/index.js.map +1 -1
- package/package.json +19 -13
- package/vitest.config.mjs +3 -0
- package/dist/utils/private/coerce-all.d.ts.map +0 -1
- package/dist/utils/private/coerce-all.js +0 -39
- package/dist/utils/private/coerce-all.js.map +0 -1
- package/dist/utils/private/find-process-env-value.d.ts.map +0 -1
- package/dist/utils/private/find-process-env-value.js +0 -14
- package/dist/utils/private/find-process-env-value.js.map +0 -1
- package/dist/utils/private/index.d.ts +0 -4
- package/dist/utils/private/index.d.ts.map +0 -1
- package/dist/utils/private/index.js +0 -20
- package/dist/utils/private/index.js.map +0 -1
- package/dist/utils/private/load-config.d.ts +0 -4
- package/dist/utils/private/load-config.d.ts.map +0 -1
- package/dist/utils/private/load-config.js +0 -16
- package/dist/utils/private/load-config.js.map +0 -1
- package/dist/utils/private/load-node-config.d.ts +0 -4
- package/dist/utils/private/load-node-config.d.ts.map +0 -1
- package/dist/utils/private/load-node-config.js +0 -17
- package/dist/utils/private/load-node-config.js.map +0 -1
- package/dist/utils/private/merge-object-with-process-env.d.ts +0 -4
- package/dist/utils/private/merge-object-with-process-env.d.ts.map +0 -1
- package/dist/utils/private/merge-object-with-process-env.js +0 -24
- package/dist/utils/private/merge-object-with-process-env.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [0.3.0](https://github.com/lindorm-io/monorepo/compare/@lindorm/config@0.2.10...@lindorm/config@0.3.0) (2026-05-02)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **config:** resolve npm.package via explicit scope, not cwd ([279b633](https://github.com/lindorm-io/monorepo/commit/279b63307beb103a82f15aeb63e637f168ef9635))
|
|
11
|
+
- migrate 20 packages from jest to vitest ([d8bfda8](https://github.com/lindorm-io/monorepo/commit/d8bfda8854dc1cb9537ba0b3e47ec4e4c7bded08))
|
|
12
|
+
|
|
13
|
+
## [0.2.10](https://github.com/lindorm-io/monorepo/compare/@lindorm/config@0.2.9...@lindorm/config@0.2.10) (2026-04-19)
|
|
14
|
+
|
|
15
|
+
**Note:** Version bump only for package @lindorm/config
|
|
16
|
+
|
|
6
17
|
## [0.2.9](https://github.com/lindorm-io/monorepo/compare/@lindorm/config@0.2.8...@lindorm/config@0.2.9) (2026-04-15)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @lindorm/config
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @lindorm/config
|
|
2
2
|
|
|
3
|
-
Type-safe configuration
|
|
3
|
+
Type-safe runtime configuration loader that merges YAML files, `.env` files, process environment, and `NODE_CONFIG`, then validates the result with a Zod schema.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,55 +8,60 @@ Type-safe configuration management with environment variable overrides, YAML fil
|
|
|
8
8
|
npm install @lindorm/config
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
This package is ESM-only. Import it with `import`; `require()` is not supported.
|
|
12
|
+
|
|
11
13
|
## Features
|
|
12
14
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
15
|
+
- Validates the merged configuration with a Zod schema you supply.
|
|
16
|
+
- Loads YAML files from a `config/` directory via the [`config`](https://www.npmjs.com/package/config) (node-config) package, layered by `NODE_ENV`.
|
|
17
|
+
- Loads `.env` and `.env.${NODE_ENV}` via [`@dotenvx/dotenvx`](https://www.npmjs.com/package/@dotenvx/dotenvx).
|
|
18
|
+
- Overrides any nested config value from a single environment variable named in `CONSTANT_CASE`.
|
|
19
|
+
- Accepts a full configuration object as JSON in the `NODE_CONFIG` environment variable.
|
|
20
|
+
- Coerces string inputs to the schema's primitive types (numbers, booleans, dates, bigints) and parses JSON-encoded arrays and objects.
|
|
21
|
+
- Resolves the running package's `name` and `version` and exposes them as `config.npm.package`.
|
|
22
|
+
- Ships a `config` CLI for converting a local `.node_config` file into a `NODE_CONFIG` env string.
|
|
21
23
|
|
|
22
|
-
## Quick
|
|
24
|
+
## Quick start
|
|
23
25
|
|
|
24
26
|
```typescript
|
|
25
27
|
import { configuration } from "@lindorm/config";
|
|
26
28
|
import { z } from "zod";
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
export const config = configuration(
|
|
31
|
+
{
|
|
32
|
+
server: z.object({
|
|
33
|
+
port: z.number().default(3000),
|
|
34
|
+
host: z.string().default("localhost"),
|
|
35
|
+
}),
|
|
36
|
+
database: z.object({
|
|
37
|
+
url: z.string(),
|
|
38
|
+
poolSize: z.number().default(10),
|
|
39
|
+
}),
|
|
40
|
+
features: z.array(z.string()).default([]),
|
|
41
|
+
isProduction: z.boolean().default(false),
|
|
42
|
+
},
|
|
43
|
+
{ scope: import.meta.url },
|
|
44
|
+
);
|
|
45
|
+
|
|
43
46
|
console.log(config.server.port); // number
|
|
44
47
|
console.log(config.database.url); // string
|
|
45
|
-
console.log(config.npm.package.name); //
|
|
48
|
+
console.log(config.npm.package.name); // resolved from the nearest package.json
|
|
46
49
|
```
|
|
47
50
|
|
|
48
|
-
## Configuration
|
|
51
|
+
## Configuration sources
|
|
52
|
+
|
|
53
|
+
`configuration(schema, options?)` runs once at call time and resolves values in this order — later sources override earlier ones for the same key:
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
1. `.env` and `.env.${NODE_ENV}` files (loaded into `process.env` via `@dotenvx/dotenvx`).
|
|
56
|
+
2. YAML files in `./config/` (loaded by the `config` npm package; see its docs for the full list of supported file types and search paths). Keys are normalised to `camelCase`.
|
|
57
|
+
3. `process.env` entries that match a schema key in `CONSTANT_CASE` (nested keys joined with `_`).
|
|
58
|
+
4. `NODE_CONFIG` — a JSON object passed in a single environment variable.
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
2. **Environment Variables** (automatic override)
|
|
54
|
-
3. **NODE_CONFIG** environment variable (JSON object)
|
|
55
|
-
4. **NPM Package Information** (name and version)
|
|
60
|
+
The merged object is then coerced and validated against the Zod schema.
|
|
56
61
|
|
|
57
|
-
###
|
|
62
|
+
### YAML files
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
Place files in a `config/` directory at the process working directory. Keys may be written in `snake_case` or `camelCase`; both are normalised to `camelCase`.
|
|
60
65
|
|
|
61
66
|
```yaml
|
|
62
67
|
# config/default.yml
|
|
@@ -87,321 +92,111 @@ database:
|
|
|
87
92
|
is_production: true
|
|
88
93
|
```
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
### Environment variables
|
|
96
|
+
|
|
97
|
+
Each schema key maps to a single environment variable in `CONSTANT_CASE`. Nested keys are joined with `_`.
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
| Schema key | Environment variable |
|
|
100
|
+
| ----------------------- | ------------------------ |
|
|
101
|
+
| `server.port` | `SERVER_PORT` |
|
|
102
|
+
| `database.poolSize` | `DATABASE_POOL_SIZE` |
|
|
103
|
+
| `nested.some.deepValue` | `NESTED_SOME_DEEP_VALUE` |
|
|
95
104
|
|
|
96
|
-
|
|
105
|
+
Values are passed through `safelyParse` from `@lindorm/utils`, so JSON-encoded arrays and objects are decoded automatically:
|
|
97
106
|
|
|
98
107
|
```bash
|
|
99
|
-
# Override server.port
|
|
100
108
|
SERVER_PORT=8080
|
|
101
|
-
|
|
102
|
-
# Override database.url
|
|
103
109
|
DATABASE_URL=postgresql://prod-server/myapp
|
|
104
|
-
|
|
105
|
-
# Override nested values
|
|
106
110
|
DATABASE_POOL_SIZE=50
|
|
107
|
-
|
|
108
|
-
# Arrays (JSON format)
|
|
109
|
-
FEATURES='["feature1", "feature2", "feature3"]'
|
|
110
|
-
|
|
111
|
-
# Booleans
|
|
111
|
+
FEATURES='["feature1","feature2","feature3"]'
|
|
112
112
|
IS_PRODUCTION=true
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
- All uppercase for environment variables
|
|
115
|
+
### `.env` files
|
|
116
|
+
|
|
117
|
+
`@dotenvx/dotenvx` is invoked at the start of `configuration()` and loads, in order:
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
- `
|
|
122
|
-
- `database.poolSize` → `DATABASE_POOL_SIZE`
|
|
123
|
-
- `nested.some.deepValue` → `NESTED_SOME_DEEP_VALUE`
|
|
119
|
+
- `.env.${NODE_ENV}` (only when `NODE_ENV` is set)
|
|
120
|
+
- `.env`
|
|
124
121
|
|
|
125
|
-
|
|
122
|
+
The values become part of `process.env` and follow the same `CONSTANT_CASE` rules as above.
|
|
126
123
|
|
|
127
|
-
|
|
124
|
+
### `NODE_CONFIG`
|
|
125
|
+
|
|
126
|
+
`NODE_CONFIG` must be a JSON object — the string has to start with `{` and end with `}`, otherwise `configuration()` throws. Anything declared here overrides everything else.
|
|
128
127
|
|
|
129
128
|
```bash
|
|
130
129
|
NODE_CONFIG='{"server":{"port":9000},"features":["feat1","feat2"]}'
|
|
131
130
|
```
|
|
132
131
|
|
|
133
|
-
|
|
132
|
+
## Type coercion
|
|
134
133
|
|
|
135
|
-
|
|
134
|
+
Before validation the schema is rewritten so primitive leaves use Zod's coercing variants. Arrays and objects are walked recursively, and `optional`, `nullable`, and `default` wrappers are preserved.
|
|
136
135
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
| Zod type | Coerced via |
|
|
137
|
+
| --------------- | ------------------------------ |
|
|
138
|
+
| `z.string()` | `z.coerce.string()` |
|
|
139
|
+
| `z.number()` | `z.coerce.number()` |
|
|
140
|
+
| `z.boolean()` | `z.coerce.boolean()` |
|
|
141
|
+
| `z.date()` | `z.coerce.date()` |
|
|
142
|
+
| `z.bigint()` | `z.coerce.bigint()` |
|
|
143
|
+
| `z.array(...)` | recurses into the element type |
|
|
144
|
+
| `z.object(...)` | recurses into each property |
|
|
141
145
|
|
|
142
|
-
|
|
143
|
-
DATABASE_URL=postgresql://prod-server/myapp
|
|
144
|
-
SERVER_PORT=8080
|
|
145
|
-
```
|
|
146
|
+
Other Zod types (such as `z.enum`, `z.union`, `z.record`, `z.literal`) are passed through unchanged — values reaching them must already match the expected runtime type. JSON-encoded strings from environment variables are decoded before validation runs, so an array or object env var only needs the surrounding type to be `z.array(...)` or `z.object(...)`.
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
- `.env` - Always loaded
|
|
149
|
-
- `.env.{NODE_ENV}` - Environment-specific overrides
|
|
148
|
+
## NPM identity
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
`config.npm.package.name` and `config.npm.package.version` are resolved in this order:
|
|
152
151
|
|
|
153
|
-
|
|
152
|
+
1. If `options.scope` is provided, `loadNpmInfo` walks up from that path and reads the nearest `package.json`. Pass `import.meta.url` from your entry file — this is deterministic regardless of `cwd`, including under `node dist/index.js`, Docker `CMD`, systemd, and inside test runners.
|
|
153
|
+
2. Otherwise it falls back to the `npm_package_name` / `npm_package_version` environment variables that `npm run` populates.
|
|
154
|
+
3. If neither resolves, both fields are empty strings.
|
|
154
155
|
|
|
155
156
|
```typescript
|
|
156
|
-
|
|
157
|
-
import { z } from "zod";
|
|
158
|
-
|
|
159
|
-
const config = configuration({
|
|
160
|
-
app: z.object({
|
|
161
|
-
name: z.string(),
|
|
162
|
-
version: z.string().optional(),
|
|
163
|
-
environment: z.enum(["development", "staging", "production"]),
|
|
164
|
-
}),
|
|
165
|
-
|
|
166
|
-
server: z.object({
|
|
167
|
-
port: z.number().min(1).max(65535),
|
|
168
|
-
host: z.string().ip(), // IP address validation
|
|
169
|
-
cors: z.object({
|
|
170
|
-
enabled: z.boolean().default(true),
|
|
171
|
-
origins: z.array(z.string().url()).default([]),
|
|
172
|
-
}),
|
|
173
|
-
}),
|
|
174
|
-
|
|
175
|
-
database: z.object({
|
|
176
|
-
primary: z.object({
|
|
177
|
-
host: z.string(),
|
|
178
|
-
port: z.number().default(5432),
|
|
179
|
-
name: z.string(),
|
|
180
|
-
user: z.string(),
|
|
181
|
-
password: z.string(),
|
|
182
|
-
ssl: z.boolean().default(false),
|
|
183
|
-
}),
|
|
184
|
-
replica: z.object({
|
|
185
|
-
host: z.string(),
|
|
186
|
-
port: z.number(),
|
|
187
|
-
}).optional(),
|
|
188
|
-
}),
|
|
189
|
-
|
|
190
|
-
redis: z.object({
|
|
191
|
-
url: z.string().url(),
|
|
192
|
-
ttl: z.number().default(3600),
|
|
193
|
-
}),
|
|
194
|
-
|
|
195
|
-
logging: z.object({
|
|
196
|
-
level: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
197
|
-
format: z.enum(["json", "pretty"]).default("json"),
|
|
198
|
-
}),
|
|
199
|
-
|
|
200
|
-
features: z.record(z.boolean()).default({}),
|
|
201
|
-
});
|
|
157
|
+
const config = configuration(schema, { scope: import.meta.url });
|
|
202
158
|
```
|
|
203
159
|
|
|
204
|
-
|
|
160
|
+
## CLI
|
|
205
161
|
|
|
206
|
-
|
|
207
|
-
// Type-safe access to configuration
|
|
208
|
-
if (config.server.cors.enabled) {
|
|
209
|
-
app.use(cors({ origins: config.server.cors.origins }));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Database connection
|
|
213
|
-
const dbConfig = {
|
|
214
|
-
host: config.database.primary.host,
|
|
215
|
-
port: config.database.primary.port,
|
|
216
|
-
database: config.database.primary.name,
|
|
217
|
-
user: config.database.primary.user,
|
|
218
|
-
password: config.database.primary.password,
|
|
219
|
-
ssl: config.database.primary.ssl,
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// Feature flags
|
|
223
|
-
if (config.features.newDashboard) {
|
|
224
|
-
app.use("/dashboard", newDashboardRouter);
|
|
225
|
-
}
|
|
162
|
+
The package installs a `config` binary with a single command:
|
|
226
163
|
|
|
227
|
-
// Logging configuration
|
|
228
|
-
logger.setLevel(config.logging.level);
|
|
229
164
|
```
|
|
230
|
-
|
|
231
|
-
### Working with Arrays and Objects
|
|
232
|
-
|
|
233
|
-
Environment variables support JSON format for complex types:
|
|
234
|
-
|
|
235
|
-
```bash
|
|
236
|
-
# Array of strings
|
|
237
|
-
CORS_ORIGINS='["https://app.example.com", "https://admin.example.com"]'
|
|
238
|
-
|
|
239
|
-
# Object
|
|
240
|
-
FEATURES='{"newDashboard": true, "betaApi": false}'
|
|
241
|
-
|
|
242
|
-
# Array of objects
|
|
243
|
-
SERVERS='[{"host": "server1.com", "port": 8080}, {"host": "server2.com", "port": 8081}]'
|
|
165
|
+
config node_config [-f, --file <file>]
|
|
244
166
|
```
|
|
245
167
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
The package includes a CLI tool for working with configuration:
|
|
249
|
-
|
|
250
|
-
### Convert Config to NODE_CONFIG
|
|
168
|
+
It reads `./.node_config` by default and prints a `NODE_CONFIG='...'` line you can paste into your environment. JSON, YAML, and YML files are supported; the extension determines the parser. When `--file` is omitted and `./.node_config` does not exist, the CLI also tries `.node_config.json`, `.node_config.yml`, and `.node_config.yaml` in that order.
|
|
251
169
|
|
|
252
170
|
```bash
|
|
253
|
-
|
|
254
|
-
echo '{"server": {"port": 9000}}' > .node_config
|
|
255
|
-
|
|
256
|
-
# Generate NODE_CONFIG environment variable
|
|
171
|
+
echo '{"server":{"port":9000}}' > .node_config
|
|
257
172
|
npx config node_config
|
|
258
|
-
#
|
|
259
|
-
|
|
260
|
-
#
|
|
261
|
-
export $(npx config node_config)
|
|
173
|
+
# Insert the following into your env:
|
|
174
|
+
#
|
|
175
|
+
# NODE_CONFIG='{"server":{"port":9000}}'
|
|
262
176
|
```
|
|
263
177
|
|
|
264
|
-
|
|
265
|
-
- Docker containers
|
|
266
|
-
- CI/CD pipelines
|
|
267
|
-
- Deployment scripts
|
|
268
|
-
|
|
269
|
-
## Best Practices
|
|
178
|
+
## API
|
|
270
179
|
|
|
271
|
-
###
|
|
272
|
-
|
|
273
|
-
Define your schema in a separate file for reusability:
|
|
180
|
+
### `configuration(schema, options?)`
|
|
274
181
|
|
|
275
182
|
```typescript
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
server: z.object({
|
|
281
|
-
port: z.number().default(3000),
|
|
282
|
-
host: z.string().default("localhost"),
|
|
283
|
-
}),
|
|
284
|
-
// ... rest of schema
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
// config/index.ts
|
|
288
|
-
import { configuration } from "@lindorm/config";
|
|
289
|
-
import { configSchema } from "./schema";
|
|
290
|
-
|
|
291
|
-
export const config = configuration(configSchema);
|
|
183
|
+
const configuration: <T extends Record<string, z.ZodType>>(
|
|
184
|
+
schema: T,
|
|
185
|
+
options?: ConfigurationOptions,
|
|
186
|
+
) => NpmInformation & z.infer<z.ZodObject<T>>;
|
|
292
187
|
```
|
|
293
188
|
|
|
294
|
-
|
|
189
|
+
Loads, merges, coerces, and validates configuration. Throws if `NODE_CONFIG` is not a valid JSON object or if Zod validation fails. The returned object is the parsed schema plus an `npm.package` field.
|
|
295
190
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
```yaml
|
|
299
|
-
# config/default.yml
|
|
300
|
-
logging:
|
|
301
|
-
level: debug
|
|
302
|
-
format: pretty
|
|
303
|
-
|
|
304
|
-
# config/production.yml
|
|
305
|
-
logging:
|
|
306
|
-
level: info
|
|
307
|
-
format: json
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### 3. Secret Management
|
|
311
|
-
|
|
312
|
-
Never commit secrets to config files. Use environment variables:
|
|
313
|
-
|
|
314
|
-
```yaml
|
|
315
|
-
# config/default.yml
|
|
316
|
-
database:
|
|
317
|
-
host: localhost
|
|
318
|
-
port: 5432
|
|
319
|
-
name: myapp
|
|
320
|
-
# DON'T put passwords here
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
```bash
|
|
324
|
-
# .env (add to .gitignore)
|
|
325
|
-
DATABASE_USER=myuser
|
|
326
|
-
DATABASE_PASSWORD=secret123
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### 4. Validation
|
|
330
|
-
|
|
331
|
-
Use Zod's features for comprehensive validation:
|
|
191
|
+
### `ConfigurationOptions`
|
|
332
192
|
|
|
333
193
|
```typescript
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
url: z.string().url(),
|
|
338
|
-
apiKey: z.string().min(32),
|
|
339
|
-
retryAttempts: z.number().int().positive().max(10),
|
|
340
|
-
});
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## Automatic Type Coercion
|
|
344
|
-
|
|
345
|
-
The package automatically converts string values to the correct types:
|
|
346
|
-
|
|
347
|
-
| Zod Type | Input | Output |
|
|
348
|
-
|----------|-------|--------|
|
|
349
|
-
| z.number() | "123" | 123 |
|
|
350
|
-
| z.boolean() | "true" | true |
|
|
351
|
-
| z.array() | '["a","b"]' | ["a", "b"] |
|
|
352
|
-
| z.object() | '{"a":1}' | { a: 1 } |
|
|
353
|
-
| z.bigint() | "9007199254740991" | 9007199254740991n |
|
|
354
|
-
| z.date() | "2023-01-01" | Date object |
|
|
355
|
-
|
|
356
|
-
## NPM Package Information
|
|
357
|
-
|
|
358
|
-
The configuration automatically includes package information:
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
const config = configuration({ /* your schema */ });
|
|
362
|
-
|
|
363
|
-
console.log(config.npm.package.name); // from package.json
|
|
364
|
-
console.log(config.npm.package.version); // from package.json
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
## Error Handling
|
|
368
|
-
|
|
369
|
-
Configuration errors are thrown during initialization:
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
try {
|
|
373
|
-
const config = configuration({
|
|
374
|
-
required: z.string(), // No default
|
|
375
|
-
});
|
|
376
|
-
} catch (error) {
|
|
377
|
-
console.error("Configuration error:", error);
|
|
378
|
-
// Error will include details about missing required fields
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
## Testing
|
|
383
|
-
|
|
384
|
-
For testing, you can override configuration using NODE_ENV and environment variables:
|
|
385
|
-
|
|
386
|
-
```typescript
|
|
387
|
-
// test/setup.ts
|
|
388
|
-
process.env.NODE_ENV = "test";
|
|
389
|
-
process.env.DATABASE_URL = "postgresql://localhost/test";
|
|
390
|
-
process.env.REDIS_URL = "redis://localhost:6379/1";
|
|
194
|
+
type ConfigurationOptions = {
|
|
195
|
+
scope?: string;
|
|
196
|
+
};
|
|
391
197
|
```
|
|
392
198
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
```yaml
|
|
396
|
-
database:
|
|
397
|
-
url: postgresql://localhost/test
|
|
398
|
-
|
|
399
|
-
redis:
|
|
400
|
-
url: redis://localhost:6379/1
|
|
401
|
-
|
|
402
|
-
logging:
|
|
403
|
-
level: error # Reduce noise in tests
|
|
404
|
-
```
|
|
199
|
+
`scope` is a path or `file://` URL — typically `import.meta.url` at the call site — used to locate the `package.json` that describes the running process. See [NPM identity](#npm-identity) for the resolution rules.
|
|
405
200
|
|
|
406
201
|
## License
|
|
407
202
|
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const path_1 = require("path");
|
|
8
|
-
commander_1.program.name("config").description("CLI for managing configuration files");
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { load } from "js-yaml";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
program.name("config").description("CLI for managing configuration files");
|
|
9
7
|
const parseFile = (file) => {
|
|
10
8
|
if (file.endsWith(".json")) {
|
|
11
9
|
return parseJson(file);
|
|
@@ -15,31 +13,31 @@ const parseFile = (file) => {
|
|
|
15
13
|
}
|
|
16
14
|
throw new Error("Unsupported file type");
|
|
17
15
|
};
|
|
18
|
-
const parseJson = (file) => JSON.parse(
|
|
19
|
-
const parseYaml = (file) =>
|
|
20
|
-
|
|
16
|
+
const parseJson = (file) => JSON.parse(readFileSync(file, "utf-8"));
|
|
17
|
+
const parseYaml = (file) => load(readFileSync(file, "utf-8"));
|
|
18
|
+
program
|
|
21
19
|
.command("node_config")
|
|
22
20
|
.description("Generate a NODE_CONFIG environment string from existing .node_config file")
|
|
23
21
|
.option("-f, --file <file>", "Path to the .node_config(?.json|yml|yaml) file", ".node_config")
|
|
24
22
|
.action((options) => {
|
|
25
23
|
try {
|
|
26
24
|
if (options.file !== ".node_config" &&
|
|
27
|
-
!
|
|
25
|
+
!existsSync(join(process.cwd(), options.file))) {
|
|
28
26
|
throw new Error(`File ${options.file} does not exist. Please provide a valid path to a .node_config file.`);
|
|
29
27
|
}
|
|
30
28
|
let file = options.file;
|
|
31
29
|
let result = "";
|
|
32
|
-
if (
|
|
30
|
+
if (existsSync(join(process.cwd(), options.file))) {
|
|
33
31
|
file = options.file;
|
|
34
32
|
}
|
|
35
|
-
else if (
|
|
36
|
-
file =
|
|
33
|
+
else if (existsSync(join(process.cwd(), ".node_config.json"))) {
|
|
34
|
+
file = join(process.cwd(), ".node_config.json");
|
|
37
35
|
}
|
|
38
|
-
else if (
|
|
39
|
-
file =
|
|
36
|
+
else if (existsSync(join(process.cwd(), ".node_config.yml"))) {
|
|
37
|
+
file = join(process.cwd(), ".node_config.yml");
|
|
40
38
|
}
|
|
41
|
-
else if (
|
|
42
|
-
file =
|
|
39
|
+
else if (existsSync(join(process.cwd(), ".node_config.yaml"))) {
|
|
40
|
+
file = join(process.cwd(), ".node_config.yaml");
|
|
43
41
|
}
|
|
44
42
|
else {
|
|
45
43
|
throw new Error("No .node_config file found. Please provide a valid path to a .node_config file.");
|
|
@@ -52,5 +50,5 @@ commander_1.program
|
|
|
52
50
|
process.exit(1);
|
|
53
51
|
}
|
|
54
52
|
});
|
|
55
|
-
|
|
53
|
+
program.parse();
|
|
56
54
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./utils";
|
|
1
|
+
export * from "./utils/index.js";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./utils"), exports);
|
|
1
|
+
export * from "./utils/index.js";
|
|
18
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coerce-all.d.ts","sourceRoot":"","sources":["../../src/internal/coerce-all.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAG,CA2C1D,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const coerceAll = (schema) => {
|
|
3
|
+
const def = schema._zod?.def ?? schema._def;
|
|
4
|
+
switch (def.type) {
|
|
5
|
+
case "string":
|
|
6
|
+
return z.coerce.string();
|
|
7
|
+
case "number":
|
|
8
|
+
return z.coerce.number();
|
|
9
|
+
case "boolean":
|
|
10
|
+
return z.coerce.boolean();
|
|
11
|
+
case "date":
|
|
12
|
+
return z.coerce.date();
|
|
13
|
+
case "bigint":
|
|
14
|
+
return z.coerce.bigint();
|
|
15
|
+
case "array":
|
|
16
|
+
return z.array(coerceAll(def.element));
|
|
17
|
+
case "object": {
|
|
18
|
+
const shape = def.shape;
|
|
19
|
+
const newShape = {};
|
|
20
|
+
for (const key in shape) {
|
|
21
|
+
newShape[key] = coerceAll(shape[key]);
|
|
22
|
+
}
|
|
23
|
+
return z.object(newShape);
|
|
24
|
+
}
|
|
25
|
+
case "optional":
|
|
26
|
+
return coerceAll(def.innerType).optional();
|
|
27
|
+
case "nullable":
|
|
28
|
+
return coerceAll(def.innerType).nullable();
|
|
29
|
+
case "default":
|
|
30
|
+
return coerceAll(def.innerType).default(def.defaultValue);
|
|
31
|
+
default:
|
|
32
|
+
return schema;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=coerce-all.js.map
|