@entro314labs/at3-stack-kit
Version:
Upgrade existing projects to AT3 Stack with intelligent migration
1,162 lines (1,104 loc) • 39.5 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
addAI: () => addAI,
addI18n: () => addI18n,
addPWA: () => addPWA,
addSupabase: () => addSupabase,
addTesting: () => addTesting,
analyzeProject: () => analyzeProject,
detectProjectType: () => detectProjectType,
getMissingFeatures: () => getMissingFeatures,
getRecommendations: () => getRecommendations,
isCompatible: () => isCompatible,
program: () => import_commander.program
});
module.exports = __toCommonJS(index_exports);
// src/cli.ts
var import_prompts = require("@clack/prompts");
var import_chalk2 = __toESM(require("chalk"), 1);
var import_commander = require("commander");
var import_detect_package_manager2 = require("detect-package-manager");
var import_fs_extra6 = require("fs-extra");
var import_path4 = require("path");
// src/detect.ts
var import_detect_package_manager = require("detect-package-manager");
var import_fs_extra = require("fs-extra");
var import_path = require("path");
async function detectProjectType(projectPath) {
const packageJsonPath = (0, import_path.join)(projectPath, "package.json");
if (!(0, import_fs_extra.existsSync)(packageJsonPath)) {
return "unknown";
}
try {
const packageJson = JSON.parse((0, import_fs_extra.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 || (0, import_fs_extra.existsSync)((0, import_path.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";
}
}
async function analyzeProject(projectPath) {
if (!(0, import_fs_extra.existsSync)(projectPath)) {
throw new Error(`Project path does not exist: ${projectPath}`);
}
const packageJsonPath = (0, import_path.join)(projectPath, "package.json");
if (!(0, import_fs_extra.existsSync)(packageJsonPath)) {
throw new Error("No package.json found. This does not appear to be a Node.js project.");
}
const packageJson = JSON.parse((0, import_fs_extra.readFileSync)(packageJsonPath, "utf-8"));
let packageManager = "npm";
try {
packageManager = await (0, import_detect_package_manager.detect)({ cwd: projectPath });
} catch {
if ((0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "pnpm-lock.yaml"))) packageManager = "pnpm";
else if ((0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "yarn.lock"))) packageManager = "yarn";
else if ((0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "bun.lockb"))) packageManager = "bun";
}
const dependencies = analyzeDependencies(packageJson);
const configFiles = findConfigFiles(projectPath);
const hasTypeScript = hasTypeScriptSupport(projectPath, dependencies);
const hasNextjs = hasDependency(dependencies, "next");
const hasReact = hasDependency(dependencies, "react");
const hasVue = hasDependency(dependencies, "vue");
const hasTailwind = hasDependency(dependencies, "tailwindcss");
const hasTRPC = dependencies.some((dep) => dep.name.includes("@trpc/"));
const hasEslint = hasDependency(dependencies, "eslint");
const hasPrettier = hasDependency(dependencies, "prettier");
const hasBiome = hasDependency(dependencies, "@biomejs/biome");
const hasAI = hasDependency(dependencies, "ai") || hasDependency(dependencies, "@ai-sdk/openai") || hasDependency(dependencies, "@ai-sdk/anthropic") || hasDependency(dependencies, "@ai-sdk/google");
const hasSupabase = hasDependency(dependencies, "@supabase/supabase-js") || hasDependency(dependencies, "@supabase/ssr");
const hasEdgeRuntime = (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "middleware.ts")) || (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "src/middleware.ts"));
const hasVectorDB = hasSupabaseVectorConfig(projectPath);
const hasPWA = dependencies.some(
(dep) => dep.name.includes("workbox") || dep.name.includes("pwa")
);
const hasI18n = dependencies.some(
(dep) => dep.name.includes("next-intl") || dep.name.includes("i18n")
);
const hasVitest = hasDependency(dependencies, "vitest");
const hasPlaywright = hasDependency(dependencies, "playwright") || hasDependency(dependencies, "@playwright/test");
return {
path: projectPath,
type: await detectProjectType(projectPath),
hasNextjs,
hasReact,
hasVue,
hasTypeScript,
hasTailwind,
hasTRPC,
hasSupabase,
hasAI,
hasPWA,
hasI18n,
hasVitest,
hasPlaywright,
hasEslint,
hasPrettier,
hasBiome,
hasEdgeRuntime,
hasVectorDB,
packageManager,
dependencies,
configFiles
};
}
function analyzeDependencies(packageJson) {
const deps = [];
if (packageJson.dependencies) {
Object.entries(packageJson.dependencies).forEach(([name, version]) => {
deps.push({
name,
version,
type: "dependency"
});
});
}
if (packageJson.devDependencies) {
Object.entries(packageJson.devDependencies).forEach(([name, version]) => {
deps.push({
name,
version,
type: "devDependency"
});
});
}
if (packageJson.peerDependencies) {
Object.entries(packageJson.peerDependencies).forEach(([name, version]) => {
deps.push({
name,
version,
type: "peerDependency"
});
});
}
return deps;
}
function findConfigFiles(projectPath) {
const configFiles = [];
const commonConfigFiles = [
// TypeScript
"tsconfig.json",
"tsconfig.build.json",
// Next.js
"next.config.js",
"next.config.ts",
"next.config.mjs",
// Tailwind
"tailwind.config.js",
"tailwind.config.ts",
"tailwind.config.mjs",
"postcss.config.js",
"postcss.config.mjs",
// Linting
".eslintrc.js",
".eslintrc.json",
".eslintrc.yml",
".eslintrc.yaml",
"eslint.config.js",
"eslint.config.mjs",
".prettierrc",
".prettierrc.js",
".prettierrc.json",
"biome.json",
// Testing
"vitest.config.ts",
"vitest.config.js",
"jest.config.js",
"jest.config.ts",
"playwright.config.ts",
// Build tools
"vite.config.ts",
"vite.config.js",
"webpack.config.js",
"rollup.config.js",
// Other
".gitignore",
".env.example",
".env.local",
"README.md"
];
commonConfigFiles.forEach((file) => {
if ((0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, file))) {
configFiles.push(file);
}
});
return configFiles;
}
function hasTypeScriptSupport(projectPath, dependencies) {
return (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "tsconfig.json")) || hasDependency(dependencies, "typescript");
}
function hasDependency(dependencies, name) {
return dependencies.some((dep) => dep.name === name);
}
function hasSupabaseVectorConfig(projectPath) {
const supabaseMigrationDir = (0, import_path.join)(projectPath, "supabase", "migrations");
if ((0, import_fs_extra.existsSync)(supabaseMigrationDir)) {
try {
const { readdirSync } = require("fs");
const migrationFiles = readdirSync(supabaseMigrationDir);
return migrationFiles.some((file) => {
if (file.endsWith(".sql")) {
const content = (0, import_fs_extra.readFileSync)((0, import_path.join)(supabaseMigrationDir, file), "utf8");
return content.includes("vector") || content.includes("embedding");
}
return false;
});
} catch (error) {
return false;
}
}
return false;
}
function getMissingFeatures(info) {
const missing = [];
if (!info.hasSupabase) missing.push("supabase");
if (!info.hasAI) missing.push("ai");
if (!info.hasPWA) missing.push("pwa");
if (!info.hasI18n) missing.push("i18n");
if (!info.hasVitest) missing.push("testing");
if (!info.hasTailwind) missing.push("tailwind");
if (!info.hasTypeScript) missing.push("typescript");
return missing;
}
function isCompatible(info) {
if (!info.hasNextjs && !info.hasReact) return false;
if (!["npm", "pnpm", "yarn", "bun"].includes(info.packageManager)) return false;
return true;
}
function getRecommendations(info) {
const recommendations = [];
if (!info.hasTypeScript) {
recommendations.push({
priority: "high",
feature: "typescript",
reason: "TypeScript provides better development experience and type safety"
});
}
if (!info.hasTailwind) {
recommendations.push({
priority: "high",
feature: "tailwind",
reason: "Tailwind CSS is essential for AT3 stack styling"
});
}
if (!info.hasSupabase) {
recommendations.push({
priority: "medium",
feature: "supabase",
reason: "Supabase provides database, auth, and edge functions"
});
}
if (!info.hasAI) {
recommendations.push({
priority: "medium",
feature: "ai",
reason: "AI integration is a core feature of AT3 stack"
});
}
if (!info.hasVitest && !info.hasPlaywright) {
recommendations.push({
priority: "low",
feature: "testing",
reason: "Comprehensive testing improves code quality"
});
}
return recommendations;
}
// src/features/add-ai.ts
var import_node_path = require("path");
var import_fs_extra2 = require("fs-extra");
async function addAI(type, projectPath) {
const srcPath = (0, import_node_path.join)(projectPath, "src");
await (0, import_fs_extra2.ensureDir)(srcPath);
const aiPath = (0, import_node_path.join)(srcPath, "lib", "ai");
await (0, import_fs_extra2.ensureDir)(aiPath);
if (type === "custom") {
await addCustomAI(aiPath);
}
if (type === "vercel") {
await addVercelAI(aiPath);
}
const apiPath = (0, import_node_path.join)(srcPath, "app", "api");
await (0, import_fs_extra2.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 (0, import_fs_extra2.writeFile)((0, import_node_path.join)(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 (0, import_fs_extra2.writeFile)((0, import_node_path.join)(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 (0, import_fs_extra2.writeFile)((0, import_node_path.join)(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 (0, import_fs_extra2.writeFile)((0, import_node_path.join)(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 (0, import_fs_extra2.ensureDir)((0, import_node_path.join)(apiPath, "chat"));
await (0, import_fs_extra2.writeFile)((0, import_node_path.join)(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 (0, import_fs_extra2.ensureDir)((0, import_node_path.join)(apiPath, "completion"));
await (0, import_fs_extra2.writeFile)((0, import_node_path.join)(apiPath, "completion", "route.ts"), completionRoute);
}
}
async function updatePackageJson(projectPath, type) {
const packageJsonPath = (0, import_node_path.join)(projectPath, "package.json");
if (!await (0, import_fs_extra2.pathExists)(packageJsonPath)) return;
const packageJson = JSON.parse(await (0, import_fs_extra2.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 (0, import_fs_extra2.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
var import_node_path2 = require("path");
var import_fs_extra3 = require("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 = (0, import_node_path2.join)(projectPath, "public");
await (0, import_fs_extra3.ensureDir)(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 (0, import_fs_extra3.writeFile)((0, import_node_path2.join)(publicPath, "manifest.json"), JSON.stringify(manifest, null, 2));
}
async function addServiceWorker(projectPath) {
const publicPath = (0, import_node_path2.join)(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 (0, import_fs_extra3.writeFile)((0, import_node_path2.join)(publicPath, "sw.js"), sw);
}
function updatePackageJson2(_projectPath) {
console.log("PWA package.json updates would go here");
}
// src/features/add-supabase.ts
var import_fs_extra4 = require("fs-extra");
var import_path2 = require("path");
async function addSupabase(projectPath) {
const srcPath = (0, import_path2.join)(projectPath, "src");
await (0, import_fs_extra4.ensureDir)(srcPath);
const supabasePath = (0, import_path2.join)(srcPath, "lib", "supabase");
await (0, import_fs_extra4.ensureDir)(supabasePath);
await addSupabaseClients(supabasePath);
await addAuthHelpers((0, import_path2.join)(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 (0, import_fs_extra4.writeFile)((0, import_path2.join)(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 (0, import_fs_extra4.writeFile)((0, import_path2.join)(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 (0, import_fs_extra4.writeFile)((0, import_path2.join)(supabasePath, "middleware.ts"), middlewareCode);
}
async function addAuthHelpers(authPath) {
await (0, import_fs_extra4.ensureDir)(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 (0, import_fs_extra4.writeFile)((0, import_path2.join)(authPath, "auth-helpers.ts"), authHelpersCode);
}
async function updatePackageJson3(projectPath) {
const packageJsonPath = (0, import_path2.join)(projectPath, "package.json");
if (!await (0, import_fs_extra4.pathExists)(packageJsonPath)) return;
const packageJson = JSON.parse(await (0, import_fs_extra4.readFile)(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 (0, import_fs_extra4.writeFile)(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
async function addEnvExample(projectPath) {
const envExamplePath = (0, import_path2.join)(projectPath, ".env.example");
let envContent = "";
if (await (0, import_fs_extra4.pathExists)(envExamplePath)) {
envContent = await (0, import_fs_extra4.readFile)(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 (0, import_fs_extra4.writeFile)(envExamplePath, envContent);
}
}
async function initSupabaseConfig(projectPath) {
const supabaseConfigPath = (0, import_path2.join)(projectPath, "supabase", "config.toml");
if (await (0, import_fs_extra4.pathExists)(supabaseConfigPath)) return;
await (0, import_fs_extra4.ensureDir)((0, import_path2.join)(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 (0, import_fs_extra4.writeFile)(supabaseConfigPath, config);
}
// src/features/add-testing.ts
function addTesting(_projectPath) {
console.log("\u2713 Testing suite would be added here");
}
// src/utils/cli-styling.ts
var import_boxen = __toESM(require("boxen"), 1);
var import_chalk = __toESM(require("chalk"), 1);
var import_figures = __toESM(require("figures"), 1);
var import_gradient_string = __toESM(require("gradient-string"), 1);
var colors = {
primary: import_chalk.default.hex("#2563eb"),
// Blue
secondary: import_chalk.default.hex("#7c3aed"),
// Purple
success: import_chalk.default.green,
error: import_chalk.default.red,
warning: import_chalk.default.yellow,
info: import_chalk.default.blue,
muted: import_chalk.default.gray,
accent: import_chalk.default.magenta,
ai: import_chalk.default.hex("#10b981")
// Emerald for AI features
};
var gradients = {
at3: (0, import_gradient_string.default)("#2563eb", "#10b981"),
// Blue to Emerald
ai: (0, import_gradient_string.default)("#10b981", "#059669"),
// Emerald gradient
success: (0, import_gradient_string.default)("#22c55e", "#16a34a"),
// Green gradient
warning: (0, import_gradient_string.default)("#f59e0b", "#d97706"),
// Amber gradient
error: (0, import_gradient_string.default)("#ef4444", "#dc2626")
// Red gradient
};
var symbols = {
success: colors.success(import_figures.default.tick),
error: colors.error(import_figures.default.cross),
warning: colors.warning(import_figures.default.warning),
info: colors.info(import_figures.default.info),
arrow: colors.muted(import_figures.default.arrowRight),
bullet: colors.muted(import_figures.default.bullet),
line: colors.muted(import_figures.default.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) => import_chalk.default.cyan.italic(text),
path: (text) => import_chalk.default.dim.underline(text),
command: (text) => import_chalk.default.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
var import_fs_extra5 = require("fs-extra");
var import_path3 = require("path");
function updateAT3Config(projectPath, addedFeatures) {
const configPath = (0, import_path3.join)(projectPath, ".at3-config.json");
let config;
if ((0, import_fs_extra5.existsSync)(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();
(0, import_prompts.intro)(style.title("AT3 Stack Kit"));
const s = (0, import_prompts.spinner)();
s.start("Analyzing your project...");
const projectType = await detectProjectType(process.cwd());
const packageManager = await (0, import_detect_package_manager2.detect)().catch(() => "npm");
s.stop(
`${symbols.success} Detected: ${style.value(getProjectTypeLabel(projectType))} project using ${style.accent(packageManager)}`
);
if (projectType === "unknown") {
(0, import_prompts.cancel)(
"This doesn't appear to be a supported project type. AT3 Kit supports Next.js, T3, and React projects."
);
process.exit(1);
}
(0, import_prompts.note)(getProjectInfo(projectType), "Current Project");
const selectedFeatures = await (0, import_prompts.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 ((0, import_prompts.isCancel)(selectedFeatures) || selectedFeatures.length === 0) {
(0, import_prompts.cancel)("No features selected.");
process.exit(0);
}
const shouldInstall = await (0, import_prompts.confirm)({
message: `Install dependencies with ${packageManager}?`,
initialValue: true
});
if ((0, import_prompts.isCancel)(shouldInstall)) {
(0, import_prompts.cancel)("Operation cancelled.");
process.exit(0);
}
const addSpinner = (0, import_prompts.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);
(0, import_prompts.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
];
(0, import_prompts.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(import_chalk2.default.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 = (0, import_path4.join)(process.cwd(), "package.json");
if (!(0, import_fs_extra6.existsSync)(packageJsonPath)) {
return "No package.json found";
}
const packageJson = JSON.parse((0, import_fs_extra6.readFileSync)(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;
}
import_commander.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();
});
import_commander.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(import_chalk2.default.red(`Unknown feature: ${feature}`));
console.log(import_chalk2.default.dim(`Valid features: ${validFeatures.join(", ")}`));
process.exit(1);
}
console.log(import_chalk2.default.blue(`Adding ${getFeatureName(feature)}...`));
});
import_commander.program.command("detect").description("Detect current project type and show available upgrades").action(async () => {
const projectType = await detectProjectType(process.cwd());
const packageManager = await (0, import_detect_package_manager2.detect)().catch(() => "npm");
console.log(import_chalk2.default.green("Project Analysis:"));
console.log(` Type: ${getProjectTypeLabel(projectType)}`);
console.log(` Package Manager: ${packageManager}`);
console.log("\n" + getProjectInfo(projectType));
});
import_commander.program.parse();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
addAI,
addI18n,
addPWA,
addSupabase,
addTesting,
analyzeProject,
detectProjectType,
getMissingFeatures,
getRecommendations,
isCompatible,
program
});
//# sourceMappingURL=index.cjs.map