UNPKG

@entro314labs/at3-stack-kit

Version:

Upgrade existing projects to AT3 Stack with intelligent migration

896 lines (839 loc) 28.1 kB
#!/usr/bin/env node #!/usr/bin/env node var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/cli.ts import { cancel, confirm, intro, isCancel, multiselect, note, outro, spinner } from "@clack/prompts"; import chalk2 from "chalk"; import { program } from "commander"; import { detect as detectPackageManager2 } from "detect-package-manager"; import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs-extra"; import { join as join6 } from "path"; // src/detect.ts import { detect as detectPackageManager } from "detect-package-manager"; import { existsSync, readFileSync } from "fs-extra"; import { join } from "path"; async function detectProjectType(projectPath) { const packageJsonPath = join(projectPath, "package.json"); if (!existsSync(packageJsonPath)) { return "unknown"; } try { const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; const hasAI = deps.ai || deps["@ai-sdk/openai"] || deps["@ai-sdk/anthropic"] || deps["@ai-sdk/google"]; const hasSupabase = deps["@supabase/supabase-js"] || deps["@supabase/ssr"]; const hasNextJS = deps.next; const hasTailwind = deps.tailwindcss; const hasTypeScript = deps.typescript || existsSync(join(projectPath, "tsconfig.json")); if (hasAI && hasSupabase && hasNextJS && (hasTailwind || hasTypeScript)) { return "ait3e"; } if (deps["@t3-oss/create-t3-app"] || deps.next && deps["@trpc/server"] && deps.prisma) { return "t3"; } if (deps.next) return "nextjs"; if (deps.nuxt || deps["@nuxt/core"]) return "nuxt"; if (deps.vue) return "vue"; if (deps.react) return "react"; if (deps.vite) return "vite"; if (deps.webpack) return "webpack"; return "node"; } catch (error) { return "unknown"; } } // src/features/add-ai.ts import { join as join2 } from "path"; import { ensureDir, pathExists, readFile, writeFile } from "fs-extra"; async function addAI(type, projectPath) { const srcPath = join2(projectPath, "src"); await ensureDir(srcPath); const aiPath = join2(srcPath, "lib", "ai"); await ensureDir(aiPath); if (type === "custom") { await addCustomAI(aiPath); } if (type === "vercel") { await addVercelAI(aiPath); } const apiPath = join2(srcPath, "app", "api"); await ensureDir(apiPath); await addAPIRoutes(apiPath, type); await updatePackageJson(projectPath, type); } async function addCustomAI(aiPath) { const clientConfig = `/** * Custom AI client configuration */ export interface AIProvider { name: string; baseURL: string; apiKey: string; } export const AI_PROVIDERS = { openai: { name: 'OpenAI', baseURL: 'https://api.openai.com/v1', apiKey: process.env.OPENAI_API_KEY!, }, anthropic: { name: 'Anthropic', baseURL: 'https://api.anthropic.com', apiKey: process.env.ANTHROPIC_API_KEY!, }, google: { name: 'Google AI', baseURL: 'https://generativelanguage.googleapis.com', apiKey: process.env.GOOGLE_AI_API_KEY!, }, } as const; export type AIProviderKey = keyof typeof AI_PROVIDERS; `; await writeFile(join2(aiPath, "config.ts"), clientConfig); const client = `/** * Custom AI client implementation */ import { AI_PROVIDERS, type AIProviderKey } from './config.js'; export interface ChatMessage { role: 'system' | 'user' | 'assistant'; content: string; } export interface CompletionOptions { model: string; temperature?: number; maxTokens?: number; } export class AIClient { constructor( private provider: AIProviderKey, private options: CompletionOptions = {} ) {} async completion( messages: ChatMessage[], options: Partial<CompletionOptions> = {} ): Promise<string> { const config = AI_PROVIDERS[this.provider]; const finalOptions = { ...this.options, ...options }; // Implementation would depend on the provider // This is a basic structure const response = await fetch(\`\${config.baseURL}/chat/completions\`, { method: 'POST', headers: { 'Authorization': \`Bearer \${config.apiKey}\`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: finalOptions.model || 'gpt-3.5-turbo', messages, temperature: finalOptions.temperature || 0.7, max_tokens: finalOptions.maxTokens || 1000, }), }); const data = await response.json(); return data.choices[0]?.message?.content || ''; } } `; await writeFile(join2(aiPath, "client.ts"), client); } async function addVercelAI(aiPath) { const vercelClient = `/** * Vercel AI SDK integration */ import { anthropic } from '@ai-sdk/anthropic'; import { google } from '@ai-sdk/google'; import { openai } from '@ai-sdk/openai'; import { generateText, streamText } from 'ai'; export const AI_MODELS = { 'gpt-4-turbo': openai('gpt-4-turbo'), 'gpt-3.5-turbo': openai('gpt-3.5-turbo'), 'claude-3-haiku': anthropic('claude-3-haiku-20240307'), 'claude-3-sonnet': anthropic('claude-3-sonnet-20240229'), 'gemini-pro': google('models/gemini-pro'), } as const; export type AIModelKey = keyof typeof AI_MODELS; export async function generateCompletion( model: AIModelKey, prompt: string, options?: { temperature?: number; maxTokens?: number; } ) { const { text } = await generateText({ model: AI_MODELS[model], prompt, temperature: options?.temperature || 0.7, maxTokens: options?.maxTokens || 1000, }); return text; } export async function streamCompletion( model: AIModelKey, prompt: string, options?: { temperature?: number; maxTokens?: number; } ) { const { textStream } = await streamText({ model: AI_MODELS[model], prompt, temperature: options?.temperature || 0.7, maxTokens: options?.maxTokens || 1000, }); return textStream; } `; await writeFile(join2(aiPath, "vercel-client.ts"), vercelClient); const hooks = `/** * React hooks for Vercel AI SDK */ 'use client'; import { useChat, useCompletion } from 'ai/react'; import { type AIModelKey } from './vercel-client.js'; export function useAIChat(model: AIModelKey = 'gpt-3.5-turbo') { return useChat({ api: '/api/chat', body: { model, }, }); } export function useAICompletion(model: AIModelKey = 'gpt-3.5-turbo') { return useCompletion({ api: '/api/completion', body: { model, }, }); } `; await writeFile(join2(aiPath, "vercel-hooks.ts"), hooks); } async function addAPIRoutes(apiPath, type) { if (type === "vercel") { const chatRoute = `import { AI_MODELS, type AIModelKey } from '@/lib/ai/vercel-client'; import { streamText } from 'ai'; export async function POST(req: Request) { const { messages, model = 'gpt-3.5-turbo' } = await req.json(); const result = await streamText({ model: AI_MODELS[model as AIModelKey], messages, }); return result.toDataStreamResponse(); } `; await ensureDir(join2(apiPath, "chat")); await writeFile(join2(apiPath, "chat", "route.ts"), chatRoute); const completionRoute = `import { AI_MODELS, type AIModelKey } from '@/lib/ai/vercel-client'; import { generateText } from 'ai'; export async function POST(req: Request) { const { prompt, model = 'gpt-3.5-turbo' } = await req.json(); const { text } = await generateText({ model: AI_MODELS[model as AIModelKey], prompt, }); return Response.json({ text }); } `; await ensureDir(join2(apiPath, "completion")); await writeFile(join2(apiPath, "completion", "route.ts"), completionRoute); } } async function updatePackageJson(projectPath, type) { const packageJsonPath = join2(projectPath, "package.json"); if (!await pathExists(packageJsonPath)) return; const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8")); if (!packageJson.dependencies) packageJson.dependencies = {}; if (type === "vercel") { packageJson.dependencies.ai = "^5.0.8"; packageJson.dependencies["@ai-sdk/openai"] = "^2.0.7"; packageJson.dependencies["@ai-sdk/anthropic"] = "^2.0.1"; packageJson.dependencies["@ai-sdk/google"] = "^2.0.3"; } await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); } // src/features/add-i18n.ts function addI18n(_projectPath) { console.log("\u2713 i18n support would be added here"); } // src/features/add-pwa.ts import { join as join3 } from "path"; import { ensureDir as ensureDir2, writeFile as writeFile2 } from "fs-extra"; async function addPWA(projectPath) { await addManifest(projectPath); await addServiceWorker(projectPath); await updatePackageJson2(projectPath); console.log("\u2713 PWA support added"); } async function addManifest(projectPath) { const publicPath = join3(projectPath, "public"); await ensureDir2(publicPath); const manifest = { name: "AT3 App", short_name: "AT3", description: "AT3 Stack Application", start_url: "/", display: "standalone", background_color: "#ffffff", theme_color: "#000000", icons: [ { src: "/icons/icon-192x192.png", sizes: "192x192", type: "image/png" }, { src: "/icons/icon-512x512.png", sizes: "512x512", type: "image/png" } ] }; await writeFile2(join3(publicPath, "manifest.json"), JSON.stringify(manifest, null, 2)); } async function addServiceWorker(projectPath) { const publicPath = join3(projectPath, "public"); const sw = `// Basic service worker for PWA self.addEventListener('install', (event) => { console.log('Service worker installing...'); self.skipWaiting(); }); self.addEventListener('activate', (event) => { console.log('Service worker activating...'); event.waitUntil(self.clients.claim()); }); self.addEventListener('fetch', (event) => { // Add caching logic here }); `; await writeFile2(join3(publicPath, "sw.js"), sw); } function updatePackageJson2(_projectPath) { console.log("PWA package.json updates would go here"); } // src/features/add-supabase.ts import { ensureDir as ensureDir3, pathExists as pathExists2, readFile as readFile2, writeFile as writeFile3 } from "fs-extra"; import { join as join4 } from "path"; async function addSupabase(projectPath) { const srcPath = join4(projectPath, "src"); await ensureDir3(srcPath); const supabasePath = join4(srcPath, "lib", "supabase"); await ensureDir3(supabasePath); await addSupabaseClients(supabasePath); await addAuthHelpers(join4(srcPath, "lib", "auth")); await updatePackageJson3(projectPath); await addEnvExample(projectPath); await initSupabaseConfig(projectPath); } async function addSupabaseClients(supabasePath) { const clientCode = `import { createBrowserClient } from '@supabase/ssr'; export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ); } `; await writeFile3(join4(supabasePath, "client.ts"), clientCode); const serverCode = `import { createServerClient } from '@supabase/ssr'; import { cookies } from 'next/headers'; export async function createServerClient() { const cookieStore = await cookies(); return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return cookieStore.getAll(); }, setAll(cookiesToSet) { try { cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options) ); } catch { // The 'setAll' method was called from a Server Component. // This can be ignored if you have middleware refreshing // user sessions. } }, }, } ); } `; await writeFile3(join4(supabasePath, "server.ts"), serverCode); const middlewareCode = `import { createServerClient } from '@supabase/ssr'; import { NextResponse, type NextRequest } from 'next/server'; export async function updateSession(request: NextRequest) { let supabaseResponse = NextResponse.next({ request, }); const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return request.cookies.getAll(); }, setAll(cookiesToSet) { cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value)); supabaseResponse = NextResponse.next({ request, }); cookiesToSet.forEach(({ name, value, options }) => supabaseResponse.cookies.set(name, value, options) ); }, }, } ); // This will refresh session if expired - required for Server Components await supabase.auth.getUser(); return supabaseResponse; } `; await writeFile3(join4(supabasePath, "middleware.ts"), middlewareCode); } async function addAuthHelpers(authPath) { await ensureDir3(authPath); const authHelpersCode = `import { createServerClient } from '@/lib/supabase/server'; import { redirect } from 'next/navigation'; export async function requireAuth() { const supabase = await createServerClient(); const { data: { user }, error } = await supabase.auth.getUser(); if (error || !user) { redirect('/auth/sign-in'); } return user; } export async function getUser() { const supabase = await createServerClient(); const { data: { user } } = await supabase.auth.getUser(); return user; } export async function signOut() { const supabase = await createServerClient(); await supabase.auth.signOut(); redirect('/'); } `; await writeFile3(join4(authPath, "auth-helpers.ts"), authHelpersCode); } async function updatePackageJson3(projectPath) { const packageJsonPath = join4(projectPath, "package.json"); if (!await pathExists2(packageJsonPath)) return; const packageJson = JSON.parse(await readFile2(packageJsonPath, "utf-8")); if (!packageJson.dependencies) packageJson.dependencies = {}; if (!packageJson.devDependencies) packageJson.devDependencies = {}; packageJson.dependencies["@supabase/supabase-js"] = "^2.54.0"; packageJson.dependencies["@supabase/ssr"] = "^0.7.0-rc.2"; packageJson.devDependencies["supabase"] = "^2.33.9"; await writeFile3(packageJsonPath, JSON.stringify(packageJson, null, 2)); } async function addEnvExample(projectPath) { const envExamplePath = join4(projectPath, ".env.example"); let envContent = ""; if (await pathExists2(envExamplePath)) { envContent = await readFile2(envExamplePath, "utf-8"); } if (!envContent.includes("NEXT_PUBLIC_SUPABASE_URL")) { const supabaseVars = ` # Supabase NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key SUPABASE_SERVICE_ROLE_KEY=your_service_role_key `; envContent = envContent + supabaseVars; await writeFile3(envExamplePath, envContent); } } async function initSupabaseConfig(projectPath) { const supabaseConfigPath = join4(projectPath, "supabase", "config.toml"); if (await pathExists2(supabaseConfigPath)) return; await ensureDir3(join4(projectPath, "supabase")); const config = `# A string used to distinguish different Supabase projects on the same host. project_id = "your-project-id" [api] enabled = true port = 54321 schemas = ["public", "graphql_public"] extra_search_path = ["public", "extensions"] max_rows = 1000 [auth] enabled = true port = 9999 site_url = "http://localhost:3000" additional_redirect_urls = ["https://localhost:3000"] jwt_expiry = 3600 refresh_token_rotation_enabled = true security_update_password_require_reauthentication = true [auth.email] enable_signup = true double_confirm_changes = true enable_confirmations = false `; await writeFile3(supabaseConfigPath, config); } // src/features/add-testing.ts function addTesting(_projectPath) { console.log("\u2713 Testing suite would be added here"); } // src/utils/cli-styling.ts import boxen from "boxen"; import chalk from "chalk"; import figures from "figures"; import gradient from "gradient-string"; var colors = { primary: chalk.hex("#2563eb"), // Blue secondary: chalk.hex("#7c3aed"), // Purple success: chalk.green, error: chalk.red, warning: chalk.yellow, info: chalk.blue, muted: chalk.gray, accent: chalk.magenta, ai: chalk.hex("#10b981") // Emerald for AI features }; var gradients = { at3: gradient("#2563eb", "#10b981"), // Blue to Emerald ai: gradient("#10b981", "#059669"), // Emerald gradient success: gradient("#22c55e", "#16a34a"), // Green gradient warning: gradient("#f59e0b", "#d97706"), // Amber gradient error: gradient("#ef4444", "#dc2626") // Red gradient }; var symbols = { success: colors.success(figures.tick), error: colors.error(figures.cross), warning: colors.warning(figures.warning), info: colors.info(figures.info), arrow: colors.muted(figures.arrowRight), bullet: colors.muted(figures.bullet), line: colors.muted(figures.line), ai: colors.ai("\u{1F916}"), stack: colors.primary("\u{1F4DA}"), edge: colors.secondary("\u26A1"), database: colors.info("\u{1F5C4}\uFE0F"), config: colors.muted("\u2699\uFE0F") }; var style = { title: (text) => gradients.at3(text), subtitle: (text) => colors.secondary(text), heading: (text) => colors.primary.bold(text), label: (text) => colors.muted(text), value: (text) => colors.primary(text), success: (text) => colors.success(text), error: (text) => colors.error(text), warning: (text) => colors.warning(text), info: (text) => colors.info(text), muted: (text) => colors.muted(text), accent: (text) => colors.accent(text), ai: (text) => colors.ai(text), code: (text) => chalk.cyan.italic(text), path: (text) => chalk.dim.underline(text), command: (text) => chalk.bgBlack.white.bold(` ${text} `) }; var header = { main: (title, subtitle) => { const titleText = gradients.at3.multiline(title); const subtitleText = subtitle ? ` ${colors.muted(subtitle)}` : ""; return `${titleText}${subtitleText}`; }, section: (title) => ` ${colors.primary.bold(title)} ${colors.muted("\u2500".repeat(title.length))}` }; // src/utils/integration.ts import { existsSync as existsSync2 } from "fs-extra"; import { join as join5 } from "path"; function updateAT3Config(projectPath, addedFeatures) { const configPath = join5(projectPath, ".at3-config.json"); let config; if (existsSync2(configPath)) { try { config = JSON.parse(__require("fs").readFileSync(configPath, "utf8")); } catch { config = createDefaultConfig(); } } else { config = createDefaultConfig(); } config.features = [.../* @__PURE__ */ new Set([...config.features, ...addedFeatures])]; config.toolsUsed = [.../* @__PURE__ */ new Set([...config.toolsUsed, "at3-stack-kit"])]; config.lastMigration = (/* @__PURE__ */ new Date()).toISOString(); try { __require("fs").writeFileSync(configPath, JSON.stringify(config, null, 2)); } catch { } return config; } function createDefaultConfig() { return { version: "0.1.0", created: (/* @__PURE__ */ new Date()).toISOString(), features: [], toolsUsed: ["at3-stack-kit"] }; } function suggestAT3Tools(projectInfo, addedFeatures) { const suggestions = []; if (!projectInfo.hasEslint || !projectInfo.hasPrettier || !projectInfo.hasVitest) { suggestions.push( `${colors.info("\u{1F4A1} Tip:")} Use ${style.command("@entro314-labs/at3t")} for advanced linting, testing, and development workflow optimization` ); } if (addedFeatures.length > 0) { suggestions.push( `${colors.info("\u{1F4A1} Tip:")} For new projects, use ${style.command("create-at3-app")} to start with the AT3 stack from the beginning` ); } return suggestions; } function getPostMigrationWorkflow(projectInfo, addedFeatures) { const workflows = []; if (addedFeatures.includes("ai")) { workflows.push("Configure your AI provider API keys in .env.local"); workflows.push("Explore the AI integration examples in your project"); } if (addedFeatures.includes("supabase")) { workflows.push("Set up your Supabase project and update connection strings"); workflows.push("Run database migrations if any were added"); } if (addedFeatures.includes("testing")) { workflows.push( `Run ${style.command(projectInfo.packageManager + " test")} to verify your test setup` ); } if (projectInfo.type === "ait3e") { workflows.push("Your project now includes the complete AT3 stack!"); workflows.push("Consider using at3t for advanced development workflow optimization"); } return workflows; } // src/cli.ts var features = [ { id: "ai-custom", name: "AI Integration (Custom)", description: "Add custom AI integration with multiple providers", value: "ai-custom" }, { id: "ai-vercel", name: "AI Integration (Vercel SDK)", description: "Add Vercel AI SDK for streaming responses", value: "ai-vercel" }, { id: "supabase", name: "Supabase", description: "Add Supabase for database, auth, and edge functions", value: "supabase" }, { id: "pwa", name: "PWA Support", description: "Add Progressive Web App features", value: "pwa" }, { id: "i18n", name: "Internationalization", description: "Add next-intl for multi-language support", value: "i18n" }, { id: "testing", name: "Testing Suite", description: "Add Vitest and Playwright for comprehensive testing", value: "testing" } ]; async function main() { console.clear(); intro(style.title("AT3 Stack Kit")); const s = spinner(); s.start("Analyzing your project..."); const projectType = await detectProjectType(process.cwd()); const packageManager = await detectPackageManager2().catch(() => "npm"); s.stop( `${symbols.success} Detected: ${style.value(getProjectTypeLabel(projectType))} project using ${style.accent(packageManager)}` ); if (projectType === "unknown") { cancel( "This doesn't appear to be a supported project type. AT3 Kit supports Next.js, T3, and React projects." ); process.exit(1); } note(getProjectInfo(projectType), "Current Project"); const selectedFeatures = await multiselect({ message: "Which features would you like to add?", options: features.map((feature) => ({ value: feature.value, label: feature.name, hint: feature.description })), required: true }); if (isCancel(selectedFeatures) || selectedFeatures.length === 0) { cancel("No features selected."); process.exit(0); } const shouldInstall = await confirm({ message: `Install dependencies with ${packageManager}?`, initialValue: true }); if (isCancel(shouldInstall)) { cancel("Operation cancelled."); process.exit(0); } const addSpinner = spinner(); try { for (const featureId of selectedFeatures) { addSpinner.start(`Adding ${getFeatureName(featureId)}...`); switch (featureId) { case "ai-custom": await addAI("custom", process.cwd()); break; case "ai-vercel": await addAI("vercel", process.cwd()); break; case "supabase": await addSupabase(process.cwd()); break; case "pwa": await addPWA(process.cwd()); break; case "i18n": await addI18n(process.cwd()); break; case "testing": await addTesting(process.cwd()); break; } addSpinner.stop(`\u2713 Added ${getFeatureName(featureId)}`); } if (shouldInstall) { addSpinner.start(`Installing dependencies with ${packageManager}...`); addSpinner.stop("\u2713 Dependencies installed"); } const projectInfo = { type: projectType, packageManager }; const addedFeatures = selectedFeatures; updateAT3Config(process.cwd(), addedFeatures); const toolSuggestions = suggestAT3Tools(projectInfo, addedFeatures); const workflows = getPostMigrationWorkflow(projectInfo, addedFeatures); outro(style.success("\u{1F389} Your project has been upgraded to AT3 Stack!")); const nextSteps = [ "Review the added files and configurations", "Update your .env.local with required API keys", `Run ${style.command(packageManager + " dev")} to test your upgraded project`, ...workflows ]; note( ` ${header.section("Next steps:")} ${nextSteps.map((step, i) => ` ${colors.muted(`${i + 1}.`)} ${step}`).join("\n")} ${colors.primary("Documentation:")} ${colors.muted("\u2022")} AT3 Stack Guide: https://at3-stack.dev/docs ${colors.muted("\u2022")} Feature Guides: https://at3-stack.dev/docs/features ${toolSuggestions.length > 0 ? `${colors.primary("AT3 Ecosystem:")} ${toolSuggestions.map((s2) => `${colors.muted("\u2022")} ${s2}`).join("\n ")} ` : ""}${colors.primary("Need help?")} ${colors.muted("\u2022")} GitHub Issues: https://github.com/entro314-labs/at3-stack-kit/issues ${colors.muted("\u2022")} Discord: https://discord.gg/at3-stack `, "Welcome to AT3!" ); } catch (error) { addSpinner.stop("Error occurred"); console.error(chalk2.red("Error upgrading project:"), error); process.exit(1); } } function getProjectTypeLabel(type) { switch (type) { case "nextjs": return "Next.js"; case "t3": return "T3 Stack"; case "react": return "React"; case "unknown": return "Unknown"; default: return "Unknown"; } } function getProjectInfo(type) { const packageJsonPath = join6(process.cwd(), "package.json"); if (!existsSync3(packageJsonPath)) { return "No package.json found"; } const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8")); const deps = Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }); let info = `Project: ${packageJson.name || "unnamed"} `; info += `Type: ${getProjectTypeLabel(type)} `; const keyDeps = ["next", "react", "typescript", "tailwindcss"]; const foundDeps = keyDeps.filter((dep) => deps.some((d) => d.includes(dep))); if (foundDeps.length > 0) { info += `Dependencies: ${foundDeps.join(", ")}`; } return info; } function getFeatureName(id) { return features.find((f) => f.value === id)?.name || id; } program.name("at3-kit").description("Upgrade existing projects to AT3 Stack").version("0.1.0").option("-d, --dry-run", "Show what would be changed without making changes").option("--no-install", "Skip installing dependencies").action(async (options) => { await main(); }); program.command("add <feature>").description("Add a specific feature to your project").option("--no-install", "Skip installing dependencies").action(async (feature, options) => { const validFeatures = features.map((f) => f.value); if (!validFeatures.includes(feature)) { console.error(chalk2.red(`Unknown feature: ${feature}`)); console.log(chalk2.dim(`Valid features: ${validFeatures.join(", ")}`)); process.exit(1); } console.log(chalk2.blue(`Adding ${getFeatureName(feature)}...`)); }); program.command("detect").description("Detect current project type and show available upgrades").action(async () => { const projectType = await detectProjectType(process.cwd()); const packageManager = await detectPackageManager2().catch(() => "npm"); console.log(chalk2.green("Project Analysis:")); console.log(` Type: ${getProjectTypeLabel(projectType)}`); console.log(` Package Manager: ${packageManager}`); console.log("\n" + getProjectInfo(projectType)); }); program.parse(); export { program }; //# sourceMappingURL=cli.js.map