configate 1.0.0 → 1.1.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 +44 -6
- package/dist/importConfigFiles.d.ts +2 -1
- package/dist/importConfigFiles.js +28 -4
- package/dist/loadConfig.d.ts +11 -5
- package/dist/loadConfig.js +11 -5
- package/dist/makeSecureDeepProxy.js +4 -2
- package/package.json +1 -1
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
|
|
@@ -153,14 +154,52 @@ Add this to `.gitignore` to prevent committing local configuration files:
|
|
|
153
154
|
**/config/local*
|
|
154
155
|
```
|
|
155
156
|
|
|
157
|
+
### Second layer of variant configurations
|
|
158
|
+
|
|
159
|
+
Some applications may need another layer of overrides besides environments (for example: regions, brands, customers etc).
|
|
160
|
+
Pass the `variant` option to `loadConfig` and create files that follow the `*-{variant}` naming convention to scope those overrides.
|
|
161
|
+
|
|
162
|
+
When `variant` is set, Configate looks for these optional files on top of the usual ones:
|
|
163
|
+
- `default-{variant}.ext`
|
|
164
|
+
- `{environment}-{variant}.ext`
|
|
165
|
+
- `local-{variant}.ext`
|
|
166
|
+
- `local-{environment}-{variant}.ext`
|
|
167
|
+
|
|
168
|
+
Example setup:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
config/
|
|
172
|
+
default.ts
|
|
173
|
+
default-customerA.ts
|
|
174
|
+
default-customerB.ts
|
|
175
|
+
production.ts
|
|
176
|
+
production-customerA.ts
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// src/config.ts
|
|
181
|
+
import { loadConfig } from 'configate';
|
|
182
|
+
|
|
183
|
+
export const { config } = await loadConfig<AppConfig>({
|
|
184
|
+
environment: 'production',
|
|
185
|
+
variant: 'customerA',
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
This keeps the base config reusable while still letting you target per-variant overrides without duplicating entire files.
|
|
190
|
+
|
|
156
191
|
### Order of loading configuration files
|
|
157
192
|
|
|
158
193
|
```
|
|
159
|
-
default.ext
|
|
160
|
-
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
194
|
+
1. default.ext
|
|
195
|
+
2. default-{variant}.ext
|
|
196
|
+
3. {environment}.ext
|
|
197
|
+
4. {environment}-{variant}.ext
|
|
198
|
+
5. local.ext
|
|
199
|
+
6. local-{variant}.ext
|
|
200
|
+
7. local-{environment}.ext
|
|
201
|
+
8. local-{environment}-{variant}.ext
|
|
202
|
+
9. custom-environment-variables.ext
|
|
164
203
|
```
|
|
165
204
|
|
|
166
205
|
### Using unsecure config
|
|
@@ -221,4 +260,3 @@ export const { config } = await loadConfig<TestConfig>({
|
|
|
221
260
|
],
|
|
222
261
|
});
|
|
223
262
|
```
|
|
224
|
-
|
|
@@ -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({
|
|
@@ -41,5 +65,5 @@ export async function importConfigFiles({ configDir, environment, fileExtensions
|
|
|
41
65
|
filePath: `${configDir}/custom-environment-variables`,
|
|
42
66
|
fileExtensions: fileExtensions.filter((ext) => ext !== 'json'), // JSON not supported because it cannot use `process.env`
|
|
43
67
|
});
|
|
44
|
-
return deepMerge(structuredClone(defaultConfig), structuredClone(
|
|
68
|
+
return deepMerge(structuredClone(defaultConfig), structuredClone(defaultVariantConfig ?? {}), structuredClone(environmentConfig ?? {}), structuredClone(environmentVariantConfig ?? {}), structuredClone(localConfig), structuredClone(localVariantConfig ?? {}), structuredClone(localEnvConfig), structuredClone(customEnvVarsConfig));
|
|
45
69
|
}
|
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)) {
|