#!/usr/bin/env node
import * as fs from "node:fs";
import * as path from "node:path";
import chalk from "chalk";
import { Command } from "commander";
import { ConfigLoader } from "./config/config-loader";
import { ProjectBuilder } from "./core/ProjectBuilder";
import { type WorkflowConfig, WorkflowManager } from "./core/WorkflowManager";
import type { CireConfig } from "./types";
import { logger } from "./utils/logger";
const program = new Command();
program
.name("cire")
.description(
"Static website generator providing IDE-like experiences for documentation",
)
.version("0.1.0");
// Smart mode - auto-detect single file vs project mode
program
.option("-i, --input <file>", "Input source code file (single file mode)")
.option(
"-o, --output <file|dir>",
"Output file (single file) or directory (project mode)",
)
.option(
"-c, --config <path>",
"Path to .cire configuration file (project mode)",
)
.option("-s, --scip <path>", "SCIP index file path for hover documentation")
.option(
"-l, --language <lang>",
"Language (default: typescript)",
"typescript",
)
.option("--no-highlight", "Disable syntax highlighting")
.option("--no-hover", "Disable hover documentation")
.option("--no-comment-markdown", "Disable comment-to-markdown conversion")
.option("-v, --verbose", "Enable verbose logging")
.action(async (options) => {
try {
// Smart mode detection
if (options.config) {
// Project mode explicitly requested
const config = await new ConfigLoader().loadConfig(
options.config,
);
logger.setLevel(config.logLevel);
await runProjectMode(config);
return;
}
if (options.input) {
// Single file mode explicitly requested
await runSingleFileMode(options);
return;
}
// Auto-detection mode
await autoDetectAndRun(options);
} catch (error) {
console.error(
chalk.red.bold("❌ Error:"),
error instanceof Error ? error.message : error,
);
process.exit(1);
}
});
Run in project mode using ProjectBuilder
async function runProjectMode(config: CireConfig): Promise<void> {
console.log(chalk.blue.bold("🔨 Cire Project Mode Started"));
const builder = new ProjectBuilder(config);
if (config.logLevel === "debug") {
console.log(chalk.gray(`Using config: `));
}
const stats = await builder.buildProject();
// Show results
console.log();
console.log(chalk.green.bold("🎉 Build completed successfully!"));
console.log(chalk.cyan(`📊 Processed files`));
if (stats.failedFiles > 0) {
console.log(
chalk.yellow(`! files failed to process`),
);
}
console.log(
chalk.cyan(
`⏱ Processing time: s`,
),
);
console.log(chalk.cyan(`📁 Total files found: `));
const outputDir = config.output.directory;
console.log();
console.log(chalk.blue.bold("📂 Output directory:"));
console.log(chalk.cyan(` `));
}
Run in single file mode using WorkflowManager
async function runSingleFileMode(options: any): Promise<void> {
// Resolve input file path
const inputFile = path.resolve(options.input);
// Check if input file exists
if (!fs.existsSync(inputFile)) {
console.error(chalk.red.bold(`❌ Input file not found: `));
process.exit(1);
}
// Determine output file path
const outputFile = options.output
? path.resolve(options.output)
: path.resolve(
path.dirname(inputFile),
`.html`,
);
// Validate SCIP index if hover is enabled
if (options.hover && options.scip && !fs.existsSync(options.scip)) {
console.error(
chalk.red.bold(`❌ SCIP index file not found: `),
);
process.exit(1);
}
// Create workflow configuration
const config: WorkflowConfig = {
syntaxHighlighting: options.highlight,
hoverDocumentation: options.hover && !!options.scip,
commentToMarkdown: options.commentMarkdown,
scipIndexPath: options.scip,
language: options.language,
};
// Initialize workflow manager
const workflow = new WorkflowManager(config);
if (options.verbose) {
console.log(
chalk.blue.bold("🚀 Starting Cire single file analysis pipeline"),
);
console.log(chalk.gray(`📁 Input: `));
console.log(chalk.gray(`📝 Output: `));
console.log(chalk.gray(`🔤 Language: `));
console.log(
chalk.gray(`✨ Syntax Highlighting: `),
);
console.log(
chalk.gray(`💬 Comment-to-Markdown: `),
);
console.log(
chalk.gray(`🔍 Hover Documentation: `),
);
if (config.scipIndexPath) {
console.log(chalk.gray(`📊 SCIP Index: `));
}
const stats = workflow.getStats();
console.log(
chalk.gray(
`🔧 TSHighlighter: `,
),
);
console.log(
chalk.gray(`🔧 SCIPAnalyzer: `),
);
console.log();
}
// Create FileIR with relative path (for single file, just the filename)
const fileIR = {
relativePath: path.basename(inputFile),
language: options.language,
};
// For single file mode, use input file's directory as projectRoot
const projectRoot = path.dirname(inputFile);
// Process file through workflow
const html = workflow.processFile(fileIR, projectRoot);
// Write output file
console.log(chalk.blue(`💾 Writing HTML...`));
fs.writeFileSync(outputFile, html);
console.log(chalk.green.bold(`🎉 Successfully generated documentation!`));
console.log(
chalk.cyan(`📁 Open in your browser to view the result.`),
);
// Show feature summary
const stats = workflow.getStats();
console.log();
console.log(chalk.blue.bold("🎯 Features included:"));
if (stats.syntaxHighlighter) {
console.log(chalk.green(` ✓ Syntax highlighting (Tree-sitter)`));
}
if (config.commentToMarkdown) {
console.log(chalk.green(` ✓ Block comment to Markdown conversion`));
}
if (stats.scipAnalyzer) {
console.log(chalk.green(` ✓ Hover documentation (SCIP)`));
}
console.log(chalk.green(` ✓ Token processing pipeline`));
console.log(chalk.green(` ✓ Modern HTML output`));
}
Auto-detect mode and run appropriate handler
async function autoDetectAndRun(options: any): Promise<void> {
// Check for common config files
const configFiles = [
options.config || ".cire.json5",
".cire",
".cire.json",
"cire.json5",
"cire.json",
];
for (const configFile of configFiles) {
if (fs.existsSync(configFile)) {
if (options.verbose) {
console.log(chalk.gray(`🔍 Found config file: `));
console.log(chalk.blue("🏗 Switching to project mode..."));
}
const config = await new ConfigLoader().loadConfig(configFile);
logger.setLevel(config.logLevel);
await runProjectMode(config);
return;
}
}
// No config found, show error
console.error(
chalk.red.bold("❌ No configuration found and no input file specified"),
);
console.error();
console.error(chalk.cyan("Usage options:"));
console.error(chalk.cyan(" Single file mode: cire -i <source-file>"));
console.error(chalk.cyan(" Project mode: cire -c <config-file>"));
console.error(
chalk.cyan(" Auto-detect: cire (when .cire.json5 exists)"),
);
console.error();
console.error(
chalk.gray("Create a .cire.json5 file to enable project mode"),
);
process.exit(1);
}
// Parse command line arguments
program.parse();
// Handle unknown commands
program.on("command:*", () => {
console.error(
chalk.red.bold("❌ Invalid command:"),
program.args.join(" "),
);
console.log("See --help for a list of available commands.");
process.exit(1);
});
Cire CLI - Command Line Interface for Cire Static Website Generator