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.

291 lines (284 loc) 11.3 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.dev = dev; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const chokidar = __importStar(require("chokidar")); const browserSync = __importStar(require("browser-sync")); const build_helper_1 = require("../utils/build-helper"); const errors_1 = require("../utils/errors"); const logger_1 = require("../utils/logger"); /** * Display help for the dev command */ function showDevHelp() { console.log(` Projection Dev - Development Server USAGE: projection dev [options] DESCRIPTION: Starts a development server with live reload. Automatically rebuilds and refreshes the browser when project files change. OPTIONS: --config <path> Path to custom config file --output <path> Custom output directory (default: dist) --port <number> Server port (default: 8080) --no-open Don't open browser automatically --help Show this help message EXAMPLES: projection dev # Start dev server on port 8080 projection dev --port 3000 # Use custom port projection dev --no-open # Don't open browser WATCHED FILES: projects.yaml / projects.yml / projects.json projection.config.json styles/ scripts/ The server will automatically rebuild when any of these files change. `); } /** * Dev command - starts development server with file watching and live reload */ async function dev(options = {}) { if (options.help) { showDevHelp(); return; } const port = options.port || 8080; const shouldOpen = !options.noOpen; const cwd = process.cwd(); logger_1.Logger.newline(); logger_1.Logger.header('🚀 Starting development server'); logger_1.Logger.newline(); try { // Perform initial build using shared helper logger_1.Logger.step('Performing initial build...'); logger_1.Logger.newline(); let result = await build_helper_1.BuildHelper.runBuild({ cwd, configPath: options.config, outputDir: options.output }); const outputDir = result.outputDir; // Check if dist directory exists after build if (!fs.existsSync(outputDir)) { logger_1.Logger.newline(); logger_1.Logger.error(`Output directory '${outputDir}' was not created.`); logger_1.Logger.newline(); process.exit(1); } // Initialize browser-sync const bs = browserSync.create(); // Start browser-sync server with middleware to serve screenshots bs.init({ server: { baseDir: outputDir, routes: { '/screenshots': path.join(cwd, 'screenshots') } }, port, open: shouldOpen, notify: false, ui: false, logLevel: 'silent' }, (err) => { if (err) { logger_1.Logger.newline(); logger_1.Logger.error(`Failed to start dev server: ${err.message}`); logger_1.Logger.newline(); process.exit(1); } logger_1.Logger.newline(); logger_1.Logger.icon('✨', 'Development server running!', '\x1b[32m'); logger_1.Logger.keyValue('Serving', outputDir); logger_1.Logger.keyValue('Local', `http://localhost:${port}`); logger_1.Logger.icon('👀', 'Watching for changes...', '\x1b[36m'); logger_1.Logger.newline(); logger_1.Logger.dim('💡 Press Ctrl+C to stop'); logger_1.Logger.newline(); }); // Set up file watcher const watchPaths = [ path.join(cwd, 'projects.yaml'), path.join(cwd, 'projects.yml'), path.join(cwd, 'projects.json'), path.join(cwd, 'projection.config.json'), path.join(cwd, 'styles'), path.join(cwd, 'scripts') ]; // Filter to only watch paths that exist const existingPaths = watchPaths.filter(p => fs.existsSync(p)); if (existingPaths.length === 0) { logger_1.Logger.warn('No files to watch. Make sure you have projects.yaml or config files.'); logger_1.Logger.newline(); } const watcher = chokidar.watch(existingPaths, { persistent: true, ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 100 } }); // Track rebuild state to prevent concurrent rebuilds let isRebuilding = false; let rebuildQueued = false; const rebuild = async () => { if (isRebuilding) { rebuildQueued = true; return; } isRebuilding = true; try { logger_1.Logger.icon('🔄', 'Change detected, rebuilding...', '\x1b[33m'); // Rebuild using shared helper result = await build_helper_1.BuildHelper.runBuild({ cwd, configPath: options.config, outputDir: options.output, silent: true // Don't show full build logs on rebuild }); logger_1.Logger.success('Rebuild complete'); logger_1.Logger.newline(); // Reload browser bs.reload(); } catch (error) { if (error instanceof errors_1.ProjectionError) { logger_1.Logger.newline(); logger_1.Logger.error(`Rebuild failed: ${error.message}`); if (error.details) { if (error.details.errors && Array.isArray(error.details.errors)) { logger_1.Logger.error('Errors:'); error.details.errors.forEach((err) => { logger_1.Logger.dim(` • ${err}`); }); } else if (error.details.message) { logger_1.Logger.dim(error.details.message); } } logger_1.Logger.newline(); logger_1.Logger.icon('👀', 'Watching for changes...', '\x1b[36m'); logger_1.Logger.newline(); } else { logger_1.Logger.newline(); logger_1.Logger.error('Unexpected error during rebuild:'); logger_1.Logger.dim(error.message); logger_1.Logger.newline(); logger_1.Logger.icon('👀', 'Watching for changes...', '\x1b[36m'); logger_1.Logger.newline(); } } finally { isRebuilding = false; // If another change came in while rebuilding, rebuild again if (rebuildQueued) { rebuildQueued = false; setTimeout(() => rebuild(), 100); } } }; // Watch for file changes watcher.on('change', (filePath) => { const relativePath = path.relative(cwd, filePath); logger_1.Logger.icon('📝', `Changed: ${relativePath}`, '\x1b[33m'); rebuild(); }); watcher.on('add', (filePath) => { const relativePath = path.relative(cwd, filePath); logger_1.Logger.icon('➕', `Added: ${relativePath}`, '\x1b[32m'); rebuild(); }); watcher.on('unlink', (filePath) => { const relativePath = path.relative(cwd, filePath); logger_1.Logger.icon('➖', `Removed: ${relativePath}`, '\x1b[31m'); rebuild(); }); watcher.on('error', (error) => { logger_1.Logger.newline(); logger_1.Logger.error(`Watcher error: ${error.message}`); logger_1.Logger.newline(); }); // Handle graceful shutdown const shutdown = () => { logger_1.Logger.newline(); logger_1.Logger.newline(); logger_1.Logger.icon('👋', 'Shutting down development server...', '\x1b[33m'); watcher.close(); bs.exit(); logger_1.Logger.success('Server stopped'); logger_1.Logger.newline(); process.exit(0); }; process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); } catch (error) { if (error instanceof errors_1.ProjectionError) { logger_1.Logger.newline(); logger_1.Logger.error(`Failed to start dev server: ${error.message}`); logger_1.Logger.newline(); if (error.details) { if (error.details.errors && Array.isArray(error.details.errors)) { logger_1.Logger.error('Errors:'); error.details.errors.forEach((err) => { logger_1.Logger.dim(` • ${err}`); }); logger_1.Logger.newline(); } else if (error.details.message) { logger_1.Logger.dim(error.details.message); logger_1.Logger.newline(); } } process.exit(1); } else { logger_1.Logger.newline(); logger_1.Logger.error('Unexpected error:'); logger_1.Logger.dim(error.message); logger_1.Logger.newline(); logger_1.Logger.dim('Please report this issue if it persists.'); logger_1.Logger.newline(); process.exit(1); } } } //# sourceMappingURL=dev.js.map