@theunwalked/cardigantime 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +699 -0
- package/dist/cardigantime.cjs +907 -15
- package/dist/cardigantime.cjs.map +1 -1
- package/dist/cardigantime.d.ts +42 -0
- package/dist/cardigantime.js +49 -345
- package/dist/cardigantime.js.map +1 -1
- package/dist/configure.d.ts +50 -1
- package/dist/configure.js +102 -3
- package/dist/configure.js.map +1 -1
- package/dist/constants.d.ts +17 -0
- package/dist/constants.js +17 -9
- package/dist/constants.js.map +1 -1
- package/dist/error/ArgumentError.d.ts +26 -0
- package/dist/error/ArgumentError.js +48 -0
- package/dist/error/ArgumentError.js.map +1 -0
- package/dist/error/ConfigurationError.d.ts +21 -0
- package/dist/error/ConfigurationError.js +46 -0
- package/dist/error/ConfigurationError.js.map +1 -0
- package/dist/error/FileSystemError.d.ts +30 -0
- package/dist/error/FileSystemError.js +58 -0
- package/dist/error/FileSystemError.js.map +1 -0
- package/dist/error/index.d.ts +3 -0
- package/dist/read.d.ts +30 -0
- package/dist/read.js +105 -12
- package/dist/read.js.map +1 -1
- package/dist/types.d.ts +63 -0
- package/dist/types.js +5 -3
- package/dist/types.js.map +1 -1
- package/dist/util/storage.js +33 -4
- package/dist/util/storage.js.map +1 -1
- package/dist/validate.d.ts +96 -1
- package/dist/validate.js +164 -20
- package/dist/validate.js.map +1 -1
- package/package.json +30 -23
- package/.gitcarve/config.yaml +0 -10
- package/.gitcarve/context/content.md +0 -1
- package/dist/configure.cjs +0 -12
- package/dist/configure.cjs.map +0 -1
- package/dist/constants.cjs +0 -35
- package/dist/constants.cjs.map +0 -1
- package/dist/read.cjs +0 -69
- package/dist/read.cjs.map +0 -1
- package/dist/types.cjs +0 -13
- package/dist/types.cjs.map +0 -1
- package/dist/util/storage.cjs +0 -149
- package/dist/util/storage.cjs.map +0 -1
- package/dist/validate.cjs +0 -130
- package/dist/validate.cjs.map +0 -1
- package/eslint.config.mjs +0 -82
- package/nodemon.json +0 -14
- package/vite.config.ts +0 -98
- package/vitest.config.ts +0 -17
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileSystemError.js","sources":["../../src/error/FileSystemError.ts"],"sourcesContent":["/**\n * Error thrown when file system operations fail\n */\nexport class FileSystemError extends Error {\n public readonly errorType: 'not_found' | 'not_readable' | 'not_writable' | 'creation_failed' | 'operation_failed';\n public readonly path: string;\n public readonly operation: string;\n public readonly originalError?: Error;\n\n constructor(\n errorType: 'not_found' | 'not_readable' | 'not_writable' | 'creation_failed' | 'operation_failed',\n message: string,\n path: string,\n operation: string,\n originalError?: Error\n ) {\n super(message);\n this.name = 'FileSystemError';\n this.errorType = errorType;\n this.path = path;\n this.operation = operation;\n this.originalError = originalError;\n }\n\n /**\n * Creates an error for when a required directory doesn't exist\n */\n static directoryNotFound(path: string, isRequired: boolean = false): FileSystemError {\n const message = isRequired\n ? 'Configuration directory does not exist and is required'\n : 'Configuration directory not found';\n return new FileSystemError('not_found', message, path, 'directory_access');\n }\n\n /**\n * Creates an error for when a directory exists but isn't readable\n */\n static directoryNotReadable(path: string): FileSystemError {\n const message = 'Configuration directory exists but is not readable';\n return new FileSystemError('not_readable', message, path, 'directory_read');\n }\n\n /**\n * Creates an error for directory creation failures\n */\n static directoryCreationFailed(path: string, originalError: Error): FileSystemError {\n const message = 'Failed to create directory: ' + (originalError.message || 'Unknown error');\n return new FileSystemError('creation_failed', message, path, 'directory_create', originalError);\n }\n\n /**\n * Creates an error for file operation failures (glob, etc.)\n */\n static operationFailed(operation: string, path: string, originalError: Error): FileSystemError {\n const message = `Failed to ${operation}: ${originalError.message || 'Unknown error'}`;\n return new FileSystemError('operation_failed', message, path, operation, originalError);\n }\n\n /**\n * Creates an error for when a file is not found\n */\n static fileNotFound(path: string): FileSystemError {\n const message = 'Configuration file not found';\n return new FileSystemError('not_found', message, path, 'file_read');\n }\n} "],"names":["FileSystemError","Error","directoryNotFound","path","isRequired","message","directoryNotReadable","directoryCreationFailed","originalError","operationFailed","operation","fileNotFound","errorType","name"],"mappings":"AAAA;;AAEC,IAAA,SAAA,gBAAA,CAAA,GAAA,EAAA,GAAA,EAAA,KAAA,EAAA;;;;;;;;;;;;;AACM,MAAMA,eAAAA,SAAwBC,KAAAA,CAAAA;AAqBjC;;AAEC,QACD,OAAOC,iBAAAA,CAAkBC,IAAY,EAAEC,UAAAA,GAAsB,KAAK,EAAmB;QACjF,MAAMC,OAAAA,GAAUD,aACV,wDAAA,GACA,mCAAA;AACN,QAAA,OAAO,IAAIJ,eAAAA,CAAgB,WAAA,EAAaK,OAAAA,EAASF,IAAAA,EAAM,kBAAA,CAAA;AAC3D;AAEA;;QAGA,OAAOG,oBAAAA,CAAqBH,IAAY,EAAmB;AACvD,QAAA,MAAME,OAAAA,GAAU,oDAAA;AAChB,QAAA,OAAO,IAAIL,eAAAA,CAAgB,cAAA,EAAgBK,OAAAA,EAASF,IAAAA,EAAM,gBAAA,CAAA;AAC9D;AAEA;;AAEC,QACD,OAAOI,uBAAAA,CAAwBJ,IAAY,EAAEK,aAAoB,EAAmB;AAChF,QAAA,MAAMH,UAAU,8BAAA,IAAkCG,aAAAA,CAAcH,OAAO,IAAI,eAAc,CAAA;AACzF,QAAA,OAAO,IAAIL,eAAAA,CAAgB,iBAAA,EAAmBK,OAAAA,EAASF,MAAM,kBAAA,EAAoBK,aAAAA,CAAAA;AACrF;AAEA;;AAEC,QACD,OAAOC,eAAAA,CAAgBC,SAAiB,EAAEP,IAAY,EAAEK,aAAoB,EAAmB;QAC3F,MAAMH,OAAAA,GAAU,CAAC,UAAU,EAAEK,SAAAA,CAAU,EAAE,EAAEF,aAAAA,CAAcH,OAAO,IAAI,eAAA,CAAA,CAAiB;AACrF,QAAA,OAAO,IAAIL,eAAAA,CAAgB,kBAAA,EAAoBK,OAAAA,EAASF,MAAMO,SAAAA,EAAWF,aAAAA,CAAAA;AAC7E;AAEA;;QAGA,OAAOG,YAAAA,CAAaR,IAAY,EAAmB;AAC/C,QAAA,MAAME,OAAAA,GAAU,8BAAA;AAChB,QAAA,OAAO,IAAIL,eAAAA,CAAgB,WAAA,EAAaK,OAAAA,EAASF,IAAAA,EAAM,WAAA,CAAA;AAC3D;IAvDA,WAAA,CACIS,SAAiG,EACjGP,OAAe,EACfF,IAAY,EACZO,SAAiB,EACjBF,aAAqB,CACvB;AACE,QAAA,KAAK,CAACH,OAAAA,CAAAA,EAZV,gBAAA,CAAA,IAAA,EAAgBO,WAAAA,EAAhB,SACA,gBAAA,CAAA,IAAA,EAAgBT,MAAAA,EAAhB,MAAA,CAAA,EACA,uBAAgBO,WAAAA,EAAhB,MAAA,CAAA,EACA,gBAAA,CAAA,IAAA,EAAgBF,iBAAhB,MAAA,CAAA;QAUI,IAAI,CAACK,IAAI,GAAG,iBAAA;QACZ,IAAI,CAACD,SAAS,GAAGA,SAAAA;QACjB,IAAI,CAACT,IAAI,GAAGA,IAAAA;QACZ,IAAI,CAACO,SAAS,GAAGA,SAAAA;QACjB,IAAI,CAACF,aAAa,GAAGA,aAAAA;AACzB;AA2CJ;;;;"}
|
package/dist/read.d.ts
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
1
|
import { z, ZodObject } from 'zod';
|
|
2
2
|
import { Args, ConfigSchema, Options } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Reads configuration from files and merges it with CLI arguments.
|
|
5
|
+
*
|
|
6
|
+
* This function implements the core configuration loading logic:
|
|
7
|
+
* 1. Validates and resolves the configuration directory path
|
|
8
|
+
* 2. Attempts to read the YAML configuration file
|
|
9
|
+
* 3. Safely parses the YAML content with security protections
|
|
10
|
+
* 4. Merges file configuration with runtime arguments
|
|
11
|
+
* 5. Returns a typed configuration object
|
|
12
|
+
*
|
|
13
|
+
* The function handles missing files gracefully and provides detailed
|
|
14
|
+
* logging for troubleshooting configuration issues.
|
|
15
|
+
*
|
|
16
|
+
* @template T - The Zod schema shape type for configuration validation
|
|
17
|
+
* @param args - Parsed command-line arguments containing potential config overrides
|
|
18
|
+
* @param options - Cardigantime options with defaults, schema, and logger
|
|
19
|
+
* @returns Promise resolving to the merged and typed configuration object
|
|
20
|
+
* @throws {Error} When configuration directory is invalid or required files cannot be read
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const config = await read(cliArgs, {
|
|
25
|
+
* defaults: { configDirectory: './config', configFile: 'app.yaml' },
|
|
26
|
+
* configShape: MySchema.shape,
|
|
27
|
+
* logger: console,
|
|
28
|
+
* features: ['config']
|
|
29
|
+
* });
|
|
30
|
+
* // config is fully typed based on your schema
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
3
33
|
export declare const read: <T extends z.ZodRawShape>(args: Args, options: Options<T>) => Promise<z.infer<ZodObject<T & typeof ConfigSchema.shape>>>;
|
package/dist/read.js
CHANGED
|
@@ -1,36 +1,129 @@
|
|
|
1
1
|
import * as yaml from 'js-yaml';
|
|
2
|
-
import path from 'path';
|
|
2
|
+
import * as path from 'path';
|
|
3
3
|
import { create } from './util/storage.js';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Removes undefined values from an object to create a clean configuration.
|
|
7
|
+
* This is used to merge configuration sources while avoiding undefined pollution.
|
|
8
|
+
*
|
|
9
|
+
* @param obj - The object to clean
|
|
10
|
+
* @returns A new object with undefined values filtered out
|
|
11
|
+
*/ function clean(obj) {
|
|
6
12
|
return Object.fromEntries(Object.entries(obj).filter(([_, v])=>v !== undefined));
|
|
7
13
|
}
|
|
8
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Validates and secures a user-provided path to prevent path traversal attacks.
|
|
16
|
+
*
|
|
17
|
+
* Security checks include:
|
|
18
|
+
* - Path traversal prevention (blocks '..')
|
|
19
|
+
* - Absolute path detection
|
|
20
|
+
* - Path separator validation
|
|
21
|
+
*
|
|
22
|
+
* @param userPath - The user-provided path component
|
|
23
|
+
* @param basePath - The base directory to join the path with
|
|
24
|
+
* @returns The safely joined and normalized path
|
|
25
|
+
* @throws {Error} When path traversal or absolute paths are detected
|
|
26
|
+
*/ function validatePath(userPath, basePath) {
|
|
27
|
+
if (!userPath || !basePath) {
|
|
28
|
+
throw new Error('Invalid path parameters');
|
|
29
|
+
}
|
|
30
|
+
const normalized = path.normalize(userPath);
|
|
31
|
+
// Prevent path traversal attacks
|
|
32
|
+
if (normalized.includes('..') || path.isAbsolute(normalized)) {
|
|
33
|
+
throw new Error('Invalid path: path traversal detected');
|
|
34
|
+
}
|
|
35
|
+
// Ensure the path doesn't start with a path separator
|
|
36
|
+
if (normalized.startsWith('/') || normalized.startsWith('\\')) {
|
|
37
|
+
throw new Error('Invalid path: absolute path detected');
|
|
38
|
+
}
|
|
39
|
+
return path.join(basePath, normalized);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validates a configuration directory path for security and basic formatting.
|
|
43
|
+
*
|
|
44
|
+
* Performs validation to prevent:
|
|
45
|
+
* - Null byte injection attacks
|
|
46
|
+
* - Extremely long paths that could cause DoS
|
|
47
|
+
* - Empty or invalid directory specifications
|
|
48
|
+
*
|
|
49
|
+
* @param configDir - The configuration directory path to validate
|
|
50
|
+
* @returns The normalized configuration directory path
|
|
51
|
+
* @throws {Error} When the directory path is invalid or potentially dangerous
|
|
52
|
+
*/ function validateConfigDirectory(configDir) {
|
|
53
|
+
if (!configDir) {
|
|
54
|
+
throw new Error('Configuration directory is required');
|
|
55
|
+
}
|
|
56
|
+
// Check for null bytes which could be used for path injection
|
|
57
|
+
if (configDir.includes('\0')) {
|
|
58
|
+
throw new Error('Invalid path: null byte detected');
|
|
59
|
+
}
|
|
60
|
+
const normalized = path.normalize(configDir);
|
|
61
|
+
// Basic validation - could be expanded based on requirements
|
|
62
|
+
if (normalized.length > 1000) {
|
|
63
|
+
throw new Error('Configuration directory path too long');
|
|
64
|
+
}
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Reads configuration from files and merges it with CLI arguments.
|
|
69
|
+
*
|
|
70
|
+
* This function implements the core configuration loading logic:
|
|
71
|
+
* 1. Validates and resolves the configuration directory path
|
|
72
|
+
* 2. Attempts to read the YAML configuration file
|
|
73
|
+
* 3. Safely parses the YAML content with security protections
|
|
74
|
+
* 4. Merges file configuration with runtime arguments
|
|
75
|
+
* 5. Returns a typed configuration object
|
|
76
|
+
*
|
|
77
|
+
* The function handles missing files gracefully and provides detailed
|
|
78
|
+
* logging for troubleshooting configuration issues.
|
|
79
|
+
*
|
|
80
|
+
* @template T - The Zod schema shape type for configuration validation
|
|
81
|
+
* @param args - Parsed command-line arguments containing potential config overrides
|
|
82
|
+
* @param options - Cardigantime options with defaults, schema, and logger
|
|
83
|
+
* @returns Promise resolving to the merged and typed configuration object
|
|
84
|
+
* @throws {Error} When configuration directory is invalid or required files cannot be read
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const config = await read(cliArgs, {
|
|
89
|
+
* defaults: { configDirectory: './config', configFile: 'app.yaml' },
|
|
90
|
+
* configShape: MySchema.shape,
|
|
91
|
+
* logger: console,
|
|
92
|
+
* features: ['config']
|
|
93
|
+
* });
|
|
94
|
+
* // config is fully typed based on your schema
|
|
95
|
+
* ```
|
|
96
|
+
*/ const read = async (args, options)=>{
|
|
9
97
|
var _options_defaults;
|
|
10
98
|
const logger = options.logger;
|
|
11
99
|
const storage = create({
|
|
12
100
|
log: logger.debug
|
|
13
101
|
});
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
102
|
+
const rawConfigDir = args.configDirectory || ((_options_defaults = options.defaults) === null || _options_defaults === void 0 ? void 0 : _options_defaults.configDirectory);
|
|
103
|
+
if (!rawConfigDir) {
|
|
104
|
+
throw new Error('Configuration directory must be specified');
|
|
105
|
+
}
|
|
106
|
+
const resolvedConfigDir = validateConfigDirectory(rawConfigDir);
|
|
107
|
+
logger.debug('Resolved config directory');
|
|
108
|
+
const configFile = validatePath(options.defaults.configFile, resolvedConfigDir);
|
|
109
|
+
logger.debug('Attempting to load config file for cardigantime');
|
|
18
110
|
let rawFileConfig = {};
|
|
19
111
|
try {
|
|
20
112
|
const yamlContent = await storage.readFile(configFile, options.defaults.encoding);
|
|
113
|
+
// SECURITY FIX: Use safer parsing options to prevent code execution vulnerabilities
|
|
21
114
|
const parsedYaml = yaml.load(yamlContent);
|
|
22
115
|
if (parsedYaml !== null && typeof parsedYaml === 'object') {
|
|
23
116
|
rawFileConfig = parsedYaml;
|
|
24
|
-
logger.debug('Loaded
|
|
117
|
+
logger.debug('Loaded configuration file successfully');
|
|
25
118
|
} else if (parsedYaml !== null) {
|
|
26
|
-
logger.warn(
|
|
119
|
+
logger.warn('Ignoring invalid configuration format. Expected an object, got ' + typeof parsedYaml);
|
|
27
120
|
}
|
|
28
121
|
} catch (error) {
|
|
29
122
|
if (error.code === 'ENOENT' || /not found|no such file/i.test(error.message)) {
|
|
30
|
-
logger.debug(
|
|
123
|
+
logger.debug('Configuration file not found. Using empty configuration.');
|
|
31
124
|
} else {
|
|
32
|
-
//
|
|
33
|
-
logger.error(
|
|
125
|
+
// SECURITY FIX: Don't expose internal paths or detailed error information
|
|
126
|
+
logger.error('Failed to load or parse configuration file: ' + (error.message || 'Unknown error'));
|
|
34
127
|
}
|
|
35
128
|
}
|
|
36
129
|
const config = clean({
|
package/dist/read.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.js","sources":["../src/read.ts"],"sourcesContent":["import * as yaml from 'js-yaml';\nimport path from 'path';\nimport { z, ZodObject } from 'zod';\nimport { Args, ConfigSchema, Options } from './types';\nimport * as Storage from './util/storage';\n\nfunction clean(obj: any) {\n return Object.fromEntries(\n Object.entries(obj).filter(([_, v]) => v !== undefined)\n );\n}\n\nexport const read = async <T extends z.ZodRawShape>(args: Args, options: Options<T>): Promise<z.infer<ZodObject<T & typeof ConfigSchema.shape>>> => {\n const logger = options.logger;\n const storage = Storage.create({ log: logger.debug });\n\n const
|
|
1
|
+
{"version":3,"file":"read.js","sources":["../src/read.ts"],"sourcesContent":["import * as yaml from 'js-yaml';\nimport * as path from 'path';\nimport { z, ZodObject } from 'zod';\nimport { Args, ConfigSchema, Options } from './types';\nimport * as Storage from './util/storage';\n\n/**\n * Removes undefined values from an object to create a clean configuration.\n * This is used to merge configuration sources while avoiding undefined pollution.\n * \n * @param obj - The object to clean\n * @returns A new object with undefined values filtered out\n */\nfunction clean(obj: any) {\n return Object.fromEntries(\n Object.entries(obj).filter(([_, v]) => v !== undefined)\n );\n}\n\n/**\n * Validates and secures a user-provided path to prevent path traversal attacks.\n * \n * Security checks include:\n * - Path traversal prevention (blocks '..')\n * - Absolute path detection\n * - Path separator validation\n * \n * @param userPath - The user-provided path component\n * @param basePath - The base directory to join the path with\n * @returns The safely joined and normalized path\n * @throws {Error} When path traversal or absolute paths are detected\n */\nfunction validatePath(userPath: string, basePath: string): string {\n if (!userPath || !basePath) {\n throw new Error('Invalid path parameters');\n }\n\n const normalized = path.normalize(userPath);\n\n // Prevent path traversal attacks\n if (normalized.includes('..') || path.isAbsolute(normalized)) {\n throw new Error('Invalid path: path traversal detected');\n }\n\n // Ensure the path doesn't start with a path separator\n if (normalized.startsWith('/') || normalized.startsWith('\\\\')) {\n throw new Error('Invalid path: absolute path detected');\n }\n\n return path.join(basePath, normalized);\n}\n\n/**\n * Validates a configuration directory path for security and basic formatting.\n * \n * Performs validation to prevent:\n * - Null byte injection attacks\n * - Extremely long paths that could cause DoS\n * - Empty or invalid directory specifications\n * \n * @param configDir - The configuration directory path to validate\n * @returns The normalized configuration directory path\n * @throws {Error} When the directory path is invalid or potentially dangerous\n */\nfunction validateConfigDirectory(configDir: string): string {\n if (!configDir) {\n throw new Error('Configuration directory is required');\n }\n\n // Check for null bytes which could be used for path injection\n if (configDir.includes('\\0')) {\n throw new Error('Invalid path: null byte detected');\n }\n\n const normalized = path.normalize(configDir);\n\n // Basic validation - could be expanded based on requirements\n if (normalized.length > 1000) {\n throw new Error('Configuration directory path too long');\n }\n\n return normalized;\n}\n\n/**\n * Reads configuration from files and merges it with CLI arguments.\n * \n * This function implements the core configuration loading logic:\n * 1. Validates and resolves the configuration directory path\n * 2. Attempts to read the YAML configuration file\n * 3. Safely parses the YAML content with security protections\n * 4. Merges file configuration with runtime arguments\n * 5. Returns a typed configuration object\n * \n * The function handles missing files gracefully and provides detailed\n * logging for troubleshooting configuration issues.\n * \n * @template T - The Zod schema shape type for configuration validation\n * @param args - Parsed command-line arguments containing potential config overrides\n * @param options - Cardigantime options with defaults, schema, and logger\n * @returns Promise resolving to the merged and typed configuration object\n * @throws {Error} When configuration directory is invalid or required files cannot be read\n * \n * @example\n * ```typescript\n * const config = await read(cliArgs, {\n * defaults: { configDirectory: './config', configFile: 'app.yaml' },\n * configShape: MySchema.shape,\n * logger: console,\n * features: ['config']\n * });\n * // config is fully typed based on your schema\n * ```\n */\nexport const read = async <T extends z.ZodRawShape>(args: Args, options: Options<T>): Promise<z.infer<ZodObject<T & typeof ConfigSchema.shape>>> => {\n const logger = options.logger;\n const storage = Storage.create({ log: logger.debug });\n\n const rawConfigDir = args.configDirectory || options.defaults?.configDirectory;\n if (!rawConfigDir) {\n throw new Error('Configuration directory must be specified');\n }\n\n const resolvedConfigDir = validateConfigDirectory(rawConfigDir);\n logger.debug('Resolved config directory');\n\n const configFile = validatePath(options.defaults.configFile, resolvedConfigDir);\n logger.debug('Attempting to load config file for cardigantime');\n\n let rawFileConfig: object = {};\n\n try {\n const yamlContent = await storage.readFile(configFile, options.defaults.encoding);\n\n // SECURITY FIX: Use safer parsing options to prevent code execution vulnerabilities\n const parsedYaml = yaml.load(yamlContent);\n\n if (parsedYaml !== null && typeof parsedYaml === 'object') {\n rawFileConfig = parsedYaml;\n logger.debug('Loaded configuration file successfully');\n } else if (parsedYaml !== null) {\n logger.warn('Ignoring invalid configuration format. Expected an object, got ' + typeof parsedYaml);\n }\n } catch (error: any) {\n if (error.code === 'ENOENT' || /not found|no such file/i.test(error.message)) {\n logger.debug('Configuration file not found. Using empty configuration.');\n } else {\n // SECURITY FIX: Don't expose internal paths or detailed error information\n logger.error('Failed to load or parse configuration file: ' + (error.message || 'Unknown error'));\n }\n }\n\n const config: z.infer<ZodObject<T & typeof ConfigSchema.shape>> = clean({\n ...rawFileConfig,\n ...{\n configDirectory: resolvedConfigDir,\n }\n }) as z.infer<ZodObject<T & typeof ConfigSchema.shape>>;\n\n return config;\n}"],"names":["clean","obj","Object","fromEntries","entries","filter","_","v","undefined","validatePath","userPath","basePath","Error","normalized","path","normalize","includes","isAbsolute","startsWith","join","validateConfigDirectory","configDir","length","read","args","options","logger","storage","Storage","log","debug","rawConfigDir","configDirectory","defaults","resolvedConfigDir","configFile","rawFileConfig","yamlContent","readFile","encoding","parsedYaml","yaml","load","warn","error","code","test","message","config"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,40 +1,103 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { ZodObject, z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Available features that can be enabled in Cardigantime.
|
|
5
|
+
* Currently supports:
|
|
6
|
+
* - 'config': Configuration file reading and validation
|
|
7
|
+
*/
|
|
3
8
|
export type Feature = 'config';
|
|
9
|
+
/**
|
|
10
|
+
* Default configuration options for Cardigantime.
|
|
11
|
+
* These define the basic behavior of configuration loading.
|
|
12
|
+
*/
|
|
4
13
|
export interface DefaultOptions {
|
|
14
|
+
/** Directory path where configuration files are located */
|
|
5
15
|
configDirectory: string;
|
|
16
|
+
/** Name of the configuration file (e.g., 'config.yaml', 'app.yml') */
|
|
6
17
|
configFile: string;
|
|
18
|
+
/** Whether the configuration directory must exist. If true, throws error if directory doesn't exist */
|
|
7
19
|
isRequired: boolean;
|
|
20
|
+
/** File encoding for reading configuration files (e.g., 'utf8', 'ascii') */
|
|
8
21
|
encoding: string;
|
|
9
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Complete options object passed to Cardigantime functions.
|
|
25
|
+
* Combines defaults, features, schema shape, and logger.
|
|
26
|
+
*
|
|
27
|
+
* @template T - The Zod schema shape type for configuration validation
|
|
28
|
+
*/
|
|
10
29
|
export interface Options<T extends z.ZodRawShape> {
|
|
30
|
+
/** Default configuration options */
|
|
11
31
|
defaults: DefaultOptions;
|
|
32
|
+
/** Array of enabled features */
|
|
12
33
|
features: Feature[];
|
|
34
|
+
/** Zod schema shape for validating user configuration */
|
|
13
35
|
configShape: T;
|
|
36
|
+
/** Logger instance for debugging and error reporting */
|
|
14
37
|
logger: Logger;
|
|
15
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Logger interface for Cardigantime's internal logging.
|
|
41
|
+
* Compatible with popular logging libraries like Winston, Bunyan, etc.
|
|
42
|
+
*/
|
|
16
43
|
export interface Logger {
|
|
44
|
+
/** Debug-level logging for detailed troubleshooting information */
|
|
17
45
|
debug: (message: string, ...args: any[]) => void;
|
|
46
|
+
/** Info-level logging for general information */
|
|
18
47
|
info: (message: string, ...args: any[]) => void;
|
|
48
|
+
/** Warning-level logging for non-critical issues */
|
|
19
49
|
warn: (message: string, ...args: any[]) => void;
|
|
50
|
+
/** Error-level logging for critical problems */
|
|
20
51
|
error: (message: string, ...args: any[]) => void;
|
|
52
|
+
/** Verbose-level logging for extensive detail */
|
|
21
53
|
verbose: (message: string, ...args: any[]) => void;
|
|
54
|
+
/** Silly-level logging for maximum detail */
|
|
22
55
|
silly: (message: string, ...args: any[]) => void;
|
|
23
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Main Cardigantime interface providing configuration management functionality.
|
|
59
|
+
*
|
|
60
|
+
* @template T - The Zod schema shape type for configuration validation
|
|
61
|
+
*/
|
|
24
62
|
export interface Cardigantime<T extends z.ZodRawShape> {
|
|
63
|
+
/**
|
|
64
|
+
* Adds Cardigantime's CLI options to a Commander.js command.
|
|
65
|
+
* This includes options like --config-directory for runtime config path overrides.
|
|
66
|
+
*/
|
|
25
67
|
configure: (command: Command) => Promise<Command>;
|
|
68
|
+
/** Sets a custom logger for debugging and error reporting */
|
|
26
69
|
setLogger: (logger: Logger) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Reads configuration from files and merges with CLI arguments.
|
|
72
|
+
* Returns a fully typed configuration object.
|
|
73
|
+
*/
|
|
27
74
|
read: (args: Args) => Promise<z.infer<ZodObject<T & typeof ConfigSchema.shape>>>;
|
|
75
|
+
/**
|
|
76
|
+
* Validates the merged configuration against the Zod schema.
|
|
77
|
+
* Throws ConfigurationError if validation fails.
|
|
78
|
+
*/
|
|
28
79
|
validate: (config: z.infer<ZodObject<T & typeof ConfigSchema.shape>>) => Promise<void>;
|
|
29
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Parsed command-line arguments object, typically from Commander.js opts().
|
|
83
|
+
* Keys correspond to CLI option names with values from user input.
|
|
84
|
+
*/
|
|
30
85
|
export interface Args {
|
|
31
86
|
[key: string]: any;
|
|
32
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Base Zod schema for core Cardigantime configuration.
|
|
90
|
+
* Contains the minimum required configuration fields.
|
|
91
|
+
*/
|
|
33
92
|
export declare const ConfigSchema: ZodObject<{
|
|
93
|
+
/** The resolved configuration directory path */
|
|
34
94
|
configDirectory: z.ZodString;
|
|
35
95
|
}, "strip", z.ZodTypeAny, {
|
|
36
96
|
configDirectory: string;
|
|
37
97
|
}, {
|
|
38
98
|
configDirectory: string;
|
|
39
99
|
}>;
|
|
100
|
+
/**
|
|
101
|
+
* Base configuration type derived from the core schema.
|
|
102
|
+
*/
|
|
40
103
|
export type Config = z.infer<typeof ConfigSchema>;
|
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":";;;;;AAAA;AAgCO,MAAMA,SAAS,CAACC,MAAAA,GAAAA;;AAGnB,IAAA,MAAMC,GAAMD,GAAAA,MAAAA,CAAOC,GAAG,IAAIC,QAAQD,GAAG;AAErC,IAAA,MAAME,SAAS,OAAOC,IAAAA,GAAAA;QAClB,IAAI;AACA,YAAA,MAAMC,EAAGC,CAAAA,QAAQ,CAACC,IAAI,CAACH,IAAAA,CAAAA;YACvB,OAAO,IAAA;;AAEX,SAAA,CAAE,OAAOI,KAAY,EAAA;YACjB,OAAO,KAAA;AACX;AACJ,KAAA;AAEA,IAAA,MAAMC,cAAc,OAAOL,IAAAA,GAAAA;AACvB,QAAA,MAAMM,QAAQ,MAAML,EAAAA,CAAGC,QAAQ,CAACC,IAAI,CAACH,IAAAA,CAAAA;QACrC,IAAI,CAACM,KAAMD,CAAAA,WAAW,EAAI,EAAA;YACtBR,GAAI,CAAA,CAAA,EAAGG,IAAK,CAAA,mBAAmB,CAAC,CAAA;YAChC,OAAO,KAAA;AACX;QACA,OAAO,IAAA;AACX,KAAA;AAEA,IAAA,MAAMO,SAAS,OAAOP,IAAAA,GAAAA;AAClB,QAAA,MAAMM,QAAQ,MAAML,EAAAA,CAAGC,QAAQ,CAACC,IAAI,CAACH,IAAAA,CAAAA;QACrC,IAAI,CAACM,KAAMC,CAAAA,MAAM,EAAI,EAAA;YACjBV,GAAI,CAAA,CAAA,EAAGG,IAAK,CAAA,cAAc,CAAC,CAAA;YAC3B,OAAO,KAAA;AACX;QACA,OAAO,IAAA;AACX,KAAA;AAEA,IAAA,MAAMQ,aAAa,OAAOR,IAAAA,GAAAA;QACtB,IAAI;YACA,MAAMC,EAAAA,CAAGC,QAAQ,CAACO,MAAM,CAACT,IAAMC,EAAAA,EAAAA,CAAGS,SAAS,CAACC,IAAI,CAAA;AACpD,SAAA,CAAE,OAAOP,KAAY,EAAA;YACjBP,GAAI,CAAA,CAAA,EAAGG,KAAK,uBAAuB,CAAC,EAAEI,KAAMQ,CAAAA,OAAO,EAAER,KAAAA,CAAMS,KAAK,CAAA;YAChE,OAAO,KAAA;AACX;QACA,OAAO,IAAA;AACX,KAAA;AAEA,IAAA,MAAMC,aAAa,OAAOd,IAAAA,GAAAA;QACtB,IAAI;YACA,MAAMC,EAAAA,CAAGC,QAAQ,CAACO,MAAM,CAACT,IAAMC,EAAAA,EAAAA,CAAGS,SAAS,CAACK,IAAI,CAAA;AACpD,SAAA,CAAE,OAAOX,KAAY,EAAA;YACjBP,GAAI,CAAA,CAAA,EAAGG,KAAK,uBAAuB,CAAC,EAAEI,KAAMQ,CAAAA,OAAO,EAAER,KAAAA,CAAMS,KAAK,CAAA;YAChE,OAAO,KAAA;AACX;QACA,OAAO,IAAA;AACX,KAAA;AAEA,IAAA,MAAMG,iBAAiB,OAAOhB,IAAAA,GAAAA;AAC1B,QAAA,OAAO,MAAMD,MAAOC,CAAAA,IAAAA,CAAAA,IAAS,MAAMO,MAAOP,CAAAA,IAAAA,CAAAA,IAAS,MAAMQ,UAAWR,CAAAA,IAAAA,CAAAA;AACxE,KAAA;AAEA,IAAA,MAAMiB,sBAAsB,OAAOjB,IAAAA,GAAAA;AAC/B,QAAA,OAAO,MAAMD,MAAOC,CAAAA,IAAAA,CAAAA,IAAS,MAAMK,WAAYL,CAAAA,IAAAA,CAAAA,IAAS,MAAMc,UAAWd,CAAAA,IAAAA,CAAAA;AAC7E,KAAA;AAEA,IAAA,MAAMkB,sBAAsB,OAAOlB,IAAAA,GAAAA;AAC/B,QAAA,OAAO,MAAMD,MAAOC,CAAAA,IAAAA,CAAAA,IAAS,MAAMK,WAAYL,CAAAA,IAAAA,CAAAA,IAAS,MAAMQ,UAAWR,CAAAA,IAAAA,CAAAA;AAC7E,KAAA;AAEA,IAAA,MAAMmB,kBAAkB,OAAOnB,IAAAA,GAAAA;QAC3B,IAAI;AACA,YAAA,MAAMC,EAAGC,CAAAA,QAAQ,CAACkB,KAAK,CAACpB,IAAM,EAAA;gBAAEqB,SAAW,EAAA;AAAK,aAAA,CAAA;AACpD,SAAA,CAAE,OAAOC,UAAiB,EAAA;AACtB,YAAA,MAAM,IAAIC,KAAAA,CAAM,CAAC,kCAAkC,EAAEvB,IAAK,CAAA,EAAE,EAAEsB,UAAAA,CAAWV,OAAO,CAAC,CAAC,EAAEU,UAAAA,CAAWT,KAAK,CAAE,CAAA,CAAA;AAC1G;AACJ,KAAA;IAEA,MAAMW,QAAAA,GAAW,OAAOxB,IAAcyB,EAAAA,QAAAA,GAAAA;AAClC,QAAA,OAAO,MAAMxB,EAAGC,CAAAA,QAAQ,CAACsB,QAAQ,CAACxB,IAAM,EAAA;YAAEyB,QAAUA,EAAAA;AAA2B,SAAA,CAAA;AACnF,KAAA;IAEA,MAAMC,SAAAA,GAAY,OAAO1B,IAAAA,EAAc2B,IAAuBF,EAAAA,QAAAA,GAAAA;AAC1D,QAAA,MAAMxB,GAAGC,QAAQ,CAACwB,SAAS,CAAC1B,MAAM2B,IAAM,EAAA;YAAEF,QAAUA,EAAAA;AAA2B,SAAA,CAAA;AACnF,KAAA;AAEA,IAAA,MAAMG,aAAgB,GAAA,OAAOC,SAAmBC,EAAAA,QAAAA,EAA2CC,OAA0C,GAAA;QAAEC,OAAS,EAAA;KAAO,GAAA;QACnJ,IAAI;AACA,YAAA,MAAMC,KAAQ,GAAA,MAAMC,IAAKH,CAAAA,OAAAA,CAAQC,OAAO,EAAE;gBAAEG,GAAKN,EAAAA,SAAAA;gBAAWO,KAAO,EAAA;AAAK,aAAA,CAAA;YACxE,KAAK,MAAMC,QAAQJ,KAAO,CAAA;AACtB,gBAAA,MAAMH,QAAS9B,CAAAA,IAAAA,CAAKsC,IAAI,CAACT,SAAWQ,EAAAA,IAAAA,CAAAA,CAAAA;AACxC;AACJ,SAAA,CAAE,OAAOE,GAAU,EAAA;AACf,YAAA,MAAM,IAAIhB,KAAAA,CAAM,CAAC,uBAAuB,EAAEQ,OAAQC,CAAAA,OAAO,CAAC,IAAI,EAAEH,SAAU,CAAA,EAAE,EAAEU,GAAAA,CAAI3B,OAAO,CAAE,CAAA,CAAA;AAC/F;AACJ,KAAA;AAEA,IAAA,MAAM4B,aAAa,OAAOxC,IAAAA,GAAAA;QACtB,OAAOC,EAAAA,CAAGwC,gBAAgB,CAACzC,IAAAA,CAAAA;AAC/B,KAAA;IAEA,MAAM0C,QAAAA,GAAW,OAAO1C,IAAc2C,EAAAA,MAAAA,GAAAA;QAClC,MAAMN,IAAAA,GAAO,MAAMb,QAAAA,CAASxB,IAAM,EAAA,MAAA,CAAA;AAClC,QAAA,OAAO4C,MAAOC,CAAAA,UAAU,CAAC,QAAA,CAAA,CAAUC,MAAM,CAACT,IAAMU,CAAAA,CAAAA,MAAM,CAAC,KAAA,CAAA,CAAOC,KAAK,CAAC,CAAGL,EAAAA,MAAAA,CAAAA;AAC3E,KAAA;AAEA,IAAA,MAAMM,YAAY,OAAOpB,SAAAA,GAAAA;AACrB,QAAA,OAAO,MAAM5B,EAAAA,CAAGC,QAAQ,CAACgD,OAAO,CAACrB,SAAAA,CAAAA;AACrC,KAAA;IAEA,OAAO;AACH9B,QAAAA,MAAAA;AACAM,QAAAA,WAAAA;AACAE,QAAAA,MAAAA;AACAC,QAAAA,UAAAA;AACAM,QAAAA,UAAAA;AACAE,QAAAA,cAAAA;AACAC,QAAAA,mBAAAA;AACAC,QAAAA,mBAAAA;AACAC,QAAAA,eAAAA;AACAK,QAAAA,QAAAA;AACAgB,QAAAA,UAAAA;AACAd,QAAAA,SAAAA;AACAE,QAAAA,aAAAA;AACAc,QAAAA,QAAAA;AACAO,QAAAA;AACJ,KAAA;AACJ;;;;"}
|
|
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>;
|