@controlplane/cli
Version:
Control Plane Corporation CLI
228 lines • 9.3 kB
JavaScript
;
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