@theunwalked/cardigantime 0.0.15 → 0.0.17

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
@@ -29,9 +29,9 @@ Cardigantime provides a complete, battle-tested solution for all of this complex
29
29
  ```bash
30
30
  npm install @theunwalked/cardigantime
31
31
  # or
32
- pnpm add @theunwalked/cardigantime
33
- # or
34
32
  yarn add @theunwalked/cardigantime
33
+ # or
34
+ pnpm add @theunwalked/cardigantime
35
35
  ```
36
36
 
37
37
  ## Quick Start
@@ -149,16 +149,16 @@ Comprehensive error handling with detailed, actionable error messages to help us
149
149
 
150
150
  ## Documentation
151
151
 
152
- 📚 **[Complete Documentation](https://semicolonambulance.github.io/cardigantime/)** - Full documentation site
152
+ 📚 **[Complete Documentation](https://tobrien.github.io/cardigantime/)** - Full documentation site
153
153
 
154
154
  **Quick Links:**
155
- - [Getting Started Guide](https://semicolonambulance.github.io/cardigantime/#getting-started) - Detailed setup and basic concepts
156
- - [Core Concepts](https://semicolonambulance.github.io/cardigantime/#core-concepts) - Configuration sources, hierarchical discovery
157
- - [API Reference](https://semicolonambulance.github.io/cardigantime/#api-reference) - Complete API documentation
158
- - [Configuration Options](https://semicolonambulance.github.io/cardigantime/#configuration-options) - All available options
159
- - [Debugging & Analysis](https://semicolonambulance.github.io/cardigantime/#debugging-analysis) - Tools for analyzing config
160
- - [Advanced Usage](https://semicolonambulance.github.io/cardigantime/#advanced-usage) - Complex examples and scenarios
161
- - [Error Handling](https://semicolonambulance.github.io/cardigantime/#error-handling) - Comprehensive error handling guide
155
+ - [Getting Started Guide](https://tobrien.github.io/cardigantime/#getting-started) - Detailed setup and basic concepts
156
+ - [Core Concepts](https://tobrien.github.io/cardigantime/#core-concepts) - Configuration sources, hierarchical discovery
157
+ - [API Reference](https://tobrien.github.io/cardigantime/#api-reference) - Complete API documentation
158
+ - [Configuration Options](https://tobrien.github.io/cardigantime/#configuration-options) - All available options
159
+ - [Debugging & Analysis](https://tobrien.github.io/cardigantime/#debugging-analysis) - Tools for analyzing config
160
+ - [Advanced Usage](https://tobrien.github.io/cardigantime/#advanced-usage) - Complex examples and scenarios
161
+ - [Error Handling](https://tobrien.github.io/cardigantime/#error-handling) - Comprehensive error handling guide
162
162
 
163
163
  ## Contributing
164
164
 
@@ -30,6 +30,19 @@ const yaml__namespace = /*#__PURE__*/_interopNamespaceDefault(yaml);
30
30
  const path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
31
31
  const fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
32
32
 
33
+ function _define_property$2(obj, key, value) {
34
+ if (key in obj) {
35
+ Object.defineProperty(obj, key, {
36
+ value: value,
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true
40
+ });
41
+ } else {
42
+ obj[key] = value;
43
+ }
44
+ return obj;
45
+ }
33
46
  /**
34
47
  * Error thrown when CLI arguments or function parameters are invalid.
35
48
  *
@@ -43,20 +56,7 @@ const fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
43
56
  * // Error message: "Path cannot be empty"
44
57
  * // error.argument: "config-directory"
45
58
  * ```
46
- */ function _define_property$2(obj, key, value) {
47
- if (key in obj) {
48
- Object.defineProperty(obj, key, {
49
- value: value,
50
- enumerable: true,
51
- configurable: true,
52
- writable: true
53
- });
54
- } else {
55
- obj[key] = value;
56
- }
57
- return obj;
58
- }
59
- class ArgumentError extends Error {
59
+ */ class ArgumentError extends Error {
60
60
  /**
61
61
  * Gets the name of the argument that caused this error.
62
62
  *
@@ -217,9 +217,7 @@ class ArgumentError extends Error {
217
217
  silly: ()=>{}
218
218
  };
219
219
 
220
- /**
221
- * Error thrown when file system operations fail
222
- */ function _define_property$1(obj, key, value) {
220
+ function _define_property$1(obj, key, value) {
223
221
  if (key in obj) {
224
222
  Object.defineProperty(obj, key, {
225
223
  value: value,
@@ -232,7 +230,9 @@ class ArgumentError extends Error {
232
230
  }
233
231
  return obj;
234
232
  }
235
- class FileSystemError extends Error {
233
+ /**
234
+ * Error thrown when file system operations fail
235
+ */ class FileSystemError extends Error {
236
236
  /**
237
237
  * Creates an error for when a required directory doesn't exist
238
238
  */ static directoryNotFound(path, isRequired = false) {
@@ -973,7 +973,8 @@ const create$1 = (params)=>{
973
973
  let discoveredConfigDirs = [];
974
974
  let resolvedConfigDirs = [];
975
975
  // Check if hierarchical configuration discovery is enabled
976
- if (options.features.includes('hierarchical')) {
976
+ // Use optional chaining for safety although options.features is defaulted
977
+ if (options.features && options.features.includes('hierarchical')) {
977
978
  logger.verbose('Hierarchical configuration discovery enabled');
978
979
  try {
979
980
  var _options_defaults_pathResolution1, _options_defaults_pathResolution2;
@@ -1252,7 +1253,8 @@ const create$1 = (params)=>{
1252
1253
  let resolvedConfigDirs = [];
1253
1254
  let tracker = {};
1254
1255
  // Check if hierarchical configuration discovery is enabled
1255
- if (options.features.includes('hierarchical')) {
1256
+ // Use optional chaining for safety although options.features is defaulted
1257
+ if (options.features && options.features.includes('hierarchical')) {
1256
1258
  logger.verbose('Using hierarchical configuration discovery for source tracking');
1257
1259
  try {
1258
1260
  var _options_defaults_pathResolution1, _options_defaults_pathResolution2;
@@ -1389,9 +1391,7 @@ const create$1 = (params)=>{
1389
1391
  displayConfigWithSources(finalConfig, tracker, discoveredDirs, logger);
1390
1392
  };
1391
1393
 
1392
- /**
1393
- * Error thrown when configuration validation fails
1394
- */ function _define_property(obj, key, value) {
1394
+ function _define_property(obj, key, value) {
1395
1395
  if (key in obj) {
1396
1396
  Object.defineProperty(obj, key, {
1397
1397
  value: value,
@@ -1404,7 +1404,9 @@ const create$1 = (params)=>{
1404
1404
  }
1405
1405
  return obj;
1406
1406
  }
1407
- class ConfigurationError extends Error {
1407
+ /**
1408
+ * Error thrown when configuration validation fails
1409
+ */ class ConfigurationError extends Error {
1408
1410
  /**
1409
1411
  * Creates a validation error for when config doesn't match the schema
1410
1412
  */ static validation(message, zodError, configPath) {
@@ -1444,22 +1446,22 @@ class ConfigurationError extends Error {
1444
1446
 
1445
1447
  /**
1446
1448
  * Recursively extracts all keys from a Zod schema in dot notation.
1447
- *
1449
+ *
1448
1450
  * This function traverses a Zod schema structure and builds a flat list
1449
1451
  * of all possible keys, using dot notation for nested objects. It handles
1450
1452
  * optional/nullable types by unwrapping them and supports arrays by
1451
1453
  * introspecting their element type.
1452
- *
1454
+ *
1453
1455
  * Special handling for:
1454
1456
  * - ZodOptional/ZodNullable: Unwraps to get the underlying type
1455
1457
  * - ZodAny/ZodRecord: Accepts any keys, returns the prefix or empty array
1456
1458
  * - ZodArray: Introspects the element type
1457
1459
  * - ZodObject: Recursively processes all shape properties
1458
- *
1460
+ *
1459
1461
  * @param schema - The Zod schema to introspect
1460
1462
  * @param prefix - Internal parameter for building nested key paths
1461
1463
  * @returns Array of strings representing all possible keys in dot notation
1462
- *
1464
+ *
1463
1465
  * @example
1464
1466
  * ```typescript
1465
1467
  * const schema = z.object({
@@ -1469,32 +1471,26 @@ class ConfigurationError extends Error {
1469
1471
  * }),
1470
1472
  * debug: z.boolean()
1471
1473
  * });
1472
- *
1474
+ *
1473
1475
  * const keys = listZodKeys(schema);
1474
1476
  * // Returns: ['user.name', 'user.settings.theme', 'debug']
1475
1477
  * ```
1476
1478
  */ const listZodKeys = (schema, prefix = '')=>{
1477
- // Check if schema has unwrap method (which both ZodOptional and ZodNullable have)
1478
- if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {
1479
- // Use type assertion to handle the unwrap method
1480
- const unwrappable = schema;
1481
- return listZodKeys(unwrappable.unwrap(), prefix);
1479
+ // Handle ZodOptional and ZodNullable - unwrap to get the underlying type
1480
+ if (schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodNullable) {
1481
+ return listZodKeys(schema.unwrap(), prefix);
1482
1482
  }
1483
1483
  // Handle ZodAny and ZodRecord - these accept any keys, so don't introspect
1484
- if (schema._def && (schema._def.typeName === 'ZodAny' || schema._def.typeName === 'ZodRecord')) {
1484
+ if (schema instanceof zod.z.ZodAny || schema instanceof zod.z.ZodRecord) {
1485
1485
  return prefix ? [
1486
1486
  prefix
1487
1487
  ] : [];
1488
1488
  }
1489
- if (schema._def && schema._def.typeName === 'ZodArray') {
1490
- // Use type assertion to handle the element property
1491
- const arraySchema = schema;
1492
- return listZodKeys(arraySchema.element, prefix);
1489
+ if (schema instanceof zod.z.ZodArray) {
1490
+ return listZodKeys(schema.element, prefix);
1493
1491
  }
1494
- if (schema._def && schema._def.typeName === 'ZodObject') {
1495
- // Use type assertion to handle the shape property
1496
- const objectSchema = schema;
1497
- return Object.entries(objectSchema.shape).flatMap(([key, subschema])=>{
1492
+ if (schema instanceof zod.z.ZodObject) {
1493
+ return Object.entries(schema.shape).flatMap(([key, subschema])=>{
1498
1494
  const fullKey = prefix ? `${prefix}.${key}` : key;
1499
1495
  const nested = listZodKeys(subschema, fullKey);
1500
1496
  return nested.length ? nested : fullKey;
@@ -1504,7 +1500,7 @@ class ConfigurationError extends Error {
1504
1500
  };
1505
1501
  /**
1506
1502
  * Type guard to check if a value is a plain object (not array, null, or other types).
1507
- *
1503
+ *
1508
1504
  * @param value - The value to check
1509
1505
  * @returns True if the value is a plain object
1510
1506
  */ const isPlainObject = (value)=>{
@@ -1552,26 +1548,26 @@ class ConfigurationError extends Error {
1552
1548
  };
1553
1549
  /**
1554
1550
  * Validates that the configuration object contains only keys allowed by the schema.
1555
- *
1551
+ *
1556
1552
  * This function prevents configuration errors by detecting typos or extra keys
1557
1553
  * that aren't defined in the Zod schema. It intelligently handles:
1558
1554
  * - ZodRecord types that accept arbitrary keys
1559
1555
  * - Nested objects and their key structures
1560
1556
  * - Arrays and their element key structures
1561
- *
1557
+ *
1562
1558
  * The function throws a ConfigurationError if extra keys are found, providing
1563
1559
  * helpful information about what keys are allowed vs. what was found.
1564
- *
1560
+ *
1565
1561
  * @param mergedSources - The merged configuration object to validate
1566
1562
  * @param fullSchema - The complete Zod schema including base and user schemas
1567
1563
  * @param logger - Logger for error reporting
1568
1564
  * @throws {ConfigurationError} When extra keys are found that aren't in the schema
1569
- *
1565
+ *
1570
1566
  * @example
1571
1567
  * ```typescript
1572
1568
  * const schema = z.object({ name: z.string(), age: z.number() });
1573
1569
  * const config = { name: 'John', age: 30, typo: 'invalid' };
1574
- *
1570
+ *
1575
1571
  * checkForExtraKeys(config, schema, console);
1576
1572
  * // Throws: ConfigurationError with details about 'typo' being an extra key
1577
1573
  * ```
@@ -1582,18 +1578,16 @@ class ConfigurationError extends Error {
1582
1578
  const recordPrefixes = new Set();
1583
1579
  // Find all prefixes that are ZodRecord types
1584
1580
  const findRecordPrefixes = (schema, prefix = '')=>{
1585
- if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {
1586
- const unwrappable = schema;
1587
- findRecordPrefixes(unwrappable.unwrap(), prefix);
1581
+ if (schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodNullable) {
1582
+ findRecordPrefixes(schema.unwrap(), prefix);
1588
1583
  return;
1589
1584
  }
1590
- if (schema._def && (schema._def.typeName === 'ZodAny' || schema._def.typeName === 'ZodRecord')) {
1585
+ if (schema instanceof zod.z.ZodAny || schema instanceof zod.z.ZodRecord) {
1591
1586
  if (prefix) recordPrefixes.add(prefix);
1592
1587
  return;
1593
1588
  }
1594
- if (schema._def && schema._def.typeName === 'ZodObject') {
1595
- const objectSchema = schema;
1596
- Object.entries(objectSchema.shape).forEach(([key, subschema])=>{
1589
+ if (schema instanceof zod.z.ZodObject) {
1590
+ Object.entries(schema.shape).forEach(([key, subschema])=>{
1597
1591
  const fullKey = prefix ? `${prefix}.${key}` : key;
1598
1592
  findRecordPrefixes(subschema, fullKey);
1599
1593
  });
@@ -1620,11 +1614,11 @@ class ConfigurationError extends Error {
1620
1614
  };
1621
1615
  /**
1622
1616
  * Validates that a configuration directory exists and is accessible.
1623
- *
1617
+ *
1624
1618
  * This function performs file system checks to ensure the configuration
1625
1619
  * directory can be used. It handles the isRequired flag to determine
1626
1620
  * whether a missing directory should cause an error or be silently ignored.
1627
- *
1621
+ *
1628
1622
  * @param configDirectory - Path to the configuration directory
1629
1623
  * @param isRequired - Whether the directory must exist
1630
1624
  * @param logger - Optional logger for debug information
@@ -1647,30 +1641,30 @@ class ConfigurationError extends Error {
1647
1641
  };
1648
1642
  /**
1649
1643
  * Validates a configuration object against the combined Zod schema.
1650
- *
1644
+ *
1651
1645
  * This is the main validation function that:
1652
1646
  * 1. Validates the configuration directory (if config feature enabled)
1653
1647
  * 2. Combines the base ConfigSchema with user-provided schema shape
1654
1648
  * 3. Checks for extra keys not defined in the schema
1655
1649
  * 4. Validates all values against their schema definitions
1656
1650
  * 5. Provides detailed error reporting for validation failures
1657
- *
1651
+ *
1658
1652
  * The validation is comprehensive and catches common configuration errors
1659
1653
  * including typos, missing required fields, wrong types, and invalid values.
1660
- *
1654
+ *
1661
1655
  * @template T - The Zod schema shape type for configuration validation
1662
1656
  * @param config - The merged configuration object to validate
1663
1657
  * @param options - Cardigantime options containing schema, defaults, and logger
1664
1658
  * @throws {ConfigurationError} When configuration validation fails
1665
1659
  * @throws {FileSystemError} When configuration directory validation fails
1666
- *
1660
+ *
1667
1661
  * @example
1668
1662
  * ```typescript
1669
1663
  * const schema = z.object({
1670
1664
  * apiKey: z.string().min(1),
1671
1665
  * timeout: z.number().positive(),
1672
1666
  * });
1673
- *
1667
+ *
1674
1668
  * await validate(config, {
1675
1669
  * configShape: schema.shape,
1676
1670
  * defaults: { configDirectory: './config', isRequired: true },
@@ -1703,18 +1697,17 @@ class ConfigurationError extends Error {
1703
1697
  };
1704
1698
 
1705
1699
  /**
1706
- * Extracts default values from a Zod schema recursively.
1707
- *
1708
- * This function traverses a Zod schema and builds an object containing
1709
- * all the default values defined in the schema. It handles:
1710
- * - ZodDefault types with explicit default values
1711
- * - ZodOptional/ZodNullable types by unwrapping them
1712
- * - ZodObject types by recursively processing their shape
1713
- * - ZodArray types by providing an empty array as default
1714
- *
1700
+ * Extracts default values from a Zod schema recursively using Zod v4's parsing mechanisms.
1701
+ *
1702
+ * This function leverages Zod's own parsing behavior to extract defaults rather than
1703
+ * accessing internal properties. It works by:
1704
+ * 1. For ZodDefault types: parsing undefined to trigger the default
1705
+ * 2. For ZodObject types: creating a minimal object and parsing to get all defaults
1706
+ * 3. For wrapped types: unwrapping and recursing
1707
+ *
1715
1708
  * @param schema - The Zod schema to extract defaults from
1716
1709
  * @returns An object containing all default values from the schema
1717
- *
1710
+ *
1718
1711
  * @example
1719
1712
  * ```typescript
1720
1713
  * const schema = z.object({
@@ -1726,68 +1719,72 @@ class ConfigurationError extends Error {
1726
1719
  * port: z.number().default(5432)
1727
1720
  * })
1728
1721
  * });
1729
- *
1722
+ *
1730
1723
  * const defaults = extractSchemaDefaults(schema);
1731
1724
  * // Returns: { name: 'app', port: 3000, debug: false, database: { host: 'localhost', port: 5432 } }
1732
1725
  * ```
1733
1726
  */ const extractSchemaDefaults = (schema)=>{
1734
- // Handle ZodDefault - extract the default value
1735
- if (schema._def && schema._def.typeName === 'ZodDefault') {
1736
- const defaultSchema = schema;
1737
- return defaultSchema._def.defaultValue();
1738
- }
1739
- // Handle ZodOptional and ZodNullable - only recurse if there's an explicit default
1740
- if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {
1741
- const unwrappable = schema;
1742
- const unwrapped = unwrappable.unwrap();
1743
- // Only provide defaults if the unwrapped schema has explicit defaults
1744
- // This prevents optional arrays/objects from automatically getting [] or {} defaults
1745
- if (unwrapped._def && unwrapped._def.typeName === 'ZodDefault') {
1746
- return extractSchemaDefaults(unwrapped);
1727
+ // Handle ZodDefault - parse undefined to get the default value
1728
+ if (schema instanceof zod.z.ZodDefault) {
1729
+ try {
1730
+ return schema.parse(undefined);
1731
+ } catch {
1732
+ // If parsing undefined fails, return undefined
1733
+ return undefined;
1747
1734
  }
1748
- // For optional fields without explicit defaults, return undefined
1749
- return undefined;
1750
- }
1751
- // Handle ZodObject - recursively process shape
1752
- if (schema._def && schema._def.typeName === 'ZodObject') {
1753
- const objectSchema = schema;
1754
- const result = {};
1755
- for (const [key, subschema] of Object.entries(objectSchema.shape)){
1735
+ }
1736
+ // Handle ZodOptional and ZodNullable by unwrapping
1737
+ if (schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodNullable) {
1738
+ return extractSchemaDefaults(schema.unwrap());
1739
+ }
1740
+ // Handle ZodObject - create an object with defaults by parsing an empty object
1741
+ if (schema instanceof zod.z.ZodObject) {
1742
+ const defaults = {};
1743
+ const shape = schema.shape;
1744
+ // First, try to extract defaults from individual fields
1745
+ for (const [key, subschema] of Object.entries(shape)){
1756
1746
  const defaultValue = extractSchemaDefaults(subschema);
1757
1747
  if (defaultValue !== undefined) {
1758
- result[key] = defaultValue;
1748
+ defaults[key] = defaultValue;
1759
1749
  }
1760
1750
  }
1761
- return Object.keys(result).length > 0 ? result : undefined;
1751
+ // Then parse an empty object to trigger any schema-level defaults
1752
+ const result = schema.safeParse({});
1753
+ if (result.success) {
1754
+ // Merge the parsed result with our extracted defaults
1755
+ return {
1756
+ ...defaults,
1757
+ ...result.data
1758
+ };
1759
+ }
1760
+ return Object.keys(defaults).length > 0 ? defaults : undefined;
1762
1761
  }
1763
- // Handle ZodArray - provide empty array as default
1764
- if (schema._def && schema._def.typeName === 'ZodArray') {
1765
- const arraySchema = schema;
1766
- const elementDefaults = extractSchemaDefaults(arraySchema.element);
1767
- // Return an empty array, or an array with one example element if it has defaults
1762
+ // Handle ZodArray - return empty array as a reasonable default
1763
+ if (schema instanceof zod.z.ZodArray) {
1764
+ const elementDefaults = extractSchemaDefaults(schema.element);
1768
1765
  return elementDefaults !== undefined ? [
1769
1766
  elementDefaults
1770
1767
  ] : [];
1771
1768
  }
1772
- // Handle ZodRecord - provide empty object as default
1773
- if (schema._def && schema._def.typeName === 'ZodRecord') {
1769
+ // Handle ZodRecord - return empty object as default
1770
+ if (schema instanceof zod.z.ZodRecord) {
1774
1771
  return {};
1775
1772
  }
1776
- // For other types, return undefined (no default available)
1773
+ // No default available for other schema types
1777
1774
  return undefined;
1778
1775
  };
1779
1776
  /**
1780
1777
  * Generates a complete configuration object with all default values populated.
1781
- *
1778
+ *
1782
1779
  * This function combines the base ConfigSchema with a user-provided schema shape
1783
1780
  * and extracts all available default values to create a complete configuration
1784
1781
  * example that can be serialized to YAML.
1785
- *
1782
+ *
1786
1783
  * @template T - The Zod schema shape type
1787
1784
  * @param configShape - The user's configuration schema shape
1788
1785
  * @param configDirectory - The configuration directory to include in the defaults
1789
1786
  * @returns An object containing all default values suitable for YAML serialization
1790
- *
1787
+ *
1791
1788
  * @example
1792
1789
  * ```typescript
1793
1790
  * const shape = z.object({
@@ -1795,7 +1792,7 @@ class ConfigurationError extends Error {
1795
1792
  * timeout: z.number().default(5000).describe('Request timeout in milliseconds'),
1796
1793
  * features: z.array(z.string()).default(['auth', 'logging'])
1797
1794
  * }).shape;
1798
- *
1795
+ *
1799
1796
  * const config = generateDefaultConfig(shape, './config');
1800
1797
  * // Returns: { timeout: 5000, features: ['auth', 'logging'] }
1801
1798
  * // Note: apiKey is not included since it has no default
@@ -1939,7 +1936,7 @@ class ConfigurationError extends Error {
1939
1936
  # Modify the values below to customize your application's behavior.
1940
1937
  #
1941
1938
  # For more information about Cardigantime configuration:
1942
- # https://github.com/SemicolonAmbulance/cardigantime
1939
+ # https://tobrien.github.io/cardigantime/
1943
1940
 
1944
1941
  `;
1945
1942
  const finalContent = header + yamlContent;