Files
bDS/src/main/engine/BlogGenerationOutputService.ts

128 lines
4.7 KiB
TypeScript

import * as crypto from 'node:crypto';
import * as fs from 'fs/promises';
import * as path from 'node:path';
import { getGeneratedFileHash, setGeneratedFileHash } from '../database/generatedFileHashStore';
import { PREVIEW_ASSETS, PREVIEW_IMAGE_ASSETS } from './PageRenderer';
export function normalizeGeneratedUrlPath(urlPath: string): string {
const trimmed = (urlPath || '').trim();
if (!trimmed || trimmed === '/') {
return '/';
}
const noQuery = trimmed.split('?')[0]?.split('#')[0] ?? '';
const withoutSlashes = noQuery.replace(/^\/+|\/+$/g, '');
return withoutSlashes ? `/${withoutSlashes}` : '/';
}
export function urlPathToHtmlIndexPath(htmlDir: string, urlPath: string): string {
const normalizedPath = normalizeGeneratedUrlPath(urlPath);
if (normalizedPath === '/') {
return path.join(htmlDir, 'index.html');
}
return path.join(htmlDir, normalizedPath.slice(1), 'index.html');
}
export function computeContentHash(content: string): string {
return crypto.createHash('sha256').update(content).digest('hex');
}
export async function writeFileIfHashChanged(params: {
projectId: string;
filePath: string;
relativePath: string;
content: string;
hashCache?: Map<string, string | null>;
getGeneratedFileHash?: (projectId: string, relativePath: string) => Promise<string | null>;
setGeneratedFileHash?: (projectId: string, relativePath: string, hash: string) => Promise<void>;
computeHash?: (content: string) => string;
}): Promise<boolean> {
const getHash = params.getGeneratedFileHash ?? getGeneratedFileHash;
const setHash = params.setGeneratedFileHash ?? setGeneratedFileHash;
const hashFn = params.computeHash ?? computeContentHash;
const hash = hashFn(params.content);
let previousHash: string | null;
if (params.hashCache && params.hashCache.has(params.relativePath)) {
previousHash = params.hashCache.get(params.relativePath) ?? null;
} else {
previousHash = await getHash(params.projectId, params.relativePath);
params.hashCache?.set(params.relativePath, previousHash);
}
if (previousHash === hash) {
return false;
}
await fs.writeFile(params.filePath, params.content, 'utf-8');
await setHash(params.projectId, params.relativePath, hash);
params.hashCache?.set(params.relativePath, hash);
return true;
}
export async function writeHtmlPage(params: {
projectId: string;
htmlDir: string;
urlPath: string;
content: string;
knownDirectories?: Set<string>;
hashCache?: Map<string, string | null>;
ensureDirectory?: (dirPath: string) => Promise<void>;
getGeneratedFileHash?: (projectId: string, relativePath: string) => Promise<string | null>;
setGeneratedFileHash?: (projectId: string, relativePath: string, hash: string) => Promise<void>;
computeHash?: (content: string) => string;
}): Promise<boolean> {
const normalizedPath = params.urlPath.replace(/^\//, '');
const filePath = normalizedPath
? path.join(params.htmlDir, normalizedPath, 'index.html')
: path.join(params.htmlDir, 'index.html');
const relativePath = normalizedPath ? `${normalizedPath}/index.html` : 'index.html';
const directoryPath = path.dirname(filePath);
const ensureDirectory = params.ensureDirectory ?? (async (dirPath: string) => {
await fs.mkdir(dirPath, { recursive: true });
});
if (params.knownDirectories) {
if (!params.knownDirectories.has(directoryPath)) {
await ensureDirectory(directoryPath);
params.knownDirectories.add(directoryPath);
}
} else {
await ensureDirectory(directoryPath);
}
return writeFileIfHashChanged({
projectId: params.projectId,
filePath,
relativePath,
content: params.content,
hashCache: params.hashCache,
getGeneratedFileHash: params.getGeneratedFileHash,
setGeneratedFileHash: params.setGeneratedFileHash,
computeHash: params.computeHash,
});
}
export async function copyPreviewAssets(htmlDir: string): Promise<void> {
const assetsDir = path.join(htmlDir, 'assets');
const imagesDir = path.join(htmlDir, 'images');
await fs.mkdir(assetsDir, { recursive: true });
await fs.mkdir(imagesDir, { recursive: true });
for (const [filename, definition] of Object.entries(PREVIEW_ASSETS)) {
const destPath = path.join(assetsDir, filename);
const content = definition.sourceText !== undefined
? Buffer.from(definition.sourceText, 'utf-8')
: await fs.readFile(require.resolve(definition.modulePath as string));
await fs.writeFile(destPath, content);
}
for (const [filename, definition] of Object.entries(PREVIEW_IMAGE_ASSETS)) {
const sourcePath = require.resolve(definition.modulePath);
const destPath = path.join(imagesDir, filename);
const content = await fs.readFile(sourcePath);
await fs.writeFile(destPath, content);
}
}