configate 1.0.0 → 1.2.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/README.md +54 -7
- package/dist/deepFreezeConfig.js +1 -1
- package/dist/importConfigFiles.d.ts +2 -1
- package/dist/importConfigFiles.js +35 -4
- package/dist/loadConfig.d.ts +11 -5
- package/dist/loadConfig.js +11 -5
- package/dist/makeSecureDeepProxy.js +4 -2
- package/package.json +16 -2
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ Use the `loadConfig` function once, to load and merge configurations from specif
|
|
|
45
45
|
With parameters you can define:
|
|
46
46
|
- `configDirs`: (Default: `['${current-working-directory}/config']`) An array of directories to load configurations from
|
|
47
47
|
- `environment`: (Default: `process.env.NODE_ENV`) The environment to load specific configurations for
|
|
48
|
+
- `variant`: (Default: `undefined`) Allows to define 2nd dimension of configs above environment. Will load configs matching `*-{variant}` pattern
|
|
48
49
|
- `fileExtensions`: (Default: `['ts', 'js']`) An array of file extensions to load configurations from
|
|
49
50
|
- `throwOnUndefinedProp`: (Default: `true`) If true, throws an error when accessing undefined properties
|
|
50
51
|
- `freezeConfig`: (Default: `true`) If true, freezes the configuration object to make it immutable
|
|
@@ -123,8 +124,10 @@ Do the same for other environments if you need to override more properties for t
|
|
|
123
124
|
When you need to define secrets in configuration, they should be defined via environment variables.
|
|
124
125
|
|
|
125
126
|
Create `custom-environment-variables.ts` file in `config` directory and export a `config` object.
|
|
127
|
+
|
|
126
128
|
- use `process.env` to read environment variables and assign to property in your configuration
|
|
127
|
-
- this file
|
|
129
|
+
- this file is merged last within its directory, so its defined values override other files in that directory
|
|
130
|
+
- when loading multiple directories, later directories can override these values.
|
|
128
131
|
|
|
129
132
|
```ts
|
|
130
133
|
// config/custom-environment-variables.ts
|
|
@@ -153,14 +156,54 @@ Add this to `.gitignore` to prevent committing local configuration files:
|
|
|
153
156
|
**/config/local*
|
|
154
157
|
```
|
|
155
158
|
|
|
159
|
+
### Second layer of variant configurations
|
|
160
|
+
|
|
161
|
+
Some applications may need another layer of overrides besides environments (for example: regions, brands, customers etc).
|
|
162
|
+
Pass the `variant` option to `loadConfig` and create files that follow the `*-{variant}` naming convention to scope those overrides.
|
|
163
|
+
|
|
164
|
+
When `variant` is set, Configate looks for these optional files on top of the usual ones:
|
|
165
|
+
- `default-{variant}.ext`
|
|
166
|
+
- `{environment}-{variant}.ext`
|
|
167
|
+
- `local-{variant}.ext`
|
|
168
|
+
- `local-{environment}-{variant}.ext`
|
|
169
|
+
|
|
170
|
+
Example setup:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
config/
|
|
174
|
+
default.ts
|
|
175
|
+
default-customerA.ts
|
|
176
|
+
default-customerB.ts
|
|
177
|
+
production.ts
|
|
178
|
+
production-customerA.ts
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// src/config.ts
|
|
183
|
+
import { loadConfig } from 'configate';
|
|
184
|
+
|
|
185
|
+
export const { config } = await loadConfig<AppConfig>({
|
|
186
|
+
environment: 'production',
|
|
187
|
+
variant: 'customerA',
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This keeps the base config reusable while still letting you target per-variant overrides without duplicating entire files.
|
|
192
|
+
|
|
156
193
|
### Order of loading configuration files
|
|
157
194
|
|
|
195
|
+
The following order applies separately within each configuration directory.
|
|
196
|
+
|
|
158
197
|
```
|
|
159
|
-
default.ext
|
|
160
|
-
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
198
|
+
1. default.ext
|
|
199
|
+
2. default-{variant}.ext
|
|
200
|
+
3. {environment}.ext
|
|
201
|
+
4. {environment}-{variant}.ext
|
|
202
|
+
5. local.ext
|
|
203
|
+
6. local-{variant}.ext
|
|
204
|
+
7. local-{environment}.ext
|
|
205
|
+
8. local-{environment}-{variant}.ext
|
|
206
|
+
9. custom-environment-variables.ext
|
|
164
207
|
```
|
|
165
208
|
|
|
166
209
|
### Using unsecure config
|
|
@@ -211,6 +254,11 @@ const host = config.database.host;
|
|
|
211
254
|
|
|
212
255
|
#### Loading configurations from multiple directories
|
|
213
256
|
|
|
257
|
+
Directories are fully loaded and merged in `configDirs` order.
|
|
258
|
+
Values from later directories override matching properties from earlier directories.
|
|
259
|
+
|
|
260
|
+
For example, a value in the second directory's `default.ts` can override the same property from the first directory's `custom-environment-variables.ts`.
|
|
261
|
+
|
|
214
262
|
```ts
|
|
215
263
|
import { loadConfig } from 'configate';
|
|
216
264
|
|
|
@@ -221,4 +269,3 @@ export const { config } = await loadConfig<TestConfig>({
|
|
|
221
269
|
],
|
|
222
270
|
});
|
|
223
271
|
```
|
|
224
|
-
|
package/dist/deepFreezeConfig.js
CHANGED
|
@@ -4,7 +4,7 @@ import { isObject } from "./common.js";
|
|
|
4
4
|
*/
|
|
5
5
|
export function deepFreezeConfig(config) {
|
|
6
6
|
for (const key in config) {
|
|
7
|
-
if (isObject(config[key])) {
|
|
7
|
+
if (isObject(config[key]) || Array.isArray(config[key])) {
|
|
8
8
|
config[key] = deepFreezeConfig(config[key]);
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -2,10 +2,11 @@ import type { DefaultConfig, Environment, FileExtension } from './common.ts';
|
|
|
2
2
|
/**
|
|
3
3
|
* Import multiple config files in defined order and merge them deeply together
|
|
4
4
|
*/
|
|
5
|
-
export declare function importConfigFiles<Config extends DefaultConfig>({ configDir, environment, fileExtensions, }: ImportConfigFilesOptions): Promise<DefaultConfig>;
|
|
5
|
+
export declare function importConfigFiles<Config extends DefaultConfig>({ configDir, environment, variant, fileExtensions, }: ImportConfigFilesOptions): Promise<DefaultConfig>;
|
|
6
6
|
type ImportConfigFilesOptions = {
|
|
7
7
|
configDir: string;
|
|
8
8
|
environment?: Environment;
|
|
9
|
+
variant?: string;
|
|
9
10
|
fileExtensions: FileExtension[];
|
|
10
11
|
};
|
|
11
12
|
export {};
|
|
@@ -3,18 +3,27 @@ import { importConfigFile } from "./importConfigFile.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Import multiple config files in defined order and merge them deeply together
|
|
5
5
|
*/
|
|
6
|
-
export async function importConfigFiles({ configDir, environment, fileExtensions, }) {
|
|
6
|
+
export async function importConfigFiles({ configDir, environment, variant, fileExtensions, }) {
|
|
7
7
|
/** default.ext **/
|
|
8
8
|
const defaultConfig = await importConfigFile({
|
|
9
9
|
filePath: `${configDir}/default`,
|
|
10
10
|
fileExtensions,
|
|
11
11
|
shouldThrowError: true,
|
|
12
12
|
});
|
|
13
|
+
/** default-{variant}.ext **/
|
|
14
|
+
let defaultVariantConfig;
|
|
15
|
+
if (variant) {
|
|
16
|
+
defaultVariantConfig = await importConfigFile({
|
|
17
|
+
filePath: `${configDir}/default-${variant}`,
|
|
18
|
+
fileExtensions,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
13
21
|
/** {environment}.ext **/
|
|
14
|
-
let
|
|
22
|
+
let environmentConfig;
|
|
23
|
+
let environmentVariantConfig;
|
|
15
24
|
if (environment) {
|
|
16
25
|
try {
|
|
17
|
-
|
|
26
|
+
environmentConfig = await importConfigFile({
|
|
18
27
|
filePath: `${configDir}/${environment}`,
|
|
19
28
|
fileExtensions,
|
|
20
29
|
shouldThrowError: true,
|
|
@@ -23,12 +32,27 @@ export async function importConfigFiles({ configDir, environment, fileExtensions
|
|
|
23
32
|
catch (_error) {
|
|
24
33
|
console.warn(`Environment ${environment} defined but no config file found`);
|
|
25
34
|
}
|
|
35
|
+
/** {environment}-{variant}.ext **/
|
|
36
|
+
if (variant) {
|
|
37
|
+
environmentVariantConfig = await importConfigFile({
|
|
38
|
+
filePath: `${configDir}/${environment}-${variant}`,
|
|
39
|
+
fileExtensions,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
26
42
|
}
|
|
27
43
|
/** local.ext **/
|
|
28
44
|
const localConfig = await importConfigFile({
|
|
29
45
|
filePath: `${configDir}/local`,
|
|
30
46
|
fileExtensions,
|
|
31
47
|
});
|
|
48
|
+
/** local-{variant}.ext **/
|
|
49
|
+
let localVariantConfig;
|
|
50
|
+
if (variant) {
|
|
51
|
+
localVariantConfig = await importConfigFile({
|
|
52
|
+
filePath: `${configDir}/local-${variant}`,
|
|
53
|
+
fileExtensions,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
32
56
|
/** local-{environment}.ext **/
|
|
33
57
|
const localEnvConfig = environment
|
|
34
58
|
? await importConfigFile({
|
|
@@ -36,10 +60,17 @@ export async function importConfigFiles({ configDir, environment, fileExtensions
|
|
|
36
60
|
fileExtensions,
|
|
37
61
|
})
|
|
38
62
|
: {};
|
|
63
|
+
/** local-{environment}-{variant}.ext **/
|
|
64
|
+
const localEnvVariantConfig = environment && variant
|
|
65
|
+
? await importConfigFile({
|
|
66
|
+
filePath: `${configDir}/local-${environment}-${variant}`,
|
|
67
|
+
fileExtensions,
|
|
68
|
+
})
|
|
69
|
+
: {};
|
|
39
70
|
/** custom-environment-variables.ext **/
|
|
40
71
|
const customEnvVarsConfig = await importConfigFile({
|
|
41
72
|
filePath: `${configDir}/custom-environment-variables`,
|
|
42
73
|
fileExtensions: fileExtensions.filter((ext) => ext !== 'json'), // JSON not supported because it cannot use `process.env`
|
|
43
74
|
});
|
|
44
|
-
return deepMerge(structuredClone(defaultConfig), structuredClone(
|
|
75
|
+
return deepMerge(structuredClone(defaultConfig), structuredClone(defaultVariantConfig ?? {}), structuredClone(environmentConfig ?? {}), structuredClone(environmentVariantConfig ?? {}), structuredClone(localConfig), structuredClone(localVariantConfig ?? {}), structuredClone(localEnvConfig), structuredClone(localEnvVariantConfig), structuredClone(customEnvVarsConfig));
|
|
45
76
|
}
|
package/dist/loadConfig.d.ts
CHANGED
|
@@ -5,10 +5,14 @@ import type { DefaultConfig, Environment, FileExtension } from './common.ts';
|
|
|
5
5
|
* Features:
|
|
6
6
|
* - Load config files in order:
|
|
7
7
|
* 1. default.ext
|
|
8
|
-
* 2. {
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 5.
|
|
8
|
+
* 2. default-{variant}.ext
|
|
9
|
+
* 3. {environment}.ext
|
|
10
|
+
* 4. {environment}-{variant}.ext
|
|
11
|
+
* 5. local.ext
|
|
12
|
+
* 6. local-{variant}.ext
|
|
13
|
+
* 7. local-{environment}.ext
|
|
14
|
+
* 8. local-{environment}-{variant}.ext
|
|
15
|
+
* 9. custom-environment-variables.ext (only for .ts and .js files)
|
|
12
16
|
* - Throw an error if a property is accessed that is not defined in the config
|
|
13
17
|
* - Freeze the config object to prevent modifications
|
|
14
18
|
* - Supported config file formats: TypeScript (.ts, .mts), JavaScript (.js, .mjs), JSON (.json)
|
|
@@ -17,6 +21,7 @@ import type { DefaultConfig, Environment, FileExtension } from './common.ts';
|
|
|
17
21
|
*
|
|
18
22
|
* @param configDirs - paths to directories where the config files are located. Relative or absolute. Directories are merged in the array order. Default: ['${current-working-directory}/config']
|
|
19
23
|
* @param environment - the environment to load the config for. Default: process.env.NODE_ENV
|
|
24
|
+
* @param variant - allows to define 2nd dimension of configs above environment. Default: undefined
|
|
20
25
|
* @param fileExtensions - an array of file extensions to use when looking for config files. Configs are loaded in this order. Default: ['ts', 'js']
|
|
21
26
|
* @param throwOnUndefinedProp - throw an error if a property is accessed that is not defined in the config. Default: true
|
|
22
27
|
* @param freezeConfig - freeze the config object to prevent modifications. Default: true
|
|
@@ -25,13 +30,14 @@ import type { DefaultConfig, Environment, FileExtension } from './common.ts';
|
|
|
25
30
|
* - `config` - a secure, frozen object with the loaded config. Throws an error if an undefined property is accessed
|
|
26
31
|
* - `unsecureConfig` - an unsecure config. May be needed if config is passed directly to some module that wants to read undefined props or modify it
|
|
27
32
|
*/
|
|
28
|
-
export declare function loadConfig<Config extends DefaultConfig>({ configDirs, environment, fileExtensions, throwOnUndefinedProp, freezeConfig, }?: LoadConfigOptions): Promise<{
|
|
33
|
+
export declare function loadConfig<Config extends DefaultConfig>({ configDirs, environment, variant, fileExtensions, throwOnUndefinedProp, freezeConfig, }?: LoadConfigOptions): Promise<{
|
|
29
34
|
config: Config;
|
|
30
35
|
unsecureConfig: Config;
|
|
31
36
|
}>;
|
|
32
37
|
type LoadConfigOptions = {
|
|
33
38
|
configDirs?: string[];
|
|
34
39
|
environment?: Environment;
|
|
40
|
+
variant?: string;
|
|
35
41
|
fileExtensions?: FileExtension[];
|
|
36
42
|
throwOnUndefinedProp?: boolean;
|
|
37
43
|
freezeConfig?: boolean;
|
package/dist/loadConfig.js
CHANGED
|
@@ -8,10 +8,14 @@ import { makeSecureDeepProxy } from "./makeSecureDeepProxy.js";
|
|
|
8
8
|
* Features:
|
|
9
9
|
* - Load config files in order:
|
|
10
10
|
* 1. default.ext
|
|
11
|
-
* 2. {
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 5.
|
|
11
|
+
* 2. default-{variant}.ext
|
|
12
|
+
* 3. {environment}.ext
|
|
13
|
+
* 4. {environment}-{variant}.ext
|
|
14
|
+
* 5. local.ext
|
|
15
|
+
* 6. local-{variant}.ext
|
|
16
|
+
* 7. local-{environment}.ext
|
|
17
|
+
* 8. local-{environment}-{variant}.ext
|
|
18
|
+
* 9. custom-environment-variables.ext (only for .ts and .js files)
|
|
15
19
|
* - Throw an error if a property is accessed that is not defined in the config
|
|
16
20
|
* - Freeze the config object to prevent modifications
|
|
17
21
|
* - Supported config file formats: TypeScript (.ts, .mts), JavaScript (.js, .mjs), JSON (.json)
|
|
@@ -20,6 +24,7 @@ import { makeSecureDeepProxy } from "./makeSecureDeepProxy.js";
|
|
|
20
24
|
*
|
|
21
25
|
* @param configDirs - paths to directories where the config files are located. Relative or absolute. Directories are merged in the array order. Default: ['${current-working-directory}/config']
|
|
22
26
|
* @param environment - the environment to load the config for. Default: process.env.NODE_ENV
|
|
27
|
+
* @param variant - allows to define 2nd dimension of configs above environment. Default: undefined
|
|
23
28
|
* @param fileExtensions - an array of file extensions to use when looking for config files. Configs are loaded in this order. Default: ['ts', 'js']
|
|
24
29
|
* @param throwOnUndefinedProp - throw an error if a property is accessed that is not defined in the config. Default: true
|
|
25
30
|
* @param freezeConfig - freeze the config object to prevent modifications. Default: true
|
|
@@ -28,12 +33,13 @@ import { makeSecureDeepProxy } from "./makeSecureDeepProxy.js";
|
|
|
28
33
|
* - `config` - a secure, frozen object with the loaded config. Throws an error if an undefined property is accessed
|
|
29
34
|
* - `unsecureConfig` - an unsecure config. May be needed if config is passed directly to some module that wants to read undefined props or modify it
|
|
30
35
|
*/
|
|
31
|
-
export async function loadConfig({ configDirs = [`${process.cwd()}/config`], environment = process.env.NODE_ENV, fileExtensions = ['ts', 'js'], throwOnUndefinedProp = true, freezeConfig = true, } = {}) {
|
|
36
|
+
export async function loadConfig({ configDirs = [`${process.cwd()}/config`], environment = process.env.NODE_ENV, variant, fileExtensions = ['ts', 'js'], throwOnUndefinedProp = true, freezeConfig = true, } = {}) {
|
|
32
37
|
let mergedConfig = {};
|
|
33
38
|
for (const configDir of configDirs) {
|
|
34
39
|
const config = await importConfigFiles({
|
|
35
40
|
configDir,
|
|
36
41
|
environment,
|
|
42
|
+
variant,
|
|
37
43
|
fileExtensions,
|
|
38
44
|
});
|
|
39
45
|
mergedConfig = deepMerge(mergedConfig, config);
|
|
@@ -23,8 +23,10 @@ function makeSecureProxy(targetObject) {
|
|
|
23
23
|
get(target, prop) {
|
|
24
24
|
// Handle Symbol and Array properties
|
|
25
25
|
if (typeof prop === 'symbol' ||
|
|
26
|
-
|
|
27
|
-
(Array.isArray(target) && Number.isNaN(Number(prop)))
|
|
26
|
+
/** Don't throw error when using array methods */
|
|
27
|
+
(Array.isArray(target) && Number.isNaN(Number(prop))) ||
|
|
28
|
+
/** Allow stringifying proxied config (because JSON.stringify calls this method) */
|
|
29
|
+
prop === 'toJSON') {
|
|
28
30
|
return Reflect.get(target, prop);
|
|
29
31
|
}
|
|
30
32
|
if (!(prop in target)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Configuration helper for TypeScript applications",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"type": "git",
|
|
31
31
|
"url": "git+https://github.com/mdrobny/configate.git"
|
|
32
32
|
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"registry": "https://registry.npmjs.org",
|
|
35
|
+
"provenance": true
|
|
36
|
+
},
|
|
33
37
|
"keywords": [
|
|
34
38
|
"config"
|
|
35
39
|
],
|
|
@@ -40,8 +44,18 @@
|
|
|
40
44
|
},
|
|
41
45
|
"homepage": "https://github.com/mdrobny/configate#readme",
|
|
42
46
|
"devDependencies": {
|
|
43
|
-
"@biomejs/biome": "2.
|
|
47
|
+
"@biomejs/biome": "2.5.13",
|
|
44
48
|
"@types/node": "^22.10.6",
|
|
45
49
|
"typescript": "^5.7.3"
|
|
50
|
+
},
|
|
51
|
+
"devEngines": {
|
|
52
|
+
"runtime": {
|
|
53
|
+
"name": "node",
|
|
54
|
+
"onFail": "error"
|
|
55
|
+
},
|
|
56
|
+
"packageManager": {
|
|
57
|
+
"name": "npm",
|
|
58
|
+
"onFail": "error"
|
|
59
|
+
}
|
|
46
60
|
}
|
|
47
61
|
}
|