#!/usr/bin/env node /** * Template Initialization Script * * This script personalizes the Astro portfolio template with your information. * * Usage: * node init-template.js # Interactive mode (prompts for info) * node init-template.js --config # Config mode (reads template.config.json) * node init-template.js --help # Show help */ import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import readline from 'readline'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // ANSI color codes for better terminal output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', red: '\x1b[31m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function prompt(question) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(`${colors.cyan}${question}${colors.reset} `, (answer) => { rl.close(); resolve(answer.trim()); }); }); } async function loadConfig() { try { const configPath = path.join(__dirname, 'template.config.json'); const configData = await fs.readFile(configPath, 'utf-8'); return JSON.parse(configData); } catch (error) { log('Warning: Could not load template.config.json', 'yellow'); return null; } } async function interactiveMode() { log('\n=== Astro Portfolio Template Setup ===\n', 'bright'); log('This wizard will help you personalize your template.\n', 'dim'); const config = { site: {}, branding: {}, personal: {}, social: {}, seo: {}, cloudflare: {} }; // Site information log('\n--- Site Information ---\n', 'blue'); config.personal.fullName = await prompt('Your full name:') || 'Your Name'; config.personal.firstName = config.personal.fullName.split(' ')[0]; config.personal.lastName = config.personal.fullName.split(' ').slice(1).join(' ') || 'Name'; config.personal.jobTitle = await prompt('Your profession/job title:') || 'Your Profession'; config.site.title = `${config.personal.fullName} — ${config.personal.jobTitle}`; config.site.description = await prompt('Brief description of what you do:') || 'Professional portfolio and blog'; config.site.url = await prompt('Your site URL (e.g., https://example.com):') || 'https://yoursite.com'; config.site.author = config.personal.fullName; // Branding log('\n--- Branding ---\n', 'blue'); const defaultInitials = config.personal.firstName[0] + (config.personal.lastName[0] || 'N'); config.branding.initials = await prompt(`Your initials (default: ${defaultInitials}):`) || defaultInitials; config.branding.year = await prompt('Portfolio year (default: 2025):') || '2025'; config.branding.backgroundText = await prompt(`Large background text (default: ${config.personal.lastName.toUpperCase()}):`) || config.personal.lastName.toUpperCase(); config.branding.companyName = await prompt('Company name (optional):') || 'Your Company'; // Contact information log('\n--- Contact Information ---\n', 'blue'); config.personal.email = await prompt('Email address:') || 'your@email.com'; config.personal.location = await prompt('Location (e.g., San Francisco, CA):') || 'Your City, State'; config.personal.locationCountry = await prompt('Country:') || 'Your Country'; config.personal.bio = await prompt('Professional bio (1-2 sentences):') || 'Professional bio here.'; // Social links log('\n--- Social Links ---\n', 'blue'); config.social.linkedin = await prompt('LinkedIn URL (full URL):') || 'https://linkedin.com/in/yourprofile'; config.social.github = await prompt('GitHub URL:') || 'https://github.com/yourusername'; config.social.twitter = await prompt('Twitter URL:') || 'https://twitter.com/yourhandle'; const twitterHandle = config.social.twitter.split('/').pop(); config.social.twitterHandle = twitterHandle.startsWith('@') ? twitterHandle : `@${twitterHandle}`; config.social.website = config.site.url; // Cloudflare log('\n--- Deployment ---\n', 'blue'); const defaultProjectName = config.site.url.replace(/https?:\/\//, '').replace(/[^a-z0-9-]/gi, '-').toLowerCase(); config.cloudflare.projectName = await prompt(`Cloudflare project name (default: ${defaultProjectName}):`) || defaultProjectName; // SEO config.seo.keywords = ['Portfolio', 'Blog', config.personal.jobTitle]; config.seo.serviceTypes = ['Service 1', 'Service 2', 'Service 3']; config.seo.companyUrl = config.branding.companyName !== 'Your Company' ? await prompt('Company website URL (optional):') : 'https://example.com'; return config; } async function applyConfig(config) { log('\nšŸ”§ Applying configuration...\n', 'bright'); const replacements = { // Site-wide 'Your Name — Your Profession': config.site.title, 'Your Name': config.personal.fullName, 'Your Profession': config.personal.jobTitle, 'Your professional description here. Describe what you do, who you work with, and what makes you unique.': config.site.description, 'your professional description': config.site.description, 'https://yoursite.com': config.site.url, 'your@email.com': config.personal.email, // Branding 'YN / 2025': `${config.branding.initials} / ${config.branding.year}`, 'NAME': config.branding.backgroundText, // Personal 'Your': config.personal.firstName, 'Your City, State': config.personal.location, 'Your Country': config.personal.locationCountry, 'Your Company': config.branding.companyName, // Social 'https://linkedin.com/in/yourprofile': config.social.linkedin, 'https://github.com/yourusername': config.social.github, 'https://twitter.com/yourhandle': config.social.twitter, '@yourhandle': config.social.twitterHandle, // Cloudflare 'astro-portfolio-template': config.cloudflare.projectName }; const filesToUpdate = [ 'src/consts.ts', 'src/components/BaseHead.astro', 'src/components/Navigation.astro', 'src/components/Footer.astro', 'src/content/sections/hero.mdx', 'src/content/sections/experience.mdx', 'src/content/pages/contact.mdx', 'astro.config.mjs', 'wrangler.jsonc', 'package.json' ]; for (const file of filesToUpdate) { try { const filePath = path.join(__dirname, file); let content = await fs.readFile(filePath, 'utf-8'); for (const [search, replace] of Object.entries(replacements)) { content = content.replaceAll(search, replace); } await fs.writeFile(filePath, content, 'utf-8'); log(`āœ“ Updated ${file}`, 'green'); } catch (error) { log(`āœ— Failed to update ${file}: ${error.message}`, 'red'); } } // Save config for reference try { const configPath = path.join(__dirname, 'template.config.json'); await fs.writeFile( configPath, JSON.stringify(config, null, 2), 'utf-8' ); log('āœ“ Saved configuration to template.config.json', 'green'); } catch (error) { log(`āœ— Failed to save config: ${error.message}`, 'red'); } log('\n✨ Template personalization complete!\n', 'bright'); log('Next steps:', 'cyan'); log(' 1. Review the changes made to your files', 'dim'); log(' 2. Replace placeholder images in src/assets/ and public/media/', 'dim'); log(' 3. Update content in src/content/sections/ with your info', 'dim'); log(' 4. Run `pnpm dev` to preview your site', 'dim'); log(' 5. Run `pnpm deploy` when ready to publish\n', 'dim'); } async function showHelp() { log('\n=== Astro Portfolio Template Setup ===\n', 'bright'); log('Usage:', 'cyan'); log(' node init-template.js Interactive mode (prompts for info)', 'dim'); log(' node init-template.js --config Config mode (reads template.config.json)', 'dim'); log(' node init-template.js --help Show this help message\n', 'dim'); log('Interactive mode will ask you questions and personalize the template.', 'dim'); log('Config mode reads from template.config.json file.\n', 'dim'); } async function main() { const args = process.argv.slice(2); if (args.includes('--help') || args.includes('-h')) { await showHelp(); return; } if (args.includes('--config')) { log('\nšŸ“„ Running in config mode...\n', 'bright'); const config = await loadConfig(); if (!config) { log('Error: template.config.json not found or invalid.', 'red'); log('Create one or run without --config flag for interactive mode.\n', 'yellow'); process.exit(1); } await applyConfig(config); } else { // Interactive mode const config = await interactiveMode(); log('\nšŸ“‹ Configuration summary:', 'yellow'); console.log(JSON.stringify(config, null, 2)); const confirm = await prompt('\nApply this configuration? (yes/no):'); if (confirm.toLowerCase() === 'yes' || confirm.toLowerCase() === 'y') { await applyConfig(config); } else { log('\nSetup cancelled. No changes were made.\n', 'yellow'); } } } main().catch((error) => { log(`\nāŒ Error: ${error.message}\n`, 'red'); console.error(error); process.exit(1); });