@theunwalked/cardigantime 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +699 -0
- package/dist/cardigantime.cjs +605 -44
- package/dist/cardigantime.cjs.map +1 -1
- package/dist/cardigantime.d.ts +42 -0
- package/dist/cardigantime.js +44 -2
- 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 +15 -5
- 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 +16 -16
package/dist/types.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Base Zod schema for core Cardigantime configuration.
|
|
5
|
+
* Contains the minimum required configuration fields.
|
|
6
|
+
*/ const ConfigSchema = z.object({
|
|
7
|
+
/** The resolved configuration directory path */ configDirectory: z.string()
|
|
6
8
|
});
|
|
7
9
|
|
|
8
10
|
export { ConfigSchema };
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../src/types.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { ZodObject } from \"zod\";\n\nimport { z } from \"zod\";\n\nexport type Feature = 'config';\n\nexport interface DefaultOptions {\n configDirectory: string;\n configFile: string;\n isRequired: boolean;\n encoding: string;\n}\n\n
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../src/types.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { ZodObject } from \"zod\";\n\nimport { z } from \"zod\";\n\n/**\n * Available features that can be enabled in Cardigantime.\n * Currently supports:\n * - 'config': Configuration file reading and validation\n */\nexport type Feature = 'config';\n\n/**\n * Default configuration options for Cardigantime.\n * These define the basic behavior of configuration loading.\n */\nexport interface DefaultOptions {\n /** Directory path where configuration files are located */\n configDirectory: string;\n /** Name of the configuration file (e.g., 'config.yaml', 'app.yml') */\n configFile: string;\n /** Whether the configuration directory must exist. If true, throws error if directory doesn't exist */\n isRequired: boolean;\n /** File encoding for reading configuration files (e.g., 'utf8', 'ascii') */\n encoding: string;\n}\n\n/**\n * Complete options object passed to Cardigantime functions.\n * Combines defaults, features, schema shape, and logger.\n * \n * @template T - The Zod schema shape type for configuration validation\n */\nexport interface Options<T extends z.ZodRawShape> {\n /** Default configuration options */\n defaults: DefaultOptions,\n /** Array of enabled features */\n features: Feature[],\n /** Zod schema shape for validating user configuration */\n configShape: T;\n /** Logger instance for debugging and error reporting */\n logger: Logger;\n}\n\n/**\n * Logger interface for Cardigantime's internal logging.\n * Compatible with popular logging libraries like Winston, Bunyan, etc.\n */\nexport interface Logger {\n /** Debug-level logging for detailed troubleshooting information */\n debug: (message: string, ...args: any[]) => void;\n /** Info-level logging for general information */\n info: (message: string, ...args: any[]) => void;\n /** Warning-level logging for non-critical issues */\n warn: (message: string, ...args: any[]) => void;\n /** Error-level logging for critical problems */\n error: (message: string, ...args: any[]) => void;\n /** Verbose-level logging for extensive detail */\n verbose: (message: string, ...args: any[]) => void;\n /** Silly-level logging for maximum detail */\n silly: (message: string, ...args: any[]) => void;\n}\n\n/**\n * Main Cardigantime interface providing configuration management functionality.\n * \n * @template T - The Zod schema shape type for configuration validation\n */\nexport interface Cardigantime<T extends z.ZodRawShape> {\n /** \n * Adds Cardigantime's CLI options to a Commander.js command.\n * This includes options like --config-directory for runtime config path overrides.\n */\n configure: (command: Command) => Promise<Command>;\n /** Sets a custom logger for debugging and error reporting */\n setLogger: (logger: Logger) => void;\n /** \n * Reads configuration from files and merges with CLI arguments.\n * Returns a fully typed configuration object.\n */\n read: (args: Args) => Promise<z.infer<ZodObject<T & typeof ConfigSchema.shape>>>;\n /** \n * Validates the merged configuration against the Zod schema.\n * Throws ConfigurationError if validation fails.\n */\n validate: (config: z.infer<ZodObject<T & typeof ConfigSchema.shape>>) => Promise<void>;\n}\n\n/**\n * Parsed command-line arguments object, typically from Commander.js opts().\n * Keys correspond to CLI option names with values from user input.\n */\nexport interface Args {\n [key: string]: any;\n}\n\n/**\n * Base Zod schema for core Cardigantime configuration.\n * Contains the minimum required configuration fields.\n */\nexport const ConfigSchema = z.object({\n /** The resolved configuration directory path */\n configDirectory: z.string(),\n});\n\n/**\n * Base configuration type derived from the core schema.\n */\nexport type Config = z.infer<typeof ConfigSchema>;\n"],"names":["ConfigSchema","z","object","configDirectory","string"],"mappings":";;AAgGA;;;AAGC,IACM,MAAMA,YAAAA,GAAeC,CAAAA,CAAEC,MAAM,CAAC;qDAEjCC,eAAAA,EAAiBF,CAAAA,CAAEG,MAAM;AAC7B,CAAA;;;;"}
|
package/dist/util/storage.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import { glob } from 'glob';
|
|
3
|
-
import
|
|
3
|
+
import path__default from 'path';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
|
+
import { FileSystemError } from '../error/FileSystemError.js';
|
|
5
6
|
|
|
6
7
|
// eslint-disable-next-line no-restricted-imports
|
|
7
8
|
const create = (params)=>{
|
|
@@ -65,10 +66,38 @@ const create = (params)=>{
|
|
|
65
66
|
recursive: true
|
|
66
67
|
});
|
|
67
68
|
} catch (mkdirError) {
|
|
68
|
-
throw
|
|
69
|
+
throw FileSystemError.directoryCreationFailed(path, mkdirError);
|
|
69
70
|
}
|
|
70
71
|
};
|
|
71
72
|
const readFile = async (path, encoding)=>{
|
|
73
|
+
// Validate encoding parameter
|
|
74
|
+
const validEncodings = [
|
|
75
|
+
'utf8',
|
|
76
|
+
'utf-8',
|
|
77
|
+
'ascii',
|
|
78
|
+
'latin1',
|
|
79
|
+
'base64',
|
|
80
|
+
'hex',
|
|
81
|
+
'utf16le',
|
|
82
|
+
'ucs2',
|
|
83
|
+
'ucs-2'
|
|
84
|
+
];
|
|
85
|
+
if (!validEncodings.includes(encoding.toLowerCase())) {
|
|
86
|
+
throw new Error('Invalid encoding specified');
|
|
87
|
+
}
|
|
88
|
+
// Check file size before reading to prevent DoS
|
|
89
|
+
try {
|
|
90
|
+
const stats = await fs.promises.stat(path);
|
|
91
|
+
const maxFileSize = 10 * 1024 * 1024; // 10MB limit
|
|
92
|
+
if (stats.size > maxFileSize) {
|
|
93
|
+
throw new Error('File too large to process');
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error.code === 'ENOENT') {
|
|
97
|
+
throw FileSystemError.fileNotFound(path);
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
72
101
|
return await fs.promises.readFile(path, {
|
|
73
102
|
encoding: encoding
|
|
74
103
|
});
|
|
@@ -87,10 +116,10 @@ const create = (params)=>{
|
|
|
87
116
|
nodir: true
|
|
88
117
|
});
|
|
89
118
|
for (const file of files){
|
|
90
|
-
await callback(
|
|
119
|
+
await callback(path__default.join(directory, file));
|
|
91
120
|
}
|
|
92
121
|
} catch (err) {
|
|
93
|
-
throw
|
|
122
|
+
throw FileSystemError.operationFailed(`glob pattern ${options.pattern}`, directory, err);
|
|
94
123
|
}
|
|
95
124
|
};
|
|
96
125
|
const readStream = async (path)=>{
|
package/dist/util/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sources":["../../src/util/storage.ts"],"sourcesContent":["// eslint-disable-next-line no-restricted-imports\nimport * as fs from 'fs';\nimport { glob } from 'glob';\nimport path from 'path';\nimport crypto from 'crypto';\n/**\n * This module exists to isolate filesystem operations from the rest of the codebase.\n * This makes testing easier by avoiding direct fs mocking in jest configuration.\n * \n * Additionally, abstracting storage operations allows for future flexibility - \n * this export utility may need to work with storage systems other than the local filesystem\n * (e.g. S3, Google Cloud Storage, etc).\n */\n\nexport interface Utility {\n exists: (path: string) => Promise<boolean>;\n isDirectory: (path: string) => Promise<boolean>;\n isFile: (path: string) => Promise<boolean>;\n isReadable: (path: string) => Promise<boolean>;\n isWritable: (path: string) => Promise<boolean>;\n isFileReadable: (path: string) => Promise<boolean>;\n isDirectoryWritable: (path: string) => Promise<boolean>;\n isDirectoryReadable: (path: string) => Promise<boolean>;\n createDirectory: (path: string) => Promise<void>;\n readFile: (path: string, encoding: string) => Promise<string>;\n readStream: (path: string) => Promise<fs.ReadStream>;\n writeFile: (path: string, data: string | Buffer, encoding: string) => Promise<void>;\n forEachFileIn: (directory: string, callback: (path: string) => Promise<void>, options?: { pattern: string }) => Promise<void>;\n hashFile: (path: string, length: number) => Promise<string>;\n listFiles: (directory: string) => Promise<string[]>;\n}\n\nexport const create = (params: { log?: (message: string, ...args: any[]) => void }): Utility => {\n\n // eslint-disable-next-line no-console\n const log = params.log || console.log;\n\n const exists = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.stat(path);\n return true;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (error: any) {\n return false;\n }\n }\n\n const isDirectory = async (path: string): Promise<boolean> => {\n const stats = await fs.promises.stat(path);\n if (!stats.isDirectory()) {\n log(`${path} is not a directory`);\n return false;\n }\n return true;\n }\n\n const isFile = async (path: string): Promise<boolean> => {\n const stats = await fs.promises.stat(path);\n if (!stats.isFile()) {\n log(`${path} is not a file`);\n return false;\n }\n return true;\n }\n\n const isReadable = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.access(path, fs.constants.R_OK);\n } catch (error: any) {\n log(`${path} is not readable: %s %s`, error.message, error.stack);\n return false;\n }\n return true;\n }\n\n const isWritable = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.access(path, fs.constants.W_OK);\n } catch (error: any) {\n log(`${path} is not writable: %s %s`, error.message, error.stack);\n return false;\n }\n return true;\n }\n\n const isFileReadable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isFile(path) && await isReadable(path);\n }\n\n const isDirectoryWritable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isDirectory(path) && await isWritable(path);\n }\n\n const isDirectoryReadable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isDirectory(path) && await isReadable(path);\n }\n\n const createDirectory = async (path: string): Promise<void> => {\n try {\n await fs.promises.mkdir(path, { recursive: true });\n } catch (mkdirError: any) {\n throw new Error(`Failed to create output directory ${path}: ${mkdirError.message} ${mkdirError.stack}`);\n }\n }\n\n const readFile = async (path: string, encoding: string): Promise<string> => {\n return await fs.promises.readFile(path, { encoding: encoding as BufferEncoding });\n }\n\n const writeFile = async (path: string, data: string | Buffer, encoding: string): Promise<void> => {\n await fs.promises.writeFile(path, data, { encoding: encoding as BufferEncoding });\n }\n\n const forEachFileIn = async (directory: string, callback: (file: string) => Promise<void>, options: { pattern: string | string[] } = { pattern: '*.*' }): Promise<void> => {\n try {\n const files = await glob(options.pattern, { cwd: directory, nodir: true });\n for (const file of files) {\n await callback(path.join(directory, file));\n }\n } catch (err: any) {\n throw new Error(`Failed to glob pattern ${options.pattern} in ${directory}: ${err.message}`);\n }\n }\n\n const readStream = async (path: string): Promise<fs.ReadStream> => {\n return fs.createReadStream(path);\n }\n\n const hashFile = async (path: string, length: number): Promise<string> => {\n const file = await readFile(path, 'utf8');\n return crypto.createHash('sha256').update(file).digest('hex').slice(0, length);\n }\n\n const listFiles = async (directory: string): Promise<string[]> => {\n return await fs.promises.readdir(directory);\n }\n\n return {\n exists,\n isDirectory,\n isFile,\n isReadable,\n isWritable,\n isFileReadable,\n isDirectoryWritable,\n isDirectoryReadable,\n createDirectory,\n readFile,\n readStream,\n writeFile,\n forEachFileIn,\n hashFile,\n listFiles,\n };\n}"],"names":["create","params","log","console","exists","path","fs","promises","stat","error","isDirectory","stats","isFile","isReadable","access","constants","R_OK","message","stack","isWritable","W_OK","isFileReadable","isDirectoryWritable","isDirectoryReadable","createDirectory","mkdir","recursive","mkdirError","Error","readFile","encoding","writeFile","data","forEachFileIn","directory","callback","options","pattern","files","glob","cwd","nodir","file","join","err","readStream","createReadStream","hashFile","length","crypto","createHash","update","digest","slice","listFiles","readdir"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage.js","sources":["../../src/util/storage.ts"],"sourcesContent":["// eslint-disable-next-line no-restricted-imports\nimport * as fs from 'fs';\nimport { glob } from 'glob';\nimport path from 'path';\nimport crypto from 'crypto';\nimport { FileSystemError } from '../error/FileSystemError';\n/**\n * This module exists to isolate filesystem operations from the rest of the codebase.\n * This makes testing easier by avoiding direct fs mocking in jest configuration.\n * \n * Additionally, abstracting storage operations allows for future flexibility - \n * this export utility may need to work with storage systems other than the local filesystem\n * (e.g. S3, Google Cloud Storage, etc).\n */\n\nexport interface Utility {\n exists: (path: string) => Promise<boolean>;\n isDirectory: (path: string) => Promise<boolean>;\n isFile: (path: string) => Promise<boolean>;\n isReadable: (path: string) => Promise<boolean>;\n isWritable: (path: string) => Promise<boolean>;\n isFileReadable: (path: string) => Promise<boolean>;\n isDirectoryWritable: (path: string) => Promise<boolean>;\n isDirectoryReadable: (path: string) => Promise<boolean>;\n createDirectory: (path: string) => Promise<void>;\n readFile: (path: string, encoding: string) => Promise<string>;\n readStream: (path: string) => Promise<fs.ReadStream>;\n writeFile: (path: string, data: string | Buffer, encoding: string) => Promise<void>;\n forEachFileIn: (directory: string, callback: (path: string) => Promise<void>, options?: { pattern: string }) => Promise<void>;\n hashFile: (path: string, length: number) => Promise<string>;\n listFiles: (directory: string) => Promise<string[]>;\n}\n\nexport const create = (params: { log?: (message: string, ...args: any[]) => void }): Utility => {\n\n // eslint-disable-next-line no-console\n const log = params.log || console.log;\n\n const exists = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.stat(path);\n return true;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (error: any) {\n return false;\n }\n }\n\n const isDirectory = async (path: string): Promise<boolean> => {\n const stats = await fs.promises.stat(path);\n if (!stats.isDirectory()) {\n log(`${path} is not a directory`);\n return false;\n }\n return true;\n }\n\n const isFile = async (path: string): Promise<boolean> => {\n const stats = await fs.promises.stat(path);\n if (!stats.isFile()) {\n log(`${path} is not a file`);\n return false;\n }\n return true;\n }\n\n const isReadable = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.access(path, fs.constants.R_OK);\n } catch (error: any) {\n log(`${path} is not readable: %s %s`, error.message, error.stack);\n return false;\n }\n return true;\n }\n\n const isWritable = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.access(path, fs.constants.W_OK);\n } catch (error: any) {\n log(`${path} is not writable: %s %s`, error.message, error.stack);\n return false;\n }\n return true;\n }\n\n const isFileReadable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isFile(path) && await isReadable(path);\n }\n\n const isDirectoryWritable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isDirectory(path) && await isWritable(path);\n }\n\n const isDirectoryReadable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isDirectory(path) && await isReadable(path);\n }\n\n const createDirectory = async (path: string): Promise<void> => {\n try {\n await fs.promises.mkdir(path, { recursive: true });\n } catch (mkdirError: any) {\n throw FileSystemError.directoryCreationFailed(path, mkdirError);\n }\n }\n\n const readFile = async (path: string, encoding: string): Promise<string> => {\n // Validate encoding parameter\n const validEncodings = ['utf8', 'utf-8', 'ascii', 'latin1', 'base64', 'hex', 'utf16le', 'ucs2', 'ucs-2'];\n if (!validEncodings.includes(encoding.toLowerCase())) {\n throw new Error('Invalid encoding specified');\n }\n\n // Check file size before reading to prevent DoS\n try {\n const stats = await fs.promises.stat(path);\n const maxFileSize = 10 * 1024 * 1024; // 10MB limit\n if (stats.size > maxFileSize) {\n throw new Error('File too large to process');\n }\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n throw FileSystemError.fileNotFound(path);\n }\n throw error;\n }\n\n return await fs.promises.readFile(path, { encoding: encoding as BufferEncoding });\n }\n\n const writeFile = async (path: string, data: string | Buffer, encoding: string): Promise<void> => {\n await fs.promises.writeFile(path, data, { encoding: encoding as BufferEncoding });\n }\n\n const forEachFileIn = async (directory: string, callback: (file: string) => Promise<void>, options: { pattern: string | string[] } = { pattern: '*.*' }): Promise<void> => {\n try {\n const files = await glob(options.pattern, { cwd: directory, nodir: true });\n for (const file of files) {\n await callback(path.join(directory, file));\n }\n } catch (err: any) {\n throw FileSystemError.operationFailed(`glob pattern ${options.pattern}`, directory, err);\n }\n }\n\n const readStream = async (path: string): Promise<fs.ReadStream> => {\n return fs.createReadStream(path);\n }\n\n const hashFile = async (path: string, length: number): Promise<string> => {\n const file = await readFile(path, 'utf8');\n return crypto.createHash('sha256').update(file).digest('hex').slice(0, length);\n }\n\n const listFiles = async (directory: string): Promise<string[]> => {\n return await fs.promises.readdir(directory);\n }\n\n return {\n exists,\n isDirectory,\n isFile,\n isReadable,\n isWritable,\n isFileReadable,\n isDirectoryWritable,\n isDirectoryReadable,\n createDirectory,\n readFile,\n readStream,\n writeFile,\n forEachFileIn,\n hashFile,\n listFiles,\n };\n}"],"names":["create","params","log","console","exists","path","fs","promises","stat","error","isDirectory","stats","isFile","isReadable","access","constants","R_OK","message","stack","isWritable","W_OK","isFileReadable","isDirectoryWritable","isDirectoryReadable","createDirectory","mkdir","recursive","mkdirError","FileSystemError","directoryCreationFailed","readFile","encoding","validEncodings","includes","toLowerCase","Error","maxFileSize","size","code","fileNotFound","writeFile","data","forEachFileIn","directory","callback","options","pattern","files","glob","cwd","nodir","file","join","err","operationFailed","readStream","createReadStream","hashFile","length","crypto","createHash","update","digest","slice","listFiles","readdir"],"mappings":"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"}
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
import { z, ZodObject } from 'zod';
|
|
2
2
|
import { ArgumentError } from './error/ArgumentError';
|
|
3
|
+
import { ConfigurationError } from './error/ConfigurationError';
|
|
4
|
+
import { FileSystemError } from './error/FileSystemError';
|
|
3
5
|
import { ConfigSchema, Logger, Options } from './types';
|
|
4
|
-
export { ArgumentError };
|
|
6
|
+
export { ArgumentError, ConfigurationError, FileSystemError };
|
|
7
|
+
/**
|
|
8
|
+
* Recursively extracts all keys from a Zod schema in dot notation.
|
|
9
|
+
*
|
|
10
|
+
* This function traverses a Zod schema structure and builds a flat list
|
|
11
|
+
* of all possible keys, using dot notation for nested objects. It handles
|
|
12
|
+
* optional/nullable types by unwrapping them and supports arrays by
|
|
13
|
+
* introspecting their element type.
|
|
14
|
+
*
|
|
15
|
+
* Special handling for:
|
|
16
|
+
* - ZodOptional/ZodNullable: Unwraps to get the underlying type
|
|
17
|
+
* - ZodAny/ZodRecord: Accepts any keys, returns the prefix or empty array
|
|
18
|
+
* - ZodArray: Introspects the element type
|
|
19
|
+
* - ZodObject: Recursively processes all shape properties
|
|
20
|
+
*
|
|
21
|
+
* @param schema - The Zod schema to introspect
|
|
22
|
+
* @param prefix - Internal parameter for building nested key paths
|
|
23
|
+
* @returns Array of strings representing all possible keys in dot notation
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const schema = z.object({
|
|
28
|
+
* user: z.object({
|
|
29
|
+
* name: z.string(),
|
|
30
|
+
* settings: z.object({ theme: z.string() })
|
|
31
|
+
* }),
|
|
32
|
+
* debug: z.boolean()
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* const keys = listZodKeys(schema);
|
|
36
|
+
* // Returns: ['user.name', 'user.settings.theme', 'debug']
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
5
39
|
export declare const listZodKeys: (schema: z.ZodTypeAny, prefix?: string) => string[];
|
|
6
40
|
/**
|
|
7
41
|
* Generates a list of all keys within a JavaScript object, using dot notation for nested keys.
|
|
@@ -14,5 +48,66 @@ export declare const listZodKeys: (schema: z.ZodTypeAny, prefix?: string) => str
|
|
|
14
48
|
* @returns An array of strings representing all keys in dot notation.
|
|
15
49
|
*/
|
|
16
50
|
export declare const listObjectKeys: (obj: Record<string, unknown>, prefix?: string) => string[];
|
|
51
|
+
/**
|
|
52
|
+
* Validates that the configuration object contains only keys allowed by the schema.
|
|
53
|
+
*
|
|
54
|
+
* This function prevents configuration errors by detecting typos or extra keys
|
|
55
|
+
* that aren't defined in the Zod schema. It intelligently handles:
|
|
56
|
+
* - ZodRecord types that accept arbitrary keys
|
|
57
|
+
* - Nested objects and their key structures
|
|
58
|
+
* - Arrays and their element key structures
|
|
59
|
+
*
|
|
60
|
+
* The function throws a ConfigurationError if extra keys are found, providing
|
|
61
|
+
* helpful information about what keys are allowed vs. what was found.
|
|
62
|
+
*
|
|
63
|
+
* @param mergedSources - The merged configuration object to validate
|
|
64
|
+
* @param fullSchema - The complete Zod schema including base and user schemas
|
|
65
|
+
* @param logger - Logger for error reporting
|
|
66
|
+
* @throws {ConfigurationError} When extra keys are found that aren't in the schema
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const schema = z.object({ name: z.string(), age: z.number() });
|
|
71
|
+
* const config = { name: 'John', age: 30, typo: 'invalid' };
|
|
72
|
+
*
|
|
73
|
+
* checkForExtraKeys(config, schema, console);
|
|
74
|
+
* // Throws: ConfigurationError with details about 'typo' being an extra key
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
17
77
|
export declare const checkForExtraKeys: (mergedSources: object, fullSchema: ZodObject<any>, logger: Logger | typeof console) => void;
|
|
78
|
+
/**
|
|
79
|
+
* Validates a configuration object against the combined Zod schema.
|
|
80
|
+
*
|
|
81
|
+
* This is the main validation function that:
|
|
82
|
+
* 1. Validates the configuration directory (if config feature enabled)
|
|
83
|
+
* 2. Combines the base ConfigSchema with user-provided schema shape
|
|
84
|
+
* 3. Checks for extra keys not defined in the schema
|
|
85
|
+
* 4. Validates all values against their schema definitions
|
|
86
|
+
* 5. Provides detailed error reporting for validation failures
|
|
87
|
+
*
|
|
88
|
+
* The validation is comprehensive and catches common configuration errors
|
|
89
|
+
* including typos, missing required fields, wrong types, and invalid values.
|
|
90
|
+
*
|
|
91
|
+
* @template T - The Zod schema shape type for configuration validation
|
|
92
|
+
* @param config - The merged configuration object to validate
|
|
93
|
+
* @param options - Cardigantime options containing schema, defaults, and logger
|
|
94
|
+
* @throws {ConfigurationError} When configuration validation fails
|
|
95
|
+
* @throws {FileSystemError} When configuration directory validation fails
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const schema = z.object({
|
|
100
|
+
* apiKey: z.string().min(1),
|
|
101
|
+
* timeout: z.number().positive(),
|
|
102
|
+
* });
|
|
103
|
+
*
|
|
104
|
+
* await validate(config, {
|
|
105
|
+
* configShape: schema.shape,
|
|
106
|
+
* defaults: { configDirectory: './config', isRequired: true },
|
|
107
|
+
* logger: console,
|
|
108
|
+
* features: ['config']
|
|
109
|
+
* });
|
|
110
|
+
* // Throws detailed errors if validation fails
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
18
113
|
export declare const validate: <T extends z.ZodRawShape>(config: z.infer<ZodObject<T & typeof ConfigSchema.shape>>, options: Options<T>) => Promise<void>;
|
package/dist/validate.js
CHANGED
|
@@ -1,14 +1,53 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { ConfigurationError } from './error/ConfigurationError.js';
|
|
3
|
+
import { FileSystemError } from './error/FileSystemError.js';
|
|
2
4
|
import { ConfigSchema } from './types.js';
|
|
3
5
|
import { create } from './util/storage.js';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Recursively extracts all keys from a Zod schema in dot notation.
|
|
9
|
+
*
|
|
10
|
+
* This function traverses a Zod schema structure and builds a flat list
|
|
11
|
+
* of all possible keys, using dot notation for nested objects. It handles
|
|
12
|
+
* optional/nullable types by unwrapping them and supports arrays by
|
|
13
|
+
* introspecting their element type.
|
|
14
|
+
*
|
|
15
|
+
* Special handling for:
|
|
16
|
+
* - ZodOptional/ZodNullable: Unwraps to get the underlying type
|
|
17
|
+
* - ZodAny/ZodRecord: Accepts any keys, returns the prefix or empty array
|
|
18
|
+
* - ZodArray: Introspects the element type
|
|
19
|
+
* - ZodObject: Recursively processes all shape properties
|
|
20
|
+
*
|
|
21
|
+
* @param schema - The Zod schema to introspect
|
|
22
|
+
* @param prefix - Internal parameter for building nested key paths
|
|
23
|
+
* @returns Array of strings representing all possible keys in dot notation
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const schema = z.object({
|
|
28
|
+
* user: z.object({
|
|
29
|
+
* name: z.string(),
|
|
30
|
+
* settings: z.object({ theme: z.string() })
|
|
31
|
+
* }),
|
|
32
|
+
* debug: z.boolean()
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* const keys = listZodKeys(schema);
|
|
36
|
+
* // Returns: ['user.name', 'user.settings.theme', 'debug']
|
|
37
|
+
* ```
|
|
38
|
+
*/ const listZodKeys = (schema, prefix = '')=>{
|
|
6
39
|
// Check if schema has unwrap method (which both ZodOptional and ZodNullable have)
|
|
7
40
|
if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {
|
|
8
41
|
// Use type assertion to handle the unwrap method
|
|
9
42
|
const unwrappable = schema;
|
|
10
43
|
return listZodKeys(unwrappable.unwrap(), prefix);
|
|
11
44
|
}
|
|
45
|
+
// Handle ZodAny and ZodRecord - these accept any keys, so don't introspect
|
|
46
|
+
if (schema._def && (schema._def.typeName === 'ZodAny' || schema._def.typeName === 'ZodRecord')) {
|
|
47
|
+
return prefix ? [
|
|
48
|
+
prefix
|
|
49
|
+
] : [];
|
|
50
|
+
}
|
|
12
51
|
if (schema._def && schema._def.typeName === 'ZodArray') {
|
|
13
52
|
// Use type assertion to handle the element property
|
|
14
53
|
const arraySchema = schema;
|
|
@@ -25,7 +64,12 @@ const listZodKeys = (schema, prefix = '')=>{
|
|
|
25
64
|
}
|
|
26
65
|
return [];
|
|
27
66
|
};
|
|
28
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Type guard to check if a value is a plain object (not array, null, or other types).
|
|
69
|
+
*
|
|
70
|
+
* @param value - The value to check
|
|
71
|
+
* @returns True if the value is a plain object
|
|
72
|
+
*/ const isPlainObject = (value)=>{
|
|
29
73
|
// Check if it's an object, not null, and not an array.
|
|
30
74
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
31
75
|
};
|
|
@@ -68,56 +112,156 @@ const isPlainObject = (value)=>{
|
|
|
68
112
|
}
|
|
69
113
|
return Array.from(keys); // Convert Set back to Array
|
|
70
114
|
};
|
|
71
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Validates that the configuration object contains only keys allowed by the schema.
|
|
117
|
+
*
|
|
118
|
+
* This function prevents configuration errors by detecting typos or extra keys
|
|
119
|
+
* that aren't defined in the Zod schema. It intelligently handles:
|
|
120
|
+
* - ZodRecord types that accept arbitrary keys
|
|
121
|
+
* - Nested objects and their key structures
|
|
122
|
+
* - Arrays and their element key structures
|
|
123
|
+
*
|
|
124
|
+
* The function throws a ConfigurationError if extra keys are found, providing
|
|
125
|
+
* helpful information about what keys are allowed vs. what was found.
|
|
126
|
+
*
|
|
127
|
+
* @param mergedSources - The merged configuration object to validate
|
|
128
|
+
* @param fullSchema - The complete Zod schema including base and user schemas
|
|
129
|
+
* @param logger - Logger for error reporting
|
|
130
|
+
* @throws {ConfigurationError} When extra keys are found that aren't in the schema
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const schema = z.object({ name: z.string(), age: z.number() });
|
|
135
|
+
* const config = { name: 'John', age: 30, typo: 'invalid' };
|
|
136
|
+
*
|
|
137
|
+
* checkForExtraKeys(config, schema, console);
|
|
138
|
+
* // Throws: ConfigurationError with details about 'typo' being an extra key
|
|
139
|
+
* ```
|
|
140
|
+
*/ const checkForExtraKeys = (mergedSources, fullSchema, logger)=>{
|
|
72
141
|
const allowedKeys = new Set(listZodKeys(fullSchema));
|
|
73
142
|
const actualKeys = listObjectKeys(mergedSources);
|
|
74
|
-
|
|
143
|
+
// Filter out keys that are under a record type (ZodRecord accepts any keys)
|
|
144
|
+
const recordPrefixes = new Set();
|
|
145
|
+
// Find all prefixes that are ZodRecord types
|
|
146
|
+
const findRecordPrefixes = (schema, prefix = '')=>{
|
|
147
|
+
if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {
|
|
148
|
+
const unwrappable = schema;
|
|
149
|
+
findRecordPrefixes(unwrappable.unwrap(), prefix);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (schema._def && (schema._def.typeName === 'ZodAny' || schema._def.typeName === 'ZodRecord')) {
|
|
153
|
+
if (prefix) recordPrefixes.add(prefix);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (schema._def && schema._def.typeName === 'ZodObject') {
|
|
157
|
+
const objectSchema = schema;
|
|
158
|
+
Object.entries(objectSchema.shape).forEach(([key, subschema])=>{
|
|
159
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
160
|
+
findRecordPrefixes(subschema, fullKey);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
findRecordPrefixes(fullSchema);
|
|
165
|
+
// Filter out keys that are under record prefixes
|
|
166
|
+
const extraKeys = actualKeys.filter((key)=>{
|
|
167
|
+
if (allowedKeys.has(key)) return false;
|
|
168
|
+
// Check if this key is under a record prefix
|
|
169
|
+
for (const recordPrefix of recordPrefixes){
|
|
170
|
+
if (key.startsWith(recordPrefix + '.')) {
|
|
171
|
+
return false; // This key is allowed under a record
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return true; // This is an extra key
|
|
175
|
+
});
|
|
75
176
|
if (extraKeys.length > 0) {
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
throw new Error(`Configuration validation failed: Unknown keys found (${extraKeysString}). Check logs for details.`);
|
|
177
|
+
const allowedKeysArray = Array.from(allowedKeys);
|
|
178
|
+
const error = ConfigurationError.extraKeys(extraKeys, allowedKeysArray);
|
|
179
|
+
logger.error(error.message);
|
|
180
|
+
throw error;
|
|
81
181
|
}
|
|
82
182
|
};
|
|
83
|
-
|
|
84
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Validates that a configuration directory exists and is accessible.
|
|
185
|
+
*
|
|
186
|
+
* This function performs file system checks to ensure the configuration
|
|
187
|
+
* directory can be used. It handles the isRequired flag to determine
|
|
188
|
+
* whether a missing directory should cause an error or be silently ignored.
|
|
189
|
+
*
|
|
190
|
+
* @param configDirectory - Path to the configuration directory
|
|
191
|
+
* @param isRequired - Whether the directory must exist
|
|
192
|
+
* @param logger - Optional logger for debug information
|
|
193
|
+
* @throws {FileSystemError} When the directory is required but missing or unreadable
|
|
194
|
+
*/ const validateConfigDirectory = async (configDirectory, isRequired, logger)=>{
|
|
85
195
|
const storage = create({
|
|
86
|
-
log:
|
|
196
|
+
log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
|
|
87
197
|
});
|
|
88
198
|
const exists = await storage.exists(configDirectory);
|
|
89
199
|
if (!exists) {
|
|
90
200
|
if (isRequired) {
|
|
91
|
-
throw
|
|
201
|
+
throw FileSystemError.directoryNotFound(configDirectory, true);
|
|
92
202
|
}
|
|
93
203
|
} else if (exists) {
|
|
94
204
|
const isReadable = await storage.isDirectoryReadable(configDirectory);
|
|
95
205
|
if (!isReadable) {
|
|
96
|
-
throw
|
|
206
|
+
throw FileSystemError.directoryNotReadable(configDirectory);
|
|
97
207
|
}
|
|
98
208
|
}
|
|
99
209
|
};
|
|
100
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Validates a configuration object against the combined Zod schema.
|
|
212
|
+
*
|
|
213
|
+
* This is the main validation function that:
|
|
214
|
+
* 1. Validates the configuration directory (if config feature enabled)
|
|
215
|
+
* 2. Combines the base ConfigSchema with user-provided schema shape
|
|
216
|
+
* 3. Checks for extra keys not defined in the schema
|
|
217
|
+
* 4. Validates all values against their schema definitions
|
|
218
|
+
* 5. Provides detailed error reporting for validation failures
|
|
219
|
+
*
|
|
220
|
+
* The validation is comprehensive and catches common configuration errors
|
|
221
|
+
* including typos, missing required fields, wrong types, and invalid values.
|
|
222
|
+
*
|
|
223
|
+
* @template T - The Zod schema shape type for configuration validation
|
|
224
|
+
* @param config - The merged configuration object to validate
|
|
225
|
+
* @param options - Cardigantime options containing schema, defaults, and logger
|
|
226
|
+
* @throws {ConfigurationError} When configuration validation fails
|
|
227
|
+
* @throws {FileSystemError} When configuration directory validation fails
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* const schema = z.object({
|
|
232
|
+
* apiKey: z.string().min(1),
|
|
233
|
+
* timeout: z.number().positive(),
|
|
234
|
+
* });
|
|
235
|
+
*
|
|
236
|
+
* await validate(config, {
|
|
237
|
+
* configShape: schema.shape,
|
|
238
|
+
* defaults: { configDirectory: './config', isRequired: true },
|
|
239
|
+
* logger: console,
|
|
240
|
+
* features: ['config']
|
|
241
|
+
* });
|
|
242
|
+
* // Throws detailed errors if validation fails
|
|
243
|
+
* ```
|
|
244
|
+
*/ const validate = async (config, options)=>{
|
|
101
245
|
const logger = options.logger;
|
|
102
246
|
if (options.features.includes('config') && config.configDirectory) {
|
|
103
|
-
await validateConfigDirectory(config.configDirectory, options.defaults.isRequired);
|
|
247
|
+
await validateConfigDirectory(config.configDirectory, options.defaults.isRequired, logger);
|
|
104
248
|
}
|
|
105
249
|
// Combine the base schema with the user-provided shape
|
|
106
250
|
const fullSchema = z.object({
|
|
107
251
|
...ConfigSchema.shape,
|
|
108
252
|
...options.configShape
|
|
109
253
|
});
|
|
110
|
-
logger.debug('Full Schema: \n\n%s\n\n', JSON.stringify(listZodKeys(fullSchema), null, 2));
|
|
111
254
|
// Validate the merged sources against the full schema
|
|
112
255
|
const validationResult = fullSchema.safeParse(config);
|
|
113
256
|
// Check for extraneous keys
|
|
114
257
|
checkForExtraKeys(config, fullSchema, logger);
|
|
115
258
|
if (!validationResult.success) {
|
|
116
|
-
|
|
117
|
-
|
|
259
|
+
const formattedError = JSON.stringify(validationResult.error.format(), null, 2);
|
|
260
|
+
logger.error('Configuration validation failed: %s', formattedError);
|
|
261
|
+
throw ConfigurationError.validation('Configuration validation failed. Check logs for details.', validationResult.error);
|
|
118
262
|
}
|
|
119
263
|
return;
|
|
120
264
|
};
|
|
121
265
|
|
|
122
|
-
export { checkForExtraKeys, listObjectKeys, listZodKeys, validate };
|
|
266
|
+
export { ConfigurationError, FileSystemError, checkForExtraKeys, listObjectKeys, listZodKeys, validate };
|
|
123
267
|
//# sourceMappingURL=validate.js.map
|
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 { ConfigSchema, Logger, Options } from \"./types\";\nimport * as Storage from \"./util/storage\";\nexport { ArgumentError };\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 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\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\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 const extraKeys = actualKeys.filter(key => !allowedKeys.has(key));\n\n if (extraKeys.length > 0) {\n const allowedKeysString = Array.from(allowedKeys).join(', ');\n const extraKeysString = extraKeys.join(', ');\n const errorMessage = `Unknown configuration keys found: ${extraKeysString}. Allowed keys are: ${allowedKeysString}`;\n logger.error(errorMessage);\n throw new Error(`Configuration validation failed: Unknown keys found (${extraKeysString}). Check logs for details.`);\n }\n}\n\nconst validateConfigDirectory = async (configDirectory: string, isRequired: boolean): Promise<void> => {\n // eslint-disable-next-line no-console\n const storage = Storage.create({ log: console.log });\n const exists = await storage.exists(configDirectory);\n if (!exists) {\n if (isRequired) {\n throw new Error(`Config directory does not exist and is required: ${configDirectory}`);\n }\n } else if (exists) {\n const isReadable = await storage.isDirectoryReadable(configDirectory);\n if (!isReadable) {\n throw new Error(`Config directory exists but is not readable: ${configDirectory}`);\n }\n }\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);\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 logger.debug('Full Schema: \\n\\n%s\\n\\n', JSON.stringify(listZodKeys(fullSchema), null, 2));\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 logger.error('Configuration validation failed: %s', JSON.stringify(validationResult.error.format(), null, 2));\n throw new Error(`Configuration validation failed. Check logs for details.`);\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","extraKeys","filter","has","allowedKeysString","join","extraKeysString","errorMessage","error","Error","validateConfigDirectory","configDirectory","isRequired","storage","Storage","log","console","exists","isReadable","isDirectoryReadable","validate","config","options","features","includes","defaults","z","object","ConfigSchema","configShape","debug","JSON","stringify","validationResult","safeParse","success","format"],"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 // 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: %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","validation"],"mappings":"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"}
|