@rawnodes/config-loader 1.4.0 → 1.5.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 CHANGED
@@ -6,6 +6,8 @@ Flexible YAML configuration loader for Node.js applications with environment ove
6
6
 
7
7
  - **YAML Configuration** - Load config from YAML files with environment-specific overrides
8
8
  - **Environment Variables** - Replace `${VAR}` placeholders with env values
9
+ - **HashiCorp Vault** - Read secrets from Vault using `${vault:path:field}` syntax
10
+ - **AWS Secrets Manager** - Read secrets from AWS using `${aws:secret-name:field}` syntax
9
11
  - **Zod Validation** - Optional schema validation with detailed error messages
10
12
  - **Docker/K8s Ready** - Mount additional config files via `overrideDir`
11
13
  - **Secret Masking** - Automatic masking of sensitive values in logs
@@ -103,6 +105,21 @@ interface ConfigLoaderOptions<T> {
103
105
  // Remove empty strings and empty objects from config (default: false)
104
106
  // Useful for optional fields with ${VAR:} placeholders
105
107
  stripEmpty?: boolean;
108
+
109
+ // HashiCorp Vault options (requires loadConfigAsync)
110
+ vault?: {
111
+ endpoint: string; // Vault server URL
112
+ roleId: string; // AppRole role_id
113
+ secretId: string; // AppRole secret_id
114
+ namespace?: string; // Optional Vault Enterprise namespace
115
+ };
116
+
117
+ // AWS Secrets Manager options (requires loadConfigAsync)
118
+ aws?: {
119
+ region: string; // AWS region
120
+ accessKeyId: string; // AWS access key ID
121
+ secretAccessKey: string; // AWS secret access key
122
+ };
106
123
  }
107
124
  ```
108
125
 
@@ -161,6 +178,116 @@ const { config } = loadConfig({
161
178
  - Arrays: empty strings filtered out
162
179
  - Other falsy values (`0`, `false`, `null`) are preserved
163
180
 
181
+ ## HashiCorp Vault Integration
182
+
183
+ Read secrets directly from HashiCorp Vault using AppRole authentication.
184
+
185
+ ### Setup
186
+
187
+ ```yaml
188
+ # config/base.yml
189
+ database:
190
+ host: localhost
191
+ password: ${vault:secret/data/api:DB_PASSWORD}
192
+ port: ${vault:secret/data/api:DB_PORT:5432} # with default value
193
+ ```
194
+
195
+ ```typescript
196
+ import { loadConfigAsync } from '@rawnodes/config-loader';
197
+
198
+ const { config } = await loadConfigAsync({
199
+ configDir: './config',
200
+ vault: {
201
+ endpoint: 'https://vault.example.com',
202
+ roleId: process.env.VAULT_ROLE_ID!,
203
+ secretId: process.env.VAULT_SECRET_ID!,
204
+ namespace: 'optional-namespace', // for Vault Enterprise
205
+ },
206
+ });
207
+ ```
208
+
209
+ ### Syntax
210
+
211
+ ```
212
+ ${vault:PATH:FIELD} - Required secret
213
+ ${vault:PATH:FIELD:DEFAULT} - With default value (used if secret not found)
214
+ ```
215
+
216
+ **Examples:**
217
+ ```yaml
218
+ database:
219
+ # Read DB_PASSWORD from secret/data/api
220
+ password: ${vault:secret/data/api:DB_PASSWORD}
221
+
222
+ # With default value (supports colons in default)
223
+ url: ${vault:secret/data/db:URL:postgres://localhost:5432/app}
224
+
225
+ # Mix with env variables
226
+ host: ${DB_HOST:localhost}
227
+ ```
228
+
229
+ ### Notes
230
+
231
+ - Use `loadConfigAsync()` instead of `loadConfig()` when using Vault
232
+ - Secrets are cached per path during config load (multiple fields from same path = 1 API call)
233
+ - Supports Vault KV v1 and v2 secret engines
234
+
235
+ ## AWS Secrets Manager Integration
236
+
237
+ Read secrets from AWS Secrets Manager.
238
+
239
+ ### Setup
240
+
241
+ ```yaml
242
+ # config/base.yml
243
+ database:
244
+ password: ${aws:my-app/database:DB_PASSWORD}
245
+ host: ${aws:my-app/database:DB_HOST:localhost}
246
+ api:
247
+ key: ${aws:my-app/api-key} # plain string secret (no field)
248
+ ```
249
+
250
+ ```typescript
251
+ import { loadConfigAsync } from '@rawnodes/config-loader';
252
+
253
+ const { config } = await loadConfigAsync({
254
+ configDir: './config',
255
+ aws: {
256
+ region: 'us-east-1',
257
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
258
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
259
+ },
260
+ });
261
+ ```
262
+
263
+ ### Syntax
264
+
265
+ ```
266
+ ${aws:SECRET_NAME} - Plain string secret (entire value)
267
+ ${aws:SECRET_NAME:FIELD} - JSON secret, extract field
268
+ ${aws:SECRET_NAME:FIELD:DEFAULT} - With default value
269
+ ```
270
+
271
+ **Examples:**
272
+ ```yaml
273
+ database:
274
+ # JSON secret: {"DB_PASSWORD": "secret", "DB_USER": "admin"}
275
+ password: ${aws:my-app/database:DB_PASSWORD}
276
+ user: ${aws:my-app/database:DB_USER}
277
+
278
+ # Plain string secret (no field)
279
+ api_key: ${aws:my-app/api-key}
280
+
281
+ # With default value
282
+ host: ${aws:my-app/database:DB_HOST:localhost}
283
+ ```
284
+
285
+ ### Notes
286
+
287
+ - Use `loadConfigAsync()` instead of `loadConfig()` when using AWS
288
+ - Secrets are cached per secret name during config load
289
+ - Supports both JSON and plain string secrets
290
+
164
291
  ## Zod Validation
165
292
 
166
293
  ```typescript
@@ -234,7 +361,7 @@ export class ConfigModule {}
234
361
 
235
362
  ### `loadConfig<T>(options?): ConfigLoaderResult<T>`
236
363
 
237
- Loads and merges configuration files.
364
+ Loads and merges configuration files synchronously.
238
365
 
239
366
  **Returns:**
240
367
  ```typescript
@@ -245,6 +372,21 @@ interface ConfigLoaderResult<T> {
245
372
  }
246
373
  ```
247
374
 
375
+ ### `loadConfigAsync<T>(options?): Promise<ConfigLoaderResult<T>>`
376
+
377
+ Async version with Vault support. Required when using `vault` option.
378
+
379
+ ```typescript
380
+ const { config } = await loadConfigAsync({
381
+ configDir: './config',
382
+ vault: {
383
+ endpoint: 'https://vault.example.com',
384
+ roleId: 'role-id',
385
+ secretId: 'secret-id',
386
+ },
387
+ });
388
+ ```
389
+
248
390
  ### `maskSecrets(obj): unknown`
249
391
 
250
392
  Masks sensitive values in an object. Useful for logging.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,39 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ interface VaultOptions {
4
+ /**
5
+ * Vault server endpoint URL
6
+ * @example 'https://vault.example.com'
7
+ */
8
+ endpoint: string;
9
+ /**
10
+ * AppRole role_id for authentication
11
+ */
12
+ roleId: string;
13
+ /**
14
+ * AppRole secret_id for authentication
15
+ */
16
+ secretId: string;
17
+ /**
18
+ * Optional namespace for Vault Enterprise
19
+ */
20
+ namespace?: string;
21
+ }
22
+ interface AwsSecretsOptions {
23
+ /**
24
+ * AWS region
25
+ * @example 'us-east-1'
26
+ */
27
+ region: string;
28
+ /**
29
+ * AWS access key ID
30
+ */
31
+ accessKeyId: string;
32
+ /**
33
+ * AWS secret access key
34
+ */
35
+ secretAccessKey: string;
36
+ }
3
37
  interface DotenvOptions {
4
38
  /**
5
39
  * Path to .env file
@@ -63,6 +97,18 @@ interface ConfigLoaderOptions<T = unknown> {
63
97
  * @default false
64
98
  */
65
99
  stripEmpty?: boolean;
100
+ /**
101
+ * HashiCorp Vault options for secret retrieval
102
+ * If provided, ${vault:path:field} placeholders will be resolved
103
+ * Requires using loadConfigAsync() instead of loadConfig()
104
+ */
105
+ vault?: VaultOptions;
106
+ /**
107
+ * AWS Secrets Manager options for secret retrieval
108
+ * If provided, ${aws:secret-name:field} placeholders will be resolved
109
+ * Requires using loadConfigAsync() instead of loadConfig()
110
+ */
111
+ aws?: AwsSecretsOptions;
66
112
  }
67
113
  interface ConfigLoaderResult<T> {
68
114
  config: T;
@@ -71,14 +117,37 @@ interface ConfigLoaderResult<T> {
71
117
  }
72
118
 
73
119
  declare function loadConfig<T>(options?: ConfigLoaderOptions<T>): ConfigLoaderResult<T>;
120
+ declare function loadConfigAsync<T>(options?: ConfigLoaderOptions<T>): Promise<ConfigLoaderResult<T>>;
74
121
 
75
122
  type DeepObject = Record<string, unknown>;
76
123
  declare function deepMerge(base: DeepObject, override: DeepObject): DeepObject;
77
124
 
78
- declare function replacePlaceholders(obj: unknown): unknown;
125
+ interface VaultSecretCache {
126
+ [path: string]: Record<string, unknown>;
127
+ }
128
+ interface VaultClient {
129
+ fetchSecrets(obj: unknown): Promise<VaultSecretCache>;
130
+ getSecret(path: string, field: string): unknown;
131
+ }
132
+ declare function createVaultClient(options: VaultOptions): Promise<VaultClient>;
133
+
134
+ interface AwsSecretsCache {
135
+ [secretName: string]: string | Record<string, unknown>;
136
+ }
137
+ interface AwsSecretsClient {
138
+ fetchSecrets(obj: unknown): Promise<AwsSecretsCache>;
139
+ getSecret(secretName: string, field?: string): unknown;
140
+ }
141
+ declare function createAwsSecretsClient(options: AwsSecretsOptions): Promise<AwsSecretsClient>;
142
+
143
+ interface ReplacePlaceholdersOptions {
144
+ vaultClient?: VaultClient;
145
+ awsClient?: AwsSecretsClient;
146
+ }
147
+ declare function replacePlaceholders(obj: unknown, options?: ReplacePlaceholdersOptions): unknown;
79
148
 
80
149
  declare function maskSecrets(obj: unknown): unknown;
81
150
 
82
151
  declare function stripEmpty(obj: unknown): unknown;
83
152
 
84
- export { type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, deepMerge, loadConfig, maskSecrets, replacePlaceholders, stripEmpty };
153
+ export { type AwsSecretsCache, type AwsSecretsClient, type AwsSecretsOptions, type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, type VaultClient, type VaultOptions, type VaultSecretCache, createAwsSecretsClient, createVaultClient, deepMerge, loadConfig, loadConfigAsync, maskSecrets, replacePlaceholders, stripEmpty };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,39 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ interface VaultOptions {
4
+ /**
5
+ * Vault server endpoint URL
6
+ * @example 'https://vault.example.com'
7
+ */
8
+ endpoint: string;
9
+ /**
10
+ * AppRole role_id for authentication
11
+ */
12
+ roleId: string;
13
+ /**
14
+ * AppRole secret_id for authentication
15
+ */
16
+ secretId: string;
17
+ /**
18
+ * Optional namespace for Vault Enterprise
19
+ */
20
+ namespace?: string;
21
+ }
22
+ interface AwsSecretsOptions {
23
+ /**
24
+ * AWS region
25
+ * @example 'us-east-1'
26
+ */
27
+ region: string;
28
+ /**
29
+ * AWS access key ID
30
+ */
31
+ accessKeyId: string;
32
+ /**
33
+ * AWS secret access key
34
+ */
35
+ secretAccessKey: string;
36
+ }
3
37
  interface DotenvOptions {
4
38
  /**
5
39
  * Path to .env file
@@ -63,6 +97,18 @@ interface ConfigLoaderOptions<T = unknown> {
63
97
  * @default false
64
98
  */
65
99
  stripEmpty?: boolean;
100
+ /**
101
+ * HashiCorp Vault options for secret retrieval
102
+ * If provided, ${vault:path:field} placeholders will be resolved
103
+ * Requires using loadConfigAsync() instead of loadConfig()
104
+ */
105
+ vault?: VaultOptions;
106
+ /**
107
+ * AWS Secrets Manager options for secret retrieval
108
+ * If provided, ${aws:secret-name:field} placeholders will be resolved
109
+ * Requires using loadConfigAsync() instead of loadConfig()
110
+ */
111
+ aws?: AwsSecretsOptions;
66
112
  }
67
113
  interface ConfigLoaderResult<T> {
68
114
  config: T;
@@ -71,14 +117,37 @@ interface ConfigLoaderResult<T> {
71
117
  }
72
118
 
73
119
  declare function loadConfig<T>(options?: ConfigLoaderOptions<T>): ConfigLoaderResult<T>;
120
+ declare function loadConfigAsync<T>(options?: ConfigLoaderOptions<T>): Promise<ConfigLoaderResult<T>>;
74
121
 
75
122
  type DeepObject = Record<string, unknown>;
76
123
  declare function deepMerge(base: DeepObject, override: DeepObject): DeepObject;
77
124
 
78
- declare function replacePlaceholders(obj: unknown): unknown;
125
+ interface VaultSecretCache {
126
+ [path: string]: Record<string, unknown>;
127
+ }
128
+ interface VaultClient {
129
+ fetchSecrets(obj: unknown): Promise<VaultSecretCache>;
130
+ getSecret(path: string, field: string): unknown;
131
+ }
132
+ declare function createVaultClient(options: VaultOptions): Promise<VaultClient>;
133
+
134
+ interface AwsSecretsCache {
135
+ [secretName: string]: string | Record<string, unknown>;
136
+ }
137
+ interface AwsSecretsClient {
138
+ fetchSecrets(obj: unknown): Promise<AwsSecretsCache>;
139
+ getSecret(secretName: string, field?: string): unknown;
140
+ }
141
+ declare function createAwsSecretsClient(options: AwsSecretsOptions): Promise<AwsSecretsClient>;
142
+
143
+ interface ReplacePlaceholdersOptions {
144
+ vaultClient?: VaultClient;
145
+ awsClient?: AwsSecretsClient;
146
+ }
147
+ declare function replacePlaceholders(obj: unknown, options?: ReplacePlaceholdersOptions): unknown;
79
148
 
80
149
  declare function maskSecrets(obj: unknown): unknown;
81
150
 
82
151
  declare function stripEmpty(obj: unknown): unknown;
83
152
 
84
- export { type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, deepMerge, loadConfig, maskSecrets, replacePlaceholders, stripEmpty };
153
+ export { type AwsSecretsCache, type AwsSecretsClient, type AwsSecretsOptions, type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, type VaultClient, type VaultOptions, type VaultSecretCache, createAwsSecretsClient, createVaultClient, deepMerge, loadConfig, loadConfigAsync, maskSecrets, replacePlaceholders, stripEmpty };
package/dist/index.js CHANGED
@@ -4,6 +4,10 @@ var fs = require('fs');
4
4
  var yaml = require('js-yaml');
5
5
  var dotenv = require('dotenv');
6
6
  var path = require('path');
7
+ var NodeVault = require('node-vault');
8
+ var clientSecretsManager = require('@aws-sdk/client-secrets-manager');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
11
 
8
12
  function _interopNamespace(e) {
9
13
  if (e && e.__esModule) return e;
@@ -25,6 +29,7 @@ function _interopNamespace(e) {
25
29
 
26
30
  var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
27
31
  var dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
32
+ var NodeVault__default = /*#__PURE__*/_interopDefault(NodeVault);
28
33
 
29
34
  // src/loader.ts
30
35
 
@@ -48,41 +53,104 @@ function isPlainObject(value) {
48
53
 
49
54
  // src/utils/env-replacer.ts
50
55
  var ENV_PLACEHOLDER_REGEX = /\${(.*?)}/g;
51
- function replacePlaceholders(obj) {
56
+ var VAULT_PREFIX = "vault:";
57
+ var AWS_PREFIX = "aws:";
58
+ function replacePlaceholders(obj, options = {}) {
52
59
  if (typeof obj === "string") {
53
- return replaceStringPlaceholders(obj);
60
+ return replaceStringPlaceholders(obj, options);
54
61
  }
55
62
  if (Array.isArray(obj)) {
56
- return obj.map((item) => replacePlaceholders(item));
63
+ return obj.map((item) => replacePlaceholders(item, options));
57
64
  }
58
65
  if (isPlainObject2(obj)) {
59
66
  const result = {};
60
67
  for (const key of Object.keys(obj)) {
61
- result[key] = replacePlaceholders(obj[key]);
68
+ result[key] = replacePlaceholders(obj[key], options);
62
69
  }
63
70
  return result;
64
71
  }
65
72
  return obj;
66
73
  }
67
- function replaceStringPlaceholders(str) {
74
+ function replaceStringPlaceholders(str, options) {
68
75
  return str.replace(ENV_PLACEHOLDER_REGEX, (match, key) => {
69
- const [envKey, ...rest] = key.split(":");
70
- const defaultValue = rest.length > 0 ? rest.join(":") : void 0;
71
- const envValue = process.env[envKey];
72
- let value;
73
- if (envValue !== void 0) {
74
- value = envValue;
75
- } else if (defaultValue !== void 0) {
76
- value = defaultValue;
77
- } else {
78
- throw new Error(`Environment variable "${envKey}" is not defined and no default value provided`);
76
+ if (key.startsWith(VAULT_PREFIX)) {
77
+ return resolveVaultPlaceholder(key, match, options);
79
78
  }
80
- if (value.includes("${")) {
81
- return replacePlaceholders(value);
79
+ if (key.startsWith(AWS_PREFIX)) {
80
+ return resolveAwsPlaceholder(key, match, options);
82
81
  }
83
- return value;
82
+ return resolveEnvPlaceholder(key, match, options);
84
83
  });
85
84
  }
85
+ function resolveVaultPlaceholder(key, originalMatch, options) {
86
+ const parts = key.substring(VAULT_PREFIX.length).split(":");
87
+ if (parts.length < 2) {
88
+ throw new Error(
89
+ `Invalid Vault placeholder format "${originalMatch}". Expected \${vault:path:field} or \${vault:path:field:default}`
90
+ );
91
+ }
92
+ const [path, field, ...defaultParts] = parts;
93
+ const defaultValue = defaultParts.length > 0 ? defaultParts.join(":") : void 0;
94
+ if (!options.vaultClient) {
95
+ if (defaultValue !== void 0) {
96
+ return defaultValue;
97
+ }
98
+ throw new Error(`Vault placeholder "${originalMatch}" found but no Vault options provided`);
99
+ }
100
+ const secretValue = options.vaultClient.getSecret(path, field);
101
+ if (secretValue !== void 0) {
102
+ return String(secretValue);
103
+ }
104
+ if (defaultValue !== void 0) {
105
+ return defaultValue;
106
+ }
107
+ throw new Error(`Vault secret "${field}" not found at path "${path}" and no default value provided`);
108
+ }
109
+ function resolveAwsPlaceholder(key, originalMatch, options) {
110
+ const parts = key.substring(AWS_PREFIX.length).split(":");
111
+ if (parts.length < 1 || !parts[0]) {
112
+ throw new Error(
113
+ `Invalid AWS placeholder format "${originalMatch}". Expected \${aws:secret-name} or \${aws:secret-name:field} or \${aws:secret-name:field:default}`
114
+ );
115
+ }
116
+ const [secretName, field, ...defaultParts] = parts;
117
+ const defaultValue = defaultParts.length > 0 ? defaultParts.join(":") : void 0;
118
+ if (!options.awsClient) {
119
+ if (defaultValue !== void 0) {
120
+ return defaultValue;
121
+ }
122
+ throw new Error(`AWS placeholder "${originalMatch}" found but no AWS options provided`);
123
+ }
124
+ const secretValue = options.awsClient.getSecret(secretName, field || void 0);
125
+ if (secretValue !== void 0) {
126
+ return String(secretValue);
127
+ }
128
+ if (defaultValue !== void 0) {
129
+ return defaultValue;
130
+ }
131
+ if (field) {
132
+ throw new Error(`AWS secret field "${field}" not found in "${secretName}" and no default value provided`);
133
+ } else {
134
+ throw new Error(`AWS secret "${secretName}" not found and no default value provided`);
135
+ }
136
+ }
137
+ function resolveEnvPlaceholder(key, _match, options) {
138
+ const [envKey, ...rest] = key.split(":");
139
+ const defaultValue = rest.length > 0 ? rest.join(":") : void 0;
140
+ const envValue = process.env[envKey];
141
+ let value;
142
+ if (envValue !== void 0) {
143
+ value = envValue;
144
+ } else if (defaultValue !== void 0) {
145
+ value = defaultValue;
146
+ } else {
147
+ throw new Error(`Environment variable "${envKey}" is not defined and no default value provided`);
148
+ }
149
+ if (value.includes("${")) {
150
+ return replacePlaceholders(value, options);
151
+ }
152
+ return value;
153
+ }
86
154
  function isPlainObject2(value) {
87
155
  return typeof value === "object" && value !== null && !Array.isArray(value);
88
156
  }
@@ -161,6 +229,129 @@ function stripEmpty(obj) {
161
229
  function isPlainObject4(value) {
162
230
  return typeof value === "object" && value !== null && !Array.isArray(value);
163
231
  }
232
+ var VAULT_PLACEHOLDER_REGEX = /\${vault:([^:}]+):([^:}]+)(?::([^}]*))?}/g;
233
+ async function createVaultClient(options) {
234
+ const vault = NodeVault__default.default({
235
+ endpoint: options.endpoint,
236
+ apiVersion: "v1",
237
+ namespace: options.namespace
238
+ });
239
+ const loginResult = await vault.approleLogin({
240
+ role_id: options.roleId,
241
+ secret_id: options.secretId
242
+ });
243
+ vault.token = loginResult.auth.client_token;
244
+ const cache = {};
245
+ return {
246
+ async fetchSecrets(obj) {
247
+ const paths = extractVaultPaths(obj);
248
+ await Promise.all(
249
+ paths.map(async (path) => {
250
+ if (!cache[path]) {
251
+ try {
252
+ const result = await vault.read(path);
253
+ cache[path] = result.data?.data ?? result.data ?? {};
254
+ } catch {
255
+ cache[path] = {};
256
+ }
257
+ }
258
+ })
259
+ );
260
+ return cache;
261
+ },
262
+ getSecret(path, field) {
263
+ return cache[path]?.[field];
264
+ }
265
+ };
266
+ }
267
+ function extractVaultPaths(obj) {
268
+ const paths = /* @__PURE__ */ new Set();
269
+ function traverse(value) {
270
+ if (typeof value === "string") {
271
+ let match;
272
+ while ((match = VAULT_PLACEHOLDER_REGEX.exec(value)) !== null) {
273
+ paths.add(match[1]);
274
+ }
275
+ VAULT_PLACEHOLDER_REGEX.lastIndex = 0;
276
+ } else if (Array.isArray(value)) {
277
+ value.forEach(traverse);
278
+ } else if (value !== null && typeof value === "object") {
279
+ Object.values(value).forEach(traverse);
280
+ }
281
+ }
282
+ traverse(obj);
283
+ return Array.from(paths);
284
+ }
285
+ var AWS_PLACEHOLDER_REGEX = /\${aws:([^:}]+)(?::([^:}]+))?(?::([^}]*))?}/g;
286
+ async function createAwsSecretsClient(options) {
287
+ const client = new clientSecretsManager.SecretsManagerClient({
288
+ region: options.region,
289
+ credentials: {
290
+ accessKeyId: options.accessKeyId,
291
+ secretAccessKey: options.secretAccessKey
292
+ }
293
+ });
294
+ const cache = {};
295
+ return {
296
+ async fetchSecrets(obj) {
297
+ const secretNames = extractAwsSecretNames(obj);
298
+ await Promise.all(
299
+ secretNames.map(async (secretName) => {
300
+ if (cache[secretName] === void 0) {
301
+ try {
302
+ const command = new clientSecretsManager.GetSecretValueCommand({ SecretId: secretName });
303
+ const response = await client.send(command);
304
+ const secretString = response.SecretString;
305
+ if (secretString) {
306
+ try {
307
+ cache[secretName] = JSON.parse(secretString);
308
+ } catch {
309
+ cache[secretName] = secretString;
310
+ }
311
+ } else {
312
+ cache[secretName] = {};
313
+ }
314
+ } catch {
315
+ cache[secretName] = {};
316
+ }
317
+ }
318
+ })
319
+ );
320
+ return cache;
321
+ },
322
+ getSecret(secretName, field) {
323
+ const secret = cache[secretName];
324
+ if (secret === void 0) {
325
+ return void 0;
326
+ }
327
+ if (!field) {
328
+ return typeof secret === "string" ? secret : void 0;
329
+ }
330
+ if (typeof secret === "object" && secret !== null) {
331
+ return secret[field];
332
+ }
333
+ return void 0;
334
+ }
335
+ };
336
+ }
337
+ function extractAwsSecretNames(obj) {
338
+ const names = /* @__PURE__ */ new Set();
339
+ function traverse(value) {
340
+ if (typeof value === "string") {
341
+ let match;
342
+ while ((match = AWS_PLACEHOLDER_REGEX.exec(value)) !== null) {
343
+ names.add(match[1]);
344
+ }
345
+ AWS_PLACEHOLDER_REGEX.lastIndex = 0;
346
+ } else if (Array.isArray(value)) {
347
+ value.forEach(traverse);
348
+ } else if (value !== null && typeof value === "object") {
349
+ Object.values(value).forEach(traverse);
350
+ }
351
+ }
352
+ traverse(obj);
353
+ return Array.from(names);
354
+ }
164
355
 
165
356
  // src/loader.ts
166
357
  var DEFAULT_OPTIONS = {
@@ -171,6 +362,9 @@ var DEFAULT_OPTIONS = {
171
362
  overrideDir: "/etc/app/config"
172
363
  };
173
364
  function loadConfig(options = {}) {
365
+ if (options.vault || options.aws) {
366
+ throw new Error("Vault/AWS options require async loading. Use loadConfigAsync() instead of loadConfig()");
367
+ }
174
368
  const opts = { ...DEFAULT_OPTIONS, ...options };
175
369
  const { configDir, baseFileName, environment, extension } = opts;
176
370
  if (options.dotenv) {
@@ -214,6 +408,60 @@ ${JSON.stringify(maskedConfig, null, 2)}`);
214
408
  configDir: resolvedConfigDir
215
409
  };
216
410
  }
411
+ async function loadConfigAsync(options = {}) {
412
+ const opts = { ...DEFAULT_OPTIONS, ...options };
413
+ const { configDir, baseFileName, environment, extension } = opts;
414
+ if (options.dotenv) {
415
+ loadDotenv(options.dotenv, environment);
416
+ }
417
+ const resolvedConfigDir = resolveConfigDir(configDir);
418
+ const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);
419
+ const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);
420
+ let mergedConfig = deepMerge(baseConfig, envConfig);
421
+ const overrideDir = options.overrideDir !== false ? options.overrideDir ?? DEFAULT_OPTIONS.overrideDir : null;
422
+ if (overrideDir) {
423
+ const overrideConfigs = loadOverrideDir(overrideDir);
424
+ for (const override of overrideConfigs) {
425
+ mergedConfig = deepMerge(mergedConfig, override);
426
+ }
427
+ }
428
+ let vaultClient;
429
+ if (options.vault) {
430
+ vaultClient = await createVaultClient(options.vault);
431
+ await vaultClient.fetchSecrets(mergedConfig);
432
+ }
433
+ let awsClient;
434
+ if (options.aws) {
435
+ awsClient = await createAwsSecretsClient(options.aws);
436
+ await awsClient.fetchSecrets(mergedConfig);
437
+ }
438
+ let config2 = replacePlaceholders(mergedConfig, { vaultClient, awsClient });
439
+ if (options.stripEmpty) {
440
+ config2 = stripEmpty(config2);
441
+ }
442
+ if (options.postProcess) {
443
+ config2 = options.postProcess(config2);
444
+ }
445
+ if (options.schema) {
446
+ const result = options.schema.safeParse(config2);
447
+ if (!result.success) {
448
+ const errors = result.error.issues.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
449
+ throw new Error(`Config validation failed:
450
+ ${errors}`);
451
+ }
452
+ config2 = result.data;
453
+ }
454
+ if (options.logger) {
455
+ const maskedConfig = maskSecrets(config2);
456
+ options.logger(`Config loaded (${environment}):
457
+ ${JSON.stringify(maskedConfig, null, 2)}`);
458
+ }
459
+ return {
460
+ config: config2,
461
+ environment,
462
+ configDir: resolvedConfigDir
463
+ };
464
+ }
217
465
  function loadDotenv(dotenvOption, environment) {
218
466
  let envPath;
219
467
  if (typeof dotenvOption === "object" && dotenvOption.path) {
@@ -256,8 +504,11 @@ function loadOverrideDir(dir) {
256
504
  return files.map((file) => loadYamlFile(dir, file));
257
505
  }
258
506
 
507
+ exports.createAwsSecretsClient = createAwsSecretsClient;
508
+ exports.createVaultClient = createVaultClient;
259
509
  exports.deepMerge = deepMerge;
260
510
  exports.loadConfig = loadConfig;
511
+ exports.loadConfigAsync = loadConfigAsync;
261
512
  exports.maskSecrets = maskSecrets;
262
513
  exports.replacePlaceholders = replacePlaceholders;
263
514
  exports.stripEmpty = stripEmpty;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/deep-merge.ts","../src/utils/env-replacer.ts","../src/utils/mask-secrets.ts","../src/utils/strip-empty.ts","../src/loader.ts"],"names":["isPlainObject","config","dotenv","join","path","existsSync","readFileSync","yaml","readdirSync"],"mappings":"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","file":"index.js","sourcesContent":["type DeepObject = Record<string, unknown>;\n\nexport function deepMerge(base: DeepObject, override: DeepObject): DeepObject {\n const merged = { ...base };\n\n for (const key in override) {\n const overrideValue = override[key];\n const baseValue = base[key];\n\n if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {\n merged[key] = deepMerge(baseValue as DeepObject, overrideValue as DeepObject);\n } else {\n merged[key] = overrideValue;\n }\n }\n\n return merged;\n}\n\nfunction isPlainObject(value: unknown): value is DeepObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","const ENV_PLACEHOLDER_REGEX = /\\${(.*?)}/g;\n\nexport function replacePlaceholders(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return replaceStringPlaceholders(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => replacePlaceholders(item));\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n result[key] = replacePlaceholders(obj[key]);\n }\n return result;\n }\n\n return obj;\n}\n\nfunction replaceStringPlaceholders(str: string): string {\n return str.replace(ENV_PLACEHOLDER_REGEX, (match, key) => {\n const [envKey, ...rest] = key.split(':');\n const defaultValue = rest.length > 0 ? rest.join(':') : undefined;\n const envValue = process.env[envKey];\n\n let value: string;\n if (envValue !== undefined) {\n value = envValue;\n } else if (defaultValue !== undefined) {\n value = defaultValue;\n } else {\n throw new Error(`Environment variable \"${envKey}\" is not defined and no default value provided`);\n }\n\n if (value.includes('${')) {\n return replacePlaceholders(value) as string;\n }\n\n return value;\n });\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","const SECRET_PATTERNS = ['token', 'password', 'secret', 'key', 'apikey', 'api_key', 'credential'];\n\nconst URL_WITH_CREDENTIALS_REGEX = /^([a-z]+:\\/\\/)([^:]+):([^@]+)@(.+)$/i;\n\nexport function maskSecrets(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return maskUrlCredentials(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => maskSecrets(item));\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n const value = obj[key];\n if (isSecretKey(key)) {\n result[key] = maskValue(value);\n } else {\n result[key] = maskSecrets(value);\n }\n }\n return result;\n }\n\n return obj;\n}\n\nfunction isSecretKey(key: string): boolean {\n const lowerKey = key.toLowerCase();\n return SECRET_PATTERNS.some((pattern) => lowerKey.includes(pattern));\n}\n\nfunction maskValue(value: unknown): string {\n if (typeof value !== 'string') {\n return '***';\n }\n if (value.length <= 4) {\n return '***';\n }\n return value.slice(0, 2) + '***' + value.slice(-2);\n}\n\nfunction maskUrlCredentials(url: string): string {\n const match = url.match(URL_WITH_CREDENTIALS_REGEX);\n if (match) {\n const [, protocol, user, , host] = match;\n return `${protocol}${user}:***@${host}`;\n }\n return url;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","export function stripEmpty(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return obj === '' ? undefined : obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => stripEmpty(item)).filter((item) => item !== undefined);\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n let hasValues = false;\n\n for (const key of Object.keys(obj)) {\n const value = stripEmpty(obj[key]);\n if (value !== undefined) {\n result[key] = value;\n hasValues = true;\n }\n }\n\n return hasValues ? result : undefined;\n }\n\n return obj;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import { readFileSync, existsSync, readdirSync } from 'fs';\nimport * as yaml from 'js-yaml';\nimport * as dotenv from 'dotenv';\nimport { join } from 'path';\nimport type { ConfigLoaderOptions, ConfigLoaderResult } from './types.js';\nimport { deepMerge, replacePlaceholders, maskSecrets, stripEmpty } from './utils/index.js';\n\nconst DEFAULT_OPTIONS = {\n configDir: process.cwd(),\n baseFileName: 'base',\n environment: process.env.NODE_ENV || 'local',\n extension: 'yml' as const,\n overrideDir: '/etc/app/config',\n};\n\nexport function loadConfig<T>(options: ConfigLoaderOptions<T> = {}): ConfigLoaderResult<T> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const { configDir, baseFileName, environment, extension } = opts;\n\n // Load .env if requested\n if (options.dotenv) {\n loadDotenv(options.dotenv, environment);\n }\n\n const resolvedConfigDir = resolveConfigDir(configDir);\n\n const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);\n const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);\n\n let mergedConfig = deepMerge(baseConfig, envConfig);\n\n // Load override files from directory\n const overrideDir = options.overrideDir !== false ? (options.overrideDir ?? DEFAULT_OPTIONS.overrideDir) : null;\n if (overrideDir) {\n const overrideConfigs = loadOverrideDir(overrideDir);\n for (const override of overrideConfigs) {\n mergedConfig = deepMerge(mergedConfig, override);\n }\n }\n\n let config = replacePlaceholders(mergedConfig) as T;\n\n // Strip empty strings and objects\n if (options.stripEmpty) {\n config = stripEmpty(config) as T;\n }\n\n // Post-process\n if (options.postProcess) {\n config = options.postProcess(config);\n }\n\n // Validate with Zod schema\n if (options.schema) {\n const result = options.schema.safeParse(config);\n if (!result.success) {\n const errors = result.error.issues\n .map((e) => ` - ${e.path.join('.')}: ${e.message}`)\n .join('\\n');\n throw new Error(`Config validation failed:\\n${errors}`);\n }\n config = result.data;\n }\n\n // Log config with masked secrets\n if (options.logger) {\n const maskedConfig = maskSecrets(config);\n options.logger(`Config loaded (${environment}):\\n${JSON.stringify(maskedConfig, null, 2)}`);\n }\n\n return {\n config,\n environment,\n configDir: resolvedConfigDir,\n };\n}\n\nfunction loadDotenv(\n dotenvOption: boolean | { path?: string },\n environment: string,\n): void {\n let envPath: string;\n\n if (typeof dotenvOption === 'object' && dotenvOption.path) {\n envPath = dotenvOption.path;\n } else {\n envPath = environment === 'production' ? '.env' : `.env.${environment}`;\n }\n\n dotenv.config({ path: envPath });\n}\n\nfunction resolveConfigDir(configDir: string): string {\n const possiblePaths = [\n join(configDir, 'src', 'config'),\n join(configDir, 'dist', 'config'),\n join(configDir, 'config'),\n configDir,\n ];\n\n for (const path of possiblePaths) {\n if (existsSync(join(path, 'base.yml')) || existsSync(join(path, 'base.yaml'))) {\n return path;\n }\n }\n\n throw new Error(\n `Config files not found. Searched in:\\n${possiblePaths.map((p) => ` - ${p}`).join('\\n')}`,\n );\n}\n\nfunction loadYamlFile(dir: string, filename: string): Record<string, unknown> {\n const filePath = join(dir, filename);\n\n if (!existsSync(filePath)) {\n return {};\n }\n\n const content = readFileSync(filePath, 'utf8');\n return (yaml.load(content) as Record<string, unknown>) || {};\n}\n\nfunction loadOverrideDir(dir: string): Record<string, unknown>[] {\n if (!existsSync(dir)) {\n return [];\n }\n\n const files = readdirSync(dir)\n .filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'))\n .sort();\n\n return files.map((file) => loadYamlFile(dir, file));\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/deep-merge.ts","../src/utils/env-replacer.ts","../src/utils/mask-secrets.ts","../src/utils/strip-empty.ts","../src/utils/vault-client.ts","../src/utils/aws-secrets-client.ts","../src/loader.ts"],"names":["isPlainObject","NodeVault","SecretsManagerClient","GetSecretValueCommand","config","dotenv","join","path","existsSync","readFileSync","yaml","readdirSync"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEO,SAAS,SAAA,CAAU,MAAkB,QAAA,EAAkC;AAC5E,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,IAAA,EAAK;AAEzB,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,IAAA,MAAM,aAAA,GAAgB,SAAS,GAAG,CAAA;AAClC,IAAA,MAAM,SAAA,GAAY,KAAK,GAAG,CAAA;AAE1B,IAAA,IAAI,aAAA,CAAc,aAAa,CAAA,IAAK,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5D,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,SAAA,CAAU,SAAA,EAAyB,aAA2B,CAAA;AAAA,IAC9E,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,aAAA;AAAA,IAChB;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,cAAc,KAAA,EAAqC;AAC1D,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;;;AClBA,IAAM,qBAAA,GAAwB,YAAA;AAC9B,IAAM,YAAA,GAAe,QAAA;AACrB,IAAM,UAAA,GAAa,MAAA;AAOZ,SAAS,mBAAA,CAAoB,GAAA,EAAc,OAAA,GAAsC,EAAC,EAAY;AACnG,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,yBAAA,CAA0B,KAAK,OAAO,CAAA;AAAA,EAC/C;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,EAAG;AACtB,IAAA,OAAO,IAAI,GAAA,CAAI,CAAC,SAAS,mBAAA,CAAoB,IAAA,EAAM,OAAO,CAAC,CAAA;AAAA,EAC7D;AAEA,EAAA,IAAIA,cAAAA,CAAc,GAAG,CAAA,EAAG;AACtB,IAAA,MAAM,SAAkC,EAAC;AACzC,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG;AAClC,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,mBAAA,CAAoB,GAAA,CAAI,GAAG,GAAG,OAAO,CAAA;AAAA,IACrD;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,yBAAA,CAA0B,KAAa,OAAA,EAA6C;AAC3F,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,qBAAA,EAAuB,CAAC,OAAO,GAAA,KAAQ;AAExD,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,EAAG;AAChC,MAAA,OAAO,uBAAA,CAAwB,GAAA,EAAK,KAAA,EAAO,OAAO,CAAA;AAAA,IACpD;AAGA,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,UAAU,CAAA,EAAG;AAC9B,MAAA,OAAO,qBAAA,CAAsB,GAAA,EAAK,KAAA,EAAO,OAAO,CAAA;AAAA,IAClD;AAGA,IAAA,OAAO,qBAAA,CAAsB,GAAA,EAAK,KAAA,EAAO,OAAO,CAAA;AAAA,EAClD,CAAC,CAAA;AACH;AAEA,SAAS,uBAAA,CACP,GAAA,EACA,aAAA,EACA,OAAA,EACQ;AAER,EAAA,MAAM,QAAQ,GAAA,CAAI,SAAA,CAAU,aAAa,MAAM,CAAA,CAAE,MAAM,GAAG,CAAA;AAE1D,EAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,qCAAqC,aAAa,CAAA,gEAAA;AAAA,KAEpD;AAAA,EACF;AAEA,EAAA,MAAM,CAAC,IAAA,EAAM,KAAA,EAAO,GAAG,YAAY,CAAA,GAAI,KAAA;AACvC,EAAA,MAAM,eAAe,YAAA,CAAa,MAAA,GAAS,IAAI,YAAA,CAAa,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA;AAExE,EAAA,IAAI,CAAC,QAAQ,WAAA,EAAa;AACxB,IAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,MAAA,OAAO,YAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,aAAa,CAAA,qCAAA,CAAuC,CAAA;AAAA,EAC5F;AAEA,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,WAAA,CAAY,SAAA,CAAU,MAAM,KAAK,CAAA;AAE7D,EAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,IAAA,OAAO,OAAO,WAAW,CAAA;AAAA,EAC3B;AAEA,EAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,IAAA,OAAO,YAAA;AAAA,EACT;AAEA,EAAA,MAAM,IAAI,KAAA,CAAM,CAAA,cAAA,EAAiB,KAAK,CAAA,qBAAA,EAAwB,IAAI,CAAA,+BAAA,CAAiC,CAAA;AACrG;AAEA,SAAS,qBAAA,CACP,GAAA,EACA,aAAA,EACA,OAAA,EACQ;AAER,EAAA,MAAM,QAAQ,GAAA,CAAI,SAAA,CAAU,WAAW,MAAM,CAAA,CAAE,MAAM,GAAG,CAAA;AAExD,EAAA,IAAI,MAAM,MAAA,GAAS,CAAA,IAAK,CAAC,KAAA,CAAM,CAAC,CAAA,EAAG;AACjC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,mCAAmC,aAAa,CAAA,iGAAA;AAAA,KAElD;AAAA,EACF;AAEA,EAAA,MAAM,CAAC,UAAA,EAAY,KAAA,EAAO,GAAG,YAAY,CAAA,GAAI,KAAA;AAC7C,EAAA,MAAM,eAAe,YAAA,CAAa,MAAA,GAAS,IAAI,YAAA,CAAa,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA;AAExE,EAAA,IAAI,CAAC,QAAQ,SAAA,EAAW;AACtB,IAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,MAAA,OAAO,YAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,aAAa,CAAA,mCAAA,CAAqC,CAAA;AAAA,EACxF;AAEA,EAAA,MAAM,cAAc,OAAA,CAAQ,SAAA,CAAU,SAAA,CAAU,UAAA,EAAY,SAAS,MAAS,CAAA;AAE9E,EAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,IAAA,OAAO,OAAO,WAAW,CAAA;AAAA,EAC3B;AAEA,EAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,IAAA,OAAO,YAAA;AAAA,EACT;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,KAAK,CAAA,gBAAA,EAAmB,UAAU,CAAA,+BAAA,CAAiC,CAAA;AAAA,EAC1G,CAAA,MAAO;AACL,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,UAAU,CAAA,yCAAA,CAA2C,CAAA;AAAA,EACtF;AACF;AAEA,SAAS,qBAAA,CAAsB,GAAA,EAAa,MAAA,EAAgB,OAAA,EAA6C;AACvG,EAAA,MAAM,CAAC,MAAA,EAAQ,GAAG,IAAI,CAAA,GAAI,GAAA,CAAI,MAAM,GAAG,CAAA;AACvC,EAAA,MAAM,eAAe,IAAA,CAAK,MAAA,GAAS,IAAI,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA;AACxD,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AAEnC,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,IAAA,KAAA,GAAQ,QAAA;AAAA,EACV,CAAA,MAAA,IAAW,iBAAiB,MAAA,EAAW;AACrC,IAAA,KAAA,GAAQ,YAAA;AAAA,EACV,CAAA,MAAO;AACL,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,MAAM,CAAA,8CAAA,CAAgD,CAAA;AAAA,EACjG;AAGA,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,EAAG;AACxB,IAAA,OAAO,mBAAA,CAAoB,OAAO,OAAO,CAAA;AAAA,EAC3C;AAEA,EAAA,OAAO,KAAA;AACT;AAEA,SAASA,eAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;;;ACzJA,IAAM,eAAA,GAAkB,CAAC,OAAA,EAAS,UAAA,EAAY,UAAU,KAAA,EAAO,QAAA,EAAU,WAAW,YAAY,CAAA;AAEhG,IAAM,0BAAA,GAA6B,sCAAA;AAE5B,SAAS,YAAY,GAAA,EAAuB;AACjD,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,mBAAmB,GAAG,CAAA;AAAA,EAC/B;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,EAAG;AACtB,IAAA,OAAO,IAAI,GAAA,CAAI,CAAC,IAAA,KAAS,WAAA,CAAY,IAAI,CAAC,CAAA;AAAA,EAC5C;AAEA,EAAA,IAAIA,cAAAA,CAAc,GAAG,CAAA,EAAG;AACtB,IAAA,MAAM,SAAkC,EAAC;AACzC,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG;AAClC,MAAA,MAAM,KAAA,GAAQ,IAAI,GAAG,CAAA;AACrB,MAAA,IAAI,WAAA,CAAY,GAAG,CAAA,EAAG;AACpB,QAAA,MAAA,CAAO,GAAG,CAAA,GAAI,SAAA,CAAU,KAAK,CAAA;AAAA,MAC/B,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,GAAG,CAAA,GAAI,WAAA,CAAY,KAAK,CAAA;AAAA,MACjC;AAAA,IACF;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,YAAY,GAAA,EAAsB;AACzC,EAAA,MAAM,QAAA,GAAW,IAAI,WAAA,EAAY;AACjC,EAAA,OAAO,gBAAgB,IAAA,CAAK,CAAC,YAAY,QAAA,CAAS,QAAA,CAAS,OAAO,CAAC,CAAA;AACrE;AAEA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,KAAA,CAAM,UAAU,CAAA,EAAG;AACrB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA,CAAM,MAAM,CAAA,EAAG,CAAC,IAAI,KAAA,GAAQ,KAAA,CAAM,MAAM,EAAE,CAAA;AACnD;AAEA,SAAS,mBAAmB,GAAA,EAAqB;AAC/C,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,0BAA0B,CAAA;AAClD,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,MAAM,GAAG,QAAA,EAAU,IAAA,IAAQ,IAAI,CAAA,GAAI,KAAA;AACnC,IAAA,OAAO,CAAA,EAAG,QAAQ,CAAA,EAAG,IAAI,QAAQ,IAAI,CAAA,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAASA,eAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;;;ACvDO,SAAS,WAAW,GAAA,EAAuB;AAChD,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,GAAA,KAAQ,KAAK,MAAA,GAAY,GAAA;AAAA,EAClC;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,EAAG;AACtB,IAAA,OAAO,GAAA,CAAI,GAAA,CAAI,CAAC,IAAA,KAAS,UAAA,CAAW,IAAI,CAAC,CAAA,CAAE,MAAA,CAAO,CAAC,IAAA,KAAS,IAAA,KAAS,MAAS,CAAA;AAAA,EAChF;AAEA,EAAA,IAAIA,cAAAA,CAAc,GAAG,CAAA,EAAG;AACtB,IAAA,MAAM,SAAkC,EAAC;AACzC,IAAA,IAAI,SAAA,GAAY,KAAA;AAEhB,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG;AAClC,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,GAAG,CAAC,CAAA;AACjC,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AACd,QAAA,SAAA,GAAY,IAAA;AAAA,MACd;AAAA,IACF;AAEA,IAAA,OAAO,YAAY,MAAA,GAAS,MAAA;AAAA,EAC9B;AAEA,EAAA,OAAO,GAAA;AACT;AAEA,SAASA,eAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AC1BA,IAAM,uBAAA,GAA0B,2CAAA;AAWhC,eAAsB,kBAAkB,OAAA,EAA6C;AACnF,EAAA,MAAM,QAAQC,0BAAA,CAAU;AAAA,IACtB,UAAU,OAAA,CAAQ,QAAA;AAAA,IAClB,UAAA,EAAY,IAAA;AAAA,IACZ,WAAW,OAAA,CAAQ;AAAA,GACpB,CAAA;AAGD,EAAA,MAAM,WAAA,GAAc,MAAM,KAAA,CAAM,YAAA,CAAa;AAAA,IAC3C,SAAS,OAAA,CAAQ,MAAA;AAAA,IACjB,WAAW,OAAA,CAAQ;AAAA,GACpB,CAAA;AAED,EAAA,KAAA,CAAM,KAAA,GAAQ,YAAY,IAAA,CAAK,YAAA;AAE/B,EAAA,MAAM,QAA0B,EAAC;AAEjC,EAAA,OAAO;AAAA,IACL,MAAM,aAAa,GAAA,EAAyC;AAC1D,MAAA,MAAM,KAAA,GAAQ,kBAAkB,GAAG,CAAA;AAGnC,MAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,QACZ,KAAA,CAAM,GAAA,CAAI,OAAO,IAAA,KAAS;AACxB,UAAA,IAAI,CAAC,KAAA,CAAM,IAAI,CAAA,EAAG;AAChB,YAAA,IAAI;AACF,cAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AAGpC,cAAA,KAAA,CAAM,IAAI,CAAA,GAAI,MAAA,CAAO,MAAM,IAAA,IAAQ,MAAA,CAAO,QAAQ,EAAC;AAAA,YACrD,CAAA,CAAA,MAAQ;AAEN,cAAA,KAAA,CAAM,IAAI,IAAI,EAAC;AAAA,YACjB;AAAA,UACF;AAAA,QACF,CAAC;AAAA,OACH;AAEA,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IAEA,SAAA,CAAU,MAAc,KAAA,EAAwB;AAC9C,MAAA,OAAO,KAAA,CAAM,IAAI,CAAA,GAAI,KAAK,CAAA;AAAA,IAC5B;AAAA,GACF;AACF;AAKA,SAAS,kBAAkB,GAAA,EAAwB;AACjD,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAY;AAE9B,EAAA,SAAS,SAAS,KAAA,EAAsB;AACtC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,IAAI,KAAA;AACJ,MAAA,OAAA,CAAQ,KAAA,GAAQ,uBAAA,CAAwB,IAAA,CAAK,KAAK,OAAO,IAAA,EAAM;AAC7D,QAAA,KAAA,CAAM,GAAA,CAAI,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,MACpB;AACA,MAAA,uBAAA,CAAwB,SAAA,GAAY,CAAA;AAAA,IACtC,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/B,MAAA,KAAA,CAAM,QAAQ,QAAQ,CAAA;AAAA,IACxB,CAAA,MAAA,IAAW,KAAA,KAAU,IAAA,IAAQ,OAAO,UAAU,QAAA,EAAU;AACtD,MAAA,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IACvC;AAAA,EACF;AAEA,EAAA,QAAA,CAAS,GAAG,CAAA;AACZ,EAAA,OAAO,KAAA,CAAM,KAAK,KAAK,CAAA;AACzB;AChFA,IAAM,qBAAA,GAAwB,8CAAA;AAW9B,eAAsB,uBAAuB,OAAA,EAAuD;AAClG,EAAA,MAAM,MAAA,GAAS,IAAIC,yCAAA,CAAqB;AAAA,IACtC,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,WAAA,EAAa;AAAA,MACX,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,iBAAiB,OAAA,CAAQ;AAAA;AAC3B,GACD,CAAA;AAED,EAAA,MAAM,QAAyB,EAAC;AAEhC,EAAA,OAAO;AAAA,IACL,MAAM,aAAa,GAAA,EAAwC;AACzD,MAAA,MAAM,WAAA,GAAc,sBAAsB,GAAG,CAAA;AAG7C,MAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,QACZ,WAAA,CAAY,GAAA,CAAI,OAAO,UAAA,KAAe;AACpC,UAAA,IAAI,KAAA,CAAM,UAAU,CAAA,KAAM,MAAA,EAAW;AACnC,YAAA,IAAI;AACF,cAAA,MAAM,UAAU,IAAIC,0CAAA,CAAsB,EAAE,QAAA,EAAU,YAAY,CAAA;AAClE,cAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAE1C,cAAA,MAAM,eAAe,QAAA,CAAS,YAAA;AAC9B,cAAA,IAAI,YAAA,EAAc;AAEhB,gBAAA,IAAI;AACF,kBAAA,KAAA,CAAM,UAAU,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,YAAY,CAAA;AAAA,gBAC7C,CAAA,CAAA,MAAQ;AACN,kBAAA,KAAA,CAAM,UAAU,CAAA,GAAI,YAAA;AAAA,gBACtB;AAAA,cACF,CAAA,MAAO;AACL,gBAAA,KAAA,CAAM,UAAU,IAAI,EAAC;AAAA,cACvB;AAAA,YACF,CAAA,CAAA,MAAQ;AAEN,cAAA,KAAA,CAAM,UAAU,IAAI,EAAC;AAAA,YACvB;AAAA,UACF;AAAA,QACF,CAAC;AAAA,OACH;AAEA,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IAEA,SAAA,CAAU,YAAoB,KAAA,EAAyB;AACrD,MAAA,MAAM,MAAA,GAAS,MAAM,UAAU,CAAA;AAE/B,MAAA,IAAI,WAAW,MAAA,EAAW;AACxB,QAAA,OAAO,MAAA;AAAA,MACT;AAGA,MAAA,IAAI,CAAC,KAAA,EAAO;AACV,QAAA,OAAO,OAAO,MAAA,KAAW,QAAA,GAAW,MAAA,GAAS,MAAA;AAAA,MAC/C;AAGA,MAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,KAAW,IAAA,EAAM;AACjD,QAAA,OAAQ,OAAmC,KAAK,CAAA;AAAA,MAClD;AAEA,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,GACF;AACF;AAKA,SAAS,sBAAsB,GAAA,EAAwB;AACrD,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAY;AAE9B,EAAA,SAAS,SAAS,KAAA,EAAsB;AACtC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,IAAI,KAAA;AACJ,MAAA,OAAA,CAAQ,KAAA,GAAQ,qBAAA,CAAsB,IAAA,CAAK,KAAK,OAAO,IAAA,EAAM;AAC3D,QAAA,KAAA,CAAM,GAAA,CAAI,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,MACpB;AACA,MAAA,qBAAA,CAAsB,SAAA,GAAY,CAAA;AAAA,IACpC,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/B,MAAA,KAAA,CAAM,QAAQ,QAAQ,CAAA;AAAA,IACxB,CAAA,MAAA,IAAW,KAAA,KAAU,IAAA,IAAQ,OAAO,UAAU,QAAA,EAAU;AACtD,MAAA,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IACvC;AAAA,EACF;AAEA,EAAA,QAAA,CAAS,GAAG,CAAA;AACZ,EAAA,OAAO,KAAA,CAAM,KAAK,KAAK,CAAA;AACzB;;;ACvFA,IAAM,eAAA,GAAkB;AAAA,EACtB,SAAA,EAAW,QAAQ,GAAA,EAAI;AAAA,EACvB,YAAA,EAAc,MAAA;AAAA,EACd,WAAA,EAAa,OAAA,CAAQ,GAAA,CAAI,QAAA,IAAY,OAAA;AAAA,EACrC,SAAA,EAAW,KAAA;AAAA,EACX,WAAA,EAAa;AACf,CAAA;AAEO,SAAS,UAAA,CAAc,OAAA,GAAkC,EAAC,EAA0B;AACzF,EAAA,IAAI,OAAA,CAAQ,KAAA,IAAS,OAAA,CAAQ,GAAA,EAAK;AAChC,IAAA,MAAM,IAAI,MAAM,wFAAwF,CAAA;AAAA,EAC1G;AAEA,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,eAAA,EAAiB,GAAG,OAAA,EAAQ;AAC9C,EAAA,MAAM,EAAE,SAAA,EAAW,YAAA,EAAc,WAAA,EAAa,WAAU,GAAI,IAAA;AAG5D,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,UAAA,CAAW,OAAA,CAAQ,QAAQ,WAAW,CAAA;AAAA,EACxC;AAEA,EAAA,MAAM,iBAAA,GAAoB,iBAAiB,SAAS,CAAA;AAEpD,EAAA,MAAM,aAAa,YAAA,CAAa,iBAAA,EAAmB,GAAG,YAAY,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AACjF,EAAA,MAAM,YAAY,YAAA,CAAa,iBAAA,EAAmB,GAAG,WAAW,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AAE/E,EAAA,IAAI,YAAA,GAAe,SAAA,CAAU,UAAA,EAAY,SAAS,CAAA;AAGlD,EAAA,MAAM,cAAc,OAAA,CAAQ,WAAA,KAAgB,QAAS,OAAA,CAAQ,WAAA,IAAe,gBAAgB,WAAA,GAAe,IAAA;AAC3G,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,MAAM,eAAA,GAAkB,gBAAgB,WAAW,CAAA;AACnD,IAAA,KAAA,MAAW,YAAY,eAAA,EAAiB;AACtC,MAAA,YAAA,GAAe,SAAA,CAAU,cAAc,QAAQ,CAAA;AAAA,IACjD;AAAA,EACF;AAEA,EAAA,IAAIC,OAAAA,GAAS,oBAAoB,YAAY,CAAA;AAG7C,EAAA,IAAI,QAAQ,UAAA,EAAY;AACtB,IAAAA,OAAAA,GAAS,WAAWA,OAAM,CAAA;AAAA,EAC5B;AAGA,EAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,IAAAA,OAAAA,GAAS,OAAA,CAAQ,WAAA,CAAYA,OAAM,CAAA;AAAA,EACrC;AAGA,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,CAAO,SAAA,CAAUA,OAAM,CAAA;AAC9C,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,MAAM,SAAS,MAAA,CAAO,KAAA,CAAM,OACzB,GAAA,CAAI,CAAC,MAAM,CAAA,IAAA,EAAO,CAAA,CAAE,KAAK,IAAA,CAAK,GAAG,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,OAAO,CAAA,CAAE,CAAA,CAClD,KAAK,IAAI,CAAA;AACZ,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA;AAAA,EAA8B,MAAM,CAAA,CAAE,CAAA;AAAA,IACxD;AACA,IAAAA,UAAS,MAAA,CAAO,IAAA;AAAA,EAClB;AAGA,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,YAAA,GAAe,YAAYA,OAAM,CAAA;AACvC,IAAA,OAAA,CAAQ,MAAA,CAAO,kBAAkB,WAAW,CAAA;AAAA,EAAO,KAAK,SAAA,CAAU,YAAA,EAAc,IAAA,EAAM,CAAC,CAAC,CAAA,CAAE,CAAA;AAAA,EAC5F;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAAA,OAAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA,EAAW;AAAA,GACb;AACF;AAEA,eAAsB,eAAA,CACpB,OAAA,GAAkC,EAAC,EACH;AAChC,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,eAAA,EAAiB,GAAG,OAAA,EAAQ;AAC9C,EAAA,MAAM,EAAE,SAAA,EAAW,YAAA,EAAc,WAAA,EAAa,WAAU,GAAI,IAAA;AAG5D,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,UAAA,CAAW,OAAA,CAAQ,QAAQ,WAAW,CAAA;AAAA,EACxC;AAEA,EAAA,MAAM,iBAAA,GAAoB,iBAAiB,SAAS,CAAA;AAEpD,EAAA,MAAM,aAAa,YAAA,CAAa,iBAAA,EAAmB,GAAG,YAAY,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AACjF,EAAA,MAAM,YAAY,YAAA,CAAa,iBAAA,EAAmB,GAAG,WAAW,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AAE/E,EAAA,IAAI,YAAA,GAAe,SAAA,CAAU,UAAA,EAAY,SAAS,CAAA;AAGlD,EAAA,MAAM,cAAc,OAAA,CAAQ,WAAA,KAAgB,QAAS,OAAA,CAAQ,WAAA,IAAe,gBAAgB,WAAA,GAAe,IAAA;AAC3G,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,MAAM,eAAA,GAAkB,gBAAgB,WAAW,CAAA;AACnD,IAAA,KAAA,MAAW,YAAY,eAAA,EAAiB;AACtC,MAAA,YAAA,GAAe,SAAA,CAAU,cAAc,QAAQ,CAAA;AAAA,IACjD;AAAA,EACF;AAGA,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,IAAA,WAAA,GAAc,MAAM,iBAAA,CAAkB,OAAA,CAAQ,KAAK,CAAA;AACnD,IAAA,MAAM,WAAA,CAAY,aAAa,YAAY,CAAA;AAAA,EAC7C;AAGA,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,QAAQ,GAAA,EAAK;AACf,IAAA,SAAA,GAAY,MAAM,sBAAA,CAAuB,OAAA,CAAQ,GAAG,CAAA;AACpD,IAAA,MAAM,SAAA,CAAU,aAAa,YAAY,CAAA;AAAA,EAC3C;AAGA,EAAA,IAAIA,UAAS,mBAAA,CAAoB,YAAA,EAAc,EAAE,WAAA,EAAa,WAAW,CAAA;AAGzE,EAAA,IAAI,QAAQ,UAAA,EAAY;AACtB,IAAAA,OAAAA,GAAS,WAAWA,OAAM,CAAA;AAAA,EAC5B;AAGA,EAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,IAAAA,OAAAA,GAAS,OAAA,CAAQ,WAAA,CAAYA,OAAM,CAAA;AAAA,EACrC;AAGA,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,CAAO,SAAA,CAAUA,OAAM,CAAA;AAC9C,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,MAAM,SAAS,MAAA,CAAO,KAAA,CAAM,OACzB,GAAA,CAAI,CAAC,MAAM,CAAA,IAAA,EAAO,CAAA,CAAE,KAAK,IAAA,CAAK,GAAG,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,OAAO,CAAA,CAAE,CAAA,CAClD,KAAK,IAAI,CAAA;AACZ,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA;AAAA,EAA8B,MAAM,CAAA,CAAE,CAAA;AAAA,IACxD;AACA,IAAAA,UAAS,MAAA,CAAO,IAAA;AAAA,EAClB;AAGA,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,YAAA,GAAe,YAAYA,OAAM,CAAA;AACvC,IAAA,OAAA,CAAQ,MAAA,CAAO,kBAAkB,WAAW,CAAA;AAAA,EAAO,KAAK,SAAA,CAAU,YAAA,EAAc,IAAA,EAAM,CAAC,CAAC,CAAA,CAAE,CAAA;AAAA,EAC5F;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAAA,OAAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA,EAAW;AAAA,GACb;AACF;AAEA,SAAS,UAAA,CACP,cACA,WAAA,EACM;AACN,EAAA,IAAI,OAAA;AAEJ,EAAA,IAAI,OAAO,YAAA,KAAiB,QAAA,IAAY,YAAA,CAAa,IAAA,EAAM;AACzD,IAAA,OAAA,GAAU,YAAA,CAAa,IAAA;AAAA,EACzB,CAAA,MAAO;AACL,IAAA,OAAA,GAAU,WAAA,KAAgB,YAAA,GAAe,MAAA,GAAS,CAAA,KAAA,EAAQ,WAAW,CAAA,CAAA;AAAA,EACvE;AAEA,EAAOC,iBAAA,CAAA,MAAA,CAAO,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AACjC;AAEA,SAAS,iBAAiB,SAAA,EAA2B;AACnD,EAAA,MAAM,aAAA,GAAgB;AAAA,IACpBC,SAAA,CAAK,SAAA,EAAW,KAAA,EAAO,QAAQ,CAAA;AAAA,IAC/BA,SAAA,CAAK,SAAA,EAAW,MAAA,EAAQ,QAAQ,CAAA;AAAA,IAChCA,SAAA,CAAK,WAAW,QAAQ,CAAA;AAAA,IACxB;AAAA,GACF;AAEA,EAAA,KAAA,MAAWC,UAAQ,aAAA,EAAe;AAChC,IAAA,IAAIC,aAAA,CAAWF,SAAA,CAAKC,MAAA,EAAM,UAAU,CAAC,CAAA,IAAKC,aAAA,CAAWF,SAAA,CAAKC,MAAA,EAAM,WAAW,CAAC,CAAA,EAAG;AAC7E,MAAA,OAAOA,MAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,CAAA;AAAA,EAAyC,aAAA,CAAc,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,IAAA,EAAO,CAAC,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,GAC1F;AACF;AAEA,SAAS,YAAA,CAAa,KAAa,QAAA,EAA2C;AAC5E,EAAA,MAAM,QAAA,GAAWD,SAAA,CAAK,GAAA,EAAK,QAAQ,CAAA;AAEnC,EAAA,IAAI,CAACE,aAAA,CAAW,QAAQ,CAAA,EAAG;AACzB,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,OAAA,GAAUC,eAAA,CAAa,QAAA,EAAU,MAAM,CAAA;AAC7C,EAAA,OAAaC,eAAA,CAAA,IAAA,CAAK,OAAO,CAAA,IAAiC,EAAC;AAC7D;AAEA,SAAS,gBAAgB,GAAA,EAAwC;AAC/D,EAAA,IAAI,CAACF,aAAA,CAAW,GAAG,CAAA,EAAG;AACpB,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,QAAQG,cAAA,CAAY,GAAG,CAAA,CAC1B,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,CAAS,MAAM,KAAK,CAAA,CAAE,QAAA,CAAS,OAAO,CAAC,EACvD,IAAA,EAAK;AAER,EAAA,OAAO,MAAM,GAAA,CAAI,CAAC,SAAS,YAAA,CAAa,GAAA,EAAK,IAAI,CAAC,CAAA;AACpD","file":"index.js","sourcesContent":["type DeepObject = Record<string, unknown>;\n\nexport function deepMerge(base: DeepObject, override: DeepObject): DeepObject {\n const merged = { ...base };\n\n for (const key in override) {\n const overrideValue = override[key];\n const baseValue = base[key];\n\n if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {\n merged[key] = deepMerge(baseValue as DeepObject, overrideValue as DeepObject);\n } else {\n merged[key] = overrideValue;\n }\n }\n\n return merged;\n}\n\nfunction isPlainObject(value: unknown): value is DeepObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import type { VaultClient } from './vault-client.js';\nimport type { AwsSecretsClient } from './aws-secrets-client.js';\n\nconst ENV_PLACEHOLDER_REGEX = /\\${(.*?)}/g;\nconst VAULT_PREFIX = 'vault:';\nconst AWS_PREFIX = 'aws:';\n\nexport interface ReplacePlaceholdersOptions {\n vaultClient?: VaultClient;\n awsClient?: AwsSecretsClient;\n}\n\nexport function replacePlaceholders(obj: unknown, options: ReplacePlaceholdersOptions = {}): unknown {\n if (typeof obj === 'string') {\n return replaceStringPlaceholders(obj, options);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => replacePlaceholders(item, options));\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n result[key] = replacePlaceholders(obj[key], options);\n }\n return result;\n }\n\n return obj;\n}\n\nfunction replaceStringPlaceholders(str: string, options: ReplacePlaceholdersOptions): string {\n return str.replace(ENV_PLACEHOLDER_REGEX, (match, key) => {\n // Handle Vault placeholders: ${vault:path:field} or ${vault:path:field:default}\n if (key.startsWith(VAULT_PREFIX)) {\n return resolveVaultPlaceholder(key, match, options);\n }\n\n // Handle AWS placeholders: ${aws:secret:field} or ${aws:secret:field:default} or ${aws:secret}\n if (key.startsWith(AWS_PREFIX)) {\n return resolveAwsPlaceholder(key, match, options);\n }\n\n // Handle environment variable placeholders: ${VAR} or ${VAR:default}\n return resolveEnvPlaceholder(key, match, options);\n });\n}\n\nfunction resolveVaultPlaceholder(\n key: string,\n originalMatch: string,\n options: ReplacePlaceholdersOptions,\n): string {\n // Parse: vault:secret/data/api:DB_PASSWORD:default_value\n const parts = key.substring(VAULT_PREFIX.length).split(':');\n\n if (parts.length < 2) {\n throw new Error(\n `Invalid Vault placeholder format \"${originalMatch}\". ` +\n `Expected \\${vault:path:field} or \\${vault:path:field:default}`,\n );\n }\n\n const [path, field, ...defaultParts] = parts;\n const defaultValue = defaultParts.length > 0 ? defaultParts.join(':') : undefined;\n\n if (!options.vaultClient) {\n if (defaultValue !== undefined) {\n return defaultValue;\n }\n throw new Error(`Vault placeholder \"${originalMatch}\" found but no Vault options provided`);\n }\n\n const secretValue = options.vaultClient.getSecret(path, field);\n\n if (secretValue !== undefined) {\n return String(secretValue);\n }\n\n if (defaultValue !== undefined) {\n return defaultValue;\n }\n\n throw new Error(`Vault secret \"${field}\" not found at path \"${path}\" and no default value provided`);\n}\n\nfunction resolveAwsPlaceholder(\n key: string,\n originalMatch: string,\n options: ReplacePlaceholdersOptions,\n): string {\n // Parse: aws:secret-name:field:default or aws:secret-name:field or aws:secret-name\n const parts = key.substring(AWS_PREFIX.length).split(':');\n\n if (parts.length < 1 || !parts[0]) {\n throw new Error(\n `Invalid AWS placeholder format \"${originalMatch}\". ` +\n `Expected \\${aws:secret-name} or \\${aws:secret-name:field} or \\${aws:secret-name:field:default}`,\n );\n }\n\n const [secretName, field, ...defaultParts] = parts;\n const defaultValue = defaultParts.length > 0 ? defaultParts.join(':') : undefined;\n\n if (!options.awsClient) {\n if (defaultValue !== undefined) {\n return defaultValue;\n }\n throw new Error(`AWS placeholder \"${originalMatch}\" found but no AWS options provided`);\n }\n\n const secretValue = options.awsClient.getSecret(secretName, field || undefined);\n\n if (secretValue !== undefined) {\n return String(secretValue);\n }\n\n if (defaultValue !== undefined) {\n return defaultValue;\n }\n\n if (field) {\n throw new Error(`AWS secret field \"${field}\" not found in \"${secretName}\" and no default value provided`);\n } else {\n throw new Error(`AWS secret \"${secretName}\" not found and no default value provided`);\n }\n}\n\nfunction resolveEnvPlaceholder(key: string, _match: string, options: ReplacePlaceholdersOptions): string {\n const [envKey, ...rest] = key.split(':');\n const defaultValue = rest.length > 0 ? rest.join(':') : undefined;\n const envValue = process.env[envKey];\n\n let value: string;\n if (envValue !== undefined) {\n value = envValue;\n } else if (defaultValue !== undefined) {\n value = defaultValue;\n } else {\n throw new Error(`Environment variable \"${envKey}\" is not defined and no default value provided`);\n }\n\n // Handle nested placeholders\n if (value.includes('${')) {\n return replacePlaceholders(value, options) as string;\n }\n\n return value;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","const SECRET_PATTERNS = ['token', 'password', 'secret', 'key', 'apikey', 'api_key', 'credential'];\n\nconst URL_WITH_CREDENTIALS_REGEX = /^([a-z]+:\\/\\/)([^:]+):([^@]+)@(.+)$/i;\n\nexport function maskSecrets(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return maskUrlCredentials(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => maskSecrets(item));\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n const value = obj[key];\n if (isSecretKey(key)) {\n result[key] = maskValue(value);\n } else {\n result[key] = maskSecrets(value);\n }\n }\n return result;\n }\n\n return obj;\n}\n\nfunction isSecretKey(key: string): boolean {\n const lowerKey = key.toLowerCase();\n return SECRET_PATTERNS.some((pattern) => lowerKey.includes(pattern));\n}\n\nfunction maskValue(value: unknown): string {\n if (typeof value !== 'string') {\n return '***';\n }\n if (value.length <= 4) {\n return '***';\n }\n return value.slice(0, 2) + '***' + value.slice(-2);\n}\n\nfunction maskUrlCredentials(url: string): string {\n const match = url.match(URL_WITH_CREDENTIALS_REGEX);\n if (match) {\n const [, protocol, user, , host] = match;\n return `${protocol}${user}:***@${host}`;\n }\n return url;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","export function stripEmpty(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return obj === '' ? undefined : obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => stripEmpty(item)).filter((item) => item !== undefined);\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n let hasValues = false;\n\n for (const key of Object.keys(obj)) {\n const value = stripEmpty(obj[key]);\n if (value !== undefined) {\n result[key] = value;\n hasValues = true;\n }\n }\n\n return hasValues ? result : undefined;\n }\n\n return obj;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import NodeVault from 'node-vault';\nimport type { VaultOptions } from '../types.js';\n\nconst VAULT_PLACEHOLDER_REGEX = /\\${vault:([^:}]+):([^:}]+)(?::([^}]*))?}/g;\n\nexport interface VaultSecretCache {\n [path: string]: Record<string, unknown>;\n}\n\nexport interface VaultClient {\n fetchSecrets(obj: unknown): Promise<VaultSecretCache>;\n getSecret(path: string, field: string): unknown;\n}\n\nexport async function createVaultClient(options: VaultOptions): Promise<VaultClient> {\n const vault = NodeVault({\n endpoint: options.endpoint,\n apiVersion: 'v1',\n namespace: options.namespace,\n });\n\n // Authenticate with AppRole\n const loginResult = await vault.approleLogin({\n role_id: options.roleId,\n secret_id: options.secretId,\n });\n\n vault.token = loginResult.auth.client_token;\n\n const cache: VaultSecretCache = {};\n\n return {\n async fetchSecrets(obj: unknown): Promise<VaultSecretCache> {\n const paths = extractVaultPaths(obj);\n\n // Fetch all unique paths in parallel\n await Promise.all(\n paths.map(async (path) => {\n if (!cache[path]) {\n try {\n const result = await vault.read(path);\n // Vault KV v2 stores data under result.data.data\n // KV v1 stores under result.data\n cache[path] = result.data?.data ?? result.data ?? {};\n } catch {\n // Store empty object for failed paths - will use defaults or throw later\n cache[path] = {};\n }\n }\n }),\n );\n\n return cache;\n },\n\n getSecret(path: string, field: string): unknown {\n return cache[path]?.[field];\n },\n };\n}\n\n/**\n * Extract all unique Vault paths from config object\n */\nfunction extractVaultPaths(obj: unknown): string[] {\n const paths = new Set<string>();\n\n function traverse(value: unknown): void {\n if (typeof value === 'string') {\n let match;\n while ((match = VAULT_PLACEHOLDER_REGEX.exec(value)) !== null) {\n paths.add(match[1]); // path is first capture group\n }\n VAULT_PLACEHOLDER_REGEX.lastIndex = 0; // Reset regex state\n } else if (Array.isArray(value)) {\n value.forEach(traverse);\n } else if (value !== null && typeof value === 'object') {\n Object.values(value).forEach(traverse);\n }\n }\n\n traverse(obj);\n return Array.from(paths);\n}\n","import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';\nimport type { AwsSecretsOptions } from '../types.js';\n\nconst AWS_PLACEHOLDER_REGEX = /\\${aws:([^:}]+)(?::([^:}]+))?(?::([^}]*))?}/g;\n\nexport interface AwsSecretsCache {\n [secretName: string]: string | Record<string, unknown>;\n}\n\nexport interface AwsSecretsClient {\n fetchSecrets(obj: unknown): Promise<AwsSecretsCache>;\n getSecret(secretName: string, field?: string): unknown;\n}\n\nexport async function createAwsSecretsClient(options: AwsSecretsOptions): Promise<AwsSecretsClient> {\n const client = new SecretsManagerClient({\n region: options.region,\n credentials: {\n accessKeyId: options.accessKeyId,\n secretAccessKey: options.secretAccessKey,\n },\n });\n\n const cache: AwsSecretsCache = {};\n\n return {\n async fetchSecrets(obj: unknown): Promise<AwsSecretsCache> {\n const secretNames = extractAwsSecretNames(obj);\n\n // Fetch all unique secrets in parallel\n await Promise.all(\n secretNames.map(async (secretName) => {\n if (cache[secretName] === undefined) {\n try {\n const command = new GetSecretValueCommand({ SecretId: secretName });\n const response = await client.send(command);\n\n const secretString = response.SecretString;\n if (secretString) {\n // Try to parse as JSON, fallback to plain string\n try {\n cache[secretName] = JSON.parse(secretString);\n } catch {\n cache[secretName] = secretString;\n }\n } else {\n cache[secretName] = {};\n }\n } catch {\n // Store empty object for failed secrets - will use defaults or throw later\n cache[secretName] = {};\n }\n }\n }),\n );\n\n return cache;\n },\n\n getSecret(secretName: string, field?: string): unknown {\n const secret = cache[secretName];\n\n if (secret === undefined) {\n return undefined;\n }\n\n // If no field specified, return the whole secret (plain string or parsed JSON)\n if (!field) {\n return typeof secret === 'string' ? secret : undefined;\n }\n\n // If field specified, secret must be an object\n if (typeof secret === 'object' && secret !== null) {\n return (secret as Record<string, unknown>)[field];\n }\n\n return undefined;\n },\n };\n}\n\n/**\n * Extract all unique AWS secret names from config object\n */\nfunction extractAwsSecretNames(obj: unknown): string[] {\n const names = new Set<string>();\n\n function traverse(value: unknown): void {\n if (typeof value === 'string') {\n let match;\n while ((match = AWS_PLACEHOLDER_REGEX.exec(value)) !== null) {\n names.add(match[1]); // secret name is first capture group\n }\n AWS_PLACEHOLDER_REGEX.lastIndex = 0; // Reset regex state\n } else if (Array.isArray(value)) {\n value.forEach(traverse);\n } else if (value !== null && typeof value === 'object') {\n Object.values(value).forEach(traverse);\n }\n }\n\n traverse(obj);\n return Array.from(names);\n}\n","import { readFileSync, existsSync, readdirSync } from 'fs';\nimport * as yaml from 'js-yaml';\nimport * as dotenv from 'dotenv';\nimport { join } from 'path';\nimport type { ConfigLoaderOptions, ConfigLoaderResult } from './types.js';\nimport {\n deepMerge,\n replacePlaceholders,\n maskSecrets,\n stripEmpty,\n createVaultClient,\n createAwsSecretsClient,\n type VaultClient,\n type AwsSecretsClient,\n} from './utils/index.js';\n\nconst DEFAULT_OPTIONS = {\n configDir: process.cwd(),\n baseFileName: 'base',\n environment: process.env.NODE_ENV || 'local',\n extension: 'yml' as const,\n overrideDir: '/etc/app/config',\n};\n\nexport function loadConfig<T>(options: ConfigLoaderOptions<T> = {}): ConfigLoaderResult<T> {\n if (options.vault || options.aws) {\n throw new Error('Vault/AWS options require async loading. Use loadConfigAsync() instead of loadConfig()');\n }\n\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const { configDir, baseFileName, environment, extension } = opts;\n\n // Load .env if requested\n if (options.dotenv) {\n loadDotenv(options.dotenv, environment);\n }\n\n const resolvedConfigDir = resolveConfigDir(configDir);\n\n const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);\n const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);\n\n let mergedConfig = deepMerge(baseConfig, envConfig);\n\n // Load override files from directory\n const overrideDir = options.overrideDir !== false ? (options.overrideDir ?? DEFAULT_OPTIONS.overrideDir) : null;\n if (overrideDir) {\n const overrideConfigs = loadOverrideDir(overrideDir);\n for (const override of overrideConfigs) {\n mergedConfig = deepMerge(mergedConfig, override);\n }\n }\n\n let config = replacePlaceholders(mergedConfig) as T;\n\n // Strip empty strings and objects\n if (options.stripEmpty) {\n config = stripEmpty(config) as T;\n }\n\n // Post-process\n if (options.postProcess) {\n config = options.postProcess(config);\n }\n\n // Validate with Zod schema\n if (options.schema) {\n const result = options.schema.safeParse(config);\n if (!result.success) {\n const errors = result.error.issues\n .map((e) => ` - ${e.path.join('.')}: ${e.message}`)\n .join('\\n');\n throw new Error(`Config validation failed:\\n${errors}`);\n }\n config = result.data;\n }\n\n // Log config with masked secrets\n if (options.logger) {\n const maskedConfig = maskSecrets(config);\n options.logger(`Config loaded (${environment}):\\n${JSON.stringify(maskedConfig, null, 2)}`);\n }\n\n return {\n config,\n environment,\n configDir: resolvedConfigDir,\n };\n}\n\nexport async function loadConfigAsync<T>(\n options: ConfigLoaderOptions<T> = {},\n): Promise<ConfigLoaderResult<T>> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const { configDir, baseFileName, environment, extension } = opts;\n\n // Load .env if requested\n if (options.dotenv) {\n loadDotenv(options.dotenv, environment);\n }\n\n const resolvedConfigDir = resolveConfigDir(configDir);\n\n const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);\n const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);\n\n let mergedConfig = deepMerge(baseConfig, envConfig);\n\n // Load override files from directory\n const overrideDir = options.overrideDir !== false ? (options.overrideDir ?? DEFAULT_OPTIONS.overrideDir) : null;\n if (overrideDir) {\n const overrideConfigs = loadOverrideDir(overrideDir);\n for (const override of overrideConfigs) {\n mergedConfig = deepMerge(mergedConfig, override);\n }\n }\n\n // Create Vault client and pre-fetch secrets if Vault options provided\n let vaultClient: VaultClient | undefined;\n if (options.vault) {\n vaultClient = await createVaultClient(options.vault);\n await vaultClient.fetchSecrets(mergedConfig);\n }\n\n // Create AWS Secrets Manager client and pre-fetch secrets if AWS options provided\n let awsClient: AwsSecretsClient | undefined;\n if (options.aws) {\n awsClient = await createAwsSecretsClient(options.aws);\n await awsClient.fetchSecrets(mergedConfig);\n }\n\n // Replace placeholders (with Vault and AWS support)\n let config = replacePlaceholders(mergedConfig, { vaultClient, awsClient }) as T;\n\n // Strip empty strings and objects\n if (options.stripEmpty) {\n config = stripEmpty(config) as T;\n }\n\n // Post-process\n if (options.postProcess) {\n config = options.postProcess(config);\n }\n\n // Validate with Zod schema\n if (options.schema) {\n const result = options.schema.safeParse(config);\n if (!result.success) {\n const errors = result.error.issues\n .map((e) => ` - ${e.path.join('.')}: ${e.message}`)\n .join('\\n');\n throw new Error(`Config validation failed:\\n${errors}`);\n }\n config = result.data;\n }\n\n // Log config with masked secrets\n if (options.logger) {\n const maskedConfig = maskSecrets(config);\n options.logger(`Config loaded (${environment}):\\n${JSON.stringify(maskedConfig, null, 2)}`);\n }\n\n return {\n config,\n environment,\n configDir: resolvedConfigDir,\n };\n}\n\nfunction loadDotenv(\n dotenvOption: boolean | { path?: string },\n environment: string,\n): void {\n let envPath: string;\n\n if (typeof dotenvOption === 'object' && dotenvOption.path) {\n envPath = dotenvOption.path;\n } else {\n envPath = environment === 'production' ? '.env' : `.env.${environment}`;\n }\n\n dotenv.config({ path: envPath });\n}\n\nfunction resolveConfigDir(configDir: string): string {\n const possiblePaths = [\n join(configDir, 'src', 'config'),\n join(configDir, 'dist', 'config'),\n join(configDir, 'config'),\n configDir,\n ];\n\n for (const path of possiblePaths) {\n if (existsSync(join(path, 'base.yml')) || existsSync(join(path, 'base.yaml'))) {\n return path;\n }\n }\n\n throw new Error(\n `Config files not found. Searched in:\\n${possiblePaths.map((p) => ` - ${p}`).join('\\n')}`,\n );\n}\n\nfunction loadYamlFile(dir: string, filename: string): Record<string, unknown> {\n const filePath = join(dir, filename);\n\n if (!existsSync(filePath)) {\n return {};\n }\n\n const content = readFileSync(filePath, 'utf8');\n return (yaml.load(content) as Record<string, unknown>) || {};\n}\n\nfunction loadOverrideDir(dir: string): Record<string, unknown>[] {\n if (!existsSync(dir)) {\n return [];\n }\n\n const files = readdirSync(dir)\n .filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'))\n .sort();\n\n return files.map((file) => loadYamlFile(dir, file));\n}\n"]}
package/dist/index.mjs CHANGED
@@ -2,6 +2,8 @@ import { existsSync, readFileSync, readdirSync } from 'fs';
2
2
  import * as yaml from 'js-yaml';
3
3
  import * as dotenv from 'dotenv';
4
4
  import { join } from 'path';
5
+ import NodeVault from 'node-vault';
6
+ import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
5
7
 
6
8
  // src/loader.ts
7
9
 
@@ -25,41 +27,104 @@ function isPlainObject(value) {
25
27
 
26
28
  // src/utils/env-replacer.ts
27
29
  var ENV_PLACEHOLDER_REGEX = /\${(.*?)}/g;
28
- function replacePlaceholders(obj) {
30
+ var VAULT_PREFIX = "vault:";
31
+ var AWS_PREFIX = "aws:";
32
+ function replacePlaceholders(obj, options = {}) {
29
33
  if (typeof obj === "string") {
30
- return replaceStringPlaceholders(obj);
34
+ return replaceStringPlaceholders(obj, options);
31
35
  }
32
36
  if (Array.isArray(obj)) {
33
- return obj.map((item) => replacePlaceholders(item));
37
+ return obj.map((item) => replacePlaceholders(item, options));
34
38
  }
35
39
  if (isPlainObject2(obj)) {
36
40
  const result = {};
37
41
  for (const key of Object.keys(obj)) {
38
- result[key] = replacePlaceholders(obj[key]);
42
+ result[key] = replacePlaceholders(obj[key], options);
39
43
  }
40
44
  return result;
41
45
  }
42
46
  return obj;
43
47
  }
44
- function replaceStringPlaceholders(str) {
48
+ function replaceStringPlaceholders(str, options) {
45
49
  return str.replace(ENV_PLACEHOLDER_REGEX, (match, key) => {
46
- const [envKey, ...rest] = key.split(":");
47
- const defaultValue = rest.length > 0 ? rest.join(":") : void 0;
48
- const envValue = process.env[envKey];
49
- let value;
50
- if (envValue !== void 0) {
51
- value = envValue;
52
- } else if (defaultValue !== void 0) {
53
- value = defaultValue;
54
- } else {
55
- throw new Error(`Environment variable "${envKey}" is not defined and no default value provided`);
50
+ if (key.startsWith(VAULT_PREFIX)) {
51
+ return resolveVaultPlaceholder(key, match, options);
56
52
  }
57
- if (value.includes("${")) {
58
- return replacePlaceholders(value);
53
+ if (key.startsWith(AWS_PREFIX)) {
54
+ return resolveAwsPlaceholder(key, match, options);
59
55
  }
60
- return value;
56
+ return resolveEnvPlaceholder(key, match, options);
61
57
  });
62
58
  }
59
+ function resolveVaultPlaceholder(key, originalMatch, options) {
60
+ const parts = key.substring(VAULT_PREFIX.length).split(":");
61
+ if (parts.length < 2) {
62
+ throw new Error(
63
+ `Invalid Vault placeholder format "${originalMatch}". Expected \${vault:path:field} or \${vault:path:field:default}`
64
+ );
65
+ }
66
+ const [path, field, ...defaultParts] = parts;
67
+ const defaultValue = defaultParts.length > 0 ? defaultParts.join(":") : void 0;
68
+ if (!options.vaultClient) {
69
+ if (defaultValue !== void 0) {
70
+ return defaultValue;
71
+ }
72
+ throw new Error(`Vault placeholder "${originalMatch}" found but no Vault options provided`);
73
+ }
74
+ const secretValue = options.vaultClient.getSecret(path, field);
75
+ if (secretValue !== void 0) {
76
+ return String(secretValue);
77
+ }
78
+ if (defaultValue !== void 0) {
79
+ return defaultValue;
80
+ }
81
+ throw new Error(`Vault secret "${field}" not found at path "${path}" and no default value provided`);
82
+ }
83
+ function resolveAwsPlaceholder(key, originalMatch, options) {
84
+ const parts = key.substring(AWS_PREFIX.length).split(":");
85
+ if (parts.length < 1 || !parts[0]) {
86
+ throw new Error(
87
+ `Invalid AWS placeholder format "${originalMatch}". Expected \${aws:secret-name} or \${aws:secret-name:field} or \${aws:secret-name:field:default}`
88
+ );
89
+ }
90
+ const [secretName, field, ...defaultParts] = parts;
91
+ const defaultValue = defaultParts.length > 0 ? defaultParts.join(":") : void 0;
92
+ if (!options.awsClient) {
93
+ if (defaultValue !== void 0) {
94
+ return defaultValue;
95
+ }
96
+ throw new Error(`AWS placeholder "${originalMatch}" found but no AWS options provided`);
97
+ }
98
+ const secretValue = options.awsClient.getSecret(secretName, field || void 0);
99
+ if (secretValue !== void 0) {
100
+ return String(secretValue);
101
+ }
102
+ if (defaultValue !== void 0) {
103
+ return defaultValue;
104
+ }
105
+ if (field) {
106
+ throw new Error(`AWS secret field "${field}" not found in "${secretName}" and no default value provided`);
107
+ } else {
108
+ throw new Error(`AWS secret "${secretName}" not found and no default value provided`);
109
+ }
110
+ }
111
+ function resolveEnvPlaceholder(key, _match, options) {
112
+ const [envKey, ...rest] = key.split(":");
113
+ const defaultValue = rest.length > 0 ? rest.join(":") : void 0;
114
+ const envValue = process.env[envKey];
115
+ let value;
116
+ if (envValue !== void 0) {
117
+ value = envValue;
118
+ } else if (defaultValue !== void 0) {
119
+ value = defaultValue;
120
+ } else {
121
+ throw new Error(`Environment variable "${envKey}" is not defined and no default value provided`);
122
+ }
123
+ if (value.includes("${")) {
124
+ return replacePlaceholders(value, options);
125
+ }
126
+ return value;
127
+ }
63
128
  function isPlainObject2(value) {
64
129
  return typeof value === "object" && value !== null && !Array.isArray(value);
65
130
  }
@@ -138,6 +203,129 @@ function stripEmpty(obj) {
138
203
  function isPlainObject4(value) {
139
204
  return typeof value === "object" && value !== null && !Array.isArray(value);
140
205
  }
206
+ var VAULT_PLACEHOLDER_REGEX = /\${vault:([^:}]+):([^:}]+)(?::([^}]*))?}/g;
207
+ async function createVaultClient(options) {
208
+ const vault = NodeVault({
209
+ endpoint: options.endpoint,
210
+ apiVersion: "v1",
211
+ namespace: options.namespace
212
+ });
213
+ const loginResult = await vault.approleLogin({
214
+ role_id: options.roleId,
215
+ secret_id: options.secretId
216
+ });
217
+ vault.token = loginResult.auth.client_token;
218
+ const cache = {};
219
+ return {
220
+ async fetchSecrets(obj) {
221
+ const paths = extractVaultPaths(obj);
222
+ await Promise.all(
223
+ paths.map(async (path) => {
224
+ if (!cache[path]) {
225
+ try {
226
+ const result = await vault.read(path);
227
+ cache[path] = result.data?.data ?? result.data ?? {};
228
+ } catch {
229
+ cache[path] = {};
230
+ }
231
+ }
232
+ })
233
+ );
234
+ return cache;
235
+ },
236
+ getSecret(path, field) {
237
+ return cache[path]?.[field];
238
+ }
239
+ };
240
+ }
241
+ function extractVaultPaths(obj) {
242
+ const paths = /* @__PURE__ */ new Set();
243
+ function traverse(value) {
244
+ if (typeof value === "string") {
245
+ let match;
246
+ while ((match = VAULT_PLACEHOLDER_REGEX.exec(value)) !== null) {
247
+ paths.add(match[1]);
248
+ }
249
+ VAULT_PLACEHOLDER_REGEX.lastIndex = 0;
250
+ } else if (Array.isArray(value)) {
251
+ value.forEach(traverse);
252
+ } else if (value !== null && typeof value === "object") {
253
+ Object.values(value).forEach(traverse);
254
+ }
255
+ }
256
+ traverse(obj);
257
+ return Array.from(paths);
258
+ }
259
+ var AWS_PLACEHOLDER_REGEX = /\${aws:([^:}]+)(?::([^:}]+))?(?::([^}]*))?}/g;
260
+ async function createAwsSecretsClient(options) {
261
+ const client = new SecretsManagerClient({
262
+ region: options.region,
263
+ credentials: {
264
+ accessKeyId: options.accessKeyId,
265
+ secretAccessKey: options.secretAccessKey
266
+ }
267
+ });
268
+ const cache = {};
269
+ return {
270
+ async fetchSecrets(obj) {
271
+ const secretNames = extractAwsSecretNames(obj);
272
+ await Promise.all(
273
+ secretNames.map(async (secretName) => {
274
+ if (cache[secretName] === void 0) {
275
+ try {
276
+ const command = new GetSecretValueCommand({ SecretId: secretName });
277
+ const response = await client.send(command);
278
+ const secretString = response.SecretString;
279
+ if (secretString) {
280
+ try {
281
+ cache[secretName] = JSON.parse(secretString);
282
+ } catch {
283
+ cache[secretName] = secretString;
284
+ }
285
+ } else {
286
+ cache[secretName] = {};
287
+ }
288
+ } catch {
289
+ cache[secretName] = {};
290
+ }
291
+ }
292
+ })
293
+ );
294
+ return cache;
295
+ },
296
+ getSecret(secretName, field) {
297
+ const secret = cache[secretName];
298
+ if (secret === void 0) {
299
+ return void 0;
300
+ }
301
+ if (!field) {
302
+ return typeof secret === "string" ? secret : void 0;
303
+ }
304
+ if (typeof secret === "object" && secret !== null) {
305
+ return secret[field];
306
+ }
307
+ return void 0;
308
+ }
309
+ };
310
+ }
311
+ function extractAwsSecretNames(obj) {
312
+ const names = /* @__PURE__ */ new Set();
313
+ function traverse(value) {
314
+ if (typeof value === "string") {
315
+ let match;
316
+ while ((match = AWS_PLACEHOLDER_REGEX.exec(value)) !== null) {
317
+ names.add(match[1]);
318
+ }
319
+ AWS_PLACEHOLDER_REGEX.lastIndex = 0;
320
+ } else if (Array.isArray(value)) {
321
+ value.forEach(traverse);
322
+ } else if (value !== null && typeof value === "object") {
323
+ Object.values(value).forEach(traverse);
324
+ }
325
+ }
326
+ traverse(obj);
327
+ return Array.from(names);
328
+ }
141
329
 
142
330
  // src/loader.ts
143
331
  var DEFAULT_OPTIONS = {
@@ -148,6 +336,9 @@ var DEFAULT_OPTIONS = {
148
336
  overrideDir: "/etc/app/config"
149
337
  };
150
338
  function loadConfig(options = {}) {
339
+ if (options.vault || options.aws) {
340
+ throw new Error("Vault/AWS options require async loading. Use loadConfigAsync() instead of loadConfig()");
341
+ }
151
342
  const opts = { ...DEFAULT_OPTIONS, ...options };
152
343
  const { configDir, baseFileName, environment, extension } = opts;
153
344
  if (options.dotenv) {
@@ -191,6 +382,60 @@ ${JSON.stringify(maskedConfig, null, 2)}`);
191
382
  configDir: resolvedConfigDir
192
383
  };
193
384
  }
385
+ async function loadConfigAsync(options = {}) {
386
+ const opts = { ...DEFAULT_OPTIONS, ...options };
387
+ const { configDir, baseFileName, environment, extension } = opts;
388
+ if (options.dotenv) {
389
+ loadDotenv(options.dotenv, environment);
390
+ }
391
+ const resolvedConfigDir = resolveConfigDir(configDir);
392
+ const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);
393
+ const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);
394
+ let mergedConfig = deepMerge(baseConfig, envConfig);
395
+ const overrideDir = options.overrideDir !== false ? options.overrideDir ?? DEFAULT_OPTIONS.overrideDir : null;
396
+ if (overrideDir) {
397
+ const overrideConfigs = loadOverrideDir(overrideDir);
398
+ for (const override of overrideConfigs) {
399
+ mergedConfig = deepMerge(mergedConfig, override);
400
+ }
401
+ }
402
+ let vaultClient;
403
+ if (options.vault) {
404
+ vaultClient = await createVaultClient(options.vault);
405
+ await vaultClient.fetchSecrets(mergedConfig);
406
+ }
407
+ let awsClient;
408
+ if (options.aws) {
409
+ awsClient = await createAwsSecretsClient(options.aws);
410
+ await awsClient.fetchSecrets(mergedConfig);
411
+ }
412
+ let config2 = replacePlaceholders(mergedConfig, { vaultClient, awsClient });
413
+ if (options.stripEmpty) {
414
+ config2 = stripEmpty(config2);
415
+ }
416
+ if (options.postProcess) {
417
+ config2 = options.postProcess(config2);
418
+ }
419
+ if (options.schema) {
420
+ const result = options.schema.safeParse(config2);
421
+ if (!result.success) {
422
+ const errors = result.error.issues.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
423
+ throw new Error(`Config validation failed:
424
+ ${errors}`);
425
+ }
426
+ config2 = result.data;
427
+ }
428
+ if (options.logger) {
429
+ const maskedConfig = maskSecrets(config2);
430
+ options.logger(`Config loaded (${environment}):
431
+ ${JSON.stringify(maskedConfig, null, 2)}`);
432
+ }
433
+ return {
434
+ config: config2,
435
+ environment,
436
+ configDir: resolvedConfigDir
437
+ };
438
+ }
194
439
  function loadDotenv(dotenvOption, environment) {
195
440
  let envPath;
196
441
  if (typeof dotenvOption === "object" && dotenvOption.path) {
@@ -233,6 +478,6 @@ function loadOverrideDir(dir) {
233
478
  return files.map((file) => loadYamlFile(dir, file));
234
479
  }
235
480
 
236
- export { deepMerge, loadConfig, maskSecrets, replacePlaceholders, stripEmpty };
481
+ export { createAwsSecretsClient, createVaultClient, deepMerge, loadConfig, loadConfigAsync, maskSecrets, replacePlaceholders, stripEmpty };
237
482
  //# sourceMappingURL=index.mjs.map
238
483
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/deep-merge.ts","../src/utils/env-replacer.ts","../src/utils/mask-secrets.ts","../src/utils/strip-empty.ts","../src/loader.ts"],"names":["isPlainObject","config"],"mappings":"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","file":"index.mjs","sourcesContent":["type DeepObject = Record<string, unknown>;\n\nexport function deepMerge(base: DeepObject, override: DeepObject): DeepObject {\n const merged = { ...base };\n\n for (const key in override) {\n const overrideValue = override[key];\n const baseValue = base[key];\n\n if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {\n merged[key] = deepMerge(baseValue as DeepObject, overrideValue as DeepObject);\n } else {\n merged[key] = overrideValue;\n }\n }\n\n return merged;\n}\n\nfunction isPlainObject(value: unknown): value is DeepObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","const ENV_PLACEHOLDER_REGEX = /\\${(.*?)}/g;\n\nexport function replacePlaceholders(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return replaceStringPlaceholders(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => replacePlaceholders(item));\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n result[key] = replacePlaceholders(obj[key]);\n }\n return result;\n }\n\n return obj;\n}\n\nfunction replaceStringPlaceholders(str: string): string {\n return str.replace(ENV_PLACEHOLDER_REGEX, (match, key) => {\n const [envKey, ...rest] = key.split(':');\n const defaultValue = rest.length > 0 ? rest.join(':') : undefined;\n const envValue = process.env[envKey];\n\n let value: string;\n if (envValue !== undefined) {\n value = envValue;\n } else if (defaultValue !== undefined) {\n value = defaultValue;\n } else {\n throw new Error(`Environment variable \"${envKey}\" is not defined and no default value provided`);\n }\n\n if (value.includes('${')) {\n return replacePlaceholders(value) as string;\n }\n\n return value;\n });\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","const SECRET_PATTERNS = ['token', 'password', 'secret', 'key', 'apikey', 'api_key', 'credential'];\n\nconst URL_WITH_CREDENTIALS_REGEX = /^([a-z]+:\\/\\/)([^:]+):([^@]+)@(.+)$/i;\n\nexport function maskSecrets(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return maskUrlCredentials(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => maskSecrets(item));\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n const value = obj[key];\n if (isSecretKey(key)) {\n result[key] = maskValue(value);\n } else {\n result[key] = maskSecrets(value);\n }\n }\n return result;\n }\n\n return obj;\n}\n\nfunction isSecretKey(key: string): boolean {\n const lowerKey = key.toLowerCase();\n return SECRET_PATTERNS.some((pattern) => lowerKey.includes(pattern));\n}\n\nfunction maskValue(value: unknown): string {\n if (typeof value !== 'string') {\n return '***';\n }\n if (value.length <= 4) {\n return '***';\n }\n return value.slice(0, 2) + '***' + value.slice(-2);\n}\n\nfunction maskUrlCredentials(url: string): string {\n const match = url.match(URL_WITH_CREDENTIALS_REGEX);\n if (match) {\n const [, protocol, user, , host] = match;\n return `${protocol}${user}:***@${host}`;\n }\n return url;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","export function stripEmpty(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return obj === '' ? undefined : obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => stripEmpty(item)).filter((item) => item !== undefined);\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n let hasValues = false;\n\n for (const key of Object.keys(obj)) {\n const value = stripEmpty(obj[key]);\n if (value !== undefined) {\n result[key] = value;\n hasValues = true;\n }\n }\n\n return hasValues ? result : undefined;\n }\n\n return obj;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import { readFileSync, existsSync, readdirSync } from 'fs';\nimport * as yaml from 'js-yaml';\nimport * as dotenv from 'dotenv';\nimport { join } from 'path';\nimport type { ConfigLoaderOptions, ConfigLoaderResult } from './types.js';\nimport { deepMerge, replacePlaceholders, maskSecrets, stripEmpty } from './utils/index.js';\n\nconst DEFAULT_OPTIONS = {\n configDir: process.cwd(),\n baseFileName: 'base',\n environment: process.env.NODE_ENV || 'local',\n extension: 'yml' as const,\n overrideDir: '/etc/app/config',\n};\n\nexport function loadConfig<T>(options: ConfigLoaderOptions<T> = {}): ConfigLoaderResult<T> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const { configDir, baseFileName, environment, extension } = opts;\n\n // Load .env if requested\n if (options.dotenv) {\n loadDotenv(options.dotenv, environment);\n }\n\n const resolvedConfigDir = resolveConfigDir(configDir);\n\n const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);\n const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);\n\n let mergedConfig = deepMerge(baseConfig, envConfig);\n\n // Load override files from directory\n const overrideDir = options.overrideDir !== false ? (options.overrideDir ?? DEFAULT_OPTIONS.overrideDir) : null;\n if (overrideDir) {\n const overrideConfigs = loadOverrideDir(overrideDir);\n for (const override of overrideConfigs) {\n mergedConfig = deepMerge(mergedConfig, override);\n }\n }\n\n let config = replacePlaceholders(mergedConfig) as T;\n\n // Strip empty strings and objects\n if (options.stripEmpty) {\n config = stripEmpty(config) as T;\n }\n\n // Post-process\n if (options.postProcess) {\n config = options.postProcess(config);\n }\n\n // Validate with Zod schema\n if (options.schema) {\n const result = options.schema.safeParse(config);\n if (!result.success) {\n const errors = result.error.issues\n .map((e) => ` - ${e.path.join('.')}: ${e.message}`)\n .join('\\n');\n throw new Error(`Config validation failed:\\n${errors}`);\n }\n config = result.data;\n }\n\n // Log config with masked secrets\n if (options.logger) {\n const maskedConfig = maskSecrets(config);\n options.logger(`Config loaded (${environment}):\\n${JSON.stringify(maskedConfig, null, 2)}`);\n }\n\n return {\n config,\n environment,\n configDir: resolvedConfigDir,\n };\n}\n\nfunction loadDotenv(\n dotenvOption: boolean | { path?: string },\n environment: string,\n): void {\n let envPath: string;\n\n if (typeof dotenvOption === 'object' && dotenvOption.path) {\n envPath = dotenvOption.path;\n } else {\n envPath = environment === 'production' ? '.env' : `.env.${environment}`;\n }\n\n dotenv.config({ path: envPath });\n}\n\nfunction resolveConfigDir(configDir: string): string {\n const possiblePaths = [\n join(configDir, 'src', 'config'),\n join(configDir, 'dist', 'config'),\n join(configDir, 'config'),\n configDir,\n ];\n\n for (const path of possiblePaths) {\n if (existsSync(join(path, 'base.yml')) || existsSync(join(path, 'base.yaml'))) {\n return path;\n }\n }\n\n throw new Error(\n `Config files not found. Searched in:\\n${possiblePaths.map((p) => ` - ${p}`).join('\\n')}`,\n );\n}\n\nfunction loadYamlFile(dir: string, filename: string): Record<string, unknown> {\n const filePath = join(dir, filename);\n\n if (!existsSync(filePath)) {\n return {};\n }\n\n const content = readFileSync(filePath, 'utf8');\n return (yaml.load(content) as Record<string, unknown>) || {};\n}\n\nfunction loadOverrideDir(dir: string): Record<string, unknown>[] {\n if (!existsSync(dir)) {\n return [];\n }\n\n const files = readdirSync(dir)\n .filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'))\n .sort();\n\n return files.map((file) => loadYamlFile(dir, file));\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/deep-merge.ts","../src/utils/env-replacer.ts","../src/utils/mask-secrets.ts","../src/utils/strip-empty.ts","../src/utils/vault-client.ts","../src/utils/aws-secrets-client.ts","../src/loader.ts"],"names":["isPlainObject","config"],"mappings":";;;;;;;;;;AAEO,SAAS,SAAA,CAAU,MAAkB,QAAA,EAAkC;AAC5E,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,IAAA,EAAK;AAEzB,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,IAAA,MAAM,aAAA,GAAgB,SAAS,GAAG,CAAA;AAClC,IAAA,MAAM,SAAA,GAAY,KAAK,GAAG,CAAA;AAE1B,IAAA,IAAI,aAAA,CAAc,aAAa,CAAA,IAAK,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5D,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,SAAA,CAAU,SAAA,EAAyB,aAA2B,CAAA;AAAA,IAC9E,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,aAAA;AAAA,IAChB;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,cAAc,KAAA,EAAqC;AAC1D,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;;;AClBA,IAAM,qBAAA,GAAwB,YAAA;AAC9B,IAAM,YAAA,GAAe,QAAA;AACrB,IAAM,UAAA,GAAa,MAAA;AAOZ,SAAS,mBAAA,CAAoB,GAAA,EAAc,OAAA,GAAsC,EAAC,EAAY;AACnG,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,yBAAA,CAA0B,KAAK,OAAO,CAAA;AAAA,EAC/C;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,EAAG;AACtB,IAAA,OAAO,IAAI,GAAA,CAAI,CAAC,SAAS,mBAAA,CAAoB,IAAA,EAAM,OAAO,CAAC,CAAA;AAAA,EAC7D;AAEA,EAAA,IAAIA,cAAAA,CAAc,GAAG,CAAA,EAAG;AACtB,IAAA,MAAM,SAAkC,EAAC;AACzC,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG;AAClC,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,mBAAA,CAAoB,GAAA,CAAI,GAAG,GAAG,OAAO,CAAA;AAAA,IACrD;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,yBAAA,CAA0B,KAAa,OAAA,EAA6C;AAC3F,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,qBAAA,EAAuB,CAAC,OAAO,GAAA,KAAQ;AAExD,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,EAAG;AAChC,MAAA,OAAO,uBAAA,CAAwB,GAAA,EAAK,KAAA,EAAO,OAAO,CAAA;AAAA,IACpD;AAGA,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,UAAU,CAAA,EAAG;AAC9B,MAAA,OAAO,qBAAA,CAAsB,GAAA,EAAK,KAAA,EAAO,OAAO,CAAA;AAAA,IAClD;AAGA,IAAA,OAAO,qBAAA,CAAsB,GAAA,EAAK,KAAA,EAAO,OAAO,CAAA;AAAA,EAClD,CAAC,CAAA;AACH;AAEA,SAAS,uBAAA,CACP,GAAA,EACA,aAAA,EACA,OAAA,EACQ;AAER,EAAA,MAAM,QAAQ,GAAA,CAAI,SAAA,CAAU,aAAa,MAAM,CAAA,CAAE,MAAM,GAAG,CAAA;AAE1D,EAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,qCAAqC,aAAa,CAAA,gEAAA;AAAA,KAEpD;AAAA,EACF;AAEA,EAAA,MAAM,CAAC,IAAA,EAAM,KAAA,EAAO,GAAG,YAAY,CAAA,GAAI,KAAA;AACvC,EAAA,MAAM,eAAe,YAAA,CAAa,MAAA,GAAS,IAAI,YAAA,CAAa,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA;AAExE,EAAA,IAAI,CAAC,QAAQ,WAAA,EAAa;AACxB,IAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,MAAA,OAAO,YAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,aAAa,CAAA,qCAAA,CAAuC,CAAA;AAAA,EAC5F;AAEA,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,WAAA,CAAY,SAAA,CAAU,MAAM,KAAK,CAAA;AAE7D,EAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,IAAA,OAAO,OAAO,WAAW,CAAA;AAAA,EAC3B;AAEA,EAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,IAAA,OAAO,YAAA;AAAA,EACT;AAEA,EAAA,MAAM,IAAI,KAAA,CAAM,CAAA,cAAA,EAAiB,KAAK,CAAA,qBAAA,EAAwB,IAAI,CAAA,+BAAA,CAAiC,CAAA;AACrG;AAEA,SAAS,qBAAA,CACP,GAAA,EACA,aAAA,EACA,OAAA,EACQ;AAER,EAAA,MAAM,QAAQ,GAAA,CAAI,SAAA,CAAU,WAAW,MAAM,CAAA,CAAE,MAAM,GAAG,CAAA;AAExD,EAAA,IAAI,MAAM,MAAA,GAAS,CAAA,IAAK,CAAC,KAAA,CAAM,CAAC,CAAA,EAAG;AACjC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,mCAAmC,aAAa,CAAA,iGAAA;AAAA,KAElD;AAAA,EACF;AAEA,EAAA,MAAM,CAAC,UAAA,EAAY,KAAA,EAAO,GAAG,YAAY,CAAA,GAAI,KAAA;AAC7C,EAAA,MAAM,eAAe,YAAA,CAAa,MAAA,GAAS,IAAI,YAAA,CAAa,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA;AAExE,EAAA,IAAI,CAAC,QAAQ,SAAA,EAAW;AACtB,IAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,MAAA,OAAO,YAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,aAAa,CAAA,mCAAA,CAAqC,CAAA;AAAA,EACxF;AAEA,EAAA,MAAM,cAAc,OAAA,CAAQ,SAAA,CAAU,SAAA,CAAU,UAAA,EAAY,SAAS,MAAS,CAAA;AAE9E,EAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,IAAA,OAAO,OAAO,WAAW,CAAA;AAAA,EAC3B;AAEA,EAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,IAAA,OAAO,YAAA;AAAA,EACT;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,KAAK,CAAA,gBAAA,EAAmB,UAAU,CAAA,+BAAA,CAAiC,CAAA;AAAA,EAC1G,CAAA,MAAO;AACL,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,UAAU,CAAA,yCAAA,CAA2C,CAAA;AAAA,EACtF;AACF;AAEA,SAAS,qBAAA,CAAsB,GAAA,EAAa,MAAA,EAAgB,OAAA,EAA6C;AACvG,EAAA,MAAM,CAAC,MAAA,EAAQ,GAAG,IAAI,CAAA,GAAI,GAAA,CAAI,MAAM,GAAG,CAAA;AACvC,EAAA,MAAM,eAAe,IAAA,CAAK,MAAA,GAAS,IAAI,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA;AACxD,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AAEnC,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,IAAA,KAAA,GAAQ,QAAA;AAAA,EACV,CAAA,MAAA,IAAW,iBAAiB,MAAA,EAAW;AACrC,IAAA,KAAA,GAAQ,YAAA;AAAA,EACV,CAAA,MAAO;AACL,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,MAAM,CAAA,8CAAA,CAAgD,CAAA;AAAA,EACjG;AAGA,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,EAAG;AACxB,IAAA,OAAO,mBAAA,CAAoB,OAAO,OAAO,CAAA;AAAA,EAC3C;AAEA,EAAA,OAAO,KAAA;AACT;AAEA,SAASA,eAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;;;ACzJA,IAAM,eAAA,GAAkB,CAAC,OAAA,EAAS,UAAA,EAAY,UAAU,KAAA,EAAO,QAAA,EAAU,WAAW,YAAY,CAAA;AAEhG,IAAM,0BAAA,GAA6B,sCAAA;AAE5B,SAAS,YAAY,GAAA,EAAuB;AACjD,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,mBAAmB,GAAG,CAAA;AAAA,EAC/B;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,EAAG;AACtB,IAAA,OAAO,IAAI,GAAA,CAAI,CAAC,IAAA,KAAS,WAAA,CAAY,IAAI,CAAC,CAAA;AAAA,EAC5C;AAEA,EAAA,IAAIA,cAAAA,CAAc,GAAG,CAAA,EAAG;AACtB,IAAA,MAAM,SAAkC,EAAC;AACzC,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG;AAClC,MAAA,MAAM,KAAA,GAAQ,IAAI,GAAG,CAAA;AACrB,MAAA,IAAI,WAAA,CAAY,GAAG,CAAA,EAAG;AACpB,QAAA,MAAA,CAAO,GAAG,CAAA,GAAI,SAAA,CAAU,KAAK,CAAA;AAAA,MAC/B,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,GAAG,CAAA,GAAI,WAAA,CAAY,KAAK,CAAA;AAAA,MACjC;AAAA,IACF;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,YAAY,GAAA,EAAsB;AACzC,EAAA,MAAM,QAAA,GAAW,IAAI,WAAA,EAAY;AACjC,EAAA,OAAO,gBAAgB,IAAA,CAAK,CAAC,YAAY,QAAA,CAAS,QAAA,CAAS,OAAO,CAAC,CAAA;AACrE;AAEA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,KAAA,CAAM,UAAU,CAAA,EAAG;AACrB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA,CAAM,MAAM,CAAA,EAAG,CAAC,IAAI,KAAA,GAAQ,KAAA,CAAM,MAAM,EAAE,CAAA;AACnD;AAEA,SAAS,mBAAmB,GAAA,EAAqB;AAC/C,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,0BAA0B,CAAA;AAClD,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,MAAM,GAAG,QAAA,EAAU,IAAA,IAAQ,IAAI,CAAA,GAAI,KAAA;AACnC,IAAA,OAAO,CAAA,EAAG,QAAQ,CAAA,EAAG,IAAI,QAAQ,IAAI,CAAA,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAASA,eAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;;;ACvDO,SAAS,WAAW,GAAA,EAAuB;AAChD,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,GAAA,KAAQ,KAAK,MAAA,GAAY,GAAA;AAAA,EAClC;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,EAAG;AACtB,IAAA,OAAO,GAAA,CAAI,GAAA,CAAI,CAAC,IAAA,KAAS,UAAA,CAAW,IAAI,CAAC,CAAA,CAAE,MAAA,CAAO,CAAC,IAAA,KAAS,IAAA,KAAS,MAAS,CAAA;AAAA,EAChF;AAEA,EAAA,IAAIA,cAAAA,CAAc,GAAG,CAAA,EAAG;AACtB,IAAA,MAAM,SAAkC,EAAC;AACzC,IAAA,IAAI,SAAA,GAAY,KAAA;AAEhB,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG;AAClC,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,GAAG,CAAC,CAAA;AACjC,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AACd,QAAA,SAAA,GAAY,IAAA;AAAA,MACd;AAAA,IACF;AAEA,IAAA,OAAO,YAAY,MAAA,GAAS,MAAA;AAAA,EAC9B;AAEA,EAAA,OAAO,GAAA;AACT;AAEA,SAASA,eAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AC1BA,IAAM,uBAAA,GAA0B,2CAAA;AAWhC,eAAsB,kBAAkB,OAAA,EAA6C;AACnF,EAAA,MAAM,QAAQ,SAAA,CAAU;AAAA,IACtB,UAAU,OAAA,CAAQ,QAAA;AAAA,IAClB,UAAA,EAAY,IAAA;AAAA,IACZ,WAAW,OAAA,CAAQ;AAAA,GACpB,CAAA;AAGD,EAAA,MAAM,WAAA,GAAc,MAAM,KAAA,CAAM,YAAA,CAAa;AAAA,IAC3C,SAAS,OAAA,CAAQ,MAAA;AAAA,IACjB,WAAW,OAAA,CAAQ;AAAA,GACpB,CAAA;AAED,EAAA,KAAA,CAAM,KAAA,GAAQ,YAAY,IAAA,CAAK,YAAA;AAE/B,EAAA,MAAM,QAA0B,EAAC;AAEjC,EAAA,OAAO;AAAA,IACL,MAAM,aAAa,GAAA,EAAyC;AAC1D,MAAA,MAAM,KAAA,GAAQ,kBAAkB,GAAG,CAAA;AAGnC,MAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,QACZ,KAAA,CAAM,GAAA,CAAI,OAAO,IAAA,KAAS;AACxB,UAAA,IAAI,CAAC,KAAA,CAAM,IAAI,CAAA,EAAG;AAChB,YAAA,IAAI;AACF,cAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AAGpC,cAAA,KAAA,CAAM,IAAI,CAAA,GAAI,MAAA,CAAO,MAAM,IAAA,IAAQ,MAAA,CAAO,QAAQ,EAAC;AAAA,YACrD,CAAA,CAAA,MAAQ;AAEN,cAAA,KAAA,CAAM,IAAI,IAAI,EAAC;AAAA,YACjB;AAAA,UACF;AAAA,QACF,CAAC;AAAA,OACH;AAEA,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IAEA,SAAA,CAAU,MAAc,KAAA,EAAwB;AAC9C,MAAA,OAAO,KAAA,CAAM,IAAI,CAAA,GAAI,KAAK,CAAA;AAAA,IAC5B;AAAA,GACF;AACF;AAKA,SAAS,kBAAkB,GAAA,EAAwB;AACjD,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAY;AAE9B,EAAA,SAAS,SAAS,KAAA,EAAsB;AACtC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,IAAI,KAAA;AACJ,MAAA,OAAA,CAAQ,KAAA,GAAQ,uBAAA,CAAwB,IAAA,CAAK,KAAK,OAAO,IAAA,EAAM;AAC7D,QAAA,KAAA,CAAM,GAAA,CAAI,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,MACpB;AACA,MAAA,uBAAA,CAAwB,SAAA,GAAY,CAAA;AAAA,IACtC,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/B,MAAA,KAAA,CAAM,QAAQ,QAAQ,CAAA;AAAA,IACxB,CAAA,MAAA,IAAW,KAAA,KAAU,IAAA,IAAQ,OAAO,UAAU,QAAA,EAAU;AACtD,MAAA,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IACvC;AAAA,EACF;AAEA,EAAA,QAAA,CAAS,GAAG,CAAA;AACZ,EAAA,OAAO,KAAA,CAAM,KAAK,KAAK,CAAA;AACzB;AChFA,IAAM,qBAAA,GAAwB,8CAAA;AAW9B,eAAsB,uBAAuB,OAAA,EAAuD;AAClG,EAAA,MAAM,MAAA,GAAS,IAAI,oBAAA,CAAqB;AAAA,IACtC,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,WAAA,EAAa;AAAA,MACX,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,iBAAiB,OAAA,CAAQ;AAAA;AAC3B,GACD,CAAA;AAED,EAAA,MAAM,QAAyB,EAAC;AAEhC,EAAA,OAAO;AAAA,IACL,MAAM,aAAa,GAAA,EAAwC;AACzD,MAAA,MAAM,WAAA,GAAc,sBAAsB,GAAG,CAAA;AAG7C,MAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,QACZ,WAAA,CAAY,GAAA,CAAI,OAAO,UAAA,KAAe;AACpC,UAAA,IAAI,KAAA,CAAM,UAAU,CAAA,KAAM,MAAA,EAAW;AACnC,YAAA,IAAI;AACF,cAAA,MAAM,UAAU,IAAI,qBAAA,CAAsB,EAAE,QAAA,EAAU,YAAY,CAAA;AAClE,cAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAE1C,cAAA,MAAM,eAAe,QAAA,CAAS,YAAA;AAC9B,cAAA,IAAI,YAAA,EAAc;AAEhB,gBAAA,IAAI;AACF,kBAAA,KAAA,CAAM,UAAU,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,YAAY,CAAA;AAAA,gBAC7C,CAAA,CAAA,MAAQ;AACN,kBAAA,KAAA,CAAM,UAAU,CAAA,GAAI,YAAA;AAAA,gBACtB;AAAA,cACF,CAAA,MAAO;AACL,gBAAA,KAAA,CAAM,UAAU,IAAI,EAAC;AAAA,cACvB;AAAA,YACF,CAAA,CAAA,MAAQ;AAEN,cAAA,KAAA,CAAM,UAAU,IAAI,EAAC;AAAA,YACvB;AAAA,UACF;AAAA,QACF,CAAC;AAAA,OACH;AAEA,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IAEA,SAAA,CAAU,YAAoB,KAAA,EAAyB;AACrD,MAAA,MAAM,MAAA,GAAS,MAAM,UAAU,CAAA;AAE/B,MAAA,IAAI,WAAW,MAAA,EAAW;AACxB,QAAA,OAAO,MAAA;AAAA,MACT;AAGA,MAAA,IAAI,CAAC,KAAA,EAAO;AACV,QAAA,OAAO,OAAO,MAAA,KAAW,QAAA,GAAW,MAAA,GAAS,MAAA;AAAA,MAC/C;AAGA,MAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,KAAW,IAAA,EAAM;AACjD,QAAA,OAAQ,OAAmC,KAAK,CAAA;AAAA,MAClD;AAEA,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,GACF;AACF;AAKA,SAAS,sBAAsB,GAAA,EAAwB;AACrD,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAY;AAE9B,EAAA,SAAS,SAAS,KAAA,EAAsB;AACtC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,IAAI,KAAA;AACJ,MAAA,OAAA,CAAQ,KAAA,GAAQ,qBAAA,CAAsB,IAAA,CAAK,KAAK,OAAO,IAAA,EAAM;AAC3D,QAAA,KAAA,CAAM,GAAA,CAAI,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,MACpB;AACA,MAAA,qBAAA,CAAsB,SAAA,GAAY,CAAA;AAAA,IACpC,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/B,MAAA,KAAA,CAAM,QAAQ,QAAQ,CAAA;AAAA,IACxB,CAAA,MAAA,IAAW,KAAA,KAAU,IAAA,IAAQ,OAAO,UAAU,QAAA,EAAU;AACtD,MAAA,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IACvC;AAAA,EACF;AAEA,EAAA,QAAA,CAAS,GAAG,CAAA;AACZ,EAAA,OAAO,KAAA,CAAM,KAAK,KAAK,CAAA;AACzB;;;ACvFA,IAAM,eAAA,GAAkB;AAAA,EACtB,SAAA,EAAW,QAAQ,GAAA,EAAI;AAAA,EACvB,YAAA,EAAc,MAAA;AAAA,EACd,WAAA,EAAa,OAAA,CAAQ,GAAA,CAAI,QAAA,IAAY,OAAA;AAAA,EACrC,SAAA,EAAW,KAAA;AAAA,EACX,WAAA,EAAa;AACf,CAAA;AAEO,SAAS,UAAA,CAAc,OAAA,GAAkC,EAAC,EAA0B;AACzF,EAAA,IAAI,OAAA,CAAQ,KAAA,IAAS,OAAA,CAAQ,GAAA,EAAK;AAChC,IAAA,MAAM,IAAI,MAAM,wFAAwF,CAAA;AAAA,EAC1G;AAEA,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,eAAA,EAAiB,GAAG,OAAA,EAAQ;AAC9C,EAAA,MAAM,EAAE,SAAA,EAAW,YAAA,EAAc,WAAA,EAAa,WAAU,GAAI,IAAA;AAG5D,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,UAAA,CAAW,OAAA,CAAQ,QAAQ,WAAW,CAAA;AAAA,EACxC;AAEA,EAAA,MAAM,iBAAA,GAAoB,iBAAiB,SAAS,CAAA;AAEpD,EAAA,MAAM,aAAa,YAAA,CAAa,iBAAA,EAAmB,GAAG,YAAY,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AACjF,EAAA,MAAM,YAAY,YAAA,CAAa,iBAAA,EAAmB,GAAG,WAAW,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AAE/E,EAAA,IAAI,YAAA,GAAe,SAAA,CAAU,UAAA,EAAY,SAAS,CAAA;AAGlD,EAAA,MAAM,cAAc,OAAA,CAAQ,WAAA,KAAgB,QAAS,OAAA,CAAQ,WAAA,IAAe,gBAAgB,WAAA,GAAe,IAAA;AAC3G,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,MAAM,eAAA,GAAkB,gBAAgB,WAAW,CAAA;AACnD,IAAA,KAAA,MAAW,YAAY,eAAA,EAAiB;AACtC,MAAA,YAAA,GAAe,SAAA,CAAU,cAAc,QAAQ,CAAA;AAAA,IACjD;AAAA,EACF;AAEA,EAAA,IAAIC,OAAAA,GAAS,oBAAoB,YAAY,CAAA;AAG7C,EAAA,IAAI,QAAQ,UAAA,EAAY;AACtB,IAAAA,OAAAA,GAAS,WAAWA,OAAM,CAAA;AAAA,EAC5B;AAGA,EAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,IAAAA,OAAAA,GAAS,OAAA,CAAQ,WAAA,CAAYA,OAAM,CAAA;AAAA,EACrC;AAGA,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,CAAO,SAAA,CAAUA,OAAM,CAAA;AAC9C,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,MAAM,SAAS,MAAA,CAAO,KAAA,CAAM,OACzB,GAAA,CAAI,CAAC,MAAM,CAAA,IAAA,EAAO,CAAA,CAAE,KAAK,IAAA,CAAK,GAAG,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,OAAO,CAAA,CAAE,CAAA,CAClD,KAAK,IAAI,CAAA;AACZ,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA;AAAA,EAA8B,MAAM,CAAA,CAAE,CAAA;AAAA,IACxD;AACA,IAAAA,UAAS,MAAA,CAAO,IAAA;AAAA,EAClB;AAGA,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,YAAA,GAAe,YAAYA,OAAM,CAAA;AACvC,IAAA,OAAA,CAAQ,MAAA,CAAO,kBAAkB,WAAW,CAAA;AAAA,EAAO,KAAK,SAAA,CAAU,YAAA,EAAc,IAAA,EAAM,CAAC,CAAC,CAAA,CAAE,CAAA;AAAA,EAC5F;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAAA,OAAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA,EAAW;AAAA,GACb;AACF;AAEA,eAAsB,eAAA,CACpB,OAAA,GAAkC,EAAC,EACH;AAChC,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,eAAA,EAAiB,GAAG,OAAA,EAAQ;AAC9C,EAAA,MAAM,EAAE,SAAA,EAAW,YAAA,EAAc,WAAA,EAAa,WAAU,GAAI,IAAA;AAG5D,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,UAAA,CAAW,OAAA,CAAQ,QAAQ,WAAW,CAAA;AAAA,EACxC;AAEA,EAAA,MAAM,iBAAA,GAAoB,iBAAiB,SAAS,CAAA;AAEpD,EAAA,MAAM,aAAa,YAAA,CAAa,iBAAA,EAAmB,GAAG,YAAY,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AACjF,EAAA,MAAM,YAAY,YAAA,CAAa,iBAAA,EAAmB,GAAG,WAAW,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AAE/E,EAAA,IAAI,YAAA,GAAe,SAAA,CAAU,UAAA,EAAY,SAAS,CAAA;AAGlD,EAAA,MAAM,cAAc,OAAA,CAAQ,WAAA,KAAgB,QAAS,OAAA,CAAQ,WAAA,IAAe,gBAAgB,WAAA,GAAe,IAAA;AAC3G,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,MAAM,eAAA,GAAkB,gBAAgB,WAAW,CAAA;AACnD,IAAA,KAAA,MAAW,YAAY,eAAA,EAAiB;AACtC,MAAA,YAAA,GAAe,SAAA,CAAU,cAAc,QAAQ,CAAA;AAAA,IACjD;AAAA,EACF;AAGA,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,IAAA,WAAA,GAAc,MAAM,iBAAA,CAAkB,OAAA,CAAQ,KAAK,CAAA;AACnD,IAAA,MAAM,WAAA,CAAY,aAAa,YAAY,CAAA;AAAA,EAC7C;AAGA,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,QAAQ,GAAA,EAAK;AACf,IAAA,SAAA,GAAY,MAAM,sBAAA,CAAuB,OAAA,CAAQ,GAAG,CAAA;AACpD,IAAA,MAAM,SAAA,CAAU,aAAa,YAAY,CAAA;AAAA,EAC3C;AAGA,EAAA,IAAIA,UAAS,mBAAA,CAAoB,YAAA,EAAc,EAAE,WAAA,EAAa,WAAW,CAAA;AAGzE,EAAA,IAAI,QAAQ,UAAA,EAAY;AACtB,IAAAA,OAAAA,GAAS,WAAWA,OAAM,CAAA;AAAA,EAC5B;AAGA,EAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,IAAAA,OAAAA,GAAS,OAAA,CAAQ,WAAA,CAAYA,OAAM,CAAA;AAAA,EACrC;AAGA,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,CAAO,SAAA,CAAUA,OAAM,CAAA;AAC9C,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,MAAM,SAAS,MAAA,CAAO,KAAA,CAAM,OACzB,GAAA,CAAI,CAAC,MAAM,CAAA,IAAA,EAAO,CAAA,CAAE,KAAK,IAAA,CAAK,GAAG,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,OAAO,CAAA,CAAE,CAAA,CAClD,KAAK,IAAI,CAAA;AACZ,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA;AAAA,EAA8B,MAAM,CAAA,CAAE,CAAA;AAAA,IACxD;AACA,IAAAA,UAAS,MAAA,CAAO,IAAA;AAAA,EAClB;AAGA,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,YAAA,GAAe,YAAYA,OAAM,CAAA;AACvC,IAAA,OAAA,CAAQ,MAAA,CAAO,kBAAkB,WAAW,CAAA;AAAA,EAAO,KAAK,SAAA,CAAU,YAAA,EAAc,IAAA,EAAM,CAAC,CAAC,CAAA,CAAE,CAAA;AAAA,EAC5F;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAAA,OAAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA,EAAW;AAAA,GACb;AACF;AAEA,SAAS,UAAA,CACP,cACA,WAAA,EACM;AACN,EAAA,IAAI,OAAA;AAEJ,EAAA,IAAI,OAAO,YAAA,KAAiB,QAAA,IAAY,YAAA,CAAa,IAAA,EAAM;AACzD,IAAA,OAAA,GAAU,YAAA,CAAa,IAAA;AAAA,EACzB,CAAA,MAAO;AACL,IAAA,OAAA,GAAU,WAAA,KAAgB,YAAA,GAAe,MAAA,GAAS,CAAA,KAAA,EAAQ,WAAW,CAAA,CAAA;AAAA,EACvE;AAEA,EAAO,MAAA,CAAA,MAAA,CAAO,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AACjC;AAEA,SAAS,iBAAiB,SAAA,EAA2B;AACnD,EAAA,MAAM,aAAA,GAAgB;AAAA,IACpB,IAAA,CAAK,SAAA,EAAW,KAAA,EAAO,QAAQ,CAAA;AAAA,IAC/B,IAAA,CAAK,SAAA,EAAW,MAAA,EAAQ,QAAQ,CAAA;AAAA,IAChC,IAAA,CAAK,WAAW,QAAQ,CAAA;AAAA,IACxB;AAAA,GACF;AAEA,EAAA,KAAA,MAAW,QAAQ,aAAA,EAAe;AAChC,IAAA,IAAI,UAAA,CAAW,IAAA,CAAK,IAAA,EAAM,UAAU,CAAC,CAAA,IAAK,UAAA,CAAW,IAAA,CAAK,IAAA,EAAM,WAAW,CAAC,CAAA,EAAG;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,CAAA;AAAA,EAAyC,aAAA,CAAc,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,IAAA,EAAO,CAAC,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,GAC1F;AACF;AAEA,SAAS,YAAA,CAAa,KAAa,QAAA,EAA2C;AAC5E,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAK,QAAQ,CAAA;AAEnC,EAAA,IAAI,CAAC,UAAA,CAAW,QAAQ,CAAA,EAAG;AACzB,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,QAAA,EAAU,MAAM,CAAA;AAC7C,EAAA,OAAa,IAAA,CAAA,IAAA,CAAK,OAAO,CAAA,IAAiC,EAAC;AAC7D;AAEA,SAAS,gBAAgB,GAAA,EAAwC;AAC/D,EAAA,IAAI,CAAC,UAAA,CAAW,GAAG,CAAA,EAAG;AACpB,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,QAAQ,WAAA,CAAY,GAAG,CAAA,CAC1B,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,CAAS,MAAM,KAAK,CAAA,CAAE,QAAA,CAAS,OAAO,CAAC,EACvD,IAAA,EAAK;AAER,EAAA,OAAO,MAAM,GAAA,CAAI,CAAC,SAAS,YAAA,CAAa,GAAA,EAAK,IAAI,CAAC,CAAA;AACpD","file":"index.mjs","sourcesContent":["type DeepObject = Record<string, unknown>;\n\nexport function deepMerge(base: DeepObject, override: DeepObject): DeepObject {\n const merged = { ...base };\n\n for (const key in override) {\n const overrideValue = override[key];\n const baseValue = base[key];\n\n if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {\n merged[key] = deepMerge(baseValue as DeepObject, overrideValue as DeepObject);\n } else {\n merged[key] = overrideValue;\n }\n }\n\n return merged;\n}\n\nfunction isPlainObject(value: unknown): value is DeepObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import type { VaultClient } from './vault-client.js';\nimport type { AwsSecretsClient } from './aws-secrets-client.js';\n\nconst ENV_PLACEHOLDER_REGEX = /\\${(.*?)}/g;\nconst VAULT_PREFIX = 'vault:';\nconst AWS_PREFIX = 'aws:';\n\nexport interface ReplacePlaceholdersOptions {\n vaultClient?: VaultClient;\n awsClient?: AwsSecretsClient;\n}\n\nexport function replacePlaceholders(obj: unknown, options: ReplacePlaceholdersOptions = {}): unknown {\n if (typeof obj === 'string') {\n return replaceStringPlaceholders(obj, options);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => replacePlaceholders(item, options));\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n result[key] = replacePlaceholders(obj[key], options);\n }\n return result;\n }\n\n return obj;\n}\n\nfunction replaceStringPlaceholders(str: string, options: ReplacePlaceholdersOptions): string {\n return str.replace(ENV_PLACEHOLDER_REGEX, (match, key) => {\n // Handle Vault placeholders: ${vault:path:field} or ${vault:path:field:default}\n if (key.startsWith(VAULT_PREFIX)) {\n return resolveVaultPlaceholder(key, match, options);\n }\n\n // Handle AWS placeholders: ${aws:secret:field} or ${aws:secret:field:default} or ${aws:secret}\n if (key.startsWith(AWS_PREFIX)) {\n return resolveAwsPlaceholder(key, match, options);\n }\n\n // Handle environment variable placeholders: ${VAR} or ${VAR:default}\n return resolveEnvPlaceholder(key, match, options);\n });\n}\n\nfunction resolveVaultPlaceholder(\n key: string,\n originalMatch: string,\n options: ReplacePlaceholdersOptions,\n): string {\n // Parse: vault:secret/data/api:DB_PASSWORD:default_value\n const parts = key.substring(VAULT_PREFIX.length).split(':');\n\n if (parts.length < 2) {\n throw new Error(\n `Invalid Vault placeholder format \"${originalMatch}\". ` +\n `Expected \\${vault:path:field} or \\${vault:path:field:default}`,\n );\n }\n\n const [path, field, ...defaultParts] = parts;\n const defaultValue = defaultParts.length > 0 ? defaultParts.join(':') : undefined;\n\n if (!options.vaultClient) {\n if (defaultValue !== undefined) {\n return defaultValue;\n }\n throw new Error(`Vault placeholder \"${originalMatch}\" found but no Vault options provided`);\n }\n\n const secretValue = options.vaultClient.getSecret(path, field);\n\n if (secretValue !== undefined) {\n return String(secretValue);\n }\n\n if (defaultValue !== undefined) {\n return defaultValue;\n }\n\n throw new Error(`Vault secret \"${field}\" not found at path \"${path}\" and no default value provided`);\n}\n\nfunction resolveAwsPlaceholder(\n key: string,\n originalMatch: string,\n options: ReplacePlaceholdersOptions,\n): string {\n // Parse: aws:secret-name:field:default or aws:secret-name:field or aws:secret-name\n const parts = key.substring(AWS_PREFIX.length).split(':');\n\n if (parts.length < 1 || !parts[0]) {\n throw new Error(\n `Invalid AWS placeholder format \"${originalMatch}\". ` +\n `Expected \\${aws:secret-name} or \\${aws:secret-name:field} or \\${aws:secret-name:field:default}`,\n );\n }\n\n const [secretName, field, ...defaultParts] = parts;\n const defaultValue = defaultParts.length > 0 ? defaultParts.join(':') : undefined;\n\n if (!options.awsClient) {\n if (defaultValue !== undefined) {\n return defaultValue;\n }\n throw new Error(`AWS placeholder \"${originalMatch}\" found but no AWS options provided`);\n }\n\n const secretValue = options.awsClient.getSecret(secretName, field || undefined);\n\n if (secretValue !== undefined) {\n return String(secretValue);\n }\n\n if (defaultValue !== undefined) {\n return defaultValue;\n }\n\n if (field) {\n throw new Error(`AWS secret field \"${field}\" not found in \"${secretName}\" and no default value provided`);\n } else {\n throw new Error(`AWS secret \"${secretName}\" not found and no default value provided`);\n }\n}\n\nfunction resolveEnvPlaceholder(key: string, _match: string, options: ReplacePlaceholdersOptions): string {\n const [envKey, ...rest] = key.split(':');\n const defaultValue = rest.length > 0 ? rest.join(':') : undefined;\n const envValue = process.env[envKey];\n\n let value: string;\n if (envValue !== undefined) {\n value = envValue;\n } else if (defaultValue !== undefined) {\n value = defaultValue;\n } else {\n throw new Error(`Environment variable \"${envKey}\" is not defined and no default value provided`);\n }\n\n // Handle nested placeholders\n if (value.includes('${')) {\n return replacePlaceholders(value, options) as string;\n }\n\n return value;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","const SECRET_PATTERNS = ['token', 'password', 'secret', 'key', 'apikey', 'api_key', 'credential'];\n\nconst URL_WITH_CREDENTIALS_REGEX = /^([a-z]+:\\/\\/)([^:]+):([^@]+)@(.+)$/i;\n\nexport function maskSecrets(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return maskUrlCredentials(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => maskSecrets(item));\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n const value = obj[key];\n if (isSecretKey(key)) {\n result[key] = maskValue(value);\n } else {\n result[key] = maskSecrets(value);\n }\n }\n return result;\n }\n\n return obj;\n}\n\nfunction isSecretKey(key: string): boolean {\n const lowerKey = key.toLowerCase();\n return SECRET_PATTERNS.some((pattern) => lowerKey.includes(pattern));\n}\n\nfunction maskValue(value: unknown): string {\n if (typeof value !== 'string') {\n return '***';\n }\n if (value.length <= 4) {\n return '***';\n }\n return value.slice(0, 2) + '***' + value.slice(-2);\n}\n\nfunction maskUrlCredentials(url: string): string {\n const match = url.match(URL_WITH_CREDENTIALS_REGEX);\n if (match) {\n const [, protocol, user, , host] = match;\n return `${protocol}${user}:***@${host}`;\n }\n return url;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","export function stripEmpty(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return obj === '' ? undefined : obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => stripEmpty(item)).filter((item) => item !== undefined);\n }\n\n if (isPlainObject(obj)) {\n const result: Record<string, unknown> = {};\n let hasValues = false;\n\n for (const key of Object.keys(obj)) {\n const value = stripEmpty(obj[key]);\n if (value !== undefined) {\n result[key] = value;\n hasValues = true;\n }\n }\n\n return hasValues ? result : undefined;\n }\n\n return obj;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import NodeVault from 'node-vault';\nimport type { VaultOptions } from '../types.js';\n\nconst VAULT_PLACEHOLDER_REGEX = /\\${vault:([^:}]+):([^:}]+)(?::([^}]*))?}/g;\n\nexport interface VaultSecretCache {\n [path: string]: Record<string, unknown>;\n}\n\nexport interface VaultClient {\n fetchSecrets(obj: unknown): Promise<VaultSecretCache>;\n getSecret(path: string, field: string): unknown;\n}\n\nexport async function createVaultClient(options: VaultOptions): Promise<VaultClient> {\n const vault = NodeVault({\n endpoint: options.endpoint,\n apiVersion: 'v1',\n namespace: options.namespace,\n });\n\n // Authenticate with AppRole\n const loginResult = await vault.approleLogin({\n role_id: options.roleId,\n secret_id: options.secretId,\n });\n\n vault.token = loginResult.auth.client_token;\n\n const cache: VaultSecretCache = {};\n\n return {\n async fetchSecrets(obj: unknown): Promise<VaultSecretCache> {\n const paths = extractVaultPaths(obj);\n\n // Fetch all unique paths in parallel\n await Promise.all(\n paths.map(async (path) => {\n if (!cache[path]) {\n try {\n const result = await vault.read(path);\n // Vault KV v2 stores data under result.data.data\n // KV v1 stores under result.data\n cache[path] = result.data?.data ?? result.data ?? {};\n } catch {\n // Store empty object for failed paths - will use defaults or throw later\n cache[path] = {};\n }\n }\n }),\n );\n\n return cache;\n },\n\n getSecret(path: string, field: string): unknown {\n return cache[path]?.[field];\n },\n };\n}\n\n/**\n * Extract all unique Vault paths from config object\n */\nfunction extractVaultPaths(obj: unknown): string[] {\n const paths = new Set<string>();\n\n function traverse(value: unknown): void {\n if (typeof value === 'string') {\n let match;\n while ((match = VAULT_PLACEHOLDER_REGEX.exec(value)) !== null) {\n paths.add(match[1]); // path is first capture group\n }\n VAULT_PLACEHOLDER_REGEX.lastIndex = 0; // Reset regex state\n } else if (Array.isArray(value)) {\n value.forEach(traverse);\n } else if (value !== null && typeof value === 'object') {\n Object.values(value).forEach(traverse);\n }\n }\n\n traverse(obj);\n return Array.from(paths);\n}\n","import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';\nimport type { AwsSecretsOptions } from '../types.js';\n\nconst AWS_PLACEHOLDER_REGEX = /\\${aws:([^:}]+)(?::([^:}]+))?(?::([^}]*))?}/g;\n\nexport interface AwsSecretsCache {\n [secretName: string]: string | Record<string, unknown>;\n}\n\nexport interface AwsSecretsClient {\n fetchSecrets(obj: unknown): Promise<AwsSecretsCache>;\n getSecret(secretName: string, field?: string): unknown;\n}\n\nexport async function createAwsSecretsClient(options: AwsSecretsOptions): Promise<AwsSecretsClient> {\n const client = new SecretsManagerClient({\n region: options.region,\n credentials: {\n accessKeyId: options.accessKeyId,\n secretAccessKey: options.secretAccessKey,\n },\n });\n\n const cache: AwsSecretsCache = {};\n\n return {\n async fetchSecrets(obj: unknown): Promise<AwsSecretsCache> {\n const secretNames = extractAwsSecretNames(obj);\n\n // Fetch all unique secrets in parallel\n await Promise.all(\n secretNames.map(async (secretName) => {\n if (cache[secretName] === undefined) {\n try {\n const command = new GetSecretValueCommand({ SecretId: secretName });\n const response = await client.send(command);\n\n const secretString = response.SecretString;\n if (secretString) {\n // Try to parse as JSON, fallback to plain string\n try {\n cache[secretName] = JSON.parse(secretString);\n } catch {\n cache[secretName] = secretString;\n }\n } else {\n cache[secretName] = {};\n }\n } catch {\n // Store empty object for failed secrets - will use defaults or throw later\n cache[secretName] = {};\n }\n }\n }),\n );\n\n return cache;\n },\n\n getSecret(secretName: string, field?: string): unknown {\n const secret = cache[secretName];\n\n if (secret === undefined) {\n return undefined;\n }\n\n // If no field specified, return the whole secret (plain string or parsed JSON)\n if (!field) {\n return typeof secret === 'string' ? secret : undefined;\n }\n\n // If field specified, secret must be an object\n if (typeof secret === 'object' && secret !== null) {\n return (secret as Record<string, unknown>)[field];\n }\n\n return undefined;\n },\n };\n}\n\n/**\n * Extract all unique AWS secret names from config object\n */\nfunction extractAwsSecretNames(obj: unknown): string[] {\n const names = new Set<string>();\n\n function traverse(value: unknown): void {\n if (typeof value === 'string') {\n let match;\n while ((match = AWS_PLACEHOLDER_REGEX.exec(value)) !== null) {\n names.add(match[1]); // secret name is first capture group\n }\n AWS_PLACEHOLDER_REGEX.lastIndex = 0; // Reset regex state\n } else if (Array.isArray(value)) {\n value.forEach(traverse);\n } else if (value !== null && typeof value === 'object') {\n Object.values(value).forEach(traverse);\n }\n }\n\n traverse(obj);\n return Array.from(names);\n}\n","import { readFileSync, existsSync, readdirSync } from 'fs';\nimport * as yaml from 'js-yaml';\nimport * as dotenv from 'dotenv';\nimport { join } from 'path';\nimport type { ConfigLoaderOptions, ConfigLoaderResult } from './types.js';\nimport {\n deepMerge,\n replacePlaceholders,\n maskSecrets,\n stripEmpty,\n createVaultClient,\n createAwsSecretsClient,\n type VaultClient,\n type AwsSecretsClient,\n} from './utils/index.js';\n\nconst DEFAULT_OPTIONS = {\n configDir: process.cwd(),\n baseFileName: 'base',\n environment: process.env.NODE_ENV || 'local',\n extension: 'yml' as const,\n overrideDir: '/etc/app/config',\n};\n\nexport function loadConfig<T>(options: ConfigLoaderOptions<T> = {}): ConfigLoaderResult<T> {\n if (options.vault || options.aws) {\n throw new Error('Vault/AWS options require async loading. Use loadConfigAsync() instead of loadConfig()');\n }\n\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const { configDir, baseFileName, environment, extension } = opts;\n\n // Load .env if requested\n if (options.dotenv) {\n loadDotenv(options.dotenv, environment);\n }\n\n const resolvedConfigDir = resolveConfigDir(configDir);\n\n const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);\n const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);\n\n let mergedConfig = deepMerge(baseConfig, envConfig);\n\n // Load override files from directory\n const overrideDir = options.overrideDir !== false ? (options.overrideDir ?? DEFAULT_OPTIONS.overrideDir) : null;\n if (overrideDir) {\n const overrideConfigs = loadOverrideDir(overrideDir);\n for (const override of overrideConfigs) {\n mergedConfig = deepMerge(mergedConfig, override);\n }\n }\n\n let config = replacePlaceholders(mergedConfig) as T;\n\n // Strip empty strings and objects\n if (options.stripEmpty) {\n config = stripEmpty(config) as T;\n }\n\n // Post-process\n if (options.postProcess) {\n config = options.postProcess(config);\n }\n\n // Validate with Zod schema\n if (options.schema) {\n const result = options.schema.safeParse(config);\n if (!result.success) {\n const errors = result.error.issues\n .map((e) => ` - ${e.path.join('.')}: ${e.message}`)\n .join('\\n');\n throw new Error(`Config validation failed:\\n${errors}`);\n }\n config = result.data;\n }\n\n // Log config with masked secrets\n if (options.logger) {\n const maskedConfig = maskSecrets(config);\n options.logger(`Config loaded (${environment}):\\n${JSON.stringify(maskedConfig, null, 2)}`);\n }\n\n return {\n config,\n environment,\n configDir: resolvedConfigDir,\n };\n}\n\nexport async function loadConfigAsync<T>(\n options: ConfigLoaderOptions<T> = {},\n): Promise<ConfigLoaderResult<T>> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const { configDir, baseFileName, environment, extension } = opts;\n\n // Load .env if requested\n if (options.dotenv) {\n loadDotenv(options.dotenv, environment);\n }\n\n const resolvedConfigDir = resolveConfigDir(configDir);\n\n const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);\n const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);\n\n let mergedConfig = deepMerge(baseConfig, envConfig);\n\n // Load override files from directory\n const overrideDir = options.overrideDir !== false ? (options.overrideDir ?? DEFAULT_OPTIONS.overrideDir) : null;\n if (overrideDir) {\n const overrideConfigs = loadOverrideDir(overrideDir);\n for (const override of overrideConfigs) {\n mergedConfig = deepMerge(mergedConfig, override);\n }\n }\n\n // Create Vault client and pre-fetch secrets if Vault options provided\n let vaultClient: VaultClient | undefined;\n if (options.vault) {\n vaultClient = await createVaultClient(options.vault);\n await vaultClient.fetchSecrets(mergedConfig);\n }\n\n // Create AWS Secrets Manager client and pre-fetch secrets if AWS options provided\n let awsClient: AwsSecretsClient | undefined;\n if (options.aws) {\n awsClient = await createAwsSecretsClient(options.aws);\n await awsClient.fetchSecrets(mergedConfig);\n }\n\n // Replace placeholders (with Vault and AWS support)\n let config = replacePlaceholders(mergedConfig, { vaultClient, awsClient }) as T;\n\n // Strip empty strings and objects\n if (options.stripEmpty) {\n config = stripEmpty(config) as T;\n }\n\n // Post-process\n if (options.postProcess) {\n config = options.postProcess(config);\n }\n\n // Validate with Zod schema\n if (options.schema) {\n const result = options.schema.safeParse(config);\n if (!result.success) {\n const errors = result.error.issues\n .map((e) => ` - ${e.path.join('.')}: ${e.message}`)\n .join('\\n');\n throw new Error(`Config validation failed:\\n${errors}`);\n }\n config = result.data;\n }\n\n // Log config with masked secrets\n if (options.logger) {\n const maskedConfig = maskSecrets(config);\n options.logger(`Config loaded (${environment}):\\n${JSON.stringify(maskedConfig, null, 2)}`);\n }\n\n return {\n config,\n environment,\n configDir: resolvedConfigDir,\n };\n}\n\nfunction loadDotenv(\n dotenvOption: boolean | { path?: string },\n environment: string,\n): void {\n let envPath: string;\n\n if (typeof dotenvOption === 'object' && dotenvOption.path) {\n envPath = dotenvOption.path;\n } else {\n envPath = environment === 'production' ? '.env' : `.env.${environment}`;\n }\n\n dotenv.config({ path: envPath });\n}\n\nfunction resolveConfigDir(configDir: string): string {\n const possiblePaths = [\n join(configDir, 'src', 'config'),\n join(configDir, 'dist', 'config'),\n join(configDir, 'config'),\n configDir,\n ];\n\n for (const path of possiblePaths) {\n if (existsSync(join(path, 'base.yml')) || existsSync(join(path, 'base.yaml'))) {\n return path;\n }\n }\n\n throw new Error(\n `Config files not found. Searched in:\\n${possiblePaths.map((p) => ` - ${p}`).join('\\n')}`,\n );\n}\n\nfunction loadYamlFile(dir: string, filename: string): Record<string, unknown> {\n const filePath = join(dir, filename);\n\n if (!existsSync(filePath)) {\n return {};\n }\n\n const content = readFileSync(filePath, 'utf8');\n return (yaml.load(content) as Record<string, unknown>) || {};\n}\n\nfunction loadOverrideDir(dir: string): Record<string, unknown>[] {\n if (!existsSync(dir)) {\n return [];\n }\n\n const files = readdirSync(dir)\n .filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'))\n .sort();\n\n return files.map((file) => loadYamlFile(dir, file));\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rawnodes/config-loader",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Flexible YAML config loader with environment overrides, Zod validation, and Docker-friendly features",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -52,8 +52,10 @@
52
52
  "node": ">=18"
53
53
  },
54
54
  "dependencies": {
55
+ "@aws-sdk/client-secrets-manager": "^3.964.0",
55
56
  "dotenv": "^16.4.5",
56
- "js-yaml": "^4.1.0"
57
+ "js-yaml": "^4.1.0",
58
+ "node-vault": "^0.10.9"
57
59
  },
58
60
  "peerDependencies": {
59
61
  "zod": "^3.0.0 || ^4.0.0"