UNPKG

codalware-auth

Version:

Complete authentication system with enterprise security, attack protection, team workspaces, waitlist, billing, UI components, 2FA, and account recovery - production-ready in 5 minutes. Enhanced CLI with verification, rollback, and App Router scaffolding.

993 lines (794 loc) 30.5 kB
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); /** * AuthCore Full Template Copy Script * Copies ALL source code into the developer's project for complete customization */ /** * Copy directory recursively */ function copyDirectoryRecursive(sourceDir, targetDir, options = {}) { const { rewriteImports = false, excludePatterns = [], importAlias = '@/', rewriteBase } = options; if (!fs.existsSync(sourceDir)) { console.warn(`⚠️ Source directory not found: ${sourceDir}`); return []; } const copied = []; if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) { const sourcePath = path.join(sourceDir, entry.name); const targetPath = path.join(targetDir, entry.name); // Check exclusion patterns const shouldExclude = excludePatterns.some(pattern => { if (typeof pattern === 'string') { return entry.name === pattern; } if (pattern instanceof RegExp) { return pattern.test(entry.name); } return false; }); if (shouldExclude) { continue; } if (entry.isDirectory()) { copied.push(...copyDirectoryRecursive(sourcePath, targetPath, options)); } else { copied.push(copyFileWithTransforms(sourcePath, targetPath, { rewriteImports, importAlias, rewriteBase })); } } return copied; } /** * Copy a single file applying optional transforms */ function copyFileWithTransforms(sourcePath, targetPath, options = {}) { const { rewriteImports = false, importAlias = '@/', rewriteBase } = options; const ext = path.extname(sourcePath); const isCodeFile = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.mts'].includes(ext); fs.mkdirSync(path.dirname(targetPath), { recursive: true }); if (!rewriteImports || !isCodeFile) { fs.copyFileSync(sourcePath, targetPath); return targetPath; } const baseDir = rewriteBase || path.dirname(sourcePath); let relativePath = path.relative(baseDir, sourcePath); const isWithinBase = relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath); if (!isWithinBase) { relativePath = ''; } const normalizedRelative = relativePath ? relativePath.replace(/\\/g, '/') : ''; const rewriteBaseDir = isWithinBase ? baseDir : undefined; let content = fs.readFileSync(sourcePath, 'utf8'); content = rewriteImportsToAlias(content, importAlias, normalizedRelative, rewriteBaseDir); fs.writeFileSync(targetPath, content); return targetPath; } /** * Rewrite imports from 'codalware-auth' to local paths */ function rewriteImportsToAlias(content, importAlias = '@/', fileRelativePath = '', baseDir) { const aliasPrefix = importAlias.endsWith('/') ? importAlias : `${importAlias}/`; const posix = path.posix; const toAliasPath = (relativePath) => { const cleaned = relativePath.replace(/^\/+/, ''); return `${aliasPrefix}${cleaned}`; }; const codalwareMappings = [ ['codalware-auth/styles/theme.css', 'styles/theme.css'], ['codalware-auth/styles/tailwind-preset', 'styles/tailwind-preset'], ['codalware-auth/styles', 'styles'], ['codalware-auth/server', 'lib/auth'], ['codalware-auth/validation', 'lib/validation'], ['codalware-auth/utils', 'lib/utils'], ['codalware-auth/hooks', 'hooks'], ['codalware-auth/types', 'types'], ['codalware-auth/email', 'email'], ['codalware-auth/i18n', 'i18n'], ['codalware-auth/components', 'components'], ['codalware-auth', 'components'] ]; const transformSpecifier = (specifier) => { if (!specifier) return specifier; for (const [pkgPath, targetPath] of codalwareMappings) { if (specifier === pkgPath) { return toAliasPath(targetPath); } if (specifier.startsWith(`${pkgPath}/`)) { const remainder = specifier.slice(pkgPath.length + 1); return toAliasPath(`${targetPath}/${remainder}`); } } if (specifier.startsWith('.')) { if (!fileRelativePath || !baseDir) { return specifier; } const fromDir = posix.dirname(fileRelativePath || '.'); const absoluteTarget = path.resolve(baseDir, fromDir, specifier); let relativeTarget = path.relative(baseDir, absoluteTarget); if (!relativeTarget || relativeTarget.startsWith('..') || path.isAbsolute(relativeTarget)) { return specifier; } relativeTarget = relativeTarget.replace(/\\/g, '/'); return toAliasPath(relativeTarget); } return specifier; }; const replaceWithAlias = (match, prefix, quote, specifier, suffix = '') => { const updated = transformSpecifier(specifier); if (updated === specifier) { return match; } return `${prefix}${quote}${updated}${quote}${suffix}`; }; let rewritten = content; rewritten = rewritten.replace(/(from\s+)(['"])([^'"\n]+)\2/g, (match, prefix, quote, spec) => replaceWithAlias(match, prefix, quote, spec) ); rewritten = rewritten.replace(/(import\()(\s*)(['"])([^'"\n]+)\3(\s*)(\))/g, (match, open, preSpace, quote, spec, postSpace, close) => { const updated = transformSpecifier(spec); if (updated === spec) { return match; } return `${open}${preSpace}${quote}${updated}${quote}${postSpace}${close}`; }); rewritten = rewritten.replace(/(require\()(\s*)(['"])([^'"\n]+)\3(\s*)(\))/g, (match, open, preSpace, quote, spec, postSpace, close) => { const updated = transformSpecifier(spec); if (updated === spec) { return match; } return `${open}${preSpace}${quote}${updated}${quote}${postSpace}${close}`; }); return rewritten; } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /** * Copy all components */ function copyComponents(packageRoot, projectRoot) { console.log('📦 Copying components...'); const sourceDir = path.join(packageRoot, 'src', 'components'); const targetDir = path.join(projectRoot, 'src', 'components', 'authcore'); const copied = copyDirectoryRecursive(sourceDir, targetDir, { rewriteImports: true, excludePatterns: ['.eslintrc.json', 'index.ts'], rewriteBase: path.join(packageRoot, 'src') }); console.log(`✅ Copied ${copied.length} component files to src/components/authcore/\n`); return copied; } /** * Copy all hooks */ function copyHooks(packageRoot, projectRoot) { console.log('📦 Copying hooks...'); const sourceDir = path.join(packageRoot, 'src', 'hooks'); const targetDir = path.join(projectRoot, 'src', 'hooks', 'authcore'); const copied = copyDirectoryRecursive(sourceDir, targetDir, { rewriteImports: true, excludePatterns: ['.eslintrc.json', 'index.ts'], rewriteBase: path.join(packageRoot, 'src') }); console.log(`✅ Copied ${copied.length} hook files to src/hooks/authcore/\n`); return copied; } /** * Copy server utilities */ function copyServerUtils(packageRoot, projectRoot) { console.log('📦 Copying server utilities...'); const copied = []; // Copy auth logic const authSource = path.join(packageRoot, 'src', 'auth'); const authTarget = path.join(projectRoot, 'src', 'lib', 'auth'); copied.push(...copyDirectoryRecursive(authSource, authTarget, { rewriteImports: true, excludePatterns: ['.eslintrc.json'], rewriteBase: path.join(packageRoot, 'src') })); // Copy validation const validationSource = path.join(packageRoot, 'src', 'validation'); const validationTarget = path.join(projectRoot, 'src', 'lib', 'validation'); copied.push(...copyDirectoryRecursive(validationSource, validationTarget, { rewriteImports: true, excludePatterns: ['.eslintrc.json'], rewriteBase: path.join(packageRoot, 'src') })); // Copy utils const utilsSource = path.join(packageRoot, 'src', 'utils'); const utilsTarget = path.join(projectRoot, 'src', 'lib', 'utils'); if (fs.existsSync(utilsSource)) { copied.push(...copyDirectoryRecursive(utilsSource, utilsTarget, { rewriteImports: true, excludePatterns: ['.eslintrc.json'], rewriteBase: path.join(packageRoot, 'src') })); } console.log(`✅ Copied ${copied.length} server utility files to src/lib/\n`); return copied; } /** * Copy types */ function copyTypes(packageRoot, projectRoot) { console.log('📦 Copying type definitions...'); const sourceDir = path.join(packageRoot, 'src', 'types'); const targetDir = path.join(projectRoot, 'src', 'types', 'authcore'); const copied = copyDirectoryRecursive(sourceDir, targetDir, { rewriteImports: true, rewriteBase: path.join(packageRoot, 'src') }); console.log(`✅ Copied ${copied.length} type files to src/types/authcore/\n`); return copied; } /** * Copy i18n */ function copyI18n(packageRoot, projectRoot) { console.log('📦 Copying i18n translations...'); const sourceDir = path.join(packageRoot, 'src', 'i18n'); const targetDir = path.join(projectRoot, 'src', 'lib', 'i18n'); const copied = copyDirectoryRecursive(sourceDir, targetDir, { rewriteImports: true, rewriteBase: path.join(packageRoot, 'src') }); // Copy locale files const localesSource = path.join(packageRoot, 'locales'); const localesTarget = path.join(projectRoot, 'locales'); if (fs.existsSync(localesSource)) { copied.push(...copyDirectoryRecursive(localesSource, localesTarget)); } console.log(`✅ Copied ${copied.length} i18n files\n`); return copied; } /** * Copy API routes from templates * Respects src/ directory structure if it exists */ function copyApiRoutes(packageRoot, projectRoot, routerType = 'app') { console.log('🔌 Copying API routes...'); const copied = []; // Detect if project uses src/ directory const useSrcDirectory = fs.existsSync(path.join(projectRoot, 'src')); const baseTarget = useSrcDirectory ? path.join(projectRoot, 'src') : projectRoot; if (useSrcDirectory) { console.log(' 📂 Using src/ directory structure for API routes'); } if (routerType === 'app') { // App Router API routes const appApiSource = path.join(packageRoot, 'templates', 'app', 'api'); const appApiTarget = path.join(baseTarget, 'app', 'api'); if (fs.existsSync(appApiSource)) { copied.push(...copyDirectoryRecursive(appApiSource, appApiTarget, { rewriteImports: true, rewriteBase: path.join(packageRoot, 'templates', 'app') })); } // App Router actions const appActionsSource = path.join(packageRoot, 'templates', 'app', 'actions'); const appActionsTarget = path.join(baseTarget, 'app', 'actions'); if (fs.existsSync(appActionsSource)) { copied.push(...copyDirectoryRecursive(appActionsSource, appActionsTarget, { rewriteImports: true, rewriteBase: path.join(packageRoot, 'templates', 'app') })); } // App Router services const appServicesSource = path.join(packageRoot, 'templates', 'app', 'services'); const appServicesTarget = path.join(baseTarget, 'app', 'services'); if (fs.existsSync(appServicesSource)) { copied.push(...copyDirectoryRecursive(appServicesSource, appServicesTarget, { rewriteImports: true, rewriteBase: path.join(packageRoot, 'templates', 'app') })); } } else if (routerType === 'pages') { // Pages Router API routes const pagesApiSource = path.join(packageRoot, 'templates', 'pages', 'api'); const pagesApiTarget = path.join(baseTarget, 'pages', 'api'); if (fs.existsSync(pagesApiSource)) { copied.push(...copyDirectoryRecursive(pagesApiSource, pagesApiTarget, { rewriteImports: true, rewriteBase: path.join(packageRoot, 'templates', 'pages') })); } } console.log(`✅ Copied ${copied.length} API route files\n`); return copied; } /** * Create index files for easy imports */ function createIndexFiles(projectRoot) { console.log('📝 Creating index files...'); // Components index const componentsIndex = `/** * AuthCore Components * All authentication components - fully customizable * * Generated by: npx authcore init --template-mode=full * You own this code - modify as needed! */ // Auth Forms export { default as LoginForm } from './LoginForm'; export { default as RegisterForm } from './RegisterForm'; export { default as AuthForm } from './AuthForm'; // 2FA export { default as TwoFactorSetup } from './TwoFactorSetup'; // User Management export { default as UserProfile } from './UserProfile'; export { default as ProfileDropdown } from './ProfileDropdown'; export { default as ProfileTabs } from './ProfileTabs'; // Organization export { CreateOrganization } from './organization/CreateOrganization'; export { OrganizationSwitcher } from './organization/OrganizationSwitcher'; // Security export { SecuritySettings } from './security/SecuritySettings'; export { EmailListManager } from './security/EmailListManager'; // Layout export { default as DashboardLayout } from './DashboardLayout'; export { default as PageLayout } from './PageLayout'; // HOCs export { default as withAuthentication } from './withAuthentication'; // Utility export { default as LoadingSpinner } from './LoadingSpinner'; export { default as ThemeSwitcher } from './ThemeSwitcher'; `; fs.writeFileSync( path.join(projectRoot, 'src', 'components', 'authcore', 'index.ts'), componentsIndex ); // Hooks index const hooksIndex = `/** * AuthCore Hooks * All authentication hooks - fully customizable * * Generated by: npx authcore init --template-mode=full * You own this code - modify as needed! */ export { useAuth } from './useAuth'; export { useLogin } from './useLogin'; export { useRegister } from './useRegister'; export { useTwoFactor } from './useTwoFactor'; export { useAuthPolicy } from './useAuthPolicy'; export { useMagicLink } from './useMagicLink'; export { usePasswordChange } from './usePasswordChange'; export { useUserProfile } from './useUserProfile'; export { useUserDevices } from './useUserDevices'; export { useAuditLogs } from './useAuditLogs'; export { useMutations } from './useMutations'; `; fs.writeFileSync( path.join(projectRoot, 'src', 'hooks', 'authcore', 'index.ts'), hooksIndex ); console.log('✅ Created index files\n'); /** * Create README explaining the template */ function createTemplateReadme(projectRoot) { const readme = `# AuthCore - Full Template Mode You've installed AuthCore in **Full Template Mode**! 🎉 ## What This Means Instead of importing components from \`codalware-auth\`, you now have: - ✅ **Complete source code** in your project - ✅ **Full customization** - modify any file - ✅ **No dependency lock-in** - the code is yours - ✅ **Easy debugging** - all code is visible ## Project Structure \`\`\` src/ ├── components/authcore/ ← All UI components │ ├── LoginForm.tsx │ ├── RegisterForm.tsx │ ├── AuthForm.tsx │ └── ... (all other components) │ ├── hooks/authcore/ ← All authentication hooks │ ├── useAuth.ts │ ├── useLogin.ts │ └── ... (all other hooks) │ ├── lib/ │ ├── auth/ ← Server-side auth logic │ ├── validation/ ← Form validation schemas │ ├── i18n/ ← Internationalization │ └── utils/ ← Utility functions │ ├── types/authcore/ ← TypeScript definitions │ └── app/api/auth/ ← API routes (already generated) styles/ ← AuthCore styles and Tailwind preset ├── tailwind-preset.js ← Tailwind CSS preset └── authcore-theme.css ← Compiled theme styles \`\`\` ## Usage ### Import Components \`\`\`typescript // Instead of: // import { LoginForm } from 'codalware-auth'; // Use: import { LoginForm } from '@/components/authcore'; \`\`\` ### Import Hooks \`\`\`typescript // Instead of: // import { useAuth } from 'codalware-auth'; // Use: import { useAuth } from '@/hooks/authcore'; \`\`\` ### Import Server Utils \`\`\`typescript // Instead of: // import { AuthService } from 'codalware-auth/server'; // Use: import { AuthService } from '@/lib/auth'; \`\`\` ## Customization ### Modify Components Want to change the LoginForm design? Just edit: \`src/components/authcore/LoginForm.tsx\` ### Modify Auth Logic Want to change registration flow? Edit: \`src/lib/auth/logic/auth.ts\` ### Modify Validation Want custom password rules? Edit: \`src/lib/validation/schemas.ts\` ## Package Dependency The \`codalware-auth\` package is now **optional**. You can: 1. Keep it for potential updates 2. Remove it completely - your code will still work! To remove the dependency: \`\`\`bash npm uninstall codalware-auth \`\`\` ## Updates Since you own the code, updates work differently: 1. Check for new features: \`npx authcore check-updates\` 2. See what changed: \`npx authcore diff\` 3. Manually apply updates you want Or regenerate specific parts: \`\`\`bash npx authcore generate components # Regenerate components npx authcore generate hooks # Regenerate hooks \`\`\` ## Support - **Documentation**: https://authcore-liard.vercel.app/ - **Examples**: https://authcore-liard.vercel.app/examples - **Customization Guide**: https://authcore-liard.vercel.app/guides/customization --- **You own this code!** Modify it however you want. Good luck! 🚀 `; fs.writeFileSync( path.join(projectRoot, 'AUTHCORE_TEMPLATE_README.md'), readme ); console.log('✅ Created AUTHCORE_TEMPLATE_README.md\n'); } /** * Create .env.example file for environment setup */ function createEnvExample(projectRoot) { const envPath = path.join(projectRoot, '.env.example'); if (fs.existsSync(envPath)) { console.log('✅ .env.example already exists\n'); return; } console.log('📝 Creating .env.example...'); const envExample = `# AuthCore Environment Variables # Copy this file to .env.local and fill in your values # Database DATABASE_URL="postgresql://username:password@localhost:5432/authcore" # Email Configuration (choose one method) # Method 1: Gmail (App Password) GMAIL_ACCOUNT_EMAIL="your-email@gmail.com" GOOGLE_PASS="your-app-password" # Method 2: SMTP Server EMAIL_SERVER_HOST="smtp.gmail.com" EMAIL_SERVER_PORT="587" EMAIL_SERVER_USER="your-email@gmail.com" EMAIL_SERVER_PASSWORD="your-password" # Admin User (for initial setup) ADMIN_EMAIL="admin@example.com" ADMIN_PASSWORD="secure-admin-password" ADMIN_NAME="Admin User" # Development NODE_ENV="development" # Skip wizard on startup (optional) SKIP_AUTHCORE_WIZARD="false" # Optional: NextAuth (if using NextAuth provider) NEXTAUTH_SECRET="your-random-secret-generate-with-openssl-rand-base64-32" NEXTAUTH_URL="http://localhost:3000" # Optional: Social Providers # GOOGLE_CLIENT_ID="your-google-client-id" # GOOGLE_CLIENT_SECRET="your-google-client-secret" # GITHUB_CLIENT_ID="your-github-client-id" # GITHUB_CLIENT_SECRET="your-github-client-secret" # Optional: Stripe (for billing features) # STRIPE_SECRET_KEY="sk_test_..." # STRIPE_WEBHOOK_SECRET="whsec_..." # NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..." # Optional: Resend (alternative to SMTP) # RESEND_API_KEY="re_..." # Optional: Development settings # LOG_LEVEL="debug" `; fs.writeFileSync(envPath, envExample); console.log('✅ Created .env.example\n'); } /** * Copy AuthCore as main source code (flat structure) * This is the actual implementation for main template mode */ async function copyMainTemplate(options = {}) { const packageRoot = path.join(__dirname, '..'); const projectRoot = process.cwd(); const srcBase = path.join(packageRoot, 'src'); console.log('🏠 Copying AuthCore as main source code...\n'); console.log('📍 From:', packageRoot); console.log('📍 To:', projectRoot); console.log(''); // Detect project structure (src or root) const useSrcDirectory = fs.existsSync(path.join(projectRoot, 'src')); const baseTarget = useSrcDirectory ? path.join(projectRoot, 'src') : projectRoot; if (useSrcDirectory) { console.log('📂 Detected "src" directory structure.'); } else { console.log('📂 Detected root directory structure.'); } console.log(''); const allCopied = []; try { console.log('📦 Copying AuthCore source tree...'); const srcEntries = fs.readdirSync(srcBase, { withFileTypes: true }); for (const entry of srcEntries) { const sourcePath = path.join(srcBase, entry.name); const targetPath = path.join(baseTarget, entry.name); if (entry.isDirectory()) { allCopied.push(...copyDirectoryRecursive(sourcePath, targetPath, { rewriteImports: true, rewriteBase: srcBase })); } else { const rewriteImports = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.mts'].includes(path.extname(entry.name)); const copied = copyFileWithTransforms(sourcePath, targetPath, { rewriteImports, rewriteBase: srcBase }); if (copied) { allCopied.push(copied); } } } console.log(`✅ Copied ${allCopied.length} files from src/ tree\n`); // Copy API routes - detect router type const hasAppDir = fs.existsSync(path.join(projectRoot, 'app')) || (useSrcDirectory && fs.existsSync(path.join(projectRoot, 'src', 'app'))); const hasPagesDir = fs.existsSync(path.join(projectRoot, 'pages')) || (useSrcDirectory && fs.existsSync(path.join(projectRoot, 'src', 'pages'))); const routerType = hasAppDir ? 'app' : (hasPagesDir ? 'pages' : 'app'); // default to app allCopied.push(...copyApiRoutes(packageRoot, projectRoot, routerType)); // Copy supporting root-level directories const rootDirectories = ['config', 'locales', 'styles', 'prisma']; for (const dir of rootDirectories) { const dirSource = path.join(packageRoot, dir); if (fs.existsSync(dirSource)) { const dirTarget = path.join(projectRoot, dir); allCopied.push(...copyDirectoryRecursive(dirSource, dirTarget)); } } // Copy root-level config file const rootConfigPath = path.join(packageRoot, 'config.ts'); if (fs.existsSync(rootConfigPath)) { const copied = copyFileWithTransforms(rootConfigPath, path.join(projectRoot, 'config.ts'), { rewriteImports: false }); if (copied) { allCopied.push(copied); } } // Create README createMainTemplateReadme(projectRoot); // Create .env.example createEnvExample(projectRoot); console.log('✨ Main template copy complete!\n'); console.log(`📦 Copied ${allCopied.length} source files`); console.log(''); console.log('📖 Read AUTHCORE_TEMPLATE_README.md for usage instructions'); console.log(''); console.log('💡 Quick Start:'); console.log(` import { LoginForm } from '@/components';`); console.log(` import { useAuth } from '@/hooks';`); console.log(''); console.log(`🏠 Your ${useSrcDirectory ? 'src/' : ''} directory is now AuthCore - customize everything!`); console.log(''); return allCopied; } catch (error) { console.error('❌ Failed to copy main template:', error.message); if (process.env.DEBUG) { console.error(error); } process.exit(1); } } /** * Create README for main template */ function createMainTemplateReadme(projectRoot) { const readmePath = path.join(projectRoot, 'AUTHCORE_TEMPLATE_README.md'); const content = `# AuthCore Main Template This project was initialized with AuthCore as the main source code. AuthCore components, hooks, and utilities are now your primary codebase. ## What's Included - **Components**: All AuthCore UI components in \`src/components/\` - **Hooks**: Authentication hooks in \`src/hooks/\` - **Server Utils**: Authentication logic in \`src/lib/auth/\` - **Validation**: Form validation utilities in \`src/lib/validation/\` - **Utils**: Helper functions in \`src/lib/utils/\` - **I18n**: Internationalization in \`src/lib/i18n/\` - **Types**: TypeScript definitions in \`src/types/\` - **Styles**: Tailwind preset and CSS in \`styles/\` - **Locales**: Translation files in \`locales/\` ## Quick Start \`\`\`tsx import { LoginForm, RegisterForm } from '@/components'; import { useAuth } from '@/hooks'; export default function App() { const { user, login, logout } = useAuth(); return ( <div> {user ? ( <div> <p>Welcome, {user.email}!</p> <button onClick={logout}>Logout</button> </div> ) : ( <LoginForm onSubmit={login} /> )} </div> ); } \`\`\` ## Configuration 1. Copy \`.env.example\` to \`.env.local\` 2. Configure your database and email settings 3. Run database migrations: \`npm run migrate\` 4. Start the development server: \`npm run dev\` ## Customization Since AuthCore is now your main source code, you can: - Modify any component in \`src/components/\` - Add new authentication flows in \`src/hooks/\` - Extend server utilities in \`src/lib/\` - Update styles in \`styles/\` - Add new locales in \`locales/\` ## Database Setup This template includes Prisma schema and migrations. To set up: \`\`\`bash # Install dependencies npm install # Set up database npm run setup-db # Run migrations npm run migrate # Seed database (optional) npm run seed \`\`\` ## API Routes AuthCore provides the following API routes: - \`POST /api/auth/login\` - User login - \`POST /api/auth/register\` - User registration - \`POST /api/auth/logout\` - User logout - \`GET /api/auth/session\` - Get current session - \`POST /api/auth/forgot-password\` - Request password reset - \`POST /api/auth/reset-password\` - Reset password ## Next Steps 1. Review and customize the components in \`src/components/\` 2. Configure your authentication providers in \`src/lib/auth/\` 3. Set up your database connection in \`.env.local\` 4. Test the authentication flows 5. Deploy your application ## Support For questions or issues, check the [AuthCore documentation](https://authcore.dev/docs) or create an issue on GitHub. `; fs.writeFileSync(readmePath, content); } /** * Copy AuthCore as full template with authcore/ subdirectories */ function copyFullTemplate(options = {}) { const packageRoot = path.join(__dirname, '..'); const projectRoot = process.cwd(); const srcRoot = path.join(projectRoot, 'src'); console.log('📦 Copying AuthCore full template...\n'); console.log('📍 From:', packageRoot); console.log('📍 To:', projectRoot); console.log(''); if (!fs.existsSync(srcRoot)) { console.warn('⚠️ No src/ directory detected. Creating src/ before copying template files.'); fs.mkdirSync(srcRoot, { recursive: true }); } const allCopied = []; try { const srcDirectories = [ 'adapters', 'auth', 'components', 'hooks', 'i18n', 'lib', 'types', 'utils', 'validation', 'email', 'config' ]; const srcBase = path.join(packageRoot, 'src'); for (const dir of srcDirectories) { const sourceDir = path.join(srcBase, dir); const targetDir = path.join(srcRoot, dir); allCopied.push(...copyDirectoryRecursive(sourceDir, targetDir, { rewriteImports: true, rewriteBase: srcBase })); } const srcFiles = ['errors.ts', 'index.ts', 'pack-index.ts']; for (const file of srcFiles) { const copied = copyFileWithTransforms( path.join(srcBase, file), path.join(srcRoot, file), { rewriteImports: true, rewriteBase: srcBase } ); if (copied) { allCopied.push(copied); } } const rootDirectories = ['config', 'locales', 'styles', 'prisma']; for (const dir of rootDirectories) { const sourceDir = path.join(packageRoot, dir); const targetDir = path.join(projectRoot, dir); allCopied.push(...copyDirectoryRecursive(sourceDir, targetDir)); } const rootFiles = ['config.ts']; for (const file of rootFiles) { const copied = copyFileWithTransforms( path.join(packageRoot, file), path.join(projectRoot, file), { rewriteImports: false } ); if (copied) { allCopied.push(copied); } } // Create .env.example if missing createEnvExample(projectRoot); console.log('✨ Full template copy complete!\n'); console.log(`📦 Copied ${allCopied.length} files into your project`); console.log(''); console.log('💡 Quick Start:'); console.log(' import { LoginForm } from \"@/components\";'); console.log(' import { useAuth } from \"@/hooks\";'); console.log(''); console.log('🔧 AuthCore source is now available directly under src/ — customize freely.'); console.log(''); return allCopied; } catch (error) { console.error('❌ Failed to copy full template:', error.message); if (process.env.DEBUG) { console.error(error); } process.exit(1); } } // Export for use in CLI (CommonJS exports) module.exports = { copyFullTemplate, copyMainTemplate }; // Run if called directly if (require.main === module) { copyFullTemplate().catch(err => { console.error('Failed:', err); process.exit(1); }); }