/
githubmirror
/
awesome-chatgpt-prompts
Обзор
Документация
Войти
/
githubmirror
/
awesome-chatgpt-prompts
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
scripts/setup.js
596 строк
19 KB
Fatih Kadir Akın
chore(scripts): update database setup in setup.js
06 янв 2026, 10:55
06 янв 2026, 10:55
89a5424
Код
Авторство
О чём код?
#!/usr/bin/env node /* eslint-disable @typescript-eslint/no-require-imports */ /** * Interactive setup script for private prompts.chat clones. * Configures branding, theme, authentication, and features. * * Usage: node scripts/setup.js */ const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const { execSync, spawn } = require('child_process'); const p = require('@clack/prompts'); const color = require('picocolors'); const CONFIG_FILE = path.join(__dirname, '..', 'prompts.config.ts'); const ENV_FILE = path.join(__dirname, '..', '.env'); function generateAuthSecret() { return crypto.randomBytes(32).toString('base64'); } function buildDatabaseUrl(config) { const { host, port, username, password, database } = config; return `postgresql://${username}:${password}@${host}:${port}/${database}`; } function generateEnvFile(config) { const isLocalhost = config.env.domain.includes('localhost'); const protocol = isLocalhost ? 'http' : 'https'; let envContent = `# Generated by setup script # prompts.chat environment configuration # Database DATABASE_URL="${config.env.databaseUrl}" # Authentication AUTH_SECRET="${config.env.authSecret}" AUTH_URL="${protocol}://${config.env.domain}" AUTH_TRUST_HOST=true # Cron Job Secret CRON_SECRET="${crypto.randomBytes(16).toString('hex')}" `; if (config.auth.providers.includes('github')) { envContent += ` # GitHub OAuth (configure at https://github.com/settings/developers) AUTH_GITHUB_ID="" AUTH_GITHUB_SECRET="" `; } if (config.auth.providers.includes('google')) { envContent += ` # Google OAuth (configure at https://console.cloud.google.com/apis/credentials) AUTH_GOOGLE_ID="" AUTH_GOOGLE_SECRET="" `; } if (config.auth.providers.includes('apple')) { envContent += ` # Apple Sign In AUTH_APPLE_ID="" AUTH_APPLE_SECRET="" `; } if (config.auth.providers.includes('azure')) { envContent += ` # Microsoft Azure AD AUTH_AZURE_AD_CLIENT_ID="" AUTH_AZURE_AD_CLIENT_SECRET="" AUTH_AZURE_AD_ISSUER="" `; } if (config.features.aiSearch || config.features.aiGeneration) { envContent += ` # AI Features OPENAI_API_KEY="" # OPENAI_BASE_URL=https://api.openai.com/v1 # OPENAI_EMBEDDING_MODEL=text-embedding-3-small # OPENAI_GENERATIVE_MODEL=gpt-4o-mini `; } return envContent; } function generateConfig(config) { const sponsorsSection = config.sponsors.length > 0 ? `[ ${config.sponsors.map(s => `{ name: "${s.name}", logo: "${s.logo}", url: "${s.url}" }`).join(',\n ')} ]` : '[]'; return `import { defineConfig } from "@/lib/config"; // Private clone configuration - generated by setup script const useCloneBranding = true; export default defineConfig({ // Branding - your organization's identity branding: { name: "${config.branding.name}", logo: "${config.branding.logo}", logoDark: "${config.branding.logoDark}", favicon: "${config.branding.favicon}", description: "${config.branding.description}", }, // Theme - design system configuration theme: { radius: "${config.theme.radius}", variant: "${config.theme.variant}", density: "${config.theme.density}", colors: { primary: "${config.theme.primaryColor}", }, }, // Authentication plugins auth: { providers: [${config.auth.providers.map(provider => `"${provider}"`).join(', ')}], allowRegistration: ${config.auth.allowRegistration}, }, // Internationalization i18n: { locales: [${config.i18n.locales.map(l => `"${l}"`).join(', ')}], defaultLocale: "${config.i18n.defaultLocale}", }, // Features features: { privatePrompts: ${config.features.privatePrompts}, changeRequests: ${config.features.changeRequests}, categories: ${config.features.categories}, tags: ${config.features.tags}, comments: ${config.features.comments}, aiSearch: ${config.features.aiSearch}, aiGeneration: ${config.features.aiGeneration}, mcp: ${config.features.mcp}, }, // Homepage customization (clone branding mode) homepage: { useCloneBranding, achievements: { enabled: false, // Disabled for private clones }, sponsors: { enabled: ${config.sponsors.length > 0}, items: ${sponsorsSection}, }, }, }); `; } function handleCancel() { p.cancel('Setup cancelled.'); process.exit(0); } async function main() { console.clear(); p.intro(color.bgCyan(color.black(' prompts.chat - Private Clone Setup '))); const config = { env: {}, branding: {}, theme: {}, auth: {}, i18n: {}, features: {}, sponsors: [] }; // === ENVIRONMENT === p.log.step(color.cyan('Environment Configuration')); const envConfig = await p.group({ domain: () => p.text({ message: 'Application domain', placeholder: 'localhost:3000', defaultValue: 'localhost:3000', }), }, { onCancel: handleCancel }); config.env.domain = envConfig.domain; config.env.authSecret = generateAuthSecret(); // === DATABASE === p.log.step(color.cyan('Database Configuration')); const dbConfig = await p.group({ host: () => p.text({ message: 'Database host', placeholder: 'localhost', defaultValue: 'localhost', }), port: () => p.text({ message: 'Database port', placeholder: '5432', defaultValue: '5432', }), database: () => p.text({ message: 'Database name', placeholder: 'prompts', defaultValue: 'prompts', }), username: () => p.text({ message: 'Database username', placeholder: 'postgres', defaultValue: 'postgres', }), password: () => p.password({ message: 'Database password', mask: '*', }), }, { onCancel: handleCancel }); config.env.database = dbConfig; config.env.databaseUrl = buildDatabaseUrl(dbConfig); // === BRANDING === p.log.step(color.cyan('Branding')); const branding = await p.group({ name: () => p.text({ message: 'Organization/App name', placeholder: 'My Prompt Library', defaultValue: 'My Prompt Library', }), description: () => p.text({ message: 'Description', placeholder: 'Collect, organize, and share AI prompts', defaultValue: 'Collect, organize, and share AI prompts', }), logo: () => p.text({ message: 'Logo path (public folder)', placeholder: '/logo.svg', defaultValue: '/logo.svg', }), logoDark: ({ results }) => p.text({ message: 'Dark mode logo path', placeholder: results.logo || '/logo.svg', defaultValue: results.logo || '/logo.svg', }), favicon: () => p.text({ message: 'Favicon path', placeholder: '/logo.svg', defaultValue: '/logo.svg', }), }, { onCancel: handleCancel }); config.branding = branding; // === THEME === p.log.step(color.cyan('Theme')); const theme = await p.group({ primaryColor: () => p.text({ message: 'Primary color (hex)', placeholder: '#6366f1', defaultValue: '#6366f1', validate: (value) => { if (!/^#[0-9A-Fa-f]{6}$/.test(value)) { return 'Please enter a valid hex color (e.g., #6366f1)'; } }, }), radius: () => p.select({ message: 'Border radius style', initialValue: 'sm', options: [ { value: 'none', label: 'None', hint: 'Sharp corners' }, { value: 'sm', label: 'Small', hint: 'Subtle rounding' }, { value: 'md', label: 'Medium', hint: 'Moderate rounding' }, { value: 'lg', label: 'Large', hint: 'Very rounded' }, ], }), variant: () => p.select({ message: 'UI variant', initialValue: 'default', options: [ { value: 'default', label: 'Default', hint: 'Standard modern look' }, { value: 'flat', label: 'Flat', hint: 'Minimal shadows' }, { value: 'brutal', label: 'Brutal', hint: 'Bold neo-brutalist style' }, ], }), density: () => p.select({ message: 'Spacing density', initialValue: 'default', options: [ { value: 'compact', label: 'Compact', hint: 'Tighter spacing' }, { value: 'default', label: 'Default', hint: 'Balanced spacing' }, { value: 'comfortable', label: 'Comfortable', hint: 'More breathing room' }, ], }), }, { onCancel: handleCancel }); config.theme = theme; // === AUTHENTICATION === p.log.step(color.cyan('Authentication')); const authProviders = await p.multiselect({ message: 'Select authentication providers', options: [ { value: 'credentials', label: 'Email/Password', hint: 'Traditional auth (recommended)' }, { value: 'github', label: 'GitHub OAuth', hint: 'Most popular for developers' }, { value: 'google', label: 'Google OAuth', hint: 'Widely used' }, { value: 'apple', label: 'Apple Sign In', hint: 'Sign in with Apple' }, { value: 'azure', label: 'Microsoft Azure AD', hint: 'Enterprise SSO' }, ], initialValues: ['credentials'], required: false, }); if (p.isCancel(authProviders)) handleCancel(); config.auth.providers = authProviders.length > 0 ? authProviders : ['credentials']; if (config.auth.providers.length === 0) { p.log.warn('No providers selected, defaulting to credentials'); config.auth.providers = ['credentials']; } if (config.auth.providers.includes('credentials')) { const allowReg = await p.confirm({ message: 'Allow public registration?', initialValue: true, }); if (p.isCancel(allowReg)) handleCancel(); config.auth.allowRegistration = allowReg; } else { config.auth.allowRegistration = false; } // === INTERNATIONALIZATION === p.log.step(color.cyan('Internationalization')); const selectedLocales = await p.multiselect({ message: 'Select supported languages', options: [ { value: 'en', label: 'English', hint: 'Default' }, { value: 'es', label: 'Spanish' }, { value: 'fr', label: 'French' }, { value: 'de', label: 'German' }, { value: 'it', label: 'Italian' }, { value: 'pt', label: 'Portuguese' }, { value: 'tr', label: 'Turkish' }, { value: 'az', label: 'Azerbaijani' }, { value: 'ja', label: 'Japanese' }, { value: 'ko', label: 'Korean' }, { value: 'zh', label: 'Chinese' }, { value: 'ru', label: 'Russian' }, { value: 'ar', label: 'Arabic', hint: 'RTL' }, { value: 'fa', label: 'Persian', hint: 'RTL' }, { value: 'he', label: 'Hebrew', hint: 'RTL' }, { value: 'el', label: 'Greek' }, ], required: false, }); if (p.isCancel(selectedLocales)) handleCancel(); config.i18n.locales = selectedLocales.length > 0 ? selectedLocales : ['en']; if (config.i18n.locales.length > 1) { const defaultLocale = await p.select({ message: 'Default locale', options: config.i18n.locales.map(l => ({ value: l, label: l })), }); if (p.isCancel(defaultLocale)) handleCancel(); config.i18n.defaultLocale = defaultLocale; } else { config.i18n.defaultLocale = config.i18n.locales[0]; } // === FEATURES === p.log.step(color.cyan('Features')); const features = await p.multiselect({ message: 'Enable features', options: [ { value: 'privatePrompts', label: 'Private Prompts', hint: 'Users can create private prompts' }, { value: 'changeRequests', label: 'Change Requests', hint: 'Version control system' }, { value: 'categories', label: 'Categories', hint: 'Organize prompts by category' }, { value: 'tags', label: 'Tags', hint: 'Tag-based organization' }, { value: 'comments', label: 'Comments', hint: 'Allow comments on prompts' }, { value: 'aiSearch', label: 'AI Search', hint: 'Requires OPENAI_API_KEY' }, { value: 'aiGeneration', label: 'AI Generation', hint: 'AI-powered prompt generation (requires OPENAI_API_KEY)' }, { value: 'mcp', label: 'MCP Support', hint: 'Model Context Protocol features & API keys' }, ], initialValues: ['privatePrompts', 'changeRequests', 'categories', 'tags', 'comments'], required: false, }); if (p.isCancel(features)) handleCancel(); config.features = { privatePrompts: features.includes('privatePrompts'), changeRequests: features.includes('changeRequests'), categories: features.includes('categories'), tags: features.includes('tags'), comments: features.includes('comments'), aiSearch: features.includes('aiSearch'), aiGeneration: features.includes('aiGeneration'), mcp: features.includes('mcp'), }; // === SPONSORS === p.log.step(color.cyan('Sponsors (Optional)')); const addSponsors = await p.confirm({ message: 'Add sponsor logos to homepage?', initialValue: false, }); if (p.isCancel(addSponsors)) handleCancel(); if (addSponsors) { let addMore = true; while (addMore) { const sponsor = await p.group({ name: () => p.text({ message: 'Sponsor name', placeholder: 'Acme Inc' }), logo: () => p.text({ message: 'Logo URL', placeholder: '/sponsors/acme.svg' }), url: () => p.text({ message: 'Website URL', placeholder: 'https://acme.com' }), }, { onCancel: handleCancel }); if (sponsor.name && sponsor.logo && sponsor.url) { config.sponsors.push(sponsor); p.log.success(`Added ${sponsor.name}`); } const another = await p.confirm({ message: 'Add another sponsor?', initialValue: false, }); if (p.isCancel(another)) handleCancel(); addMore = another; } } // === SUMMARY === p.log.step(color.cyan('Configuration Summary')); const summaryLines = [ `${color.dim('Name:')} ${config.branding.name}`, `${color.dim('Description:')} ${config.branding.description}`, `${color.dim('Primary Color:')} ${config.theme.primaryColor}`, `${color.dim('UI Style:')} ${config.theme.variant} / ${config.theme.radius} radius`, `${color.dim('Auth:')} ${config.auth.providers.join(', ')}`, `${color.dim('Languages:')} ${config.i18n.locales.join(', ')}`, `${color.dim('Features:')} ${Object.entries(config.features).filter(([,v]) => v).map(([k]) => k).join(', ')}`, ]; if (config.sponsors.length > 0) { summaryLines.push(`${color.dim('Sponsors:')} ${config.sponsors.map(s => s.name).join(', ')}`); } p.note(summaryLines.join('\n'), 'Review your configuration'); const proceed = await p.confirm({ message: 'Generate configuration file?', initialValue: true, }); if (p.isCancel(proceed) || !proceed) { p.cancel('Setup cancelled.'); process.exit(0); } // === GENERATE CONFIG === const s = p.spinner(); s.start('Generating configuration...'); // Backup existing config if it exists if (fs.existsSync(CONFIG_FILE)) { const backupPath = CONFIG_FILE + '.backup'; fs.copyFileSync(CONFIG_FILE, backupPath); } const configContent = generateConfig(config); fs.writeFileSync(CONFIG_FILE, configContent); s.stop('Generated prompts.config.ts'); // === ENV FILE === s.start('Creating .env file...'); const envContent = generateEnvFile(config); fs.writeFileSync(ENV_FILE, envContent); s.stop('Created .env file'); // === DATABASE SETUP === const setupDb = await p.confirm({ message: 'Create database and run migrations now?', initialValue: true, }); if (!p.isCancel(setupDb) && setupDb) { // Create database s.start(`Creating database "${config.env.database.database}"...`); try { const createDbCmd = `createdb -h ${config.env.database.host} -p ${config.env.database.port} -U ${config.env.database.username} ${config.env.database.database}`; execSync(createDbCmd, { stdio: 'pipe', env: { ...process.env, PGPASSWORD: config.env.database.password } }); s.stop(`Created database "${config.env.database.database}"`); } catch (err) { if (err.message.includes('already exists')) { s.stop(`Database "${config.env.database.database}" already exists`); } else { s.stop(color.yellow(`Could not create database (may already exist or createdb not available)`)); p.log.warn(`You may need to create the database manually: createdb ${config.env.database.database}`); } } // Run db:setup (generate + migrate + seed) s.start('Running database setup (prisma generate + migrate)...'); try { execSync('npm run db:setup', { stdio: 'inherit', cwd: path.join(__dirname, '..'), env: { ...process.env, DATABASE_URL: config.env.databaseUrl } }); s.stop('Database setup complete'); } catch (err) { s.stop(color.red('Database setup failed')); p.log.error('Run manually: npm run db:setup'); } } // === ADDITIONAL ENV VARS NEEDED === const additionalEnvVars = []; if (config.auth.providers.includes('github')) { additionalEnvVars.push('AUTH_GITHUB_ID - GitHub OAuth client ID'); additionalEnvVars.push('AUTH_GITHUB_SECRET - GitHub OAuth client secret'); } if (config.auth.providers.includes('google')) { additionalEnvVars.push('AUTH_GOOGLE_ID - Google OAuth client ID'); additionalEnvVars.push('AUTH_GOOGLE_SECRET - Google OAuth client secret'); } if (config.auth.providers.includes('azure')) { additionalEnvVars.push('AUTH_AZURE_AD_CLIENT_ID - Azure AD client ID'); additionalEnvVars.push('AUTH_AZURE_AD_CLIENT_SECRET - Azure AD client secret'); additionalEnvVars.push('AUTH_AZURE_AD_ISSUER - Azure AD issuer URL'); } if (config.auth.providers.includes('apple')) { additionalEnvVars.push('AUTH_APPLE_ID - Apple Services ID'); additionalEnvVars.push('AUTH_APPLE_SECRET - Apple secret key'); } if (config.features.aiSearch || config.features.aiGeneration) { additionalEnvVars.push('OPENAI_API_KEY - OpenAI API key for AI features'); } if (additionalEnvVars.length > 0) { p.log.warn(color.yellow('⚠️ Action required: You must configure the following in .env before the app will work:')); additionalEnvVars.forEach(v => p.log.message(` ${color.yellow('→')} ${v}`)); p.log.message(''); } // === NEXT STEPS === const nextSteps = additionalEnvVars.length > 0 ? `1. Edit ${color.cyan('.env')} to add OAuth credentials (if using OAuth providers)\n` + `2. Add your logo files to the ${color.cyan('public/')} folder\n` + `3. Run: ${color.cyan('npm run db:push')}\n` + `4. Run: ${color.cyan('npm run db:seed')} (optional - seed with prompts)\n` + `5. Run: ${color.cyan('npm run dev')}` : `1. Add your logo files to the ${color.cyan('public/')} folder\n` + `2. Run: ${color.cyan('npm run db:push')}\n` + `3. Run: ${color.cyan('npm run db:seed')} (optional - seed with prompts)\n` + `4. Run: ${color.cyan('npm run dev')}`; p.note(nextSteps, 'Next steps'); p.outro(color.green('Setup complete! See SELF-HOSTING.md for more details.')); } main().catch((err) => { p.log.error('Setup failed: ' + err.message); process.exit(1); });