@theunwalked/cardigantime 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cardigantime.cjs +80 -85
- package/dist/cardigantime.cjs.map +1 -1
- package/dist/types.d.ts +3 -14
- package/dist/util/schema-defaults.d.ts +27 -9
- package/dist/util/schema-defaults.js +48 -45
- package/dist/util/schema-defaults.js.map +1 -1
- package/dist/validate.js +31 -39
- package/dist/validate.js.map +1 -1
- package/package.json +7 -10
package/dist/types.d.ts
CHANGED
|
@@ -143,21 +143,10 @@ export interface Args {
|
|
|
143
143
|
* Contains the minimum required configuration fields.
|
|
144
144
|
*/
|
|
145
145
|
export declare const ConfigSchema: ZodObject<{
|
|
146
|
-
/** The resolved configuration directory path */
|
|
147
146
|
configDirectory: z.ZodString;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
resolvedConfigDirs: z.ZodArray<z.ZodString, "many">;
|
|
152
|
-
}, "strip", z.ZodTypeAny, {
|
|
153
|
-
configDirectory: string;
|
|
154
|
-
discoveredConfigDirs: string[];
|
|
155
|
-
resolvedConfigDirs: string[];
|
|
156
|
-
}, {
|
|
157
|
-
configDirectory: string;
|
|
158
|
-
discoveredConfigDirs: string[];
|
|
159
|
-
resolvedConfigDirs: string[];
|
|
160
|
-
}>;
|
|
147
|
+
discoveredConfigDirs: z.ZodArray<z.ZodString>;
|
|
148
|
+
resolvedConfigDirs: z.ZodArray<z.ZodString>;
|
|
149
|
+
}, z.core.$strip>;
|
|
161
150
|
/**
|
|
162
151
|
* Base configuration type derived from the core schema.
|
|
163
152
|
*/
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
/**
|
|
3
|
-
* Extracts default values from a Zod schema recursively.
|
|
3
|
+
* Extracts default values from a Zod schema recursively using Zod v4's parsing mechanisms.
|
|
4
4
|
*
|
|
5
|
-
* This function
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - ZodArray types by providing an empty array as default
|
|
5
|
+
* This function leverages Zod's own parsing behavior to extract defaults rather than
|
|
6
|
+
* accessing internal properties. It works by:
|
|
7
|
+
* 1. For ZodDefault types: parsing undefined to trigger the default
|
|
8
|
+
* 2. For ZodObject types: creating a minimal object and parsing to get all defaults
|
|
9
|
+
* 3. For wrapped types: unwrapping and recursing
|
|
11
10
|
*
|
|
12
11
|
* @param schema - The Zod schema to extract defaults from
|
|
13
12
|
* @returns An object containing all default values from the schema
|
|
@@ -30,8 +29,27 @@ import { z } from 'zod';
|
|
|
30
29
|
*/
|
|
31
30
|
export declare const extractSchemaDefaults: (schema: z.ZodTypeAny) => any;
|
|
32
31
|
/**
|
|
33
|
-
* Extracts
|
|
34
|
-
*
|
|
32
|
+
* Extracts default values that should be included in generated config files.
|
|
33
|
+
*
|
|
34
|
+
* This function is similar to extractSchemaDefaults but filters out certain types
|
|
35
|
+
* of defaults that shouldn't appear in generated configuration files, such as
|
|
36
|
+
* computed defaults or system-specific values.
|
|
37
|
+
*
|
|
38
|
+
* @param schema - The Zod schema to extract config file defaults from
|
|
39
|
+
* @returns An object containing default values suitable for config files
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const schema = z.object({
|
|
44
|
+
* appName: z.string().default('my-app'),
|
|
45
|
+
* timestamp: z.number().default(() => Date.now()), // Excluded from config files
|
|
46
|
+
* port: z.number().default(3000)
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* const configDefaults = extractConfigFileDefaults(schema);
|
|
50
|
+
* // Returns: { appName: 'my-app', port: 3000 }
|
|
51
|
+
* // Note: timestamp is excluded because it's a function-based default
|
|
52
|
+
* ```
|
|
35
53
|
*/
|
|
36
54
|
export declare const extractConfigFileDefaults: (schema: z.ZodTypeAny) => any;
|
|
37
55
|
/**
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Extracts default values from a Zod schema recursively.
|
|
5
|
-
*
|
|
6
|
-
* This function
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
4
|
+
* Extracts default values from a Zod schema recursively using Zod v4's parsing mechanisms.
|
|
5
|
+
*
|
|
6
|
+
* This function leverages Zod's own parsing behavior to extract defaults rather than
|
|
7
|
+
* accessing internal properties. It works by:
|
|
8
|
+
* 1. For ZodDefault types: parsing undefined to trigger the default
|
|
9
|
+
* 2. For ZodObject types: creating a minimal object and parsing to get all defaults
|
|
10
|
+
* 3. For wrapped types: unwrapping and recursing
|
|
11
|
+
*
|
|
13
12
|
* @param schema - The Zod schema to extract defaults from
|
|
14
13
|
* @returns An object containing all default values from the schema
|
|
15
|
-
*
|
|
14
|
+
*
|
|
16
15
|
* @example
|
|
17
16
|
* ```typescript
|
|
18
17
|
* const schema = z.object({
|
|
@@ -24,68 +23,72 @@ import { z } from 'zod';
|
|
|
24
23
|
* port: z.number().default(5432)
|
|
25
24
|
* })
|
|
26
25
|
* });
|
|
27
|
-
*
|
|
26
|
+
*
|
|
28
27
|
* const defaults = extractSchemaDefaults(schema);
|
|
29
28
|
* // Returns: { name: 'app', port: 3000, debug: false, database: { host: 'localhost', port: 5432 } }
|
|
30
29
|
* ```
|
|
31
30
|
*/ const extractSchemaDefaults = (schema)=>{
|
|
32
|
-
// Handle ZodDefault -
|
|
33
|
-
if (schema
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const unwrappable = schema;
|
|
40
|
-
const unwrapped = unwrappable.unwrap();
|
|
41
|
-
// Only provide defaults if the unwrapped schema has explicit defaults
|
|
42
|
-
// This prevents optional arrays/objects from automatically getting [] or {} defaults
|
|
43
|
-
if (unwrapped._def && unwrapped._def.typeName === 'ZodDefault') {
|
|
44
|
-
return extractSchemaDefaults(unwrapped);
|
|
31
|
+
// Handle ZodDefault - parse undefined to get the default value
|
|
32
|
+
if (schema instanceof z.ZodDefault) {
|
|
33
|
+
try {
|
|
34
|
+
return schema.parse(undefined);
|
|
35
|
+
} catch {
|
|
36
|
+
// If parsing undefined fails, return undefined
|
|
37
|
+
return undefined;
|
|
45
38
|
}
|
|
46
|
-
// For optional fields without explicit defaults, return undefined
|
|
47
|
-
return undefined;
|
|
48
39
|
}
|
|
49
|
-
// Handle
|
|
50
|
-
if (schema.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
// Handle ZodOptional and ZodNullable by unwrapping
|
|
41
|
+
if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
|
|
42
|
+
return extractSchemaDefaults(schema.unwrap());
|
|
43
|
+
}
|
|
44
|
+
// Handle ZodObject - create an object with defaults by parsing an empty object
|
|
45
|
+
if (schema instanceof z.ZodObject) {
|
|
46
|
+
const defaults = {};
|
|
47
|
+
const shape = schema.shape;
|
|
48
|
+
// First, try to extract defaults from individual fields
|
|
49
|
+
for (const [key, subschema] of Object.entries(shape)){
|
|
54
50
|
const defaultValue = extractSchemaDefaults(subschema);
|
|
55
51
|
if (defaultValue !== undefined) {
|
|
56
|
-
|
|
52
|
+
defaults[key] = defaultValue;
|
|
57
53
|
}
|
|
58
54
|
}
|
|
59
|
-
|
|
55
|
+
// Then parse an empty object to trigger any schema-level defaults
|
|
56
|
+
const result = schema.safeParse({});
|
|
57
|
+
if (result.success) {
|
|
58
|
+
// Merge the parsed result with our extracted defaults
|
|
59
|
+
return {
|
|
60
|
+
...defaults,
|
|
61
|
+
...result.data
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return Object.keys(defaults).length > 0 ? defaults : undefined;
|
|
60
65
|
}
|
|
61
|
-
// Handle ZodArray -
|
|
62
|
-
if (schema
|
|
63
|
-
const
|
|
64
|
-
const elementDefaults = extractSchemaDefaults(arraySchema.element);
|
|
65
|
-
// Return an empty array, or an array with one example element if it has defaults
|
|
66
|
+
// Handle ZodArray - return empty array as a reasonable default
|
|
67
|
+
if (schema instanceof z.ZodArray) {
|
|
68
|
+
const elementDefaults = extractSchemaDefaults(schema.element);
|
|
66
69
|
return elementDefaults !== undefined ? [
|
|
67
70
|
elementDefaults
|
|
68
71
|
] : [];
|
|
69
72
|
}
|
|
70
|
-
// Handle ZodRecord -
|
|
71
|
-
if (schema
|
|
73
|
+
// Handle ZodRecord - return empty object as default
|
|
74
|
+
if (schema instanceof z.ZodRecord) {
|
|
72
75
|
return {};
|
|
73
76
|
}
|
|
74
|
-
//
|
|
77
|
+
// No default available for other schema types
|
|
75
78
|
return undefined;
|
|
76
79
|
};
|
|
77
80
|
/**
|
|
78
81
|
* Generates a complete configuration object with all default values populated.
|
|
79
|
-
*
|
|
82
|
+
*
|
|
80
83
|
* This function combines the base ConfigSchema with a user-provided schema shape
|
|
81
84
|
* and extracts all available default values to create a complete configuration
|
|
82
85
|
* example that can be serialized to YAML.
|
|
83
|
-
*
|
|
86
|
+
*
|
|
84
87
|
* @template T - The Zod schema shape type
|
|
85
88
|
* @param configShape - The user's configuration schema shape
|
|
86
89
|
* @param configDirectory - The configuration directory to include in the defaults
|
|
87
90
|
* @returns An object containing all default values suitable for YAML serialization
|
|
88
|
-
*
|
|
91
|
+
*
|
|
89
92
|
* @example
|
|
90
93
|
* ```typescript
|
|
91
94
|
* const shape = z.object({
|
|
@@ -93,7 +96,7 @@ import { z } from 'zod';
|
|
|
93
96
|
* timeout: z.number().default(5000).describe('Request timeout in milliseconds'),
|
|
94
97
|
* features: z.array(z.string()).default(['auth', 'logging'])
|
|
95
98
|
* }).shape;
|
|
96
|
-
*
|
|
99
|
+
*
|
|
97
100
|
* const config = generateDefaultConfig(shape, './config');
|
|
98
101
|
* // Returns: { timeout: 5000, features: ['auth', 'logging'] }
|
|
99
102
|
* // Note: apiKey is not included since it has no default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-defaults.js","sources":["../../src/util/schema-defaults.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Extracts default values from a Zod schema recursively.\n * \n * This function traverses a Zod schema and builds an object containing\n * all the default values defined in the schema. It handles:\n * - ZodDefault types with explicit default values\n * - ZodOptional/ZodNullable types by unwrapping them\n * - ZodObject types by recursively processing their shape\n * - ZodArray types by providing an empty array as default\n * \n * @param schema - The Zod schema to extract defaults from\n * @returns An object containing all default values from the schema\n * \n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().default('app'),\n * port: z.number().default(3000),\n * debug: z.boolean().default(false),\n * database: z.object({\n * host: z.string().default('localhost'),\n * port: z.number().default(5432)\n * })\n * });\n * \n * const defaults = extractSchemaDefaults(schema);\n * // Returns: { name: 'app', port: 3000, debug: false, database: { host: 'localhost', port: 5432 } }\n * ```\n */\nexport const extractSchemaDefaults = (schema: z.ZodTypeAny): any => {\n // Handle ZodDefault - extract the default value\n if (schema._def && schema._def.typeName === 'ZodDefault') {\n const defaultSchema = schema as z.ZodDefault<any>;\n return defaultSchema._def.defaultValue();\n }\n\n // Handle ZodOptional and ZodNullable - only recurse if there's an explicit default\n if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {\n const unwrappable = schema as z.ZodOptional<any> | z.ZodNullable<any>;\n const unwrapped = unwrappable.unwrap();\n\n // Only provide defaults if the unwrapped schema has explicit defaults\n // This prevents optional arrays/objects from automatically getting [] or {} defaults\n if (unwrapped._def && unwrapped._def.typeName === 'ZodDefault') {\n return extractSchemaDefaults(unwrapped);\n }\n\n // For optional fields without explicit defaults, return undefined\n return undefined;\n }\n\n // Handle ZodObject - recursively process shape\n if (schema._def && schema._def.typeName === 'ZodObject') {\n const objectSchema = schema as z.ZodObject<any>;\n const result: any = {};\n\n for (const [key, subschema] of Object.entries(objectSchema.shape)) {\n const defaultValue = extractSchemaDefaults(subschema as z.ZodTypeAny);\n if (defaultValue !== undefined) {\n result[key] = defaultValue;\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n }\n\n // Handle ZodArray - provide empty array as default\n if (schema._def && schema._def.typeName === 'ZodArray') {\n const arraySchema = schema as z.ZodArray<any>;\n const elementDefaults = extractSchemaDefaults(arraySchema.element);\n // Return an empty array, or an array with one example element if it has defaults\n return elementDefaults !== undefined ? [elementDefaults] : [];\n }\n\n // Handle ZodRecord - provide empty object as default\n if (schema._def && schema._def.typeName === 'ZodRecord') {\n return {};\n }\n\n // For other types, return undefined (no default available)\n return undefined;\n};\n\n/**\n * Extracts meaningful defaults for config file generation, including sensible defaults for optional fields.\n * This is more generous than extractSchemaDefaults for the purpose of creating helpful config files.\n */\nexport const extractConfigFileDefaults = (schema: z.ZodTypeAny): any => {\n // Handle ZodDefault - extract the default value\n if (schema._def && schema._def.typeName === 'ZodDefault') {\n const defaultSchema = schema as z.ZodDefault<any>;\n return defaultSchema._def.defaultValue();\n }\n\n // Handle ZodOptional and ZodNullable - provide sensible defaults for config generation\n if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {\n const unwrappable = schema as z.ZodOptional<any> | z.ZodNullable<any>;\n const unwrapped = unwrappable.unwrap();\n\n // Recurse into the unwrapped schema to get its default or provide a sensible one\n const unwrappedDefault = extractConfigFileDefaults(unwrapped);\n if (unwrappedDefault !== undefined) {\n return unwrappedDefault;\n }\n\n // Provide sensible defaults for common types when generating config files\n if (unwrapped._def) {\n switch (unwrapped._def.typeName) {\n case 'ZodBoolean':\n return false;\n case 'ZodNumber':\n return 0;\n case 'ZodString':\n return '';\n case 'ZodArray':\n return [];\n case 'ZodRecord':\n return {};\n case 'ZodObject':\n return extractConfigFileDefaults(unwrapped);\n }\n }\n\n return undefined;\n }\n\n // Handle ZodObject - recursively process shape\n if (schema._def && schema._def.typeName === 'ZodObject') {\n const objectSchema = schema as z.ZodObject<any>;\n const result: any = {};\n\n for (const [key, subschema] of Object.entries(objectSchema.shape)) {\n const defaultValue = extractConfigFileDefaults(subschema as z.ZodTypeAny);\n if (defaultValue !== undefined) {\n result[key] = defaultValue;\n }\n }\n\n return Object.keys(result).length > 0 ? result : {};\n }\n\n // Handle ZodArray - provide empty array as default\n if (schema._def && schema._def.typeName === 'ZodArray') {\n return [];\n }\n\n // Handle ZodRecord - provide empty object as default\n if (schema._def && schema._def.typeName === 'ZodRecord') {\n return {};\n }\n\n // For other types, return undefined (no default available)\n return undefined;\n};\n\n/**\n * Generates a complete configuration object with all default values populated.\n * \n * This function combines the base ConfigSchema with a user-provided schema shape\n * and extracts all available default values to create a complete configuration\n * example that can be serialized to YAML.\n * \n * @template T - The Zod schema shape type\n * @param configShape - The user's configuration schema shape\n * @param configDirectory - The configuration directory to include in the defaults\n * @returns An object containing all default values suitable for YAML serialization\n * \n * @example\n * ```typescript\n * const shape = z.object({\n * apiKey: z.string().describe('Your API key'),\n * timeout: z.number().default(5000).describe('Request timeout in milliseconds'),\n * features: z.array(z.string()).default(['auth', 'logging'])\n * }).shape;\n * \n * const config = generateDefaultConfig(shape, './config');\n * // Returns: { timeout: 5000, features: ['auth', 'logging'] }\n * // Note: apiKey is not included since it has no default\n * ```\n */\nexport const generateDefaultConfig = <T extends z.ZodRawShape>(\n configShape: T,\n _configDirectory: string\n): Record<string, any> => {\n // Create the full schema by combining base and user schema\n const fullSchema = z.object({\n ...configShape,\n });\n\n // Extract defaults from the full schema using only explicit defaults\n const defaults = extractSchemaDefaults(fullSchema);\n\n // Don't include configDirectory in the generated file since it's runtime-specific\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { configDirectory: _, ...configDefaults } = defaults || {};\n\n return configDefaults || {};\n}; "],"names":["extractSchemaDefaults","schema","_def","typeName","defaultSchema","defaultValue","unwrappable","unwrapped","unwrap","undefined","objectSchema","result","key","subschema","Object","entries","shape","keys","length","arraySchema","elementDefaults","element","generateDefaultConfig","configShape","_configDirectory","fullSchema","z","object","defaults","configDirectory","_","configDefaults"],"mappings":";;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6BO,MAAMA,qBAAAA,GAAwB,CAACC,MAAAA,GAAAA;;IAElC,IAAIA,MAAAA,CAAOC,IAAI,IAAID,MAAAA,CAAOC,IAAI,CAACC,QAAQ,KAAK,YAAA,EAAc;AACtD,QAAA,MAAMC,aAAAA,GAAgBH,MAAAA;QACtB,OAAOG,aAAAA,CAAcF,IAAI,CAACG,YAAY,EAAA;AAC1C,IAAA;;AAGA,IAAA,IAAIJ,OAAOC,IAAI,KAAKD,MAAAA,CAAOC,IAAI,CAACC,QAAQ,KAAK,aAAA,IAAiBF,OAAOC,IAAI,CAACC,QAAQ,KAAK,aAAY,CAAA,EAAI;AACnG,QAAA,MAAMG,WAAAA,GAAcL,MAAAA;QACpB,MAAMM,SAAAA,GAAYD,YAAYE,MAAM,EAAA;;;QAIpC,IAAID,SAAAA,CAAUL,IAAI,IAAIK,SAAAA,CAAUL,IAAI,CAACC,QAAQ,KAAK,YAAA,EAAc;AAC5D,YAAA,OAAOH,qBAAAA,CAAsBO,SAAAA,CAAAA;AACjC,QAAA;;QAGA,OAAOE,SAAAA;AACX,IAAA;;IAGA,IAAIR,MAAAA,CAAOC,IAAI,IAAID,MAAAA,CAAOC,IAAI,CAACC,QAAQ,KAAK,WAAA,EAAa;AACrD,QAAA,MAAMO,YAAAA,GAAeT,MAAAA;AACrB,QAAA,MAAMU,SAAc,EAAC;QAErB,KAAK,MAAM,CAACC,GAAAA,EAAKC,SAAAA,CAAU,IAAIC,OAAOC,OAAO,CAACL,YAAAA,CAAaM,KAAK,CAAA,CAAG;AAC/D,YAAA,MAAMX,eAAeL,qBAAAA,CAAsBa,SAAAA,CAAAA;AAC3C,YAAA,IAAIR,iBAAiBI,SAAAA,EAAW;gBAC5BE,MAAM,CAACC,IAAI,GAAGP,YAAAA;AAClB,YAAA;AACJ,QAAA;AAEA,QAAA,OAAOS,OAAOG,IAAI,CAACN,QAAQO,MAAM,GAAG,IAAIP,MAAAA,GAASF,SAAAA;AACrD,IAAA;;IAGA,IAAIR,MAAAA,CAAOC,IAAI,IAAID,MAAAA,CAAOC,IAAI,CAACC,QAAQ,KAAK,UAAA,EAAY;AACpD,QAAA,MAAMgB,WAAAA,GAAclB,MAAAA;QACpB,MAAMmB,eAAAA,GAAkBpB,qBAAAA,CAAsBmB,WAAAA,CAAYE,OAAO,CAAA;;AAEjE,QAAA,OAAOD,oBAAoBX,SAAAA,GAAY;AAACW,YAAAA;AAAgB,SAAA,GAAG,EAAE;AACjE,IAAA;;IAGA,IAAInB,MAAAA,CAAOC,IAAI,IAAID,MAAAA,CAAOC,IAAI,CAACC,QAAQ,KAAK,WAAA,EAAa;AACrD,QAAA,OAAO,EAAC;AACZ,IAAA;;IAGA,OAAOM,SAAAA;AACX;AA0EA;;;;;;;;;;;;;;;;;;;;;;;;AAwBC,IACM,MAAMa,qBAAAA,GAAwB,CACjCC,WAAAA,EACAC,gBAAAA,GAAAA;;IAGA,MAAMC,UAAAA,GAAaC,CAAAA,CAAEC,MAAM,CAAC;AACxB,QAAA,GAAGJ;AACP,KAAA,CAAA;;AAGA,IAAA,MAAMK,WAAW5B,qBAAAA,CAAsByB,UAAAA,CAAAA;;;IAIvC,MAAM,EAAEI,iBAAiBC,CAAC,EAAE,GAAGC,cAAAA,EAAgB,GAAGH,YAAY,EAAC;AAE/D,IAAA,OAAOG,kBAAkB,EAAC;AAC9B;;;;"}
|
|
1
|
+
{"version":3,"file":"schema-defaults.js","sources":["../../src/util/schema-defaults.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Extracts default values from a Zod schema recursively using Zod v4's parsing mechanisms.\n *\n * This function leverages Zod's own parsing behavior to extract defaults rather than\n * accessing internal properties. It works by:\n * 1. For ZodDefault types: parsing undefined to trigger the default\n * 2. For ZodObject types: creating a minimal object and parsing to get all defaults\n * 3. For wrapped types: unwrapping and recursing\n *\n * @param schema - The Zod schema to extract defaults from\n * @returns An object containing all default values from the schema\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().default('app'),\n * port: z.number().default(3000),\n * debug: z.boolean().default(false),\n * database: z.object({\n * host: z.string().default('localhost'),\n * port: z.number().default(5432)\n * })\n * });\n *\n * const defaults = extractSchemaDefaults(schema);\n * // Returns: { name: 'app', port: 3000, debug: false, database: { host: 'localhost', port: 5432 } }\n * ```\n */\nexport const extractSchemaDefaults = (schema: z.ZodTypeAny): any => {\n // Handle ZodDefault - parse undefined to get the default value\n if (schema instanceof z.ZodDefault) {\n try {\n return schema.parse(undefined);\n } catch {\n // If parsing undefined fails, return undefined\n return undefined;\n }\n }\n\n // Handle ZodOptional and ZodNullable by unwrapping\n if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {\n return extractSchemaDefaults(schema.unwrap() as any);\n }\n\n // Handle ZodObject - create an object with defaults by parsing an empty object\n if (schema instanceof z.ZodObject) {\n const defaults: any = {};\n const shape = schema.shape;\n\n // First, try to extract defaults from individual fields\n for (const [key, subschema] of Object.entries(shape)) {\n const defaultValue = extractSchemaDefaults(subschema as any);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n\n // Then parse an empty object to trigger any schema-level defaults\n const result = schema.safeParse({});\n if (result.success) {\n // Merge the parsed result with our extracted defaults\n return { ...defaults, ...result.data };\n }\n\n return Object.keys(defaults).length > 0 ? defaults : undefined;\n }\n\n // Handle ZodArray - return empty array as a reasonable default\n if (schema instanceof z.ZodArray) {\n const elementDefaults = extractSchemaDefaults(schema.element as any);\n return elementDefaults !== undefined ? [elementDefaults] : [];\n }\n\n // Handle ZodRecord - return empty object as default\n if (schema instanceof z.ZodRecord) {\n return {};\n }\n\n // No default available for other schema types\n return undefined;\n};\n\n/**\n * Extracts default values that should be included in generated config files.\n *\n * This function is similar to extractSchemaDefaults but filters out certain types\n * of defaults that shouldn't appear in generated configuration files, such as\n * computed defaults or system-specific values.\n *\n * @param schema - The Zod schema to extract config file defaults from\n * @returns An object containing default values suitable for config files\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * appName: z.string().default('my-app'),\n * timestamp: z.number().default(() => Date.now()), // Excluded from config files\n * port: z.number().default(3000)\n * });\n *\n * const configDefaults = extractConfigFileDefaults(schema);\n * // Returns: { appName: 'my-app', port: 3000 }\n * // Note: timestamp is excluded because it's a function-based default\n * ```\n */\nexport const extractConfigFileDefaults = (schema: z.ZodTypeAny): any => {\n // Handle ZodDefault - parse undefined to get the default value\n if (schema instanceof z.ZodDefault) {\n try {\n const defaultValue = schema.parse(undefined);\n // Exclude function-generated defaults from config files\n // These are typically runtime-computed values\n if (typeof defaultValue === 'function') {\n return undefined;\n }\n return defaultValue;\n } catch {\n return undefined;\n }\n }\n\n // Handle ZodOptional and ZodNullable by unwrapping\n if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {\n return extractConfigFileDefaults(schema.unwrap() as any);\n }\n\n // Handle ZodObject - extract defaults suitable for config files\n if (schema instanceof z.ZodObject) {\n const defaults: any = {};\n const shape = schema.shape;\n\n for (const [key, subschema] of Object.entries(shape)) {\n const defaultValue = extractConfigFileDefaults(subschema as any);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n\n // Parse an empty object to get any schema-level defaults\n const result = schema.safeParse({});\n if (result.success) {\n // Filter out any function-based or computed values\n const filteredData: any = {};\n for (const [key, value] of Object.entries(result.data)) {\n if (typeof value !== 'function' && value !== null) {\n filteredData[key] = value;\n }\n }\n return { ...defaults, ...filteredData };\n }\n\n return Object.keys(defaults).length > 0 ? defaults : undefined;\n }\n\n // Handle ZodArray - typically don't include array defaults in config files\n if (schema instanceof z.ZodArray) {\n // For config files, we usually don't want to pre-populate arrays\n return undefined;\n }\n\n // Handle ZodRecord - return empty object as default for config files\n if (schema instanceof z.ZodRecord) {\n return {};\n }\n\n // No default available for other schema types\n return undefined;\n};\n\n/**\n * Generates a complete configuration object with all default values populated.\n *\n * This function combines the base ConfigSchema with a user-provided schema shape\n * and extracts all available default values to create a complete configuration\n * example that can be serialized to YAML.\n *\n * @template T - The Zod schema shape type\n * @param configShape - The user's configuration schema shape\n * @param configDirectory - The configuration directory to include in the defaults\n * @returns An object containing all default values suitable for YAML serialization\n *\n * @example\n * ```typescript\n * const shape = z.object({\n * apiKey: z.string().describe('Your API key'),\n * timeout: z.number().default(5000).describe('Request timeout in milliseconds'),\n * features: z.array(z.string()).default(['auth', 'logging'])\n * }).shape;\n *\n * const config = generateDefaultConfig(shape, './config');\n * // Returns: { timeout: 5000, features: ['auth', 'logging'] }\n * // Note: apiKey is not included since it has no default\n * ```\n */\nexport const generateDefaultConfig = <T extends z.ZodRawShape>(\n configShape: T,\n _configDirectory: string\n): Record<string, any> => {\n // Create the full schema by combining base and user schema\n const fullSchema = z.object({\n ...configShape,\n });\n\n // Extract defaults from the full schema using only explicit defaults\n const defaults = extractSchemaDefaults(fullSchema);\n\n // Don't include configDirectory in the generated file since it's runtime-specific\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { configDirectory: _, ...configDefaults } = defaults || {};\n\n return configDefaults || {};\n};\n\n"],"names":["extractSchemaDefaults","schema","z","ZodDefault","parse","undefined","ZodOptional","ZodNullable","unwrap","ZodObject","defaults","shape","key","subschema","Object","entries","defaultValue","result","safeParse","success","data","keys","length","ZodArray","elementDefaults","element","ZodRecord","generateDefaultConfig","configShape","_configDirectory","fullSchema","object","configDirectory","_","configDefaults"],"mappings":";;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4BO,MAAMA,qBAAAA,GAAwB,CAACC,MAAAA,GAAAA;;IAElC,IAAIA,MAAAA,YAAkBC,CAAAA,CAAEC,UAAU,EAAE;QAChC,IAAI;YACA,OAAOF,MAAAA,CAAOG,KAAK,CAACC,SAAAA,CAAAA;AACxB,QAAA,CAAA,CAAE,OAAM;;YAEJ,OAAOA,SAAAA;AACX,QAAA;AACJ,IAAA;;AAGA,IAAA,IAAIJ,kBAAkBC,CAAAA,CAAEI,WAAW,IAAIL,MAAAA,YAAkBC,CAAAA,CAAEK,WAAW,EAAE;QACpE,OAAOP,qBAAAA,CAAsBC,OAAOO,MAAM,EAAA,CAAA;AAC9C,IAAA;;IAGA,IAAIP,MAAAA,YAAkBC,CAAAA,CAAEO,SAAS,EAAE;AAC/B,QAAA,MAAMC,WAAgB,EAAC;QACvB,MAAMC,KAAAA,GAAQV,OAAOU,KAAK;;QAG1B,KAAK,MAAM,CAACC,GAAAA,EAAKC,SAAAA,CAAU,IAAIC,MAAAA,CAAOC,OAAO,CAACJ,KAAAA,CAAAA,CAAQ;AAClD,YAAA,MAAMK,eAAehB,qBAAAA,CAAsBa,SAAAA,CAAAA;AAC3C,YAAA,IAAIG,iBAAiBX,SAAAA,EAAW;gBAC5BK,QAAQ,CAACE,IAAI,GAAGI,YAAAA;AACpB,YAAA;AACJ,QAAA;;AAGA,QAAA,MAAMC,MAAAA,GAAShB,MAAAA,CAAOiB,SAAS,CAAC,EAAC,CAAA;QACjC,IAAID,MAAAA,CAAOE,OAAO,EAAE;;YAEhB,OAAO;AAAE,gBAAA,GAAGT,QAAQ;AAAE,gBAAA,GAAGO,OAAOG;AAAK,aAAA;AACzC,QAAA;AAEA,QAAA,OAAON,OAAOO,IAAI,CAACX,UAAUY,MAAM,GAAG,IAAIZ,QAAAA,GAAWL,SAAAA;AACzD,IAAA;;IAGA,IAAIJ,MAAAA,YAAkBC,CAAAA,CAAEqB,QAAQ,EAAE;QAC9B,MAAMC,eAAAA,GAAkBxB,qBAAAA,CAAsBC,MAAAA,CAAOwB,OAAO,CAAA;AAC5D,QAAA,OAAOD,oBAAoBnB,SAAAA,GAAY;AAACmB,YAAAA;AAAgB,SAAA,GAAG,EAAE;AACjE,IAAA;;IAGA,IAAIvB,MAAAA,YAAkBC,CAAAA,CAAEwB,SAAS,EAAE;AAC/B,QAAA,OAAO,EAAC;AACZ,IAAA;;IAGA,OAAOrB,SAAAA;AACX;AAyFA;;;;;;;;;;;;;;;;;;;;;;;;AAwBC,IACM,MAAMsB,qBAAAA,GAAwB,CACjCC,WAAAA,EACAC,gBAAAA,GAAAA;;IAGA,MAAMC,UAAAA,GAAa5B,CAAAA,CAAE6B,MAAM,CAAC;AACxB,QAAA,GAAGH;AACP,KAAA,CAAA;;AAGA,IAAA,MAAMlB,WAAWV,qBAAAA,CAAsB8B,UAAAA,CAAAA;;;IAIvC,MAAM,EAAEE,iBAAiBC,CAAC,EAAE,GAAGC,cAAAA,EAAgB,GAAGxB,YAAY,EAAC;AAE/D,IAAA,OAAOwB,kBAAkB,EAAC;AAC9B;;;;"}
|
package/dist/validate.js
CHANGED
|
@@ -6,22 +6,22 @@ import { create } from './util/storage.js';
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Recursively extracts all keys from a Zod schema in dot notation.
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
10
|
* This function traverses a Zod schema structure and builds a flat list
|
|
11
11
|
* of all possible keys, using dot notation for nested objects. It handles
|
|
12
12
|
* optional/nullable types by unwrapping them and supports arrays by
|
|
13
13
|
* introspecting their element type.
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* Special handling for:
|
|
16
16
|
* - ZodOptional/ZodNullable: Unwraps to get the underlying type
|
|
17
17
|
* - ZodAny/ZodRecord: Accepts any keys, returns the prefix or empty array
|
|
18
18
|
* - ZodArray: Introspects the element type
|
|
19
19
|
* - ZodObject: Recursively processes all shape properties
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
21
|
* @param schema - The Zod schema to introspect
|
|
22
22
|
* @param prefix - Internal parameter for building nested key paths
|
|
23
23
|
* @returns Array of strings representing all possible keys in dot notation
|
|
24
|
-
*
|
|
24
|
+
*
|
|
25
25
|
* @example
|
|
26
26
|
* ```typescript
|
|
27
27
|
* const schema = z.object({
|
|
@@ -31,32 +31,26 @@ import { create } from './util/storage.js';
|
|
|
31
31
|
* }),
|
|
32
32
|
* debug: z.boolean()
|
|
33
33
|
* });
|
|
34
|
-
*
|
|
34
|
+
*
|
|
35
35
|
* const keys = listZodKeys(schema);
|
|
36
36
|
* // Returns: ['user.name', 'user.settings.theme', 'debug']
|
|
37
37
|
* ```
|
|
38
38
|
*/ const listZodKeys = (schema, prefix = '')=>{
|
|
39
|
-
//
|
|
40
|
-
if (schema
|
|
41
|
-
|
|
42
|
-
const unwrappable = schema;
|
|
43
|
-
return listZodKeys(unwrappable.unwrap(), prefix);
|
|
39
|
+
// Handle ZodOptional and ZodNullable - unwrap to get the underlying type
|
|
40
|
+
if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
|
|
41
|
+
return listZodKeys(schema.unwrap(), prefix);
|
|
44
42
|
}
|
|
45
43
|
// Handle ZodAny and ZodRecord - these accept any keys, so don't introspect
|
|
46
|
-
if (schema
|
|
44
|
+
if (schema instanceof z.ZodAny || schema instanceof z.ZodRecord) {
|
|
47
45
|
return prefix ? [
|
|
48
46
|
prefix
|
|
49
47
|
] : [];
|
|
50
48
|
}
|
|
51
|
-
if (schema
|
|
52
|
-
|
|
53
|
-
const arraySchema = schema;
|
|
54
|
-
return listZodKeys(arraySchema.element, prefix);
|
|
49
|
+
if (schema instanceof z.ZodArray) {
|
|
50
|
+
return listZodKeys(schema.element, prefix);
|
|
55
51
|
}
|
|
56
|
-
if (schema
|
|
57
|
-
|
|
58
|
-
const objectSchema = schema;
|
|
59
|
-
return Object.entries(objectSchema.shape).flatMap(([key, subschema])=>{
|
|
52
|
+
if (schema instanceof z.ZodObject) {
|
|
53
|
+
return Object.entries(schema.shape).flatMap(([key, subschema])=>{
|
|
60
54
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
61
55
|
const nested = listZodKeys(subschema, fullKey);
|
|
62
56
|
return nested.length ? nested : fullKey;
|
|
@@ -66,7 +60,7 @@ import { create } from './util/storage.js';
|
|
|
66
60
|
};
|
|
67
61
|
/**
|
|
68
62
|
* Type guard to check if a value is a plain object (not array, null, or other types).
|
|
69
|
-
*
|
|
63
|
+
*
|
|
70
64
|
* @param value - The value to check
|
|
71
65
|
* @returns True if the value is a plain object
|
|
72
66
|
*/ const isPlainObject = (value)=>{
|
|
@@ -114,26 +108,26 @@ import { create } from './util/storage.js';
|
|
|
114
108
|
};
|
|
115
109
|
/**
|
|
116
110
|
* Validates that the configuration object contains only keys allowed by the schema.
|
|
117
|
-
*
|
|
111
|
+
*
|
|
118
112
|
* This function prevents configuration errors by detecting typos or extra keys
|
|
119
113
|
* that aren't defined in the Zod schema. It intelligently handles:
|
|
120
114
|
* - ZodRecord types that accept arbitrary keys
|
|
121
115
|
* - Nested objects and their key structures
|
|
122
116
|
* - Arrays and their element key structures
|
|
123
|
-
*
|
|
117
|
+
*
|
|
124
118
|
* The function throws a ConfigurationError if extra keys are found, providing
|
|
125
119
|
* helpful information about what keys are allowed vs. what was found.
|
|
126
|
-
*
|
|
120
|
+
*
|
|
127
121
|
* @param mergedSources - The merged configuration object to validate
|
|
128
122
|
* @param fullSchema - The complete Zod schema including base and user schemas
|
|
129
123
|
* @param logger - Logger for error reporting
|
|
130
124
|
* @throws {ConfigurationError} When extra keys are found that aren't in the schema
|
|
131
|
-
*
|
|
125
|
+
*
|
|
132
126
|
* @example
|
|
133
127
|
* ```typescript
|
|
134
128
|
* const schema = z.object({ name: z.string(), age: z.number() });
|
|
135
129
|
* const config = { name: 'John', age: 30, typo: 'invalid' };
|
|
136
|
-
*
|
|
130
|
+
*
|
|
137
131
|
* checkForExtraKeys(config, schema, console);
|
|
138
132
|
* // Throws: ConfigurationError with details about 'typo' being an extra key
|
|
139
133
|
* ```
|
|
@@ -144,18 +138,16 @@ import { create } from './util/storage.js';
|
|
|
144
138
|
const recordPrefixes = new Set();
|
|
145
139
|
// Find all prefixes that are ZodRecord types
|
|
146
140
|
const findRecordPrefixes = (schema, prefix = '')=>{
|
|
147
|
-
if (schema
|
|
148
|
-
|
|
149
|
-
findRecordPrefixes(unwrappable.unwrap(), prefix);
|
|
141
|
+
if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
|
|
142
|
+
findRecordPrefixes(schema.unwrap(), prefix);
|
|
150
143
|
return;
|
|
151
144
|
}
|
|
152
|
-
if (schema
|
|
145
|
+
if (schema instanceof z.ZodAny || schema instanceof z.ZodRecord) {
|
|
153
146
|
if (prefix) recordPrefixes.add(prefix);
|
|
154
147
|
return;
|
|
155
148
|
}
|
|
156
|
-
if (schema
|
|
157
|
-
|
|
158
|
-
Object.entries(objectSchema.shape).forEach(([key, subschema])=>{
|
|
149
|
+
if (schema instanceof z.ZodObject) {
|
|
150
|
+
Object.entries(schema.shape).forEach(([key, subschema])=>{
|
|
159
151
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
160
152
|
findRecordPrefixes(subschema, fullKey);
|
|
161
153
|
});
|
|
@@ -182,11 +174,11 @@ import { create } from './util/storage.js';
|
|
|
182
174
|
};
|
|
183
175
|
/**
|
|
184
176
|
* Validates that a configuration directory exists and is accessible.
|
|
185
|
-
*
|
|
177
|
+
*
|
|
186
178
|
* This function performs file system checks to ensure the configuration
|
|
187
179
|
* directory can be used. It handles the isRequired flag to determine
|
|
188
180
|
* whether a missing directory should cause an error or be silently ignored.
|
|
189
|
-
*
|
|
181
|
+
*
|
|
190
182
|
* @param configDirectory - Path to the configuration directory
|
|
191
183
|
* @param isRequired - Whether the directory must exist
|
|
192
184
|
* @param logger - Optional logger for debug information
|
|
@@ -209,30 +201,30 @@ import { create } from './util/storage.js';
|
|
|
209
201
|
};
|
|
210
202
|
/**
|
|
211
203
|
* Validates a configuration object against the combined Zod schema.
|
|
212
|
-
*
|
|
204
|
+
*
|
|
213
205
|
* This is the main validation function that:
|
|
214
206
|
* 1. Validates the configuration directory (if config feature enabled)
|
|
215
207
|
* 2. Combines the base ConfigSchema with user-provided schema shape
|
|
216
208
|
* 3. Checks for extra keys not defined in the schema
|
|
217
209
|
* 4. Validates all values against their schema definitions
|
|
218
210
|
* 5. Provides detailed error reporting for validation failures
|
|
219
|
-
*
|
|
211
|
+
*
|
|
220
212
|
* The validation is comprehensive and catches common configuration errors
|
|
221
213
|
* including typos, missing required fields, wrong types, and invalid values.
|
|
222
|
-
*
|
|
214
|
+
*
|
|
223
215
|
* @template T - The Zod schema shape type for configuration validation
|
|
224
216
|
* @param config - The merged configuration object to validate
|
|
225
217
|
* @param options - Cardigantime options containing schema, defaults, and logger
|
|
226
218
|
* @throws {ConfigurationError} When configuration validation fails
|
|
227
219
|
* @throws {FileSystemError} When configuration directory validation fails
|
|
228
|
-
*
|
|
220
|
+
*
|
|
229
221
|
* @example
|
|
230
222
|
* ```typescript
|
|
231
223
|
* const schema = z.object({
|
|
232
224
|
* apiKey: z.string().min(1),
|
|
233
225
|
* timeout: z.number().positive(),
|
|
234
226
|
* });
|
|
235
|
-
*
|
|
227
|
+
*
|
|
236
228
|
* await validate(config, {
|
|
237
229
|
* configShape: schema.shape,
|
|
238
230
|
* defaults: { configDirectory: './config', isRequired: true },
|
package/dist/validate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sources":["../src/validate.ts"],"sourcesContent":["import { z, ZodObject } from \"zod\";\nimport { ArgumentError } from \"./error/ArgumentError\";\nimport { ConfigurationError } from \"./error/ConfigurationError\";\nimport { FileSystemError } from \"./error/FileSystemError\";\nimport { ConfigSchema, Logger, Options } from \"./types\";\nimport * as Storage from \"./util/storage\";\nexport { ArgumentError, ConfigurationError, FileSystemError };\n\n/**\n * Recursively extracts all keys from a Zod schema in dot notation.\n * \n * This function traverses a Zod schema structure and builds a flat list\n * of all possible keys, using dot notation for nested objects. It handles\n * optional/nullable types by unwrapping them and supports arrays by\n * introspecting their element type.\n * \n * Special handling for:\n * - ZodOptional/ZodNullable: Unwraps to get the underlying type\n * - ZodAny/ZodRecord: Accepts any keys, returns the prefix or empty array\n * - ZodArray: Introspects the element type\n * - ZodObject: Recursively processes all shape properties\n * \n * @param schema - The Zod schema to introspect\n * @param prefix - Internal parameter for building nested key paths\n * @returns Array of strings representing all possible keys in dot notation\n * \n * @example\n * ```typescript\n * const schema = z.object({\n * user: z.object({\n * name: z.string(),\n * settings: z.object({ theme: z.string() })\n * }),\n * debug: z.boolean()\n * });\n * \n * const keys = listZodKeys(schema);\n * // Returns: ['user.name', 'user.settings.theme', 'debug']\n * ```\n */\nexport const listZodKeys = (schema: z.ZodTypeAny, prefix = ''): string[] => {\n // Check if schema has unwrap method (which both ZodOptional and ZodNullable have)\n if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {\n // Use type assertion to handle the unwrap method\n const unwrappable = schema as z.ZodOptional<any> | z.ZodNullable<any>;\n return listZodKeys(unwrappable.unwrap(), prefix);\n }\n\n // Handle ZodAny and ZodRecord - these accept any keys, so don't introspect\n if (schema._def && (schema._def.typeName === 'ZodAny' || schema._def.typeName === 'ZodRecord')) {\n return prefix ? [prefix] : [];\n }\n\n if (schema._def && schema._def.typeName === 'ZodArray') {\n // Use type assertion to handle the element property\n const arraySchema = schema as z.ZodArray<any>;\n return listZodKeys(arraySchema.element, prefix);\n }\n if (schema._def && schema._def.typeName === 'ZodObject') {\n // Use type assertion to handle the shape property\n const objectSchema = schema as z.ZodObject<any>;\n return Object.entries(objectSchema.shape).flatMap(([key, subschema]) => {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n const nested = listZodKeys(subschema as z.ZodTypeAny, fullKey);\n return nested.length ? nested : fullKey;\n });\n }\n return [];\n}\n\n/**\n * Type guard to check if a value is a plain object (not array, null, or other types).\n * \n * @param value - The value to check\n * @returns True if the value is a plain object\n */\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n // Check if it's an object, not null, and not an array.\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n};\n\n/**\n * Generates a list of all keys within a JavaScript object, using dot notation for nested keys.\n * Mimics the behavior of listZodKeys but operates on plain objects.\n * For arrays, it inspects the first element that is a plain object to determine nested keys.\n * If an array contains no plain objects, or is empty, the key for the array itself is listed.\n *\n * @param obj The object to introspect.\n * @param prefix Internal use for recursion: the prefix for the current nesting level.\n * @returns An array of strings representing all keys in dot notation.\n */\nexport const listObjectKeys = (obj: Record<string, unknown>, prefix = ''): string[] => {\n const keys = new Set<string>(); // Use Set to automatically handle duplicates from array recursion\n\n for (const key in obj) {\n // Ensure it's an own property, not from the prototype chain\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const value = obj[key];\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (Array.isArray(value)) {\n // Find the first element that is a plain object to determine structure\n const firstObjectElement = value.find(isPlainObject);\n if (firstObjectElement) {\n // Recurse into the structure of the first object element found\n const nestedKeys = listObjectKeys(firstObjectElement, fullKey);\n nestedKeys.forEach(k => keys.add(k));\n } else {\n // Array is empty or contains no plain objects, list the array key itself\n keys.add(fullKey);\n }\n } else if (isPlainObject(value)) {\n // Recurse into nested plain objects\n const nestedKeys = listObjectKeys(value, fullKey);\n nestedKeys.forEach(k => keys.add(k));\n } else {\n // It's a primitive, null, or other non-plain object/array type\n keys.add(fullKey);\n }\n }\n }\n return Array.from(keys); // Convert Set back to Array\n};\n\n/**\n * Validates that the configuration object contains only keys allowed by the schema.\n * \n * This function prevents configuration errors by detecting typos or extra keys\n * that aren't defined in the Zod schema. It intelligently handles:\n * - ZodRecord types that accept arbitrary keys\n * - Nested objects and their key structures\n * - Arrays and their element key structures\n * \n * The function throws a ConfigurationError if extra keys are found, providing\n * helpful information about what keys are allowed vs. what was found.\n * \n * @param mergedSources - The merged configuration object to validate\n * @param fullSchema - The complete Zod schema including base and user schemas\n * @param logger - Logger for error reporting\n * @throws {ConfigurationError} When extra keys are found that aren't in the schema\n * \n * @example\n * ```typescript\n * const schema = z.object({ name: z.string(), age: z.number() });\n * const config = { name: 'John', age: 30, typo: 'invalid' };\n * \n * checkForExtraKeys(config, schema, console);\n * // Throws: ConfigurationError with details about 'typo' being an extra key\n * ```\n */\nexport const checkForExtraKeys = (mergedSources: object, fullSchema: ZodObject<any>, logger: Logger | typeof console): void => {\n const allowedKeys = new Set(listZodKeys(fullSchema));\n const actualKeys = listObjectKeys(mergedSources as Record<string, unknown>);\n\n // Filter out keys that are under a record type (ZodRecord accepts any keys)\n const recordPrefixes = new Set<string>();\n\n // Find all prefixes that are ZodRecord types\n const findRecordPrefixes = (schema: z.ZodTypeAny, prefix = ''): void => {\n if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {\n const unwrappable = schema as z.ZodOptional<any> | z.ZodNullable<any>;\n findRecordPrefixes(unwrappable.unwrap(), prefix);\n return;\n }\n\n if (schema._def && (schema._def.typeName === 'ZodAny' || schema._def.typeName === 'ZodRecord')) {\n if (prefix) recordPrefixes.add(prefix);\n return;\n }\n\n if (schema._def && schema._def.typeName === 'ZodObject') {\n const objectSchema = schema as z.ZodObject<any>;\n Object.entries(objectSchema.shape).forEach(([key, subschema]) => {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n findRecordPrefixes(subschema as z.ZodTypeAny, fullKey);\n });\n }\n };\n\n findRecordPrefixes(fullSchema);\n\n // Filter out keys that are under record prefixes\n const extraKeys = actualKeys.filter(key => {\n if (allowedKeys.has(key)) return false;\n\n // Check if this key is under a record prefix\n for (const recordPrefix of recordPrefixes) {\n if (key.startsWith(recordPrefix + '.')) {\n return false; // This key is allowed under a record\n }\n }\n\n return true; // This is an extra key\n });\n\n if (extraKeys.length > 0) {\n const allowedKeysArray = Array.from(allowedKeys);\n const error = ConfigurationError.extraKeys(extraKeys, allowedKeysArray);\n logger.error(error.message);\n throw error;\n }\n}\n\n/**\n * Validates that a configuration directory exists and is accessible.\n * \n * This function performs file system checks to ensure the configuration\n * directory can be used. It handles the isRequired flag to determine\n * whether a missing directory should cause an error or be silently ignored.\n * \n * @param configDirectory - Path to the configuration directory\n * @param isRequired - Whether the directory must exist\n * @param logger - Optional logger for debug information\n * @throws {FileSystemError} When the directory is required but missing or unreadable\n */\nconst validateConfigDirectory = async (configDirectory: string, isRequired: boolean, logger?: Logger): Promise<void> => {\n const storage = Storage.create({ log: logger?.debug || (() => { }) });\n const exists = await storage.exists(configDirectory);\n if (!exists) {\n if (isRequired) {\n throw FileSystemError.directoryNotFound(configDirectory, true);\n }\n } else if (exists) {\n const isReadable = await storage.isDirectoryReadable(configDirectory);\n if (!isReadable) {\n throw FileSystemError.directoryNotReadable(configDirectory);\n }\n }\n}\n\n/**\n * Validates a configuration object against the combined Zod schema.\n * \n * This is the main validation function that:\n * 1. Validates the configuration directory (if config feature enabled)\n * 2. Combines the base ConfigSchema with user-provided schema shape\n * 3. Checks for extra keys not defined in the schema\n * 4. Validates all values against their schema definitions\n * 5. Provides detailed error reporting for validation failures\n * \n * The validation is comprehensive and catches common configuration errors\n * including typos, missing required fields, wrong types, and invalid values.\n * \n * @template T - The Zod schema shape type for configuration validation\n * @param config - The merged configuration object to validate\n * @param options - Cardigantime options containing schema, defaults, and logger\n * @throws {ConfigurationError} When configuration validation fails\n * @throws {FileSystemError} When configuration directory validation fails\n * \n * @example\n * ```typescript\n * const schema = z.object({\n * apiKey: z.string().min(1),\n * timeout: z.number().positive(),\n * });\n * \n * await validate(config, {\n * configShape: schema.shape,\n * defaults: { configDirectory: './config', isRequired: true },\n * logger: console,\n * features: ['config']\n * });\n * // Throws detailed errors if validation fails\n * ```\n */\nexport const validate = async <T extends z.ZodRawShape>(config: z.infer<ZodObject<T & typeof ConfigSchema.shape>>, options: Options<T>): Promise<void> => {\n const logger = options.logger;\n\n if (options.features.includes('config') && config.configDirectory) {\n await validateConfigDirectory(config.configDirectory, options.defaults.isRequired, logger);\n }\n\n // Combine the base schema with the user-provided shape\n const fullSchema = z.object({\n ...ConfigSchema.shape,\n ...options.configShape,\n });\n\n // Validate the merged sources against the full schema\n const validationResult = fullSchema.safeParse(config);\n\n // Check for extraneous keys\n checkForExtraKeys(config, fullSchema, logger);\n\n if (!validationResult.success) {\n const formattedError = JSON.stringify(validationResult.error.format(), null, 2);\n logger.error('Configuration validation failed. Check logs for details.');\n logger.silly('Configuration validation failed: %s', formattedError);\n throw ConfigurationError.validation('Configuration validation failed. Check logs for details.', validationResult.error);\n }\n\n return;\n}\n\n"],"names":["listZodKeys","schema","prefix","_def","typeName","unwrappable","unwrap","arraySchema","element","objectSchema","Object","entries","shape","flatMap","key","subschema","fullKey","nested","length","isPlainObject","value","Array","isArray","listObjectKeys","obj","keys","Set","prototype","hasOwnProperty","call","firstObjectElement","find","nestedKeys","forEach","k","add","from","checkForExtraKeys","mergedSources","fullSchema","logger","allowedKeys","actualKeys","recordPrefixes","findRecordPrefixes","extraKeys","filter","has","recordPrefix","startsWith","allowedKeysArray","error","ConfigurationError","message","validateConfigDirectory","configDirectory","isRequired","storage","Storage","log","debug","exists","FileSystemError","directoryNotFound","isReadable","isDirectoryReadable","directoryNotReadable","validate","config","options","features","includes","defaults","z","object","ConfigSchema","configShape","validationResult","safeParse","success","formattedError","JSON","stringify","format","silly","validation"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validate.js","sources":["../src/validate.ts"],"sourcesContent":["import { z, ZodObject } from \"zod\";\nimport { ArgumentError } from \"./error/ArgumentError\";\nimport { ConfigurationError } from \"./error/ConfigurationError\";\nimport { FileSystemError } from \"./error/FileSystemError\";\nimport { ConfigSchema, Logger, Options } from \"./types\";\nimport * as Storage from \"./util/storage\";\nexport { ArgumentError, ConfigurationError, FileSystemError };\n\n/**\n * Recursively extracts all keys from a Zod schema in dot notation.\n *\n * This function traverses a Zod schema structure and builds a flat list\n * of all possible keys, using dot notation for nested objects. It handles\n * optional/nullable types by unwrapping them and supports arrays by\n * introspecting their element type.\n *\n * Special handling for:\n * - ZodOptional/ZodNullable: Unwraps to get the underlying type\n * - ZodAny/ZodRecord: Accepts any keys, returns the prefix or empty array\n * - ZodArray: Introspects the element type\n * - ZodObject: Recursively processes all shape properties\n *\n * @param schema - The Zod schema to introspect\n * @param prefix - Internal parameter for building nested key paths\n * @returns Array of strings representing all possible keys in dot notation\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * user: z.object({\n * name: z.string(),\n * settings: z.object({ theme: z.string() })\n * }),\n * debug: z.boolean()\n * });\n *\n * const keys = listZodKeys(schema);\n * // Returns: ['user.name', 'user.settings.theme', 'debug']\n * ```\n */\nexport const listZodKeys = (schema: z.ZodTypeAny, prefix = ''): string[] => {\n // Handle ZodOptional and ZodNullable - unwrap to get the underlying type\n if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {\n return listZodKeys(schema.unwrap() as z.ZodTypeAny, prefix);\n }\n\n // Handle ZodAny and ZodRecord - these accept any keys, so don't introspect\n if (schema instanceof z.ZodAny || schema instanceof z.ZodRecord) {\n return prefix ? [prefix] : [];\n }\n\n if (schema instanceof z.ZodArray) {\n return listZodKeys(schema.element as z.ZodTypeAny, prefix);\n }\n\n if (schema instanceof z.ZodObject) {\n return Object.entries(schema.shape).flatMap(([key, subschema]) => {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n const nested = listZodKeys(subschema as z.ZodTypeAny, fullKey);\n return nested.length ? nested : fullKey;\n });\n }\n return [];\n}\n\n/**\n * Type guard to check if a value is a plain object (not array, null, or other types).\n *\n * @param value - The value to check\n * @returns True if the value is a plain object\n */\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n // Check if it's an object, not null, and not an array.\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n};\n\n/**\n * Generates a list of all keys within a JavaScript object, using dot notation for nested keys.\n * Mimics the behavior of listZodKeys but operates on plain objects.\n * For arrays, it inspects the first element that is a plain object to determine nested keys.\n * If an array contains no plain objects, or is empty, the key for the array itself is listed.\n *\n * @param obj The object to introspect.\n * @param prefix Internal use for recursion: the prefix for the current nesting level.\n * @returns An array of strings representing all keys in dot notation.\n */\nexport const listObjectKeys = (obj: Record<string, unknown>, prefix = ''): string[] => {\n const keys = new Set<string>(); // Use Set to automatically handle duplicates from array recursion\n\n for (const key in obj) {\n // Ensure it's an own property, not from the prototype chain\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const value = obj[key];\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (Array.isArray(value)) {\n // Find the first element that is a plain object to determine structure\n const firstObjectElement = value.find(isPlainObject);\n if (firstObjectElement) {\n // Recurse into the structure of the first object element found\n const nestedKeys = listObjectKeys(firstObjectElement, fullKey);\n nestedKeys.forEach(k => keys.add(k));\n } else {\n // Array is empty or contains no plain objects, list the array key itself\n keys.add(fullKey);\n }\n } else if (isPlainObject(value)) {\n // Recurse into nested plain objects\n const nestedKeys = listObjectKeys(value, fullKey);\n nestedKeys.forEach(k => keys.add(k));\n } else {\n // It's a primitive, null, or other non-plain object/array type\n keys.add(fullKey);\n }\n }\n }\n return Array.from(keys); // Convert Set back to Array\n};\n\n/**\n * Validates that the configuration object contains only keys allowed by the schema.\n *\n * This function prevents configuration errors by detecting typos or extra keys\n * that aren't defined in the Zod schema. It intelligently handles:\n * - ZodRecord types that accept arbitrary keys\n * - Nested objects and their key structures\n * - Arrays and their element key structures\n *\n * The function throws a ConfigurationError if extra keys are found, providing\n * helpful information about what keys are allowed vs. what was found.\n *\n * @param mergedSources - The merged configuration object to validate\n * @param fullSchema - The complete Zod schema including base and user schemas\n * @param logger - Logger for error reporting\n * @throws {ConfigurationError} When extra keys are found that aren't in the schema\n *\n * @example\n * ```typescript\n * const schema = z.object({ name: z.string(), age: z.number() });\n * const config = { name: 'John', age: 30, typo: 'invalid' };\n *\n * checkForExtraKeys(config, schema, console);\n * // Throws: ConfigurationError with details about 'typo' being an extra key\n * ```\n */\nexport const checkForExtraKeys = (mergedSources: object, fullSchema: ZodObject<any>, logger: Logger | typeof console): void => {\n const allowedKeys = new Set(listZodKeys(fullSchema));\n const actualKeys = listObjectKeys(mergedSources as Record<string, unknown>);\n\n // Filter out keys that are under a record type (ZodRecord accepts any keys)\n const recordPrefixes = new Set<string>();\n\n // Find all prefixes that are ZodRecord types\n const findRecordPrefixes = (schema: z.ZodTypeAny, prefix = ''): void => {\n if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {\n findRecordPrefixes(schema.unwrap() as z.ZodTypeAny, prefix);\n return;\n }\n\n if (schema instanceof z.ZodAny || schema instanceof z.ZodRecord) {\n if (prefix) recordPrefixes.add(prefix);\n return;\n }\n\n if (schema instanceof z.ZodObject) {\n Object.entries(schema.shape).forEach(([key, subschema]) => {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n findRecordPrefixes(subschema as z.ZodTypeAny, fullKey);\n });\n }\n };\n\n findRecordPrefixes(fullSchema);\n\n // Filter out keys that are under record prefixes\n const extraKeys = actualKeys.filter(key => {\n if (allowedKeys.has(key)) return false;\n\n // Check if this key is under a record prefix\n for (const recordPrefix of recordPrefixes) {\n if (key.startsWith(recordPrefix + '.')) {\n return false; // This key is allowed under a record\n }\n }\n\n return true; // This is an extra key\n });\n\n if (extraKeys.length > 0) {\n const allowedKeysArray = Array.from(allowedKeys);\n const error = ConfigurationError.extraKeys(extraKeys, allowedKeysArray);\n logger.error(error.message);\n throw error;\n }\n}\n\n/**\n * Validates that a configuration directory exists and is accessible.\n *\n * This function performs file system checks to ensure the configuration\n * directory can be used. It handles the isRequired flag to determine\n * whether a missing directory should cause an error or be silently ignored.\n *\n * @param configDirectory - Path to the configuration directory\n * @param isRequired - Whether the directory must exist\n * @param logger - Optional logger for debug information\n * @throws {FileSystemError} When the directory is required but missing or unreadable\n */\nconst validateConfigDirectory = async (configDirectory: string, isRequired: boolean, logger?: Logger): Promise<void> => {\n const storage = Storage.create({ log: logger?.debug || (() => { }) });\n const exists = await storage.exists(configDirectory);\n if (!exists) {\n if (isRequired) {\n throw FileSystemError.directoryNotFound(configDirectory, true);\n }\n } else if (exists) {\n const isReadable = await storage.isDirectoryReadable(configDirectory);\n if (!isReadable) {\n throw FileSystemError.directoryNotReadable(configDirectory);\n }\n }\n}\n\n/**\n * Validates a configuration object against the combined Zod schema.\n *\n * This is the main validation function that:\n * 1. Validates the configuration directory (if config feature enabled)\n * 2. Combines the base ConfigSchema with user-provided schema shape\n * 3. Checks for extra keys not defined in the schema\n * 4. Validates all values against their schema definitions\n * 5. Provides detailed error reporting for validation failures\n *\n * The validation is comprehensive and catches common configuration errors\n * including typos, missing required fields, wrong types, and invalid values.\n *\n * @template T - The Zod schema shape type for configuration validation\n * @param config - The merged configuration object to validate\n * @param options - Cardigantime options containing schema, defaults, and logger\n * @throws {ConfigurationError} When configuration validation fails\n * @throws {FileSystemError} When configuration directory validation fails\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * apiKey: z.string().min(1),\n * timeout: z.number().positive(),\n * });\n *\n * await validate(config, {\n * configShape: schema.shape,\n * defaults: { configDirectory: './config', isRequired: true },\n * logger: console,\n * features: ['config']\n * });\n * // Throws detailed errors if validation fails\n * ```\n */\nexport const validate = async <T extends z.ZodRawShape>(config: z.infer<ZodObject<T & typeof ConfigSchema.shape>>, options: Options<T>): Promise<void> => {\n const logger = options.logger;\n\n if (options.features.includes('config') && (config as any).configDirectory) {\n await validateConfigDirectory((config as any).configDirectory, options.defaults.isRequired, logger);\n }\n\n // Combine the base schema with the user-provided shape\n const fullSchema = z.object({\n ...ConfigSchema.shape,\n ...options.configShape,\n });\n\n // Validate the merged sources against the full schema\n const validationResult = fullSchema.safeParse(config);\n\n // Check for extraneous keys\n checkForExtraKeys(config, fullSchema, logger);\n\n if (!validationResult.success) {\n const formattedError = JSON.stringify(validationResult.error.format(), null, 2);\n logger.error('Configuration validation failed. Check logs for details.');\n logger.silly('Configuration validation failed: %s', formattedError);\n throw ConfigurationError.validation('Configuration validation failed. Check logs for details.', validationResult.error);\n }\n\n return;\n}\n\n"],"names":["listZodKeys","schema","prefix","z","ZodOptional","ZodNullable","unwrap","ZodAny","ZodRecord","ZodArray","element","ZodObject","Object","entries","shape","flatMap","key","subschema","fullKey","nested","length","isPlainObject","value","Array","isArray","listObjectKeys","obj","keys","Set","prototype","hasOwnProperty","call","firstObjectElement","find","nestedKeys","forEach","k","add","from","checkForExtraKeys","mergedSources","fullSchema","logger","allowedKeys","actualKeys","recordPrefixes","findRecordPrefixes","extraKeys","filter","has","recordPrefix","startsWith","allowedKeysArray","error","ConfigurationError","message","validateConfigDirectory","configDirectory","isRequired","storage","Storage","log","debug","exists","FileSystemError","directoryNotFound","isReadable","isDirectoryReadable","directoryNotReadable","validate","config","options","features","includes","defaults","object","ConfigSchema","configShape","validationResult","safeParse","success","formattedError","JSON","stringify","format","silly","validation"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theunwalked/cardigantime",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "cardigantime is a tool to help you time your cardigans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cardigantime.cjs",
|
|
@@ -33,28 +33,25 @@
|
|
|
33
33
|
"license": "Apache-2.0",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"commander": "^14.0.0",
|
|
36
|
-
"dotenv": "^17.0.1",
|
|
37
36
|
"glob": "^11.0.3",
|
|
38
37
|
"js-yaml": "^4.1.0",
|
|
39
|
-
"
|
|
40
|
-
"zod": "^3.25.74"
|
|
38
|
+
"zod": "^4.0.2"
|
|
41
39
|
},
|
|
42
40
|
"devDependencies": {
|
|
43
41
|
"@eslint/eslintrc": "^3.3.1",
|
|
44
42
|
"@eslint/js": "^9.30.1",
|
|
45
43
|
"@rollup/plugin-replace": "^6.0.2",
|
|
46
|
-
"@swc/core": "^1.12.
|
|
44
|
+
"@swc/core": "^1.12.11",
|
|
47
45
|
"@types/js-yaml": "^4.0.9",
|
|
48
|
-
"@types/
|
|
49
|
-
"@
|
|
50
|
-
"@typescript-eslint/
|
|
51
|
-
"@typescript-eslint/parser": "^8.35.1",
|
|
46
|
+
"@types/node": "^24.0.12",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^8.36.0",
|
|
48
|
+
"@typescript-eslint/parser": "^8.36.0",
|
|
52
49
|
"@vitest/coverage-v8": "^3.2.4",
|
|
53
50
|
"eslint": "^9.30.1",
|
|
54
51
|
"eslint-plugin-import": "^2.32.0",
|
|
55
52
|
"globals": "^16.3.0",
|
|
56
53
|
"typescript": "^5.8.3",
|
|
57
|
-
"vite": "^7.0.
|
|
54
|
+
"vite": "^7.0.4",
|
|
58
55
|
"vite-plugin-dts": "^4.5.4",
|
|
59
56
|
"vite-plugin-node": "^7.0.0",
|
|
60
57
|
"vitest": "^3.2.4"
|