@confytome/markdown
Version:
Standalone Markdown generator - creates Confluence-friendly Markdown documentation from OpenAPI specs using Mustache templates with Turkish Unicode support.
184 lines (163 loc) • 6.81 kB
JavaScript
import { program } from 'commander';
import fs from 'node:fs';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { StandaloneMarkdownGenerator } from './standalone-generator.js';
const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
/**
* Generate OpenAPI spec by calling @confytome/core
* @param {string} configPath - Path to server config file
* @param {Array} files - Array of JSDoc files
* @param {string} outputDir - Output directory
* @returns {Promise<string>} Path to generated spec file
*/
async function generateOpenApiSpec(configPath, files, outputDir) {
return new Promise((resolve, reject) => {
// Core package seems to use a default output directory, let's use absolute path
const absoluteOutputDir = path.resolve(outputDir);
const specPath = path.join(absoluteOutputDir, 'api-spec.json');
// Ensure output directory exists
if (!fs.existsSync(absoluteOutputDir)) {
fs.mkdirSync(absoluteOutputDir, { recursive: true });
}
// Detect config format and build appropriate command
let args;
if (configPath && fs.existsSync(configPath)) {
try {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
// Check if this is a confytome.json format (has serverConfig and routeFiles)
if (config.serverConfig && config.routeFiles) {
args = ['generate', '--config', configPath, '--output', absoluteOutputDir];
console.log(`📖 Running: npx @confytome/core ${args.join(' ')}`);
} else {
// Traditional server config format
args = ['openapi', '-c', configPath, '-f', ...(files || []), '-o', absoluteOutputDir];
console.log(`📖 Running: npx @confytome/core ${args.join(' ')}`);
}
} catch (error) {
reject(new Error(`Invalid config file: ${error.message}`));
return;
}
} else if (files && files.length > 0) {
reject(new Error('Config file is required when providing JSDoc files'));
return;
} else {
reject(new Error('Either config file or JSDoc files must be provided'));
return;
}
const child = spawn('npx', ['@confytome/core', ...args], {
stdio: 'inherit',
shell: true
});
child.on('close', (code) => {
if (code === 0) {
// Check both expected location and core's default location
const possiblePaths = [
specPath,
path.join(process.cwd(), 'confytome', 'api-spec.json'),
path.join(absoluteOutputDir, 'confytome', 'api-spec.json')
];
let foundPath = null;
for (const possiblePath of possiblePaths) {
if (fs.existsSync(possiblePath)) {
foundPath = possiblePath;
break;
}
}
if (foundPath) {
// Move spec to expected location if it's not there
if (foundPath !== specPath) {
fs.copyFileSync(foundPath, specPath);
console.log(`📋 Moved spec from ${foundPath} to ${specPath}`);
}
console.log(`✅ OpenAPI spec ready: ${specPath}`);
resolve(specPath);
} else {
reject(new Error(`OpenAPI spec not found in any expected location: ${possiblePaths.join(', ')}`));
}
} else {
reject(new Error(`@confytome/core exited with code ${code}`));
}
});
child.on('error', (error) => {
reject(new Error(`Failed to start @confytome/core: ${error.message}`));
});
});
}
program
.name('@confytome/markdown')
.description('Generate Confluence-friendly Markdown documentation from OpenAPI specs')
.version(pkg.version);
program
.command('generate')
.description('Generate Markdown documentation from OpenAPI spec')
.option('-s, --spec <path>', 'Path to OpenAPI spec file', './api-spec.json')
.option('-c, --config <path>', 'Server config JSON file (for generating spec from JSDoc)')
.option('-f, --files <files...>', 'JSDoc files to process (will generate OpenAPI spec if no --spec provided)')
.option('-o, --output <path>', 'Output directory for generated files', './confytome')
.option('--no-brand', 'Exclude confytome branding from generated documentation')
.option('--no-url-encode', 'Disable URL encoding for anchor links (preserve original anchor format)')
.action(async(options) => {
try {
let specPath = options.spec;
// If spec doesn't exist but config is provided, generate spec first
if (!fs.existsSync(specPath) && options.config) {
console.log('🔧 No OpenAPI spec found, generating from JSDoc files...');
specPath = await generateOpenApiSpec(options.config, options.files, options.output);
}
const generator = new StandaloneMarkdownGenerator(options.output, {
specPath: path.resolve(specPath),
excludeBrand: !options.brand,
urlEncodeAnchors: options.urlEncode !== false // Default to true, disable with --no-url-encode
});
const result = await generator.generate();
if (result.success) {
console.log('✅ Markdown generation completed successfully');
console.log(`📄 Generated: ${result.outputPath} (${result.size} bytes)`);
} else {
console.error(`❌ Generation failed: ${result.stats?.error || 'Unknown error'}`);
process.exit(1);
}
} catch (error) {
console.error(`❌ Error: ${error.message}`);
process.exit(1);
}
});
program
.command('validate')
.description('Validate OpenAPI spec file')
.option('-s, --spec <path>', 'Path to OpenAPI spec file', './api-spec.json')
.action(async(options) => {
try {
const generator = new StandaloneMarkdownGenerator('./', {
specPath: path.resolve(options.spec)
});
const result = await generator.validate();
if (result.success) {
console.log('✅ OpenAPI specification is valid');
console.log('✅ Ready for Markdown generation');
} else {
console.error('❌ Validation failed:');
result.errors.forEach(error => console.error(` - ${error}`));
process.exit(1);
}
} catch (error) {
console.error(`❌ Error: ${error.message}`);
process.exit(1);
}
});
program
.command('info')
.description('Show generator information')
.action(() => {
const metadata = StandaloneMarkdownGenerator.getMetadata();
console.log(`${metadata.packageName}`);
console.log('Standalone Markdown Generator');
console.log(`${metadata.description}`);
console.log('OpenAPI 3.x support');
console.log('Confluence-friendly formatting');
console.log(`${metadata.cliCommand}`);
console.log(`Version: ${metadata.version}`);
});
program.parse();