@syntropysoft/praetorian
Version:
Praetorian CLI – A universal multi-environment configuration validator for DevSecOps teams. Validate, compare, and secure YAML/ENV files with ease.
200 lines • 7.39 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HclFileAdapter = void 0;
const AbstractFileAdapter_1 = require("../base/AbstractFileAdapter");
class HclFileAdapter extends AbstractFileAdapter_1.AbstractFileAdapter {
canHandle(filePath) {
return filePath.endsWith('.hcl') || filePath.endsWith('.tf') || filePath.endsWith('.tfvars');
}
async read(filePath) {
this.validateFileExists(filePath);
try {
const content = await this.readFileContent(filePath);
return this.parseHclContent(content);
}
catch (error) {
throw new Error(`Failed to parse HCL file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
parseHclContent(content) {
const result = {};
const lines = content.split('\n');
let currentBlock = null;
let currentBlockData = {};
for (let i = 0; i < lines.length; i++) {
let line = lines[i].trim();
// Remove comments (both # and //)
line = line.replace(/#.*$/, '').replace(/\/\/.*$/, '').trim();
if (!line)
continue;
// Handle block definitions (e.g., "resource aws_instance web {")
const blockMatch = line.match(/^(\w+)\s+([^{]+)\s*{/);
if (blockMatch) {
const [, blockType, blockName] = blockMatch;
currentBlock = `${blockType}.${blockName.trim()}`;
currentBlockData = {};
continue;
}
// Handle variable definitions (e.g., "variable region {")
const variableMatch = line.match(/^variable\s+(\w+)\s*{/);
if (variableMatch) {
currentBlock = `variable.${variableMatch[1]}`;
currentBlockData = {};
continue;
}
// Handle data source definitions (e.g., "data aws_ami ubuntu {")
const dataMatch = line.match(/^data\s+([^{]+)\s*{/);
if (dataMatch) {
currentBlock = `data.${dataMatch[1].trim()}`;
currentBlockData = {};
continue;
}
// Handle module definitions (e.g., "module vpc {")
const moduleMatch = line.match(/^module\s+(\w+)\s*{/);
if (moduleMatch) {
currentBlock = `module.${moduleMatch[1]}`;
currentBlockData = {};
continue;
}
// Handle key-value assignments
const assignmentMatch = line.match(/^(\w+)\s*=\s*(.+)$/);
if (assignmentMatch && currentBlock) {
const [, key, value] = assignmentMatch;
currentBlockData[key] = this.parseValue(value.trim());
continue;
}
// Handle nested blocks
if (line.includes('{') && currentBlock) {
const nestedData = this.parseNestedBlock(lines, i + 1);
const keyMatch = line.match(/^(\w+)\s*{/);
if (keyMatch) {
const key = keyMatch[1];
currentBlockData[key] = nestedData;
}
// Skip the nested block lines
i += this.countNestedBlockLines(lines, i + 1);
continue;
}
// Handle closing braces
if (line === '}' && currentBlock) {
if (Object.keys(currentBlockData).length > 0) {
result[currentBlock] = currentBlockData;
}
currentBlock = null;
currentBlockData = {};
}
}
// Save final block if exists
if (currentBlock && Object.keys(currentBlockData).length > 0) {
result[currentBlock] = currentBlockData;
}
return result;
}
parseNestedBlock(lines, startIndex) {
const result = {};
let braceCount = 1;
for (let i = startIndex; i < lines.length; i++) {
let line = lines[i].trim();
// Remove comments
line = line.replace(/#.*$/, '').replace(/\/\/.*$/, '').trim();
if (!line)
continue;
if (line.includes('{')) {
braceCount++;
}
if (line.includes('}')) {
braceCount--;
if (braceCount === 0) {
break;
}
}
// Parse key-value pairs in nested block
const assignmentMatch = line.match(/^(\w+)\s*=\s*(.+)$/);
if (assignmentMatch) {
const [, key, value] = assignmentMatch;
result[key] = this.parseValue(value.trim());
}
// Handle nested blocks within nested blocks
if (line.includes('{')) {
const nestedData = this.parseNestedBlock(lines, i + 1);
const keyMatch = line.match(/^(\w+)\s*{/);
if (keyMatch) {
const key = keyMatch[1];
result[key] = nestedData;
}
// Skip the nested block lines
i += this.countNestedBlockLines(lines, i + 1);
}
}
return result;
}
countNestedBlockLines(lines, startIndex) {
let braceCount = 1;
let lineCount = 0;
for (let i = startIndex; i < lines.length; i++) {
lineCount++;
const line = lines[i].trim();
if (line.includes('{')) {
braceCount++;
}
if (line.includes('}')) {
braceCount--;
if (braceCount === 0) {
break;
}
}
}
return lineCount;
}
parseValue(value) {
// Remove quotes
if ((value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))) {
return value.slice(1, -1);
}
// Parse booleans
if (value === 'true')
return true;
if (value === 'false')
return false;
// Parse arrays
if (value.startsWith('[') && value.endsWith(']')) {
try {
// Try to parse as JSON array
return JSON.parse(value);
}
catch {
// Fallback: parse manually
const content = value.slice(1, -1).trim();
if (!content)
return [];
return content.split(',').map(item => this.parseValue(item.trim()));
}
}
// Parse objects
if (value.startsWith('{') && value.endsWith('}')) {
try {
// Try to parse as JSON object
return JSON.parse(value);
}
catch {
// Fallback: return as string for now
return value;
}
}
// Parse numbers
if (!isNaN(Number(value))) {
return Number(value);
}
// Return as string
return value;
}
getFormat() {
return 'hcl';
}
getSupportedExtensions() {
return ['.hcl', '.tf', '.tfvars'];
}
}
exports.HclFileAdapter = HclFileAdapter;
//# sourceMappingURL=HclFileAdapter.js.map