@controlplane/cli
Version:
Control Plane Corporation CLI
328 lines • 13.3 kB
JavaScript
"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