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.

257 lines 10.9 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.Generator = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const yaml = __importStar(require("js-yaml")); const config_1 = require("./config"); const validator_1 = require("./validator"); const html_builder_1 = require("./html-builder"); const asset_copier_1 = require("./asset-copier"); const errors_1 = require("../utils/errors"); const logger_1 = require("../utils/logger"); const project_file_finder_1 = require("../utils/project-file-finder"); /** * Main generator orchestrator that coordinates the build process */ class Generator { constructor(config, cwd = process.cwd(), adminMode = false) { this.config = config; this.cwd = cwd; this.adminMode = adminMode; this.outputDir = path.isAbsolute(config.output || 'dist') ? config.output || 'dist' : path.join(cwd, config.output || 'dist'); this.configLoader = new config_1.ConfigLoader(cwd); this.validator = new validator_1.Validator(cwd); this.htmlBuilder = new html_builder_1.HTMLBuilder(config, { adminMode: this.adminMode }); this.assetCopier = new asset_copier_1.AssetCopier(cwd, this.outputDir); } /** * Create a Generator instance by loading configuration */ static async create(options = {}) { const cwd = options.cwd || process.cwd(); const configLoader = new config_1.ConfigLoader(cwd); // Load configuration const config = await configLoader.load({ configPath: options.configPath }); // Override output directory if specified in options if (options.outputDir) { config.output = options.outputDir; } // Override baseUrl if specified in options (useful for dev server) if (options.baseUrl !== undefined) { config.baseUrl = options.baseUrl; } // Pass adminMode to constructor (defaults to false for production builds) const adminMode = options.adminMode || false; return new Generator(config, cwd, adminMode); } /** * Main generation method that orchestrates the entire build process */ async generate() { try { logger_1.Logger.header('🚀 Starting build process'); // 1. Load project data logger_1.Logger.step('Loading project data...'); const projectsData = await this.loadProjectData(); logger_1.Logger.success(`Loaded ${projectsData.projects.length} project${projectsData.projects.length !== 1 ? 's' : ''}`); // 2. Validate projects logger_1.Logger.step('Validating project data...'); const warnings = this.validator.validate(projectsData.projects); logger_1.Logger.success('Validation passed'); // Display warnings if any if (warnings.length > 0) { logger_1.Logger.newline(); logger_1.Logger.warn(`Found ${warnings.length} warning${warnings.length !== 1 ? 's' : ''}:`); warnings.forEach(warning => { const projectInfo = warning.projectId ? `[${warning.projectId}]` : `[Project ${warning.projectIndex}]`; logger_1.Logger.dim(` ${projectInfo} ${warning.field}: ${warning.message}`); }); logger_1.Logger.newline(); } // 3. Clean output directory if requested if (this.outputDir && fs.existsSync(this.outputDir)) { logger_1.Logger.step('Cleaning output directory...'); this.cleanOutputDirectory(); logger_1.Logger.success('Output directory cleaned'); } // 4. Generate HTML logger_1.Logger.step('Generating HTML...'); const html = this.htmlBuilder.generateHTML(projectsData); logger_1.Logger.success('HTML generated'); // 5. Write output logger_1.Logger.step('Writing output files...'); await this.writeOutput(html); logger_1.Logger.success('Output written'); // 6. Copy assets logger_1.Logger.step('Copying assets...'); const thumbnails = projectsData.projects .map(p => p.thumbnailLink) .filter((t) => !!t); await this.assetCopier.copyAssets(this.config, thumbnails); logger_1.Logger.success('Assets copied'); logger_1.Logger.newline(); logger_1.Logger.icon('✨', `Build complete! Output: ${this.outputDir}`, '\x1b[32m'); } catch (error) { if (error instanceof errors_1.ProjectionError) { throw error; } throw new errors_1.ProjectionError('Build process failed', errors_1.ErrorCodes.RUNTIME_ERROR, { originalError: error.message }); } } /** * Load project data from projects.yaml or projects.json */ async loadProjectData() { // Use shared utility to find projects file const projectFileResult = project_file_finder_1.ProjectFileFinder.find(this.cwd); if (!projectFileResult) { throw new errors_1.ProjectionError('Projects file not found', errors_1.ErrorCodes.FILE_NOT_FOUND, { message: `Could not find ${project_file_finder_1.ProjectFileFinder.getSupportedFileNames().join(', ')} in the current directory`, searchedPaths: project_file_finder_1.ProjectFileFinder.getPossiblePaths(this.cwd), cwd: this.cwd }); } const projectsPath = projectFileResult.path; try { const content = fs.readFileSync(projectsPath, 'utf-8'); const ext = path.extname(projectsPath); let data; if (ext === '.yaml' || ext === '.yml') { data = yaml.load(content); } else if (ext === '.json') { data = JSON.parse(content); } else { throw new errors_1.ProjectionError(`Unsupported file format: ${ext}`, errors_1.ErrorCodes.PARSE_ERROR, { path: projectsPath }); } // Validate structure if (!data || typeof data !== 'object') { throw new errors_1.ProjectionError('Invalid projects file: must contain an object', errors_1.ErrorCodes.PARSE_ERROR, { path: projectsPath }); } if (!data.projects || !Array.isArray(data.projects)) { throw new errors_1.ProjectionError('Invalid projects file: must contain a "projects" array', errors_1.ErrorCodes.PARSE_ERROR, { path: projectsPath }); } return { config: data.config, projects: data.projects }; } catch (error) { if (error instanceof errors_1.ProjectionError) { throw error; } throw new errors_1.ProjectionError(`Failed to parse projects file: ${projectsPath}`, errors_1.ErrorCodes.PARSE_ERROR, { path: projectsPath, originalError: error.message }); } } /** * Write generated HTML to output file */ async writeOutput(html) { // Ensure output directory exists if (!fs.existsSync(this.outputDir)) { try { fs.mkdirSync(this.outputDir, { recursive: true }); } catch (error) { throw new errors_1.ProjectionError(`Failed to create output directory: ${this.outputDir}`, errors_1.ErrorCodes.FILE_WRITE_ERROR, { path: this.outputDir, originalError: error.message }); } } const outputPath = path.join(this.outputDir, 'index.html'); try { fs.writeFileSync(outputPath, html, 'utf-8'); } catch (error) { throw new errors_1.ProjectionError(`Failed to write output file: ${outputPath}`, errors_1.ErrorCodes.FILE_WRITE_ERROR, { path: outputPath, originalError: error.message }); } } /** * Clean the output directory */ cleanOutputDirectory() { if (!fs.existsSync(this.outputDir)) { return; } try { // Remove all files and subdirectories const entries = fs.readdirSync(this.outputDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(this.outputDir, entry.name); if (entry.isDirectory()) { fs.rmSync(fullPath, { recursive: true, force: true }); } else { fs.unlinkSync(fullPath); } } } catch (error) { throw new errors_1.ProjectionError(`Failed to clean output directory: ${this.outputDir}`, errors_1.ErrorCodes.FILE_WRITE_ERROR, { path: this.outputDir, originalError: error.message }); } } /** * Get the output directory path */ getOutputDir() { return this.outputDir; } /** * Get the current configuration */ getConfig() { return this.config; } } exports.Generator = Generator; //# sourceMappingURL=index.js.map