@entro314labs/at3-stack-kit
Version:
Upgrade existing projects to AT3 Stack with intelligent migration
1,447 lines (1,369 loc) • 121 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,
addBetterAuth: () => addBetterAuth,
addClerk: () => addClerk,
addDrizzle: () => addDrizzle,
addI18n: () => addI18n,
addPWA: () => addPWA,
addSupabase: () => addSupabase,
addTesting: () => addTesting,
analyzeProject: () => analyzeProject,
detectProjectType: () => detectProjectType,
getAT3Score: () => getAT3Score,
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_extra11 = require("fs-extra");
var import_path9 = 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.next && deps["@trpc/server"]) {
if (deps.prisma || deps["@prisma/client"] || deps["drizzle-orm"]) {
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 = await analyzeDependencies(packageJson, projectPath);
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 = detectTRPC(dependencies);
const hasEslint = hasDependency(dependencies, "eslint");
const hasPrettier = hasDependency(dependencies, "prettier");
const hasBiome = hasDependency(dependencies, "@biomejs/biome");
const hasAI = detectAISupport(dependencies);
const hasSupabase = detectSupabase(dependencies, projectPath);
const hasEdgeRuntime = detectEdgeRuntime(projectPath);
const hasVectorDB = hasSupabaseVectorConfig(projectPath);
const hasPWA = detectPWA(dependencies, projectPath);
const hasI18n = detectI18n(dependencies, projectPath);
const hasDrizzle = detectDrizzle(dependencies, projectPath);
const hasPrisma = detectPrisma(dependencies, projectPath);
const authProvider = detectAuthProvider(dependencies, projectPath);
const testing = detectTesting(dependencies);
const hasVitest = testing.unit === "vitest";
const hasPlaywright = testing.e2e === "playwright";
return {
path: projectPath,
type: await detectProjectType(projectPath),
hasNextjs,
hasReact,
hasVue,
hasTypeScript,
hasTailwind,
hasTRPC,
hasSupabase,
hasAI,
hasPWA,
hasI18n,
hasVitest,
hasPlaywright,
hasEslint,
hasPrettier,
hasBiome,
hasEdgeRuntime,
hasVectorDB,
hasDrizzle,
hasPrisma,
authProvider,
testing,
packageManager,
dependencies,
configFiles
};
}
async function analyzeDependencies(packageJson, projectPath) {
const deps = [];
if (packageJson.dependencies) {
for (const [name, version] of Object.entries(packageJson.dependencies)) {
const info = {
name,
version,
type: "dependency"
};
try {
const installedPkgPath = (0, import_path.join)(projectPath, "node_modules", name, "package.json");
if ((0, import_fs_extra.existsSync)(installedPkgPath)) {
const installedPkg = JSON.parse((0, import_fs_extra.readFileSync)(installedPkgPath, "utf-8"));
info.current = installedPkg.version;
}
} catch {
}
deps.push(info);
}
}
if (packageJson.devDependencies) {
for (const [name, version] of Object.entries(packageJson.devDependencies)) {
const info = {
name,
version,
type: "devDependency"
};
try {
const installedPkgPath = (0, import_path.join)(projectPath, "node_modules", name, "package.json");
if ((0, import_fs_extra.existsSync)(installedPkgPath)) {
const installedPkg = JSON.parse((0, import_fs_extra.readFileSync)(installedPkgPath, "utf-8"));
info.current = installedPkg.version;
}
} catch {
}
deps.push(info);
}
}
if (packageJson.peerDependencies) {
for (const [name, version] of Object.entries(packageJson.peerDependencies)) {
deps.push({
name,
version,
type: "peerDependency"
});
}
}
return deps;
}
function findConfigFiles(projectPath) {
const configFiles = [];
const commonConfigFiles = [
// TypeScript
"tsconfig.json",
"tsconfig.build.json",
"tsconfig.test.json",
// Next.js
"next.config.js",
"next.config.ts",
"next.config.mjs",
"next-env.d.ts",
// 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",
"biome.jsonc",
// Testing
"vitest.config.ts",
"vitest.config.js",
"vitest.config.mts",
"jest.config.js",
"jest.config.ts",
"playwright.config.ts",
"cypress.config.js",
"cypress.config.ts",
// Build tools
"vite.config.ts",
"vite.config.js",
"webpack.config.js",
"rollup.config.js",
"turbo.json",
// Database
"drizzle.config.ts",
"drizzle.config.js",
"prisma/schema.prisma",
// Environment
".env",
".env.local",
".env.example",
".env.development",
".env.production",
// Other
".gitignore",
"README.md",
"package.json",
"pnpm-workspace.yaml",
"vercel.json",
"netlify.toml"
];
commonConfigFiles.forEach((file) => {
if ((0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, file))) {
configFiles.push(file);
}
});
if ((0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "supabase", "config.toml"))) {
configFiles.push("supabase/config.toml");
}
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 detectAISupport(dependencies) {
const aiDeps = [
"ai",
"@ai-sdk/openai",
"@ai-sdk/anthropic",
"@ai-sdk/google",
"@ai-sdk/azure",
"@ai-sdk/mistral",
"@ai-sdk/cohere",
"openai",
"@anthropic-ai/sdk",
"@google/generative-ai",
"langchain",
"@langchain/core",
"llamaindex"
];
return aiDeps.some((dep) => hasDependency(dependencies, dep));
}
function detectSupabase(dependencies, projectPath) {
const hasSupabaseDeps = hasDependency(dependencies, "@supabase/supabase-js") || hasDependency(dependencies, "@supabase/ssr") || hasDependency(dependencies, "@supabase/auth-helpers-nextjs");
const hasSupabaseConfig = (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "supabase", "config.toml"));
return hasSupabaseDeps || hasSupabaseConfig;
}
function detectEdgeRuntime(projectPath) {
const middlewarePaths = [
(0, import_path.join)(projectPath, "middleware.ts"),
(0, import_path.join)(projectPath, "middleware.js"),
(0, import_path.join)(projectPath, "src/middleware.ts"),
(0, import_path.join)(projectPath, "src/middleware.js")
];
if (middlewarePaths.some((p) => (0, import_fs_extra.existsSync)(p))) {
return true;
}
const apiPaths = [
(0, import_path.join)(projectPath, "app/api"),
(0, import_path.join)(projectPath, "src/app/api"),
(0, import_path.join)(projectPath, "pages/api")
];
for (const apiPath of apiPaths) {
if ((0, import_fs_extra.existsSync)(apiPath)) {
try {
const files = getAllFiles(apiPath, [".ts", ".js"]);
for (const file of files) {
const content = (0, import_fs_extra.readFileSync)(file, "utf8");
if (content.includes("export const runtime = 'edge'")) {
return true;
}
}
} catch {
}
}
}
return false;
}
function hasSupabaseVectorConfig(projectPath) {
const supabaseMigrationDir = (0, import_path.join)(projectPath, "supabase", "migrations");
if (!(0, import_fs_extra.existsSync)(supabaseMigrationDir)) return false;
try {
const migrationFiles = (0, import_fs_extra.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") || content.includes("pgvector");
}
return false;
});
} catch {
return false;
}
}
function detectDrizzle(dependencies, projectPath) {
const hasDrizzleDeps = hasDependency(dependencies, "drizzle-orm") || hasDependency(dependencies, "drizzle-kit");
const hasDrizzleConfig = (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "drizzle.config.ts")) || (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "drizzle.config.js"));
return hasDrizzleDeps || hasDrizzleConfig;
}
function detectPrisma(dependencies, projectPath) {
const hasPrismaDeps = hasDependency(dependencies, "prisma") || hasDependency(dependencies, "@prisma/client");
const hasPrismaSchema = (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "prisma", "schema.prisma"));
return hasPrismaDeps || hasPrismaSchema;
}
function detectAuthProvider(dependencies, projectPath) {
if (hasDependency(dependencies, "@supabase/auth-helpers-nextjs") || hasDependency(dependencies, "@supabase/ssr")) {
const hasAuthConfig = (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "src/lib/supabase")) || (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "lib/supabase"));
if (hasAuthConfig) return "supabase";
}
if (hasDependency(dependencies, "@clerk/nextjs") || hasDependency(dependencies, "@clerk/clerk-react")) {
return "clerk";
}
if (hasDependency(dependencies, "better-auth")) {
return "better-auth";
}
if (hasDependency(dependencies, "next-auth") || hasDependency(dependencies, "@auth/core")) {
return "next-auth";
}
if (hasDependency(dependencies, "lucia")) {
return "lucia";
}
return "none";
}
function detectTRPC(dependencies) {
return hasDependency(dependencies, "@trpc/server") || hasDependency(dependencies, "@trpc/client") || hasDependency(dependencies, "@trpc/react-query");
}
function detectPWA(dependencies, projectPath) {
const hasPWADeps = hasDependency(dependencies, "@ducanh2912/next-pwa") || hasDependency(dependencies, "next-pwa") || hasDependency(dependencies, "workbox-webpack-plugin");
const hasManifest = (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "public", "manifest.json"));
const hasServiceWorker = (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "public", "sw.js")) || (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "public", "service-worker.js"));
return hasPWADeps || hasManifest && hasServiceWorker;
}
function detectI18n(dependencies, projectPath) {
const hasI18nDeps = hasDependency(dependencies, "next-intl") || hasDependency(dependencies, "next-i18next") || hasDependency(dependencies, "react-i18next") || hasDependency(dependencies, "i18next");
const hasMessagesDir = (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "messages")) || (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "locales")) || (0, import_fs_extra.existsSync)((0, import_path.join)(projectPath, "public/locales"));
return hasI18nDeps || hasMessagesDir;
}
function detectTesting(dependencies) {
let unit = "none";
if (hasDependency(dependencies, "vitest")) {
unit = "vitest";
} else if (hasDependency(dependencies, "jest")) {
unit = "jest";
}
let e2e = "none";
if (hasDependency(dependencies, "@playwright/test") || hasDependency(dependencies, "playwright")) {
e2e = "playwright";
} else if (hasDependency(dependencies, "cypress")) {
e2e = "cypress";
}
return { unit, e2e };
}
function getAllFiles(dirPath, extensions) {
const files = [];
try {
const entries = (0, import_fs_extra.readdirSync)(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = (0, import_path.join)(dirPath, entry.name);
if (entry.isDirectory()) {
files.push(...getAllFiles(fullPath, extensions));
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
files.push(fullPath);
}
}
} catch {
}
return files;
}
function getMissingFeatures(info) {
const missing = [];
if (!info.hasSupabase && !info.hasDrizzle && !info.hasPrisma) missing.push("database");
if (!info.hasAI) missing.push("ai");
if (!info.hasPWA) missing.push("pwa");
if (!info.hasI18n) missing.push("i18n");
if (info.testing.unit === "none") missing.push("testing");
if (!info.hasTailwind) missing.push("tailwind");
if (!info.hasTypeScript) missing.push("typescript");
if (info.authProvider === "none") missing.push("auth");
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 && !info.hasDrizzle && !info.hasPrisma) {
recommendations.push({
priority: "high",
feature: "database",
reason: "A database solution is needed for most applications"
});
}
if (info.authProvider === "none") {
recommendations.push({
priority: "medium",
feature: "auth",
reason: "Authentication is essential for user management"
});
}
if (!info.hasAI) {
recommendations.push({
priority: "medium",
feature: "ai",
reason: "AI integration is a core feature of AT3 stack"
});
}
if (info.testing.unit === "none") {
recommendations.push({
priority: "low",
feature: "testing",
reason: "Comprehensive testing improves code quality"
});
}
if (!info.hasBiome && (info.hasEslint || info.hasPrettier)) {
recommendations.push({
priority: "low",
feature: "biome",
reason: "Biome provides faster linting and formatting than ESLint/Prettier"
});
}
return recommendations;
}
function getAT3Score(info) {
let score = 0;
const maxScore = 10;
if (info.hasNextjs) score += 1;
if (info.hasTypeScript) score += 1;
if (info.hasTailwind) score += 1;
if (info.hasSupabase || info.hasDrizzle || info.hasPrisma) score += 1;
if (info.authProvider !== "none") score += 1;
if (info.hasAI) score += 2;
if (info.hasEdgeRuntime) score += 1;
if (info.testing.unit !== "none") score += 1;
if (info.hasBiome) score += 1;
const percentage = Math.round(score / maxScore * 100);
let level;
if (percentage === 0) level = "none";
else if (percentage < 30) level = "basic";
else if (percentage < 60) level = "intermediate";
else if (percentage < 90) level = "advanced";
else level = "full";
return { score, maxScore, percentage, level };
}
// 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.104";
packageJson.dependencies["@ai-sdk/openai"] = "^2.0.74";
packageJson.dependencies["@ai-sdk/anthropic"] = "^2.0.50";
packageJson.dependencies["@ai-sdk/google"] = "^2.0.44";
}
await (0, import_fs_extra2.writeFile)(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
// src/features/add-better-auth.ts
var import_fs_extra3 = require("fs-extra");
var import_path2 = require("path");
// 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/features/add-better-auth.ts
async function addBetterAuth(projectPath) {
console.log(colors.info("Adding Better Auth..."));
await updatePackageJson2(projectPath);
await addEnvExample(projectPath);
await createAuthFiles(projectPath);
console.log(colors.success("\u2713 Added Better Auth configuration"));
}
async function updatePackageJson2(projectPath) {
const packageJsonPath = (0, import_path2.join)(projectPath, "package.json");
if (!await (0, import_fs_extra3.pathExists)(packageJsonPath)) return;
const packageJson = JSON.parse(await (0, import_fs_extra3.readFile)(packageJsonPath, "utf-8"));
if (!packageJson.dependencies) packageJson.dependencies = {};
packageJson.dependencies["better-auth"] = "^1.1.0";
await (0, import_fs_extra3.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_extra3.pathExists)(envExamplePath)) {
envContent = await (0, import_fs_extra3.readFile)(envExamplePath, "utf-8");
}
if (!envContent.includes("BETTER_AUTH_SECRET")) {
envContent += `
# Better Auth
BETTER_AUTH_SECRET=your_secret_here
BETTER_AUTH_URL=http://localhost:3000
`;
await (0, import_fs_extra3.writeFile)(envExamplePath, envContent);
}
}
async function createAuthFiles(projectPath) {
const libPath = (0, import_path2.join)(projectPath, "src", "lib", "auth");
await (0, import_fs_extra3.ensureDir)(libPath);
await (0, import_fs_extra3.writeFile)((0, import_path2.join)(libPath, "client.ts"), `import { createAuthClient } from "better-auth/react"
export const authClient = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL
})
`);
await (0, import_fs_extra3.writeFile)((0, import_path2.join)(libPath, "auth.ts"), `import { betterAuth } from "better-auth";
// import { db } from "@/db";
// import { drizzleAdapter } from "better-auth/adapters/drizzle";
export const auth = betterAuth({
// adapter: drizzleAdapter(db, {
// provider: "pg",
// }),
emailAndPassword: {
enabled: true
},
// socialProviders: {
// github: {
// clientId: process.env.GITHUB_CLIENT_ID,
// clientSecret: process.env.GITHUB_CLIENT_SECRET,
// }
// },
})
`);
const apiPath = (0, import_path2.join)(projectPath, "src", "app", "api", "auth", "[...all]");
await (0, import_fs_extra3.ensureDir)(apiPath);
await (0, import_fs_extra3.writeFile)((0, import_path2.join)(apiPath, "route.ts"), `import { auth } from "@/lib/auth/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);
`);
}
// src/features/add-clerk.ts
var import_fs_extra4 = require("fs-extra");
var import_path3 = require("path");
async function addClerk(projectPath) {
console.log(colors.info("Adding Clerk authentication..."));
await updatePackageJson3(projectPath);
await addEnvExample2(projectPath);
await addMiddleware(projectPath);
console.log(colors.warning("NOTE: You need to wrap your root layout with <ClerkProvider> manually."));
console.log(colors.success("\u2713 Added Clerk configuration"));
}
async function updatePackageJson3(projectPath) {
const packageJsonPath = (0, import_path3.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 = {};
packageJson.dependencies["@clerk/nextjs"] = "^6.9.0";
await (0, import_fs_extra4.writeFile)(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
async function addEnvExample2(projectPath) {
const envExamplePath = (0, import_path3.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_CLERK_PUBLISHABLE_KEY")) {
envContent += `
# Clerk Auth
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
`;
await (0, import_fs_extra4.writeFile)(envExamplePath, envContent);
}
}
async function addMiddleware(projectPath) {
const middlewarePath = (0, import_path3.join)(projectPath, "src", "middleware.ts");
const content = `import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
export const config = {
matcher: ["/((?!.*\\\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
`;
await (0, import_fs_extra4.writeFile)(middlewarePath, content);
}
// src/features/add-drizzle.ts
var import_fs_extra5 = require("fs-extra");
var import_path4 = require("path");
async function addDrizzle(projectPath) {
const srcPath = (0, import_path4.join)(projectPath, "src");
await (0, import_fs_extra5.ensureDir)(srcPath);
const dbPath = (0, import_path4.join)(srcPath, "db");
await (0, import_fs_extra5.ensureDir)(dbPath);
await addDrizzleConfig(projectPath);
await addSchema(dbPath);
await addClient(dbPath);
await updatePackageJson4(projectPath);
await addEnvExample3(projectPath);
console.log("\u2713 Added Drizzle ORM + PostgreSQL support");
}
async function addDrizzleConfig(projectPath) {
const config = `import { defineConfig } from 'drizzle-kit';
import * as dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL is missing');
}
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL,
},
});
`;
await (0, import_fs_extra5.writeFile)((0, import_path4.join)(projectPath, "drizzle.config.ts"), config);
}
async function addSchema(dbPath) {
const schema = `import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
email: text('email').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
`;
await (0, import_fs_extra5.writeFile)((0, import_path4.join)(dbPath, "schema.ts"), schema);
}
async function addClient(dbPath) {
const client = `import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const connectionString = process.env.DATABASE_URL!;
// Disable prefetch as it is not supported for "Transaction" pool mode
const client = postgres(connectionString, { prepare: false });
export const db = drizzle(client, { schema });
`;
await (0, import_fs_extra5.writeFile)((0, import_path4.join)(dbPath, "index.ts"), client);
}
async function updatePackageJson4(projectPath) {
const packageJsonPath = (0, import_path4.join)(projectPath, "package.json");
if (!await (0, import_fs_extra5.pathExists)(packageJsonPath)) return;
const packageJson = JSON.parse(await (0, import_fs_extra5.readFile)(packageJsonPath, "utf-8"));
if (!packageJson.dependencies) packageJson.dependencies = {};
if (!packageJson.devDependencies) packageJson.devDependencies = {};
if (!packageJson.scripts) packageJson.scripts = {};
packageJson.dependencies["drizzle-orm"] = "^0.36.0";
packageJson.dependencies["postgres"] = "^3.4.4";
packageJson.dependencies["dotenv"] = "^16.4.5";
packageJson.devDependencies["drizzle-kit"] = "^0.28.0";
packageJson.devDependencies["pg"] = "^8.13.0";
packageJson.devDependencies["@types/pg"] = "^8.11.10";
packageJson.scripts["db:generate"] = "drizzle-kit generate";
packageJson.scripts["db:migrate"] = "drizzle-kit migrate";
packageJson.scripts["db:push"] = "drizzle-kit push";
packageJson.scripts["db:studio"] = "drizzle-kit studio";
await (0, import_fs_extra5.writeFile)(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
async function addEnvExample3(projectPath) {
const envExamplePath = (0, import_path4.join)(projectPath, ".env.example");
let envContent = "";
if (await (0, import_fs_extra5.pathExists)(envExamplePath)) {
envContent = await (0, import_fs_extra5.readFile)(envExamplePath, "utf-8");
}
if (!envContent.includes("DATABASE_URL")) {
const dbVars = `
# Database (PostgreSQL)
DATABASE_URL="postgresql://postgres:password@localhost:5432/postgres"
`;
envContent = envContent + dbVars;
await (0, import_fs_extra5.writeFile)(envExamplePath, envContent);
}
}
// src/features/add-i18n.ts
var import_fs_extra6 = require("fs-extra");
var import_path5 = require("path");
async function addI18n(projectPath) {
const srcPath = (0, import_path5.join)(projectPath, "src");
await (0, import_fs_extra6.ensureDir)(srcPath);
const messagesPath = (0, import_path5.join)(projectPath, "messages");
await (0, import_fs_extra6.ensureDir)(messagesPath);
await addDefaultMessages(messagesPath);
const i18nPath = (0, import_path5.join)(srcPath, "lib", "i18n");
await (0, import_fs_extra6.ensureDir)(i18nPath);
await addI18nConfig(i18nPath);
await addI18nRequest(i18nPath);
await addI18nNavigation(i18nPath);
await addI18nMiddleware(srcPath);
await addLocaleProvider(i18nPath);
await addLanguageSwitcher(srcPath);
await addLocaleLayout(srcPath);
await updatePackageJson5(projectPath);
await updateNextConfig(projectPath);
console.log("\u2713 Added i18n support (next-intl v4)");
}
async function addDefaultMessages(messagesPath) {
const enMessages = {
Common: {
loading: "Loading...",
error: "An error occurred",
retry: "Try again",
cancel: "Cancel",
save: "Save",
delete: "Delete",
edit: "Edit",
back: "Back",
next: "Next",
previous: "Previous",
search: "Search",
noResults: "No results found"
},
Navigation: {
home: "Home",
about: "About",
contact: "Contact",
dashboard: "Dashboard",
settings: "Settings",
profile: "Profile"
},
Auth: {
signIn: "Sign In",
signOut: "Sign Out",
signUp: "Sign Up",
email: "Email",
password: "Password",
forgotPassword: "Forgot password?",
rememberMe: "Remember me",
noAccount: "Don't have an account?",
hasAccount: "Already have an account?"
},
Index: {
title: "Welcome to AT3 Stack",
description: "Build AI-native applications with edge deployment",
getStarted: "Get Started",
learnMore: "Learn More"
},
Errors: {
notFound: "Page not found",
notFoundDescription: "The page you are looking for does not exist.",
serverError: "Server error",
serverErrorDescription: "Something went wrong on our end.",
goHome: "Go to home"
},
LanguageSwitcher: {
label: "Language",
en: "English",
es: "Espa\xF1ol",
fr: "Fran\xE7ais",
de: "Deutsch",
ja: "\u65E5\u672C\u8A9E",
zh: "\u4E2D\u6587"
}
};
const esMessages = {
Common: {
loading: "Cargando...",
error: "Ocurri\xF3 un error",
retry: "Intentar de nuevo",
cancel: "Cancelar",
save: "Guardar",
delete: "Eliminar",
edit: "Editar",
back: "Atr\xE1s",
next: "Siguiente",
previous: "Anterior",
search: "Buscar",
noResults: "No se encontraron resultados"
},
Navigation: {
home: "Inicio",
about: "Acerca de",
contact: "Contacto",
dashboard: "Panel",
settings: "Configuraci\xF3n",
profile: "Perfil"
},
Auth: {
signIn: "Iniciar sesi\xF3n",
signOut: "Cerrar sesi\xF3n",
signUp: "Registrarse",
email: "Correo electr\xF3nico",
password: "Contrase\xF1a",
forgotPassword: "\xBFOlvidaste tu contrase\xF1a?",
rememberMe: "Recordarme",
noAccount: "\xBFNo tienes una cuenta?",
hasAccount: "\xBFYa tienes una cuenta?"
},
Index: {
title: "Bienvenido a AT3 Stack",
description: "Construye aplicaciones nativas de IA con despliegue en el borde",
getStarted: "Comenzar",
learnMore: "Saber m\xE1s"
},
Errors: {
notFound: "P\xE1gina no encontrada",
notFoundDescription: "La p\xE1gina que buscas no existe.",
serverError: "Error del servidor",
serverErrorDescription: "Algo sali\xF3 mal de nuestro lado.",
goHome: "Ir al inicio"
},
LanguageSwitcher: {
label: "Idioma",
en: "English",
es: "Espa\xF1ol",
fr: "Fran\xE7ais",
de: "Deutsch",
ja: "\u65E5\u672C\u8A9E",
zh: "\u4E2D\u6587"
}
};
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(messagesPath, "en.json"), JSON.stringify(enMessages, null, 2));
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(messagesPath, "es.json"), JSON.stringify(esMessages, null, 2));
}
async function addI18nConfig(i18nPath) {
const config = `import { getRequestConfig } from 'next-intl/server'
import { hasLocale } from 'next-intl'
import { routing } from './navigation'
export default getRequestConfig(async ({ requestLocale }) => {
// Typically corresponds to the \`[locale]\` segment
const requested = await requestLocale
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale
return {
locale,
messages: (await import(\`../../../messages/\${locale}.json\`)).default,
}
})
`;
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(i18nPath, "config.ts"), config);
}
async function addI18nRequest(i18nPath) {
const request = `import { getRequestConfig } from 'next-intl/server'
import { routing } from './navigation'
export default getRequestConfig(async ({ requestLocale }) => {
// This typically corresponds to the \`[locale]\` segment
let locale = await requestLocale
// Ensure that the incoming \`locale\` is valid
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale
}
return {
locale,
messages: (await import(\`../../../messages/\${locale}.json\`)).default,
timeZone: 'UTC',
now: new Date(),
}
})
`;
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(i18nPath, "request.ts"), request);
}
async function addI18nNavigation(i18nPath) {
const navigation = `import { createNavigation } from 'next-intl/navigation'
import { defineRouting } from 'next-intl/routing'
export const locales = ['en', 'es'] as const
export const defaultLocale = 'en' as const
export type Locale = (typeof locales)[number]
export const routing = defineRouting({
locales,
defaultLocale,
localePrefix: 'as-needed',
})
// Lightweight wrappers around Next.js' navigation APIs
// that will consider the routing configuration
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing)
`;
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(i18nPath, "navigation.ts"), navigation);
}
async function addI18nMiddleware(srcPath) {
const middleware = `import createMiddleware from 'next-intl/middleware'
import { routing } from './lib/i18n/navigation'
export default createMiddleware(routing)
export const config = {
// Match all pathnames except for
// - ... if they start with \`/api\`, \`/_next\` or \`/_vercel\`
// - ... the ones containing a dot (e.g. \`favicon.ico\`)
matcher: ['/((?!api|_next|_vercel|.*\\\\..*).*)'],
}
`;
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(srcPath, "middleware.ts"), middleware);
}
async function addLocaleProvider(i18nPath) {
const provider = `'use client'
import { NextIntlClientProvider } from 'next-intl'
import type { ReactNode } from 'react'
interface LocaleProviderProps {
children: ReactNode
locale: string
messages: Record<string, unknown>
timeZone?: string
now?: Date
}
export function LocaleProvider({
children,
locale,
messages,
timeZone = 'UTC',
now,
}: LocaleProviderProps) {
return (
<NextIntlClientProvider
locale={locale}
messages={messages}
timeZone={timeZone}
now={now}
>
{children}
</NextIntlClientProvider>
)
}
`;
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(i18nPath, "provider.tsx"), provider);
const index = `export { locales, defaultLocale, routing, type Locale } from './navigation'
export { Link, redirect, usePathname, useRouter, getPathname } from './navigation'
export { LocaleProvider } from './provider'
`;
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(i18nPath, "index.ts"), index);
}
async function addLanguageSwitcher(srcPath) {
const componentPath = (0, import_path5.join)(srcPath, "components", "layout");
await (0, import_fs_extra6.ensureDir)(componentPath);
const switcher = `'use client'
import { useLocale, useTranslations } from 'next-intl'
import { usePathname, useRouter } from '@/lib/i18n/navigation'
import { locales, type Locale } from '@/lib/i18n/navigation'
interface LanguageSwitcherProps {
className?: string
}
export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
const t = useTranslations('LanguageSwitcher')
const locale = useLocale()
const router = useRouter()
const pathname = usePathname()
const handleChange = (newLocale: string) => {
router.replace(pathname, { locale: newLocale as Locale })
}
return (
<div className={className}>
<label htmlFor="language-select" className="sr-only">
{t('label')}
</label>
<select
id="language-select"
value={locale}
onChange={(e) => handleChange(e.target.value)}
className="rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
>
{locales.map((loc) => (
<option key={loc} value={loc}>
{t(loc)}
</option>
))}
</select>
</div>
)
}
/**
* Alternative: Language switcher with buttons
*/
export function LanguageSwitcherButtons({ className }: LanguageSwitcherProps) {
const t = useTranslations('LanguageSwitcher')
const locale = useLocale()
const router = useRouter()
const pathname = usePathname()
return (
<div className={\`flex gap-2 \${className}\`}>
{locales.map((loc) => (
<button
key={loc}
onClick={() => router.replace(pathname, { locale: loc })}
disabled={locale === loc}
className={\`px-3 py-1 text-sm rounded-md transition-colors \${
locale === loc
? 'bg-primary text-primary-foreground'
: 'bg-secondary text-secondary-foreground hover:bg-secondary/80'
}\`}
>
{t(loc)}
</button>
))}
</div>
)
}
`;
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(componentPath, "language-switcher.tsx"), switcher);
}
async function addLocaleLayout(srcPath) {
const localePath = (0, import_path5.join)(srcPath, "app", "[locale]");
await (0, import_fs_extra6.ensureDir)(localePath);
const layout = `import { NextIntlClientProvider, hasLocale } from 'next-intl'
import { getMessages, setRequestLocale } from 'next-intl/server'
import { notFound } from 'next/navigation'
import type { ReactNode } from 'react'
import { routing } from '@/lib/i18n/navigation'
interface LocaleLayoutProps {
children: ReactNode
params: Promise<{ locale: string }>
}
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }))
}
export default async function LocaleLayout({
children,
params,
}: LocaleLayoutProps) {
const { locale } = await params
// Ensure that the incoming \`locale\` is valid
if (!hasLocale(routing.locales, locale)) {
notFound()
}
// Enable static rendering
setRequestLocale(locale)
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages()
return (
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
)
}
`;
await (0, import_fs_extra6.writeFile)((0, import_path5.join)(localePath, "layout.tsx"), layout);
const page = `import { useTranslations } from 'next-intl'
import { setRequestLocale } from 'next-intl/server'
import { Link } from '@/lib/i18n/navigation'
interface HomePageProps {
params: Promise<{ locale: string }>
}
export default async function HomePage({ params }: HomePageProps) {
const { locale } = await params
// Enable static rendering
setRequestLocale(lo