@hg-ts/config-loader 0.5.16 → 0.5.18

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.
@@ -1 +1 @@
1
- {"version":3,"file":"config.module.d.ts","sourceRoot":"","sources":["../src/config.module.ts"],"names":[],"mappings":"AAQA,qBAgBa,YAAY;CAAG"}
1
+ {"version":3,"file":"config.module.d.ts","sourceRoot":"","sources":["../src/config.module.ts"],"names":[],"mappings":"AAWA,qBAkBa,YAAY;CAAG"}
@@ -1,4 +1,5 @@
1
1
  import { __decorate } from "tslib";
2
+ import { ExecutionMode, ExecutionModeModule, } from '@hg-ts/execution-mode';
2
3
  import { Global, Module, } from '@nestjs/common';
3
4
  import { ConfigLoader } from './config-loader.js';
4
5
  import { FileConfigLoader } from './file.config-loader.js';
@@ -7,6 +8,7 @@ let ConfigModule = class ConfigModule {
7
8
  ConfigModule = __decorate([
8
9
  Global(),
9
10
  Module({
11
+ imports: [ExecutionModeModule],
10
12
  providers: [
11
13
  {
12
14
  provide: ConfigLoader,
@@ -17,6 +19,7 @@ ConfigModule = __decorate([
17
19
  overrideEnv: env,
18
20
  });
19
21
  },
22
+ inject: [ExecutionMode],
20
23
  },
21
24
  ],
22
25
  exports: [ConfigLoader],
@@ -1 +1 @@
1
- {"version":3,"file":"config.module.js","sourceRoot":"","sources":["../src/config.module.ts"],"names":[],"mappings":";AACA,OAAO,EACN,MAAM,EACN,MAAM,GACN,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAkBpD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,YAAY;IAhBxB,MAAM,EAAE;IACR,MAAM,CAAC;QACP,SAAS,EAAE;YACV;gBACC,OAAO,EAAE,YAAY;gBACrB,UAAU,CAAC,GAAkB;oBAC5B,OAAO,IAAI,gBAAgB,CAAC;wBAC3B,UAAU,EAAE,IAAI;wBAChB,SAAS,EAAE,QAAQ;wBACnB,WAAW,EAAE,GAAG;qBAChB,CAAC,CAAC;gBACJ,CAAC;aACD;SACD;QACD,OAAO,EAAE,CAAC,YAAY,CAAC;KACvB,CAAC;GACW,YAAY,CAAG"}
1
+ {"version":3,"file":"config.module.js","sourceRoot":"","sources":["../src/config.module.ts"],"names":[],"mappings":";AAAA,OAAO,EACN,aAAa,EACb,mBAAmB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACN,MAAM,EACN,MAAM,GACN,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAoBpD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,YAAY;IAlBxB,MAAM,EAAE;IACR,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,mBAAmB,CAAC;QAC9B,SAAS,EAAE;YACV;gBACC,OAAO,EAAE,YAAY;gBACrB,UAAU,CAAC,GAAkB;oBAC5B,OAAO,IAAI,gBAAgB,CAAC;wBAC3B,UAAU,EAAE,IAAI;wBAChB,SAAS,EAAE,QAAQ;wBACnB,WAAW,EAAE,GAAG;qBAChB,CAAC,CAAC;gBACJ,CAAC;gBACD,MAAM,EAAE,CAAC,aAAa,CAAC;aACvB;SACD;QACD,OAAO,EAAE,CAAC,YAAY,CAAC;KACvB,CAAC;GACW,YAAY,CAAG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hg-ts/config-loader",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,12 +18,12 @@
18
18
  "test:dev": "vitest watch"
19
19
  },
20
20
  "devDependencies": {
21
- "@hg-ts-config/typescript": "0.5.16",
22
- "@hg-ts/exception": "0.5.16",
23
- "@hg-ts/execution-mode": "0.5.16",
24
- "@hg-ts/linter": "0.5.16",
25
- "@hg-ts/tests": "0.5.16",
26
- "@hg-ts/types": "0.5.16",
21
+ "@hg-ts-config/typescript": "0.5.18",
22
+ "@hg-ts/exception": "0.5.18",
23
+ "@hg-ts/execution-mode": "0.5.18",
24
+ "@hg-ts/linter": "0.5.18",
25
+ "@hg-ts/tests": "0.5.18",
26
+ "@hg-ts/types": "0.5.18",
27
27
  "@nestjs/common": "11.1.0",
28
28
  "@types/node": "22.19.1",
29
29
  "@vitest/coverage-v8": "4.0.14",
@@ -37,8 +37,8 @@
37
37
  "vitest": "4.0.14"
38
38
  },
39
39
  "peerDependencies": {
40
- "@hg-ts/exception": "0.5.16",
41
- "@hg-ts/execution-mode": "0.5.16",
40
+ "@hg-ts/exception": "0.5.18",
41
+ "@hg-ts/execution-mode": "0.5.18",
42
42
  "@nestjs/common": "*",
43
43
  "reflect-metadata": "*",
44
44
  "rxjs": "*",
@@ -46,6 +46,6 @@
46
46
  "vitest": "*"
47
47
  },
48
48
  "dependencies": {
49
- "@hg-ts/validation": "0.5.16"
49
+ "@hg-ts/validation": "0.5.18"
50
50
  }
51
51
  }
@@ -0,0 +1,5 @@
1
+ import { ZodDto } from '@hg-ts/validation';
2
+
3
+ export abstract class ConfigLoader {
4
+ public abstract load<ConfigType extends object>(schema: ZodDto<ConfigType>, name: string): Promise<ConfigType>;
5
+ }
@@ -0,0 +1,30 @@
1
+ import {
2
+ ExecutionMode,
3
+ ExecutionModeModule,
4
+ } from '@hg-ts/execution-mode';
5
+ import {
6
+ Global,
7
+ Module,
8
+ } from '@nestjs/common';
9
+ import { ConfigLoader } from './config-loader.js';
10
+ import { FileConfigLoader } from './file.config-loader.js';
11
+
12
+ @Global()
13
+ @Module({
14
+ imports: [ExecutionModeModule],
15
+ providers: [
16
+ {
17
+ provide: ConfigLoader,
18
+ useFactory(env: ExecutionMode): ConfigLoader {
19
+ return new FileConfigLoader({
20
+ envBuilder: true,
21
+ configDir: 'config',
22
+ overrideEnv: env,
23
+ });
24
+ },
25
+ inject: [ExecutionMode],
26
+ },
27
+ ],
28
+ exports: [ConfigLoader],
29
+ })
30
+ export class ConfigModule {}
@@ -0,0 +1 @@
1
+ export * from './no-base-config.exception.js';
@@ -0,0 +1,7 @@
1
+ import { BaseException } from '@hg-ts/exception';
2
+
3
+ export class NoBaseConfigException extends BaseException {
4
+ public constructor(configName: string) {
5
+ super(`Base config with name ${configName} not found`);
6
+ }
7
+ }
@@ -0,0 +1,127 @@
1
+ import {
2
+ ZodDto,
3
+ ZodType,
4
+ } from '@hg-ts/validation';
5
+ import assert from 'node:assert/strict';
6
+ import fs from 'node:fs/promises';
7
+ import { ConfigLoader } from './config-loader.js';
8
+
9
+ import { NoBaseConfigException } from './exceptions/index.js';
10
+ import {
11
+ PathBuilder,
12
+ PathBuilderOptions,
13
+ } from './path-builder.js';
14
+
15
+ export type ConfigLoaderOptions = PathBuilderOptions & {
16
+ recursive?: boolean;
17
+ cache?: boolean;
18
+ };
19
+
20
+ type CacheItem = {
21
+ schema: ZodType<object>;
22
+ config: object;
23
+ };
24
+
25
+ export class FileConfigLoader implements ConfigLoader {
26
+ private readonly pathBuilder: PathBuilder;
27
+ private readonly options: ConfigLoaderOptions;
28
+ private readonly cacheMap = new Map<string, CacheItem>();
29
+
30
+ public constructor(options: ConfigLoaderOptions = {}) {
31
+ this.pathBuilder = new PathBuilder(options);
32
+ this.options = this.pathBuilder.options;
33
+ }
34
+
35
+ public async load<ConfigType extends object>(dto: ZodDto<ConfigType>, name: string): Promise<ConfigType> {
36
+ const { schema } = dto;
37
+ if (this.cacheMap.has(name)) {
38
+ const cacheItem = this.cacheMap.get(name)!;
39
+
40
+ assert.ok(cacheItem.schema === schema, `cached instance of config "${name}" has another schema`);
41
+
42
+ return cacheItem.config as ConfigType;
43
+ }
44
+
45
+ const rawConfig = await this.loadRawConfig(name);
46
+
47
+ const config = await this.validate(schema, rawConfig);
48
+
49
+ if (this.cacheEnabled) {
50
+ this.cacheMap.set(name, { schema, config });
51
+ }
52
+
53
+ return config;
54
+ }
55
+
56
+ protected async loadRawConfig(name: string): Promise<unknown> {
57
+ const paths = this.pathBuilder.build(name);
58
+
59
+ const configs = await Promise.all(paths.map(async path => this.loadConfigFile(path)));
60
+
61
+ if (!configs[0]) {
62
+ throw new NoBaseConfigException(name);
63
+ }
64
+
65
+ return this.mergeConfigs(configs);
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @todo Add options to add custom loaders with own ext names (for example yaml)
71
+ * @param {string} path
72
+ * @protected
73
+ */
74
+ protected async loadConfigFile(path: string): Promise<unknown> {
75
+ try {
76
+ const content = await fs.readFile(path, { encoding: 'utf-8' });
77
+
78
+ return JSON.parse(content) as unknown;
79
+ } catch (error: unknown) {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ protected mergeConfigs(configs: unknown[]): Record<string, unknown> {
85
+ return configs
86
+ .filter(config => config !== null)
87
+ .filter((config): config is Record<string, unknown> => typeof config === 'object')
88
+ .reduce((merged, next) => {
89
+ if (this.isRecursive && merged[this.recursiveRootKey] === true) {
90
+ return merged;
91
+ }
92
+ return {
93
+ ...merged,
94
+ ...next,
95
+ };
96
+ });
97
+ }
98
+
99
+ protected async validate<ConfigType>(
100
+ schema: ZodType<ConfigType>,
101
+ config: unknown,
102
+ ): Promise<ConfigType> {
103
+ return schema.parseAsync(config);
104
+ }
105
+
106
+ protected get cacheEnabled(): boolean {
107
+ if (this.options.cache) {
108
+ return true;
109
+ }
110
+
111
+ return false;
112
+ }
113
+
114
+ protected get isRecursive(): boolean {
115
+ if (this.options.recursive) {
116
+ return true;
117
+ }
118
+
119
+ return false;
120
+ }
121
+
122
+ // TODO: Get field name from options
123
+ // eslint-disable-next-line @typescript/class-literal-property-style
124
+ protected get recursiveRootKey(): string {
125
+ return 'root';
126
+ }
127
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './config-loader.js';
2
+ export * from './file.config-loader.js';
3
+ export * from './mock.config-loader.js';
4
+ export * from './config.module.js';
@@ -0,0 +1,16 @@
1
+ import { ZodDto } from '@hg-ts/validation';
2
+ import { ConfigLoader } from './config-loader.js';
3
+
4
+ export class MockConfigLoader implements ConfigLoader {
5
+ private readonly configMap: Map<string, unknown>;
6
+
7
+ public constructor(configMap: Map<string, unknown>) {
8
+ this.configMap = configMap;
9
+ }
10
+
11
+ public async load<ConfigType extends object>(dto: ZodDto<ConfigType>, name: string): Promise<ConfigType> {
12
+ const config = this.configMap.get(name);
13
+
14
+ return dto.schema.parse(config);
15
+ }
16
+ }
@@ -0,0 +1,158 @@
1
+ import type { ExecutionMode } from '@hg-ts/execution-mode';
2
+ import {
3
+ dirname,
4
+ resolve,
5
+ } from 'path';
6
+
7
+ export type PathBuilderOptions = {
8
+
9
+ /**
10
+ *
11
+ * Override default app path
12
+ * @default process.cwd()
13
+ */
14
+ appPath?: string;
15
+
16
+ /**
17
+ *
18
+ * Additional directory to appPath
19
+ * @default null
20
+ */
21
+ configDir?: Nullable<string>;
22
+
23
+ /**
24
+ *
25
+ * Use environment variable for build config.
26
+ * Get base config, apply ${env} config and, after, apply local config
27
+ * @default false
28
+ */
29
+ envBuilder?: boolean;
30
+
31
+ /**
32
+ *
33
+ * Override injected env
34
+ * Uses only with envBuilder: true
35
+ */
36
+ overrideEnv?: Nullable<ExecutionMode>;
37
+
38
+ /**
39
+ *
40
+ * Used for loading root package config. Useful with overridePostfix option
41
+ * @default null
42
+ * @todo Implement
43
+ */
44
+ basePostfix?: Nullable<string>;
45
+
46
+ /**
47
+ *
48
+ * Useful with basePostfix.
49
+ * For example:
50
+ * You can specify configName to 'hg', basePostfix to 'config' and overridePostfix to 'override'.
51
+ * In this case will be loaded 'bg.config.{ext}' and overrode by 'hg.override.{ext}'.
52
+ * Similar scheme used by many tools like 'docker-compose'.
53
+ * @default null
54
+ * @todo Implement
55
+ */
56
+ overridePostfix?: Nullable<string>;
57
+
58
+ /**
59
+ * Apply all configs from `appDir` to `/` until `root` field won't be specified
60
+ * @default false
61
+ * @todo Implement
62
+ */
63
+ recursive?: boolean;
64
+ };
65
+
66
+ export class PathBuilder {
67
+ public readonly options: Required<PathBuilderOptions>;
68
+ private paths: string[];
69
+
70
+ public constructor(options: PathBuilderOptions = {}) {
71
+ this.options = {
72
+ appPath: process.cwd(),
73
+ configDir: null,
74
+ envBuilder: false,
75
+ overrideEnv: null,
76
+ overridePostfix: null,
77
+ basePostfix: null,
78
+ recursive: false,
79
+ ...options,
80
+ };
81
+ this.paths = [];
82
+ }
83
+
84
+ public build(name: string): string[] {
85
+ return this.prepareBasePaths()
86
+ .addEnvFolders()
87
+ .addConfigName(name)
88
+ .paths;
89
+ }
90
+
91
+ protected prepareBasePaths(): this {
92
+ const { appPath, configDir, recursive } = this.options;
93
+ if (recursive) {
94
+ let nextPath = appPath;
95
+ const basePaths: string[] = [appPath];
96
+
97
+ while (nextPath !== '/') {
98
+ nextPath = dirname(nextPath);
99
+ basePaths.push(nextPath);
100
+ }
101
+
102
+ this.paths = basePaths;
103
+
104
+ if (configDir) {
105
+ this.mergePaths([configDir]);
106
+ }
107
+ } else {
108
+ this.paths = [this.buildPathToConfig([appPath, configDir])];
109
+ }
110
+
111
+ return this;
112
+ }
113
+
114
+ protected addEnvFolders(): this {
115
+ const { envBuilder, overrideEnv } = this.options;
116
+ const envFolders: string[] = [];
117
+
118
+ if (!envBuilder) {
119
+ return this;
120
+ }
121
+
122
+ envFolders.push('base');
123
+
124
+ if (overrideEnv) {
125
+ envFolders.push(overrideEnv.getValue());
126
+ }
127
+
128
+ envFolders.push('local');
129
+
130
+ return this.mergePaths(envFolders);
131
+ }
132
+
133
+ protected addConfigName(configName: string): this {
134
+ const { overridePostfix, basePostfix } = this.options;
135
+ const baseName = basePostfix ? `${configName}.${basePostfix}.json` : `${configName}.json`;
136
+ const overrideName = overridePostfix ? `${configName}.${overridePostfix}.json` : null;
137
+
138
+ const names = [baseName];
139
+
140
+ if (overrideName) {
141
+ names.push(overrideName);
142
+ }
143
+
144
+ return this.mergePaths(names);
145
+ }
146
+
147
+ private mergePaths(additionalParts: string[]): this {
148
+ this.paths = this.paths.flatMap(path => additionalParts.map(part => this.buildPathToConfig([path, part])));
149
+
150
+ return this;
151
+ }
152
+
153
+ private buildPathToConfig(parts: Nullable<string>[]): string {
154
+ const filteredParts = parts.filter((part): part is string => part !== null);
155
+
156
+ return resolve(...filteredParts);
157
+ }
158
+ }
@@ -0,0 +1,202 @@
1
+ import {
2
+ Describe,
3
+ expect,
4
+ ExpectException,
5
+ Suite,
6
+ Test,
7
+ } from '@hg-ts/tests';
8
+ import zod, { z } from '@hg-ts/validation';
9
+ import fs from 'node:fs/promises';
10
+ import { vi } from 'vitest';
11
+ import { NoBaseConfigException } from '../exceptions/index.js';
12
+
13
+ import { FileConfigLoader } from '../file.config-loader.js';
14
+
15
+ @Describe()
16
+ export class ConfigLoaderTest extends Suite {
17
+ @Test()
18
+ public async simple(): Promise<void> {
19
+ const loader = new FileConfigLoader();
20
+
21
+ const merged = loader['mergeConfigs']([
22
+ {
23
+ a: 'a',
24
+ b: 'b',
25
+ },
26
+ {
27
+ a: 'aa',
28
+ c: 'c',
29
+ },
30
+ ]);
31
+
32
+ expect(merged['a']).toBe('aa');
33
+ expect(merged['b']).toBe('b');
34
+ expect(merged['c']).toBe('c');
35
+ }
36
+
37
+ @Test()
38
+ public async recursive(): Promise<void> {
39
+ const loader = new FileConfigLoader({ recursive: true });
40
+
41
+ const merged = loader['mergeConfigs']([
42
+ {
43
+ a: 'a',
44
+ b: 'b',
45
+ },
46
+ {
47
+ a: 'aa',
48
+ c: 'c',
49
+ root: true,
50
+ },
51
+ {
52
+ a: 'aaa',
53
+ b: 'bbb',
54
+ c: 'ccc',
55
+ },
56
+ ]);
57
+
58
+ expect(merged['a']).toBe('aa');
59
+ expect(merged['b']).toBe('b');
60
+ expect(merged['c']).toBe('c');
61
+ }
62
+
63
+ @Test()
64
+ public async enforceEnv(): Promise<void> {
65
+ const loader = new FileConfigLoader();
66
+ const configSchema = zod.object({
67
+ number: zod.union([
68
+ zod.number(),
69
+ zod.string(),
70
+ ])
71
+ .enforceEnv('SOME_NUMERIC_ENV')
72
+ .transform(value => Number(value))
73
+ .pipe(zod.number()),
74
+ bool: zod.union([
75
+ zod.string(),
76
+ zod.boolean(),
77
+ ])
78
+ .enforceEnv('SOME_BOOLEAN_ENV')
79
+ .transformBooleanString(),
80
+ });
81
+
82
+ process.env['SOME_NUMERIC_ENV'] = '10';
83
+ process.env['SOME_BOOLEAN_ENV'] = 'false';
84
+
85
+ const config = { number: 1, bool: true };
86
+
87
+ const transformed = await loader['validate'](configSchema, config);
88
+
89
+ expect(transformed.number).toBe(10);
90
+ expect(transformed.bool).toBe(false);
91
+ }
92
+
93
+ @Test()
94
+ public async fullLoadSuccessWithCache(): Promise<void> {
95
+ const schema = z.object({
96
+ a: z.string(),
97
+ b: z.string(),
98
+ c: z.string(),
99
+ d: z.string(),
100
+ });
101
+ const loader = new FileConfigLoader({ recursive: true, cache: true });
102
+
103
+ const loadFileSpy = vi.spyOn(fs, 'readFile');
104
+ const reults = [
105
+ {
106
+ a: 'a',
107
+ b: 'b',
108
+ c: 'c',
109
+ },
110
+ {
111
+ a: 'a',
112
+ b: 'd',
113
+ d: 'b',
114
+ },
115
+ 'sdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdf',
116
+ ];
117
+ loadFileSpy.mockImplementation(async() => JSON.stringify(reults.shift()!));
118
+
119
+ const result = await loader.load(schema.toClass(), 'test');
120
+
121
+ expect(result).toMatchObject({
122
+ a: 'a',
123
+ b: 'd',
124
+ c: 'c',
125
+ d: 'b',
126
+ });
127
+
128
+ const cached = await loader.load(schema.toClass(), 'test');
129
+
130
+ expect(cached).toMatchObject(result);
131
+ }
132
+
133
+
134
+ @Test()
135
+ public async fullLoadSuccessWithoutCache(): Promise<void> {
136
+ const schema = z.object({
137
+ a: z.string(),
138
+ b: z.string(),
139
+ c: z.string(),
140
+ d: z.string(),
141
+ });
142
+ const loader = new FileConfigLoader({ recursive: true, cache: false });
143
+
144
+ const loadFileSpy = vi.spyOn(fs, 'readFile');
145
+ const expects: any[] = [
146
+ {
147
+ a: 'a',
148
+ b: 'b',
149
+ c: 'c',
150
+ },
151
+ {
152
+ a: 'a',
153
+ b: 'd',
154
+ d: 'b',
155
+ },
156
+ 'sdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdf',
157
+ ];
158
+ loadFileSpy.mockImplementation(async() => JSON.stringify(expects.shift()));
159
+
160
+ const result = await loader.load(schema.toClass(), 'test');
161
+
162
+ expects.push({
163
+ a: 'a',
164
+ b: 'b',
165
+ c: 'c',
166
+ },
167
+ {
168
+ a: 'a',
169
+ d: 'd',
170
+ });
171
+
172
+ expect(result).toMatchObject({
173
+ a: 'a',
174
+ b: 'd',
175
+ c: 'c',
176
+ d: 'b',
177
+ });
178
+
179
+ const cached = await loader.load(schema.toClass(), 'test');
180
+
181
+ expect(cached).not.toMatchObject(result);
182
+ }
183
+
184
+ @Test()
185
+ @ExpectException(NoBaseConfigException)
186
+ public async failOnNoBaseConfig(): Promise<void> {
187
+ const schema = z.object({
188
+ a: z.string(),
189
+ b: z.string(),
190
+ c: z.string(),
191
+ d: z.string(),
192
+ });
193
+ const loader = new FileConfigLoader({ recursive: true, cache: true });
194
+
195
+ const loadFileSpy = vi.spyOn(fs, 'readFile');
196
+ loadFileSpy.mockImplementation(() => {
197
+ throw new Error('file not found');
198
+ });
199
+
200
+ await loader.load(schema.toClass(), 'test');
201
+ }
202
+ }
@@ -0,0 +1,133 @@
1
+ import {
2
+ ExecutionModeVariants,
3
+ MockExecutionMode,
4
+ } from '@hg-ts/execution-mode';
5
+ import {
6
+ Describe,
7
+ expect,
8
+ Suite,
9
+ Test,
10
+ } from '@hg-ts/tests';
11
+
12
+ import {
13
+ PathBuilder,
14
+ PathBuilderOptions,
15
+ } from '../path-builder.js';
16
+
17
+ @Describe()
18
+ export class PathBuilderTest extends Suite {
19
+ @Test()
20
+ public async simple(): Promise<void> {
21
+ const paths = this.getPaths();
22
+
23
+ expect(paths).toHaveLength(1);
24
+ expect(paths[0]).toBe('/tmp/example.json');
25
+ }
26
+
27
+ @Test()
28
+ public async recursive(): Promise<void> {
29
+ const paths = this.getPaths({ recursive: true });
30
+
31
+ expect(paths).toHaveLength(2);
32
+ expect(paths[0]).toBe('/tmp/example.json');
33
+ expect(paths[1]).toBe('/example.json');
34
+ }
35
+
36
+ @Test()
37
+ public async full(): Promise<void> {
38
+ const paths = this.getPaths({
39
+ recursive: true,
40
+ overridePostfix: 'override',
41
+ basePostfix: 'config',
42
+ envBuilder: true,
43
+ overrideEnv: new MockExecutionMode(ExecutionModeVariants.DEMO),
44
+ configDir: 'config',
45
+ });
46
+
47
+ let index = 0;
48
+
49
+ expect(paths).toHaveLength(12);
50
+ expect(paths[index++]).toBe('/tmp/config/base/example.config.json');
51
+ expect(paths[index++]).toBe('/tmp/config/base/example.override.json');
52
+ expect(paths[index++]).toBe('/tmp/config/demo/example.config.json');
53
+ expect(paths[index++]).toBe('/tmp/config/demo/example.override.json');
54
+ expect(paths[index++]).toBe('/tmp/config/local/example.config.json');
55
+ expect(paths[index++]).toBe('/tmp/config/local/example.override.json');
56
+
57
+ expect(paths[index++]).toBe('/config/base/example.config.json');
58
+ expect(paths[index++]).toBe('/config/base/example.override.json');
59
+ expect(paths[index++]).toBe('/config/demo/example.config.json');
60
+ expect(paths[index++]).toBe('/config/demo/example.override.json');
61
+ expect(paths[index++]).toBe('/config/local/example.config.json');
62
+ expect(paths[index++]).toBe('/config/local/example.override.json');
63
+ }
64
+
65
+ @Test()
66
+ public async configDir(): Promise<void> {
67
+ const paths = this.getPaths({ configDir: 'config' });
68
+
69
+ expect(paths).toHaveLength(1);
70
+ expect(paths[0]).toBe('/tmp/config/example.json');
71
+ }
72
+
73
+ @Test()
74
+ public async envBuilder(): Promise<void> {
75
+ const paths = this.getPaths({ envBuilder: true });
76
+
77
+ expect(paths).toHaveLength(2);
78
+ expect(paths[0]).toBe('/tmp/base/example.json');
79
+ expect(paths[1]).toBe('/tmp/local/example.json');
80
+ }
81
+
82
+ @Test()
83
+ public async envBuilderWithOverrideProd(): Promise<void> {
84
+ const paths = this.getPaths({ envBuilder: true, overrideEnv: new MockExecutionMode(ExecutionModeVariants.PROD) });
85
+
86
+ expect(paths).toHaveLength(3);
87
+ expect(paths[0]).toBe('/tmp/base/example.json');
88
+ expect(paths[1]).toBe('/tmp/prod/example.json');
89
+ expect(paths[2]).toBe('/tmp/local/example.json');
90
+ }
91
+
92
+ @Test()
93
+ public async envBuilderWithOverrideDev(): Promise<void> {
94
+ const paths = this.getPaths({ envBuilder: true, overrideEnv: new MockExecutionMode(ExecutionModeVariants.DEV) });
95
+
96
+ expect(paths).toHaveLength(3);
97
+ expect(paths[0]).toBe('/tmp/base/example.json');
98
+ expect(paths[1]).toBe('/tmp/dev/example.json');
99
+ expect(paths[2]).toBe('/tmp/local/example.json');
100
+ }
101
+
102
+ @Test()
103
+ public async basePostfix(): Promise<void> {
104
+ const paths = this.getPaths({ basePostfix: 'config' });
105
+
106
+ expect(paths).toHaveLength(1);
107
+ expect(paths[0]).toBe('/tmp/example.config.json');
108
+ }
109
+
110
+ @Test()
111
+ public async basePostfixWithOverridePostfix(): Promise<void> {
112
+ const paths = this.getPaths({ basePostfix: 'config', overridePostfix: 'override' });
113
+
114
+ expect(paths).toHaveLength(2);
115
+ expect(paths[0]).toBe('/tmp/example.config.json');
116
+ expect(paths[1]).toBe('/tmp/example.override.json');
117
+ }
118
+
119
+ @Test()
120
+ public async overridePostfix(): Promise<void> {
121
+ const paths = this.getPaths({ overridePostfix: 'override' });
122
+
123
+ expect(paths).toHaveLength(2);
124
+ expect(paths[0]).toBe('/tmp/example.json');
125
+ expect(paths[1]).toBe('/tmp/example.override.json');
126
+ }
127
+
128
+ private getPaths(options: Omit<PathBuilderOptions, 'appPath'> = {}): string[] {
129
+ const builder = new PathBuilder({ appPath: '/tmp', ...options });
130
+
131
+ return builder.build('example');
132
+ }
133
+ }