UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

381 lines 14.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.computeHash = exports.sortIHasKindByPriority = exports.sortByKindPriority = exports.toVolumes = exports.toEnv = exports.cartesian = exports.removeUndefinedValues = exports.pick = exports.toArray = exports.merge = exports.kindPriorityOrder = exports.gvcRelatedKinds = void 0; const _ = require("lodash"); const crypto = require("crypto"); const VALID_KEY_REGEX = /^(?:[A-Za-z_][A-Za-z0-9_]*)$/; exports.gvcRelatedKinds = ['identity', 'volumeset', 'workload']; exports.kindPriorityOrder = ['agent', 'secret', 'cloudaccount', 'gvc', 'identity', 'volumeset', 'policy', 'workload']; function merge(...objs) { let result = {}; for (let obj of objs) { Object.assign(result, _.pickBy(obj, (x) => x !== undefined && x !== null && x !== '')); } return result; } exports.merge = merge; function toArray(obj) { if (obj === undefined) { return []; } if (Array.isArray(obj)) { return obj; } return [obj]; } exports.toArray = toArray; function pick(obj, ...props) { let result = {}; for (let prop of props) { const val = obj[prop]; if (val != null) { result[prop] = val; } } return result; } exports.pick = pick; function removeUndefinedValues(obj) { // Iterate over all the keys of the object Object.keys(obj).forEach((key) => { // If the value is undefined, delete the key if (obj[key] === undefined) { delete obj[key]; } // If the value is an object, recurse into it else if (typeof obj[key] === 'object' && obj[key] !== null) { removeUndefinedValues(obj[key]); // If the nested object becomes empty after the removal of undefined values, delete the key if (Object.keys(obj[key]).length === 0) { delete obj[key]; } } }); } exports.removeUndefinedValues = removeUndefinedValues; function cartesian(t1, t2, join) { const res = []; if (t1 === undefined || t2 === undefined) { return undefined; } for (let s1 of t1) { for (let s2 of t2) { res.push(join(s1, s2)); } } return res; } exports.cartesian = cartesian; /** * Converts raw .env lines into {name, value} entries. * * @param {string[]} kvs - Raw lines read from a .env file (already split by newline). * @returns {EnvEntry[]} - Parsed environment entries with stable ordering by final occurrence. * @throws {Error} - If kvs is not an array of strings. */ function toEnv(kvs) { // Create a mutable mapping so the last occurrence of a key overwrites earlier ones const map = {}; // Use a numeric for-loop to keep variable types explicit for (let i = 0; i < kvs.length; i++) { // Ensure the raw item is a string and trim outer whitespace const line = typeof kvs[i] === 'string' ? kvs[i].trim() : ''; // Continue to the next line when current line is empty if (line.length === 0) { continue; } // Skip full-line comments that start with '#' if (line.startsWith('#')) { continue; } // Remove leading "export " if present (common in shell-style env files) const withoutExport = line.startsWith('export ') ? line.slice(7).trimStart() : line; // Remove inline comments that appear outside of quotes only (retain those inside quotes) const noInlineComment = stripInlineCommentSafe(withoutExport); // Skip the line if it becomes empty after stripping the inline comment if (noInlineComment.length === 0) { continue; } // Split the logical line into a key/value pair at the first unquoted '=' or ':' const pair = splitKeyValue(noInlineComment); // Skip non key/value forms if (pair === null) { // Trim the remaining line to see if it is a bare key const bareKey = noInlineComment.trim(); // Validate the bare key against the allowed key regex const isBareValid = VALID_KEY_REGEX.test(bareKey); // Treat a valid bare key (e.g., "WITH_NO_EQUAL_SIGN") as an empty assignment if (isBareValid) { // Assign an empty string for the bare key map[bareKey] = ''; // Continue to the next input line after recording the empty assignment continue; } // Continue to the next input line if not a valid bare key continue; } // Extract the key name from the parsed pair const name = pair.key; // Validate the key against the allowed regex and skip if it is invalid if (!VALID_KEY_REGEX.test(name)) { continue; } // Extract the raw value for post-processing const rawValue = pair.value; // Clean the value by unquoting and unescaping if necessary const value = unquoteValue(rawValue); // Record the value in the map so later duplicates overwrite earlier ones map[name] = value !== null && value !== void 0 ? value : ''; } // Convert the map into a stable array of EnvEntry items const entries = Object.entries(map).map(([k, v]) => { // Define the entry name explicitly const name = k; // Define the entry value explicitly, ensuring it is a string const value = String(v !== null && v !== void 0 ? v : ''); // Return the structured environment entry return { name, value }; }); // Return the final parsed entries return entries; } exports.toEnv = toEnv; function toVolumes(kvs) { if (!kvs) { return []; } const list = []; for (let e of kvs) { const parts = e.split('@'); let uri = parts[0]; if (!uri) { continue; } let path = parts.slice(1).join('@'); list.push({ uri, path, }); } return list; } exports.toVolumes = toVolumes; function sortByKindPriority(resources, reverse = false) { const sortedResources = []; for (const resource of resources) { if (resource.kind === 'list' || resource.kind === 'queryresult') { for (let item of resource.items) { sortedResources.push(item); } } else { sortedResources.push(resource); } } sortIHasKindByPriority(sortedResources, reverse); return sortedResources; } exports.sortByKindPriority = sortByKindPriority; function sortIHasKindByPriority(resources, reverse) { if (resources.length === 0) { return; } resources.sort((a, b) => { const indexA = exports.kindPriorityOrder.indexOf(a.kind); const indexB = exports.kindPriorityOrder.indexOf(b.kind); // If a's kind isn't found, set its index to Infinity so it's placed at the end if (indexA === -1) return 1; // If b's kind isn't found, set its index to Infinity so it's placed at the end if (indexB === -1) return -1; return indexA - indexB; }); if (reverse) { resources.reverse(); } } exports.sortIHasKindByPriority = sortIHasKindByPriority; function computeHash(data) { // Create an SHA-256 hash instance const hash = crypto.createHash('sha256'); // Update the hash with the serialized data hash.update(JSON.stringify(data)); // Return the hash in hexadecimal format return hash.digest('hex'); } exports.computeHash = computeHash; // Internal File Use Functions // /** * Removes an inline comment that starts with '#' when the '#' is outside of quotes. * - Preserves '#' characters that appear within single or double quoted strings. * * @param {string} line - A single logical line that may contain an inline comment. * @returns {string} - The line with any trailing comment removed and whitespace trimmed on the right. */ function stripInlineCommentSafe(line) { // Track whether the scan is currently inside a single or double quote let inQuote = null; // Track whether the current character should be treated as escaped let escaped = false; // Loop index for the character scan let i = 0; // Iterate over the input characters for (i = 0; i < line.length; i++) { // Current character under inspection const ch = line[i]; // Handle previously escaped character if (escaped) { // Reset the escaped flag after consuming the escaped character escaped = false; // Continue scanning the next character continue; } // If a backslash is found, mark the next character as escaped if (ch === '\\') { // Mark the next character as escaped escaped = true; // Continue scanning after the backslash continue; } // If inside a quoted region, only check for the closing quote if (inQuote !== null) { // Close the quote if we see the matching quote character if (ch === inQuote) { // Exit quoted mode inQuote = null; } // Continue scanning inside the quoted region continue; } // If encountering an opening quote, enter quoted mode if (ch === "'" || ch === '"') { // Enter quoted mode using the detected quote character inQuote = ch; // Continue scanning after entering quoted mode continue; } // If encountering a '#', treat the rest of the line as an inline comment if (ch === '#') { // Slice everything before the '#' and trim trailing whitespace const result = line.slice(0, i).trimEnd(); // Return the stripped line return result; } } // If no inline comment boundary is found, return the original line trimmed on the right return line.trimEnd(); } /** * Splits a logical "KEY=VALUE" or "KEY: VALUE" line into a key and value, * ignoring separators found inside quotes. * * @param {string} line - A single logical line with export/comment already stripped. * @returns {KeyValuePair | null} - Parsed key/value pair or null if no separator found. */ function splitKeyValue(line) { // Track quote state to ignore separators inside quotes let inQuote = null; // Track escape state for the current character let escaped = false; // Current loop index while scanning characters let i = 0; // Iterate to find the first unquoted '=' or ':' for (i = 0; i < line.length; i++) { // Current character const ch = line[i]; // If the previous character escaped this one, continue if (escaped) { // Reset escaped flag after consuming the escaped character escaped = false; // Continue to next character continue; } // If a backslash is seen, mark the next character as escaped if (ch === '\\') { // Mark escape for the next character escaped = true; // Continue to next character continue; } // If in a quoted region, only check for closing the quote if (inQuote !== null) { // Close the quote when encountering the matching quote character if (ch === inQuote) { // Exit quoted mode inQuote = null; } // Continue scanning inside quotes continue; } // If encountering an opening quote, enter quoted mode if (ch === "'" || ch === '"') { // Set the current quote character inQuote = ch; // Continue to next character after entering quoted mode continue; } // Accept '=' or ':' as the first unquoted separator if (ch === '=' || ch === ':') { // Slice the key to the left of the separator and trim const key = line.slice(0, i).trim(); // Slice the value to the right of the separator and trim const value = line.slice(i + 1).trim(); // Return the structured pair as a KeyValuePair const pair = { key, value }; // Return the parsed pair to the caller return pair; } } // Return null if no valid separator was found const noSep = null; // Return the null sentinel to indicate failure to split return noSep; } /** * Unquotes and unescapes a value if wrapped in single or double quotes. * - Single quotes: literal except for handling of \\' and \\\\. * - Double quotes: supports \\n, \\r, \\t, \\\", \\\', \\\\ sequences. * * @param {string} value - Raw value that may be quoted and escaped. * @returns {string} - Clean, ready-to-use value string. */ function unquoteValue(value) { // Return an empty string when value is empty or undefined if (!value) { return ''; } // Determine whether the value is enclosed in matching quotes const hasDouble = value.startsWith('"') && value.endsWith('"'); const hasSingle = value.startsWith("'") && value.endsWith("'"); const isQuoted = hasDouble || hasSingle; // If the value is not quoted, return a trimmed version if (!isQuoted) { // Trim surrounding whitespace for unquoted values const trimmed = value.trim(); // Return the trimmed unquoted value return trimmed; } // Determine the quote character used const q = value[0]; // Extract the inner content (without the surrounding quotes) const inner = value.slice(1, -1); // Handle single-quoted strings (mostly literal) if (q === "'") { // Replace escaped single quote and backslash const singleProcessed = inner.replace(/\\'/g, "'").replace(/\\\\/g, '\\'); // Return the processed single-quoted value return singleProcessed; } // Handle double-quoted strings with common escape sequences const doubleProcessed = inner .replace(/\\n/g, '\n') .replace(/\\r/g, '\r') .replace(/\\t/g, '\t') .replace(/\\"/g, '"') .replace(/\\'/g, "'") .replace(/\\\\/g, '\\'); // Return the processed double-quoted content return doubleProcessed; } //# sourceMappingURL=objects.js.map