UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

328 lines 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.K8sShapePrinter = void 0; const chalk = require("chalk"); const yaml = require("js-yaml"); const logger_1 = require("../../util/logger"); /** * A utility class for traversing and analyzing Kubernetes resource objects. * It processes the object properties, comparing them against a recognized shape * and building a labeled string representation. */ class K8sShapePrinter { /** * Constructor to initialize the K8sShapePrinter with an original object and a shape object. * @param originalObject - The original Kubernetes object file. * @param shapeObject - The shape object acting as a schema. */ constructor(originalObject, shapeObject) { /*** Private Properties ***/ this.outputWithColor = []; this.outputPlainText = []; this.originalObject = originalObject; this.shapeObject = shapeObject; } /** * Print and stringify the Kubernetes object with specific lines colored as ignored. * @returns A YAML string with specific lines that end with the 'ignore' text. */ printAndStringify() { // Append the source file path this.appendLine(`# Source: ${this.originalObject.filePath}`); // Deep copy the original resource object const newObject = JSON.parse(JSON.stringify(this.originalObject.resource)); // If shapeObject is not provided, modify all keys to be ignored if (!this.shapeObject) { this.prefixAllKeys(newObject); } else { // Modify keys in the new object based on the shape object so we can know which properties to ignore this.modifyKeys(newObject, this.shapeObject); } // Convert the modified object to YAML and split into lines const yamlString = yaml.safeDump(newObject); const lines = yamlString.split('\n'); // Get ranges of multiline text const ranges = this.getMultilineTextRanges(lines); // Iterate over each line for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Find if the current line is the start of a multiline range const range = ranges.find((r) => r.start === i); // Handle multiline property if (range) { // Check if the line is the start of a multiline range that we need to ignore const isIgnored = line.includes(K8sShapePrinter.IGNORE_PREFIX); // Handle multiline as ignored if (isIgnored) { // Color the line and add a comment if ignored this.appendLine(this.removeIgnorePrefix(line), true); // Color the entire range if ignored for (let j = i + 1; j <= range.end; j++) { this.appendLine(lines[j], true, false); } // Add a comment at the end of the range this.outputWithColor[this.outputWithColor.length - 1] += this.colorText(` ${K8sShapePrinter.IGNORE_TEXT}`); this.outputPlainText[this.outputPlainText.length - 1] += ' ' + K8sShapePrinter.IGNORE_TEXT; // Skip to the end of the range i = range.end; } else { // Add the line to results if not ignored this.appendLine(line); } } else { // Handle normal lines if (line.includes(K8sShapePrinter.IGNORE_PREFIX)) { // Color the line and add a comment if ignored this.appendLine(this.removeIgnorePrefix(line), true); } else { // Add the line to results if not ignored this.appendLine(line); } } } // Print the colored YAML string to --verbose logger_1.logger.debug(this.outputWithColor.join('\n')); // Join the results into a single string and return return this.outputPlainText.join('\n'); } /** * Append lines to the outputs with or without color. * @param line - The line to append. * @param isIgnored - Whether the line is ignored. * @param addIgnored - Whether the ignored line should have the ignored comment. */ appendLine(line, isIgnored = false, addIgnored = true) { if (isIgnored) { let ignoredLine = line; if (addIgnored) { ignoredLine += ` ${K8sShapePrinter.IGNORE_TEXT}`; } this.outputWithColor.push(this.colorText(ignoredLine)); this.outputPlainText.push(ignoredLine); } else { this.outputWithColor.push(line); this.outputPlainText.push(line); } } /** * Modify keys in the object based on the shape object. * @param obj - The object to modify. Can be a primitive or object value. * @param shape - The shape object to compare against. Defines the structure that obj should match. */ modifyKeys(obj, shape) { if (!this.isValidObject(obj)) { if (typeof shape === 'boolean' && shape) { return; } // TODO: Handle primitive values that aren't in shape // I am pretty much sure we will never get here, so don't worry about it return; } // Iterate over each key in the object for (const key of Object.keys(obj)) { // Check if the key exists in the shape object if (this.isKeyInShape(shape, key)) { // Process the key if it matches in both object and shape this.processMatchingKey(obj, shape, key); } else if (!this.isKeyValuePairShape(shape)) { // Prefix the key with the 'ignore' prefix if it does not match and shape is not a key-value pair type this.prefixKeys(obj, key); } } } /** * Checks if the provided value is a valid object for processing * @param value - The value to check. Can be any type. * @returns boolean indicating if the value is a non-null object */ isValidObject(value) { return typeof value === 'object' && value !== null; } /** * Checks if the key exists in the shape object * @param shape - The shape object to check against * @param key - The key to look for in the shape object * @returns boolean indicating if the key exists in shape */ isKeyInShape(shape, key) { return shape.hasOwnProperty(key); } /** * Checks if the shape is a key-value pair type * @param shape - The shape object to check * @returns boolean indicating if the shape has a $keyValuePair property */ isKeyValuePairShape(shape) { return shape.hasOwnProperty('$keyValuePair'); } /** * Processes a key that matches in both object and shape * @param obj - The source object containing the key to process * @param shape - The shape object containing the matching key * @param key - The key to process in both objects */ processMatchingKey(obj, shape, key) { const objValue = obj[key]; const shapeValue = shape[key]; if (this.isMatchingArrays(objValue, shapeValue)) { this.processArray(objValue, shapeValue[0]); } else if (this.areMatchingObjects(objValue, shapeValue)) { this.modifyKeys(objValue, shapeValue); } } /** * Checks if both values are arrays * @param objValue - The value from the source object * @param shapeValue - The value from the shape object * @returns boolean indicating if both values are arrays */ isMatchingArrays(objValue, shapeValue) { return Array.isArray(objValue) && Array.isArray(shapeValue); } /** * Checks if both values are objects * @param objValue - The value from the source object * @param shapeValue - The value from the shape object * @returns boolean indicating if both values are valid objects */ areMatchingObjects(objValue, shapeValue) { return this.isValidObject(objValue) && this.isValidObject(shapeValue); } /** * Processes array items by recursively modifying their keys * @param array - The array of items to process * @param shapeItem - The shape template to compare each array item against */ processArray(array, shapeItem) { for (const item of array) { this.modifyKeys(item, shapeItem); } } /** * Prefix keys in the object with the IGNORE_PREFIX value. * @param obj - The object containing the key to prefix * @param key - The key to prefix with the 'ignore' prefix */ prefixKeys(obj, key) { // Create a new key by prefixing the original key with the 'ignore' prefix const newKey = `${K8sShapePrinter.IGNORE_PREFIX}${key}`; // Retrieve the value associated with the original key const value = obj[key]; // Assign the value to the new key and delete the original key obj[newKey] = value; delete obj[key]; // If the value is an array, process each item in the array if (Array.isArray(value)) { this.processIgnoredArray(obj[newKey]); // If the value is an object, recursively process its keys } else if (this.isValidObject(value)) { this.processIgnoredObject(obj[newKey]); } } /** * Processes an ignored array by prefixing its items * @param array - The array to process. For primitive values, add 'ignore' prefix. * For objects, recursively prefixes their keys. */ processIgnoredArray(array) { // Iterate over each item in the array for (let i = 0; i < array.length; i++) { const item = array[i]; // Check if the item is a valid object if (this.isValidObject(item)) { // Iterate over each key in the object and prefix it for (const key of Object.keys(item)) { this.prefixKeys(item, key); } } else { // If the item is a primitive value, prefix it directly array[i] = `${K8sShapePrinter.IGNORE_PREFIX}${item}`; } } } /** * Processes an ignored object by prefixing its keys * @param obj - The object whose keys need to be prefixed with the 'ignore' prefix */ processIgnoredObject(obj) { for (const key of Object.keys(obj)) { this.prefixKeys(obj, key); } } /** * Prefix all keys in the object with the IGNORE_PREFIX value. * @param obj - The object to process. */ prefixAllKeys(obj) { for (const key of Object.keys(obj)) { this.prefixKeys(obj, key); } } /** * Get ranges of multiline text in the YAML lines. * @param lines - The lines of YAML text. * @returns An array of ranges indicating the start and end of a multiline text. */ getMultilineTextRanges(lines) { // Initialize an array to store the ranges const ranges = []; // Flag to indicate if currently in a range let inRange = false; // Variable to store the start of a range let start = 0; // Regular expression to match all YAML block scalar headers // Matches ': |', ': |-', ': |+', ': >', ': >-', ': >+' const blockScalarRegex = /:\s*[|>][-+]?$/; // Iterate over each line for (let i = 0; i < lines.length; i++) { // Check if the line is the start of a multiline text if (blockScalarRegex.test(lines[i])) { inRange = true; start = i; } else if (inRange && lines[i].trim() === '') { // Check if the line is the end of a multiline text ranges.push({ start, end: i - 1 }); inRange = false; } } // Add the last range if still in a range if (inRange) { ranges.push({ start, end: lines.length - 1 }); } // Return the array of ranges return ranges; } /** * Remove the 'ignore' prefix from a line. * @param line - The line to process. * @returns The line without the 'ignore' prefix. */ removeIgnorePrefix(line) { // Remove the 'ignore' prefix from the line return line.replace(new RegExp(K8sShapePrinter.IGNORE_PREFIX, 'g'), ''); } /** * Add color to text using chalk. * @param text - The text to color. * @returns The colored text. */ colorText(text) { // Use chalk to color the text return chalk.yellow(text); } } exports.K8sShapePrinter = K8sShapePrinter; /*** Private Static Properties ***/ K8sShapePrinter.IGNORE_PREFIX = 'k8s_converter_cpln_ignore_'; K8sShapePrinter.IGNORE_TEXT = '# (ignored)'; //# sourceMappingURL=shape-printer.js.map