UNPKG

@quasarbright/projection

Version:

A static site generator that creates a beautiful, interactive gallery to showcase your coding projects. Features search, filtering, tags, responsive design, and an admin UI.

382 lines (371 loc) 14 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.init = init; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const readline = __importStar(require("readline")); const logger_1 = require("../utils/logger"); const git_helper_1 = require("../utils/git-helper"); const deployment_config_1 = require("../utils/deployment-config"); /** * Display help for the init command */ function showInitHelp() { console.log(` Projection Init - Initialize a New Project USAGE: projection init [options] DESCRIPTION: Creates a new Projection project in the current directory with sample project data, configuration, and documentation. OPTIONS: --force Overwrite existing files without prompting --format <fmt> Choose data format: yaml or json (default: yaml) --minimal Create minimal example instead of full sample --help Show this help message EXAMPLES: projection init # Initialize with YAML format projection init --format json # Initialize with JSON format projection init --minimal # Create minimal example projection init --force # Overwrite existing files CREATED FILES: projects.yaml (or .json) Project data file projection.config.json Site configuration .gitignore Git ignore patterns README.md Project documentation `); } /** * Initialize a new Projection project in the current directory */ async function init(options = {}) { if (options.help) { showInitHelp(); return; } const cwd = process.cwd(); const format = options.format || 'yaml'; const projectsFileName = format === 'yaml' ? 'projects.yaml' : 'projects.json'; const configFileName = 'projection.config.json'; const gitignoreFileName = '.gitignore'; const readmeFileName = 'README.md'; const projectsFilePath = path.join(cwd, projectsFileName); const configFilePath = path.join(cwd, configFileName); const gitignorePath = path.join(cwd, gitignoreFileName); const readmePath = path.join(cwd, readmeFileName); // Check for existing files const projectsExists = fs.existsSync(projectsFilePath); const configExists = fs.existsSync(configFilePath); const gitignoreExists = fs.existsSync(gitignorePath); const readmeExists = fs.existsSync(readmePath); if ((projectsExists || configExists || gitignoreExists || readmeExists) && !options.force) { const existingFiles = []; if (projectsExists) existingFiles.push(projectsFileName); if (configExists) existingFiles.push(configFileName); if (gitignoreExists) existingFiles.push(gitignoreFileName); if (readmeExists) existingFiles.push(readmeFileName); logger_1.Logger.newline(); logger_1.Logger.warn('The following files already exist:'); logger_1.Logger.list(existingFiles); logger_1.Logger.newline(); logger_1.Logger.dim('Use --force to overwrite existing files.'); logger_1.Logger.newline(); const shouldContinue = await promptUser('Do you want to overwrite these files? (y/N): '); if (!shouldContinue.toLowerCase().startsWith('y')) { logger_1.Logger.newline(); logger_1.Logger.error('Initialization cancelled.'); logger_1.Logger.newline(); return; } } // Get template directory path const templateDir = getTemplateDirectory(); // Detect Git repository and extract deployment info const gitInfo = await detectGitRepository(cwd); // Copy projects file await copyProjectsTemplate(templateDir, projectsFilePath, format, options.minimal); // Copy config file with deployment support await copyConfigTemplate(templateDir, configFilePath, gitInfo); // Create .gitignore file await createGitignoreFile(cwd, gitignorePath); // Create README.md file await createReadmeFile(templateDir, readmePath); // Display success message with deployment instructions displaySuccessMessage(projectsFileName, configFileName, gitignoreFileName, readmeFileName, gitInfo); } /** * Detect Git repository and extract deployment information */ async function detectGitRepository(cwd) { const result = { isGitRepo: false, hasRemote: false, repositoryUrl: null, baseUrl: null, repoName: null, }; // Check if Git is installed const gitInstalled = await git_helper_1.GitHelper.isGitInstalled(); if (!gitInstalled) { return result; } // Validate repository try { const validation = await git_helper_1.GitHelper.validateRepository(cwd); result.isGitRepo = validation.isGitRepo; result.hasRemote = validation.hasRemote; if (validation.hasRemote && validation.remoteUrl) { result.repositoryUrl = validation.remoteUrl; // Extract repository name and generate baseUrl result.repoName = deployment_config_1.DeploymentConfigLoader.extractRepoName(validation.remoteUrl); result.baseUrl = `/${result.repoName}/`; } } catch (error) { // Silently fail - Git detection is optional during init } return result; } /** * Get the template directory path */ function getTemplateDirectory() { // In development (running from src), templates are in src/templates/init // In production (running from lib), templates are in lib/templates/init const devPath = path.join(__dirname, '..', 'templates', 'init'); const prodPath = path.join(__dirname, '..', '..', 'lib', 'templates', 'init'); if (fs.existsSync(devPath)) { return devPath; } else if (fs.existsSync(prodPath)) { return prodPath; } else { // Try relative to the compiled location const compiledPath = path.join(__dirname, '..', 'templates', 'init'); if (fs.existsSync(compiledPath)) { return compiledPath; } throw new Error('Template directory not found. Please ensure the package is installed correctly.'); } } /** * Copy the projects template file */ async function copyProjectsTemplate(templateDir, targetPath, format, minimal) { const templatePath = path.join(templateDir, 'projects.yaml.template'); if (!fs.existsSync(templatePath)) { throw new Error(`Template file not found: ${templatePath}`); } let content = fs.readFileSync(templatePath, 'utf-8'); if (minimal) { // Create minimal version with only one project content = createMinimalProjectsContent(format); } else if (format === 'json') { // Convert YAML template to JSON content = convertYamlTemplateToJson(content); } fs.writeFileSync(targetPath, content, 'utf-8'); logger_1.Logger.success(`Created ${path.basename(targetPath)}`); } /** * Copy the config template file */ async function copyConfigTemplate(templateDir, targetPath, gitInfo) { // Create config object const config = { title: "My Projects", description: "A showcase of my coding projects", baseUrl: gitInfo.hasRemote && gitInfo.baseUrl ? gitInfo.baseUrl : "./", dynamicBackgrounds: [] }; // Write as JSON with 2-space indentation const content = JSON.stringify(config, null, 2); fs.writeFileSync(targetPath, content, 'utf-8'); logger_1.Logger.success(`Created ${path.basename(targetPath)}`); } /** * Create .gitignore file with common patterns */ async function createGitignoreFile(cwd, targetPath) { const gitignoreContent = `# Projection build output dist/ # Backup files .backup # macOS .DS_Store # Node modules (if using npm scripts) node_modules/ # Editor directories .vscode/ .idea/ `; fs.writeFileSync(targetPath, gitignoreContent, 'utf-8'); logger_1.Logger.success('Created .gitignore'); } /** * Create README.md file from template */ async function createReadmeFile(templateDir, targetPath) { const templatePath = path.join(templateDir, 'README.md.template'); if (!fs.existsSync(templatePath)) { throw new Error(`Template file not found: ${templatePath}`); } const content = fs.readFileSync(templatePath, 'utf-8'); fs.writeFileSync(targetPath, content, 'utf-8'); logger_1.Logger.success('Created README.md'); } /** * Create minimal projects content */ function createMinimalProjectsContent(format) { if (format === 'json') { return JSON.stringify({ projects: [ { id: "my-first-project", title: "My First Project", description: "Replace this with your own project!", creationDate: new Date().toISOString().split('T')[0], tags: ["example"], pageLink: "https://example.com" } ] }, null, 2); } else { const today = new Date().toISOString().split('T')[0]; return `# Projection Project Data projects: - id: "my-first-project" title: "My First Project" description: "Replace this with your own project!" creationDate: "${today}" tags: - "example" pageLink: "https://example.com" `; } } /** * Convert YAML template to JSON format (basic conversion) */ function convertYamlTemplateToJson(yamlContent) { // For now, we'll use a simple approach // In a real implementation, we'd parse the YAML and convert to JSON // But since this is a template with comments, we'll create a clean JSON version return JSON.stringify({ projects: [ { id: "example-project", title: "Example Project", description: "This is an example project demonstrating all available fields. Replace this with your own projects!", creationDate: "2024-01-15", tags: ["web", "javascript", "example"], pageLink: "https://example.com/project", sourceLink: "https://github.com/username/example-project", thumbnailLink: "./images/example-thumbnail.png", featured: true }, { id: "minimal-project", title: "Minimal Project", description: "A minimal project with only required fields.", creationDate: "2024-02-20", tags: ["example"], pageLink: "https://example.com/minimal" } ] }, null, 2); } /** * Prompt user for input */ function promptUser(question) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(question, (answer) => { rl.close(); resolve(answer); }); }); } /** * Display success message with next steps */ function displaySuccessMessage(projectsFile, configFile, gitignoreFile, readmeFile, gitInfo) { logger_1.Logger.newline(); logger_1.Logger.icon('🎉', 'Successfully initialized Projection project!', '\x1b[32m'); logger_1.Logger.newline(); logger_1.Logger.info('Created files:'); logger_1.Logger.list([projectsFile, configFile, gitignoreFile, readmeFile]); logger_1.Logger.newline(); // Display Git repository information if detected if (gitInfo.isGitRepo && gitInfo.hasRemote) { logger_1.Logger.info('Git repository detected:'); logger_1.Logger.list([ `Repository: ${gitInfo.repositoryUrl}`, `Base URL configured: ${gitInfo.baseUrl}` ]); logger_1.Logger.newline(); } else if (gitInfo.isGitRepo && !gitInfo.hasRemote) { logger_1.Logger.warn('Git repository detected but no remote configured.'); logger_1.Logger.dim('Add a remote to enable GitHub Pages deployment:'); logger_1.Logger.dim(' git remote add origin <repository-url>'); logger_1.Logger.newline(); } logger_1.Logger.info('Next steps:'); const steps = [ `Run 'projection admin' to manage your projects and see a live preview`, `Run 'projection dev' to start development server` ]; logger_1.Logger.list(steps); logger_1.Logger.newline(); logger_1.Logger.dim('📚 Documentation: https://github.com/quasarbright/projection'); logger_1.Logger.newline(); logger_1.Logger.icon('🚀', 'Happy building!', '\x1b[36m'); logger_1.Logger.newline(); } //# sourceMappingURL=init.js.map