diff options
Diffstat (limited to 'app/builder')
| -rw-r--r-- | app/builder/build.ts | 85 | ||||
| -rw-r--r-- | app/builder/pages.ts | 112 | ||||
| -rw-r--r-- | app/builder/render.ts | 13 |
3 files changed, 210 insertions, 0 deletions
diff --git a/app/builder/build.ts b/app/builder/build.ts new file mode 100644 index 0000000..d0ed5a9 --- /dev/null +++ b/app/builder/build.ts @@ -0,0 +1,85 @@ +import { render } from './render.js'; +import { Page, PageDirectory } from './pages.js'; +import fs from 'fs'; +import path from 'path'; +import { logger } from '../logger.js'; + +export async function buildPages(): Promise<{ success: boolean, errors: number, pageDirectory: PageDirectory}> { + // Recreate output directory + try { + if (fs.existsSync(process.env.OUTPUT_DIR)) { + fs.rmSync(process.env.OUTPUT_DIR, { recursive: true }); + } + fs.mkdirSync(process.env.OUTPUT_DIR); + } catch (e) { + logger.error(`Failed to create output directory: ${e.message}`); + return { success: false, errors: 0, pageDirectory: null }; + } + + + // Load pages + logger.info(`Reading pages from disk...`); + const pageDirectory = new PageDirectory(process.env.PAGES_DIR); + + let pagesCount = Object.keys(pageDirectory.getPages()).length; + logger.info(`Found ${pagesCount} pages.`); + + + // Render pages + logger.info(`Rendering pages...`); + let pagesRendered = 0; + let pagesFailed = 0; + for (const page of pageDirectory.getPages()) { + if (await renderPage(page, pageDirectory)) { + pagesRendered++; + } else { + pagesFailed++; + } + } + + logger.info(`Rendered ${pagesRendered} of ${pagesCount} pages.`); + + + // Copy static files + logger.info(`Copying static files...`); + try { + fs.cpSync(`${process.env.STATIC_DIR}`, `${process.env.OUTPUT_DIR}/static`, { recursive: true }); + logger.info(`Done.`); + } catch (e) { + logger.error(`Failed to copy static files: ${e.message}`); + } + + return { success: true, errors: pagesFailed, pageDirectory: pageDirectory}; +} + +async function renderPage(page: Page, pageDirectory: PageDirectory): Promise<boolean> { + let html; + try { + html = await render(page, pageDirectory); + } catch (e) { + logger.error(`Failed to render page ${page.originalPath}: ${e.message}`); + return false; + } + + try { + const file = page.buildPath; + const dir = path.dirname(file); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(file, html); + } catch (e) { + logger.error(`Failed to write page ${page.buildPath}: ${e.message}`); + return false; + } + return true; +} + +export async function rebuildSinglePage(path: string, pageDirectory: PageDirectory): Promise<boolean> { + const page = pageDirectory.loadPage(path); + if (!page) { + return false; + } + + return await renderPage(page, pageDirectory); +} diff --git a/app/builder/pages.ts b/app/builder/pages.ts new file mode 100644 index 0000000..c1c2474 --- /dev/null +++ b/app/builder/pages.ts @@ -0,0 +1,112 @@ +import { readFileSync } from 'fs'; +import glob from 'glob'; +import { logger } from '../logger.js' +import { marked } from 'marked'; +import matter from 'gray-matter'; + +export function parsePage(page: Page) { + try { + const result = matter(page.raw); + const config = result.data; + const html = marked.parse(result.content, { mangle: false, headerIds: false }); + + page.html = html; + page.config = config; + page.view = config.view || 'index'; + page.buildTime = Date.now(); + } catch (e) { + logger.error(`Failed to parse page ${page.originalPath}: ${e.message}`); + } +} + +function loadRaw(path: string): string { + return readFileSync(`${path}`, 'utf-8'); +} + +export class PageDirectory { + private pagesPath: string; + + private pages: Record<string, Page> = {}; + private lastFullBuild: number; + + constructor(pagesPath: string) { + this.pagesPath = pagesPath; + + for (const page in this.pages) { + delete this.pages[page]; + } + + const localPages = glob.sync(`**/*.{md,html}`, { cwd: this.pagesPath }) + + localPages.forEach(this.loadPage); + + this.lastFullBuild = Date.now(); + } + + public loadPage = (page: string): Page => { + let route = page.replace(/\.[^.]*$/,'') + let name = /[^/]*$/.exec(route)[0]; + let originalPath = page; + let fullPath = `${this.pagesPath}/${page}` + let buildPath = `${process.env.OUTPUT_DIR}/${route}.html` + let view = `${route}.ejs` + let raw: string; + try { + raw = loadRaw(fullPath); + } catch (e) { + logger.error(`Failed to read page ${originalPath}: ${e.message}`); + return undefined; + } + + this.pages[route] = { + route: route, + name: name, + originalPath: originalPath, + fullPath: fullPath, + buildPath: buildPath, + view: view, + raw: raw, + buildTime: 0, + config: {} + } + + parsePage(this.pages[route]); + + return this.pages[route]; + } + + public removePage = (page: string): void => { + let route = page.replace(/\.[^.]*$/,'') + delete this.pages[route]; + } + + public get(name: string): Page { + const page = this.pages[name]; + if (!page) { + return undefined; + } + + return page; + } + + public getPages(): Page[] { + return Object.values(this.pages); + } + + public getPagesBeginningWith(prefix: string): Page[] { + return Object.values(this.pages).filter(page => page.route.startsWith(prefix)); + } +} + +export type Page = { + html?: string; + raw?: string; + route: string; + name: string; + originalPath: string; + fullPath: string; + buildPath: string; + view: string; + buildTime: number; + config: any; +}; diff --git a/app/builder/render.ts b/app/builder/render.ts new file mode 100644 index 0000000..28c6d80 --- /dev/null +++ b/app/builder/render.ts @@ -0,0 +1,13 @@ +import { Page, PageDirectory } from "./pages"; +import ejs from 'ejs'; +import path from 'path'; + +export async function render(page: Page, pageDirectory: PageDirectory): Promise<string> { + const options = { + page: page, + site: { + pages: pageDirectory, + } + }; + return await ejs.renderFile(path.join(process.env.VIEWS_DIR, `${page.view}.ejs`), options); +} |
