@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
JavaScript
;
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