diff --git a/packages/@zblog/toolchain/src/markdown-loader.ts b/packages/@zblog/toolchain/src/markdown-loader.ts new file mode 100644 index 0000000..a974b0f --- /dev/null +++ b/packages/@zblog/toolchain/src/markdown-loader.ts @@ -0,0 +1,112 @@ +import fs from "fs"; +import { + copyFile, + readFileAsText, + writeTextAsFile, +} from "./toolchain-helpers.js"; +import { toHtml } from "./markdown-parser.js"; +import { MenuManifest } from "./zblog-toolchain.js"; + +type MarkdownLoaderOptions = { + notesRootPath: string; + noteFileName: string; + assetsDirectoryName: string; +}; + +type MarkdownLoaderManifest = { + directoryName: string; + name: string; + markdown: string; + assetDirectoryPath: string; + publicAssetDirectoryPath: string; + assetFiles: { path: string; name: string }[]; +}[]; + +const createMarkdownLoaderManifest = ({ + notesRootPath, + assetsDirectoryName, + noteFileName, +}: MarkdownLoaderOptions): MarkdownLoaderManifest => { + const directoryNames = fs.readdirSync(notesRootPath); + return directoryNames.map((noteDirectory) => { + const assetDirectoryPath = `${notesRootPath}/${noteDirectory}/${assetsDirectoryName}`; + return { + directoryName: noteDirectory, + name: noteDirectory, + markdown: fs.readFileSync( + `${notesRootPath}/${noteDirectory}/${noteFileName}`, + { encoding: "utf-8" } + ), + assetDirectoryPath, + publicAssetDirectoryPath: `/${noteDirectory}_`, + assetFiles: fs.existsSync(assetDirectoryPath) + ? fs.readdirSync(assetDirectoryPath).map((name) => ({ + path: `${assetDirectoryPath}/${name}`, + name, + })) + : [], + }; + }); +}; + +const copyAssetsToOutputDirectory = ( + manifest: MarkdownLoaderManifest, + outputDirectory: string +) => { + manifest.forEach((m) => + m.assetFiles.forEach((asset) => + copyFile(asset.path, `${outputDirectory}/${m.name}_${asset.name}`) + ) + ); +}; + +const writeMarkdownAsHtmlToOutputDirectory = ( + markdownManifest: MarkdownLoaderManifest, + noteHtmlTemplate: string, + markdownHtmlReplacementTag: string, + outputDirectory: string +) => { + markdownManifest.forEach((m) => { + const markdownAsHtml = toHtml(m.markdown, m.publicAssetDirectoryPath); + const noteHtmlDocument = noteHtmlTemplate.replace( + markdownHtmlReplacementTag, + markdownAsHtml + ); + writeTextAsFile( + `${outputDirectory}/${m.directoryName}.html`, + noteHtmlDocument + ); + }); +}; + +export const loadMarkdown = ( + options: { + rootPath: string; + assetsDirectoryName: string; + noteFileName: string; + noteHtmlTemplatePath: string; + outputDirectory: string; + markdownHtmlReplacementTag: string; + }, + menuManifest: MenuManifest +) => { + const markdownManifest = createMarkdownLoaderManifest({ + notesRootPath: options.rootPath, + assetsDirectoryName: options.assetsDirectoryName, + noteFileName: options.noteFileName, + }); + menuManifest.push( + ...markdownManifest.map((m) => ({ + name: m.name, + link: `/${m.directoryName}.html`, + })) + ); + const noteHtmlTemplate = readFileAsText(options.noteHtmlTemplatePath); + writeMarkdownAsHtmlToOutputDirectory( + markdownManifest, + noteHtmlTemplate, + options.markdownHtmlReplacementTag, + options.outputDirectory + ); + copyAssetsToOutputDirectory(markdownManifest, options.outputDirectory); +}; diff --git a/packages/@zblog/toolchain/src/markdown.ts b/packages/@zblog/toolchain/src/markdown-parser.ts similarity index 100% rename from packages/@zblog/toolchain/src/markdown.ts rename to packages/@zblog/toolchain/src/markdown-parser.ts diff --git a/packages/@zblog/toolchain/src/toolchain-helpers.ts b/packages/@zblog/toolchain/src/toolchain-helpers.ts new file mode 100644 index 0000000..5aed8c1 --- /dev/null +++ b/packages/@zblog/toolchain/src/toolchain-helpers.ts @@ -0,0 +1,24 @@ +import fs from "fs"; +export const destructivelyRecreateDirectory = (directoryPath: string) => { + if (fs.existsSync(directoryPath)) { + fs.rmdirSync(directoryPath, { recursive: true }); + } + fs.mkdirSync(directoryPath, { recursive: true }); +}; + +export const readFileAsText = (path: string) => { + return fs.readFileSync(path, { + encoding: "utf-8", + }); +}; + +export const writeTextAsFile = (path: string, text: string) => { + fs.writeFileSync(path, text, { + encoding: "utf-8", + flag: "ax", + }); +}; + +export const copyFile = (from: string, to: string) => { + fs.copyFileSync(from, to); +}; diff --git a/packages/@zblog/toolchain/src/zblog-toolchain.ts b/packages/@zblog/toolchain/src/zblog-toolchain.ts index ae48d90..a10fb73 100644 --- a/packages/@zblog/toolchain/src/zblog-toolchain.ts +++ b/packages/@zblog/toolchain/src/zblog-toolchain.ts @@ -1,5 +1,6 @@ import * as fs from "fs"; -import { toHtml } from "./markdown.js"; +import { destructivelyRecreateDirectory } from "./toolchain-helpers.js"; +import { loadMarkdown } from "./markdown-loader.js"; export type Paths = { outputDirectory: string; @@ -7,6 +8,7 @@ export type Paths = { rootPath: string; assetsDirectoryName: string; noteFileName: string; + markdownHtmlVariableReplacementTag: string; }; templates: { rootPath: string; @@ -16,60 +18,52 @@ export type Paths = { }; }; +export type MenuManifest = { name: string; link: string }[]; + +export type PluginBuilder = () => Plugin; + +export type Plugin = (builderContext: { + menuManifest: MenuManifest; +}) => void | Promise; + export class ToolchainBuilder { private paths: Paths; + private menuManifest: MenuManifest = []; constructor(paths: Paths) { this.paths = paths; } - build() { - if (fs.readdirSync(".").includes(this.paths.outputDirectory)) { - fs.rmSync(this.paths.outputDirectory, { recursive: true, force: true }); - console.log("Starting rebuild"); - } else { - console.log("Starting build"); + async build() { + destructivelyRecreateDirectory(this.paths.outputDirectory); + + loadMarkdown( + { + rootPath: this.paths.notes.rootPath, + assetsDirectoryName: this.paths.notes.assetsDirectoryName, + noteFileName: this.paths.notes.noteFileName, + markdownHtmlReplacementTag: + this.paths.notes.markdownHtmlVariableReplacementTag, + noteHtmlTemplatePath: `${this.paths.templates.rootPath}/${this.paths.templates.noteTemplateName}`, + outputDirectory: this.paths.outputDirectory, + }, + this.menuManifest + ); + + const plugins: Plugin[] = [ + () => {}, + async () => { + return new Promise((resolve) => { + resolve(); + }); + }, + ]; + + for (const plugin of plugins) { + await plugin({ menuManifest: this.menuManifest }); } - fs.mkdirSync(this.paths.outputDirectory); - console.log("Indexing content.."); - const directoryNames = fs.readdirSync(this.paths.notes.rootPath); - const manifest = directoryNames.map((noteDirectory) => ({ - directoryName: noteDirectory, - name: noteDirectory, - markdown: fs.readFileSync( - `${this.paths.notes.rootPath}/${noteDirectory}/${this.paths.notes.noteFileName}`, - { encoding: "utf-8" } - ), - assetDirectoryPath: `${this.paths.notes.rootPath}/${noteDirectory}/${this.paths.notes.assetsDirectoryName}`, - publicAssetDirectoryPath: `/${noteDirectory}_`, - })); - console.log("Parsing content.."); - manifest.forEach((m) => { - const notePath = `${this.paths.templates.rootPath}/${this.paths.templates.noteTemplateName}`; - let htmlTemplate = fs.readFileSync(notePath, { - encoding: "utf-8", - }); - htmlTemplate = htmlTemplate.replace( - "{{markdown}}", - toHtml(m.markdown, m.publicAssetDirectoryPath) - ); - fs.writeFileSync( - `${this.paths.outputDirectory}/${m.directoryName}.html`, - htmlTemplate, - { - encoding: "utf-8", - flag: "ax", - } - ); - if (!fs.existsSync(m.assetDirectoryPath)) return; - const assetsList = fs.readdirSync(m.assetDirectoryPath); - assetsList.forEach((assetName) => - fs.cpSync( - `${m.assetDirectoryPath}/${assetName}`, - `${this.paths.outputDirectory}/${m.name}_${assetName}` - ) - ); - }); - console.log("Moving static templates.."); + + // static html loader + [this.paths.templates.notFoundName].forEach((filename) => { fs.copyFileSync( `${this.paths.templates.rootPath}/${filename}`, @@ -83,8 +77,8 @@ export class ToolchainBuilder { encoding: "utf-8", } ); - const links = manifest - .map((m) => `${m.name}`) + const links = this.menuManifest + .map((m) => `${m.name}`) .reverse(); const unorderedListItems = links.map((l) => `
  • ${l}
  • `).join("\r\n"); const html = ` diff --git a/src/index.ts b/src/index.ts index a2f8421..e85041a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ const paths: Paths = { rootPath: "src/notes", assetsDirectoryName: "assets", noteFileName: "note.md", + markdownHtmlVariableReplacementTag: "{{markdown}}", }, templates: { rootPath: "src/templates",