@rawnodes/config-loader 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +235 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{types.d.ts → index.d.cts} +16 -5
- package/dist/index.d.ts +74 -4
- package/dist/index.js +208 -2
- package/dist/index.js.map +1 -1
- package/package.json +9 -6
- package/dist/index.d.ts.map +0 -1
- package/dist/loader.d.ts +0 -3
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -100
- package/dist/loader.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/deep-merge.d.ts +0 -4
- package/dist/utils/deep-merge.d.ts.map +0 -1
- package/dist/utils/deep-merge.js +0 -18
- package/dist/utils/deep-merge.js.map +0 -1
- package/dist/utils/env-replacer.d.ts +0 -2
- package/dist/utils/env-replacer.d.ts.map +0 -1
- package/dist/utils/env-replacer.js +0 -42
- package/dist/utils/env-replacer.js.map +0 -1
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -4
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/mask-secrets.d.ts +0 -2
- package/dist/utils/mask-secrets.d.ts.map +0 -1
- package/dist/utils/mask-secrets.js +0 -49
- package/dist/utils/mask-secrets.js.map +0 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var yaml = require('js-yaml');
|
|
5
|
+
var dotenv = require('dotenv');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
|
|
27
|
+
var dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
|
|
28
|
+
|
|
29
|
+
// src/loader.ts
|
|
30
|
+
|
|
31
|
+
// src/utils/deep-merge.ts
|
|
32
|
+
function deepMerge(base, override) {
|
|
33
|
+
const merged = { ...base };
|
|
34
|
+
for (const key in override) {
|
|
35
|
+
const overrideValue = override[key];
|
|
36
|
+
const baseValue = base[key];
|
|
37
|
+
if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {
|
|
38
|
+
merged[key] = deepMerge(baseValue, overrideValue);
|
|
39
|
+
} else {
|
|
40
|
+
merged[key] = overrideValue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return merged;
|
|
44
|
+
}
|
|
45
|
+
function isPlainObject(value) {
|
|
46
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/utils/env-replacer.ts
|
|
50
|
+
var ENV_PLACEHOLDER_REGEX = /\${(.*?)}/g;
|
|
51
|
+
function replacePlaceholders(obj) {
|
|
52
|
+
if (typeof obj === "string") {
|
|
53
|
+
return replaceStringPlaceholders(obj);
|
|
54
|
+
}
|
|
55
|
+
if (Array.isArray(obj)) {
|
|
56
|
+
return obj.map((item) => replacePlaceholders(item));
|
|
57
|
+
}
|
|
58
|
+
if (isPlainObject2(obj)) {
|
|
59
|
+
const result = {};
|
|
60
|
+
for (const key of Object.keys(obj)) {
|
|
61
|
+
result[key] = replacePlaceholders(obj[key]);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
return obj;
|
|
66
|
+
}
|
|
67
|
+
function replaceStringPlaceholders(str) {
|
|
68
|
+
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`);
|
|
79
|
+
}
|
|
80
|
+
if (value.includes("${")) {
|
|
81
|
+
return replacePlaceholders(value);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function isPlainObject2(value) {
|
|
87
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/utils/mask-secrets.ts
|
|
91
|
+
var SECRET_PATTERNS = ["token", "password", "secret", "key", "apikey", "api_key", "credential"];
|
|
92
|
+
var URL_WITH_CREDENTIALS_REGEX = /^([a-z]+:\/\/)([^:]+):([^@]+)@(.+)$/i;
|
|
93
|
+
function maskSecrets(obj) {
|
|
94
|
+
if (typeof obj === "string") {
|
|
95
|
+
return maskUrlCredentials(obj);
|
|
96
|
+
}
|
|
97
|
+
if (Array.isArray(obj)) {
|
|
98
|
+
return obj.map((item) => maskSecrets(item));
|
|
99
|
+
}
|
|
100
|
+
if (isPlainObject3(obj)) {
|
|
101
|
+
const result = {};
|
|
102
|
+
for (const key of Object.keys(obj)) {
|
|
103
|
+
const value = obj[key];
|
|
104
|
+
if (isSecretKey(key)) {
|
|
105
|
+
result[key] = maskValue(value);
|
|
106
|
+
} else {
|
|
107
|
+
result[key] = maskSecrets(value);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
return obj;
|
|
113
|
+
}
|
|
114
|
+
function isSecretKey(key) {
|
|
115
|
+
const lowerKey = key.toLowerCase();
|
|
116
|
+
return SECRET_PATTERNS.some((pattern) => lowerKey.includes(pattern));
|
|
117
|
+
}
|
|
118
|
+
function maskValue(value) {
|
|
119
|
+
if (typeof value !== "string") {
|
|
120
|
+
return "***";
|
|
121
|
+
}
|
|
122
|
+
if (value.length <= 4) {
|
|
123
|
+
return "***";
|
|
124
|
+
}
|
|
125
|
+
return value.slice(0, 2) + "***" + value.slice(-2);
|
|
126
|
+
}
|
|
127
|
+
function maskUrlCredentials(url) {
|
|
128
|
+
const match = url.match(URL_WITH_CREDENTIALS_REGEX);
|
|
129
|
+
if (match) {
|
|
130
|
+
const [, protocol, user, , host] = match;
|
|
131
|
+
return `${protocol}${user}:***@${host}`;
|
|
132
|
+
}
|
|
133
|
+
return url;
|
|
134
|
+
}
|
|
135
|
+
function isPlainObject3(value) {
|
|
136
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/loader.ts
|
|
140
|
+
var DEFAULT_OPTIONS = {
|
|
141
|
+
configDir: process.cwd(),
|
|
142
|
+
baseFileName: "base",
|
|
143
|
+
environment: process.env.NODE_ENV || "local",
|
|
144
|
+
extension: "yml",
|
|
145
|
+
overrideDir: "/etc/app/config"
|
|
146
|
+
};
|
|
147
|
+
function loadConfig(options = {}) {
|
|
148
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
149
|
+
const { configDir, baseFileName, environment, extension } = opts;
|
|
150
|
+
if (options.dotenv) {
|
|
151
|
+
loadDotenv(options.dotenv, environment);
|
|
152
|
+
}
|
|
153
|
+
const resolvedConfigDir = resolveConfigDir(configDir);
|
|
154
|
+
const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);
|
|
155
|
+
const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);
|
|
156
|
+
let mergedConfig = deepMerge(baseConfig, envConfig);
|
|
157
|
+
const overrideDir = options.overrideDir !== false ? options.overrideDir ?? DEFAULT_OPTIONS.overrideDir : null;
|
|
158
|
+
if (overrideDir) {
|
|
159
|
+
const overrideConfigs = loadOverrideDir(overrideDir);
|
|
160
|
+
for (const override of overrideConfigs) {
|
|
161
|
+
mergedConfig = deepMerge(mergedConfig, override);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
let config2 = replacePlaceholders(mergedConfig);
|
|
165
|
+
if (options.postProcess) {
|
|
166
|
+
config2 = options.postProcess(config2);
|
|
167
|
+
}
|
|
168
|
+
if (options.schema) {
|
|
169
|
+
const result = options.schema.safeParse(config2);
|
|
170
|
+
if (!result.success) {
|
|
171
|
+
const errors = result.error.issues.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
|
|
172
|
+
throw new Error(`Config validation failed:
|
|
173
|
+
${errors}`);
|
|
174
|
+
}
|
|
175
|
+
config2 = result.data;
|
|
176
|
+
}
|
|
177
|
+
if (options.logger) {
|
|
178
|
+
const maskedConfig = maskSecrets(config2);
|
|
179
|
+
options.logger(`Config loaded (${environment}):
|
|
180
|
+
${JSON.stringify(maskedConfig, null, 2)}`);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
config: config2,
|
|
184
|
+
environment,
|
|
185
|
+
configDir: resolvedConfigDir
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function loadDotenv(dotenvOption, environment) {
|
|
189
|
+
let envPath;
|
|
190
|
+
if (typeof dotenvOption === "object" && dotenvOption.path) {
|
|
191
|
+
envPath = dotenvOption.path;
|
|
192
|
+
} else {
|
|
193
|
+
envPath = environment === "production" ? ".env" : `.env.${environment}`;
|
|
194
|
+
}
|
|
195
|
+
dotenv__namespace.config({ path: envPath });
|
|
196
|
+
}
|
|
197
|
+
function resolveConfigDir(configDir) {
|
|
198
|
+
const possiblePaths = [
|
|
199
|
+
path.join(configDir, "src", "config"),
|
|
200
|
+
path.join(configDir, "dist", "config"),
|
|
201
|
+
path.join(configDir, "config"),
|
|
202
|
+
configDir
|
|
203
|
+
];
|
|
204
|
+
for (const path$1 of possiblePaths) {
|
|
205
|
+
if (fs.existsSync(path.join(path$1, "base.yml")) || fs.existsSync(path.join(path$1, "base.yaml"))) {
|
|
206
|
+
return path$1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Config files not found. Searched in:
|
|
211
|
+
${possiblePaths.map((p) => ` - ${p}`).join("\n")}`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
function loadYamlFile(dir, filename) {
|
|
215
|
+
const filePath = path.join(dir, filename);
|
|
216
|
+
if (!fs.existsSync(filePath)) {
|
|
217
|
+
return {};
|
|
218
|
+
}
|
|
219
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
220
|
+
return yaml__namespace.load(content) || {};
|
|
221
|
+
}
|
|
222
|
+
function loadOverrideDir(dir) {
|
|
223
|
+
if (!fs.existsSync(dir)) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")).sort();
|
|
227
|
+
return files.map((file) => loadYamlFile(dir, file));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
exports.deepMerge = deepMerge;
|
|
231
|
+
exports.loadConfig = loadConfig;
|
|
232
|
+
exports.maskSecrets = maskSecrets;
|
|
233
|
+
exports.replacePlaceholders = replacePlaceholders;
|
|
234
|
+
//# sourceMappingURL=index.cjs.map
|
|
235
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/deep-merge.ts","../src/utils/env-replacer.ts","../src/utils/mask-secrets.ts","../src/loader.ts"],"names":["isPlainObject","config","dotenv","join","path","existsSync","readFileSync","yaml","readdirSync"],"mappings":"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","file":"index.cjs","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","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 } 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 // 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,12 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
interface DotenvOptions {
|
|
3
4
|
/**
|
|
4
5
|
* Path to .env file
|
|
5
6
|
* @default auto-detected based on NODE_ENV
|
|
6
7
|
*/
|
|
7
8
|
path?: string;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
+
interface ConfigLoaderOptions<T = unknown> {
|
|
10
11
|
/**
|
|
11
12
|
* Directory where config files are located
|
|
12
13
|
* @default process.cwd()
|
|
@@ -55,9 +56,19 @@ export interface ConfigLoaderOptions<T = unknown> {
|
|
|
55
56
|
*/
|
|
56
57
|
overrideDir?: string | false;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
interface ConfigLoaderResult<T> {
|
|
59
60
|
config: T;
|
|
60
61
|
environment: string;
|
|
61
62
|
configDir: string;
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
+
|
|
65
|
+
declare function loadConfig<T>(options?: ConfigLoaderOptions<T>): ConfigLoaderResult<T>;
|
|
66
|
+
|
|
67
|
+
type DeepObject = Record<string, unknown>;
|
|
68
|
+
declare function deepMerge(base: DeepObject, override: DeepObject): DeepObject;
|
|
69
|
+
|
|
70
|
+
declare function replacePlaceholders(obj: unknown): unknown;
|
|
71
|
+
|
|
72
|
+
declare function maskSecrets(obj: unknown): unknown;
|
|
73
|
+
|
|
74
|
+
export { type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, deepMerge, loadConfig, maskSecrets, replacePlaceholders };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
interface DotenvOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Path to .env file
|
|
6
|
+
* @default auto-detected based on NODE_ENV
|
|
7
|
+
*/
|
|
8
|
+
path?: string;
|
|
9
|
+
}
|
|
10
|
+
interface ConfigLoaderOptions<T = unknown> {
|
|
11
|
+
/**
|
|
12
|
+
* Directory where config files are located
|
|
13
|
+
* @default process.cwd()
|
|
14
|
+
*/
|
|
15
|
+
configDir?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Base config filename (without extension)
|
|
18
|
+
* @default 'base'
|
|
19
|
+
*/
|
|
20
|
+
baseFileName?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Environment name for environment-specific config
|
|
23
|
+
* @default process.env.NODE_ENV || 'local'
|
|
24
|
+
*/
|
|
25
|
+
environment?: string;
|
|
26
|
+
/**
|
|
27
|
+
* File extension
|
|
28
|
+
* @default 'yml'
|
|
29
|
+
*/
|
|
30
|
+
extension?: 'yml' | 'yaml';
|
|
31
|
+
/**
|
|
32
|
+
* Custom post-processing function to transform the loaded config
|
|
33
|
+
*/
|
|
34
|
+
postProcess?: (config: T) => T;
|
|
35
|
+
/**
|
|
36
|
+
* Zod schema for validation
|
|
37
|
+
* If provided, config will be validated against this schema
|
|
38
|
+
*/
|
|
39
|
+
schema?: z.ZodType<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Logger callback for logging loaded config
|
|
42
|
+
* Config will be logged with secrets masked
|
|
43
|
+
*/
|
|
44
|
+
logger?: (message: string) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Load .env file before loading config
|
|
47
|
+
* - true: auto-detect .env file based on NODE_ENV
|
|
48
|
+
* - object: custom options
|
|
49
|
+
*/
|
|
50
|
+
dotenv?: boolean | DotenvOptions;
|
|
51
|
+
/**
|
|
52
|
+
* Directory with additional YAML files to merge
|
|
53
|
+
* All *.yml files in this directory will be merged (sorted by filename)
|
|
54
|
+
* Useful for Docker/Kubernetes config mounts
|
|
55
|
+
* @default '/etc/app/config'
|
|
56
|
+
*/
|
|
57
|
+
overrideDir?: string | false;
|
|
58
|
+
}
|
|
59
|
+
interface ConfigLoaderResult<T> {
|
|
60
|
+
config: T;
|
|
61
|
+
environment: string;
|
|
62
|
+
configDir: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
declare function loadConfig<T>(options?: ConfigLoaderOptions<T>): ConfigLoaderResult<T>;
|
|
66
|
+
|
|
67
|
+
type DeepObject = Record<string, unknown>;
|
|
68
|
+
declare function deepMerge(base: DeepObject, override: DeepObject): DeepObject;
|
|
69
|
+
|
|
70
|
+
declare function replacePlaceholders(obj: unknown): unknown;
|
|
71
|
+
|
|
72
|
+
declare function maskSecrets(obj: unknown): unknown;
|
|
73
|
+
|
|
74
|
+
export { type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, deepMerge, loadConfig, maskSecrets, replacePlaceholders };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,209 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as dotenv from 'dotenv';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
|
|
6
|
+
// src/loader.ts
|
|
7
|
+
|
|
8
|
+
// src/utils/deep-merge.ts
|
|
9
|
+
function deepMerge(base, override) {
|
|
10
|
+
const merged = { ...base };
|
|
11
|
+
for (const key in override) {
|
|
12
|
+
const overrideValue = override[key];
|
|
13
|
+
const baseValue = base[key];
|
|
14
|
+
if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {
|
|
15
|
+
merged[key] = deepMerge(baseValue, overrideValue);
|
|
16
|
+
} else {
|
|
17
|
+
merged[key] = overrideValue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return merged;
|
|
21
|
+
}
|
|
22
|
+
function isPlainObject(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/utils/env-replacer.ts
|
|
27
|
+
var ENV_PLACEHOLDER_REGEX = /\${(.*?)}/g;
|
|
28
|
+
function replacePlaceholders(obj) {
|
|
29
|
+
if (typeof obj === "string") {
|
|
30
|
+
return replaceStringPlaceholders(obj);
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(obj)) {
|
|
33
|
+
return obj.map((item) => replacePlaceholders(item));
|
|
34
|
+
}
|
|
35
|
+
if (isPlainObject2(obj)) {
|
|
36
|
+
const result = {};
|
|
37
|
+
for (const key of Object.keys(obj)) {
|
|
38
|
+
result[key] = replacePlaceholders(obj[key]);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
return obj;
|
|
43
|
+
}
|
|
44
|
+
function replaceStringPlaceholders(str) {
|
|
45
|
+
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`);
|
|
56
|
+
}
|
|
57
|
+
if (value.includes("${")) {
|
|
58
|
+
return replacePlaceholders(value);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function isPlainObject2(value) {
|
|
64
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/utils/mask-secrets.ts
|
|
68
|
+
var SECRET_PATTERNS = ["token", "password", "secret", "key", "apikey", "api_key", "credential"];
|
|
69
|
+
var URL_WITH_CREDENTIALS_REGEX = /^([a-z]+:\/\/)([^:]+):([^@]+)@(.+)$/i;
|
|
70
|
+
function maskSecrets(obj) {
|
|
71
|
+
if (typeof obj === "string") {
|
|
72
|
+
return maskUrlCredentials(obj);
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(obj)) {
|
|
75
|
+
return obj.map((item) => maskSecrets(item));
|
|
76
|
+
}
|
|
77
|
+
if (isPlainObject3(obj)) {
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const key of Object.keys(obj)) {
|
|
80
|
+
const value = obj[key];
|
|
81
|
+
if (isSecretKey(key)) {
|
|
82
|
+
result[key] = maskValue(value);
|
|
83
|
+
} else {
|
|
84
|
+
result[key] = maskSecrets(value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
return obj;
|
|
90
|
+
}
|
|
91
|
+
function isSecretKey(key) {
|
|
92
|
+
const lowerKey = key.toLowerCase();
|
|
93
|
+
return SECRET_PATTERNS.some((pattern) => lowerKey.includes(pattern));
|
|
94
|
+
}
|
|
95
|
+
function maskValue(value) {
|
|
96
|
+
if (typeof value !== "string") {
|
|
97
|
+
return "***";
|
|
98
|
+
}
|
|
99
|
+
if (value.length <= 4) {
|
|
100
|
+
return "***";
|
|
101
|
+
}
|
|
102
|
+
return value.slice(0, 2) + "***" + value.slice(-2);
|
|
103
|
+
}
|
|
104
|
+
function maskUrlCredentials(url) {
|
|
105
|
+
const match = url.match(URL_WITH_CREDENTIALS_REGEX);
|
|
106
|
+
if (match) {
|
|
107
|
+
const [, protocol, user, , host] = match;
|
|
108
|
+
return `${protocol}${user}:***@${host}`;
|
|
109
|
+
}
|
|
110
|
+
return url;
|
|
111
|
+
}
|
|
112
|
+
function isPlainObject3(value) {
|
|
113
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/loader.ts
|
|
117
|
+
var DEFAULT_OPTIONS = {
|
|
118
|
+
configDir: process.cwd(),
|
|
119
|
+
baseFileName: "base",
|
|
120
|
+
environment: process.env.NODE_ENV || "local",
|
|
121
|
+
extension: "yml",
|
|
122
|
+
overrideDir: "/etc/app/config"
|
|
123
|
+
};
|
|
124
|
+
function loadConfig(options = {}) {
|
|
125
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
126
|
+
const { configDir, baseFileName, environment, extension } = opts;
|
|
127
|
+
if (options.dotenv) {
|
|
128
|
+
loadDotenv(options.dotenv, environment);
|
|
129
|
+
}
|
|
130
|
+
const resolvedConfigDir = resolveConfigDir(configDir);
|
|
131
|
+
const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);
|
|
132
|
+
const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);
|
|
133
|
+
let mergedConfig = deepMerge(baseConfig, envConfig);
|
|
134
|
+
const overrideDir = options.overrideDir !== false ? options.overrideDir ?? DEFAULT_OPTIONS.overrideDir : null;
|
|
135
|
+
if (overrideDir) {
|
|
136
|
+
const overrideConfigs = loadOverrideDir(overrideDir);
|
|
137
|
+
for (const override of overrideConfigs) {
|
|
138
|
+
mergedConfig = deepMerge(mergedConfig, override);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
let config2 = replacePlaceholders(mergedConfig);
|
|
142
|
+
if (options.postProcess) {
|
|
143
|
+
config2 = options.postProcess(config2);
|
|
144
|
+
}
|
|
145
|
+
if (options.schema) {
|
|
146
|
+
const result = options.schema.safeParse(config2);
|
|
147
|
+
if (!result.success) {
|
|
148
|
+
const errors = result.error.issues.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
|
|
149
|
+
throw new Error(`Config validation failed:
|
|
150
|
+
${errors}`);
|
|
151
|
+
}
|
|
152
|
+
config2 = result.data;
|
|
153
|
+
}
|
|
154
|
+
if (options.logger) {
|
|
155
|
+
const maskedConfig = maskSecrets(config2);
|
|
156
|
+
options.logger(`Config loaded (${environment}):
|
|
157
|
+
${JSON.stringify(maskedConfig, null, 2)}`);
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
config: config2,
|
|
161
|
+
environment,
|
|
162
|
+
configDir: resolvedConfigDir
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function loadDotenv(dotenvOption, environment) {
|
|
166
|
+
let envPath;
|
|
167
|
+
if (typeof dotenvOption === "object" && dotenvOption.path) {
|
|
168
|
+
envPath = dotenvOption.path;
|
|
169
|
+
} else {
|
|
170
|
+
envPath = environment === "production" ? ".env" : `.env.${environment}`;
|
|
171
|
+
}
|
|
172
|
+
dotenv.config({ path: envPath });
|
|
173
|
+
}
|
|
174
|
+
function resolveConfigDir(configDir) {
|
|
175
|
+
const possiblePaths = [
|
|
176
|
+
join(configDir, "src", "config"),
|
|
177
|
+
join(configDir, "dist", "config"),
|
|
178
|
+
join(configDir, "config"),
|
|
179
|
+
configDir
|
|
180
|
+
];
|
|
181
|
+
for (const path of possiblePaths) {
|
|
182
|
+
if (existsSync(join(path, "base.yml")) || existsSync(join(path, "base.yaml"))) {
|
|
183
|
+
return path;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Config files not found. Searched in:
|
|
188
|
+
${possiblePaths.map((p) => ` - ${p}`).join("\n")}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function loadYamlFile(dir, filename) {
|
|
192
|
+
const filePath = join(dir, filename);
|
|
193
|
+
if (!existsSync(filePath)) {
|
|
194
|
+
return {};
|
|
195
|
+
}
|
|
196
|
+
const content = readFileSync(filePath, "utf8");
|
|
197
|
+
return yaml.load(content) || {};
|
|
198
|
+
}
|
|
199
|
+
function loadOverrideDir(dir) {
|
|
200
|
+
if (!existsSync(dir)) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")).sort();
|
|
204
|
+
return files.map((file) => loadYamlFile(dir, file));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export { deepMerge, loadConfig, maskSecrets, replacePlaceholders };
|
|
208
|
+
//# sourceMappingURL=index.js.map
|
|
3
209
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"sources":["../src/utils/deep-merge.ts","../src/utils/env-replacer.ts","../src/utils/mask-secrets.ts","../src/loader.ts"],"names":["isPlainObject","config"],"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","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 } 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 // 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,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawnodes/config-loader",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Flexible YAML config loader with environment overrides, Zod validation, and Docker-friendly features",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.js"
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
"files": [
|
|
@@ -17,8 +19,8 @@
|
|
|
17
19
|
"LICENSE"
|
|
18
20
|
],
|
|
19
21
|
"scripts": {
|
|
20
|
-
"build": "
|
|
21
|
-
"dev": "
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
22
24
|
"test": "vitest run",
|
|
23
25
|
"test:watch": "vitest",
|
|
24
26
|
"test:coverage": "vitest run --coverage",
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
"js-yaml": "^4.1.0"
|
|
51
53
|
},
|
|
52
54
|
"peerDependencies": {
|
|
53
|
-
"zod": "^3.0.0"
|
|
55
|
+
"zod": "^3.0.0 || ^4.0.0"
|
|
54
56
|
},
|
|
55
57
|
"peerDependenciesMeta": {
|
|
56
58
|
"zod": {
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
"devDependencies": {
|
|
61
63
|
"@types/js-yaml": "^4.0.9",
|
|
62
64
|
"@types/node": "^22.0.0",
|
|
65
|
+
"tsup": "^8.0.0",
|
|
63
66
|
"typescript": "^5.7.0",
|
|
64
67
|
"vitest": "^2.0.0",
|
|
65
68
|
"zod": "^3.24.0"
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/E,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
package/dist/loader.d.ts
DELETED
package/dist/loader.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAW1E,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAE,mBAAmB,CAAC,CAAC,CAAM,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAuDzF"}
|
package/dist/loader.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
2
|
-
import * as yaml from 'js-yaml';
|
|
3
|
-
import * as dotenv from 'dotenv';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { deepMerge, replacePlaceholders, maskSecrets } from './utils/index.js';
|
|
6
|
-
const DEFAULT_OPTIONS = {
|
|
7
|
-
configDir: process.cwd(),
|
|
8
|
-
baseFileName: 'base',
|
|
9
|
-
environment: process.env.NODE_ENV || 'local',
|
|
10
|
-
extension: 'yml',
|
|
11
|
-
overrideDir: '/etc/app/config',
|
|
12
|
-
};
|
|
13
|
-
export function loadConfig(options = {}) {
|
|
14
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
15
|
-
const { configDir, baseFileName, environment, extension } = opts;
|
|
16
|
-
// Load .env if requested
|
|
17
|
-
if (options.dotenv) {
|
|
18
|
-
loadDotenv(options.dotenv, environment);
|
|
19
|
-
}
|
|
20
|
-
const resolvedConfigDir = resolveConfigDir(configDir);
|
|
21
|
-
const baseConfig = loadYamlFile(resolvedConfigDir, `${baseFileName}.${extension}`);
|
|
22
|
-
const envConfig = loadYamlFile(resolvedConfigDir, `${environment}.${extension}`);
|
|
23
|
-
let mergedConfig = deepMerge(baseConfig, envConfig);
|
|
24
|
-
// Load override files from directory
|
|
25
|
-
const overrideDir = options.overrideDir !== false ? (options.overrideDir ?? DEFAULT_OPTIONS.overrideDir) : null;
|
|
26
|
-
if (overrideDir) {
|
|
27
|
-
const overrideConfigs = loadOverrideDir(overrideDir);
|
|
28
|
-
for (const override of overrideConfigs) {
|
|
29
|
-
mergedConfig = deepMerge(mergedConfig, override);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
let config = replacePlaceholders(mergedConfig);
|
|
33
|
-
// Post-process
|
|
34
|
-
if (options.postProcess) {
|
|
35
|
-
config = options.postProcess(config);
|
|
36
|
-
}
|
|
37
|
-
// Validate with Zod schema
|
|
38
|
-
if (options.schema) {
|
|
39
|
-
const result = options.schema.safeParse(config);
|
|
40
|
-
if (!result.success) {
|
|
41
|
-
const errors = result.error.issues
|
|
42
|
-
.map((e) => ` - ${e.path.join('.')}: ${e.message}`)
|
|
43
|
-
.join('\n');
|
|
44
|
-
throw new Error(`Config validation failed:\n${errors}`);
|
|
45
|
-
}
|
|
46
|
-
config = result.data;
|
|
47
|
-
}
|
|
48
|
-
// Log config with masked secrets
|
|
49
|
-
if (options.logger) {
|
|
50
|
-
const maskedConfig = maskSecrets(config);
|
|
51
|
-
options.logger(`Config loaded (${environment}):\n${JSON.stringify(maskedConfig, null, 2)}`);
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
config,
|
|
55
|
-
environment,
|
|
56
|
-
configDir: resolvedConfigDir,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function loadDotenv(dotenvOption, environment) {
|
|
60
|
-
let envPath;
|
|
61
|
-
if (typeof dotenvOption === 'object' && dotenvOption.path) {
|
|
62
|
-
envPath = dotenvOption.path;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
envPath = environment === 'production' ? '.env' : `.env.${environment}`;
|
|
66
|
-
}
|
|
67
|
-
dotenv.config({ path: envPath });
|
|
68
|
-
}
|
|
69
|
-
function resolveConfigDir(configDir) {
|
|
70
|
-
const possiblePaths = [
|
|
71
|
-
join(configDir, 'src', 'config'),
|
|
72
|
-
join(configDir, 'dist', 'config'),
|
|
73
|
-
join(configDir, 'config'),
|
|
74
|
-
configDir,
|
|
75
|
-
];
|
|
76
|
-
for (const path of possiblePaths) {
|
|
77
|
-
if (existsSync(join(path, 'base.yml')) || existsSync(join(path, 'base.yaml'))) {
|
|
78
|
-
return path;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
throw new Error(`Config files not found. Searched in:\n${possiblePaths.map((p) => ` - ${p}`).join('\n')}`);
|
|
82
|
-
}
|
|
83
|
-
function loadYamlFile(dir, filename) {
|
|
84
|
-
const filePath = join(dir, filename);
|
|
85
|
-
if (!existsSync(filePath)) {
|
|
86
|
-
return {};
|
|
87
|
-
}
|
|
88
|
-
const content = readFileSync(filePath, 'utf8');
|
|
89
|
-
return yaml.load(content) || {};
|
|
90
|
-
}
|
|
91
|
-
function loadOverrideDir(dir) {
|
|
92
|
-
if (!existsSync(dir)) {
|
|
93
|
-
return [];
|
|
94
|
-
}
|
|
95
|
-
const files = readdirSync(dir)
|
|
96
|
-
.filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'))
|
|
97
|
-
.sort();
|
|
98
|
-
return files.map((file) => loadYamlFile(dir, file));
|
|
99
|
-
}
|
|
100
|
-
//# sourceMappingURL=loader.js.map
|
package/dist/loader.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,OAAO;IAC9C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAE3B;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IAE/B;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAEtB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAEjC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,CAAC;IACV,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/types.js
DELETED
package/dist/types.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deep-merge.d.ts","sourceRoot":"","sources":["../../src/utils/deep-merge.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,CAe5E"}
|
package/dist/utils/deep-merge.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export function deepMerge(base, override) {
|
|
2
|
-
const merged = { ...base };
|
|
3
|
-
for (const key in override) {
|
|
4
|
-
const overrideValue = override[key];
|
|
5
|
-
const baseValue = base[key];
|
|
6
|
-
if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {
|
|
7
|
-
merged[key] = deepMerge(baseValue, overrideValue);
|
|
8
|
-
}
|
|
9
|
-
else {
|
|
10
|
-
merged[key] = overrideValue;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return merged;
|
|
14
|
-
}
|
|
15
|
-
function isPlainObject(value) {
|
|
16
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=deep-merge.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deep-merge.js","sourceRoot":"","sources":["../../src/utils/deep-merge.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CAAC,IAAgB,EAAE,QAAoB;IAC9D,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAE3B,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QAE5B,IAAI,aAAa,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7D,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,SAAuB,EAAE,aAA2B,CAAC,CAAC;QAChF,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"env-replacer.d.ts","sourceRoot":"","sources":["../../src/utils/env-replacer.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAkBzD"}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
const ENV_PLACEHOLDER_REGEX = /\${(.*?)}/g;
|
|
2
|
-
export function replacePlaceholders(obj) {
|
|
3
|
-
if (typeof obj === 'string') {
|
|
4
|
-
return replaceStringPlaceholders(obj);
|
|
5
|
-
}
|
|
6
|
-
if (Array.isArray(obj)) {
|
|
7
|
-
return obj.map((item) => replacePlaceholders(item));
|
|
8
|
-
}
|
|
9
|
-
if (isPlainObject(obj)) {
|
|
10
|
-
const result = {};
|
|
11
|
-
for (const key of Object.keys(obj)) {
|
|
12
|
-
result[key] = replacePlaceholders(obj[key]);
|
|
13
|
-
}
|
|
14
|
-
return result;
|
|
15
|
-
}
|
|
16
|
-
return obj;
|
|
17
|
-
}
|
|
18
|
-
function replaceStringPlaceholders(str) {
|
|
19
|
-
return str.replace(ENV_PLACEHOLDER_REGEX, (match, key) => {
|
|
20
|
-
const [envKey, ...rest] = key.split(':');
|
|
21
|
-
const defaultValue = rest.length > 0 ? rest.join(':') : undefined;
|
|
22
|
-
const envValue = process.env[envKey];
|
|
23
|
-
let value;
|
|
24
|
-
if (envValue !== undefined) {
|
|
25
|
-
value = envValue;
|
|
26
|
-
}
|
|
27
|
-
else if (defaultValue !== undefined) {
|
|
28
|
-
value = defaultValue;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
throw new Error(`Environment variable "${envKey}" is not defined and no default value provided`);
|
|
32
|
-
}
|
|
33
|
-
if (value.includes('${')) {
|
|
34
|
-
return replacePlaceholders(value);
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
function isPlainObject(value) {
|
|
40
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=env-replacer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"env-replacer.js","sourceRoot":"","sources":["../../src/utils/env-replacer.ts"],"names":[],"mappings":"AAAA,MAAM,qBAAqB,GAAG,YAAY,CAAC;AAE3C,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,yBAAyB,CAAC,GAAG,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,GAAG,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,yBAAyB,CAAC,GAAW;IAC5C,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvD,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAClE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAErC,IAAI,KAAa,CAAC;QAClB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,KAAK,GAAG,QAAQ,CAAC;QACnB,CAAC;aAAM,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YACtC,KAAK,GAAG,YAAY,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,gDAAgD,CAAC,CAAC;QACnG,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,mBAAmB,CAAC,KAAK,CAAW,CAAC;QAC9C,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
|
package/dist/utils/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/utils/index.js
DELETED
package/dist/utils/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mask-secrets.d.ts","sourceRoot":"","sources":["../../src/utils/mask-secrets.ts"],"names":[],"mappings":"AAIA,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAuBjD"}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
const SECRET_PATTERNS = ['token', 'password', 'secret', 'key', 'apikey', 'api_key', 'credential'];
|
|
2
|
-
const URL_WITH_CREDENTIALS_REGEX = /^([a-z]+:\/\/)([^:]+):([^@]+)@(.+)$/i;
|
|
3
|
-
export function maskSecrets(obj) {
|
|
4
|
-
if (typeof obj === 'string') {
|
|
5
|
-
return maskUrlCredentials(obj);
|
|
6
|
-
}
|
|
7
|
-
if (Array.isArray(obj)) {
|
|
8
|
-
return obj.map((item) => maskSecrets(item));
|
|
9
|
-
}
|
|
10
|
-
if (isPlainObject(obj)) {
|
|
11
|
-
const result = {};
|
|
12
|
-
for (const key of Object.keys(obj)) {
|
|
13
|
-
const value = obj[key];
|
|
14
|
-
if (isSecretKey(key)) {
|
|
15
|
-
result[key] = maskValue(value);
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
result[key] = maskSecrets(value);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return result;
|
|
22
|
-
}
|
|
23
|
-
return obj;
|
|
24
|
-
}
|
|
25
|
-
function isSecretKey(key) {
|
|
26
|
-
const lowerKey = key.toLowerCase();
|
|
27
|
-
return SECRET_PATTERNS.some((pattern) => lowerKey.includes(pattern));
|
|
28
|
-
}
|
|
29
|
-
function maskValue(value) {
|
|
30
|
-
if (typeof value !== 'string') {
|
|
31
|
-
return '***';
|
|
32
|
-
}
|
|
33
|
-
if (value.length <= 4) {
|
|
34
|
-
return '***';
|
|
35
|
-
}
|
|
36
|
-
return value.slice(0, 2) + '***' + value.slice(-2);
|
|
37
|
-
}
|
|
38
|
-
function maskUrlCredentials(url) {
|
|
39
|
-
const match = url.match(URL_WITH_CREDENTIALS_REGEX);
|
|
40
|
-
if (match) {
|
|
41
|
-
const [, protocol, user, , host] = match;
|
|
42
|
-
return `${protocol}${user}:***@${host}`;
|
|
43
|
-
}
|
|
44
|
-
return url;
|
|
45
|
-
}
|
|
46
|
-
function isPlainObject(value) {
|
|
47
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=mask-secrets.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mask-secrets.js","sourceRoot":"","sources":["../../src/utils/mask-secrets.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AAElG,MAAM,0BAA0B,GAAG,sCAAsC,CAAC;AAE1E,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACvB,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAW;IACrC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACpD,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,AAAD,EAAG,IAAI,CAAC,GAAG,KAAK,CAAC;QACzC,OAAO,GAAG,QAAQ,GAAG,IAAI,QAAQ,IAAI,EAAE,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
|