@theunwalked/cardigantime 0.0.1 → 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 +699 -0
- package/dist/cardigantime.cjs +907 -15
- package/dist/cardigantime.cjs.map +1 -1
- package/dist/cardigantime.d.ts +42 -0
- package/dist/cardigantime.js +49 -345
- package/dist/cardigantime.js.map +1 -1
- package/dist/configure.d.ts +50 -1
- package/dist/configure.js +102 -3
- package/dist/configure.js.map +1 -1
- package/dist/constants.d.ts +17 -0
- package/dist/constants.js +17 -9
- package/dist/constants.js.map +1 -1
- package/dist/error/ArgumentError.d.ts +26 -0
- package/dist/error/ArgumentError.js +48 -0
- package/dist/error/ArgumentError.js.map +1 -0
- package/dist/error/ConfigurationError.d.ts +21 -0
- package/dist/error/ConfigurationError.js +46 -0
- package/dist/error/ConfigurationError.js.map +1 -0
- package/dist/error/FileSystemError.d.ts +30 -0
- package/dist/error/FileSystemError.js +58 -0
- package/dist/error/FileSystemError.js.map +1 -0
- package/dist/error/index.d.ts +3 -0
- package/dist/read.d.ts +30 -0
- package/dist/read.js +105 -12
- package/dist/read.js.map +1 -1
- package/dist/types.d.ts +63 -0
- package/dist/types.js +5 -3
- package/dist/types.js.map +1 -1
- package/dist/util/storage.js +33 -4
- package/dist/util/storage.js.map +1 -1
- package/dist/validate.d.ts +96 -1
- package/dist/validate.js +164 -20
- package/dist/validate.js.map +1 -1
- package/package.json +30 -23
- package/.gitcarve/config.yaml +0 -10
- package/.gitcarve/context/content.md +0 -1
- package/dist/configure.cjs +0 -12
- package/dist/configure.cjs.map +0 -1
- package/dist/constants.cjs +0 -35
- package/dist/constants.cjs.map +0 -1
- package/dist/read.cjs +0 -69
- package/dist/read.cjs.map +0 -1
- package/dist/types.cjs +0 -13
- package/dist/types.cjs.map +0 -1
- package/dist/util/storage.cjs +0 -149
- package/dist/util/storage.cjs.map +0 -1
- package/dist/validate.cjs +0 -130
- package/dist/validate.cjs.map +0 -1
- package/eslint.config.mjs +0 -82
- package/nodemon.json +0 -14
- package/vite.config.ts +0 -98
- package/vitest.config.ts +0 -17
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
|
+
|