#!/usr/bin/env node /** * Generate a Markdown changelog from two OpenAPI specs using the api-compat-diff tool. * * Usage: * node scripts/api-compat-changelog.mjs [--title "Release X"] [--fail-on-breaking] * * Output is written to stdout. */ import { execFileSync } from 'child_process'; import process from 'process'; import path from 'path'; function panic(message) { console.error(`[api-compat-changelog] ${message}`); process.exit(1); } function parseArgs(argv) { const args = argv.slice(2); if (args.length < 2) { panic('Usage: node scripts/api-compat-changelog.mjs [--title "Release X"] [--fail-on-breaking]'); } const opts = { oldSpec: args[0], newSpec: args[1], title: 'API Compatibility Report', failOnBreaking: false, }; for (let i = 2; i < args.length; i += 1) { const arg = args[i]; if (arg === '--title' && args[i + 1]) { opts.title = args[i + 1]; i += 1; } else if (arg === '--fail-on-breaking') { opts.failOnBreaking = true; } } return opts; } function runCompatDiff(oldSpec, newSpec) { const output = execFileSync( 'node', ['scripts/api-compat-diff.mjs', oldSpec, newSpec, '--output', 'json'], { encoding: 'utf8' } ); return JSON.parse(output); } function formatList(items, symbol) { if (!items || items.length === 0) { return `${symbol} None`; } return items.map((item) => `${symbol} ${item}`).join('\n'); } function renderMarkdown(title, diff, oldSpec, newSpec) { return [ `# ${title}`, '', `- Old spec: \`${path.relative(process.cwd(), oldSpec)}\``, `- New spec: \`${path.relative(process.cwd(), newSpec)}\``, '', '## Summary', `- Additive operations: ${diff.additive.operations.length}`, `- Breaking operations: ${diff.breaking.operations.length}`, `- Additive responses: ${diff.additive.responses.length}`, `- Breaking responses: ${diff.breaking.responses.length}`, '', '## Additive', '### Operations', formatList(diff.additive.operations, '-'), '', '### Responses', formatList(diff.additive.responses, '-'), '', '## Breaking', '### Operations', formatList(diff.breaking.operations, '-'), '', '### Responses', formatList(diff.breaking.responses, '-'), '', ].join('\n'); } function main() { const opts = parseArgs(process.argv); const diff = runCompatDiff(opts.oldSpec, opts.newSpec); const markdown = renderMarkdown(opts.title, diff, opts.oldSpec, opts.newSpec); console.log(markdown); if (opts.failOnBreaking && (diff.breaking.operations.length > 0 || diff.breaking.responses.length > 0)) { process.exit(2); } } if (import.meta.url === `file://${process.argv[1]}`) { main(); }