UNPKG

@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
"use strict"; 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