update-tools-docs.ts•8.29 kB
#!/usr/bin/env node
/**
* XcodeBuildMCP Tools Documentation Updater
*
* Automatically updates docs/TOOLS.md with current tool and workflow information
* using static AST analysis. Ensures documentation always reflects the actual codebase.
*
* Usage:
* npx tsx scripts/update-tools-docs.ts [--dry-run] [--verbose]
*
* Options:
* --dry-run, -d Show what would be updated without making changes
* --verbose, -v Show detailed information about the update process
* --help, -h Show this help message
*/
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import {
getStaticToolAnalysis,
type StaticAnalysisResult,
type WorkflowInfo,
} from './analysis/tools-analysis.js';
// Get project paths
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, '..');
const docsPath = path.join(projectRoot, 'docs', 'TOOLS.md');
// CLI options
const args = process.argv.slice(2);
const options = {
dryRun: args.includes('--dry-run') || args.includes('-d'),
verbose: args.includes('--verbose') || args.includes('-v'),
help: args.includes('--help') || args.includes('-h'),
};
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
magenta: '\x1b[35m',
} as const;
if (options.help) {
console.log(`
${colors.bright}${colors.blue}XcodeBuildMCP Tools Documentation Updater${colors.reset}
Automatically updates docs/TOOLS.md with current tool and workflow information.
${colors.bright}Usage:${colors.reset}
npx tsx scripts/update-tools-docs.ts [options]
${colors.bright}Options:${colors.reset}
--dry-run, -d Show what would be updated without making changes
--verbose, -v Show detailed information about the update process
--help, -h Show this help message
${colors.bright}Examples:${colors.reset}
${colors.cyan}npx tsx scripts/update-tools-docs.ts${colors.reset} # Update docs/TOOLS.md
${colors.cyan}npx tsx scripts/update-tools-docs.ts --dry-run${colors.reset} # Preview changes
${colors.cyan}npx tsx scripts/update-tools-docs.ts --verbose${colors.reset} # Show detailed progress
`);
process.exit(0);
}
/**
* Generate the workflow section content
*/
function generateWorkflowSection(workflow: WorkflowInfo): string {
const canonicalTools = workflow.tools.filter((tool) => tool.isCanonical);
const toolCount = canonicalTools.length;
let content = `### ${workflow.displayName} (\`${workflow.name}\`)\n`;
content += `**Purpose**: ${workflow.description} (${toolCount} tools)\n\n`;
// List each tool with its description
for (const tool of canonicalTools.sort((a, b) => a.name.localeCompare(b.name))) {
// Clean up the description for documentation
const cleanDescription = tool.description
.replace(/IMPORTANT:.*?Example:.*?\)/g, '') // Remove IMPORTANT sections
.replace(/\s+/g, ' ') // Normalize whitespace
.trim();
content += `- \`${tool.name}\` - ${cleanDescription}\n`;
}
return content;
}
/**
* Generate the complete TOOLS.md content
*/
function generateToolsDocumentation(analysis: StaticAnalysisResult): string {
const { workflows, stats } = analysis;
// Sort workflows by display name for consistent ordering
const sortedWorkflows = workflows.sort((a, b) => a.displayName.localeCompare(b.displayName));
const content = `# XcodeBuildMCP Tools Reference
XcodeBuildMCP provides ${stats.canonicalTools} tools organized into ${stats.workflowCount} workflow groups for comprehensive Apple development workflows.
## Workflow Groups
${sortedWorkflows.map((workflow) => generateWorkflowSection(workflow)).join('')}
## Summary Statistics
- **Total Tools**: ${stats.canonicalTools} canonical tools + ${stats.reExportTools} re-exports = ${stats.totalTools} total
- **Workflow Groups**: ${stats.workflowCount}
---
*This documentation is automatically generated by \`scripts/update-tools-docs.ts\` using static analysis. Last updated: ${new Date().toISOString().split('T')[0]}*
`;
return content;
}
/**
* Compare old and new content to show what changed
*/
function showDiff(oldContent: string, newContent: string): void {
if (!options.verbose) return;
console.log(`${colors.bright}${colors.cyan}📄 Content Comparison:${colors.reset}`);
console.log('─'.repeat(50));
const oldLines = oldContent.split('\n');
const newLines = newContent.split('\n');
const maxLength = Math.max(oldLines.length, newLines.length);
let changes = 0;
for (let i = 0; i < maxLength; i++) {
const oldLine = oldLines[i] || '';
const newLine = newLines[i] || '';
if (oldLine !== newLine) {
changes++;
if (changes <= 10) {
// Show first 10 changes
console.log(`${colors.red}- Line ${i + 1}: ${oldLine}${colors.reset}`);
console.log(`${colors.green}+ Line ${i + 1}: ${newLine}${colors.reset}`);
}
}
}
if (changes > 10) {
console.log(`${colors.yellow}... and ${changes - 10} more changes${colors.reset}`);
}
console.log(`${colors.blue}Total changes: ${changes} lines${colors.reset}\n`);
}
/**
* Main execution function
*/
async function main(): Promise<void> {
try {
console.log(
`${colors.bright}${colors.blue}🔧 XcodeBuildMCP Tools Documentation Updater${colors.reset}`,
);
if (options.dryRun) {
console.log(
`${colors.yellow}🔍 Running in dry-run mode - no files will be modified${colors.reset}`,
);
}
console.log(`${colors.cyan}📊 Analyzing tools...${colors.reset}`);
// Get current tool analysis
const analysis = await getStaticToolAnalysis();
if (options.verbose) {
console.log(
`${colors.green}✓ Found ${analysis.stats.canonicalTools} canonical tools in ${analysis.stats.workflowCount} workflows${colors.reset}`,
);
console.log(
`${colors.green}✓ Found ${analysis.stats.reExportTools} re-export files${colors.reset}`,
);
}
// Generate new documentation content
console.log(`${colors.cyan}📝 Generating documentation...${colors.reset}`);
const newContent = generateToolsDocumentation(analysis);
// Read current content for comparison
let oldContent = '';
if (fs.existsSync(docsPath)) {
oldContent = fs.readFileSync(docsPath, 'utf-8');
}
// Check if content has changed
if (oldContent === newContent) {
console.log(`${colors.green}✅ Documentation is already up to date!${colors.reset}`);
return;
}
// Show differences if verbose
if (oldContent && options.verbose) {
showDiff(oldContent, newContent);
}
if (options.dryRun) {
console.log(
`${colors.yellow}📋 Dry run completed. Documentation would be updated with:${colors.reset}`,
);
console.log(` - ${analysis.stats.canonicalTools} canonical tools`);
console.log(` - ${analysis.stats.workflowCount} workflow groups`);
console.log(` - ${newContent.split('\n').length} lines total`);
if (!options.verbose) {
console.log(`\n${colors.cyan}💡 Use --verbose to see detailed changes${colors.reset}`);
}
return;
}
// Write new content
console.log(`${colors.cyan}✏️ Writing updated documentation...${colors.reset}`);
fs.writeFileSync(docsPath, newContent, 'utf-8');
console.log(
`${colors.green}✅ Successfully updated ${path.relative(projectRoot, docsPath)}!${colors.reset}`,
);
if (options.verbose) {
console.log(`\n${colors.bright}📈 Update Summary:${colors.reset}`);
console.log(
` Tools: ${analysis.stats.canonicalTools} canonical + ${analysis.stats.reExportTools} re-exports = ${analysis.stats.totalTools} total`,
);
console.log(` Workflows: ${analysis.stats.workflowCount}`);
console.log(` File size: ${(newContent.length / 1024).toFixed(1)}KB`);
console.log(` Lines: ${newContent.split('\n').length}`);
}
} catch (error) {
console.error(`${colors.red}❌ Error: ${(error as Error).message}${colors.reset}`);
process.exit(1);
}
}
// Run the updater
main();