@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.
292 lines • 11.9 kB
JavaScript
"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.AssetCopier = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const errors_1 = require("../utils/errors");
/**
* Handles copying of template assets (styles, scripts, static files)
* with support for user customization
*/
class AssetCopier {
constructor(cwd, outputDir) {
this.cwd = cwd;
this.outputDir = outputDir;
// Package root is where lib/templates/default/ is located
// When running from compiled code, __dirname will be in lib/generator/
this.packageRoot = path.resolve(__dirname, '../..');
}
/**
* Copy all assets (styles, scripts, static assets) to output directory
*/
async copyAssets(config, projectThumbnails) {
// Ensure output directory exists
this.ensureDirectoryExists(this.outputDir);
// Copy styles
await this.copyStyles(config);
// Copy scripts
await this.copyScripts(config);
// Copy static assets (favicon, etc.)
await this.copyStaticAssets();
// Copy user asset directories (images, screenshots, etc.)
await this.copyUserAssetDirectories();
// Copy admin-uploaded screenshots to images/ directory
if (projectThumbnails && projectThumbnails.length > 0) {
await this.copyAdminScreenshots(projectThumbnails);
}
}
/**
* Copy style files, preferring user custom styles over bundled templates
*/
async copyStyles(config) {
const outputStylesDir = path.join(this.outputDir, 'styles');
this.ensureDirectoryExists(outputStylesDir);
// Determine source directory
let sourceDir;
if (config.customStyles) {
// Use explicitly configured custom styles
sourceDir = path.isAbsolute(config.customStyles)
? config.customStyles
: path.join(this.cwd, config.customStyles);
}
else {
// Check for local styles/ directory
const localStylesDir = path.join(this.cwd, 'styles');
if (fs.existsSync(localStylesDir) && fs.statSync(localStylesDir).isDirectory()) {
sourceDir = localStylesDir;
}
else {
// Fall back to bundled templates
sourceDir = path.join(this.packageRoot, 'lib/templates/default/styles');
}
}
// Verify source directory exists
if (!fs.existsSync(sourceDir)) {
throw new errors_1.ProjectionError(`Styles directory not found: ${sourceDir}`, errors_1.ErrorCodes.FILE_NOT_FOUND, { path: sourceDir, type: 'styles' });
}
// Copy all files from source to output
this.copyDirectory(sourceDir, outputStylesDir);
}
/**
* Copy script files, preferring user custom scripts over bundled templates
*/
async copyScripts(config) {
const outputScriptsDir = path.join(this.outputDir, 'scripts');
this.ensureDirectoryExists(outputScriptsDir);
// Determine source directory
let sourceDir;
if (config.customScripts) {
// Use explicitly configured custom scripts
sourceDir = path.isAbsolute(config.customScripts)
? config.customScripts
: path.join(this.cwd, config.customScripts);
}
else {
// Check for local scripts/ directory
const localScriptsDir = path.join(this.cwd, 'scripts');
if (fs.existsSync(localScriptsDir) && fs.statSync(localScriptsDir).isDirectory()) {
sourceDir = localScriptsDir;
}
else {
// Fall back to bundled templates
sourceDir = path.join(this.packageRoot, 'lib/templates/default/scripts');
}
}
// Verify source directory exists
if (!fs.existsSync(sourceDir)) {
throw new errors_1.ProjectionError(`Scripts directory not found: ${sourceDir}`, errors_1.ErrorCodes.FILE_NOT_FOUND, { path: sourceDir, type: 'scripts' });
}
// Copy all files from source to output
this.copyDirectory(sourceDir, outputScriptsDir);
}
/**
* Copy static assets (favicon, images, etc.)
*/
async copyStaticAssets() {
// Check for local assets/ directory
const localAssetsDir = path.join(this.cwd, 'assets');
const bundledAssetsDir = path.join(this.packageRoot, 'lib/templates/default/assets');
// Copy bundled assets first (as defaults)
if (fs.existsSync(bundledAssetsDir)) {
const files = fs.readdirSync(bundledAssetsDir);
for (const file of files) {
const sourcePath = path.join(bundledAssetsDir, file);
const destPath = path.join(this.outputDir, file);
if (fs.statSync(sourcePath).isFile()) {
this.copyFile(sourcePath, destPath);
}
}
}
// Copy local assets (overwriting bundled if conflicts)
if (fs.existsSync(localAssetsDir) && fs.statSync(localAssetsDir).isDirectory()) {
const files = fs.readdirSync(localAssetsDir);
for (const file of files) {
const sourcePath = path.join(localAssetsDir, file);
const destPath = path.join(this.outputDir, file);
if (fs.statSync(sourcePath).isFile()) {
this.copyFile(sourcePath, destPath);
}
}
}
}
/**
* Copy user asset directories (images, screenshots, etc.)
* This copies any directories in the project root that might contain assets
* referenced in project data (thumbnails, etc.)
*/
async copyUserAssetDirectories() {
// Common directory names that might contain assets
const assetDirNames = ['images', 'screenshots', 'img', 'photos', 'media', 'assets'];
for (const dirName of assetDirNames) {
const sourceDir = path.join(this.cwd, dirName);
// Skip if directory doesn't exist or if it's one we already handled
if (!fs.existsSync(sourceDir)) {
continue;
}
const stat = fs.statSync(sourceDir);
if (!stat.isDirectory()) {
continue;
}
// Copy the entire directory to output
const destDir = path.join(this.outputDir, dirName);
this.copyDirectory(sourceDir, destDir);
}
}
/**
* Recursively copy a directory
*/
copyDirectory(sourceDir, destDir) {
this.ensureDirectoryExists(destDir);
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
for (const entry of entries) {
const sourcePath = path.join(sourceDir, entry.name);
const destPath = path.join(destDir, entry.name);
if (entry.isDirectory()) {
this.copyDirectory(sourcePath, destPath);
}
else if (entry.isFile()) {
this.copyFile(sourcePath, destPath);
}
}
}
/**
* Copy a single file
*/
copyFile(sourcePath, destPath) {
try {
fs.copyFileSync(sourcePath, destPath);
}
catch (error) {
throw new errors_1.ProjectionError(`Failed to copy file: ${sourcePath} to ${destPath}`, errors_1.ErrorCodes.FILE_WRITE_ERROR, {
source: sourcePath,
destination: destPath,
originalError: error.message
});
}
}
/**
* Ensure a directory exists, creating it if necessary
*/
ensureDirectoryExists(dirPath) {
if (!fs.existsSync(dirPath)) {
try {
fs.mkdirSync(dirPath, { recursive: true });
}
catch (error) {
throw new errors_1.ProjectionError(`Failed to create directory: ${dirPath}`, errors_1.ErrorCodes.FILE_WRITE_ERROR, {
path: dirPath,
originalError: error.message
});
}
}
}
/**
* Get the resolved path for styles directory
* (useful for testing and debugging)
*/
getStylesSource(config) {
if (config.customStyles) {
return path.isAbsolute(config.customStyles)
? config.customStyles
: path.join(this.cwd, config.customStyles);
}
const localStylesDir = path.join(this.cwd, 'styles');
if (fs.existsSync(localStylesDir) && fs.statSync(localStylesDir).isDirectory()) {
return localStylesDir;
}
return path.join(this.packageRoot, 'lib/templates/default/styles');
}
/**
* Get the resolved path for scripts directory
* (useful for testing and debugging)
*/
getScriptsSource(config) {
if (config.customScripts) {
return path.isAbsolute(config.customScripts)
? config.customScripts
: path.join(this.cwd, config.customScripts);
}
const localScriptsDir = path.join(this.cwd, 'scripts');
if (fs.existsSync(localScriptsDir) && fs.statSync(localScriptsDir).isDirectory()) {
return localScriptsDir;
}
return path.join(this.packageRoot, 'lib/templates/default/scripts');
}
/**
* Copy admin-uploaded screenshots from screenshots/ to images/ directory
* This handles thumbnails with the admin:// prefix
*/
async copyAdminScreenshots(thumbnails) {
const screenshotsDir = path.join(this.cwd, 'screenshots');
const imagesDir = path.join(this.outputDir, 'images');
// Ensure images directory exists
this.ensureDirectoryExists(imagesDir);
// Filter for admin:// prefixed thumbnails
const adminThumbnails = thumbnails.filter(t => t && t.startsWith('admin://'));
for (const thumbnail of adminThumbnails) {
// Remove admin:// prefix to get filename
const filename = thumbnail.substring(8);
const sourcePath = path.join(screenshotsDir, filename);
const destPath = path.join(imagesDir, filename);
// Only copy if source file exists
if (fs.existsSync(sourcePath)) {
this.copyFile(sourcePath, destPath);
}
}
}
}
exports.AssetCopier = AssetCopier;
//# sourceMappingURL=asset-copier.js.map