@theunwalked/cardigantime 0.0.7 → 0.0.8
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/cardigantime.cjs +153 -8
- package/dist/cardigantime.cjs.map +1 -1
- package/dist/cardigantime.d.ts +11 -0
- package/dist/cardigantime.js +11 -0
- package/dist/cardigantime.js.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/read.js +73 -3
- package/dist/read.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.js.map +1 -1
- package/dist/util/hierarchical.d.ts +7 -1
- package/dist/util/hierarchical.js +67 -4
- package/dist/util/hierarchical.js.map +1 -1
- package/package.json +1 -1
package/dist/cardigantime.cjs
CHANGED
|
@@ -187,7 +187,8 @@ class ArgumentError extends Error {
|
|
|
187
187
|
*/ const DEFAULT_OPTIONS = {
|
|
188
188
|
configFile: DEFAULT_CONFIG_FILE,
|
|
189
189
|
isRequired: false,
|
|
190
|
-
encoding: DEFAULT_ENCODING
|
|
190
|
+
encoding: DEFAULT_ENCODING,
|
|
191
|
+
pathResolution: undefined
|
|
191
192
|
};
|
|
192
193
|
/**
|
|
193
194
|
* Default features enabled when creating a Cardigantime instance.
|
|
@@ -415,6 +416,62 @@ const create$1 = (params)=>{
|
|
|
415
416
|
};
|
|
416
417
|
};
|
|
417
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Resolves relative paths in configuration values relative to the configuration file's directory.
|
|
421
|
+
*/ function resolveConfigPaths$1(config, configDir, pathFields = [], resolvePathArray = []) {
|
|
422
|
+
if (!config || typeof config !== 'object' || pathFields.length === 0) {
|
|
423
|
+
return config;
|
|
424
|
+
}
|
|
425
|
+
const resolvedConfig = {
|
|
426
|
+
...config
|
|
427
|
+
};
|
|
428
|
+
for (const fieldPath of pathFields){
|
|
429
|
+
const value = getNestedValue$1(resolvedConfig, fieldPath);
|
|
430
|
+
if (value !== undefined) {
|
|
431
|
+
const shouldResolveArrayElements = resolvePathArray.includes(fieldPath);
|
|
432
|
+
const resolvedValue = resolvePathValue$1(value, configDir, shouldResolveArrayElements);
|
|
433
|
+
setNestedValue$1(resolvedConfig, fieldPath, resolvedValue);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return resolvedConfig;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Gets a nested value from an object using dot notation.
|
|
440
|
+
*/ function getNestedValue$1(obj, path) {
|
|
441
|
+
return path.split('.').reduce((current, key)=>current === null || current === void 0 ? void 0 : current[key], obj);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Sets a nested value in an object using dot notation.
|
|
445
|
+
*/ function setNestedValue$1(obj, path, value) {
|
|
446
|
+
const keys = path.split('.');
|
|
447
|
+
const lastKey = keys.pop();
|
|
448
|
+
const target = keys.reduce((current, key)=>{
|
|
449
|
+
if (!(key in current)) {
|
|
450
|
+
current[key] = {};
|
|
451
|
+
}
|
|
452
|
+
return current[key];
|
|
453
|
+
}, obj);
|
|
454
|
+
target[lastKey] = value;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Resolves a path value (string or array of strings) relative to the config directory.
|
|
458
|
+
*/ function resolvePathValue$1(value, configDir, resolveArrayElements) {
|
|
459
|
+
if (typeof value === 'string') {
|
|
460
|
+
return resolveSinglePath$1(value, configDir);
|
|
461
|
+
}
|
|
462
|
+
if (Array.isArray(value) && resolveArrayElements) {
|
|
463
|
+
return value.map((item)=>typeof item === 'string' ? resolveSinglePath$1(item, configDir) : item);
|
|
464
|
+
}
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Resolves a single path string relative to the config directory if it's a relative path.
|
|
469
|
+
*/ function resolveSinglePath$1(pathStr, configDir) {
|
|
470
|
+
if (!pathStr || path.isAbsolute(pathStr)) {
|
|
471
|
+
return pathStr;
|
|
472
|
+
}
|
|
473
|
+
return path.resolve(configDir, pathStr);
|
|
474
|
+
}
|
|
418
475
|
/**
|
|
419
476
|
* Discovers configuration directories by traversing up the directory tree.
|
|
420
477
|
*
|
|
@@ -493,8 +550,10 @@ const create$1 = (params)=>{
|
|
|
493
550
|
* @param configFileName Name of the configuration file
|
|
494
551
|
* @param encoding File encoding
|
|
495
552
|
* @param logger Optional logger
|
|
553
|
+
* @param pathFields Optional array of field names that contain paths to be resolved
|
|
554
|
+
* @param resolvePathArray Optional array of field names whose array elements should all be resolved as paths
|
|
496
555
|
* @returns Promise resolving to parsed configuration object or null if not found
|
|
497
|
-
*/ async function loadConfigFromDirectory(configDir, configFileName, encoding = 'utf8', logger) {
|
|
556
|
+
*/ async function loadConfigFromDirectory(configDir, configFileName, encoding = 'utf8', logger, pathFields, resolvePathArray) {
|
|
498
557
|
const storage = create$1({
|
|
499
558
|
log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
|
|
500
559
|
});
|
|
@@ -514,8 +573,13 @@ const create$1 = (params)=>{
|
|
|
514
573
|
const yamlContent = await storage.readFile(configFilePath, encoding);
|
|
515
574
|
const parsedYaml = yaml__namespace.load(yamlContent);
|
|
516
575
|
if (parsedYaml !== null && typeof parsedYaml === 'object') {
|
|
576
|
+
let config = parsedYaml;
|
|
577
|
+
// Apply path resolution if configured
|
|
578
|
+
if (pathFields && pathFields.length > 0) {
|
|
579
|
+
config = resolveConfigPaths$1(config, configDir, pathFields, resolvePathArray || []);
|
|
580
|
+
}
|
|
517
581
|
logger === null || logger === void 0 ? void 0 : logger.verbose(`Successfully loaded config from: ${configFilePath}`);
|
|
518
|
-
return
|
|
582
|
+
return config;
|
|
519
583
|
} else {
|
|
520
584
|
logger === null || logger === void 0 ? void 0 : logger.debug(`Config file contains invalid format: ${configFilePath}`);
|
|
521
585
|
return null;
|
|
@@ -622,7 +686,7 @@ const create$1 = (params)=>{
|
|
|
622
686
|
* // result.errors contains any non-fatal errors
|
|
623
687
|
* ```
|
|
624
688
|
*/ async function loadHierarchicalConfig(options) {
|
|
625
|
-
const { configFileName, encoding = 'utf8', logger } = options;
|
|
689
|
+
const { configFileName, encoding = 'utf8', logger, pathFields, resolvePathArray } = options;
|
|
626
690
|
logger === null || logger === void 0 ? void 0 : logger.verbose('Starting hierarchical configuration loading');
|
|
627
691
|
// Discover all configuration directories
|
|
628
692
|
const discoveredDirs = await discoverConfigDirectories(options);
|
|
@@ -643,7 +707,7 @@ const create$1 = (params)=>{
|
|
|
643
707
|
].sort((a, b)=>b.level - a.level);
|
|
644
708
|
for (const dir of sortedDirs){
|
|
645
709
|
try {
|
|
646
|
-
const config = await loadConfigFromDirectory(dir.path, configFileName, encoding, logger);
|
|
710
|
+
const config = await loadConfigFromDirectory(dir.path, configFileName, encoding, logger, pathFields, resolvePathArray);
|
|
647
711
|
if (config !== null) {
|
|
648
712
|
configs.push(config);
|
|
649
713
|
logger === null || logger === void 0 ? void 0 : logger.debug(`Loaded config from level ${dir.level}: ${dir.path}`);
|
|
@@ -675,6 +739,68 @@ const create$1 = (params)=>{
|
|
|
675
739
|
*/ function clean(obj) {
|
|
676
740
|
return Object.fromEntries(Object.entries(obj).filter(([_, v])=>v !== undefined));
|
|
677
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* Resolves relative paths in configuration values relative to the configuration file's directory.
|
|
744
|
+
*
|
|
745
|
+
* @param config - The configuration object to process
|
|
746
|
+
* @param configDir - The directory containing the configuration file
|
|
747
|
+
* @param pathFields - Array of field names (using dot notation) that contain paths to be resolved
|
|
748
|
+
* @param resolvePathArray - Array of field names whose array elements should all be resolved as paths
|
|
749
|
+
* @returns The configuration object with resolved paths
|
|
750
|
+
*/ function resolveConfigPaths(config, configDir, pathFields = [], resolvePathArray = []) {
|
|
751
|
+
if (!config || typeof config !== 'object' || pathFields.length === 0) {
|
|
752
|
+
return config;
|
|
753
|
+
}
|
|
754
|
+
const resolvedConfig = {
|
|
755
|
+
...config
|
|
756
|
+
};
|
|
757
|
+
for (const fieldPath of pathFields){
|
|
758
|
+
const value = getNestedValue(resolvedConfig, fieldPath);
|
|
759
|
+
if (value !== undefined) {
|
|
760
|
+
const shouldResolveArrayElements = resolvePathArray.includes(fieldPath);
|
|
761
|
+
const resolvedValue = resolvePathValue(value, configDir, shouldResolveArrayElements);
|
|
762
|
+
setNestedValue(resolvedConfig, fieldPath, resolvedValue);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return resolvedConfig;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Gets a nested value from an object using dot notation.
|
|
769
|
+
*/ function getNestedValue(obj, path) {
|
|
770
|
+
return path.split('.').reduce((current, key)=>current === null || current === void 0 ? void 0 : current[key], obj);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Sets a nested value in an object using dot notation.
|
|
774
|
+
*/ function setNestedValue(obj, path, value) {
|
|
775
|
+
const keys = path.split('.');
|
|
776
|
+
const lastKey = keys.pop();
|
|
777
|
+
const target = keys.reduce((current, key)=>{
|
|
778
|
+
if (!(key in current)) {
|
|
779
|
+
current[key] = {};
|
|
780
|
+
}
|
|
781
|
+
return current[key];
|
|
782
|
+
}, obj);
|
|
783
|
+
target[lastKey] = value;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Resolves a path value (string or array of strings) relative to the config directory.
|
|
787
|
+
*/ function resolvePathValue(value, configDir, resolveArrayElements) {
|
|
788
|
+
if (typeof value === 'string') {
|
|
789
|
+
return resolveSinglePath(value, configDir);
|
|
790
|
+
}
|
|
791
|
+
if (Array.isArray(value) && resolveArrayElements) {
|
|
792
|
+
return value.map((item)=>typeof item === 'string' ? resolveSinglePath(item, configDir) : item);
|
|
793
|
+
}
|
|
794
|
+
return value;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Resolves a single path string relative to the config directory if it's a relative path.
|
|
798
|
+
*/ function resolveSinglePath(pathStr, configDir) {
|
|
799
|
+
if (!pathStr || path__namespace.isAbsolute(pathStr)) {
|
|
800
|
+
return pathStr;
|
|
801
|
+
}
|
|
802
|
+
return path__namespace.resolve(configDir, pathStr);
|
|
803
|
+
}
|
|
678
804
|
/**
|
|
679
805
|
* Validates and secures a user-provided path to prevent path traversal attacks.
|
|
680
806
|
*
|
|
@@ -758,7 +884,7 @@ const create$1 = (params)=>{
|
|
|
758
884
|
* // config is fully typed based on your schema
|
|
759
885
|
* ```
|
|
760
886
|
*/ const read = async (args, options)=>{
|
|
761
|
-
var _options_defaults;
|
|
887
|
+
var _options_defaults, _options_defaults_pathResolution;
|
|
762
888
|
const logger = options.logger;
|
|
763
889
|
const rawConfigDir = args.configDirectory || ((_options_defaults = options.defaults) === null || _options_defaults === void 0 ? void 0 : _options_defaults.configDirectory);
|
|
764
890
|
if (!rawConfigDir) {
|
|
@@ -771,6 +897,7 @@ const create$1 = (params)=>{
|
|
|
771
897
|
if (options.features.includes('hierarchical')) {
|
|
772
898
|
logger.verbose('Hierarchical configuration discovery enabled');
|
|
773
899
|
try {
|
|
900
|
+
var _options_defaults_pathResolution1, _options_defaults_pathResolution2;
|
|
774
901
|
// Extract the config directory name from the path for hierarchical discovery
|
|
775
902
|
const configDirName = path__namespace.basename(resolvedConfigDir);
|
|
776
903
|
const startingDir = path__namespace.dirname(resolvedConfigDir);
|
|
@@ -780,7 +907,9 @@ const create$1 = (params)=>{
|
|
|
780
907
|
configFileName: options.defaults.configFile,
|
|
781
908
|
startingDir,
|
|
782
909
|
encoding: options.defaults.encoding,
|
|
783
|
-
logger
|
|
910
|
+
logger,
|
|
911
|
+
pathFields: (_options_defaults_pathResolution1 = options.defaults.pathResolution) === null || _options_defaults_pathResolution1 === void 0 ? void 0 : _options_defaults_pathResolution1.pathFields,
|
|
912
|
+
resolvePathArray: (_options_defaults_pathResolution2 = options.defaults.pathResolution) === null || _options_defaults_pathResolution2 === void 0 ? void 0 : _options_defaults_pathResolution2.resolvePathArray
|
|
784
913
|
});
|
|
785
914
|
rawFileConfig = hierarchicalResult.config;
|
|
786
915
|
if (hierarchicalResult.discoveredDirs.length > 0) {
|
|
@@ -805,8 +934,13 @@ const create$1 = (params)=>{
|
|
|
805
934
|
logger.verbose('Using single directory configuration loading');
|
|
806
935
|
rawFileConfig = await loadSingleDirectoryConfig(resolvedConfigDir, options, logger);
|
|
807
936
|
}
|
|
937
|
+
// Apply path resolution if configured
|
|
938
|
+
let processedConfig = rawFileConfig;
|
|
939
|
+
if ((_options_defaults_pathResolution = options.defaults.pathResolution) === null || _options_defaults_pathResolution === void 0 ? void 0 : _options_defaults_pathResolution.pathFields) {
|
|
940
|
+
processedConfig = resolveConfigPaths(rawFileConfig, resolvedConfigDir, options.defaults.pathResolution.pathFields, options.defaults.pathResolution.resolvePathArray || []);
|
|
941
|
+
}
|
|
808
942
|
const config = clean({
|
|
809
|
-
...
|
|
943
|
+
...processedConfig,
|
|
810
944
|
...{
|
|
811
945
|
configDirectory: resolvedConfigDir
|
|
812
946
|
}
|
|
@@ -1175,6 +1309,7 @@ class ConfigurationError extends Error {
|
|
|
1175
1309
|
* @param pOptions.defaults.configFile - Name of the configuration file (optional, defaults to 'config.yaml')
|
|
1176
1310
|
* @param pOptions.defaults.isRequired - Whether the config directory must exist (optional, defaults to false)
|
|
1177
1311
|
* @param pOptions.defaults.encoding - File encoding for reading config files (optional, defaults to 'utf8')
|
|
1312
|
+
* @param pOptions.defaults.pathResolution - Configuration for resolving relative paths in config values relative to the config file's directory (optional)
|
|
1178
1313
|
* @param pOptions.features - Array of features to enable (optional, defaults to ['config'])
|
|
1179
1314
|
* @param pOptions.configShape - Zod schema shape defining your configuration structure (required)
|
|
1180
1315
|
* @param pOptions.logger - Custom logger implementation (optional, defaults to console logger)
|
|
@@ -1189,15 +1324,25 @@ class ConfigurationError extends Error {
|
|
|
1189
1324
|
* apiKey: z.string().min(1),
|
|
1190
1325
|
* timeout: z.number().default(5000),
|
|
1191
1326
|
* debug: z.boolean().default(false),
|
|
1327
|
+
* contextDirectories: z.array(z.string()).optional(),
|
|
1192
1328
|
* });
|
|
1193
1329
|
*
|
|
1194
1330
|
* const cardigantime = create({
|
|
1195
1331
|
* defaults: {
|
|
1196
1332
|
* configDirectory: './config',
|
|
1197
1333
|
* configFile: 'myapp.yaml',
|
|
1334
|
+
* // Resolve relative paths in contextDirectories relative to config file location
|
|
1335
|
+
* pathResolution: {
|
|
1336
|
+
* pathFields: ['contextDirectories'],
|
|
1337
|
+
* resolvePathArray: ['contextDirectories']
|
|
1338
|
+
* }
|
|
1198
1339
|
* },
|
|
1199
1340
|
* configShape: MyConfigSchema.shape,
|
|
1200
1341
|
* });
|
|
1342
|
+
*
|
|
1343
|
+
* // If config file is at ../config/myapp.yaml and contains:
|
|
1344
|
+
* // contextDirectories: ['./context', './data']
|
|
1345
|
+
* // These paths will be resolved relative to ../config/ directory
|
|
1201
1346
|
* ```
|
|
1202
1347
|
*/ const create = (pOptions)=>{
|
|
1203
1348
|
const defaults = {
|