@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
574 lines • 22.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateStaticSite = generateStaticSite;
exports.processSSG = processSSG;
exports.generateStaticSiteWithProgress = generateStaticSiteWithProgress;
exports.processSSGWithProgress = processSSGWithProgress;
exports.serveSSG = serveSSG;
const config_1 = require("../domain/config");
const module_1 = require("../domain/module");
const fs_1 = require("../domain/fs");
const content_1 = require("../domain/content");
const template_1 = require("../domain/template");
const site_1 = require("../domain/site/factory/site");
const log_1 = require("../../pkg/log");
const path_1 = __importDefault(require("path"));
const resources_1 = require("../domain/resources");
// Create a domain-specific logger for SSG operations
const log = (0, log_1.getDomainLogger)('ssg', { component: 'application' });
/**
* Create a new domain instances object
*/
const createDomainInstances = (site, content, fs, config, modules, resources) => {
return {
site: site,
content: content,
fs: fs,
config: config,
modules: modules,
resources: resources
};
};
/**
* Load configuration for SSG
*/
async function loadConfiguration(projDir, modulesDir) {
const osFs = (0, fs_1.newOsFs)();
const configFilePath = path_1.default.join(projDir, 'config.json');
return await (0, config_1.loadConfigWithParams)(osFs, configFilePath, projDir, modulesDir);
}
/**
* Create modules for SSG
*/
async function createModule(config) {
const info = {
osFs: () => config.fs(),
projDir: () => config.getDir().getWorkingDir(),
moduleDir: () => config.getDir().getThemesDir(),
moduleCacheDir: () => config.getDir().getThemesCacheDir(),
importPaths: () => config.getModule().importPaths()
};
return await (0, module_1.createModules)(info);
}
/**
* Create file system for SSG
*/
async function createFileSystem(config, module) {
const workspace = {
path: config.getDir().getWorkingDir(),
publish: config.getDir().getPublishDir(),
osFs: config.fs()
};
return await (0, fs_1.createFs)(workspace, module);
}
/**
* Create content engine for SSG
*/
async function createContentEngine(filesystem, config, modules, markdown) {
const services = {
markdown: () => markdown,
newFileMetaInfo: (filename) => {
return filesystem.newFileMetaInfo(filename);
},
newFileMetaInfoWithContent: (content) => {
return filesystem.newFileMetaInfoWithContent(content);
},
contentFs: () => {
return filesystem.contentFs();
},
walkContent: (start, cb, conf) => {
return filesystem.walkContent(start, cb, conf);
},
isLanguageValid: (lang) => {
return config.getLanguage().isLanguageValid(lang);
},
getSourceLang: (source) => {
return modules.getSourceLang(source);
},
getLanguageIndex: (lang) => {
return config.getLanguage().getLanguageIndex(lang);
},
getLanguageByIndex: (idx) => {
return config.getLanguage().getLanguageByIndex(idx);
},
defaultLanguage: () => {
return config.getLanguage().defaultLanguage();
},
languageIndexes: () => {
return config.getLanguage().languageIndexes();
},
views: () => {
const viewNames = config.getTaxonomy().getViews();
return viewNames.map(name => {
const singular = name.singular;
const plural = name.plural;
return {
singular: () => singular || '',
plural: () => plural || ''
};
});
}
};
return (0, content_1.createContent)(services);
}
/**
* Create CustomizedFunctions service adapter
* This adapter implements the CustomizedFunctions interface using config and site data
* Following DDD principles - isolates template domain from external dependencies
*/
function createCustomizedFunctions(config, site, resources) {
const baseUrlString = config.getProvider().getString('baseURL') || 'http://localhost';
return {
// URLService implementation
relURL: (input) => {
return site.getURL().relURL(input);
},
absURL: (input) => {
return site.getURL().absURL(input, site.isMultipleLanguage(), site.languagePrefix());
},
urlize: (input) => {
return site.getURL().urlize(input);
},
// RefSourceService implementation
relRefFrom: async (argsm, source) => {
// Placeholder implementation
return [argsm.path || '', null];
},
// SiteService implementation
title: () => config.getProvider().getString('title') || 'My Site',
baseURL: () => baseUrlString,
params: () => config.getProvider().getParams('params'),
menus: () => {
// Placeholder implementation - return empty menus
return {};
},
isMultiLanguage: () => config.getLanguage().languageKeys().length > 1,
// HugoInfoService implementation
version: () => '0.1.0',
environment: () => 'development',
generator: () => 'AuPro Static Site Generator',
// LanguageService implementation
defaultLanguage: () => config.getLanguage().defaultLanguage(),
languageKeys: () => config.getLanguage().languageKeys(),
Get: async function (filename) {
try {
// Return the resource directly - it now has template-compatible method names
return await resources.getResource(filename); // Can be null if not found
}
catch (error) {
// Following golang pattern: if resource not found, return null instead of throwing
log.warn(`Resource not found: ${filename}`, error);
return Promise.resolve(null);
}
},
Minify: async function (resource) {
return await resources.minify(resource);
},
Fingerprint: async function (resource) {
return await resources.fingerprint(resource);
},
ExecuteAsTemplate: async function (targetPath, data, resource) {
return await resources.executeAsTemplate(resource, targetPath, data);
},
};
}
function createResourcesEngine(config, fs) {
// Create workspace with resources
const workspace = {
assetsFs: () => fs.assetsFs(),
publishFs: () => fs.publishFs(),
baseUrl: () => config.getProvider().getString('baseURL') || 'http://localhost',
executeTemplate: async (templateName, rawContent, data) => {
throw new Error('Template execution not initialized. Please call resources.setTemplateSvc() first.');
}
};
return (0, resources_1.createResources)(workspace);
}
/**
* Create template engine from filesystem using services
* Following DDD principles - template domain only depends on service interfaces
*/
async function createTemplateEngineFromFs(fs, config, site, resources) {
// Create service adapter that bridges domain boundaries
const services = createCustomizedFunctions(config, site, resources);
const templateFs = {
walk: fs.walkLayouts.bind(fs)
};
// Create template engine with services - following Go's factory.New(fs, cfs) pattern
return await (0, template_1.createTemplateEngineWithServices)(templateFs, services);
}
/**
* Create Template adapter from TemplateEngine
* Bridges TemplateEngine to Template interface for site.build()
*/
async function createTemplateAdapter(templateEngine, site) {
return {
async lookupLayout(names) {
// Use the domain template engine's findFirst method
const [tmpl, foundName, found, err] = await templateEngine.findFirst(names);
if (err) {
return { preparer: null, found: false };
}
if (!found || !tmpl || !foundName) {
return { preparer: null, found: false };
}
// Create TemplatePreparer wrapper
const preparer = {
name: () => foundName,
execute: async (data) => {
const [result, execErr] = await tmpl.Execute(data);
if (execErr) {
log.errorf("template exec error: %s", execErr);
throw execErr;
}
return result;
}
};
return { preparer: preparer, found: true };
},
async executeWithContext(preparer, data) {
if (!preparer) {
return '<html><body>Default template - no preparer</body></html>';
}
try {
return await preparer.execute(data);
}
catch (error) {
log.error(`❌ Template execution error: ${error}`);
return `<html><body>Template execution error: ${error}</body></html>`;
}
},
};
}
/**
* Create site for SSG
*/
async function createSiteForSSG(config, fs, content) {
const siteServices = {
// Config service methods
configParams: () => config.getProvider().getParams('params'),
siteTitle: () => config.getProvider().getString('title') || 'My Site',
menus: () => {
// Placeholder implementation - return empty menus
const result = {};
return result;
},
// Content service methods
globalPages: async (langIndex) => await content.globalPages(langIndex),
globalRegularPages: () => content.globalRegularPages(),
walkPages: async (langIndex, walker) => {
await content.walkPages(langIndex, walker);
},
getPageSources: async (page) => {
return content.getPageSources(page);
},
walkTaxonomies: async (langIndex, walker) => {
await content.walkTaxonomies(langIndex, walker);
},
searchPage: async (pages, page) => {
// Placeholder implementation
return [];
},
getPageFromPath: async (langIndex, path) => {
try {
const page = content.getPageFromPath(langIndex, path);
if (!page) {
log.error(`⚠️ Application.getPageFromPath: content domain returned null for path: "${path}"`);
}
return page;
}
catch (error) {
log.error(`❌ Application.getPageFromPath error delegating to content domain:`, error);
return null;
}
},
getPageFromPathSync: (langIndex, path) => {
try {
const page = content.getPageFromPath(langIndex, path);
if (!page) {
log.warn(`⚠️ Application.getPageFromPathSync: content domain returned null for path: ${path}`);
}
return page;
}
catch (error) {
log.error(`❌ Application.getPageFromPathSync error delegating to content domain:`, error);
return null;
}
},
getPageRef: async (context, ref, home) => {
// Placeholder implementation
return null;
},
// Translation service methods (placeholder)
translate: async (lang, translationID, templateData) => translationID,
// Language service methods
defaultLanguage: () => config.getLanguage().defaultLanguage(),
languageKeys: () => config.getLanguage().languageKeys(),
getLanguageIndex: (lang) => config.getLanguage().getLanguageIndex(lang),
getLanguageName: (lang) => config.getLanguage().getLanguageName(lang),
// Sitemap service methods (placeholder)
changeFreq: () => 'weekly',
priority: () => 0.5,
generateSitemap: async () => ({ urls: [] }),
// Publisher methods
publishFs: () => {
return fs.publishFs(); // Return the actual Fs object directly
},
staticFs() {
return fs.staticFs();
},
copyStaticFiles(from, to) {
return fs.copyStatic([from], to);
},
workingDir: () => config.getProvider().getString('workingDir') || process.cwd(),
// ResourceService implementation (placeholder)
getResource: async (path) => {
// Placeholder implementation
return null;
},
getResourceWithOpener: async (path, opener) => {
// Placeholder implementation - return a mock resource
return {
name: () => path,
readSeekCloser: opener,
targetPath: () => path
};
},
// URLService implementation
baseUrl: () => config.getProvider().getString('baseURL') || 'http://localhost',
};
// Create site with static copy service
return (0, site_1.newSite)(siteServices);
}
/**
* Generate static site
*/
async function generateStaticSite(projDir, modulesDir, markdown) {
try {
// Load configuration
const config = await loadConfiguration(projDir, modulesDir);
// Create modules
const modules = await createModule(config);
// Create filesystem
const filesystem = await createFileSystem(config, modules);
// Create content engine
const content = await createContentEngine(filesystem, config, modules, markdown);
// Create site
const site = await createSiteForSSG(config, filesystem, content);
// Create resources engine
const resources = createResourcesEngine(config, filesystem);
// Create template engine with services
const templateEngine = await createTemplateEngineFromFs(filesystem, config, site, resources);
resources.setTemplateSvc({
async executeTemplate(templateName, rawContent, data) {
return await templateEngine.executeRaw(templateName, rawContent, data);
}
});
content.setTemplateSvc({
async execute(templateName, data) {
return await templateEngine.executeShortcode(templateName, data);
}
});
// Collect pages
await content.collectPages();
// Create template adapter from TemplateEngine
const templateAdapter = await createTemplateAdapter(templateEngine, site);
await site.build(templateAdapter);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
log.error(`❌ Static site generation failed: ${message}`);
throw new Error(`Failed to generate static site: ${message}`);
}
}
async function processSSG(projDir, modulesDir, markdown) {
try {
// Change to project directory
const originalCwd = process.cwd();
process.chdir(projDir);
try {
await generateStaticSite(projDir, modulesDir, markdown);
}
finally {
// Restore original working directory
process.chdir(originalCwd);
}
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
log.error(`❌ SSG processing failed: ${message}`);
throw new Error(`Failed to process SSG: ${message}`);
}
}
/**
* Generate static site with progress callbacks
*/
async function generateStaticSiteWithProgress(projDir, modulesDir, markdown, onProgress) {
try {
// Load configuration
onProgress?.({
stage: 'config',
message: 'Loading configuration...',
percentage: 5
});
const config = await loadConfiguration(projDir, modulesDir);
// Create modules with progress tracking
onProgress?.({
stage: 'modules',
message: 'Creating and downloading modules...',
percentage: 10
});
const modules = await createModuleWithProgress(config, onProgress);
// Create filesystem
onProgress?.({
stage: 'filesystem',
message: 'Creating filesystem...',
percentage: 30
});
const filesystem = await createFileSystem(config, modules);
// Create content engine
onProgress?.({
stage: 'content',
message: 'Creating content engine...',
percentage: 40
});
const content = await createContentEngine(filesystem, config, modules, markdown);
// Create site
onProgress?.({
stage: 'site',
message: 'Creating site...',
percentage: 50
});
const site = await createSiteForSSG(config, filesystem, content);
// Create resources engine
const resources = createResourcesEngine(config, filesystem);
// Create template engine with services
onProgress?.({
stage: 'template',
message: 'Creating template engine...',
percentage: 60
});
const templateEngine = await createTemplateEngineFromFs(filesystem, config, site, resources);
resources.setTemplateSvc({
async executeTemplate(templateName, rawContent, data) {
return await templateEngine.executeRaw(templateName, rawContent, data);
}
});
content.setTemplateSvc({
async execute(templateName, data) {
return await templateEngine.executeShortcode(templateName, data);
}
});
// Collect pages
onProgress?.({
stage: 'pages',
message: 'Collecting pages...',
percentage: 65
});
await content.collectPages();
// Build site with progress tracking
onProgress?.({
stage: 'build',
message: 'Building site...',
percentage: 70
});
// Create template adapter from TemplateEngine
const templateAdapter = await createTemplateAdapter(templateEngine, site);
await buildSiteWithProgress(site, templateAdapter, onProgress);
onProgress?.({
stage: 'completion',
message: 'SSG generation completed',
percentage: 100
});
return createDomainInstances(site, content, filesystem, config, modules, resources);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
log.error(`❌ Static site generation failed: ${message}`);
throw new Error(`Failed to generate static site: ${message}`);
}
}
/**
* Create modules with progress tracking
*/
async function createModuleWithProgress(config, onProgress) {
const info = {
osFs: () => config.fs(),
projDir: () => config.getDir().getWorkingDir(),
moduleDir: () => config.getDir().getThemesDir(),
moduleCacheDir: () => config.getDir().getThemesCacheDir(),
importPaths: () => config.getModule().importPaths()
};
// Create modules instance with progress-aware HTTP client
return await createModulesWithProgressTracking(info, onProgress);
}
/**
* Create modules with progress tracking for downloads
*/
async function createModulesWithProgressTracking(info, onProgress) {
// Create a module-specific progress callback that adapts to SSG progress format
const moduleProgressCallback = onProgress ? (progress) => {
onProgress({
stage: 'modules',
message: `Downloading module: ${progress.modulePath}`,
percentage: 10 + Math.floor(progress.downloadPercentage * 0.2), // Map to 10-30% range
moduleDownload: {
modulePath: progress.modulePath,
downloadPercentage: progress.downloadPercentage
}
});
} : undefined;
return await (0, module_1.createModulesWithProgress)(info, moduleProgressCallback);
}
/**
* Build site with progress tracking for page rendering
*/
async function buildSiteWithProgress(site, template, onProgress) {
// Create a page-specific progress callback that adapts to SSG progress format
const pageProgressCallback = onProgress ? (progress) => {
const percentage = 70 + Math.floor((progress.currentPage / progress.totalPages) * 29);
onProgress({
stage: 'build',
message: `Rendering pages (${progress.currentPage}/${progress.totalPages})...`,
percentage,
pageRender: progress
});
} : undefined;
// Use the progress-aware build method
await site.buildWithProgress(template, pageProgressCallback);
}
/**
* Process SSG with progress callbacks
*/
async function processSSGWithProgress(projDir, modulesDir, markdown, onProgress) {
try {
const originalCwd = process.cwd();
process.chdir(projDir);
try {
await generateStaticSiteWithProgress(projDir, modulesDir, markdown, onProgress);
}
finally {
process.chdir(originalCwd);
}
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
log.error(`❌ SSG processing failed: ${message}`);
throw new Error(`Failed to process SSG: ${message}`);
}
}
async function serveSSG(projDir, modulesDir, markdown, onProgress) {
try {
process.chdir(projDir);
return await generateStaticSiteWithProgress(projDir, modulesDir, markdown, onProgress);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
log.error(`❌ SSG processing failed: ${message}`);
throw new Error(`Failed to process SSG: ${message}`);
}
}
//# sourceMappingURL=ssg.js.map