@devopsplaybook.io/common-utils 1.2.0-beta.7.84f3f67 → 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/dist/src/ConfigBase.d.ts +13 -0
- package/dist/src/ConfigBase.js +79 -12
- package/package.json +1 -1
- package/src/ConfigBase.ts +104 -13
package/dist/src/ConfigBase.d.ts
CHANGED
|
@@ -7,6 +7,19 @@ export interface ConfigFieldDef {
|
|
|
7
7
|
field: string;
|
|
8
8
|
/** When `true` the value is masked in log output. */
|
|
9
9
|
sensitive?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Alternative environment variable names to check when the primary field
|
|
12
|
+
* name is not found in `process.env`. Aliases are tried in order and the
|
|
13
|
+
* first match wins. Only checked in the environment layer, never in the
|
|
14
|
+
* config-file layer.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // Look for DATABASE_POSTGRES_HOST first, then fall back to POSTGRES_HOST
|
|
19
|
+
* { field: "DATABASE_POSTGRES_HOST", envAliases: ["POSTGRES_HOST"] }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
envAliases?: string[];
|
|
10
23
|
}
|
|
11
24
|
/**
|
|
12
25
|
* Database-specific configuration fields shared by every project that
|
package/dist/src/ConfigBase.js
CHANGED
|
@@ -40,6 +40,48 @@ exports.ConfigBase = void 0;
|
|
|
40
40
|
const fse = __importStar(require("fs-extra"));
|
|
41
41
|
const uuid_1 = require("uuid");
|
|
42
42
|
const path_1 = __importDefault(require("path"));
|
|
43
|
+
/**
|
|
44
|
+
* Coerce a string value read from an environment variable to match the
|
|
45
|
+
* type of the default value (number → parseFloat, boolean → "true"/"1",
|
|
46
|
+
* array → JSON.parse, etc.). When the default is already a string or
|
|
47
|
+
* there is no default, the original string is returned as-is.
|
|
48
|
+
*/
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
function coerceValue(value, defaultValue) {
|
|
51
|
+
if (defaultValue === undefined || defaultValue === null) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
switch (typeof defaultValue) {
|
|
55
|
+
case "number": {
|
|
56
|
+
const parsed = Number(value);
|
|
57
|
+
return Number.isNaN(parsed) ? value : parsed;
|
|
58
|
+
}
|
|
59
|
+
case "boolean": {
|
|
60
|
+
return value === "true" || value === "1" || value === "yes";
|
|
61
|
+
}
|
|
62
|
+
case "object": {
|
|
63
|
+
if (Array.isArray(defaultValue)) {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(value);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (defaultValue !== null) {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(value);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
43
85
|
/**
|
|
44
86
|
* Abstract base class for project configuration.
|
|
45
87
|
*
|
|
@@ -94,7 +136,6 @@ class ConfigBase {
|
|
|
94
136
|
* Fields registered by subclasses (or the base) that {@link reload}
|
|
95
137
|
* should process. Common / DB / OTel fields are pre-registered.
|
|
96
138
|
*/
|
|
97
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
139
|
this._fields = [];
|
|
99
140
|
this.SERVICE_ID = serviceId;
|
|
100
141
|
this.CONFIG_FILE = configFile || process.env.CONFIG_FILE || "config.json";
|
|
@@ -117,11 +158,15 @@ class ConfigBase {
|
|
|
117
158
|
{ field: "JWT_KEY", sensitive: true },
|
|
118
159
|
{ field: "LOG_LEVEL" },
|
|
119
160
|
{ field: "DATABASE_TYPE" },
|
|
120
|
-
{ field: "DATABASE_POSTGRES_HOST" },
|
|
121
|
-
{ field: "DATABASE_POSTGRES_PORT" },
|
|
122
|
-
{ field: "DATABASE_POSTGRES_USER" },
|
|
123
|
-
{
|
|
124
|
-
|
|
161
|
+
{ field: "DATABASE_POSTGRES_HOST", envAliases: ["POSTGRES_HOST"] },
|
|
162
|
+
{ field: "DATABASE_POSTGRES_PORT", envAliases: ["POSTGRES_PORT"] },
|
|
163
|
+
{ field: "DATABASE_POSTGRES_USER", envAliases: ["POSTGRES_USER"] },
|
|
164
|
+
{
|
|
165
|
+
field: "DATABASE_POSTGRES_PASSWORD",
|
|
166
|
+
sensitive: true,
|
|
167
|
+
envAliases: ["POSTGRES_PASSWORD"],
|
|
168
|
+
},
|
|
169
|
+
{ field: "DATABASE_POSTGRES_DATABASE", envAliases: ["POSTGRES_DB"] },
|
|
125
170
|
{ field: "OPENTELEMETRY_COLLECTOR_HTTP_TRACES" },
|
|
126
171
|
{ field: "OPENTELEMETRY_COLLECTOR_HTTP_METRICS" },
|
|
127
172
|
{ field: "OPENTELEMETRY_COLLECTOR_HTTP_LOGS" },
|
|
@@ -146,10 +191,11 @@ class ConfigBase {
|
|
|
146
191
|
* Call this in your subclass constructor for every project-specific field.
|
|
147
192
|
*/
|
|
148
193
|
addConfigField(def) {
|
|
149
|
-
var _a;
|
|
194
|
+
var _a, _b;
|
|
150
195
|
this._fields.push({
|
|
151
196
|
field: def.field,
|
|
152
197
|
sensitive: (_a = def.sensitive) !== null && _a !== void 0 ? _a : false,
|
|
198
|
+
envAliases: (_b = def.envAliases) !== null && _b !== void 0 ? _b : [],
|
|
153
199
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
200
|
defaultValue: this[def.field],
|
|
155
201
|
});
|
|
@@ -175,14 +221,35 @@ class ConfigBase {
|
|
|
175
221
|
log(`Configuration Value: CONFIG_FILE: ${this.CONFIG_FILE}`);
|
|
176
222
|
log(`Configuration Value: SERVICE_ID: ${this.SERVICE_ID}`);
|
|
177
223
|
log(`Configuration Value: VERSION: ${this.VERSION}`);
|
|
178
|
-
for (const { field, sensitive } of this._fields) {
|
|
224
|
+
for (const { field, sensitive, envAliases, defaultValue } of this._fields) {
|
|
179
225
|
let from = "defaults";
|
|
226
|
+
let foundValue;
|
|
227
|
+
let usedAlias = false;
|
|
228
|
+
// 1. Primary environment variable name
|
|
180
229
|
if (process.env[field] !== undefined) {
|
|
181
|
-
|
|
182
|
-
this[field] = process.env[field];
|
|
230
|
+
foundValue = process.env[field];
|
|
183
231
|
from = "environment";
|
|
184
232
|
}
|
|
185
|
-
|
|
233
|
+
// 2. Check aliases if primary env var was not set
|
|
234
|
+
if (foundValue === undefined && envAliases.length > 0) {
|
|
235
|
+
for (const alias of envAliases) {
|
|
236
|
+
if (process.env[alias] !== undefined) {
|
|
237
|
+
foundValue = process.env[alias];
|
|
238
|
+
from = "environment";
|
|
239
|
+
usedAlias = true;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// 3. Apply environment value (full name or alias) if found,
|
|
245
|
+
// coercing strings to match the default value type
|
|
246
|
+
if (foundValue !== undefined) {
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
248
|
+
this[field] = coerceValue(foundValue, defaultValue);
|
|
249
|
+
}
|
|
250
|
+
// 4. Config file override (environment always wins, but if neither
|
|
251
|
+
// environment nor alias matched, check config file)
|
|
252
|
+
if (foundValue === undefined && content[field] !== undefined) {
|
|
186
253
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
254
|
this[field] = content[field];
|
|
188
255
|
from = "config";
|
|
@@ -193,7 +260,7 @@ class ConfigBase {
|
|
|
193
260
|
else {
|
|
194
261
|
log(
|
|
195
262
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
-
`Configuration Value: ${field}: ${this[field]} (from ${from})`);
|
|
263
|
+
`Configuration Value: ${field}: ${this[field]} (from ${from}${usedAlias ? ` via alias` : ""})`);
|
|
197
264
|
}
|
|
198
265
|
}
|
|
199
266
|
}
|
package/package.json
CHANGED
package/src/ConfigBase.ts
CHANGED
|
@@ -11,6 +11,19 @@ export interface ConfigFieldDef {
|
|
|
11
11
|
field: string;
|
|
12
12
|
/** When `true` the value is masked in log output. */
|
|
13
13
|
sensitive?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Alternative environment variable names to check when the primary field
|
|
16
|
+
* name is not found in `process.env`. Aliases are tried in order and the
|
|
17
|
+
* first match wins. Only checked in the environment layer, never in the
|
|
18
|
+
* config-file layer.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* // Look for DATABASE_POSTGRES_HOST first, then fall back to POSTGRES_HOST
|
|
23
|
+
* { field: "DATABASE_POSTGRES_HOST", envAliases: ["POSTGRES_HOST"] }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
envAliases?: string[];
|
|
14
27
|
}
|
|
15
28
|
|
|
16
29
|
/**
|
|
@@ -40,6 +53,48 @@ export interface ConfigCommonInterface
|
|
|
40
53
|
LOG_LEVEL: string;
|
|
41
54
|
}
|
|
42
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Coerce a string value read from an environment variable to match the
|
|
58
|
+
* type of the default value (number → parseFloat, boolean → "true"/"1",
|
|
59
|
+
* array → JSON.parse, etc.). When the default is already a string or
|
|
60
|
+
* there is no default, the original string is returned as-is.
|
|
61
|
+
*/
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
function coerceValue(value: string, defaultValue: any): any {
|
|
64
|
+
if (defaultValue === undefined || defaultValue === null) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
switch (typeof defaultValue) {
|
|
69
|
+
case "number": {
|
|
70
|
+
const parsed = Number(value);
|
|
71
|
+
return Number.isNaN(parsed) ? value : parsed;
|
|
72
|
+
}
|
|
73
|
+
case "boolean": {
|
|
74
|
+
return value === "true" || value === "1" || value === "yes";
|
|
75
|
+
}
|
|
76
|
+
case "object": {
|
|
77
|
+
if (Array.isArray(defaultValue)) {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(value);
|
|
80
|
+
} catch {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (defaultValue !== null) {
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(value);
|
|
87
|
+
} catch {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
default:
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
43
98
|
/**
|
|
44
99
|
* Abstract base class for project configuration.
|
|
45
100
|
*
|
|
@@ -96,9 +151,13 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
96
151
|
* Fields registered by subclasses (or the base) that {@link reload}
|
|
97
152
|
* should process. Common / DB / OTel fields are pre-registered.
|
|
98
153
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
154
|
+
private _fields: {
|
|
155
|
+
field: string;
|
|
156
|
+
sensitive: boolean;
|
|
157
|
+
envAliases: string[];
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
159
|
+
defaultValue: any;
|
|
160
|
+
}[] = [];
|
|
102
161
|
|
|
103
162
|
/**
|
|
104
163
|
* @param serviceId Unique service identifier (e.g. `"cryptotrader-server"`).
|
|
@@ -127,11 +186,15 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
127
186
|
{ field: "JWT_KEY", sensitive: true },
|
|
128
187
|
{ field: "LOG_LEVEL" },
|
|
129
188
|
{ field: "DATABASE_TYPE" },
|
|
130
|
-
{ field: "DATABASE_POSTGRES_HOST" },
|
|
131
|
-
{ field: "DATABASE_POSTGRES_PORT" },
|
|
132
|
-
{ field: "DATABASE_POSTGRES_USER" },
|
|
133
|
-
{
|
|
134
|
-
|
|
189
|
+
{ field: "DATABASE_POSTGRES_HOST", envAliases: ["POSTGRES_HOST"] },
|
|
190
|
+
{ field: "DATABASE_POSTGRES_PORT", envAliases: ["POSTGRES_PORT"] },
|
|
191
|
+
{ field: "DATABASE_POSTGRES_USER", envAliases: ["POSTGRES_USER"] },
|
|
192
|
+
{
|
|
193
|
+
field: "DATABASE_POSTGRES_PASSWORD",
|
|
194
|
+
sensitive: true,
|
|
195
|
+
envAliases: ["POSTGRES_PASSWORD"],
|
|
196
|
+
},
|
|
197
|
+
{ field: "DATABASE_POSTGRES_DATABASE", envAliases: ["POSTGRES_DB"] },
|
|
135
198
|
{ field: "OPENTELEMETRY_COLLECTOR_HTTP_TRACES" },
|
|
136
199
|
{ field: "OPENTELEMETRY_COLLECTOR_HTTP_METRICS" },
|
|
137
200
|
{ field: "OPENTELEMETRY_COLLECTOR_HTTP_LOGS" },
|
|
@@ -160,6 +223,7 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
160
223
|
this._fields.push({
|
|
161
224
|
field: def.field,
|
|
162
225
|
sensitive: def.sensitive ?? false,
|
|
226
|
+
envAliases: def.envAliases ?? [],
|
|
163
227
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
228
|
defaultValue: (this as any)[def.field],
|
|
165
229
|
});
|
|
@@ -187,17 +251,44 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
187
251
|
log(`Configuration Value: SERVICE_ID: ${this.SERVICE_ID}`);
|
|
188
252
|
log(`Configuration Value: VERSION: ${this.VERSION}`);
|
|
189
253
|
|
|
190
|
-
for (const { field, sensitive } of this._fields) {
|
|
254
|
+
for (const { field, sensitive, envAliases, defaultValue } of this._fields) {
|
|
191
255
|
let from = "defaults";
|
|
256
|
+
let foundValue: string | undefined;
|
|
257
|
+
let usedAlias = false;
|
|
258
|
+
|
|
259
|
+
// 1. Primary environment variable name
|
|
192
260
|
if (process.env[field] !== undefined) {
|
|
193
|
-
|
|
194
|
-
(this as any)[field] = process.env[field];
|
|
261
|
+
foundValue = process.env[field];
|
|
195
262
|
from = "environment";
|
|
196
|
-
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 2. Check aliases if primary env var was not set
|
|
266
|
+
if (foundValue === undefined && envAliases.length > 0) {
|
|
267
|
+
for (const alias of envAliases) {
|
|
268
|
+
if (process.env[alias] !== undefined) {
|
|
269
|
+
foundValue = process.env[alias];
|
|
270
|
+
from = "environment";
|
|
271
|
+
usedAlias = true;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 3. Apply environment value (full name or alias) if found,
|
|
278
|
+
// coercing strings to match the default value type
|
|
279
|
+
if (foundValue !== undefined) {
|
|
280
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
281
|
+
(this as any)[field] = coerceValue(foundValue, defaultValue);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 4. Config file override (environment always wins, but if neither
|
|
285
|
+
// environment nor alias matched, check config file)
|
|
286
|
+
if (foundValue === undefined && content[field] !== undefined) {
|
|
197
287
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
288
|
(this as any)[field] = content[field];
|
|
199
289
|
from = "config";
|
|
200
290
|
}
|
|
291
|
+
|
|
201
292
|
if (sensitive) {
|
|
202
293
|
log(
|
|
203
294
|
`Configuration Value: ${field}: ******************** (from ${from})`,
|
|
@@ -205,7 +296,7 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
205
296
|
} else {
|
|
206
297
|
log(
|
|
207
298
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
208
|
-
`Configuration Value: ${field}: ${(this as any)[field]} (from ${from})`,
|
|
299
|
+
`Configuration Value: ${field}: ${(this as any)[field]} (from ${from}${usedAlias ? ` via alias` : ""})`,
|
|
209
300
|
);
|
|
210
301
|
}
|
|
211
302
|
}
|