UNPKG

@entro314labs/at3-toolkit

Version:

Advanced development toolkit for AT3 Stack projects

386 lines (385 loc) 17.7 kB
#!/usr/bin/env node import * as clack from "@clack/prompts"; import { Command } from "commander"; import { readFileSync } from "fs"; import { dirname, resolve } from "path"; import { fileURLToPath } from "url"; import { ProjectDetector } from "./detection/detector.js"; import { MigrationRunner } from "./migration/runner.js"; import { banner, box, colors, layout, style, symbols, table } from "./utils/cli-styling.js"; import { Logger } from "./utils/logger.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Read package.json for version const packagePath = resolve(__dirname, "..", "package.json"); const packageJson = JSON.parse(readFileSync(packagePath, "utf8")); const program = new Command(); const logger = new Logger(); // Configure CLI program .name("at3t") .description(`${symbols.star} AT3T (AT3 Toolset) - Smart migration tool for AIT3E stack and modern web development`) .version(packageJson.version) .helpOption("-h, --help", "Display help for command") .addHelpText("before", banner.simple("AT3T (AT3 Toolset)", "Smart migration tool for AIT3E stack projects")); // Main migrate command program .argument("[project-path]", "Path to the project to migrate", process.cwd()) .option("-i, --interactive", "Show migration plan and ask for confirmation") .option("-o, --overwrite", "Overwrite existing config files without merging") .option("--no-deps", "Skip dependency installation") .option("-u, --update-versions", "Update dependency versions to AIT3E versions") .option("-r, --replace-linting", "Replace ESLint/Prettier with Biome") .option("-d, --dry-run", "Show what would be done without making changes") .option("-f, --force", "Skip safety confirmations") .option("-v, --verbose", "Enable verbose logging") .option("--config <path>", "Path to custom migration configuration") .option("--backup-dir <dir>", "Custom backup directory name") .action(async (projectPath, options) => { try { // Setup logger logger.setVerbose(options.verbose); // Start Clack intro with styled header clack.intro(style.title("AT3T (AT3 Toolset) - August 2025")); // Validate project path const resolvedPath = resolve(projectPath); // Detect project const s = clack.spinner(); s.start("Analyzing project structure..."); const detector = new ProjectDetector(logger); const projectInfo = await detector.detectProject(resolvedPath); s.stop(`${style.success(`Detected: ${projectInfo.type} project`)}`); // Show project info if not forced if (!options.force && !options.dryRun) { await showProjectInfoClack(projectInfo); } // Build migration options const migrationOptions = { projectPath: resolvedPath, interactive: options.interactive, overwrite: options.overwrite, skipDeps: options.noDeps, updateVersions: options.updateVersions, replaceLinting: options.replaceLinting, dryRun: options.dryRun, force: options.force, verbose: options.verbose, configPath: options.config, backupDir: options.backupDir, }; // Interactive mode if (options.interactive) { const responses = await clack.group({ confirm: () => clack.confirm({ message: `Migrate ${projectInfo.type} project to AIT3E stack?`, initialValue: true, }), updateVersions: () => clack.confirm({ message: "Update dependencies to latest AIT3E versions?", initialValue: migrationOptions.updateVersions || false, }), replaceLinting: () => clack.confirm({ message: "Replace ESLint/Prettier with Biome?", initialValue: migrationOptions.replaceLinting || false, }), skipDeps: () => clack.confirm({ message: "Skip dependency installation?", initialValue: migrationOptions.skipDeps || false, }), }, { onCancel: () => { clack.cancel("Migration cancelled."); process.exit(0); }, }); if (!responses.confirm) { clack.cancel("Migration cancelled by user."); process.exit(0); } // Update options from interactive responses migrationOptions.updateVersions = responses.updateVersions; migrationOptions.replaceLinting = responses.replaceLinting; migrationOptions.skipDeps = responses.skipDeps; } else if (!options.force && !options.dryRun) { // Simple confirmation for non-interactive mode const shouldContinue = await clack.confirm({ message: "This will modify your project files. Continue?", initialValue: false, }); if (clack.isCancel(shouldContinue) || !shouldContinue) { clack.cancel("Migration cancelled."); process.exit(0); } } // Run migration const runner = new MigrationRunner(logger); await runner.migrate(migrationOptions); // Success message with styled box console.log(box.success("Your project has been successfully migrated to the AIT3E stack!\n\n" + `${symbols.arrow} Next steps:\n` + " 1. Review the migration log in your project directory\n" + " 2. Test your application to ensure everything works\n" + " 3. Install any new dependencies with your package manager\n" + " 4. Commit your changes when ready\n\n" + `${symbols.info} Backup files are stored in .migration-backup/`, "Migration Complete")); clack.outro(style.success("Migration to AIT3E stack completed!")); } catch (error) { clack.log.error("Migration failed"); if (options.verbose && error instanceof Error) { clack.log.error(error.stack || error.message); } process.exit(1); } }); // Additional commands program .command("detect") .argument("[project-path]", "Path to analyze", process.cwd()) .description("Analyze project structure and detect AIT3E/AT3 compatibility") .option("-v, --verbose", "Show detailed analysis") .action(async (projectPath, options) => { try { clack.intro(style.title("Project Analysis & AIT3E Compatibility Check")); const detector = new ProjectDetector(new Logger(options.verbose)); const s = clack.spinner(); s.start("Analyzing project structure and dependencies..."); const info = await detector.detectProject(resolve(projectPath)); s.stop(style.success("Analysis complete")); // Display comprehensive project information await showProjectInfoClack(info); // Configuration files summary if (info.configFiles.length > 0) { const configTable = table.create(["Configuration File", "Type"]); info.configFiles.slice(0, 10).forEach((file) => { let type = "Configuration"; if (file.includes("next.config")) type = "Next.js"; else if (file.includes("tailwind")) type = "Tailwind CSS"; else if (file.includes("tsconfig")) type = "TypeScript"; else if (file.includes("biome") || file.includes("eslint")) type = "Linting"; else if (file.includes("test") || file.includes("jest") || file.includes("vitest")) type = "Testing"; configTable.push([style.path(file), style.muted(type)]); }); clack.log.message(""); clack.log.info(style.header("Configuration Files")); clack.log.info(configTable.toString()); if (info.configFiles.length > 10) { clack.log.info(style.muted(`... and ${info.configFiles.length - 10} more configuration files`)); } } // Final recommendation const isAIT3E = info.type === "ait3e"; const ait3eScore = [ info.hasAISupport, info.hasSupabase, info.hasEdgeRuntime, info.hasVectorDB, ].filter(Boolean).length; if (isAIT3E) { console.log(box.success(`${symbols.heart} This project is already using the AIT3E stack!\n\n` + "You have all the components for AI-native development:\n" + `${symbols.checkboxOn} Modern T3 foundation (Next.js + TypeScript + Tailwind)\n` + `${symbols.checkboxOn} AI integration capabilities\n` + `${symbols.checkboxOn} Edge-optimized infrastructure\n\n` + "Keep building amazing AI-powered applications!", "AIT3E Stack Detected")); } else if (ait3eScore >= 2) { console.log(box.info(`${symbols.star} You're ${ait3eScore}/4 of the way to AIT3E!\n\n` + "Run the following command to complete your migration:\n" + `${colors.primary("npx at3t --interactive --update-versions")}\n\n` + "This will add the missing AIT3E components and optimize your stack.", "Migration Recommended")); } else { console.log(box.note(`${symbols.info} Transform your project into an AI-native powerhouse!\n\n` + "The AIT3E stack will add:\n" + `${symbols.bullet} AI SDK integration (OpenAI, Anthropic, Google AI)\n` + `${symbols.bullet} Supabase backend with vector database\n` + `${symbols.bullet} Edge-optimized deployment\n` + `${symbols.bullet} Modern tooling and best practices\n\n` + `Get started: ${colors.primary("npx at3t --interactive")}`, "Ready for AIT3E Migration")); } clack.outro(style.success("Analysis complete")); } catch (error) { console.log(box.error(`Failed to analyze project: ${error instanceof Error ? error.message : String(error)}\n\n` + "Please check that the path exists and contains a valid Node.js project.", "Detection Error")); process.exit(1); } }); program .command("rollback") .argument("[project-path]", "Path to rollback", process.cwd()) .description("Rollback the last migration") .option("-f, --force", "Skip confirmation") .action(async (projectPath, options) => { try { clack.intro("🔄 Rollback Migration"); if (!options.force) { const shouldRollback = await clack.confirm({ message: "This will restore files from the last backup. Continue?", initialValue: false, }); if (clack.isCancel(shouldRollback) || !shouldRollback) { clack.cancel("Rollback cancelled."); process.exit(0); } } const s = clack.spinner(); s.start("Rolling back changes..."); const runner = new MigrationRunner(new Logger()); await runner.rollback(resolve(projectPath), options.force); s.stop("Rollback completed"); clack.outro("✅ Successfully rolled back to previous state"); } catch (error) { clack.log.error("Rollback failed"); if (error instanceof Error) { clack.log.error(error.message); } process.exit(1); } }); program .command("init") .argument("[project-name]", "Name of the new project") .description("Initialize a new project with AIT3E stack configuration") .option("--template <type>", "Project template (ait3e, nextjs, react, vue)", "ait3e") .action(async (projectName, options) => { try { clack.intro("🚀 Initialize AIT3E Project"); // Get project details interactively const projectDetails = await clack.group({ name: () => clack.text({ message: "Project name?", initialValue: projectName || "my-ait3e-app", validate: (value) => { if (!value) return "Project name is required"; if (!/^[a-z0-9-]+$/.test(value)) return "Use lowercase letters, numbers, and hyphens only"; return undefined; }, }), template: () => clack.select({ message: "Choose a template:", initialValue: options.template, options: [ { value: "ait3e", label: "AIT3E Stack (AI + T3 + Edge)", hint: "Recommended" }, { value: "nextjs", label: "Next.js with AIT3E setup" }, { value: "react", label: "React with AI utilities" }, ], }), features: () => clack.multiselect({ message: "Select additional features:", options: [ { value: "auth", label: "Authentication (Supabase Auth)" }, { value: "database", label: "Database setup (PostgreSQL)" }, { value: "vectors", label: "Vector database (Embeddings)" }, { value: "analytics", label: "Analytics (Vercel Analytics)" }, ], required: false, }), }, { onCancel: () => { clack.cancel("Project initialization cancelled."); process.exit(0); }, }); const s = clack.spinner(); s.start(`Creating ${projectDetails.template} project: ${projectDetails.name}...`); // TODO: Implementation for creating new projects await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulate project creation s.stop(`Project ${projectDetails.name} created successfully`); clack.note(`Next steps:\n\n cd ${projectDetails.name}\n pnpm install\n pnpm dev\n\n Visit http://localhost:3000 to see your app!`, "Get started"); clack.outro("🎉 Your AIT3E project is ready to go!"); } catch (error) { clack.log.error("Project initialization failed"); if (error instanceof Error) { clack.log.error(error.message); } process.exit(1); } }); async function showProjectInfoClack(projectInfo) { // Project basic info const projectData = [ { key: "Path:", value: style.path(projectInfo.path) }, { key: "Type:", value: style.key(projectInfo.type) }, { key: "Package Manager:", value: style.value(projectInfo.packageManager) }, ]; clack.log.message(style.header("Project Analysis")); clack.log.info(""); clack.log.info(layout.columns(projectData, 18)); // Dependencies table if (projectInfo.dependencies.length > 0) { const depsTable = table.dependencies(); projectInfo.dependencies.slice(0, 5).forEach((dep) => { const status = dep.latest && dep.version !== dep.latest ? style.warning("outdated") : style.success("current"); depsTable.push([ style.key(dep.name), style.version(dep.version), style.version(dep.latest || dep.version), status, ]); }); clack.log.message(""); clack.log.info(depsTable.toString()); if (projectInfo.dependencies.length > 5) { clack.log.info(style.muted(`... and ${projectInfo.dependencies.length - 5} more dependencies`)); } } // AIT3E features table const featuresTable = table.features(); const features = [ { key: "AI Integration", status: projectInfo.hasAISupport, desc: "OpenAI, Anthropic, Google AI", }, { key: "Supabase Backend", status: projectInfo.hasSupabase, desc: "Database, Auth, Real-time" }, { key: "Edge Runtime", status: projectInfo.hasEdgeRuntime, desc: "Serverless deployment" }, { key: "Vector Database", status: projectInfo.hasVectorDB, desc: "Embeddings and search" }, ]; features.forEach(({ key, status, desc }) => { const statusText = status ? style.success("Detected") : style.muted("Missing"); featuresTable.push([status ? style.ai(key) : style.muted(key), statusText, style.muted(desc)]); }); clack.log.message(""); clack.log.info(style.header("AIT3E Stack Compatibility")); clack.log.info(featuresTable.toString()); // Summary message const hasAnyAIT3EFeatures = projectInfo.hasAISupport || projectInfo.hasSupabase || projectInfo.hasEdgeRuntime || projectInfo.hasVectorDB; if (hasAnyAIT3EFeatures) { clack.log.message(""); clack.log.info(style.ai("Some AIT3E features detected! This migration will complete your stack.")); } else { clack.log.message(""); clack.log.info(style.info("No AIT3E features detected - this migration will transform your project!")); } } // Handle uncaught errors process.on("uncaughtException", (error) => { clack.log.error("Uncaught exception:"); clack.log.error(error.message); process.exit(1); }); process.on("unhandledRejection", (reason) => { clack.log.error("Unhandled rejection:"); clack.log.error(String(reason)); process.exit(1); }); // Parse CLI arguments program.parse();