UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

228 lines 9.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CacheGenerator = void 0; const fs = require("fs"); const version_1 = require("../../util/version"); const constants_1 = require("./constants"); const logger_1 = require("../../util/logger"); class CacheGenerator { // Public Static Methods // /** * Generates and saves the completion cache to disk * @param {Arguments} args - Yargs arguments containing the parser factory * @param {string} shell - Optional shell type to store in cache metadata * @returns {string} Path to the generated cache file * @throws {Error} If cache generation or writing fails */ static generateAndSaveCache(args, shell) { const lines = CacheGenerator.generateCache(args, shell); // Ensure cache directory exists if (!fs.existsSync(constants_1.CACHE_DIR)) { fs.mkdirSync(constants_1.CACHE_DIR, { recursive: true }); } // Write cache file const content = lines.join('\n') + '\n'; fs.writeFileSync(constants_1.CACHE_FILE, content); return constants_1.CACHE_FILE; } /** * Reads the shell type from the cache file * @returns {string | null} Shell type or null if not found */ static getCachedShell() { if (!CacheGenerator.cacheExists()) { return null; } try { const content = fs.readFileSync(constants_1.CACHE_FILE, 'utf8'); const shellLine = content.split('\n').find((line) => line.startsWith('#shell:')); return shellLine ? shellLine.substring(7) : null; } catch (_a) { return null; } } /** * Removes the completion cache file */ static deleteCache() { if (fs.existsSync(constants_1.CACHE_FILE)) { fs.unlinkSync(constants_1.CACHE_FILE); } } /** * Regenerates cache if stale (version mismatch or missing) * @param {Arguments} args - Yargs arguments * @param {string} shell - Optional shell type (defaults to cached shell or undefined) * @returns {boolean} True if cache was regenerated */ static regenerateIfStale(args, shell) { // Skip for Fish - it always calls CLI directly and doesn't use cache if (shell === 'fish') { return true; } logger_1.logger.debug('Checking completion cache staleness'); if (CacheGenerator.isCacheStale()) { const cacheVersion = CacheGenerator.getCacheVersion(); const currentVersion = version_1.About.npm; if (cacheVersion) { logger_1.logger.debug(`Cache version mismatch: cached=${cacheVersion}, current=${currentVersion}`); } else { logger_1.logger.debug('Cache not found or version missing'); } logger_1.logger.debug('Regenerating completion cache'); const shellToUse = shell || CacheGenerator.getCachedShell() || undefined; CacheGenerator.generateAndSaveCache(args, shellToUse); logger_1.logger.debug(`Cache regenerated successfully at ${constants_1.CACHE_FILE}`); return true; } logger_1.logger.debug('Cache is up to date'); return false; } // Private Static Methods // /** * Generates the completion cache from the command parser * @param {Arguments} args - Yargs arguments containing the parser factory * @param {string} shell - Optional shell type to store in cache metadata * @returns {string[]} Array of cache lines */ static generateCache(args, shell) { // @ts-ignore - accessing internal yargs parser factory const parserFactory = args._parser; if (!parserFactory) { throw new Error('Parser factory not available'); } const lines = []; // Add metadata headers lines.push(`#version:${version_1.About.npm}`); lines.push(`#generated:${Date.now()}`); if (shell) { lines.push(`#shell:${shell}`); } // Get root commands const rootParsed = CacheGenerator.parseCommandLevel(parserFactory, []); if (rootParsed && (rootParsed.commands.length > 0 || rootParsed.options.length > 0)) { // Include commands, options, and yargs built-in options at root level (deduplicated) const allOptions = [...new Set([...rootParsed.options, ...constants_1.BUILTIN_OPTIONS])]; const rootCompletions = [...rootParsed.commands, ...allOptions]; lines.push(`:${rootCompletions.join(',')}`); // Recursively generate cache for each top-level command for (const topCommand of rootParsed.commands) { CacheGenerator.generateCacheRecursive({ parser: parserFactory, commandPath: [topCommand], lines, }); } } return lines; } /** * Parses a command at a specific path to extract its subcommands and options * @param {() => Argv} parserFactory - Factory function to create a new parser * @param {string[]} commandPath - Path to the command (e.g., ['workload', 'get']) * @returns {ParsedCompletionCommandLevel | null} Parsed command information or null if parsing fails */ static parseCommandLevel(parserFactory, commandPath) { try { const parser = CacheGenerator.configureParser(parserFactory()); parser.parse(commandPath); const stack = parser['_command_stack'] || []; if (stack.length === 0) { return null; } const currentModel = stack[stack.length - 1]; const commands = (currentModel.commands || []).map((c) => c.name).filter((n) => n && !constants_1.EXCLUDED_COMMANDS.includes(n)); const options = (currentModel.options || []).map((o) => `--${o.name}`); const result = { stack, commands, options, hasSubcommands: commands.length > 0, }; return result; } catch (error) { return null; } } /** * Recursively generates cache entries for a command and all its subcommands * Recursion naturally terminates when no more subcommands are found * @param {CompletionCacheGenerationContext} context - The generation context */ static generateCacheRecursive(context) { const { parser, commandPath, lines } = context; const parsed = CacheGenerator.parseCommandLevel(parser, commandPath); if (!parsed) { return; } const { commands, options, hasSubcommands } = parsed; // Build cache line - always generate an entry since we include --help const key = commandPath.length === 0 ? '' : commandPath.join(' '); const allOptions = [...new Set([...options, constants_1.HELP_OPTION])]; const allCompletions = [...commands, ...allOptions]; const cacheLine = `${key}:${allCompletions.join(',')}`; lines.push(cacheLine); // Recursively process subcommands until we reach leaf commands if (hasSubcommands) { for (const subcommand of commands) { CacheGenerator.generateCacheRecursive({ parser, commandPath: [...commandPath, subcommand], lines, }); } } } /** * Creates a parser with standard error suppression configuration * @param {Argv} baseParser - The base yargs parser * @returns {Argv} Configured parser */ static configureParser(baseParser) { return baseParser .showHelpOnFail(constants_1.DEFAULT_PARSER_CONFIG.showHelpOnFail) .exitProcess(constants_1.DEFAULT_PARSER_CONFIG.exitProcess) .fail(() => { }) .strict(constants_1.DEFAULT_PARSER_CONFIG.strict); } /** * Checks if cache is stale (missing or version mismatch) * @returns {boolean} True if cache needs regeneration */ static isCacheStale() { if (!CacheGenerator.cacheExists()) { return true; } const cacheVersion = CacheGenerator.getCacheVersion(); return cacheVersion !== version_1.About.npm; } /** * Reads the version from the cache file * @returns {string | null} Cache version or null if not found */ static getCacheVersion() { if (!CacheGenerator.cacheExists()) { return null; } try { const content = fs.readFileSync(constants_1.CACHE_FILE, 'utf8'); const versionLine = content.split('\n').find((line) => line.startsWith('#version:')); return versionLine ? versionLine.substring(9) : null; } catch (_a) { return null; } } /** * Checks if the cache file exists * @returns {boolean} True if cache exists */ static cacheExists() { return fs.existsSync(constants_1.CACHE_FILE); } } exports.CacheGenerator = CacheGenerator; //# sourceMappingURL=cache-generator.js.map