Skip to content

Instantly share code, notes, and snippets.

@dawaltconley
Last active June 13, 2023 19:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dawaltconley/673a2e6eb7c45dde66e7ff5d84791f0a to your computer and use it in GitHub Desktop.
Save dawaltconley/673a2e6eb7c45dde66e7ff5d84791f0a to your computer and use it in GitHub Desktop.
Generate markdown documentation from CloudFormation templates
/**
* @typedef {{
* [Prop: string]: any;
* Default?: string | number;
* Description?: string;
* }} Parameter
*/
/**
* @typedef {{
* [Unused: string]: any;
* Type: string;
* Properties: Object;
* }} Resource
*/
/**
* @typedef {Object} Output
* @property {string} [Output.Description]
* @property {string} [Output.Value]
* @property {string} [Output.Export]
*/
/**
* @typedef {{
* [Unused: string]: any;
* Description?: string;
* Parameters?: Object.<string, Parameter>;
* Resources?: Object.<string, Resource>;
* Outputs?: Object.<string, Output>;
* }} Template
*/
const quote = (str) => `"${str}"`;
const list = (items, indent = 0) =>
items
.map((item) => `${" ".repeat(indent)}- ${item}`)
.join("\n");
const defineProperties = (props) => {
let defs = Object.entries(props)
.map(([property, value]) => {
if (value === "") value = quote(value);
if (property === "AllowedValues") {
return `${property}:\n${list(value, 2)}`;
}
return `${property}: ${value}`;
});
defs = list(defs);
return defs;
};
/**
* Takes a title and a parsed CloudFormation template object.
* @param {string} title
* @param {Template} template
* @returns string Markdown documentation of the template.
*/
export const generateDocs = (
title,
{ Description, Parameters, Resources, Outputs },
) => {
const lines = [];
lines.push(`# ${title}`);
lines.push(Description);
const requiredParameters = [];
const optionalParameters = [];
for (const name in Parameters) {
const parameter = Parameters[name];
const type = parameter.Default === undefined
? requiredParameters
: optionalParameters;
const { Description } = parameter;
delete parameter.Description;
type.push(`### ${name}`);
if (Description) type.push(Description);
type.push(defineProperties(parameter));
}
if (requiredParameters.length) {
lines.push("## Required Parameters");
requiredParameters.forEach((p) => lines.push(p));
}
if (optionalParameters.length) {
lines.push("## Optional Parameters");
optionalParameters.forEach((p) => lines.push(p));
}
if (Resources) {
lines.push("## Resources");
for (const name in Resources) {
const resource = Resources[name];
delete resource.Properties;
lines.push(`### ${name}`);
lines.push(defineProperties(resource));
}
}
if (Outputs) {
lines.push("## Outputs");
for (const name in Outputs) {
const output = Outputs[name];
const { Description } = output;
delete output.Description;
delete output.Value;
lines.push(`### ${name}`);
if (Description) lines.push(Description);
lines.push(defineProperties(output));
}
}
return lines.filter(Boolean).join("\n\n");
};
{
"version": "0.1.0",
"name": "cfn-generate-docs",
"main": "index.js"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment