@nmakarov/cli-toolkit 0.16.0 → 0.21.0

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.
Files changed (49) hide show
  1. package/README.md +9 -0
  2. package/dist/cli-runner.cjs +5335 -0
  3. package/dist/cli-runner.cjs.map +1 -0
  4. package/dist/cli-runner.js +5318 -0
  5. package/dist/cli-runner.js.map +1 -0
  6. package/dist/db.cjs +1 -1
  7. package/dist/db.cjs.map +1 -1
  8. package/dist/db.js +1 -1
  9. package/dist/db.js.map +1 -1
  10. package/dist/filedatabase.cjs +94 -13
  11. package/dist/filedatabase.cjs.map +1 -1
  12. package/dist/filedatabase.js +94 -13
  13. package/dist/filedatabase.js.map +1 -1
  14. package/dist/http-client2.cjs +1375 -11
  15. package/dist/http-client2.cjs.map +1 -1
  16. package/dist/http-client2.js +1365 -11
  17. package/dist/http-client2.js.map +1 -1
  18. package/dist/index.cjs +1639 -30
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +1608 -29
  21. package/dist/index.js.map +1 -1
  22. package/dist/init.cjs +25 -7
  23. package/dist/init.cjs.map +1 -1
  24. package/dist/init.js +25 -7
  25. package/dist/init.js.map +1 -1
  26. package/dist/logger.cjs +9 -6
  27. package/dist/logger.cjs.map +1 -1
  28. package/dist/logger.js +9 -6
  29. package/dist/logger.js.map +1 -1
  30. package/dist/mock-server.cjs +192 -343
  31. package/dist/mock-server.cjs.map +1 -1
  32. package/dist/mock-server.js +190 -343
  33. package/dist/mock-server.js.map +1 -1
  34. package/dist/params.cjs +16 -1
  35. package/dist/params.cjs.map +1 -1
  36. package/dist/params.js +16 -1
  37. package/dist/params.js.map +1 -1
  38. package/dist/tasks.cjs +2666 -0
  39. package/dist/tasks.cjs.map +1 -0
  40. package/dist/tasks.js +2600 -0
  41. package/dist/tasks.js.map +1 -0
  42. package/dist/utils.cjs +15 -2
  43. package/dist/utils.cjs.map +1 -1
  44. package/dist/utils.js +12 -1
  45. package/dist/utils.js.map +1 -1
  46. package/package.json +20 -5
  47. package/scripts/ssm/parse-cli.ts +35 -0
  48. package/scripts/ssm/ssm-admin.ts +151 -0
  49. package/scripts/ssm/ssm-pull.ts +150 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/utils/date-utils.ts","../src/utils/fs-utils.ts","../src/utils/os-utils.ts","../src/utils/format-utils.ts","../src/utils/crypto-utils.ts"],"sourcesContent":["// Re-export everything from the utils module directory\nexport * from \"./utils/index.js\";\n\n","/**\n * Date/time utility functions for timestamp formatting and timezone conversions\n */\n\nexport interface DateFormatOptions {\n timezone?: \"UTC\" | \"user\";\n format?: \"iso\" | \"iso-date\" | \"iso-time\" | \"human\";\n}\n\n/**\n * Format a date string or Date object to ISO8601 format in specified timezone\n * Always returns UTC-based ISO strings (YYYY-MM-DDTHH:mm:ssZ) as internal representation\n * \n * @param value - Date object, ISO string, or timestamp\n * @param options - Formatting options\n * @returns ISO8601 formatted string\n */\nexport function formatDate(value: Date | string | number | undefined, options: DateFormatOptions = {}): string {\n if (value === undefined || value === null) {\n return \"\";\n }\n\n const { timezone = \"UTC\", format = \"iso\" } = options;\n \n let date: Date;\n if (value instanceof Date) {\n date = value;\n } else if (typeof value === \"string\") {\n date = new Date(value);\n } else if (typeof value === \"number\") {\n date = new Date(value);\n } else {\n return \"\";\n }\n\n if (isNaN(date.getTime())) {\n return \"\";\n }\n\n // Format based on requested format\n switch (format) {\n case \"iso\":\n // Full ISO8601: 2025-01-01T01:01:01Z\n return date.toISOString();\n \n case \"iso-date\":\n // Date only: 2025-01-01\n return date.toISOString().split(\"T\")[0];\n \n case \"iso-time\":\n // Time only: 01:01:01Z\n return date.toISOString().split(\"T\")[1];\n \n case \"human\":\n // Human-readable: Jan 1, 2025 01:01:01 UTC\n if (timezone === \"user\") {\n return date.toLocaleString();\n }\n return date.toUTCString();\n \n default:\n return date.toISOString();\n }\n}\n\n/**\n * Parse a date value and return ISO8601 string in UTC\n * This is the canonical format for internal storage\n * \n * @param value - Date object or string\n * @returns ISO8601 string in UTC timezone\n */\nexport function toISOString(value: Date | string | number | undefined): string {\n return formatDate(value, { timezone: \"UTC\", format: \"iso\" });\n}\n\n/**\n * Get current timestamp as ISO8601 string in UTC\n * @returns Current time as ISO8601 string\n */\nexport function nowISO(): string {\n return new Date().toISOString();\n}\n\n/**\n * Parse ISO8601 string to Date object\n * @param isoString - ISO8601 formatted string\n * @returns Date object\n */\nexport function fromISOString(isoString: string): Date {\n return new Date(isoString);\n}\n\n/**\n * Calculate difference between two dates in various units\n * @param start - Start date (ISO string or Date)\n * @param end - End date (ISO string or Date)\n * @returns Object with duration in multiple units\n */\nexport function dateDiff(\n start: Date | string,\n end: Date | string\n): {\n milliseconds: number;\n seconds: number;\n minutes: number;\n hours: number;\n days: number;\n} {\n const startDate = start instanceof Date ? start : new Date(start);\n const endDate = end instanceof Date ? end : new Date(end);\n \n const diffMs = endDate.getTime() - startDate.getTime();\n \n return {\n milliseconds: diffMs,\n seconds: Math.floor(diffMs / 1000),\n minutes: Math.floor(diffMs / (1000 * 60)),\n hours: Math.floor(diffMs / (1000 * 60 * 60)),\n days: Math.floor(diffMs / (1000 * 60 * 60 * 24))\n };\n}\n\n/**\n * Check if a folder name looks like an ISO8601 timestamp\n * Used to identify version folders vs regular folders\n * @param folderName - Folder name to check\n * @returns true if the folder name is a valid ISO8601 timestamp\n */\nexport function isTimestampFolder(folderName: string): boolean {\n // Match ISO8601 format: YYYY-MM-DDTHH:mm:ssZ\n const isoRegex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\.\\d{3}Z)$/;\n \n if (!isoRegex.test(folderName)) {\n return false;\n }\n \n const date = new Date(folderName);\n return !isNaN(date.getTime()) && date.getTime() > 0;\n}\n\n/**\n * Generate a new version name (ISO8601 timestamp)\n * If existingVersions provided, ensures the new version is later than all existing ones\n * @param existingVersions - Array of existing version timestamps\n * @returns New version timestamp that is unique and later than existing ones\n */\nexport function generateVersionName(existingVersions: string[] = []): string {\n if (existingVersions.length === 0) {\n // No existing versions, use current timestamp\n const now = new Date();\n return now.toISOString().split(\".\")[0] + \"Z\";\n }\n \n // Find the maximum timestamp among all existing versions\n const maxTimestamp = existingVersions.reduce((max, version) => {\n const versionDate = new Date(version);\n const maxDate = new Date(max);\n return versionDate > maxDate ? version : max;\n });\n \n // Increment by 1 second to ensure uniqueness\n const maxDate = new Date(maxTimestamp);\n const nextDate = new Date(maxDate.getTime() + 1000);\n \n return nextDate.toISOString().split(\".\")[0] + \"Z\";\n}\n\n","/**\n * File System Utilities\n * \n * Helper functions for file system operations like path management,\n * directory creation, and file type detection\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\n/**\n * Ensure a directory path exists, creating it if necessary\n * Returns the absolute path\n */\nexport async function ensurePath(...pathParts: string[]): Promise<string> {\n const fullPath = path.resolve(...pathParts);\n \n if (!fs.existsSync(fullPath)) {\n await fs.promises.mkdir(fullPath, { recursive: true });\n }\n \n return fullPath;\n}\n\n/**\n * Synchronous version of ensurePath\n */\nexport function ensurePathSync(...pathParts: string[]): string {\n const fullPath = path.resolve(...pathParts);\n \n if (!fs.existsSync(fullPath)) {\n fs.mkdirSync(fullPath, { recursive: true });\n }\n \n return fullPath;\n}\n\n/**\n * Get file extension for a given data type\n */\nexport function getFileExtension(dataType: string): string {\n switch (dataType) {\n case \"json-array\":\n case \"json-object\":\n return \"json\";\n case \"text\":\n return \"txt\";\n case \"xml\":\n return \"xml\";\n default:\n return \"json\";\n }\n}\n\n/**\n * Get __dirname equivalent for ES modules\n * \n * Returns the directory path of the module file\n * Useful for resolving relative paths in ES modules where __dirname is not available\n * \n * @param metaUrl - The import.meta.url from the calling module (must be passed from calling module)\n * @returns The directory path of the module\n * \n * @example\n * ```typescript\n * import { getDirname } from \"@nmakarov/cli-toolkit/utils\";\n * const __dirname = getDirname(import.meta.url);\n * ```\n */\nexport function getDirname(metaUrl: string | URL): string {\n return path.dirname(fileURLToPath(metaUrl));\n}\n\n","/**\n * Operating System Utilities\n * \n * Helper functions for interacting with the operating system\n * to get system statistics and information\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\n\n/**\n * Get free disk space for a given path (in bytes)\n * Returns null if unable to determine\n * Uses `df` command on Unix-like systems\n */\nexport function getFreeDiskSpace(targetPath: string): number | null {\n try {\n // If the target path doesn't exist, use its parent directory\n let pathToCheck = targetPath;\n if (!fs.existsSync(targetPath)) {\n const parentDir = path.dirname(targetPath);\n if (fs.existsSync(parentDir)) {\n pathToCheck = parentDir;\n } else {\n // If parent doesn't exist either, use the root directory\n pathToCheck = process.platform === \"win32\" ? \"C:\\\\\" : \"/\";\n }\n }\n\n if (process.platform === \"win32\") {\n // Windows: use wmic command\n // Note: This is a simplified implementation, may need adjustments\n return null; // TODO: Implement robust Windows support\n } else {\n // Unix-like systems: use df command\n const stdout = execSync(`df -k \"${pathToCheck}\"`, { encoding: \"utf8\" });\n const lines = stdout.trim().split(\"\\n\");\n const parts = lines[1].split(/\\s+/); // second line, split on whitespace\n const freeKb = parseInt(parts[3], 10); // 4th column is \"Available\"\n return freeKb * 1024; // Convert to bytes\n }\n } catch (error) {\n // Silently fail and return null\n return null;\n }\n}\n\n","/**\n * Formatting Utilities\n * \n * Helper functions for converting between different data formats\n * and human-readable representations\n */\n\n/**\n * Convert bytes to human-readable format (e.g., \"1.5 MB\")\n */\nexport function bytesToHumanReadable(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n \n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i];\n}\n\n/**\n * Convert human-readable format to bytes (e.g., \"1.5 MB\" -> 1572864)\n */\nexport function humanReadableToBytes(humanString: string): number {\n const units: Record<string, number> = {\n \"B\": 1,\n \"KB\": 1024,\n \"MB\": 1024 * 1024,\n \"GB\": 1024 * 1024 * 1024,\n \"TB\": 1024 * 1024 * 1024 * 1024,\n \"PB\": 1024 * 1024 * 1024 * 1024 * 1024,\n };\n \n const match = humanString.trim().match(/^([\\d.]+)\\s*([A-Z]+)$/i);\n if (!match) {\n throw new Error(`Invalid format: ${humanString}. Expected format like \"2MB\" or \"1.5 GB\"`);\n }\n \n const value = parseFloat(match[1]);\n const unit = match[2].toUpperCase();\n \n if (!units[unit]) {\n throw new Error(`Unknown unit: ${unit}. Supported units: ${Object.keys(units).join(\", \")}`);\n }\n \n return Math.round(value * units[unit]);\n}\n\n","/**\n * Cryptography Utilities\n * \n * Helper functions for hashing, encryption, and data masking\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Calculate MD5 hash of a string (useful for content fingerprinting)\n */\nexport function md5(value: string): string {\n return crypto.createHash(\"md5\").update(value).digest(\"hex\");\n}\n\n/**\n * Sanitize sensitive data by replacing with MD5 hash\n * Useful for logging sensitive information while maintaining traceability\n */\nexport function maskValue(value: string): string {\n return `[md5:${md5(value)}]`;\n}\n\n"],"mappings":"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","names":["maxDate","path","fs","import_fs","import_path","fs","path","crypto"]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/utils/date-utils.ts","../src/utils/fs-utils.ts","../src/utils/os-utils.ts","../src/utils/format-utils.ts","../src/utils/crypto-utils.ts","../src/utils/core-utils.ts"],"sourcesContent":["// Re-export everything from the utils module directory\nexport * from \"./utils/index.js\";\n\n","/**\n * Date/time utility functions for timestamp formatting and timezone conversions\n */\n\nexport interface DateFormatOptions {\n timezone?: \"UTC\" | \"user\";\n format?: \"iso\" | \"iso-date\" | \"iso-time\" | \"human\";\n}\n\n/**\n * Format a date string or Date object to ISO8601 format in specified timezone\n * Always returns UTC-based ISO strings (YYYY-MM-DDTHH:mm:ssZ) as internal representation\n * \n * @param value - Date object, ISO string, or timestamp\n * @param options - Formatting options\n * @returns ISO8601 formatted string\n */\nexport function formatDate(value: Date | string | number | undefined, options: DateFormatOptions = {}): string {\n if (value === undefined || value === null) {\n return \"\";\n }\n\n const { timezone = \"UTC\", format = \"iso\" } = options;\n \n let date: Date;\n if (value instanceof Date) {\n date = value;\n } else if (typeof value === \"string\") {\n date = new Date(value);\n } else if (typeof value === \"number\") {\n date = new Date(value);\n } else {\n return \"\";\n }\n\n if (isNaN(date.getTime())) {\n return \"\";\n }\n\n // Format based on requested format\n switch (format) {\n case \"iso\":\n // Full ISO8601: 2025-01-01T01:01:01Z\n return date.toISOString();\n \n case \"iso-date\":\n // Date only: 2025-01-01\n return date.toISOString().split(\"T\")[0];\n \n case \"iso-time\":\n // Time only: 01:01:01Z\n return date.toISOString().split(\"T\")[1];\n \n case \"human\":\n // Human-readable: Jan 1, 2025 01:01:01 UTC\n if (timezone === \"user\") {\n return date.toLocaleString();\n }\n return date.toUTCString();\n \n default:\n return date.toISOString();\n }\n}\n\n/**\n * Parse a date value and return ISO8601 string in UTC\n * This is the canonical format for internal storage\n * \n * @param value - Date object or string\n * @returns ISO8601 string in UTC timezone\n */\nexport function toISOString(value: Date | string | number | undefined): string {\n return formatDate(value, { timezone: \"UTC\", format: \"iso\" });\n}\n\n/**\n * Get current timestamp as ISO8601 string in UTC\n * @returns Current time as ISO8601 string\n */\nexport function nowISO(): string {\n return new Date().toISOString();\n}\n\n/**\n * Parse ISO8601 string to Date object\n * @param isoString - ISO8601 formatted string\n * @returns Date object\n */\nexport function fromISOString(isoString: string): Date {\n return new Date(isoString);\n}\n\n/**\n * Calculate difference between two dates in various units\n * @param start - Start date (ISO string or Date)\n * @param end - End date (ISO string or Date)\n * @returns Object with duration in multiple units\n */\nexport function dateDiff(\n start: Date | string,\n end: Date | string\n): {\n milliseconds: number;\n seconds: number;\n minutes: number;\n hours: number;\n days: number;\n} {\n const startDate = start instanceof Date ? start : new Date(start);\n const endDate = end instanceof Date ? end : new Date(end);\n \n const diffMs = endDate.getTime() - startDate.getTime();\n \n return {\n milliseconds: diffMs,\n seconds: Math.floor(diffMs / 1000),\n minutes: Math.floor(diffMs / (1000 * 60)),\n hours: Math.floor(diffMs / (1000 * 60 * 60)),\n days: Math.floor(diffMs / (1000 * 60 * 60 * 24))\n };\n}\n\n/**\n * Check if a folder name looks like an ISO8601 timestamp\n * Used to identify version folders vs regular folders\n * @param folderName - Folder name to check\n * @returns true if the folder name is a valid ISO8601 timestamp\n */\nexport function isTimestampFolder(folderName: string): boolean {\n // Match ISO8601 format: YYYY-MM-DDTHH:mm:ssZ\n const isoRegex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\.\\d{3}Z)$/;\n \n if (!isoRegex.test(folderName)) {\n return false;\n }\n \n const date = new Date(folderName);\n return !isNaN(date.getTime()) && date.getTime() > 0;\n}\n\n/**\n * Generate a new version name (ISO8601 timestamp)\n * If existingVersions provided, ensures the new version is later than all existing ones\n * @param existingVersions - Array of existing version timestamps\n * @returns New version timestamp that is unique and later than existing ones\n */\nexport function generateVersionName(existingVersions: string[] = []): string {\n if (existingVersions.length === 0) {\n // No existing versions, use current timestamp\n const now = new Date();\n return now.toISOString().split(\".\")[0] + \"Z\";\n }\n \n // Find the maximum timestamp among all existing versions\n const maxTimestamp = existingVersions.reduce((max, version) => {\n const versionDate = new Date(version);\n const maxDate = new Date(max);\n return versionDate > maxDate ? version : max;\n });\n \n // Increment by 1 second to ensure uniqueness\n const maxDate = new Date(maxTimestamp);\n const nextDate = new Date(maxDate.getTime() + 1000);\n \n return nextDate.toISOString().split(\".\")[0] + \"Z\";\n}\n\n","/**\n * File System Utilities\n * \n * Helper functions for file system operations like path management,\n * directory creation, and file type detection\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\n/**\n * Ensure a directory path exists, creating it if necessary\n * Returns the absolute path\n */\nexport async function ensurePath(...pathParts: string[]): Promise<string> {\n const fullPath = path.resolve(...pathParts);\n \n if (!fs.existsSync(fullPath)) {\n await fs.promises.mkdir(fullPath, { recursive: true });\n }\n \n return fullPath;\n}\n\n/**\n * Synchronous version of ensurePath\n */\nexport function ensurePathSync(...pathParts: string[]): string {\n const fullPath = path.resolve(...pathParts);\n \n if (!fs.existsSync(fullPath)) {\n fs.mkdirSync(fullPath, { recursive: true });\n }\n \n return fullPath;\n}\n\n/**\n * Get file extension for a given data type\n */\nexport function getFileExtension(dataType: string): string {\n switch (dataType) {\n case \"json-array\":\n case \"json-object\":\n return \"json\";\n case \"text\":\n return \"txt\";\n case \"xml\":\n return \"xml\";\n default:\n return \"json\";\n }\n}\n\n/**\n * Get __dirname equivalent for ES modules\n * \n * Returns the directory path of the module file\n * Useful for resolving relative paths in ES modules where __dirname is not available\n * \n * @param metaUrl - The import.meta.url from the calling module (must be passed from calling module)\n * @returns The directory path of the module\n * \n * @example\n * ```typescript\n * import { getDirname } from \"@nmakarov/cli-toolkit/utils\";\n * const __dirname = getDirname(import.meta.url);\n * ```\n */\nexport function getDirname(metaUrl: string | URL): string {\n return path.dirname(fileURLToPath(metaUrl));\n}\n\n","/**\n * Operating System Utilities\n * \n * Helper functions for interacting with the operating system\n * to get system statistics and information\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\n\n/**\n * Get free disk space for a given path (in bytes)\n * Returns null if unable to determine\n * Uses `df` command on Unix-like systems\n */\nexport function getFreeDiskSpace(targetPath: string): number | null {\n try {\n // If the target path doesn't exist, use its parent directory\n let pathToCheck = targetPath;\n if (!fs.existsSync(targetPath)) {\n const parentDir = path.dirname(targetPath);\n if (fs.existsSync(parentDir)) {\n pathToCheck = parentDir;\n } else {\n // If parent doesn't exist either, use the root directory\n pathToCheck = process.platform === \"win32\" ? \"C:\\\\\" : \"/\";\n }\n }\n\n if (process.platform === \"win32\") {\n // Windows: use wmic command\n // Note: This is a simplified implementation, may need adjustments\n return null; // TODO: Implement robust Windows support\n } else {\n // Unix-like systems: use df command\n const stdout = execSync(`df -k \"${pathToCheck}\"`, { encoding: \"utf8\" });\n const lines = stdout.trim().split(\"\\n\");\n const parts = lines[1].split(/\\s+/); // second line, split on whitespace\n const freeKb = parseInt(parts[3], 10); // 4th column is \"Available\"\n return freeKb * 1024; // Convert to bytes\n }\n } catch (error) {\n // Silently fail and return null\n return null;\n }\n}\n\n","/**\n * Formatting Utilities\n * \n * Helper functions for converting between different data formats\n * and human-readable representations\n */\n\n/**\n * Convert bytes to human-readable format (e.g., \"1.5 MB\")\n */\nexport function bytesToHumanReadable(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n \n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i];\n}\n\n/**\n * Convert human-readable format to bytes (e.g., \"1.5 MB\" -> 1572864)\n */\nexport function humanReadableToBytes(humanString: string): number {\n const units: Record<string, number> = {\n \"B\": 1,\n \"KB\": 1024,\n \"MB\": 1024 * 1024,\n \"GB\": 1024 * 1024 * 1024,\n \"TB\": 1024 * 1024 * 1024 * 1024,\n \"PB\": 1024 * 1024 * 1024 * 1024 * 1024,\n };\n \n const match = humanString.trim().match(/^([\\d.]+)\\s*([A-Z]+)$/i);\n if (!match) {\n throw new Error(`Invalid format: ${humanString}. Expected format like \"2MB\" or \"1.5 GB\"`);\n }\n \n const value = parseFloat(match[1]);\n const unit = match[2].toUpperCase();\n \n if (!units[unit]) {\n throw new Error(`Unknown unit: ${unit}. Supported units: ${Object.keys(units).join(\", \")}`);\n }\n \n return Math.round(value * units[unit]);\n}\n\n","/**\n * Cryptography Utilities\n * \n * Helper functions for hashing, encryption, and data masking\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Calculate MD5 hash of a string (useful for content fingerprinting)\n */\nexport function md5(value: string): string {\n return crypto.createHash(\"md5\").update(value).digest(\"hex\");\n}\n\n/**\n * Sanitize sensitive data by replacing with MD5 hash\n * Useful for logging sensitive information while maintaining traceability\n */\nexport function maskValue(value: string): string {\n return `[md5:${md5(value)}]`;\n}\n\n","/**\n * Core lightweight utilities\n */\n\nexport function sleepMs(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function toJsonColumn(value: any): string | null {\n if (value === undefined || value === null) return null;\n return JSON.stringify(value);\n}\n"],"mappings":"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","names":["maxDate","path","fs","import_fs","import_path","fs","path","crypto"]}
package/dist/utils.js CHANGED
@@ -178,6 +178,15 @@ function md5(value) {
178
178
  function maskValue(value) {
179
179
  return `[md5:${md5(value)}]`;
180
180
  }
181
+
182
+ // src/utils/core-utils.ts
183
+ function sleepMs(ms) {
184
+ return new Promise((resolve) => setTimeout(resolve, ms));
185
+ }
186
+ function toJsonColumn(value) {
187
+ if (value === void 0 || value === null) return null;
188
+ return JSON.stringify(value);
189
+ }
181
190
  export {
182
191
  bytesToHumanReadable,
183
192
  dateDiff,
@@ -194,6 +203,8 @@ export {
194
203
  maskValue,
195
204
  md5,
196
205
  nowISO,
197
- toISOString
206
+ sleepMs,
207
+ toISOString,
208
+ toJsonColumn
198
209
  };
199
210
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/date-utils.ts","../src/utils/fs-utils.ts","../src/utils/os-utils.ts","../src/utils/format-utils.ts","../src/utils/crypto-utils.ts"],"sourcesContent":["/**\n * Date/time utility functions for timestamp formatting and timezone conversions\n */\n\nexport interface DateFormatOptions {\n timezone?: \"UTC\" | \"user\";\n format?: \"iso\" | \"iso-date\" | \"iso-time\" | \"human\";\n}\n\n/**\n * Format a date string or Date object to ISO8601 format in specified timezone\n * Always returns UTC-based ISO strings (YYYY-MM-DDTHH:mm:ssZ) as internal representation\n * \n * @param value - Date object, ISO string, or timestamp\n * @param options - Formatting options\n * @returns ISO8601 formatted string\n */\nexport function formatDate(value: Date | string | number | undefined, options: DateFormatOptions = {}): string {\n if (value === undefined || value === null) {\n return \"\";\n }\n\n const { timezone = \"UTC\", format = \"iso\" } = options;\n \n let date: Date;\n if (value instanceof Date) {\n date = value;\n } else if (typeof value === \"string\") {\n date = new Date(value);\n } else if (typeof value === \"number\") {\n date = new Date(value);\n } else {\n return \"\";\n }\n\n if (isNaN(date.getTime())) {\n return \"\";\n }\n\n // Format based on requested format\n switch (format) {\n case \"iso\":\n // Full ISO8601: 2025-01-01T01:01:01Z\n return date.toISOString();\n \n case \"iso-date\":\n // Date only: 2025-01-01\n return date.toISOString().split(\"T\")[0];\n \n case \"iso-time\":\n // Time only: 01:01:01Z\n return date.toISOString().split(\"T\")[1];\n \n case \"human\":\n // Human-readable: Jan 1, 2025 01:01:01 UTC\n if (timezone === \"user\") {\n return date.toLocaleString();\n }\n return date.toUTCString();\n \n default:\n return date.toISOString();\n }\n}\n\n/**\n * Parse a date value and return ISO8601 string in UTC\n * This is the canonical format for internal storage\n * \n * @param value - Date object or string\n * @returns ISO8601 string in UTC timezone\n */\nexport function toISOString(value: Date | string | number | undefined): string {\n return formatDate(value, { timezone: \"UTC\", format: \"iso\" });\n}\n\n/**\n * Get current timestamp as ISO8601 string in UTC\n * @returns Current time as ISO8601 string\n */\nexport function nowISO(): string {\n return new Date().toISOString();\n}\n\n/**\n * Parse ISO8601 string to Date object\n * @param isoString - ISO8601 formatted string\n * @returns Date object\n */\nexport function fromISOString(isoString: string): Date {\n return new Date(isoString);\n}\n\n/**\n * Calculate difference between two dates in various units\n * @param start - Start date (ISO string or Date)\n * @param end - End date (ISO string or Date)\n * @returns Object with duration in multiple units\n */\nexport function dateDiff(\n start: Date | string,\n end: Date | string\n): {\n milliseconds: number;\n seconds: number;\n minutes: number;\n hours: number;\n days: number;\n} {\n const startDate = start instanceof Date ? start : new Date(start);\n const endDate = end instanceof Date ? end : new Date(end);\n \n const diffMs = endDate.getTime() - startDate.getTime();\n \n return {\n milliseconds: diffMs,\n seconds: Math.floor(diffMs / 1000),\n minutes: Math.floor(diffMs / (1000 * 60)),\n hours: Math.floor(diffMs / (1000 * 60 * 60)),\n days: Math.floor(diffMs / (1000 * 60 * 60 * 24))\n };\n}\n\n/**\n * Check if a folder name looks like an ISO8601 timestamp\n * Used to identify version folders vs regular folders\n * @param folderName - Folder name to check\n * @returns true if the folder name is a valid ISO8601 timestamp\n */\nexport function isTimestampFolder(folderName: string): boolean {\n // Match ISO8601 format: YYYY-MM-DDTHH:mm:ssZ\n const isoRegex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\.\\d{3}Z)$/;\n \n if (!isoRegex.test(folderName)) {\n return false;\n }\n \n const date = new Date(folderName);\n return !isNaN(date.getTime()) && date.getTime() > 0;\n}\n\n/**\n * Generate a new version name (ISO8601 timestamp)\n * If existingVersions provided, ensures the new version is later than all existing ones\n * @param existingVersions - Array of existing version timestamps\n * @returns New version timestamp that is unique and later than existing ones\n */\nexport function generateVersionName(existingVersions: string[] = []): string {\n if (existingVersions.length === 0) {\n // No existing versions, use current timestamp\n const now = new Date();\n return now.toISOString().split(\".\")[0] + \"Z\";\n }\n \n // Find the maximum timestamp among all existing versions\n const maxTimestamp = existingVersions.reduce((max, version) => {\n const versionDate = new Date(version);\n const maxDate = new Date(max);\n return versionDate > maxDate ? version : max;\n });\n \n // Increment by 1 second to ensure uniqueness\n const maxDate = new Date(maxTimestamp);\n const nextDate = new Date(maxDate.getTime() + 1000);\n \n return nextDate.toISOString().split(\".\")[0] + \"Z\";\n}\n\n","/**\n * File System Utilities\n * \n * Helper functions for file system operations like path management,\n * directory creation, and file type detection\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\n/**\n * Ensure a directory path exists, creating it if necessary\n * Returns the absolute path\n */\nexport async function ensurePath(...pathParts: string[]): Promise<string> {\n const fullPath = path.resolve(...pathParts);\n \n if (!fs.existsSync(fullPath)) {\n await fs.promises.mkdir(fullPath, { recursive: true });\n }\n \n return fullPath;\n}\n\n/**\n * Synchronous version of ensurePath\n */\nexport function ensurePathSync(...pathParts: string[]): string {\n const fullPath = path.resolve(...pathParts);\n \n if (!fs.existsSync(fullPath)) {\n fs.mkdirSync(fullPath, { recursive: true });\n }\n \n return fullPath;\n}\n\n/**\n * Get file extension for a given data type\n */\nexport function getFileExtension(dataType: string): string {\n switch (dataType) {\n case \"json-array\":\n case \"json-object\":\n return \"json\";\n case \"text\":\n return \"txt\";\n case \"xml\":\n return \"xml\";\n default:\n return \"json\";\n }\n}\n\n/**\n * Get __dirname equivalent for ES modules\n * \n * Returns the directory path of the module file\n * Useful for resolving relative paths in ES modules where __dirname is not available\n * \n * @param metaUrl - The import.meta.url from the calling module (must be passed from calling module)\n * @returns The directory path of the module\n * \n * @example\n * ```typescript\n * import { getDirname } from \"@nmakarov/cli-toolkit/utils\";\n * const __dirname = getDirname(import.meta.url);\n * ```\n */\nexport function getDirname(metaUrl: string | URL): string {\n return path.dirname(fileURLToPath(metaUrl));\n}\n\n","/**\n * Operating System Utilities\n * \n * Helper functions for interacting with the operating system\n * to get system statistics and information\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\n\n/**\n * Get free disk space for a given path (in bytes)\n * Returns null if unable to determine\n * Uses `df` command on Unix-like systems\n */\nexport function getFreeDiskSpace(targetPath: string): number | null {\n try {\n // If the target path doesn't exist, use its parent directory\n let pathToCheck = targetPath;\n if (!fs.existsSync(targetPath)) {\n const parentDir = path.dirname(targetPath);\n if (fs.existsSync(parentDir)) {\n pathToCheck = parentDir;\n } else {\n // If parent doesn't exist either, use the root directory\n pathToCheck = process.platform === \"win32\" ? \"C:\\\\\" : \"/\";\n }\n }\n\n if (process.platform === \"win32\") {\n // Windows: use wmic command\n // Note: This is a simplified implementation, may need adjustments\n return null; // TODO: Implement robust Windows support\n } else {\n // Unix-like systems: use df command\n const stdout = execSync(`df -k \"${pathToCheck}\"`, { encoding: \"utf8\" });\n const lines = stdout.trim().split(\"\\n\");\n const parts = lines[1].split(/\\s+/); // second line, split on whitespace\n const freeKb = parseInt(parts[3], 10); // 4th column is \"Available\"\n return freeKb * 1024; // Convert to bytes\n }\n } catch (error) {\n // Silently fail and return null\n return null;\n }\n}\n\n","/**\n * Formatting Utilities\n * \n * Helper functions for converting between different data formats\n * and human-readable representations\n */\n\n/**\n * Convert bytes to human-readable format (e.g., \"1.5 MB\")\n */\nexport function bytesToHumanReadable(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n \n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i];\n}\n\n/**\n * Convert human-readable format to bytes (e.g., \"1.5 MB\" -> 1572864)\n */\nexport function humanReadableToBytes(humanString: string): number {\n const units: Record<string, number> = {\n \"B\": 1,\n \"KB\": 1024,\n \"MB\": 1024 * 1024,\n \"GB\": 1024 * 1024 * 1024,\n \"TB\": 1024 * 1024 * 1024 * 1024,\n \"PB\": 1024 * 1024 * 1024 * 1024 * 1024,\n };\n \n const match = humanString.trim().match(/^([\\d.]+)\\s*([A-Z]+)$/i);\n if (!match) {\n throw new Error(`Invalid format: ${humanString}. Expected format like \"2MB\" or \"1.5 GB\"`);\n }\n \n const value = parseFloat(match[1]);\n const unit = match[2].toUpperCase();\n \n if (!units[unit]) {\n throw new Error(`Unknown unit: ${unit}. Supported units: ${Object.keys(units).join(\", \")}`);\n }\n \n return Math.round(value * units[unit]);\n}\n\n","/**\n * Cryptography Utilities\n * \n * Helper functions for hashing, encryption, and data masking\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Calculate MD5 hash of a string (useful for content fingerprinting)\n */\nexport function md5(value: string): string {\n return crypto.createHash(\"md5\").update(value).digest(\"hex\");\n}\n\n/**\n * Sanitize sensitive data by replacing with MD5 hash\n * Useful for logging sensitive information while maintaining traceability\n */\nexport function maskValue(value: string): string {\n return `[md5:${md5(value)}]`;\n}\n\n"],"mappings":"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","names":["maxDate","fs","path"]}
1
+ {"version":3,"sources":["../src/utils/date-utils.ts","../src/utils/fs-utils.ts","../src/utils/os-utils.ts","../src/utils/format-utils.ts","../src/utils/crypto-utils.ts","../src/utils/core-utils.ts"],"sourcesContent":["/**\n * Date/time utility functions for timestamp formatting and timezone conversions\n */\n\nexport interface DateFormatOptions {\n timezone?: \"UTC\" | \"user\";\n format?: \"iso\" | \"iso-date\" | \"iso-time\" | \"human\";\n}\n\n/**\n * Format a date string or Date object to ISO8601 format in specified timezone\n * Always returns UTC-based ISO strings (YYYY-MM-DDTHH:mm:ssZ) as internal representation\n * \n * @param value - Date object, ISO string, or timestamp\n * @param options - Formatting options\n * @returns ISO8601 formatted string\n */\nexport function formatDate(value: Date | string | number | undefined, options: DateFormatOptions = {}): string {\n if (value === undefined || value === null) {\n return \"\";\n }\n\n const { timezone = \"UTC\", format = \"iso\" } = options;\n \n let date: Date;\n if (value instanceof Date) {\n date = value;\n } else if (typeof value === \"string\") {\n date = new Date(value);\n } else if (typeof value === \"number\") {\n date = new Date(value);\n } else {\n return \"\";\n }\n\n if (isNaN(date.getTime())) {\n return \"\";\n }\n\n // Format based on requested format\n switch (format) {\n case \"iso\":\n // Full ISO8601: 2025-01-01T01:01:01Z\n return date.toISOString();\n \n case \"iso-date\":\n // Date only: 2025-01-01\n return date.toISOString().split(\"T\")[0];\n \n case \"iso-time\":\n // Time only: 01:01:01Z\n return date.toISOString().split(\"T\")[1];\n \n case \"human\":\n // Human-readable: Jan 1, 2025 01:01:01 UTC\n if (timezone === \"user\") {\n return date.toLocaleString();\n }\n return date.toUTCString();\n \n default:\n return date.toISOString();\n }\n}\n\n/**\n * Parse a date value and return ISO8601 string in UTC\n * This is the canonical format for internal storage\n * \n * @param value - Date object or string\n * @returns ISO8601 string in UTC timezone\n */\nexport function toISOString(value: Date | string | number | undefined): string {\n return formatDate(value, { timezone: \"UTC\", format: \"iso\" });\n}\n\n/**\n * Get current timestamp as ISO8601 string in UTC\n * @returns Current time as ISO8601 string\n */\nexport function nowISO(): string {\n return new Date().toISOString();\n}\n\n/**\n * Parse ISO8601 string to Date object\n * @param isoString - ISO8601 formatted string\n * @returns Date object\n */\nexport function fromISOString(isoString: string): Date {\n return new Date(isoString);\n}\n\n/**\n * Calculate difference between two dates in various units\n * @param start - Start date (ISO string or Date)\n * @param end - End date (ISO string or Date)\n * @returns Object with duration in multiple units\n */\nexport function dateDiff(\n start: Date | string,\n end: Date | string\n): {\n milliseconds: number;\n seconds: number;\n minutes: number;\n hours: number;\n days: number;\n} {\n const startDate = start instanceof Date ? start : new Date(start);\n const endDate = end instanceof Date ? end : new Date(end);\n \n const diffMs = endDate.getTime() - startDate.getTime();\n \n return {\n milliseconds: diffMs,\n seconds: Math.floor(diffMs / 1000),\n minutes: Math.floor(diffMs / (1000 * 60)),\n hours: Math.floor(diffMs / (1000 * 60 * 60)),\n days: Math.floor(diffMs / (1000 * 60 * 60 * 24))\n };\n}\n\n/**\n * Check if a folder name looks like an ISO8601 timestamp\n * Used to identify version folders vs regular folders\n * @param folderName - Folder name to check\n * @returns true if the folder name is a valid ISO8601 timestamp\n */\nexport function isTimestampFolder(folderName: string): boolean {\n // Match ISO8601 format: YYYY-MM-DDTHH:mm:ssZ\n const isoRegex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\.\\d{3}Z)$/;\n \n if (!isoRegex.test(folderName)) {\n return false;\n }\n \n const date = new Date(folderName);\n return !isNaN(date.getTime()) && date.getTime() > 0;\n}\n\n/**\n * Generate a new version name (ISO8601 timestamp)\n * If existingVersions provided, ensures the new version is later than all existing ones\n * @param existingVersions - Array of existing version timestamps\n * @returns New version timestamp that is unique and later than existing ones\n */\nexport function generateVersionName(existingVersions: string[] = []): string {\n if (existingVersions.length === 0) {\n // No existing versions, use current timestamp\n const now = new Date();\n return now.toISOString().split(\".\")[0] + \"Z\";\n }\n \n // Find the maximum timestamp among all existing versions\n const maxTimestamp = existingVersions.reduce((max, version) => {\n const versionDate = new Date(version);\n const maxDate = new Date(max);\n return versionDate > maxDate ? version : max;\n });\n \n // Increment by 1 second to ensure uniqueness\n const maxDate = new Date(maxTimestamp);\n const nextDate = new Date(maxDate.getTime() + 1000);\n \n return nextDate.toISOString().split(\".\")[0] + \"Z\";\n}\n\n","/**\n * File System Utilities\n * \n * Helper functions for file system operations like path management,\n * directory creation, and file type detection\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\n/**\n * Ensure a directory path exists, creating it if necessary\n * Returns the absolute path\n */\nexport async function ensurePath(...pathParts: string[]): Promise<string> {\n const fullPath = path.resolve(...pathParts);\n \n if (!fs.existsSync(fullPath)) {\n await fs.promises.mkdir(fullPath, { recursive: true });\n }\n \n return fullPath;\n}\n\n/**\n * Synchronous version of ensurePath\n */\nexport function ensurePathSync(...pathParts: string[]): string {\n const fullPath = path.resolve(...pathParts);\n \n if (!fs.existsSync(fullPath)) {\n fs.mkdirSync(fullPath, { recursive: true });\n }\n \n return fullPath;\n}\n\n/**\n * Get file extension for a given data type\n */\nexport function getFileExtension(dataType: string): string {\n switch (dataType) {\n case \"json-array\":\n case \"json-object\":\n return \"json\";\n case \"text\":\n return \"txt\";\n case \"xml\":\n return \"xml\";\n default:\n return \"json\";\n }\n}\n\n/**\n * Get __dirname equivalent for ES modules\n * \n * Returns the directory path of the module file\n * Useful for resolving relative paths in ES modules where __dirname is not available\n * \n * @param metaUrl - The import.meta.url from the calling module (must be passed from calling module)\n * @returns The directory path of the module\n * \n * @example\n * ```typescript\n * import { getDirname } from \"@nmakarov/cli-toolkit/utils\";\n * const __dirname = getDirname(import.meta.url);\n * ```\n */\nexport function getDirname(metaUrl: string | URL): string {\n return path.dirname(fileURLToPath(metaUrl));\n}\n\n","/**\n * Operating System Utilities\n * \n * Helper functions for interacting with the operating system\n * to get system statistics and information\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\n\n/**\n * Get free disk space for a given path (in bytes)\n * Returns null if unable to determine\n * Uses `df` command on Unix-like systems\n */\nexport function getFreeDiskSpace(targetPath: string): number | null {\n try {\n // If the target path doesn't exist, use its parent directory\n let pathToCheck = targetPath;\n if (!fs.existsSync(targetPath)) {\n const parentDir = path.dirname(targetPath);\n if (fs.existsSync(parentDir)) {\n pathToCheck = parentDir;\n } else {\n // If parent doesn't exist either, use the root directory\n pathToCheck = process.platform === \"win32\" ? \"C:\\\\\" : \"/\";\n }\n }\n\n if (process.platform === \"win32\") {\n // Windows: use wmic command\n // Note: This is a simplified implementation, may need adjustments\n return null; // TODO: Implement robust Windows support\n } else {\n // Unix-like systems: use df command\n const stdout = execSync(`df -k \"${pathToCheck}\"`, { encoding: \"utf8\" });\n const lines = stdout.trim().split(\"\\n\");\n const parts = lines[1].split(/\\s+/); // second line, split on whitespace\n const freeKb = parseInt(parts[3], 10); // 4th column is \"Available\"\n return freeKb * 1024; // Convert to bytes\n }\n } catch (error) {\n // Silently fail and return null\n return null;\n }\n}\n\n","/**\n * Formatting Utilities\n * \n * Helper functions for converting between different data formats\n * and human-readable representations\n */\n\n/**\n * Convert bytes to human-readable format (e.g., \"1.5 MB\")\n */\nexport function bytesToHumanReadable(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n \n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i];\n}\n\n/**\n * Convert human-readable format to bytes (e.g., \"1.5 MB\" -> 1572864)\n */\nexport function humanReadableToBytes(humanString: string): number {\n const units: Record<string, number> = {\n \"B\": 1,\n \"KB\": 1024,\n \"MB\": 1024 * 1024,\n \"GB\": 1024 * 1024 * 1024,\n \"TB\": 1024 * 1024 * 1024 * 1024,\n \"PB\": 1024 * 1024 * 1024 * 1024 * 1024,\n };\n \n const match = humanString.trim().match(/^([\\d.]+)\\s*([A-Z]+)$/i);\n if (!match) {\n throw new Error(`Invalid format: ${humanString}. Expected format like \"2MB\" or \"1.5 GB\"`);\n }\n \n const value = parseFloat(match[1]);\n const unit = match[2].toUpperCase();\n \n if (!units[unit]) {\n throw new Error(`Unknown unit: ${unit}. Supported units: ${Object.keys(units).join(\", \")}`);\n }\n \n return Math.round(value * units[unit]);\n}\n\n","/**\n * Cryptography Utilities\n * \n * Helper functions for hashing, encryption, and data masking\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Calculate MD5 hash of a string (useful for content fingerprinting)\n */\nexport function md5(value: string): string {\n return crypto.createHash(\"md5\").update(value).digest(\"hex\");\n}\n\n/**\n * Sanitize sensitive data by replacing with MD5 hash\n * Useful for logging sensitive information while maintaining traceability\n */\nexport function maskValue(value: string): string {\n return `[md5:${md5(value)}]`;\n}\n\n","/**\n * Core lightweight utilities\n */\n\nexport function sleepMs(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function toJsonColumn(value: any): string | null {\n if (value === undefined || value === null) return null;\n return JSON.stringify(value);\n}\n"],"mappings":"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","names":["maxDate","fs","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nmakarov/cli-toolkit",
3
- "version": "0.16.0",
3
+ "version": "0.21.0",
4
4
  "description": "A comprehensive toolkit for building CLI applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -67,20 +67,32 @@
67
67
  "import": "./dist/db.js",
68
68
  "require": "./dist/db.cjs"
69
69
  },
70
+ "./tasks": {
71
+ "types": "./dist/tasks.d.ts",
72
+ "import": "./dist/tasks.js",
73
+ "require": "./dist/tasks.cjs"
74
+ },
70
75
  "./utils": {
71
76
  "types": "./dist/utils.d.ts",
72
77
  "import": "./dist/utils.js",
73
78
  "require": "./dist/utils.cjs"
74
79
  }
75
80
  },
81
+ "bin": {
82
+ "cli-runner": "./dist/cli-runner.js"
83
+ },
76
84
  "scripts": {
85
+ "ssm:list": "tsx scripts/ssm/ssm-admin.ts list",
86
+ "ssm:get": "tsx scripts/ssm/ssm-admin.ts get",
87
+ "ssm:put": "tsx scripts/ssm/ssm-admin.ts put",
88
+ "ssm:pull": "tsx scripts/ssm/ssm-pull.ts",
77
89
  "build": "tsup && node scripts/fix-cjs-esm-deps.js",
78
90
  "dev": "tsup --watch",
79
91
  "test": "vitest",
80
92
  "test:watch": "vitest --watch",
81
93
  "test:coverage": "vitest --coverage",
82
94
  "test:coverage:open": "vitest --coverage && open coverage/index.html",
83
- "test:ci": "vitest run src/args/tests/args.ci.test.ts src/params/tests/params.ci.test.ts src/errors/tests/errors.ci.test.ts src/screen/tests/screen.ci.test.ts src/logger/tests/logger.ci.test.ts src/filedatabase/tests/filestore.ci.test.ts src/filedatabase/tests/synopsis-functions.ci.test.ts src/http-client/tests/http-client.ci.test.ts src/http-client2/tests/http-client2.ci.test.ts src/mock-server/tests/mock-server.ci.test.ts src/db/tests/db.ci.test.ts src/utils/tests/format-utils.ci.test.ts src/utils/tests/date-utils.ci.test.ts src/utils/tests/crypto-utils.ci.test.ts src/utils/tests/fs-utils.ci.test.ts src/utils/tests/os-utils.ci.test.ts src/init/tests/init.ci.test.ts --coverage",
95
+ "test:ci": "vitest run src/args/tests/args.ci.test.ts src/params/tests/params.ci.test.ts src/errors/tests/errors.ci.test.ts src/screen/tests/screen.ci.test.ts src/logger/tests/logger.ci.test.ts src/filedatabase/tests/filestore.ci.test.ts src/filedatabase/tests/synopsis-functions.ci.test.ts src/http-client/tests/http-client.ci.test.ts src/http-client2/tests/http-client2.ci.test.ts src/mock-server/tests/mock-server.ci.test.ts src/mock-server/tests/mock-storage.ci.test.ts src/db/tests/db.ci.test.ts src/utils/tests/format-utils.ci.test.ts src/utils/tests/date-utils.ci.test.ts src/utils/tests/crypto-utils.ci.test.ts src/utils/tests/fs-utils.ci.test.ts src/utils/tests/os-utils.ci.test.ts src/init/tests/init.ci.test.ts --coverage",
84
96
  "test:args": "vitest run src/args/tests",
85
97
  "test:args:ci": "vitest run src/args/tests/args.ci.test.ts --coverage",
86
98
  "test:params": "vitest run src/params/tests",
@@ -101,7 +113,7 @@
101
113
  "test:http-client2": "vitest run src/http-client2/tests",
102
114
  "test:http-client2:ci": "vitest run src/http-client2/tests/http-client2.ci.test.ts --coverage",
103
115
  "test:mock-server": "vitest run src/mock-server/tests",
104
- "test:mock-server:ci": "vitest run src/mock-server/tests/mock-server.ci.test.ts --coverage",
116
+ "test:mock-server:ci": "vitest run src/mock-server/tests/mock-server.ci.test.ts src/mock-server/tests/mock-storage.ci.test.ts --coverage",
105
117
  "test:mock-server:coverage": "vitest run src/mock-server/tests --coverage",
106
118
  "test:db": "vitest run src/db/tests",
107
119
  "test:db:ci": "vitest run src/db/tests/db.ci.test.ts --coverage",
@@ -143,6 +155,7 @@
143
155
  },
144
156
  "files": [
145
157
  "dist/",
158
+ "scripts/ssm/",
146
159
  "README.md",
147
160
  "LICENSE"
148
161
  ],
@@ -155,11 +168,13 @@
155
168
  "eslint": "^9.38.0",
156
169
  "prettier": "^3.6.2",
157
170
  "tsup": "^8.5.0",
171
+ "tsx": "^4.20.0",
158
172
  "typedoc": "^0.28.14",
159
173
  "typescript": "^5.9.3",
160
174
  "vitest": "^4.0.3"
161
175
  },
162
176
  "dependencies": {
177
+ "@aws-sdk/client-ssm": "^3.750.0",
163
178
  "@types/joi": "^17.2.3",
164
179
  "axios": "^1.6.0",
165
180
  "chalk": "^4.1.2",
@@ -168,7 +183,8 @@
168
183
  "joi": "^18.0.1",
169
184
  "knex": "^3.1.0",
170
185
  "morgan": "^1.10.0",
171
- "mysql": "^2.18.1"
186
+ "mysql": "^2.18.1",
187
+ "pg": "^8.20.0"
172
188
  },
173
189
  "engines": {
174
190
  "node": ">=20.0.0"
@@ -176,7 +192,6 @@
176
192
  "peerDependencies": {
177
193
  "ink": "^4.0.0",
178
194
  "mysql2": "^3.0.0",
179
- "pg": "^8.16.3",
180
195
  "react": "^18.0.0"
181
196
  },
182
197
  "peerDependenciesMeta": {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Minimal argv parser: supports --key=value and --key value (boolean if no value).
3
+ */
4
+ export function parseCli(argv: string[]): { _: string[]; flags: Record<string, string | boolean> } {
5
+ const flags: Record<string, string | boolean> = {};
6
+ const rest: string[] = [];
7
+ for (let i = 0; i < argv.length; i++) {
8
+ const a = argv[i];
9
+ if (!a.startsWith("--")) {
10
+ rest.push(a);
11
+ continue;
12
+ }
13
+ const body = a.slice(2);
14
+ if (body.includes("=")) {
15
+ const eq = body.indexOf("=");
16
+ const k = body.slice(0, eq);
17
+ const v = body.slice(eq + 1);
18
+ flags[k] = v;
19
+ continue;
20
+ }
21
+ const k = body;
22
+ const next = argv[i + 1];
23
+ if (next !== undefined && !next.startsWith("--")) {
24
+ flags[k] = next;
25
+ i++;
26
+ } else {
27
+ flags[k] = true;
28
+ }
29
+ }
30
+ return { _: rest, flags };
31
+ }
32
+
33
+ export function getRegion(): string {
34
+ return process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || "ca-central-1";
35
+ }
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Local admin CLI for SSM Parameter Store (uses your workstation AWS credentials / profile).
4
+ *
5
+ * Prerequisites: IAM permission for ssm:Get*, ssm:PutParameter, ssm:DeleteParameter on your parameters
6
+ * (and kms:Decrypt/Encrypt if you use SecureString).
7
+ *
8
+ * Run from this package root (`subprojects/cli-toolkit`):
9
+ *
10
+ * npx tsx scripts/ssm/ssm-admin.ts list --prefix /mlsfarm/tf3/
11
+ * npm run ssm:list -- --prefix /mlsfarm/tf3/
12
+ *
13
+ * Or from a project that depends on `@nmakarov/cli-toolkit` (after a release that ships `scripts/ssm/`):
14
+ *
15
+ * npx tsx node_modules/@nmakarov/cli-toolkit/scripts/ssm/ssm-admin.ts list --prefix /mlsfarm/tf3/
16
+ *
17
+ * Optional: AWS_REGION, AWS_PROFILE (standard SDK env).
18
+ */
19
+
20
+ import {
21
+ SSMClient,
22
+ GetParametersByPathCommand,
23
+ GetParameterCommand,
24
+ PutParameterCommand,
25
+ DeleteParameterCommand,
26
+ } from "@aws-sdk/client-ssm";
27
+ import { parseCli, getRegion } from "./parse-cli.js";
28
+
29
+ function usage(): void {
30
+ console.error(`Usage:
31
+ npx tsx scripts/ssm/ssm-admin.ts list --prefix /path/ [--recursive false]
32
+ npx tsx scripts/ssm/ssm-admin.ts get --name /path/KEY
33
+ npx tsx scripts/ssm/ssm-admin.ts put --name /path/KEY --value "..." [--type String|SecureString] [--overwrite]
34
+ npx tsx scripts/ssm/ssm-admin.ts delete --name /path/KEY`);
35
+ }
36
+
37
+ function ensureLeadingSlash(path: string): string {
38
+ const p = path.trim();
39
+ if (!p.startsWith("/")) return `/${p}`;
40
+ return p;
41
+ }
42
+
43
+ async function main(): Promise<void> {
44
+ const { _, flags } = parseCli(process.argv.slice(2));
45
+ const cmd = (_[0] || "").toLowerCase();
46
+ if (!cmd || cmd === "help" || cmd === "-h" || cmd === "--help") {
47
+ usage();
48
+ process.exit(cmd ? 0 : 1);
49
+ }
50
+
51
+ const region = typeof flags.region === "string" ? flags.region : getRegion();
52
+ const client = new SSMClient({ region });
53
+
54
+ if (cmd === "list") {
55
+ const prefix = ensureLeadingSlash(String(flags.prefix || "/mlsfarm/tf3/"));
56
+ const recursive = flags.recursive === false || flags.recursive === "false" ? false : true;
57
+ let next: string | undefined;
58
+ const rows: { name?: string; type?: string; version?: number }[] = [];
59
+ do {
60
+ const out = await client.send(
61
+ new GetParametersByPathCommand({
62
+ Path: prefix,
63
+ Recursive: recursive,
64
+ WithDecryption: true,
65
+ NextToken: next,
66
+ })
67
+ );
68
+ for (const p of out.Parameters || []) {
69
+ rows.push({ name: p.Name, type: p.Type, version: p.Version });
70
+ }
71
+ next = out.NextToken;
72
+ } while (next);
73
+
74
+ if (rows.length === 0) {
75
+ console.log(`(no parameters under ${prefix})`);
76
+ return;
77
+ }
78
+ const w = Math.max(...rows.map((r) => (r.name || "").length), 4);
79
+ console.log(`${"NAME".padEnd(w)} TYPE VER`);
80
+ for (const r of rows) {
81
+ console.log(`${(r.name || "").padEnd(w)} ${(r.type || "").padEnd(14)} ${r.version ?? ""}`);
82
+ }
83
+ return;
84
+ }
85
+
86
+ if (cmd === "get") {
87
+ const name = flags.name;
88
+ if (typeof name !== "string" || !name.trim()) {
89
+ console.error("get: --name is required");
90
+ usage();
91
+ process.exit(1);
92
+ }
93
+ const out = await client.send(
94
+ new GetParameterCommand({ Name: ensureLeadingSlash(name), WithDecryption: true })
95
+ );
96
+ const p = out.Parameter;
97
+ if (!p?.Name) {
98
+ console.error("Parameter not found");
99
+ process.exit(1);
100
+ }
101
+ console.log(`Name: ${p.Name}`);
102
+ console.log(`Type: ${p.Type}`);
103
+ console.log(`Value: ${p.Value ?? ""}`);
104
+ return;
105
+ }
106
+
107
+ if (cmd === "put") {
108
+ const name = flags.name;
109
+ const value = flags.value;
110
+ if (typeof name !== "string" || !name.trim()) {
111
+ console.error("put: --name is required");
112
+ process.exit(1);
113
+ }
114
+ if (typeof value !== "string") {
115
+ console.error("put: --value is required (string)");
116
+ process.exit(1);
117
+ }
118
+ const type = (flags.type as string) === "SecureString" ? "SecureString" : "String";
119
+ const overwrite = flags.overwrite === true || flags.overwrite === "true";
120
+ const ver = await client.send(
121
+ new PutParameterCommand({
122
+ Name: ensureLeadingSlash(name),
123
+ Value: value,
124
+ Type: type,
125
+ Overwrite: overwrite,
126
+ })
127
+ );
128
+ console.log(`OK Version ${ver.Version}`);
129
+ return;
130
+ }
131
+
132
+ if (cmd === "delete") {
133
+ const name = flags.name;
134
+ if (typeof name !== "string" || !name.trim()) {
135
+ console.error("delete: --name is required");
136
+ process.exit(1);
137
+ }
138
+ await client.send(new DeleteParameterCommand({ Name: ensureLeadingSlash(name) }));
139
+ console.log("OK deleted");
140
+ return;
141
+ }
142
+
143
+ console.error(`Unknown command: ${cmd}`);
144
+ usage();
145
+ process.exit(1);
146
+ }
147
+
148
+ main().catch((e) => {
149
+ console.error(e instanceof Error ? e.message : e);
150
+ process.exit(1);
151
+ });
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pull all SSM parameters under a path (recursive) for use on servers or locally.
4
+ * Uses default credential chain (on EC2: instance profile; e.g. mlsfarm tf3 `04.4.ssm_iam.tf`).
5
+ *
6
+ * Output modes:
7
+ * print — table to stdout (default)
8
+ * dotenv — KEY=value lines (dotenv-friendly; optional --file)
9
+ * shell — export KEY='...' lines for: eval "$(npx tsx ... --output shell)"
10
+ *
11
+ * Env key = last path segment of the parameter name (e.g. /mlsfarm/tf3/DATABASE_URL → DATABASE_URL).
12
+ *
13
+ * From package root:
14
+ *
15
+ * npx tsx scripts/ssm/ssm-pull.ts --path /mlsfarm/tf3/
16
+ * npm run ssm:pull -- --path /mlsfarm/tf3/ --output dotenv --file .env.from-ssm
17
+ * eval "$(npx tsx scripts/ssm/ssm-pull.ts --path /mlsfarm/tf3/ --output shell)"
18
+ */
19
+
20
+ import { SSMClient, GetParametersByPathCommand } from "@aws-sdk/client-ssm";
21
+ import { writeFileSync } from "fs";
22
+ import { parseCli, getRegion } from "./parse-cli.js";
23
+
24
+ function ensureLeadingSlash(path: string): string {
25
+ const p = path.trim();
26
+ if (!p.startsWith("/")) return `/${p}`;
27
+ return p.endsWith("/") ? p : `${p}/`;
28
+ }
29
+
30
+ function leafKey(paramName: string): string {
31
+ const trimmed = paramName.replace(/^\/+|\/+$/g, "");
32
+ const parts = trimmed.split("/");
33
+ const leaf = parts[parts.length - 1] || trimmed;
34
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(leaf)) {
35
+ return leaf.replace(/[^A-Za-z0-9_]+/g, "_").replace(/^(\d)/, "_$1");
36
+ }
37
+ return leaf;
38
+ }
39
+
40
+ function escapeDotenvValue(value: string): string {
41
+ if (/[\s#'"]/u.test(value) || value.includes("\n")) {
42
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
43
+ return `"${escaped}"`;
44
+ }
45
+ return value;
46
+ }
47
+
48
+ function escapeShellSingleQuoted(value: string): string {
49
+ return `'${value.replace(/'/g, `'\"'\"'`)}'`;
50
+ }
51
+
52
+ async function fetchAll(
53
+ client: SSMClient,
54
+ path: string
55
+ ): Promise<{ name: string; value: string; type?: string }[]> {
56
+ const outList: { name: string; value: string; type?: string }[] = [];
57
+ let next: string | undefined;
58
+ do {
59
+ const out = await client.send(
60
+ new GetParametersByPathCommand({
61
+ Path: path,
62
+ Recursive: true,
63
+ WithDecryption: true,
64
+ NextToken: next,
65
+ })
66
+ );
67
+ for (const p of out.Parameters || []) {
68
+ if (p.Name && p.Value !== undefined) {
69
+ outList.push({ name: p.Name, value: p.Value, type: p.Type });
70
+ }
71
+ }
72
+ next = out.NextToken;
73
+ } while (next);
74
+ outList.sort((a, b) => a.name.localeCompare(b.name));
75
+ return outList;
76
+ }
77
+
78
+ async function main(): Promise<void> {
79
+ const { flags } = parseCli(process.argv.slice(2));
80
+ const pathRaw = flags.path;
81
+ if (typeof pathRaw !== "string" || !pathRaw.trim()) {
82
+ console.error("Required: --path /your/prefix/");
83
+ process.exit(1);
84
+ }
85
+ const path = ensureLeadingSlash(pathRaw);
86
+ const output = String(flags.output || "print").toLowerCase();
87
+ const file = typeof flags.file === "string" ? flags.file : undefined;
88
+
89
+ const region = typeof flags.region === "string" ? flags.region : getRegion();
90
+ const client = new SSMClient({ region });
91
+
92
+ const params = await fetchAll(client, path);
93
+ if (params.length === 0) {
94
+ const msg = `(no parameters under ${path})`;
95
+ if (file) writeFileSync(file, "", "utf8");
96
+ else console.log(msg);
97
+ return;
98
+ }
99
+
100
+ if (output === "print") {
101
+ const lines = [
102
+ `region=${region} path=${path} count=${params.length}`,
103
+ "",
104
+ ...params.map((p) => {
105
+ const k = leafKey(p.name);
106
+ const preview =
107
+ p.value.length > 120 ? `${p.value.slice(0, 117)}...` : p.value;
108
+ return `${p.name} [${k}] (${p.type}) ${preview}`;
109
+ }),
110
+ ];
111
+ const text = lines.join("\n");
112
+ if (file) writeFileSync(file, text + "\n", "utf8");
113
+ else console.log(text);
114
+ return;
115
+ }
116
+
117
+ if (output === "dotenv") {
118
+ const body = params
119
+ .map((p) => {
120
+ const k = leafKey(p.name);
121
+ return `${k}=${escapeDotenvValue(p.value)}`;
122
+ })
123
+ .join("\n");
124
+ const text = `${body}\n`;
125
+ if (file) writeFileSync(file, text, "utf8");
126
+ else process.stdout.write(text);
127
+ return;
128
+ }
129
+
130
+ if (output === "shell") {
131
+ const body = params
132
+ .map((p) => {
133
+ const k = leafKey(p.name);
134
+ return `export ${k}=${escapeShellSingleQuoted(p.value)}`;
135
+ })
136
+ .join("\n");
137
+ const text = `${body}\n`;
138
+ if (file) writeFileSync(file, text, "utf8");
139
+ else process.stdout.write(text);
140
+ return;
141
+ }
142
+
143
+ console.error(`Unknown --output ${output}. Use: print | dotenv | shell`);
144
+ process.exit(1);
145
+ }
146
+
147
+ main().catch((e) => {
148
+ console.error(e instanceof Error ? e.message : e);
149
+ process.exit(1);
150
+ });