91e75e620b
CI: cua-driver distro-compat matrix / Resolve release version (push) Waiting to run
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / Distro compat summary (push) Blocked by required conditions
CI: Nix Linux Rust source / Nix / compositor build (push) Waiting to run
CI: Nix Linux Rust source / Nix / driver package (push) Waiting to run
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Waiting to run
CI: Rust Linux unit / Rust Linux unit and compile (push) Waiting to run
CI: Rust Windows unit / Rust Windows unit and compile (push) Waiting to run
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Waiting to run
CD: Docs MCP Server / build (linux/amd64) (push) Waiting to run
CD: Docs MCP Server / build (linux/arm64) (push) Waiting to run
CD: Docs MCP Server / merge (push) Blocked by required conditions
752 lines
22 KiB
TypeScript
752 lines
22 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
|
|
/**
|
|
* Lume Documentation Generator
|
|
*
|
|
* Generates MDX documentation files from Lume's dump-docs command output.
|
|
* This ensures documentation stays synchronized with the source code.
|
|
*
|
|
* Usage:
|
|
* npx tsx scripts/docs-generators/lume.ts # Generate docs
|
|
* npx tsx scripts/docs-generators/lume.ts --check # Check for drift (CI mode)
|
|
*/
|
|
|
|
import { execSync } from 'child_process';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
// ============================================================================
|
|
// Types
|
|
// ============================================================================
|
|
|
|
export interface CLIDocumentation {
|
|
name: string;
|
|
version: string;
|
|
abstract: string;
|
|
commands: CommandDoc[];
|
|
}
|
|
|
|
export interface CommandDoc {
|
|
name: string;
|
|
abstract: string;
|
|
discussion?: string;
|
|
arguments: ArgumentDoc[];
|
|
options: OptionDoc[];
|
|
flags: FlagDoc[];
|
|
subcommands: CommandDoc[];
|
|
}
|
|
|
|
export interface ArgumentDoc {
|
|
name: string;
|
|
help: string;
|
|
type: string;
|
|
is_optional: boolean;
|
|
}
|
|
|
|
export interface OptionDoc {
|
|
name: string;
|
|
short_name?: string;
|
|
help: string;
|
|
type: string;
|
|
default_value?: string;
|
|
is_optional: boolean;
|
|
}
|
|
|
|
export interface FlagDoc {
|
|
name: string;
|
|
short_name?: string;
|
|
help: string;
|
|
default_value: boolean;
|
|
}
|
|
|
|
export interface HTTPAPIDocumentation {
|
|
base_path: string;
|
|
version: string;
|
|
description: string;
|
|
endpoints: APIEndpointDoc[];
|
|
}
|
|
|
|
export interface APIEndpointDoc {
|
|
method: string;
|
|
path: string;
|
|
description: string;
|
|
category: string;
|
|
path_parameters: APIParameterDoc[];
|
|
query_parameters: APIParameterDoc[];
|
|
request_body?: APIRequestBodyDoc;
|
|
response_body: APIResponseDoc;
|
|
status_codes: APIStatusCodeDoc[];
|
|
}
|
|
|
|
export interface APIParameterDoc {
|
|
name: string;
|
|
type: string;
|
|
required: boolean;
|
|
description: string;
|
|
}
|
|
|
|
export interface APIRequestBodyDoc {
|
|
content_type: string;
|
|
description: string;
|
|
fields: APIFieldDoc[];
|
|
}
|
|
|
|
export interface APIResponseDoc {
|
|
content_type: string;
|
|
description: string;
|
|
fields?: APIFieldDoc[];
|
|
}
|
|
|
|
export interface APIFieldDoc {
|
|
name: string;
|
|
type: string;
|
|
required: boolean;
|
|
description: string;
|
|
default_value?: string;
|
|
}
|
|
|
|
export interface APIStatusCodeDoc {
|
|
code: number;
|
|
description: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Configuration
|
|
// ============================================================================
|
|
|
|
const ROOT_DIR = path.resolve(__dirname, '../..');
|
|
const LUME_DIR = path.join(ROOT_DIR, 'libs', 'lume');
|
|
const DOCS_OUTPUT_DIR = path.join(ROOT_DIR, 'docs', 'content', 'docs', 'reference', 'lume');
|
|
const TAG_PREFIX = 'lume-v';
|
|
|
|
// ============================================================================
|
|
// Version Discovery
|
|
// ============================================================================
|
|
|
|
interface VersionInfo {
|
|
version: string;
|
|
href: string;
|
|
isCurrent: boolean;
|
|
}
|
|
|
|
/**
|
|
* Get the latest released version from git tags.
|
|
*/
|
|
export function getLatestReleasedVersion(): string {
|
|
try {
|
|
const output = execSync(`git tag | grep "^${TAG_PREFIX}" | sort -V | tail -1`, {
|
|
encoding: 'utf-8',
|
|
cwd: ROOT_DIR,
|
|
}).trim();
|
|
if (output) {
|
|
return output.replace(TAG_PREFIX, '');
|
|
}
|
|
} catch {
|
|
// Fall through
|
|
}
|
|
return '0.0.0';
|
|
}
|
|
|
|
/**
|
|
* Discover available versioned doc folders and build version list.
|
|
*/
|
|
export function discoverVersions(currentVersion: string): VersionInfo[] {
|
|
const versions: VersionInfo[] = [];
|
|
const currentMajorMinor = currentVersion.split('.').slice(0, 2).join('.');
|
|
|
|
// Add current version (latest)
|
|
versions.push({
|
|
version: currentMajorMinor,
|
|
href: '/reference/lume/cli-reference',
|
|
isCurrent: true,
|
|
});
|
|
|
|
// Discover versioned folders (v0.2, v0.1, etc.)
|
|
if (fs.existsSync(DOCS_OUTPUT_DIR)) {
|
|
const entries = fs.readdirSync(DOCS_OUTPUT_DIR, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
if (entry.isDirectory() && entry.name.startsWith('v')) {
|
|
const version = entry.name.substring(1);
|
|
if (version === currentMajorMinor) continue;
|
|
versions.push({
|
|
version,
|
|
href: `/reference/lume/${entry.name}/cli-reference`,
|
|
isCurrent: false,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort descending
|
|
versions.sort((a, b) => {
|
|
const partsA = a.version.split('.').map((x) => parseInt(x, 10) || 0);
|
|
const partsB = b.version.split('.').map((x) => parseInt(x, 10) || 0);
|
|
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
const partA = partsA[i] || 0;
|
|
const partB = partsB[i] || 0;
|
|
if (partA !== partB) return partB - partA;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
return versions;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Main
|
|
// ============================================================================
|
|
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
const checkOnly = args.includes('--check') || args.includes('--check-only');
|
|
|
|
console.log('🔧 Lume Documentation Generator');
|
|
console.log('================================\n');
|
|
|
|
// Step 1: Build Lume if needed
|
|
console.log('📦 Building Lume...');
|
|
try {
|
|
execSync('swift build -c release', {
|
|
cwd: LUME_DIR,
|
|
stdio: 'inherit',
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Failed to build Lume');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Step 2: Get CLI documentation
|
|
console.log('\n📖 Extracting CLI documentation...');
|
|
const cliDocsJson = execSync('.build/release/lume dump-docs --type cli', {
|
|
cwd: LUME_DIR,
|
|
encoding: 'utf-8',
|
|
});
|
|
const cliDocs: CLIDocumentation = JSON.parse(cliDocsJson);
|
|
console.log(` Found ${cliDocs.commands.length} commands`);
|
|
|
|
// Step 3: Get API documentation
|
|
console.log('📖 Extracting API documentation...');
|
|
const apiDocsJson = execSync('.build/release/lume dump-docs --type api', {
|
|
cwd: LUME_DIR,
|
|
encoding: 'utf-8',
|
|
});
|
|
const apiDocs: HTTPAPIDocumentation = JSON.parse(apiDocsJson);
|
|
console.log(` Found ${apiDocs.endpoints.length} endpoints`);
|
|
|
|
// Step 4: Generate MDX files
|
|
console.log('\n📝 Generating documentation files...');
|
|
|
|
const cliMdx = generateCLIReferenceMDX(cliDocs);
|
|
const apiMdx = generateHTTPAPIMDX(apiDocs);
|
|
|
|
const cliPath = path.join(DOCS_OUTPUT_DIR, 'cli-reference.mdx');
|
|
const apiPath = path.join(DOCS_OUTPUT_DIR, 'http-api.mdx');
|
|
|
|
if (checkOnly) {
|
|
// Check mode: compare with existing files
|
|
console.log('\n🔍 Checking for documentation drift...');
|
|
|
|
let hasDrift = false;
|
|
|
|
if (fs.existsSync(cliPath)) {
|
|
const existingCli = fs.readFileSync(cliPath, 'utf-8');
|
|
if (existingCli !== cliMdx) {
|
|
console.error('❌ cli-reference.mdx is out of sync with source code');
|
|
hasDrift = true;
|
|
} else {
|
|
console.log('✅ cli-reference.mdx is up to date');
|
|
}
|
|
} else {
|
|
console.error('❌ cli-reference.mdx does not exist');
|
|
hasDrift = true;
|
|
}
|
|
|
|
if (fs.existsSync(apiPath)) {
|
|
const existingApi = fs.readFileSync(apiPath, 'utf-8');
|
|
if (existingApi !== apiMdx) {
|
|
console.error('❌ http-api.mdx is out of sync with source code');
|
|
hasDrift = true;
|
|
} else {
|
|
console.log('✅ http-api.mdx is up to date');
|
|
}
|
|
} else {
|
|
console.error('❌ http-api.mdx does not exist');
|
|
hasDrift = true;
|
|
}
|
|
|
|
if (hasDrift) {
|
|
console.error("\n💡 Run 'npx tsx scripts/docs-generators/lume.ts' to update documentation");
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('\n✅ All Lume documentation is up to date!');
|
|
} else {
|
|
// Generate mode: write files
|
|
fs.writeFileSync(cliPath, cliMdx);
|
|
console.log(` ✅ Generated ${path.relative(ROOT_DIR, cliPath)}`);
|
|
|
|
fs.writeFileSync(apiPath, apiMdx);
|
|
console.log(` ✅ Generated ${path.relative(ROOT_DIR, apiPath)}`);
|
|
|
|
console.log('\n✅ Lume documentation generated successfully!');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// CLI Reference Generator
|
|
// ============================================================================
|
|
|
|
export function generateCLIReferenceMDX(docs: CLIDocumentation): string {
|
|
const lines: string[] = [];
|
|
|
|
const documentedVersion = docs.version || getLatestReleasedVersion();
|
|
|
|
// Header - frontmatter MUST be at the very beginning of the file
|
|
lines.push('---');
|
|
lines.push('title: CLI Reference');
|
|
lines.push('description: Command Line Interface reference for Lume');
|
|
lines.push('---');
|
|
lines.push('');
|
|
lines.push(`{/*
|
|
AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
|
|
Generated by: npx tsx scripts/docs-generators/lume.ts
|
|
Source: lume dump-docs --type cli
|
|
Version: ${documentedVersion}
|
|
*/}`);
|
|
lines.push('');
|
|
lines.push(`${docs.abstract}`);
|
|
lines.push('');
|
|
lines.push(
|
|
`Documented against Lume **${documentedVersion}**. Run \`lume --version\` for your installed version.`
|
|
);
|
|
lines.push('');
|
|
lines.push('For installation steps, see [Install Lume](/how-to-guides/lume/install-lume).');
|
|
lines.push('');
|
|
|
|
const groups = [
|
|
{ title: 'VM Management', commands: ['create', 'run', 'stop', 'delete', 'clone'] },
|
|
{ title: 'VM Information and Configuration', commands: ['ls', 'get', 'set'] },
|
|
{
|
|
title: 'Image Management',
|
|
commands: ['images', 'pull', 'push', 'convert', 'ipsw', 'prune'],
|
|
},
|
|
{ title: 'Guest Access and Security', commands: ['ssh', 'setup', 'sip'] },
|
|
{
|
|
title: 'Configuration and Server',
|
|
commands: ['config', 'serve', 'logs', 'check-update', 'update'],
|
|
},
|
|
{ title: 'Developer Tools', commands: ['dump-docs'] },
|
|
];
|
|
|
|
const assignedCommands = groups.flatMap((group) => group.commands);
|
|
const duplicateAssignments = assignedCommands.filter(
|
|
(name, index) => assignedCommands.indexOf(name) !== index
|
|
);
|
|
const ungroupedCommands = docs.commands
|
|
.map((command) => command.name)
|
|
.filter((name) => !assignedCommands.includes(name));
|
|
|
|
if (duplicateAssignments.length > 0 || ungroupedCommands.length > 0) {
|
|
throw new Error(
|
|
`CLI command grouping mismatch. Ungrouped: ${[...new Set(ungroupedCommands)].sort().join(', ') || 'none'}; duplicated: ${[...new Set(duplicateAssignments)].sort().join(', ') || 'none'}`
|
|
);
|
|
}
|
|
|
|
for (const group of groups) {
|
|
lines.push(`## ${group.title}`);
|
|
lines.push('');
|
|
for (const commandName of group.commands) {
|
|
const command = docs.commands.find((candidate) => candidate.name === commandName);
|
|
if (command) {
|
|
lines.push(...generateCommandDoc(command, '###'));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global options
|
|
lines.push('## Global Options');
|
|
lines.push('');
|
|
lines.push('These options are available for all commands:');
|
|
lines.push('');
|
|
lines.push('- `--help` - Show help information');
|
|
lines.push('- `--version` - Show version number');
|
|
lines.push('');
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
export function generateCommandDoc(cmd: CommandDoc, heading: string): string[] {
|
|
const lines: string[] = [];
|
|
|
|
lines.push(`${heading} lume ${cmd.name}`);
|
|
lines.push('');
|
|
lines.push(cmd.abstract);
|
|
lines.push('');
|
|
|
|
// Arguments
|
|
if (cmd.arguments.length > 0) {
|
|
lines.push('**Arguments:**');
|
|
lines.push('');
|
|
lines.push('| Name | Type | Required | Description |');
|
|
lines.push('| ---- | ---- | -------- | ----------- |');
|
|
for (const arg of cmd.arguments) {
|
|
const required = arg.is_optional ? 'No' : 'Yes';
|
|
lines.push(`| \`<${arg.name}>\` | ${arg.type} | ${required} | ${arg.help} |`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
|
|
// Options
|
|
if (cmd.options.length > 0) {
|
|
lines.push('**Options:**');
|
|
lines.push('');
|
|
lines.push('| Name | Type | Default | Description |');
|
|
lines.push('| ---- | ---- | ------- | ----------- |');
|
|
for (const opt of cmd.options) {
|
|
const shortFlag = opt.short_name ? `-${opt.short_name}, ` : '';
|
|
const defaultVal = opt.default_value || '-';
|
|
lines.push(`| \`${shortFlag}--${opt.name}\` | ${opt.type} | ${defaultVal} | ${opt.help} |`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
|
|
// Flags
|
|
if (cmd.flags.length > 0) {
|
|
lines.push('**Flags:**');
|
|
lines.push('');
|
|
lines.push('| Name | Default | Description |');
|
|
lines.push('| ---- | ------- | ----------- |');
|
|
for (const flag of cmd.flags) {
|
|
const shortFlag = flag.short_name ? `-${flag.short_name}, ` : '';
|
|
lines.push(`| \`${shortFlag}--${flag.name}\` | ${flag.default_value} | ${flag.help} |`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
|
|
// Subcommands
|
|
if (cmd.subcommands.length > 0) {
|
|
lines.push('**Subcommands:**');
|
|
lines.push('');
|
|
for (const sub of cmd.subcommands) {
|
|
lines.push(`- \`lume ${cmd.name} ${sub.name}\` - ${sub.abstract}`);
|
|
|
|
// Show subcommand details
|
|
if (sub.arguments.length > 0 || sub.options.length > 0) {
|
|
for (const arg of sub.arguments) {
|
|
lines.push(` - \`<${arg.name}>\` - ${arg.help}`);
|
|
}
|
|
for (const opt of sub.options) {
|
|
const shortFlag = opt.short_name ? `-${opt.short_name}, ` : '';
|
|
lines.push(` - \`${shortFlag}--${opt.name}\` - ${opt.help}`);
|
|
}
|
|
}
|
|
|
|
// Nested subcommands
|
|
if (sub.subcommands.length > 0) {
|
|
for (const nested of sub.subcommands) {
|
|
lines.push(` - \`lume ${cmd.name} ${sub.name} ${nested.name}\` - ${nested.abstract}`);
|
|
}
|
|
}
|
|
}
|
|
lines.push('');
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
// ============================================================================
|
|
// HTTP API Reference Generator
|
|
// ============================================================================
|
|
|
|
export function generateHTTPAPIMDX(docs: HTTPAPIDocumentation): string {
|
|
const lines: string[] = [];
|
|
|
|
const documentedVersion = docs.version || getLatestReleasedVersion();
|
|
|
|
// Header - frontmatter MUST be at the very beginning of the file
|
|
lines.push('---');
|
|
lines.push('title: API Reference');
|
|
lines.push('description: HTTP API reference for Lume server');
|
|
lines.push('---');
|
|
lines.push('');
|
|
lines.push(`{/*
|
|
AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
|
|
Generated by: npx tsx scripts/docs-generators/lume.ts
|
|
Source: lume dump-docs --type api
|
|
Version: ${documentedVersion}
|
|
*/}`);
|
|
lines.push('');
|
|
lines.push("import { Tabs, Tab } from 'fumadocs-ui/components/tabs';");
|
|
lines.push('');
|
|
|
|
// Introduction
|
|
lines.push(docs.description);
|
|
lines.push('');
|
|
lines.push(
|
|
`Documented against Lume **${documentedVersion}**. Run \`lume --version\` for your installed version.`
|
|
);
|
|
lines.push('');
|
|
lines.push('## Default URL');
|
|
lines.push('');
|
|
lines.push('```');
|
|
lines.push('http://localhost:7777');
|
|
lines.push('```');
|
|
lines.push('');
|
|
lines.push(
|
|
'Start the server with `lume serve` or specify a custom port with `lume serve --port <port>`.'
|
|
);
|
|
lines.push('');
|
|
|
|
// Group endpoints by category
|
|
const categories = [...new Set(docs.endpoints.map((e) => e.category))];
|
|
|
|
for (const category of categories) {
|
|
lines.push(`## ${category}`);
|
|
lines.push('');
|
|
|
|
const categoryEndpoints = docs.endpoints.filter((e) => e.category === category);
|
|
|
|
for (const endpoint of categoryEndpoints) {
|
|
lines.push(...generateEndpointDoc(endpoint));
|
|
}
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
export function generateEndpointDoc(endpoint: APIEndpointDoc): string[] {
|
|
const lines: string[] = [];
|
|
|
|
lines.push(`### ${endpoint.description}`);
|
|
lines.push('');
|
|
lines.push(endpoint.description);
|
|
lines.push('');
|
|
lines.push(`\`${endpoint.method}: ${endpoint.path}\``);
|
|
lines.push('');
|
|
|
|
// Parameters table (path + query)
|
|
const allParams = [
|
|
...endpoint.path_parameters.map((p) => ({ ...p, location: 'path' })),
|
|
...endpoint.query_parameters.map((p) => ({ ...p, location: 'query' })),
|
|
];
|
|
|
|
if (allParams.length > 0) {
|
|
lines.push('#### Parameters');
|
|
lines.push('');
|
|
lines.push('| Name | Type | Required | Description |');
|
|
lines.push('| ---- | ---- | -------- | ----------- |');
|
|
for (const param of allParams) {
|
|
const required = param.required ? 'Yes' : 'No';
|
|
lines.push(`| ${param.name} | ${param.type} | ${required} | ${param.description} |`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
|
|
// Request body
|
|
if (endpoint.request_body) {
|
|
lines.push('#### Request Body');
|
|
lines.push('');
|
|
lines.push('| Name | Type | Required | Description |');
|
|
lines.push('| ---- | ---- | -------- | ----------- |');
|
|
for (const field of endpoint.request_body.fields) {
|
|
const required = field.required ? 'Yes' : 'No';
|
|
const defaultStr = field.default_value ? ` (default: ${field.default_value})` : '';
|
|
lines.push(
|
|
`| ${field.name} | ${field.type} | ${required} | ${field.description}${defaultStr} |`
|
|
);
|
|
}
|
|
lines.push('');
|
|
}
|
|
|
|
// Example request
|
|
lines.push('#### Example Request');
|
|
lines.push('');
|
|
lines.push("<Tabs groupId=\"language\" persist items={['Curl', 'Python', 'TypeScript']}>");
|
|
|
|
// Generate curl example
|
|
lines.push(' <Tab value="Curl">');
|
|
lines.push('```bash');
|
|
lines.push(generateCurlExample(endpoint));
|
|
lines.push('```');
|
|
lines.push(' </Tab>');
|
|
|
|
// Generate Python example
|
|
lines.push(' <Tab value="Python">');
|
|
lines.push('```python');
|
|
lines.push(generatePythonExample(endpoint));
|
|
lines.push('```');
|
|
lines.push(' </Tab>');
|
|
|
|
// Generate TypeScript example
|
|
lines.push(' <Tab value="TypeScript">');
|
|
lines.push('```typescript');
|
|
lines.push(generateTypeScriptExample(endpoint));
|
|
lines.push('```');
|
|
lines.push(' </Tab>');
|
|
|
|
lines.push('</Tabs>');
|
|
lines.push('');
|
|
|
|
// Status codes
|
|
lines.push('#### Response');
|
|
lines.push('');
|
|
for (const status of endpoint.status_codes) {
|
|
lines.push(`- **${status.code}**: ${status.description}`);
|
|
}
|
|
lines.push('');
|
|
|
|
lines.push('---');
|
|
lines.push('');
|
|
|
|
return lines;
|
|
}
|
|
|
|
function generateCurlExample(endpoint: APIEndpointDoc): string {
|
|
const path = endpoint.path.replace(/:(\w+)/g, (_, name) => getExamplePathValue(name));
|
|
const url = `http://localhost:7777${path}`;
|
|
|
|
if (endpoint.method === 'GET' || endpoint.method === 'DELETE') {
|
|
if (endpoint.method === 'DELETE') {
|
|
return `curl -X DELETE "${url}"`;
|
|
}
|
|
return `curl "${url}"`;
|
|
}
|
|
|
|
// POST/PATCH with body
|
|
if (endpoint.request_body && endpoint.request_body.fields.length > 0) {
|
|
const bodyObj: Record<string, unknown> = {};
|
|
for (const field of endpoint.request_body.fields) {
|
|
if (field.required) {
|
|
bodyObj[field.name] = getExampleValue(field);
|
|
}
|
|
}
|
|
const bodyJson = JSON.stringify(bodyObj, null, 2);
|
|
return `curl -X ${endpoint.method} "http://localhost:7777${path}" \\
|
|
-H "Content-Type: application/json" \\
|
|
-d '${bodyJson}'`;
|
|
}
|
|
|
|
return `curl -X ${endpoint.method} "${url}"`;
|
|
}
|
|
|
|
function generatePythonExample(endpoint: APIEndpointDoc): string {
|
|
const path = endpoint.path.replace(/:(\w+)/g, (_, name) => getExamplePathValue(name));
|
|
const method = endpoint.method.toLowerCase();
|
|
|
|
const lines: string[] = [];
|
|
lines.push('import requests');
|
|
lines.push('');
|
|
|
|
if (
|
|
endpoint.request_body &&
|
|
endpoint.request_body.fields.length > 0 &&
|
|
(endpoint.method === 'POST' || endpoint.method === 'PATCH')
|
|
) {
|
|
lines.push('data = {');
|
|
for (const field of endpoint.request_body.fields) {
|
|
if (field.required) {
|
|
const value = getExampleValuePython(field);
|
|
lines.push(` "${field.name}": ${value},`);
|
|
}
|
|
}
|
|
lines.push('}');
|
|
lines.push('');
|
|
lines.push(`response = requests.${method}("http://localhost:7777${path}", json=data)`);
|
|
} else {
|
|
lines.push(`response = requests.${method}("http://localhost:7777${path}")`);
|
|
}
|
|
|
|
lines.push('print(response.json())');
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
function generateTypeScriptExample(endpoint: APIEndpointDoc): string {
|
|
const path = endpoint.path.replace(/:(\w+)/g, (_, name) => getExamplePathValue(name));
|
|
|
|
const lines: string[] = [];
|
|
|
|
if (
|
|
endpoint.request_body &&
|
|
endpoint.request_body.fields.length > 0 &&
|
|
(endpoint.method === 'POST' || endpoint.method === 'PATCH')
|
|
) {
|
|
lines.push(`const response = await fetch(\`http://localhost:7777${path}\`, {`);
|
|
lines.push(` method: "${endpoint.method}",`);
|
|
lines.push(' headers: { "Content-Type": "application/json" },');
|
|
lines.push(' body: JSON.stringify({');
|
|
for (const field of endpoint.request_body.fields) {
|
|
if (field.required) {
|
|
const value = getExampleValueTS(field);
|
|
lines.push(` ${field.name}: ${value},`);
|
|
}
|
|
}
|
|
lines.push(' }),');
|
|
lines.push('});');
|
|
} else if (endpoint.method === 'DELETE') {
|
|
lines.push(`const response = await fetch(\`http://localhost:7777${path}\`, {`);
|
|
lines.push(` method: "DELETE",`);
|
|
lines.push('});');
|
|
} else {
|
|
lines.push(`const response = await fetch(\`http://localhost:7777${path}\`);`);
|
|
}
|
|
|
|
lines.push('const data = await response.json();');
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
function getExampleValue(field: APIFieldDoc): unknown {
|
|
switch (field.type) {
|
|
case 'string':
|
|
if (field.name === 'name') return 'my-vm';
|
|
if (field.name === 'os') return 'macOS';
|
|
if (field.name === 'memory') return '8GB';
|
|
if (field.name === 'diskSize') return '50GB';
|
|
if (field.name === 'display') return '1024x768';
|
|
if (field.name === 'image') return 'macos-tahoe-vanilla:latest';
|
|
if (field.name === 'path') return '/path/to/storage';
|
|
return 'example';
|
|
case 'integer':
|
|
if (field.name === 'cpu') return 4;
|
|
return 1;
|
|
case 'boolean':
|
|
return false;
|
|
case 'array':
|
|
if (field.name === 'tags') return ['latest'];
|
|
return [];
|
|
default:
|
|
return 'value';
|
|
}
|
|
}
|
|
|
|
function getExampleValuePython(field: APIFieldDoc): string {
|
|
const val = getExampleValue(field);
|
|
if (typeof val === 'string') return `"${val}"`;
|
|
if (typeof val === 'boolean') return val ? 'True' : 'False';
|
|
if (Array.isArray(val)) return JSON.stringify(val);
|
|
return String(val);
|
|
}
|
|
|
|
function getExampleValueTS(field: APIFieldDoc): string {
|
|
const val = getExampleValue(field);
|
|
if (typeof val === 'string') return `"${val}"`;
|
|
if (Array.isArray(val)) return JSON.stringify(val);
|
|
return String(val);
|
|
}
|
|
|
|
function getExamplePathValue(name: string): string {
|
|
if (name === 'name') return 'my-vm';
|
|
if (name === 'id') return 'example-id';
|
|
return `example-${name}`;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Run
|
|
// ============================================================================
|
|
|
|
main().catch((error) => {
|
|
console.error('Error:', error);
|
|
process.exit(1);
|
|
});
|