cfn-forge
Version:
CloudFormation deployment automation tool with git-based workflows
147 lines (130 loc) • 4.7 kB
JavaScript
/**
* Quick validation status checker for interactive menu
*/
const fs = require('fs');
const yaml = require('js-yaml');
// CloudFormation schema for parsing templates with intrinsic functions
const CloudFormationSchema = yaml.DEFAULT_SCHEMA.extend([
new yaml.Type('!Ref', { kind: 'scalar', construct: (data) => ({ 'Fn::Ref': data }) }),
new yaml.Type('!Sub', { kind: 'scalar', construct: (data) => ({ 'Fn::Sub': data }) }),
new yaml.Type('!GetAtt', { kind: 'sequence', construct: (data) => ({ 'Fn::GetAtt': data }) }),
new yaml.Type('!GetAZs', { kind: 'scalar', construct: (data) => ({ 'Fn::GetAZs': data }) }),
new yaml.Type('!ImportValue', { kind: 'scalar', construct: (data) => ({ 'Fn::ImportValue': data }) }),
new yaml.Type('!Join', { kind: 'sequence', construct: (data) => ({ 'Fn::Join': data }) }),
new yaml.Type('!Select', { kind: 'sequence', construct: (data) => ({ 'Fn::Select': data }) }),
new yaml.Type('!Split', { kind: 'sequence', construct: (data) => ({ 'Fn::Split': data }) }),
new yaml.Type('!Base64', { kind: 'scalar', construct: (data) => ({ 'Fn::Base64': data }) }),
new yaml.Type('!Cidr', { kind: 'sequence', construct: (data) => ({ 'Fn::Cidr': data }) }),
new yaml.Type('!FindInMap', { kind: 'sequence', construct: (data) => ({ 'Fn::FindInMap': data }) }),
new yaml.Type('!If', { kind: 'sequence', construct: (data) => ({ 'Fn::If': data }) }),
new yaml.Type('!Not', { kind: 'sequence', construct: (data) => ({ 'Fn::Not': data }) }),
new yaml.Type('!Equals', { kind: 'sequence', construct: (data) => ({ 'Fn::Equals': data }) }),
new yaml.Type('!And', { kind: 'sequence', construct: (data) => ({ 'Fn::And': data }) }),
new yaml.Type('!Or', { kind: 'sequence', construct: (data) => ({ 'Fn::Or': data }) }),
new yaml.Type('!Condition', { kind: 'scalar', construct: (data) => ({ Condition: data }) }),
]);
/**
* Quick validation status check (for menu display)
* Returns: { status: 'valid'|'invalid'|'no-files', summary: string }
*/
function getValidationStatus() {
try {
const { templateFile, parameterFiles } = findFiles();
if (!templateFile) {
return {
status: 'no-files',
summary: 'No template found',
};
}
// Quick template syntax check
try {
const templateContent = fs.readFileSync(templateFile, 'utf8');
yaml.load(templateContent, { schema: CloudFormationSchema });
} catch (error) {
return {
status: 'invalid',
summary: 'Template has syntax errors',
};
}
if (parameterFiles.length === 0) {
return {
status: 'valid',
summary: 'Template valid, no parameters',
};
}
// Quick parameter file format check
for (const paramFile of parameterFiles) {
try {
const paramContent = fs.readFileSync(paramFile, 'utf8');
if (paramFile.endsWith('.json')) {
JSON.parse(paramContent);
} else {
yaml.load(paramContent);
}
} catch (error) {
return {
status: 'invalid',
summary: `${parameterFiles.length} param file(s) - format errors`,
};
}
}
return {
status: 'valid',
summary: `Template + ${parameterFiles.length} param file(s)`,
};
} catch (error) {
return {
status: 'invalid',
summary: 'Validation error',
};
}
}
/**
* Find template and parameter files
*/
function findFiles() {
const templateCandidates = [
'template.yaml',
'template.yml',
'cloudformation.yaml',
'cloudformation.yml',
'stack.yaml',
'stack.yml',
];
const parameterCandidates = [
'parameters.json',
'parameters.yaml',
'parameters.yml',
'params.json',
'params.yaml',
'params.yml',
'parameters-dev.json',
'parameters-staging.json',
'parameters-prod.json',
'parameters-dev.yaml',
'parameters-staging.yaml',
'parameters-prod.yaml',
];
let templateFile = null;
// Check cfn-forge config first
try {
const configContent = fs.readFileSync('.cfn-forge.yaml', 'utf8');
const config = yaml.load(configContent);
if (config.templates && config.templates.main) {
templateFile = config.templates.main;
}
} catch {
// No config file, continue with candidate search
}
// Find first existing template if none specified
if (!templateFile || !fs.existsSync(templateFile)) {
templateFile = templateCandidates.find((file) => fs.existsSync(file));
}
// Find existing parameter files
const parameterFiles = parameterCandidates.filter((file) => fs.existsSync(file));
return { templateFile, parameterFiles };
}
module.exports = {
getValidationStatus,
findFiles,
};