Files
prompts.chat/scripts/setup.js
T

386 lines
15 KiB
JavaScript

#!/usr/bin/env node
/**
* 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 readline = require('readline');
const CONFIG_FILE = path.join(__dirname, '..', 'prompts.config.ts');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function question(prompt, defaultValue = '') {
const defaultText = defaultValue ? ` (${defaultValue})` : '';
return new Promise((resolve) => {
rl.question(`${prompt}${defaultText}: `, (answer) => {
resolve(answer.trim() || defaultValue);
});
});
}
function confirm(prompt, defaultValue = true) {
const hint = defaultValue ? '[Y/n]' : '[y/N]';
return new Promise((resolve) => {
rl.question(`${prompt} ${hint}: `, (answer) => {
if (!answer.trim()) {
resolve(defaultValue);
} else {
resolve(answer.toLowerCase().startsWith('y'));
}
});
});
}
function select(prompt, options, defaultIndex = 0) {
console.log(`\n${prompt}`);
options.forEach((opt, i) => {
const marker = i === defaultIndex ? '>' : ' ';
console.log(` ${marker} ${i + 1}. ${opt.label}`);
});
return new Promise((resolve) => {
rl.question(`Enter choice (1-${options.length}) [${defaultIndex + 1}]: `, (answer) => {
const index = answer.trim() ? parseInt(answer) - 1 : defaultIndex;
if (index >= 0 && index < options.length) {
resolve(options[index].value);
} else {
resolve(options[defaultIndex].value);
}
});
});
}
function multiSelect(prompt, options) {
console.log(`\n${prompt}`);
options.forEach((opt, i) => {
console.log(` ${i + 1}. ${opt.label}`);
});
return new Promise((resolve) => {
rl.question(`Enter choices (comma-separated, e.g., 1,2): `, (answer) => {
if (!answer.trim()) {
resolve([]);
return;
}
const indices = answer.split(',').map(s => parseInt(s.trim()) - 1);
const selected = indices
.filter(i => i >= 0 && i < options.length)
.map(i => options[i].value);
resolve(selected);
});
});
}
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(p => `"${p}"`).join(', ')}],
allowRegistration: ${config.auth.allowRegistration},
},
// Storage plugin for media uploads
storage: {
provider: "${config.storage.provider}",
},
// 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},
aiSearch: ${config.features.aiSearch},
},
// Homepage customization (clone branding mode)
homepage: {
useCloneBranding,
achievements: {
enabled: false, // Disabled for private clones
},
sponsors: {
enabled: ${config.sponsors.length > 0},
items: ${sponsorsSection},
},
},
});
`;
}
async function main() {
console.log('\n╔═══════════════════════════════════════════════════════════╗');
console.log('║ prompts.chat - Private Clone Setup ║');
console.log('╠═══════════════════════════════════════════════════════════╣');
console.log('║ This wizard will help you configure your private ║');
console.log('║ prompt library with your own branding and settings. ║');
console.log('╚═══════════════════════════════════════════════════════════╝\n');
const config = {
branding: {},
theme: {},
auth: {},
storage: {},
i18n: {},
features: {},
sponsors: []
};
// === BRANDING ===
console.log('\n─── BRANDING ───────────────────────────────────────────────\n');
config.branding.name = await question('Organization/App name', 'My Prompt Library');
config.branding.description = await question('Description', 'Collect, organize, and share AI prompts');
config.branding.logo = await question('Logo path (public folder)', '/logo.svg');
config.branding.logoDark = await question('Dark mode logo path', config.branding.logo);
config.branding.favicon = await question('Favicon path', '/logo.svg');
// === THEME ===
console.log('\n─── THEME ──────────────────────────────────────────────────\n');
config.theme.primaryColor = await question('Primary color (hex)', '#6366f1');
config.theme.radius = await select('Border radius style:', [
{ label: 'None - Sharp corners', value: 'none' },
{ label: 'Small - Subtle rounding', value: 'sm' },
{ label: 'Medium - Moderate rounding', value: 'md' },
{ label: 'Large - Very rounded', value: 'lg' }
], 1);
config.theme.variant = await select('UI variant:', [
{ label: 'Default - Standard modern look', value: 'default' },
{ label: 'Flat - Minimal shadows', value: 'flat' },
{ label: 'Brutal - Bold neo-brutalist style', value: 'brutal' }
], 0);
config.theme.density = await select('Spacing density:', [
{ label: 'Compact - Tighter spacing', value: 'compact' },
{ label: 'Default - Balanced spacing', value: 'default' },
{ label: 'Comfortable - More breathing room', value: 'comfortable' }
], 1);
// === AUTHENTICATION ===
console.log('\n─── AUTHENTICATION ─────────────────────────────────────────\n');
config.auth.providers = await multiSelect('Select authentication providers:', [
{ label: 'GitHub OAuth', value: 'github' },
{ label: 'Google OAuth', value: 'google' },
{ label: 'Microsoft Azure AD', value: 'azure' },
{ label: 'Email/Password (Credentials)', value: 'credentials' }
]);
if (config.auth.providers.length === 0) {
console.log(' ⚠️ No providers selected, defaulting to credentials');
config.auth.providers = ['credentials'];
}
if (config.auth.providers.includes('credentials')) {
config.auth.allowRegistration = await confirm('Allow public registration?', true);
} else {
config.auth.allowRegistration = false;
}
// === STORAGE ===
console.log('\n─── STORAGE ────────────────────────────────────────────────\n');
config.storage.provider = await select('Media storage provider:', [
{ label: 'URL-based (external links)', value: 'url' },
{ label: 'AWS S3 (requires configuration)', value: 's3' }
], 0);
// === INTERNATIONALIZATION ===
console.log('\n─── INTERNATIONALIZATION ───────────────────────────────────\n');
const selectedLocales = await multiSelect('Select supported languages:', [
{ label: 'English', value: 'en' },
{ label: 'Spanish', value: 'es' },
{ label: 'French', value: 'fr' },
{ label: 'German', value: 'de' },
{ label: 'Italian', value: 'it' },
{ label: 'Portuguese', value: 'pt' },
{ label: 'Turkish', value: 'tr' },
{ label: 'Japanese', value: 'ja' },
{ label: 'Korean', value: 'ko' },
{ label: 'Chinese', value: 'zh' },
{ label: 'Arabic', value: 'ar' }
]);
config.i18n.locales = selectedLocales.length > 0 ? selectedLocales : ['en'];
if (config.i18n.locales.length > 1) {
console.log(`\nSelected locales: ${config.i18n.locales.join(', ')}`);
config.i18n.defaultLocale = await question('Default locale', config.i18n.locales[0]);
} else {
config.i18n.defaultLocale = config.i18n.locales[0];
}
// === FEATURES ===
console.log('\n─── FEATURES ───────────────────────────────────────────────\n');
config.features.privatePrompts = await confirm('Enable private prompts?', true);
config.features.changeRequests = await confirm('Enable change request system (versioning)?', true);
config.features.categories = await confirm('Enable categories?', true);
config.features.tags = await confirm('Enable tags?', true);
config.features.aiSearch = await confirm('Enable AI-powered search? (requires OPENAI_API_KEY)', false);
// === SPONSORS ===
console.log('\n─── SPONSORS (Optional) ────────────────────────────────────\n');
const addSponsors = await confirm('Add sponsor logos to homepage?', false);
if (addSponsors) {
let addMore = true;
while (addMore) {
console.log('\n Adding sponsor:');
const name = await question(' Sponsor name');
const logo = await question(' Logo URL');
const url = await question(' Website URL');
if (name && logo && url) {
config.sponsors.push({ name, logo, url });
console.log(` ✓ Added ${name}`);
}
addMore = await confirm(' Add another sponsor?', false);
}
}
// === SUMMARY ===
console.log('\n─── CONFIGURATION SUMMARY ──────────────────────────────────\n');
console.log(` Name: ${config.branding.name}`);
console.log(` Description: ${config.branding.description}`);
console.log(` Primary Color: ${config.theme.primaryColor}`);
console.log(` UI Style: ${config.theme.variant} / ${config.theme.radius} radius`);
console.log(` Auth: ${config.auth.providers.join(', ')}`);
console.log(` Storage: ${config.storage.provider}`);
console.log(` Languages: ${config.i18n.locales.join(', ')}`);
console.log(` Features: ${Object.entries(config.features).filter(([,v]) => v).map(([k]) => k).join(', ')}`);
if (config.sponsors.length > 0) {
console.log(` Sponsors: ${config.sponsors.map(s => s.name).join(', ')}`);
}
console.log('');
const proceed = await confirm('Generate configuration file?', true);
if (!proceed) {
console.log('\n❌ Setup cancelled.\n');
rl.close();
process.exit(0);
}
// === GENERATE CONFIG ===
const configContent = generateConfig(config);
// Backup existing config if it exists
if (fs.existsSync(CONFIG_FILE)) {
const backupPath = CONFIG_FILE + '.backup';
fs.copyFileSync(CONFIG_FILE, backupPath);
console.log(`\n📦 Backed up existing config to prompts.config.ts.backup`);
}
fs.writeFileSync(CONFIG_FILE, configContent);
console.log('✅ Generated prompts.config.ts');
// === ENV FILE ===
console.log('\n─── ENVIRONMENT VARIABLES ──────────────────────────────────\n');
const envExample = path.join(__dirname, '..', '.env.example');
const envFile = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envFile) && fs.existsSync(envExample)) {
const createEnv = await confirm('Create .env file from .env.example?', true);
if (createEnv) {
fs.copyFileSync(envExample, envFile);
console.log('✅ Created .env file');
}
}
console.log('\n📋 Required environment variables:');
console.log(' DATABASE_URL - PostgreSQL connection string');
console.log(' AUTH_SECRET - NextAuth secret (generate with: openssl rand -base64 32)');
if (config.auth.providers.includes('github')) {
console.log(' AUTH_GITHUB_ID - GitHub OAuth client ID');
console.log(' AUTH_GITHUB_SECRET - GitHub OAuth client secret');
}
if (config.auth.providers.includes('google')) {
console.log(' AUTH_GOOGLE_ID - Google OAuth client ID');
console.log(' AUTH_GOOGLE_SECRET - Google OAuth client secret');
}
if (config.auth.providers.includes('azure')) {
console.log(' AUTH_AZURE_AD_CLIENT_ID - Azure AD client ID');
console.log(' AUTH_AZURE_AD_CLIENT_SECRET - Azure AD client secret');
console.log(' AUTH_AZURE_AD_ISSUER - Azure AD issuer URL');
}
if (config.features.aiSearch) {
console.log(' OPENAI_API_KEY - OpenAI API key for semantic search');
}
if (config.storage.provider === 's3') {
console.log(' AWS_ACCESS_KEY_ID - AWS access key');
console.log(' AWS_SECRET_ACCESS_KEY - AWS secret key');
console.log(' AWS_S3_BUCKET - S3 bucket name');
console.log(' AWS_S3_REGION - S3 region');
}
// === NEXT STEPS ===
console.log('\n─── NEXT STEPS ─────────────────────────────────────────────\n');
console.log(' 1. Edit .env with your database and auth credentials');
console.log(' 2. Add your logo files to the public/ folder');
console.log(' 3. Run: npm run db:push');
console.log(' 4. Run: npm run dev');
console.log('');
console.log('📖 For more details, see SELF-HOSTING.md');
console.log('\n═══════════════════════════════════════════════════════════════\n');
rl.close();
}
main().catch((err) => {
console.error('Setup failed:', err);
rl.close();
process.exit(1);
});