@travetto/config 2.1.3 → 2.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 +3 -2
- package/package.json +5 -5
- package/src/decorator.ts +2 -2
- package/src/internal/util.ts +9 -6
- package/src/manager.ts +15 -14
- package/support/phase.init.ts +1 -1
package/README.md
CHANGED
|
@@ -116,9 +116,10 @@ $ node @travetto/boot/bin/main ./doc/dbconfig-run.ts
|
|
|
116
116
|
errors: [
|
|
117
117
|
{
|
|
118
118
|
kind: 'required',
|
|
119
|
-
|
|
119
|
+
value: undefined,
|
|
120
|
+
message: 'port is required',
|
|
120
121
|
path: 'port',
|
|
121
|
-
|
|
122
|
+
type: undefined
|
|
122
123
|
}
|
|
123
124
|
]
|
|
124
125
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/config",
|
|
3
3
|
"displayName": "Configuration",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "Environment-aware config management using yaml files",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"yaml",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"directory": "module/config"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@travetto/schema": "^2.
|
|
32
|
-
"@travetto/transformer": "^2.
|
|
33
|
-
"@travetto/yaml": "^2.
|
|
31
|
+
"@travetto/schema": "^2.2.0",
|
|
32
|
+
"@travetto/transformer": "^2.2.0",
|
|
33
|
+
"@travetto/yaml": "^2.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@travetto/di": "^2.
|
|
36
|
+
"@travetto/di": "^2.2.0"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
package/src/decorator.ts
CHANGED
|
@@ -7,10 +7,10 @@ import { ConfigManager } from './manager';
|
|
|
7
7
|
* @augments `@trv:di/Injectable`
|
|
8
8
|
*/
|
|
9
9
|
export function Config(ns: string, params?: { internal?: boolean }) {
|
|
10
|
-
return <T extends Class>(target: T) => {
|
|
10
|
+
return <T extends Class>(target: T): T => {
|
|
11
11
|
const og = target.prototype.postConstruct;
|
|
12
12
|
|
|
13
|
-
target.prototype.postConstruct = async function () {
|
|
13
|
+
target.prototype.postConstruct = async function (): Promise<void> {
|
|
14
14
|
// Apply config
|
|
15
15
|
await ConfigManager.install(target, this, ns, params?.internal);
|
|
16
16
|
if (og) {
|
package/src/internal/util.ts
CHANGED
|
@@ -10,7 +10,7 @@ export class ConfigUtil {
|
|
|
10
10
|
/**
|
|
11
11
|
* Find the key using case insensitive search
|
|
12
12
|
*/
|
|
13
|
-
static #getKeyName(key: string, fields: string[]) {
|
|
13
|
+
static #getKeyName(key: string, fields: string[]): string | undefined {
|
|
14
14
|
key = key.trim();
|
|
15
15
|
const match = new RegExp(key, 'i');
|
|
16
16
|
const next = fields.find(x => match.test(x));
|
|
@@ -20,7 +20,7 @@ export class ConfigUtil {
|
|
|
20
20
|
/**
|
|
21
21
|
* Takes a env var, and produces a partial object against a schema definition. Does not support arrays, only objects.
|
|
22
22
|
*/
|
|
23
|
-
static #expandEnvEntry(cls: Class, key: string, value: unknown) {
|
|
23
|
+
static #expandEnvEntry(cls: Class, key: string, value: unknown): Record<string, unknown> | undefined {
|
|
24
24
|
const parts = key.split('_');
|
|
25
25
|
|
|
26
26
|
const lastPart = parts.pop()!;
|
|
@@ -49,6 +49,7 @@ export class ConfigUtil {
|
|
|
49
49
|
break;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
52
53
|
data = ((data[part!] ??= {}) as Record<string, unknown>); // Recurse
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -65,11 +66,11 @@ export class ConfigUtil {
|
|
|
65
66
|
* Bind the environment variables onto an object structure when they match by name.
|
|
66
67
|
* Will split on _ to handle nesting appropriately
|
|
67
68
|
*/
|
|
68
|
-
static getEnvOverlay(cls: Class, ns: string) {
|
|
69
|
+
static getEnvOverlay(cls: Class, ns: string): Record<string, unknown> {
|
|
69
70
|
// Handle process.env on bind as the structure we need may not
|
|
70
71
|
// fully exist until the config has been created
|
|
71
72
|
const nsRe = new RegExp(`^${ns.replace(/[.]/g, '_')}`, 'i'); // Check is case insensitive
|
|
72
|
-
const data = {};
|
|
73
|
+
const data: Record<string, unknown> = {};
|
|
73
74
|
for (const [k, v] of Object.entries(process.env)) { // Find all keys that match ns
|
|
74
75
|
if (k.includes('_') && nsRe.test(k)) { // Require at least one level (nothing should be at the top level as all configs are namespaced)
|
|
75
76
|
Util.deepAssign(data, this.#expandEnvEntry(cls, ns ? k.substring(ns.length + 1) : k, v), 'coerce');
|
|
@@ -83,7 +84,7 @@ export class ConfigUtil {
|
|
|
83
84
|
*/
|
|
84
85
|
static async getConfigFileAsData(file: string, ns: string = ''): Promise<Record<string, unknown>> {
|
|
85
86
|
const data = await ResourceManager.read(file, 'utf8');
|
|
86
|
-
const doc = YamlUtil.parse
|
|
87
|
+
const doc = YamlUtil.parse<Record<string, unknown>>(data);
|
|
87
88
|
return ns ? { [ns]: doc } : doc;
|
|
88
89
|
}
|
|
89
90
|
|
|
@@ -93,7 +94,7 @@ export class ConfigUtil {
|
|
|
93
94
|
* @param ns
|
|
94
95
|
* @returns
|
|
95
96
|
*/
|
|
96
|
-
static lookupRoot(src: Record<string, unknown>, ns?: string, createIfMissing = false) {
|
|
97
|
+
static lookupRoot(src: Record<string, unknown>, ns?: string, createIfMissing = false): Record<string, unknown> {
|
|
97
98
|
const parts = (ns ? ns.split('.') : []);
|
|
98
99
|
let sub: Record<string, unknown> = src;
|
|
99
100
|
|
|
@@ -102,6 +103,7 @@ export class ConfigUtil {
|
|
|
102
103
|
if (createIfMissing && !sub[next]) {
|
|
103
104
|
sub[next] = {};
|
|
104
105
|
}
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
105
107
|
sub = sub[next] as Record<string, unknown>;
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -121,6 +123,7 @@ export class ConfigUtil {
|
|
|
121
123
|
full[k] = '*'.repeat(10);
|
|
122
124
|
}
|
|
123
125
|
}
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
124
127
|
return BindUtil.expandPaths(full) as T;
|
|
125
128
|
}
|
|
126
129
|
}
|
package/src/manager.ts
CHANGED
|
@@ -24,14 +24,14 @@ class $ConfigManager {
|
|
|
24
24
|
'pw',
|
|
25
25
|
];
|
|
26
26
|
|
|
27
|
-
protected getStorage() {
|
|
27
|
+
protected getStorage(): Record<string, unknown> {
|
|
28
28
|
return this.#storage;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Load all config files
|
|
33
33
|
*/
|
|
34
|
-
async #load() {
|
|
34
|
+
async #load(): Promise<void> {
|
|
35
35
|
const profileIndex = Object.fromEntries(Object.entries(AppManifest.env.profiles).map(([k, v]) => [v, +k] as const));
|
|
36
36
|
|
|
37
37
|
const files = (await ResourceManager.findAll(/[.]ya?ml$/))
|
|
@@ -51,9 +51,9 @@ class $ConfigManager {
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Get a sub tree of the config, or everything if namespace is not passed
|
|
54
|
-
* @param ns The namespace of the config to search for, can be dotted for
|
|
54
|
+
* @param ns The namespace of the config to search for, can be dotted for accessing sub namespaces
|
|
55
55
|
*/
|
|
56
|
-
#get(ns?: string) {
|
|
56
|
+
#get(ns?: string): Record<string, unknown> {
|
|
57
57
|
return ConfigUtil.lookupRoot(this.#storage, ns);
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -64,7 +64,7 @@ class $ConfigManager {
|
|
|
64
64
|
* - Resource {env}.yml
|
|
65
65
|
* - Environment vars -> Overrides everything (happens at bind time)
|
|
66
66
|
*/
|
|
67
|
-
async init() {
|
|
67
|
+
async init(): Promise<void> {
|
|
68
68
|
if (!this.#initialized) {
|
|
69
69
|
this.#initialized = true;
|
|
70
70
|
await this.#load();
|
|
@@ -76,11 +76,12 @@ class $ConfigManager {
|
|
|
76
76
|
* @param namespace If only a portion of the config should be exported
|
|
77
77
|
* @param secure Determines if secrets should be redacted, defaults to true in prod, false otherwise
|
|
78
78
|
*/
|
|
79
|
-
toJSON(secure: boolean = EnvUtil.isProd()) {
|
|
79
|
+
toJSON(secure: boolean = EnvUtil.isProd()): Record<string, unknown> {
|
|
80
80
|
const copy = JSON.parse(JSON.stringify(this.#active));
|
|
81
81
|
return secure ?
|
|
82
82
|
ConfigUtil.sanitizeValuesByKey(copy, [
|
|
83
83
|
...this.#redactedKeys,
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
84
85
|
...(this.#get('config')?.redacted ?? []) as string[]
|
|
85
86
|
]) :
|
|
86
87
|
copy;
|
|
@@ -92,7 +93,7 @@ class $ConfigManager {
|
|
|
92
93
|
* @param item
|
|
93
94
|
* @param namespace
|
|
94
95
|
*/
|
|
95
|
-
bindTo<T>(cls: Class<T>, item: T, namespace: string) {
|
|
96
|
+
bindTo<T>(cls: Class<T>, item: T, namespace: string): T {
|
|
96
97
|
if (!SchemaRegistry.has(cls)) {
|
|
97
98
|
throw new AppError(`${cls.ᚕid} is not a valid schema class, config is not supported`);
|
|
98
99
|
}
|
|
@@ -103,16 +104,16 @@ class $ConfigManager {
|
|
|
103
104
|
return BindUtil.bindSchemaToObject(cls, item, cfg);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
async install<T>(cls: Class<T>, item: T, namespace: string, internal?: boolean) {
|
|
107
|
+
async install<T>(cls: Class<T>, item: T, namespace: string, internal?: boolean): Promise<T> {
|
|
107
108
|
const out = await this.bindTo(cls, item, namespace);
|
|
108
109
|
try {
|
|
109
110
|
await SchemaValidator.validate(cls, out);
|
|
110
|
-
} catch (
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err instanceof ValidationResultError) {
|
|
113
|
+
err.message = `Failed to construct ${cls.ᚕid} as validation errors have occurred`;
|
|
114
|
+
err.payload = { class: cls.ᚕid, file: cls.ᚕfile, ...(err.payload ?? {}) };
|
|
114
115
|
}
|
|
115
|
-
throw
|
|
116
|
+
throw err;
|
|
116
117
|
}
|
|
117
118
|
if (out && !internal) {
|
|
118
119
|
Util.deepAssign(ConfigUtil.lookupRoot(this.#active, namespace, true), out, 'coerce');
|
|
@@ -123,7 +124,7 @@ class $ConfigManager {
|
|
|
123
124
|
/**
|
|
124
125
|
* Reset
|
|
125
126
|
*/
|
|
126
|
-
reset() {
|
|
127
|
+
reset(): void {
|
|
127
128
|
this.#storage = {};
|
|
128
129
|
this.#initialized = false;
|
|
129
130
|
}
|
package/support/phase.init.ts
CHANGED