@theunwalked/cardigantime 0.0.9 → 0.0.10

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.
@@ -1,11 +1,15 @@
1
1
  import { configure } from './configure.js';
2
2
  import { DEFAULT_FEATURES, DEFAULT_LOGGER, DEFAULT_OPTIONS } from './constants.js';
3
- import { read } from './read.js';
3
+ import { checkConfig, read } from './read.js';
4
4
  import { validate } from './validate.js';
5
+ import * as yaml from 'js-yaml';
6
+ import * as path from 'path';
7
+ import { generateDefaultConfig } from './util/schema-defaults.js';
8
+ import { create as create$1 } from './util/storage.js';
9
+ import { FileSystemError } from './error/FileSystemError.js';
5
10
  export { ConfigSchema } from './types.js';
6
11
  export { ArgumentError } from './error/ArgumentError.js';
7
12
  export { ConfigurationError } from './error/ConfigurationError.js';
8
- export { FileSystemError } from './error/FileSystemError.js';
9
13
 
10
14
  /**
11
15
  * Creates a new Cardigantime instance for configuration management.
@@ -82,13 +86,78 @@ export { FileSystemError } from './error/FileSystemError.js';
82
86
  logger = pLogger;
83
87
  options.logger = pLogger;
84
88
  };
89
+ const generateConfig = async (configDirectory)=>{
90
+ const targetDir = configDirectory || options.defaults.configDirectory;
91
+ const configFile = options.defaults.configFile;
92
+ const encoding = options.defaults.encoding;
93
+ logger.verbose(`Generating configuration file in: ${targetDir}`);
94
+ // Create storage utility
95
+ const storage = create$1({
96
+ log: logger.debug
97
+ });
98
+ // Ensure the target directory exists
99
+ const dirExists = await storage.exists(targetDir);
100
+ if (!dirExists) {
101
+ logger.info(`Creating configuration directory: ${targetDir}`);
102
+ try {
103
+ await storage.createDirectory(targetDir);
104
+ } catch (error) {
105
+ throw FileSystemError.directoryCreationFailed(targetDir, error);
106
+ }
107
+ }
108
+ // Check if directory is writable
109
+ const isWritable = await storage.isDirectoryWritable(targetDir);
110
+ if (!isWritable) {
111
+ throw new FileSystemError('not_writable', 'Configuration directory is not writable', targetDir, 'directory_write');
112
+ }
113
+ // Build the full config file path
114
+ const configFilePath = path.join(targetDir, configFile);
115
+ // Generate default configuration
116
+ const defaultConfig = generateDefaultConfig(options.configShape);
117
+ // Convert to YAML with nice formatting
118
+ const yamlContent = yaml.dump(defaultConfig, {
119
+ indent: 2,
120
+ lineWidth: 120,
121
+ noRefs: true,
122
+ sortKeys: true
123
+ });
124
+ // Add header comment to the YAML file
125
+ const header = `# Configuration file generated by Cardigantime
126
+ # This file contains default values for your application configuration.
127
+ # Modify the values below to customize your application's behavior.
128
+ #
129
+ # For more information about Cardigantime configuration:
130
+ # https://github.com/SemicolonAmbulance/cardigantime
131
+
132
+ `;
133
+ const finalContent = header + yamlContent;
134
+ // Check if config file already exists
135
+ const configExists = await storage.exists(configFilePath);
136
+ if (configExists) {
137
+ logger.warn(`Configuration file already exists: ${configFilePath}`);
138
+ logger.warn('This file was not overwritten, but here is what the default configuration looks like if you want to copy it:');
139
+ logger.info('\n' + '='.repeat(60));
140
+ logger.info(finalContent.trim());
141
+ logger.info('='.repeat(60));
142
+ return;
143
+ }
144
+ // Write the configuration file
145
+ try {
146
+ await storage.writeFile(configFilePath, finalContent, encoding);
147
+ logger.info(`Configuration file generated successfully: ${configFilePath}`);
148
+ } catch (error) {
149
+ throw FileSystemError.operationFailed('write configuration file', configFilePath, error);
150
+ }
151
+ };
85
152
  return {
86
153
  setLogger,
87
154
  configure: (command)=>configure(command, options),
88
155
  validate: (config)=>validate(config, options),
89
- read: (args)=>read(args, options)
156
+ read: (args)=>read(args, options),
157
+ generateConfig,
158
+ checkConfig: (args)=>checkConfig(args, options)
90
159
  };
91
160
  };
92
161
 
93
- export { create };
162
+ export { FileSystemError, create };
94
163
  //# sourceMappingURL=cardigantime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cardigantime.js","sources":["../src/cardigantime.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { Args, DefaultOptions, Feature, Cardigantime, Logger, Options } from 'types';\nimport { z, ZodObject } from 'zod';\nimport { configure } from './configure';\nimport { DEFAULT_FEATURES, DEFAULT_LOGGER, DEFAULT_OPTIONS } from './constants';\nimport { read } from './read';\nimport { ConfigSchema } from 'types';\nimport { validate } from './validate';\n\nexport * from './types';\nexport { ArgumentError, ConfigurationError, FileSystemError } from './validate';\n\n/**\n * Creates a new Cardigantime instance for configuration management.\n * \n * Cardigantime handles the complete configuration lifecycle including:\n * - Reading configuration from YAML files\n * - Validating configuration against Zod schemas\n * - Merging CLI arguments with file configuration and defaults\n * - Providing type-safe configuration objects\n * \n * @template T - The Zod schema shape type for your configuration\n * @param pOptions - Configuration options for the Cardigantime instance\n * @param pOptions.defaults - Default configuration settings\n * @param pOptions.defaults.configDirectory - Directory to search for configuration files (required)\n * @param pOptions.defaults.configFile - Name of the configuration file (optional, defaults to 'config.yaml')\n * @param pOptions.defaults.isRequired - Whether the config directory must exist (optional, defaults to false)\n * @param pOptions.defaults.encoding - File encoding for reading config files (optional, defaults to 'utf8')\n * @param pOptions.defaults.pathResolution - Configuration for resolving relative paths in config values relative to the config file's directory (optional)\n * @param pOptions.features - Array of features to enable (optional, defaults to ['config'])\n * @param pOptions.configShape - Zod schema shape defining your configuration structure (required)\n * @param pOptions.logger - Custom logger implementation (optional, defaults to console logger)\n * @returns A Cardigantime instance with methods for configure, read, validate, and setLogger\n * \n * @example\n * ```typescript\n * import { create } from '@theunwalked/cardigantime';\n * import { z } from 'zod';\n * \n * const MyConfigSchema = z.object({\n * apiKey: z.string().min(1),\n * timeout: z.number().default(5000),\n * debug: z.boolean().default(false),\n * contextDirectories: z.array(z.string()).optional(),\n * });\n * \n * const cardigantime = create({\n * defaults: {\n * configDirectory: './config',\n * configFile: 'myapp.yaml',\n * // Resolve relative paths in contextDirectories relative to config file location\n * pathResolution: {\n * pathFields: ['contextDirectories'],\n * resolvePathArray: ['contextDirectories']\n * },\n * // Configure how array fields are merged in hierarchical mode\n * fieldOverlaps: {\n * 'features': 'append', // Accumulate features from all levels\n * 'excludePatterns': 'prepend' // Higher precedence patterns come first\n * }\n * },\n * configShape: MyConfigSchema.shape,\n * features: ['config', 'hierarchical'], // Enable hierarchical discovery\n * });\n * \n * // If config file is at ../config/myapp.yaml and contains:\n * // contextDirectories: ['./context', './data']\n * // These paths will be resolved relative to ../config/ directory\n * ```\n */\nexport const create = <T extends z.ZodRawShape>(pOptions: {\n defaults: Pick<DefaultOptions, 'configDirectory'> & Partial<Omit<DefaultOptions, 'configDirectory'>>,\n features?: Feature[],\n configShape: T, // Make configShape mandatory\n logger?: Logger,\n}): Cardigantime<T> => {\n\n\n const defaults: DefaultOptions = { ...DEFAULT_OPTIONS, ...pOptions.defaults } as DefaultOptions;\n const features = pOptions.features || DEFAULT_FEATURES;\n const configShape = pOptions.configShape;\n let logger = pOptions.logger || DEFAULT_LOGGER;\n\n const options: Options<T> = {\n defaults,\n features,\n configShape, // Store the shape\n logger,\n }\n\n const setLogger = (pLogger: Logger) => {\n logger = pLogger;\n options.logger = pLogger;\n }\n\n return {\n setLogger,\n configure: (command: Command) => configure(command, options),\n validate: (config: z.infer<ZodObject<T & typeof ConfigSchema.shape>>) => validate(config, options),\n read: (args: Args) => read(args, options),\n }\n}\n\n\n\n\n\n"],"names":["create","pOptions","defaults","DEFAULT_OPTIONS","features","DEFAULT_FEATURES","configShape","logger","DEFAULT_LOGGER","options","setLogger","pLogger","configure","command","validate","config","read","args"],"mappings":";;;;;;;;;AAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0DO,MAAMA,MAAAA,GAAS,CAA0BC,QAAAA,GAAAA;AAQ5C,IAAA,MAAMC,QAAAA,GAA2B;AAAE,QAAA,GAAGC,eAAe;AAAE,QAAA,GAAGF,SAASC;AAAS,KAAA;IAC5E,MAAME,QAAAA,GAAWH,QAAAA,CAASG,QAAQ,IAAIC,gBAAAA;IACtC,MAAMC,WAAAA,GAAcL,SAASK,WAAW;IACxC,IAAIC,MAAAA,GAASN,QAAAA,CAASM,MAAM,IAAIC,cAAAA;AAEhC,IAAA,MAAMC,OAAAA,GAAsB;AACxBP,QAAAA,QAAAA;AACAE,QAAAA,QAAAA;AACAE,QAAAA,WAAAA;AACAC,QAAAA;AACJ,KAAA;AAEA,IAAA,MAAMG,YAAY,CAACC,OAAAA,GAAAA;QACfJ,MAAAA,GAASI,OAAAA;AACTF,QAAAA,OAAAA,CAAQF,MAAM,GAAGI,OAAAA;AACrB,KAAA;IAEA,OAAO;AACHD,QAAAA,SAAAA;QACAE,SAAAA,EAAW,CAACC,OAAAA,GAAqBD,SAAAA,CAAUC,OAAAA,EAASJ,OAAAA,CAAAA;QACpDK,QAAAA,EAAU,CAACC,MAAAA,GAA8DD,QAAAA,CAASC,MAAAA,EAAQN,OAAAA,CAAAA;QAC1FO,IAAAA,EAAM,CAACC,IAAAA,GAAeD,IAAAA,CAAKC,IAAAA,EAAMR,OAAAA;AACrC,KAAA;AACJ;;;;"}
1
+ {"version":3,"file":"cardigantime.js","sources":["../src/cardigantime.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { Args, DefaultOptions, Feature, Cardigantime, Logger, Options } from 'types';\nimport { z, ZodObject } from 'zod';\nimport { configure } from './configure';\nimport { DEFAULT_FEATURES, DEFAULT_LOGGER, DEFAULT_OPTIONS } from './constants';\nimport { read, checkConfig } from './read';\nimport { ConfigSchema } from 'types';\nimport { validate } from './validate';\nimport * as yaml from 'js-yaml';\nimport * as path from 'path';\nimport { generateDefaultConfig } from './util/schema-defaults';\nimport * as Storage from './util/storage';\nimport { FileSystemError } from './error/FileSystemError';\n\nexport * from './types';\nexport { ArgumentError, ConfigurationError, FileSystemError } from './validate';\n\n/**\n * Creates a new Cardigantime instance for configuration management.\n * \n * Cardigantime handles the complete configuration lifecycle including:\n * - Reading configuration from YAML files\n * - Validating configuration against Zod schemas\n * - Merging CLI arguments with file configuration and defaults\n * - Providing type-safe configuration objects\n * \n * @template T - The Zod schema shape type for your configuration\n * @param pOptions - Configuration options for the Cardigantime instance\n * @param pOptions.defaults - Default configuration settings\n * @param pOptions.defaults.configDirectory - Directory to search for configuration files (required)\n * @param pOptions.defaults.configFile - Name of the configuration file (optional, defaults to 'config.yaml')\n * @param pOptions.defaults.isRequired - Whether the config directory must exist (optional, defaults to false)\n * @param pOptions.defaults.encoding - File encoding for reading config files (optional, defaults to 'utf8')\n * @param pOptions.defaults.pathResolution - Configuration for resolving relative paths in config values relative to the config file's directory (optional)\n * @param pOptions.features - Array of features to enable (optional, defaults to ['config'])\n * @param pOptions.configShape - Zod schema shape defining your configuration structure (required)\n * @param pOptions.logger - Custom logger implementation (optional, defaults to console logger)\n * @returns A Cardigantime instance with methods for configure, read, validate, and setLogger\n * \n * @example\n * ```typescript\n * import { create } from '@theunwalked/cardigantime';\n * import { z } from 'zod';\n * \n * const MyConfigSchema = z.object({\n * apiKey: z.string().min(1),\n * timeout: z.number().default(5000),\n * debug: z.boolean().default(false),\n * contextDirectories: z.array(z.string()).optional(),\n * });\n * \n * const cardigantime = create({\n * defaults: {\n * configDirectory: './config',\n * configFile: 'myapp.yaml',\n * // Resolve relative paths in contextDirectories relative to config file location\n * pathResolution: {\n * pathFields: ['contextDirectories'],\n * resolvePathArray: ['contextDirectories']\n * },\n * // Configure how array fields are merged in hierarchical mode\n * fieldOverlaps: {\n * 'features': 'append', // Accumulate features from all levels\n * 'excludePatterns': 'prepend' // Higher precedence patterns come first\n * }\n * },\n * configShape: MyConfigSchema.shape,\n * features: ['config', 'hierarchical'], // Enable hierarchical discovery\n * });\n * \n * // If config file is at ../config/myapp.yaml and contains:\n * // contextDirectories: ['./context', './data']\n * // These paths will be resolved relative to ../config/ directory\n * ```\n */\nexport const create = <T extends z.ZodRawShape>(pOptions: {\n defaults: Pick<DefaultOptions, 'configDirectory'> & Partial<Omit<DefaultOptions, 'configDirectory'>>,\n features?: Feature[],\n configShape: T, // Make configShape mandatory\n logger?: Logger,\n}): Cardigantime<T> => {\n\n\n const defaults: DefaultOptions = { ...DEFAULT_OPTIONS, ...pOptions.defaults } as DefaultOptions;\n const features = pOptions.features || DEFAULT_FEATURES;\n const configShape = pOptions.configShape;\n let logger = pOptions.logger || DEFAULT_LOGGER;\n\n const options: Options<T> = {\n defaults,\n features,\n configShape, // Store the shape\n logger,\n }\n\n const setLogger = (pLogger: Logger) => {\n logger = pLogger;\n options.logger = pLogger;\n }\n\n const generateConfig = async (configDirectory?: string): Promise<void> => {\n const targetDir = configDirectory || options.defaults.configDirectory;\n const configFile = options.defaults.configFile;\n const encoding = options.defaults.encoding;\n\n logger.verbose(`Generating configuration file in: ${targetDir}`);\n\n // Create storage utility\n const storage = Storage.create({ log: logger.debug });\n\n // Ensure the target directory exists\n const dirExists = await storage.exists(targetDir);\n if (!dirExists) {\n logger.info(`Creating configuration directory: ${targetDir}`);\n try {\n await storage.createDirectory(targetDir);\n } catch (error: any) {\n throw FileSystemError.directoryCreationFailed(targetDir, error);\n }\n }\n\n // Check if directory is writable\n const isWritable = await storage.isDirectoryWritable(targetDir);\n if (!isWritable) {\n throw new FileSystemError('not_writable', 'Configuration directory is not writable', targetDir, 'directory_write');\n }\n\n // Build the full config file path\n const configFilePath = path.join(targetDir, configFile);\n\n // Generate default configuration\n const defaultConfig = generateDefaultConfig(options.configShape, targetDir);\n\n // Convert to YAML with nice formatting\n const yamlContent = yaml.dump(defaultConfig, {\n indent: 2,\n lineWidth: 120,\n noRefs: true,\n sortKeys: true\n });\n\n // Add header comment to the YAML file\n const header = `# Configuration file generated by Cardigantime\n# This file contains default values for your application configuration.\n# Modify the values below to customize your application's behavior.\n#\n# For more information about Cardigantime configuration:\n# https://github.com/SemicolonAmbulance/cardigantime\n\n`;\n\n const finalContent = header + yamlContent;\n\n // Check if config file already exists\n const configExists = await storage.exists(configFilePath);\n if (configExists) {\n logger.warn(`Configuration file already exists: ${configFilePath}`);\n logger.warn('This file was not overwritten, but here is what the default configuration looks like if you want to copy it:');\n logger.info('\\n' + '='.repeat(60));\n logger.info(finalContent.trim());\n logger.info('='.repeat(60));\n return;\n }\n\n // Write the configuration file\n try {\n await storage.writeFile(configFilePath, finalContent, encoding);\n logger.info(`Configuration file generated successfully: ${configFilePath}`);\n } catch (error: any) {\n throw FileSystemError.operationFailed('write configuration file', configFilePath, error);\n }\n };\n\n return {\n setLogger,\n configure: (command: Command) => configure(command, options),\n validate: (config: z.infer<ZodObject<T & typeof ConfigSchema.shape>>) => validate(config, options),\n read: (args: Args) => read(args, options),\n generateConfig,\n checkConfig: (args: Args) => checkConfig(args, options),\n }\n}\n\n\n\n\n\n"],"names":["create","pOptions","defaults","DEFAULT_OPTIONS","features","DEFAULT_FEATURES","configShape","logger","DEFAULT_LOGGER","options","setLogger","pLogger","generateConfig","configDirectory","targetDir","configFile","encoding","verbose","storage","Storage","log","debug","dirExists","exists","info","createDirectory","error","FileSystemError","directoryCreationFailed","isWritable","isDirectoryWritable","configFilePath","path","join","defaultConfig","generateDefaultConfig","yamlContent","yaml","dump","indent","lineWidth","noRefs","sortKeys","header","finalContent","configExists","warn","repeat","trim","writeFile","operationFailed","configure","command","validate","config","read","args","checkConfig"],"mappings":"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"}
package/dist/configure.js CHANGED
@@ -100,6 +100,10 @@ import { ArgumentError } from './error/ArgumentError.js';
100
100
  throw error;
101
101
  }
102
102
  }, validatedDefaultDir);
103
+ // Add the init config option
104
+ retCommand = retCommand.option('--init-config', 'Generate initial configuration file and exit');
105
+ // Add the check config option
106
+ retCommand = retCommand.option('--check-config', 'Display resolved configuration with source tracking and exit');
103
107
  return retCommand;
104
108
  };
105
109
 
@@ -1 +1 @@
1
- {"version":3,"file":"configure.js","sources":["../src/configure.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { z } from \"zod\";\nimport { ArgumentError } from \"./error/ArgumentError\";\nimport { Options } from \"./types\";\nexport { ArgumentError };\n\n/**\n * Validates a configuration directory path to ensure it's safe and valid.\n * \n * Performs security and safety checks including:\n * - Non-empty string validation\n * - Null byte injection prevention\n * - Path length validation\n * - Type checking\n * \n * @param configDirectory - The configuration directory path to validate\n * @param _testThrowNonArgumentError - Internal testing parameter to simulate non-ArgumentError exceptions\n * @returns The trimmed and validated configuration directory path\n * @throws {ArgumentError} When the directory path is invalid\n * \n * @example\n * ```typescript\n * const validDir = validateConfigDirectory('./config'); // Returns './config'\n * const invalidDir = validateConfigDirectory(''); // Throws ArgumentError\n * ```\n */\nexport function validateConfigDirectory(configDirectory: string, _testThrowNonArgumentError?: boolean): string {\n if (_testThrowNonArgumentError) {\n throw new Error('Test non-ArgumentError for coverage');\n }\n\n if (!configDirectory) {\n throw new ArgumentError('configDirectory', 'Configuration directory cannot be empty');\n }\n\n if (typeof configDirectory !== 'string') {\n throw new ArgumentError('configDirectory', 'Configuration directory must be a string');\n }\n\n const trimmed = configDirectory.trim();\n if (trimmed.length === 0) {\n throw new ArgumentError('configDirectory', 'Configuration directory cannot be empty or whitespace only');\n }\n\n // Check for obviously invalid paths\n if (trimmed.includes('\\0')) {\n throw new ArgumentError('configDirectory', 'Configuration directory contains invalid null character');\n }\n\n // Validate path length (reasonable limit)\n if (trimmed.length > 1000) {\n throw new ArgumentError('configDirectory', 'Configuration directory path is too long (max 1000 characters)');\n }\n\n return trimmed;\n}\n\n/**\n * Configures a Commander.js command with Cardigantime's CLI options.\n * \n * This function adds command-line options that allow users to override\n * configuration settings at runtime, such as:\n * - --config-directory: Override the default configuration directory\n * \n * The function validates both the command object and the options to ensure\n * they meet the requirements for proper integration.\n * \n * @template T - The Zod schema shape type for configuration validation\n * @param command - The Commander.js Command instance to configure\n * @param options - Cardigantime options containing defaults and schema\n * @param _testThrowNonArgumentError - Internal testing parameter\n * @returns Promise resolving to the configured Command instance\n * @throws {ArgumentError} When command or options are invalid\n * \n * @example\n * ```typescript\n * import { Command } from 'commander';\n * import { configure } from './configure';\n * \n * const program = new Command();\n * const configuredProgram = await configure(program, options);\n * \n * // Now the program accepts: --config-directory <path>\n * ```\n */\nexport const configure = async <T extends z.ZodRawShape>(\n command: Command,\n options: Options<T>,\n _testThrowNonArgumentError?: boolean\n): Promise<Command> => {\n // Validate the command object\n if (!command) {\n throw new ArgumentError('command', 'Command instance is required');\n }\n\n if (typeof command.option !== 'function') {\n throw new ArgumentError('command', 'Command must be a valid Commander.js Command instance');\n }\n\n // Validate options\n if (!options) {\n throw new ArgumentError('options', 'Options object is required');\n }\n\n if (!options.defaults) {\n throw new ArgumentError('options.defaults', 'Options must include defaults configuration');\n }\n\n if (!options.defaults.configDirectory) {\n throw new ArgumentError('options.defaults.configDirectory', 'Default config directory is required');\n }\n\n // Validate the default config directory\n const validatedDefaultDir = validateConfigDirectory(options.defaults.configDirectory, _testThrowNonArgumentError);\n\n let retCommand = command;\n\n // Add the config directory option with validation\n retCommand = retCommand.option(\n '-c, --config-directory <configDirectory>',\n 'Configuration directory path',\n (value: string) => {\n try {\n return validateConfigDirectory(value, _testThrowNonArgumentError);\n } catch (error) {\n if (error instanceof ArgumentError) {\n // Re-throw with more specific context for CLI usage\n throw new ArgumentError('config-directory', `Invalid --config-directory: ${error.message}`);\n }\n throw error;\n }\n },\n validatedDefaultDir\n );\n\n return retCommand;\n}\n\n\n\n\n"],"names":["validateConfigDirectory","configDirectory","_testThrowNonArgumentError","ArgumentError","trimmed","trim","length","includes","configure","command","options","option","defaults","validatedDefaultDir","retCommand","value","error","message"],"mappings":";;AAMA;;;;;;;;;;;;;;;;;;;AAmBC,IACM,SAASA,uBAAAA,CAAwBC,eAAuB,EAAEC,0BAAoC,EAAA;AAKjG,IAAA,IAAI,CAACD,eAAAA,EAAiB;QAClB,MAAM,IAAIE,cAAc,iBAAA,EAAmB,yCAAA,CAAA;AAC/C;IAEA,IAAI,OAAOF,oBAAoB,QAAA,EAAU;QACrC,MAAM,IAAIE,cAAc,iBAAA,EAAmB,0CAAA,CAAA;AAC/C;IAEA,MAAMC,OAAAA,GAAUH,gBAAgBI,IAAI,EAAA;IACpC,IAAID,OAAAA,CAAQE,MAAM,KAAK,CAAA,EAAG;QACtB,MAAM,IAAIH,cAAc,iBAAA,EAAmB,4DAAA,CAAA;AAC/C;;IAGA,IAAIC,OAAAA,CAAQG,QAAQ,CAAC,IAAA,CAAA,EAAO;QACxB,MAAM,IAAIJ,cAAc,iBAAA,EAAmB,yDAAA,CAAA;AAC/C;;IAGA,IAAIC,OAAAA,CAAQE,MAAM,GAAG,IAAA,EAAM;QACvB,MAAM,IAAIH,cAAc,iBAAA,EAAmB,gEAAA,CAAA;AAC/C;IAEA,OAAOC,OAAAA;AACX;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BC,IACM,MAAMI,SAAAA,GAAY,OACrBC,SACAC,OAAAA,EACAR,0BAAAA,GAAAA;;AAGA,IAAA,IAAI,CAACO,OAAAA,EAAS;QACV,MAAM,IAAIN,cAAc,SAAA,EAAW,8BAAA,CAAA;AACvC;AAEA,IAAA,IAAI,OAAOM,OAAAA,CAAQE,MAAM,KAAK,UAAA,EAAY;QACtC,MAAM,IAAIR,cAAc,SAAA,EAAW,uDAAA,CAAA;AACvC;;AAGA,IAAA,IAAI,CAACO,OAAAA,EAAS;QACV,MAAM,IAAIP,cAAc,SAAA,EAAW,4BAAA,CAAA;AACvC;IAEA,IAAI,CAACO,OAAAA,CAAQE,QAAQ,EAAE;QACnB,MAAM,IAAIT,cAAc,kBAAA,EAAoB,6CAAA,CAAA;AAChD;AAEA,IAAA,IAAI,CAACO,OAAAA,CAAQE,QAAQ,CAACX,eAAe,EAAE;QACnC,MAAM,IAAIE,cAAc,kCAAA,EAAoC,sCAAA,CAAA;AAChE;;AAGA,IAAA,MAAMU,sBAAsBb,uBAAAA,CAAwBU,OAAAA,CAAQE,QAAQ,CAACX,eAAiBC,CAAAA;AAEtF,IAAA,IAAIY,UAAAA,GAAaL,OAAAA;;AAGjBK,IAAAA,UAAAA,GAAaA,UAAAA,CAAWH,MAAM,CAC1B,0CAAA,EACA,gCACA,CAACI,KAAAA,GAAAA;QACG,IAAI;AACA,YAAA,OAAOf,wBAAwBe,KAAAA,EAAOb,0BAAAA,CAAAA;AAC1C,SAAA,CAAE,OAAOc,KAAAA,EAAO;AACZ,YAAA,IAAIA,iBAAiBb,aAAAA,EAAe;;gBAEhC,MAAM,IAAIA,cAAc,kBAAA,EAAoB,CAAC,4BAA4B,EAAEa,KAAAA,CAAMC,OAAO,CAAA,CAAE,CAAA;AAC9F;YACA,MAAMD,KAAAA;AACV;KACJ,EACAH,mBAAAA,CAAAA;IAGJ,OAAOC,UAAAA;AACX;;;;"}
1
+ {"version":3,"file":"configure.js","sources":["../src/configure.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { z } from \"zod\";\nimport { ArgumentError } from \"./error/ArgumentError\";\nimport { Options } from \"./types\";\nexport { ArgumentError };\n\n/**\n * Validates a configuration directory path to ensure it's safe and valid.\n * \n * Performs security and safety checks including:\n * - Non-empty string validation\n * - Null byte injection prevention\n * - Path length validation\n * - Type checking\n * \n * @param configDirectory - The configuration directory path to validate\n * @param _testThrowNonArgumentError - Internal testing parameter to simulate non-ArgumentError exceptions\n * @returns The trimmed and validated configuration directory path\n * @throws {ArgumentError} When the directory path is invalid\n * \n * @example\n * ```typescript\n * const validDir = validateConfigDirectory('./config'); // Returns './config'\n * const invalidDir = validateConfigDirectory(''); // Throws ArgumentError\n * ```\n */\nexport function validateConfigDirectory(configDirectory: string, _testThrowNonArgumentError?: boolean): string {\n if (_testThrowNonArgumentError) {\n throw new Error('Test non-ArgumentError for coverage');\n }\n\n if (!configDirectory) {\n throw new ArgumentError('configDirectory', 'Configuration directory cannot be empty');\n }\n\n if (typeof configDirectory !== 'string') {\n throw new ArgumentError('configDirectory', 'Configuration directory must be a string');\n }\n\n const trimmed = configDirectory.trim();\n if (trimmed.length === 0) {\n throw new ArgumentError('configDirectory', 'Configuration directory cannot be empty or whitespace only');\n }\n\n // Check for obviously invalid paths\n if (trimmed.includes('\\0')) {\n throw new ArgumentError('configDirectory', 'Configuration directory contains invalid null character');\n }\n\n // Validate path length (reasonable limit)\n if (trimmed.length > 1000) {\n throw new ArgumentError('configDirectory', 'Configuration directory path is too long (max 1000 characters)');\n }\n\n return trimmed;\n}\n\n/**\n * Configures a Commander.js command with Cardigantime's CLI options.\n * \n * This function adds command-line options that allow users to override\n * configuration settings at runtime, such as:\n * - --config-directory: Override the default configuration directory\n * \n * The function validates both the command object and the options to ensure\n * they meet the requirements for proper integration.\n * \n * @template T - The Zod schema shape type for configuration validation\n * @param command - The Commander.js Command instance to configure\n * @param options - Cardigantime options containing defaults and schema\n * @param _testThrowNonArgumentError - Internal testing parameter\n * @returns Promise resolving to the configured Command instance\n * @throws {ArgumentError} When command or options are invalid\n * \n * @example\n * ```typescript\n * import { Command } from 'commander';\n * import { configure } from './configure';\n * \n * const program = new Command();\n * const configuredProgram = await configure(program, options);\n * \n * // Now the program accepts: --config-directory <path>\n * ```\n */\nexport const configure = async <T extends z.ZodRawShape>(\n command: Command,\n options: Options<T>,\n _testThrowNonArgumentError?: boolean\n): Promise<Command> => {\n // Validate the command object\n if (!command) {\n throw new ArgumentError('command', 'Command instance is required');\n }\n\n if (typeof command.option !== 'function') {\n throw new ArgumentError('command', 'Command must be a valid Commander.js Command instance');\n }\n\n // Validate options\n if (!options) {\n throw new ArgumentError('options', 'Options object is required');\n }\n\n if (!options.defaults) {\n throw new ArgumentError('options.defaults', 'Options must include defaults configuration');\n }\n\n if (!options.defaults.configDirectory) {\n throw new ArgumentError('options.defaults.configDirectory', 'Default config directory is required');\n }\n\n // Validate the default config directory\n const validatedDefaultDir = validateConfigDirectory(options.defaults.configDirectory, _testThrowNonArgumentError);\n\n let retCommand = command;\n\n // Add the config directory option with validation\n retCommand = retCommand.option(\n '-c, --config-directory <configDirectory>',\n 'Configuration directory path',\n (value: string) => {\n try {\n return validateConfigDirectory(value, _testThrowNonArgumentError);\n } catch (error) {\n if (error instanceof ArgumentError) {\n // Re-throw with more specific context for CLI usage\n throw new ArgumentError('config-directory', `Invalid --config-directory: ${error.message}`);\n }\n throw error;\n }\n },\n validatedDefaultDir\n );\n\n // Add the init config option\n retCommand = retCommand.option(\n '--init-config',\n 'Generate initial configuration file and exit'\n );\n\n // Add the check config option\n retCommand = retCommand.option(\n '--check-config',\n 'Display resolved configuration with source tracking and exit'\n );\n\n return retCommand;\n}\n\n\n\n\n"],"names":["validateConfigDirectory","configDirectory","_testThrowNonArgumentError","ArgumentError","trimmed","trim","length","includes","configure","command","options","option","defaults","validatedDefaultDir","retCommand","value","error","message"],"mappings":";;AAMA;;;;;;;;;;;;;;;;;;;AAmBC,IACM,SAASA,uBAAAA,CAAwBC,eAAuB,EAAEC,0BAAoC,EAAA;AAKjG,IAAA,IAAI,CAACD,eAAAA,EAAiB;QAClB,MAAM,IAAIE,cAAc,iBAAA,EAAmB,yCAAA,CAAA;AAC/C;IAEA,IAAI,OAAOF,oBAAoB,QAAA,EAAU;QACrC,MAAM,IAAIE,cAAc,iBAAA,EAAmB,0CAAA,CAAA;AAC/C;IAEA,MAAMC,OAAAA,GAAUH,gBAAgBI,IAAI,EAAA;IACpC,IAAID,OAAAA,CAAQE,MAAM,KAAK,CAAA,EAAG;QACtB,MAAM,IAAIH,cAAc,iBAAA,EAAmB,4DAAA,CAAA;AAC/C;;IAGA,IAAIC,OAAAA,CAAQG,QAAQ,CAAC,IAAA,CAAA,EAAO;QACxB,MAAM,IAAIJ,cAAc,iBAAA,EAAmB,yDAAA,CAAA;AAC/C;;IAGA,IAAIC,OAAAA,CAAQE,MAAM,GAAG,IAAA,EAAM;QACvB,MAAM,IAAIH,cAAc,iBAAA,EAAmB,gEAAA,CAAA;AAC/C;IAEA,OAAOC,OAAAA;AACX;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BC,IACM,MAAMI,SAAAA,GAAY,OACrBC,SACAC,OAAAA,EACAR,0BAAAA,GAAAA;;AAGA,IAAA,IAAI,CAACO,OAAAA,EAAS;QACV,MAAM,IAAIN,cAAc,SAAA,EAAW,8BAAA,CAAA;AACvC;AAEA,IAAA,IAAI,OAAOM,OAAAA,CAAQE,MAAM,KAAK,UAAA,EAAY;QACtC,MAAM,IAAIR,cAAc,SAAA,EAAW,uDAAA,CAAA;AACvC;;AAGA,IAAA,IAAI,CAACO,OAAAA,EAAS;QACV,MAAM,IAAIP,cAAc,SAAA,EAAW,4BAAA,CAAA;AACvC;IAEA,IAAI,CAACO,OAAAA,CAAQE,QAAQ,EAAE;QACnB,MAAM,IAAIT,cAAc,kBAAA,EAAoB,6CAAA,CAAA;AAChD;AAEA,IAAA,IAAI,CAACO,OAAAA,CAAQE,QAAQ,CAACX,eAAe,EAAE;QACnC,MAAM,IAAIE,cAAc,kCAAA,EAAoC,sCAAA,CAAA;AAChE;;AAGA,IAAA,MAAMU,sBAAsBb,uBAAAA,CAAwBU,OAAAA,CAAQE,QAAQ,CAACX,eAAiBC,CAAAA;AAEtF,IAAA,IAAIY,UAAAA,GAAaL,OAAAA;;AAGjBK,IAAAA,UAAAA,GAAaA,UAAAA,CAAWH,MAAM,CAC1B,0CAAA,EACA,gCACA,CAACI,KAAAA,GAAAA;QACG,IAAI;AACA,YAAA,OAAOf,wBAAwBe,KAAAA,EAAOb,0BAAAA,CAAAA;AAC1C,SAAA,CAAE,OAAOc,KAAAA,EAAO;AACZ,YAAA,IAAIA,iBAAiBb,aAAAA,EAAe;;gBAEhC,MAAM,IAAIA,cAAc,kBAAA,EAAoB,CAAC,4BAA4B,EAAEa,KAAAA,CAAMC,OAAO,CAAA,CAAE,CAAA;AAC9F;YACA,MAAMD,KAAAA;AACV;KACJ,EACAH,mBAAAA,CAAAA;;IAIJC,UAAAA,GAAaA,UAAAA,CAAWH,MAAM,CAC1B,eAAA,EACA,8CAAA,CAAA;;IAIJG,UAAAA,GAAaA,UAAAA,CAAWH,MAAM,CAC1B,gBAAA,EACA,8DAAA,CAAA;IAGJ,OAAOG,UAAAA;AACX;;;;"}
package/dist/read.d.ts CHANGED
@@ -31,3 +31,26 @@ import { Args, ConfigSchema, Options } from './types';
31
31
  * ```
32
32
  */
33
33
  export declare const read: <T extends z.ZodRawShape>(args: Args, options: Options<T>) => Promise<z.infer<ZodObject<T & typeof ConfigSchema.shape>>>;
34
+ /**
35
+ * Checks and displays the resolved configuration with detailed source tracking.
36
+ *
37
+ * This function provides a git blame-like view of configuration resolution,
38
+ * showing which file and hierarchical level contributed each configuration value.
39
+ *
40
+ * @template T - The Zod schema shape type for configuration validation
41
+ * @param args - Parsed command-line arguments
42
+ * @param options - Cardigantime options with defaults, schema, and logger
43
+ * @returns Promise that resolves when the configuration check is complete
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * await checkConfig(cliArgs, {
48
+ * defaults: { configDirectory: './config', configFile: 'app.yaml' },
49
+ * configShape: MySchema.shape,
50
+ * logger: console,
51
+ * features: ['config', 'hierarchical']
52
+ * });
53
+ * // Outputs detailed configuration source analysis
54
+ * ```
55
+ */
56
+ export declare const checkConfig: <T extends z.ZodRawShape>(args: Args, options: Options<T>) => Promise<void>;
package/dist/read.js CHANGED
@@ -255,6 +255,263 @@ import { loadHierarchicalConfig } from './util/hierarchical.js';
255
255
  }
256
256
  return rawFileConfig;
257
257
  }
258
+ /**
259
+ * Recursively tracks the source of configuration values from hierarchical loading.
260
+ *
261
+ * @param config - The configuration object to track
262
+ * @param sourcePath - Path to the configuration file
263
+ * @param level - Hierarchical level
264
+ * @param prefix - Current object path prefix for nested values
265
+ * @param tracker - The tracker object to populate
266
+ */ function trackConfigSources(config, sourcePath, level, prefix = '', tracker = {}) {
267
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
268
+ // For primitives and arrays, track the entire value
269
+ tracker[prefix] = {
270
+ value: config,
271
+ sourcePath,
272
+ level,
273
+ sourceLabel: `Level ${level}: ${path.basename(path.dirname(sourcePath))}`
274
+ };
275
+ return tracker;
276
+ }
277
+ // For objects, recursively track each property
278
+ for (const [key, value] of Object.entries(config)){
279
+ const fieldPath = prefix ? `${prefix}.${key}` : key;
280
+ trackConfigSources(value, sourcePath, level, fieldPath, tracker);
281
+ }
282
+ return tracker;
283
+ }
284
+ /**
285
+ * Merges multiple configuration source trackers with proper precedence.
286
+ * Lower level numbers have higher precedence.
287
+ *
288
+ * @param trackers - Array of trackers from different config sources
289
+ * @returns Merged tracker with proper precedence
290
+ */ function mergeConfigTrackers(trackers) {
291
+ const merged = {};
292
+ for (const tracker of trackers){
293
+ for (const [key, info] of Object.entries(tracker)){
294
+ // Only update if we don't have this key yet, or if this source has higher precedence (lower level)
295
+ if (!merged[key] || info.level < merged[key].level) {
296
+ merged[key] = info;
297
+ }
298
+ }
299
+ }
300
+ return merged;
301
+ }
302
+ /**
303
+ * Formats a configuration value for display, handling different types appropriately.
304
+ *
305
+ * @param value - The configuration value to format
306
+ * @returns Formatted string representation
307
+ */ function formatConfigValue(value) {
308
+ if (value === null) return 'null';
309
+ if (value === undefined) return 'undefined';
310
+ if (typeof value === 'string') return `"${value}"`;
311
+ if (typeof value === 'boolean') return value.toString();
312
+ if (typeof value === 'number') return value.toString();
313
+ if (Array.isArray(value)) {
314
+ if (value.length === 0) return '[]';
315
+ if (value.length <= 3) {
316
+ return `[${value.map(formatConfigValue).join(', ')}]`;
317
+ }
318
+ return `[${value.slice(0, 2).map(formatConfigValue).join(', ')}, ... (${value.length} items)]`;
319
+ }
320
+ if (typeof value === 'object') {
321
+ const keys = Object.keys(value);
322
+ if (keys.length === 0) return '{}';
323
+ if (keys.length <= 2) {
324
+ return `{${keys.slice(0, 2).join(', ')}}`;
325
+ }
326
+ return `{${keys.slice(0, 2).join(', ')}, ... (${keys.length} keys)}`;
327
+ }
328
+ return String(value);
329
+ }
330
+ /**
331
+ * Displays configuration with source tracking in a git blame-like format.
332
+ *
333
+ * @param config - The resolved configuration object
334
+ * @param tracker - Configuration source tracker
335
+ * @param discoveredDirs - Array of discovered configuration directories
336
+ * @param logger - Logger instance for output
337
+ */ function displayConfigWithSources(config, tracker, discoveredDirs, logger) {
338
+ logger.info('\n' + '='.repeat(80));
339
+ logger.info('CONFIGURATION SOURCE ANALYSIS');
340
+ logger.info('='.repeat(80));
341
+ // Display discovered configuration hierarchy
342
+ logger.info('\nDISCOVERED CONFIGURATION HIERARCHY:');
343
+ if (discoveredDirs.length === 0) {
344
+ logger.info(' No configuration directories found in hierarchy');
345
+ } else {
346
+ discoveredDirs.sort((a, b)=>a.level - b.level) // Sort by precedence (lower level = higher precedence)
347
+ .forEach((dir)=>{
348
+ const precedence = dir.level === 0 ? '(highest precedence)' : dir.level === Math.max(...discoveredDirs.map((d)=>d.level)) ? '(lowest precedence)' : '';
349
+ logger.info(` Level ${dir.level}: ${dir.path} ${precedence}`);
350
+ });
351
+ }
352
+ // Display resolved configuration with sources
353
+ logger.info('\nRESOLVED CONFIGURATION WITH SOURCES:');
354
+ logger.info('Format: [Source] key: value\n');
355
+ const sortedKeys = Object.keys(tracker).sort();
356
+ const maxKeyLength = Math.max(...sortedKeys.map((k)=>k.length), 20);
357
+ const maxSourceLength = Math.max(...Object.values(tracker).map((info)=>info.sourceLabel.length), 25);
358
+ for (const key of sortedKeys){
359
+ const info = tracker[key];
360
+ const paddedKey = key.padEnd(maxKeyLength);
361
+ const paddedSource = info.sourceLabel.padEnd(maxSourceLength);
362
+ const formattedValue = formatConfigValue(info.value);
363
+ logger.info(`[${paddedSource}] ${paddedKey}: ${formattedValue}`);
364
+ }
365
+ // Display summary
366
+ logger.info('\n' + '-'.repeat(80));
367
+ logger.info('SUMMARY:');
368
+ logger.info(` Total configuration keys: ${Object.keys(tracker).length}`);
369
+ logger.info(` Configuration sources: ${discoveredDirs.length}`);
370
+ // Count values by source
371
+ const sourceCount = {};
372
+ for (const info of Object.values(tracker)){
373
+ sourceCount[info.sourceLabel] = (sourceCount[info.sourceLabel] || 0) + 1;
374
+ }
375
+ logger.info(' Values by source:');
376
+ for (const [source, count] of Object.entries(sourceCount)){
377
+ logger.info(` ${source}: ${count} value(s)`);
378
+ }
379
+ logger.info('='.repeat(80));
380
+ }
381
+ /**
382
+ * Checks and displays the resolved configuration with detailed source tracking.
383
+ *
384
+ * This function provides a git blame-like view of configuration resolution,
385
+ * showing which file and hierarchical level contributed each configuration value.
386
+ *
387
+ * @template T - The Zod schema shape type for configuration validation
388
+ * @param args - Parsed command-line arguments
389
+ * @param options - Cardigantime options with defaults, schema, and logger
390
+ * @returns Promise that resolves when the configuration check is complete
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * await checkConfig(cliArgs, {
395
+ * defaults: { configDirectory: './config', configFile: 'app.yaml' },
396
+ * configShape: MySchema.shape,
397
+ * logger: console,
398
+ * features: ['config', 'hierarchical']
399
+ * });
400
+ * // Outputs detailed configuration source analysis
401
+ * ```
402
+ */ const checkConfig = async (args, options)=>{
403
+ var _options_defaults, _options_defaults_pathResolution;
404
+ const logger = options.logger;
405
+ logger.info('Starting configuration check...');
406
+ const rawConfigDir = args.configDirectory || ((_options_defaults = options.defaults) === null || _options_defaults === void 0 ? void 0 : _options_defaults.configDirectory);
407
+ if (!rawConfigDir) {
408
+ throw new Error('Configuration directory must be specified');
409
+ }
410
+ const resolvedConfigDir = validateConfigDirectory(rawConfigDir);
411
+ logger.verbose(`Resolved config directory: ${resolvedConfigDir}`);
412
+ let rawFileConfig = {};
413
+ let discoveredDirs = [];
414
+ let tracker = {};
415
+ // Check if hierarchical configuration discovery is enabled
416
+ if (options.features.includes('hierarchical')) {
417
+ logger.verbose('Using hierarchical configuration discovery for source tracking');
418
+ try {
419
+ var _options_defaults_pathResolution1, _options_defaults_pathResolution2;
420
+ // Extract the config directory name from the path for hierarchical discovery
421
+ const configDirName = path.basename(resolvedConfigDir);
422
+ const startingDir = path.dirname(resolvedConfigDir);
423
+ logger.debug(`Using hierarchical discovery: configDirName=${configDirName}, startingDir=${startingDir}`);
424
+ const hierarchicalResult = await loadHierarchicalConfig({
425
+ configDirName,
426
+ configFileName: options.defaults.configFile,
427
+ startingDir,
428
+ encoding: options.defaults.encoding,
429
+ logger,
430
+ pathFields: (_options_defaults_pathResolution1 = options.defaults.pathResolution) === null || _options_defaults_pathResolution1 === void 0 ? void 0 : _options_defaults_pathResolution1.pathFields,
431
+ resolvePathArray: (_options_defaults_pathResolution2 = options.defaults.pathResolution) === null || _options_defaults_pathResolution2 === void 0 ? void 0 : _options_defaults_pathResolution2.resolvePathArray,
432
+ fieldOverlaps: options.defaults.fieldOverlaps
433
+ });
434
+ rawFileConfig = hierarchicalResult.config;
435
+ discoveredDirs = hierarchicalResult.discoveredDirs;
436
+ // Build detailed source tracking by re-loading each config individually
437
+ const trackers = [];
438
+ // Sort by level (highest level first = lowest precedence first) to match merge order
439
+ const sortedDirs = [
440
+ ...discoveredDirs
441
+ ].sort((a, b)=>b.level - a.level);
442
+ for (const dir of sortedDirs){
443
+ const storage = create({
444
+ log: logger.debug
445
+ });
446
+ const configFilePath = path.join(dir.path, options.defaults.configFile);
447
+ try {
448
+ const exists = await storage.exists(configFilePath);
449
+ if (!exists) continue;
450
+ const isReadable = await storage.isFileReadable(configFilePath);
451
+ if (!isReadable) continue;
452
+ const yamlContent = await storage.readFile(configFilePath, options.defaults.encoding);
453
+ const parsedYaml = yaml.load(yamlContent);
454
+ if (parsedYaml !== null && typeof parsedYaml === 'object') {
455
+ const levelTracker = trackConfigSources(parsedYaml, configFilePath, dir.level);
456
+ trackers.push(levelTracker);
457
+ }
458
+ } catch (error) {
459
+ logger.debug(`Error loading config for source tracking from ${configFilePath}: ${error.message}`);
460
+ }
461
+ }
462
+ // Merge trackers with proper precedence
463
+ tracker = mergeConfigTrackers(trackers);
464
+ if (hierarchicalResult.errors.length > 0) {
465
+ logger.warn('Configuration loading warnings:');
466
+ hierarchicalResult.errors.forEach((error)=>logger.warn(` ${error}`));
467
+ }
468
+ } catch (error) {
469
+ logger.error('Hierarchical configuration loading failed: ' + (error.message || 'Unknown error'));
470
+ logger.verbose('Falling back to single directory configuration loading');
471
+ // Fall back to single directory mode for source tracking
472
+ rawFileConfig = await loadSingleDirectoryConfig(resolvedConfigDir, options, logger);
473
+ const configFilePath = path.join(resolvedConfigDir, options.defaults.configFile);
474
+ tracker = trackConfigSources(rawFileConfig, configFilePath, 0);
475
+ discoveredDirs = [
476
+ {
477
+ path: resolvedConfigDir,
478
+ level: 0
479
+ }
480
+ ];
481
+ }
482
+ } else {
483
+ // Use traditional single directory configuration loading
484
+ logger.verbose('Using single directory configuration loading for source tracking');
485
+ rawFileConfig = await loadSingleDirectoryConfig(resolvedConfigDir, options, logger);
486
+ const configFilePath = path.join(resolvedConfigDir, options.defaults.configFile);
487
+ tracker = trackConfigSources(rawFileConfig, configFilePath, 0);
488
+ discoveredDirs = [
489
+ {
490
+ path: resolvedConfigDir,
491
+ level: 0
492
+ }
493
+ ];
494
+ }
495
+ // Apply path resolution if configured (this doesn't change source tracking)
496
+ let processedConfig = rawFileConfig;
497
+ if ((_options_defaults_pathResolution = options.defaults.pathResolution) === null || _options_defaults_pathResolution === void 0 ? void 0 : _options_defaults_pathResolution.pathFields) {
498
+ processedConfig = resolveConfigPaths(rawFileConfig, resolvedConfigDir, options.defaults.pathResolution.pathFields, options.defaults.pathResolution.resolvePathArray || []);
499
+ }
500
+ // Build final configuration including built-in values
501
+ const finalConfig = clean({
502
+ ...processedConfig,
503
+ configDirectory: resolvedConfigDir
504
+ });
505
+ // Add built-in configuration to tracker
506
+ tracker['configDirectory'] = {
507
+ value: resolvedConfigDir,
508
+ sourcePath: 'built-in',
509
+ level: -1,
510
+ sourceLabel: 'Built-in (runtime)'
511
+ };
512
+ // Display the configuration with source information
513
+ displayConfigWithSources(finalConfig, tracker, discoveredDirs, logger);
514
+ };
258
515
 
259
- export { read };
516
+ export { checkConfig, read };
260
517
  //# sourceMappingURL=read.js.map