228 lines
7.7 KiB
JavaScript
228 lines
7.7 KiB
JavaScript
/**
|
|
* This script generates markdown files for each endpoint in the
|
|
* configured OpenAPI specs.
|
|
*/
|
|
|
|
import { writeFileSync, rmSync, readFileSync, existsSync, mkdirSync } from 'fs';
|
|
import * as yaml from 'js-yaml';
|
|
import { execCommand, getSwagger, isPlaceholderFragment } from './helpers.mjs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { getPageTemplate } from './templates.mjs';
|
|
import winston from 'winston';
|
|
|
|
// Get the current file's directory
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const logger = winston.createLogger({
|
|
level: 'info',
|
|
format: winston.format.json(),
|
|
defaultMeta: { service: 'user-service' },
|
|
transports: [
|
|
//
|
|
// - Write all logs with importance level of `error` or higher to `error.log`
|
|
// (i.e., error, fatal, but not other levels)
|
|
//
|
|
new winston.transports.File({ filename: path.join(__dirname, 'error.log'), level: 'error' }),
|
|
//
|
|
// - Write all logs with importance level of `info` or higher to `combined.log`
|
|
// (i.e., fatal, error, warn, and info, but not trace)
|
|
//
|
|
new winston.transports.File({ filename: path.join(__dirname, 'combined.log') }),
|
|
],
|
|
});
|
|
|
|
function getPathGroups(openapi) {
|
|
const pathGroups = {};
|
|
Object.keys(openapi.paths).sort()
|
|
.forEach((p) => {
|
|
const delimiter = '/';
|
|
let key = p.split(delimiter);
|
|
let isItemPath = isPlaceholderFragment(key[key.length - 1]);
|
|
if(isItemPath) {
|
|
key = key.slice(0, -1);
|
|
}
|
|
key = (key.slice(0, 4))
|
|
isItemPath = isPlaceholderFragment(key[key.length - 1]);
|
|
if(isItemPath) {
|
|
key = key.slice(0, -1);
|
|
}
|
|
const groupKey = key.join('/');
|
|
pathGroups[groupKey] = pathGroups[groupKey] || {};
|
|
pathGroups[groupKey][p] = openapi.paths[p];
|
|
});
|
|
return pathGroups;
|
|
}
|
|
|
|
function createPageIdentifier(uniqueName) {
|
|
return (`api-reference-${uniqueName}`).replace(/-/g, '_');
|
|
}
|
|
|
|
function createIndexPage(spec, params) {
|
|
const page = getPageTemplate('index');
|
|
const menuKey = Object.keys(params.menu)[0];
|
|
const menu = {
|
|
[menuKey]: {...page.menu, ...params.menu[menuKey]}
|
|
};
|
|
page.menu = menu;
|
|
params.menu[menuKey].name = spec.info.title;
|
|
// Create a unique identifier for the menu item
|
|
params.menu[menuKey].identifier = createPageIdentifier(params.api_name);
|
|
params.title = spec.info.title;
|
|
params.description = spec.info.description;
|
|
|
|
// Return params as a YAML frontmatter string
|
|
return (`---\n${yaml.dump(params)}\n---\n\n`)
|
|
.concat(spec.info.description, '\\n', "{{< children >}}")
|
|
}
|
|
|
|
function getPathTags(pathSpec) {
|
|
// Collect tags for methods in the path and resolve them to the tag objects
|
|
// defined in the spec.
|
|
// We assume that the first tag describes the path (aka, path group).
|
|
let tags = [];
|
|
Object.keys(pathSpec.paths).forEach( path => {
|
|
Object.keys(pathSpec.paths[path]).flatMap( method => {
|
|
if(!pathSpec.paths[path][method]['tags']) {
|
|
return [];
|
|
}
|
|
pathSpec.paths[path][method]['tags'].forEach( tag => {
|
|
tags.push(pathSpec.tags.find( t => t.name === tag && !t.traitTag));
|
|
});
|
|
});
|
|
});
|
|
return tags;
|
|
}
|
|
|
|
function getTraitTags(pathSpec) {
|
|
// Temporarily using trait tags for now, but we should migrate them to native
|
|
// Hugo content and frontmatter.
|
|
// Collect trait tags defined in the spec.
|
|
return pathSpec['tags'].filter( k => k[`x-traitTag`]);
|
|
}
|
|
|
|
// Create a page for each group of operations within a path ("path group")
|
|
// In OpenAPI, tags are used to group endpoints. OpenAPI doesn't allow
|
|
// a description field for a path, so we use the name and description of
|
|
// the first tag in the path.
|
|
// Returns a string containing the page content
|
|
// The page frontmatter contains an api.spec property to be rendered as the API reference doc for the path group.
|
|
function createPathGroupPage(pathGroup, pathSpec, params) {
|
|
|
|
const page = getPageTemplate('path');
|
|
const menuKey = Object.keys(params.menu)[0];
|
|
const menu = {
|
|
[menuKey]: {...page.menu, ...params.menu[menuKey]}
|
|
};
|
|
page.menu = menu;
|
|
params.title = pathGroup;
|
|
params.menu[menuKey].parent = pathSpec.info.title;
|
|
params.menu[menuKey].weight = 1;
|
|
params.menu[menuKey].name = params.list_title;
|
|
|
|
const primaryTag = getPathTags(pathSpec).flat()[0];
|
|
if(primaryTag) {
|
|
params.list_title = `${primaryTag['name']} ${pathGroup}`;
|
|
params.description = (primaryTag && primaryTag['description']) || '';
|
|
} else {
|
|
logger.log('warn', `Name: ${pathSpec.info.title} - No tags found for path group: ${pathGroup}`);
|
|
}
|
|
|
|
// Create a unique identifier for the menu item
|
|
params.menu[menuKey].identifier = createPageIdentifier(`${params.api_name}_${pathGroup}`);
|
|
|
|
params.api = {
|
|
spec: JSON.stringify(pathSpec),
|
|
path_group: pathGroup,
|
|
};
|
|
|
|
params.related = [];
|
|
if(pathSpec['x-influxdata-related-endpoints']) {
|
|
params.related = [...pathSpec['x-influxdata-related-endpoints']];
|
|
}
|
|
if(pathSpec['x-influxdata-related-content']) {
|
|
params.related = [
|
|
...params.related, ...pathSpec['x-influxdata-related-content']
|
|
];
|
|
}
|
|
// Return params as a YAML frontmatter string
|
|
return `---\n${yaml.dump(params)}\n---\n`;
|
|
}
|
|
|
|
export function createOverviewPage(spec, params) {
|
|
const page = getPageTemplate('overview');
|
|
const menuKey = Object.keys(params.menu)[0];
|
|
const menu = {
|
|
[menuKey]: {...page.menu, ...params.menu[menuKey]}
|
|
};
|
|
page.menu = menu;
|
|
// Create a unique identifier for the menu item
|
|
params.menu[menuKey].identifier = createPageIdentifier(`${params.api_name}-overview`);
|
|
|
|
// const overviewSpec = JSON.parse(JSON.stringify(spec));
|
|
// overviewSpec.paths = null;
|
|
// params.api = {spec: JSON.stringify(overviewSpec)};
|
|
|
|
let body = '';
|
|
|
|
getTraitTags(spec).forEach( traitTag => {
|
|
// toc = toc.concat(`- [${traitTag['name']}](#${(traitTag['name']).toLowerCase().replace(/ /g, '-')})`, "\n");
|
|
body = body.concat(traitTag['description'], "\n");
|
|
});
|
|
|
|
// Return params as a YAML frontmatter string
|
|
return (`---\n${yaml.dump(params)}\n---\n\n`)
|
|
.concat(spec.info.description, '\\n', body)
|
|
}
|
|
|
|
export function createAPIPages(params, specPath, docPath) {
|
|
try {
|
|
// Execute the script to fetch and bundle the configured spec.
|
|
execCommand(`${getSwagger} ${params.api_name} -B`);
|
|
|
|
logger.log('info', `Target: ${docPath} - Creating pages for: ${params.api_name} ${specPath}`);
|
|
const spec = yaml.load(readFileSync(specPath, 'utf8'));
|
|
|
|
if (!existsSync(docPath)) {
|
|
mkdirSync(docPath, { recursive: true });
|
|
};
|
|
|
|
// Deep copy the params object to avoid modifying the original
|
|
const paramsClone = JSON.stringify(params);
|
|
|
|
// Create the index page
|
|
writeFileSync(path.join(docPath, '_index.md'), createIndexPage(spec, JSON.parse(paramsClone)));
|
|
|
|
// // Create the overview page
|
|
writeFileSync(
|
|
path.join(docPath, 'overview.md'),
|
|
createOverviewPage(spec, JSON.parse(paramsClone)));
|
|
|
|
// Create a page for each group of operations within a path ("path group")
|
|
const pathGroups = getPathGroups(spec);
|
|
Object.keys(pathGroups).forEach( pathGroup => {
|
|
// Deep copy the spec object
|
|
const specClone = JSON.parse(JSON.stringify(spec));
|
|
specClone.paths = pathGroups[pathGroup];
|
|
const page = createPathGroupPage(pathGroup, specClone, JSON.parse(paramsClone));
|
|
// For readability, we'll write the page as a YAML
|
|
writeFileSync(path.join(docPath,
|
|
`${pathGroup.replaceAll('/', '-').replace(/^-/, '')}.md`),
|
|
page);
|
|
});
|
|
} catch (error) {
|
|
console.error(`Error creating API pages: ${docPath}`);
|
|
logger.log('error', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function deleteAPIPages(docPath) {
|
|
try {
|
|
rmSync(docPath, {recursive: true, force: true});
|
|
} catch (error) {
|
|
console.error(`Error deleting API pages: ${docPath}`);
|
|
}
|
|
}
|