@theunwalked/cardigantime 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,2 +1,701 @@
1
1
  # Cardigantime
2
2
 
3
+ A robust TypeScript library for configuration management in command-line applications. Cardigantime provides type-safe configuration loading, validation, and CLI integration with Commander.js and Zod schemas.
4
+
5
+ ## What is Cardigantime?
6
+
7
+ Cardigantime is a configuration management library designed to solve the common problem of handling configuration in CLI applications. It provides a unified way to:
8
+
9
+ - **Read configuration from YAML files** with intelligent file discovery
10
+ - **Validate configuration** using Zod schemas for type safetygit sta
11
+ - **Integrate with CLI frameworks** like Commander.js seamlessly
12
+ - **Merge configuration sources** (files, CLI args, defaults) with proper precedence
13
+ - **Handle errors gracefully** with comprehensive logging and user-friendly error messages
14
+
15
+ ## Why Cardigantime?
16
+
17
+ Building CLI applications with proper configuration management is harder than it should be. **Cardigantime was created specifically to solve the complex problem of supporting sophisticated configuration systems that seamlessly merge command-line arguments, configuration files, and default values.**
18
+
19
+ ### The Configuration Complexity Problem
20
+
21
+ Modern CLI applications need to handle increasingly complex configuration scenarios:
22
+
23
+ - **Multi-layered configuration sources** with proper precedence (CLI args > config files > defaults)
24
+ - **Nested configuration objects** with deep validation requirements
25
+ - **Environment-specific configurations** (development, staging, production)
26
+ - **Dynamic feature flags** and optional modules
27
+ - **Type safety** throughout the entire configuration pipeline
28
+ - **User-friendly error messages** when configuration goes wrong
29
+
30
+ ### What You Need to Handle
31
+
32
+ Without Cardigantime, building robust configuration management requires:
33
+
34
+ 1. **Parse command-line arguments** - handled by Commander.js, but integration is manual
35
+ 2. **Read configuration files** - YAML/JSON parsing with proper error handling
36
+ 3. **Implement sophisticated merging logic** - CLI args should override file config, which should override defaults, with proper deep merging
37
+ 4. **Validate complex nested structures** - ensure required fields exist, types are correct, and business rules are followed
38
+ 5. **Handle edge cases gracefully** - missing files, malformed YAML, permission errors, invalid paths
39
+ 6. **Provide actionable error messages** - users need to know exactly what's wrong and how to fix it
40
+ 7. **Maintain type safety** - TypeScript support with proper IntelliSense throughout the entire pipeline
41
+ 8. **Support advanced scenarios** - schema evolution, backward compatibility, configuration discovery
42
+
43
+ ### The Manual Approach Pain Points
44
+
45
+ Implementing this manually leads to common problems:
46
+
47
+ ```typescript
48
+ // Typical manual configuration merging - fragile and error-prone
49
+ const config = {
50
+ ...defaultConfig, // Defaults
51
+ ...yamlConfig, // File config
52
+ ...processCliArgs(args), // CLI overrides
53
+ };
54
+
55
+ // Problems:
56
+ // ❌ Shallow merging loses nested structure
57
+ // ❌ No validation until runtime failures
58
+ // ❌ Poor error messages: "Cannot read property 'x' of undefined"
59
+ // ❌ Type safety lost after merging
60
+ // ❌ No protection against typos in config files
61
+ // ❌ Manual path resolution and security checks
62
+ ```
63
+
64
+ ### How Cardigantime Solves This
65
+
66
+ Cardigantime provides a complete, battle-tested solution:
67
+
68
+ ```typescript
69
+ // Cardigantime approach - robust and type-safe
70
+ const cardigantime = create({
71
+ defaults: { configDirectory: './config' },
72
+ configShape: ComplexConfigSchema.shape, // Full type safety
73
+ });
74
+
75
+ const config = await cardigantime.read(args); // Smart merging
76
+ await cardigantime.validate(config); // Comprehensive validation
77
+
78
+ // Benefits:
79
+ // ✅ Deep merging preserves nested structures
80
+ // ✅ Schema validation with detailed error messages
81
+ // ✅ Full TypeScript support with IntelliSense
82
+ // ✅ Typo detection and helpful suggestions
83
+ // ✅ Built-in security protections
84
+ // ✅ Graceful error handling with actionable messages
85
+ ```
86
+
87
+ ### Real-World Example: Complex Configuration
88
+
89
+ Here's the kind of complex configuration Cardigantime was designed to handle:
90
+
91
+ ```typescript
92
+ const ComplexConfigSchema = z.object({
93
+ // Database configuration with multiple environments
94
+ database: z.object({
95
+ primary: z.object({
96
+ host: z.string().default('localhost'),
97
+ port: z.number().min(1).max(65535).default(5432),
98
+ ssl: z.boolean().default(false),
99
+ }),
100
+ replicas: z.array(z.string().url()).default([]),
101
+ maxConnections: z.number().positive().default(10),
102
+ }),
103
+
104
+ // Feature flags and optional modules
105
+ features: z.record(z.boolean()).default({}),
106
+
107
+ // API configuration with validation
108
+ api: z.object({
109
+ key: z.string().min(32, "API key must be at least 32 characters"),
110
+ timeout: z.number().min(1000).max(30000).default(5000),
111
+ retries: z.number().min(0).max(10).default(3),
112
+ baseUrl: z.string().url(),
113
+ }),
114
+
115
+ // Logging configuration
116
+ logging: z.object({
117
+ level: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
118
+ outputs: z.array(z.enum(['console', 'file', 'syslog'])).default(['console']),
119
+ rotation: z.object({
120
+ maxSize: z.string().regex(/^\d+[KMG]B$/),
121
+ maxFiles: z.number().positive().default(5),
122
+ }).optional(),
123
+ }),
124
+ });
125
+
126
+ // Users can now run:
127
+ // ./myapp --api-timeout 10000 --features-analytics true --config-directory ./prod-config
128
+ // And everything just works with full validation and type safety
129
+ ```
130
+
131
+ Cardigantime handles all of this complexity while providing excellent developer experience and robust error handling. **It was specifically created because existing solutions either lacked the sophistication needed for complex configuration scenarios or required too much boilerplate code to achieve proper integration between CLI arguments, configuration files, and defaults.**
132
+
133
+ ## Installation
134
+
135
+ ```bash
136
+ npm install @theunwalked/cardigantime
137
+ # or
138
+ pnpm add @theunwalked/cardigantime
139
+ # or
140
+ yarn add @theunwalked/cardigantime
141
+ ```
142
+
143
+ ## Quick Start
144
+
145
+ Here's a complete example of building a CLI tool with Cardigantime:
146
+
147
+ ```typescript
148
+ import { Command } from 'commander';
149
+ import { create } from '@theunwalked/cardigantime';
150
+ import { z } from 'zod';
151
+
152
+ // Define your configuration schema using Zod
153
+ const MyConfigSchema = z.object({
154
+ apiKey: z.string().min(1, "API key is required"),
155
+ timeout: z.number().min(1000).default(5000),
156
+ retries: z.number().min(0).max(10).default(3),
157
+ debug: z.boolean().default(false),
158
+ });
159
+
160
+ // Create a Cardigantime instance
161
+ const cardigantime = create({
162
+ defaults: {
163
+ configDirectory: './config', // Required: where to look for config files
164
+ configFile: 'myapp.yaml', // Optional: defaults to 'config.yaml'
165
+ isRequired: false, // Optional: whether config directory must exist
166
+ },
167
+ configShape: MyConfigSchema.shape, // Your Zod schema
168
+ features: ['config'], // Optional: enabled features
169
+ });
170
+
171
+ // Set up your CLI with Commander.js
172
+ async function main() {
173
+ const program = new Command();
174
+
175
+ program
176
+ .name('myapp')
177
+ .description('My awesome CLI application')
178
+ .version('1.0.0');
179
+
180
+ // Let Cardigantime add its CLI options (like --config-directory)
181
+ await cardigantime.configure(program);
182
+
183
+ // Add your own CLI options
184
+ program
185
+ .option('-k, --api-key <key>', 'API key for authentication')
186
+ .option('-t, --timeout <ms>', 'Request timeout in milliseconds', parseInt)
187
+ .option('--debug', 'Enable debug mode');
188
+
189
+ program.parse();
190
+ const args = program.opts();
191
+
192
+ try {
193
+ // Read and validate configuration
194
+ const config = await cardigantime.read(args);
195
+ await cardigantime.validate(config);
196
+
197
+ console.log('Configuration loaded successfully:', config);
198
+
199
+ // Your application logic here
200
+ await runMyApp(config);
201
+
202
+ } catch (error) {
203
+ console.error('Configuration error:', error.message);
204
+ process.exit(1);
205
+ }
206
+ }
207
+
208
+ main().catch(console.error);
209
+ ```
210
+
211
+ ### Example Configuration File (`config/myapp.yaml`)
212
+
213
+ ```yaml
214
+ apiKey: "your-secret-api-key"
215
+ timeout: 10000
216
+ retries: 5
217
+ debug: true
218
+ ```
219
+
220
+ ### Example Usage
221
+
222
+ ```bash
223
+ # Use config from file
224
+ ./myapp
225
+
226
+ # Override config with CLI arguments
227
+ ./myapp --api-key "different-key" --timeout 15000
228
+
229
+ # Use different config directory
230
+ ./myapp --config-directory /etc/myapp
231
+
232
+ # Enable debug mode
233
+ ./myapp --debug
234
+ ```
235
+
236
+ ## Core Concepts
237
+
238
+ ### 1. Configuration Sources & Precedence
239
+
240
+ Cardigantime merges configuration from multiple sources in this order (highest to lowest priority):
241
+
242
+ 1. **Command-line arguments** (highest priority)
243
+ 2. **Configuration file** (medium priority)
244
+ 3. **Default values** (lowest priority)
245
+
246
+ ```typescript
247
+ // If you have this config file:
248
+ // timeout: 5000
249
+ // debug: false
250
+
251
+ // And run: ./myapp --timeout 10000
252
+
253
+ // The final config will be:
254
+ // timeout: 10000 (from CLI, overrides file)
255
+ // debug: false (from file)
256
+ ```
257
+
258
+ ### 2. Schema Validation
259
+
260
+ All configuration is validated against your Zod schema:
261
+
262
+ ```typescript
263
+ const ConfigSchema = z.object({
264
+ port: z.number().min(1).max(65535),
265
+ host: z.string().ip().or(z.literal('localhost')),
266
+ database: z.object({
267
+ url: z.string().url(),
268
+ maxConnections: z.number().positive().default(10),
269
+ }),
270
+ features: z.array(z.enum(['auth', 'analytics', 'logging'])).default([]),
271
+ });
272
+
273
+ const cardigantime = create({
274
+ defaults: { configDirectory: './config' },
275
+ configShape: ConfigSchema.shape,
276
+ });
277
+ ```
278
+
279
+ ### 3. Type Safety
280
+
281
+ Cardigantime provides full TypeScript support:
282
+
283
+ ```typescript
284
+ // The config object is fully typed
285
+ const config = await cardigantime.read(args);
286
+ // config.database.maxConnections is number
287
+ // config.features is ('auth' | 'analytics' | 'logging')[]
288
+ // config.port is number
289
+
290
+ // IntelliSense works everywhere
291
+ if (config.features.includes('auth')) {
292
+ // Setup authentication
293
+ }
294
+ ```
295
+
296
+ ### 4. Error Handling
297
+
298
+ Cardigantime provides detailed error messages for common issues:
299
+
300
+ ```typescript
301
+ try {
302
+ await cardigantime.validate(config);
303
+ } catch (error) {
304
+ // Detailed validation errors:
305
+ // "Configuration validation failed: port must be between 1 and 65535"
306
+ // "Unknown configuration keys found: typoKey. Allowed keys are: port, host, database"
307
+ // "Config directory does not exist and is required: /nonexistent/path"
308
+ }
309
+ ```
310
+
311
+ ## API Reference
312
+
313
+ ### `create(options)`
314
+
315
+ Creates a new Cardigantime instance.
316
+
317
+ **Parameters:**
318
+ - `options.defaults` (required): Default configuration options
319
+ - `configDirectory` (required): Directory to look for config files
320
+ - `configFile` (optional): Config filename, defaults to `'config.yaml'`
321
+ - `isRequired` (optional): Whether config directory must exist, defaults to `false`
322
+ - `encoding` (optional): File encoding, defaults to `'utf8'`
323
+ - `options.configShape` (required): Zod schema shape for validation
324
+ - `options.features` (optional): Array of features to enable, defaults to `['config']`
325
+ - `options.logger` (optional): Custom logger implementation
326
+
327
+ **Returns:** `Cardigantime` instance
328
+
329
+ ### `cardigantime.configure(command)`
330
+
331
+ Adds Cardigantime's CLI options to a Commander.js command.
332
+
333
+ **Parameters:**
334
+ - `command`: Commander.js Command instance
335
+
336
+ **Returns:** Promise<Command> - The modified command
337
+
338
+ **Added Options:**
339
+ - `-c, --config-directory <path>`: Override config directory
340
+
341
+ ### `cardigantime.read(args)`
342
+
343
+ Reads and merges configuration from all sources.
344
+
345
+ **Parameters:**
346
+ - `args`: Parsed command-line arguments object
347
+
348
+ **Returns:** Promise<Config> - Merged and typed configuration object
349
+
350
+ ### `cardigantime.validate(config)`
351
+
352
+ Validates configuration against the schema.
353
+
354
+ **Parameters:**
355
+ - `config`: Configuration object to validate
356
+
357
+ **Returns:** Promise<void> - Throws on validation failure
358
+
359
+ ### `cardigantime.setLogger(logger)`
360
+
361
+ Sets a custom logger for debugging and error reporting.
362
+
363
+ **Parameters:**
364
+ - `logger`: Logger implementing the Logger interface
365
+
366
+ ## Advanced Usage
367
+
368
+ ### Custom Logger
369
+
370
+ ```typescript
371
+ import winston from 'winston';
372
+
373
+ const logger = winston.createLogger({
374
+ level: 'debug',
375
+ format: winston.format.json(),
376
+ transports: [
377
+ new winston.transports.File({ filename: 'app.log' }),
378
+ new winston.transports.Console(),
379
+ ],
380
+ });
381
+
382
+ const cardigantime = create({
383
+ defaults: { configDirectory: './config' },
384
+ configShape: MyConfigSchema.shape,
385
+ logger, // Use Winston for logging
386
+ });
387
+ ```
388
+
389
+ ### Complex Configuration Schema
390
+
391
+ ```typescript
392
+ const DatabaseConfig = z.object({
393
+ host: z.string(),
394
+ port: z.number().min(1).max(65535),
395
+ username: z.string(),
396
+ password: z.string(),
397
+ ssl: z.boolean().default(false),
398
+ });
399
+
400
+ const AppConfigSchema = z.object({
401
+ app: z.object({
402
+ name: z.string(),
403
+ version: z.string(),
404
+ environment: z.enum(['development', 'staging', 'production']),
405
+ }),
406
+ database: DatabaseConfig,
407
+ redis: z.object({
408
+ url: z.string().url(),
409
+ ttl: z.number().positive().default(3600),
410
+ }),
411
+ features: z.record(z.boolean()).default({}), // Dynamic feature flags
412
+ logging: z.object({
413
+ level: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
414
+ file: z.string().optional(),
415
+ }),
416
+ });
417
+ ```
418
+
419
+ ### Environment-Specific Configuration
420
+
421
+ ```typescript
422
+ // Use different config directories for different environments
423
+ const environment = process.env.NODE_ENV || 'development';
424
+
425
+ const cardigantime = create({
426
+ defaults: {
427
+ configDirectory: `./config/${environment}`,
428
+ configFile: 'app.yaml',
429
+ },
430
+ configShape: AppConfigSchema.shape,
431
+ });
432
+ ```
433
+
434
+ ### Configuration File Discovery
435
+
436
+ ```typescript
437
+ // Cardigantime will look for config files in this order:
438
+ // 1. CLI argument: --config-directory /path/to/config
439
+ // 2. Default directory: ./config
440
+ // 3. If not found and isRequired: false, continues with empty config
441
+ // 4. If not found and isRequired: true, throws error
442
+ ```
443
+
444
+ ## Error Handling
445
+
446
+ Cardigantime provides structured error types that allow you to handle different failure scenarios programmatically. All custom errors extend the standard JavaScript `Error` class and can be imported from the main package.
447
+
448
+ ### Error Types
449
+
450
+ ```typescript
451
+ import {
452
+ ConfigurationError,
453
+ FileSystemError,
454
+ ArgumentError
455
+ } from '@theunwalked/cardigantime';
456
+ ```
457
+
458
+ #### ConfigurationError
459
+
460
+ Thrown when configuration validation fails, contains extra keys, or schema issues occur.
461
+
462
+ **Properties:**
463
+ - `errorType`: `'validation' | 'schema' | 'extra_keys'`
464
+ - `details`: Additional error context (e.g., Zod error details, extra keys info)
465
+ - `configPath`: Path to the configuration file (when applicable)
466
+
467
+ #### FileSystemError
468
+
469
+ Thrown when file system operations fail (directory access, file reading, etc.).
470
+
471
+ **Properties:**
472
+ - `errorType`: `'not_found' | 'not_readable' | 'not_writable' | 'creation_failed' | 'operation_failed'`
473
+ - `path`: The file/directory path that caused the error
474
+ - `operation`: The operation that failed
475
+ - `originalError`: The underlying error (when applicable)
476
+
477
+ #### ArgumentError
478
+
479
+ Thrown when CLI arguments or function parameters are invalid.
480
+
481
+ **Properties:**
482
+ - `argument`: The name of the invalid argument
483
+
484
+ ### Error Handling Examples
485
+
486
+ #### Basic Error Handling
487
+
488
+ ```typescript
489
+ import { create, ConfigurationError, FileSystemError, ArgumentError } from '@theunwalked/cardigantime';
490
+
491
+ async function setupApp() {
492
+ const cardigantime = create({
493
+ defaults: { configDirectory: './config' },
494
+ configShape: MyConfigSchema.shape,
495
+ });
496
+
497
+ try {
498
+ const config = await cardigantime.read(args);
499
+ await cardigantime.validate(config);
500
+
501
+ // Your app logic here
502
+ await startApp(config);
503
+
504
+ } catch (error) {
505
+ if (error instanceof ConfigurationError) {
506
+ handleConfigError(error);
507
+ } else if (error instanceof FileSystemError) {
508
+ handleFileSystemError(error);
509
+ } else if (error instanceof ArgumentError) {
510
+ handleArgumentError(error);
511
+ } else {
512
+ console.error('Unexpected error:', error.message);
513
+ process.exit(1);
514
+ }
515
+ }
516
+ }
517
+ ```
518
+
519
+ #### Detailed Configuration Error Handling
520
+
521
+ ```typescript
522
+ function handleConfigError(error: ConfigurationError) {
523
+ switch (error.errorType) {
524
+ case 'validation':
525
+ console.error('❌ Configuration validation failed');
526
+ console.error('Details:', JSON.stringify(error.details, null, 2));
527
+ console.error('Please check your configuration values against the schema.');
528
+ break;
529
+
530
+ case 'extra_keys':
531
+ console.error('❌ Unknown configuration keys found');
532
+ console.error('Extra keys:', error.details.extraKeys.join(', '));
533
+ console.error('Allowed keys:', error.details.allowedKeys.join(', '));
534
+ console.error('Please remove the unknown keys or update your schema.');
535
+ break;
536
+
537
+ case 'schema':
538
+ console.error('❌ Configuration schema is invalid');
539
+ console.error('Details:', error.details);
540
+ break;
541
+ }
542
+
543
+ if (error.configPath) {
544
+ console.error(`Configuration file: ${error.configPath}`);
545
+ }
546
+
547
+ process.exit(1);
548
+ }
549
+ ```
550
+
551
+ #### File System Error Handling
552
+
553
+ ```typescript
554
+ function handleFileSystemError(error: FileSystemError) {
555
+ switch (error.errorType) {
556
+ case 'not_found':
557
+ if (error.operation === 'directory_access') {
558
+ console.error(`❌ Configuration directory not found: ${error.path}`);
559
+ console.error('Solutions:');
560
+ console.error(' 1. Create the directory: mkdir -p ' + error.path);
561
+ console.error(' 2. Use a different directory with --config-directory');
562
+ console.error(' 3. Set isRequired: false in your options');
563
+ } else {
564
+ console.error(`❌ Configuration file not found: ${error.path}`);
565
+ console.error('Create the configuration file or check the path.');
566
+ }
567
+ break;
568
+
569
+ case 'not_readable':
570
+ console.error(`❌ Cannot read ${error.path}`);
571
+ console.error('Check file/directory permissions:');
572
+ console.error(` chmod +r ${error.path}`);
573
+ break;
574
+
575
+ case 'creation_failed':
576
+ console.error(`❌ Failed to create directory: ${error.path}`);
577
+ console.error('Original error:', error.originalError?.message);
578
+ console.error('Check parent directory permissions.');
579
+ break;
580
+
581
+ case 'operation_failed':
582
+ console.error(`❌ File operation failed: ${error.operation}`);
583
+ console.error('Path:', error.path);
584
+ console.error('Error:', error.originalError?.message);
585
+ break;
586
+ }
587
+
588
+ process.exit(1);
589
+ }
590
+ ```
591
+
592
+ #### Argument Error Handling
593
+
594
+ ```typescript
595
+ function handleArgumentError(error: ArgumentError) {
596
+ console.error(`❌ Invalid argument: ${error.argument}`);
597
+ console.error(`Error: ${error.message}`);
598
+ console.error('Please check your command line arguments or function parameters.');
599
+ process.exit(1);
600
+ }
601
+ ```
602
+
603
+ #### Graceful Degradation
604
+
605
+ ```typescript
606
+ async function setupAppWithFallbacks() {
607
+ const cardigantime = create({
608
+ defaults: { configDirectory: './config' },
609
+ configShape: MyConfigSchema.shape,
610
+ });
611
+
612
+ try {
613
+ const config = await cardigantime.read(args);
614
+ await cardigantime.validate(config);
615
+ return config;
616
+
617
+ } catch (error) {
618
+ if (error instanceof FileSystemError && error.errorType === 'not_found') {
619
+ console.warn('⚠️ Configuration not found, using defaults');
620
+ return getDefaultConfig();
621
+ }
622
+
623
+ if (error instanceof ConfigurationError && error.errorType === 'extra_keys') {
624
+ console.warn('⚠️ Unknown config keys found, continuing with valid keys only');
625
+ // Filter out extra keys and retry
626
+ const cleanConfig = removeExtraKeys(config, error.details.allowedKeys);
627
+ await cardigantime.validate(cleanConfig);
628
+ return cleanConfig;
629
+ }
630
+
631
+ // Re-throw other errors
632
+ throw error;
633
+ }
634
+ }
635
+ ```
636
+
637
+ ### Error Messages and Troubleshooting
638
+
639
+ #### Common Configuration Errors
640
+
641
+ **Schema validation failed:**
642
+ ```typescript
643
+ // Error type: ConfigurationError with errorType: 'validation'
644
+ {
645
+ "port": {
646
+ "_errors": ["Number must be greater than or equal to 1"]
647
+ }
648
+ }
649
+ ```
650
+ *Solution:* Fix the configuration values to match your schema requirements.
651
+
652
+ **Unknown configuration keys:**
653
+ ```typescript
654
+ // Error type: ConfigurationError with errorType: 'extra_keys'
655
+ // error.details.extraKeys: ['databse']
656
+ // error.details.allowedKeys: ['database', 'port', 'host']
657
+ ```
658
+ *Solution:* Fix typos in your configuration file or update your schema.
659
+
660
+ #### Common File System Errors
661
+
662
+ **Configuration directory not found:**
663
+ ```typescript
664
+ // Error type: FileSystemError with errorType: 'not_found'
665
+ // error.path: '/etc/myapp'
666
+ // error.operation: 'directory_access'
667
+ ```
668
+ *Solutions:*
669
+ - Create the directory: `mkdir -p /etc/myapp`
670
+ - Use a different directory: `--config-directory ./config`
671
+ - Make it optional: `isRequired: false`
672
+
673
+ **Directory not readable:**
674
+ ```typescript
675
+ // Error type: FileSystemError with errorType: 'not_readable'
676
+ // error.path: '/etc/restricted'
677
+ // error.operation: 'directory_read'
678
+ ```
679
+ *Solution:* Check file permissions: `chmod +r /etc/restricted`
680
+
681
+ #### Common Argument Errors
682
+
683
+ **Invalid config directory argument:**
684
+ ```typescript
685
+ // Error type: ArgumentError with argument: 'config-directory'
686
+ // Triggered by: --config-directory ""
687
+ ```
688
+ *Solution:* Provide a valid directory path: `--config-directory ./config`
689
+
690
+ ## Contributing
691
+
692
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
693
+
694
+ ## License
695
+
696
+ Apache-2.0 - see [LICENSE](LICENSE) file for details.
697
+
698
+ ## Why "Cardigantime"?
699
+
700
+ Because configuration management should be as comfortable and reliable as your favorite cardigan. Just like a good cardigan keeps you warm and comfortable, Cardigantime keeps your application configuration cozy and well-organized.
701
+