Broke out all functionality into plugins

This commit is contained in:
2024-01-29 21:39:31 +01:00
parent 31c253c99b
commit fb79c2d10e
10 changed files with 171 additions and 128 deletions

2
package-lock.json generated
View File

@@ -1733,7 +1733,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"zblog-toolchain": "bin/zblog-toolchain.js" "zblog-toolchain": "bin/index.js"
} }
} }
} }

View File

@@ -3,12 +3,12 @@
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"type": "module", "type": "module",
"module": "bin/zblog-toolchain.js", "module": "bin/index.js",
"main": "bin/zblog-toolchain.js", "main": "bin/index.js",
"bin": { "bin": {
"zblog-toolchain": "./bin/zblog-toolchain.js" "zblog-toolchain": "./bin/index.js"
}, },
"types": "./bin/zblog-toolchain.d.ts", "types": "./bin/index.d.ts",
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json" "build": "tsc -p tsconfig.json"
}, },

View File

@@ -0,0 +1,30 @@
import { IPluginBuilder, Plugin } from "./typings.js";
import { copyFile } from "./toolchain-helpers.js";
type Options = {
files: { path: string; menuEntry?: boolean; nameOverride?: string }[];
};
export class FileLoader implements IPluginBuilder {
options: Options;
constructor(options: Options) {
this.options = options;
}
build(): Plugin {
return async (builderContext) => {
const { menuManifest } = builderContext;
const { files } = this.options;
files.forEach((file) => {
const fileName =
file.nameOverride ?? file.path.split("/").pop() ?? file.path;
copyFile(file.path, `${builderContext.outputDirectory}/${fileName}`);
if (file.menuEntry) {
menuManifest.push({
name: fileName,
link: file.path,
});
}
});
};
}
}

View File

@@ -0,0 +1,9 @@
import { ToolchainBuilder as TSB, Paths as P } from "./zblog-toolchain.js";
export const ToolchainBuilder = TSB;
export type Paths = P;
import { FileLoader as FL } from "./file-loader.js";
export const FileLoader = FL;
import { MarkdownLoader as ML } from "./markdown-loader.js";
export const MarkdownLoader = ML;
import { StartPagePlugin as SPP } from "./start-page-plugin.js";
export const StartPagePlugin = SPP;

View File

@@ -5,9 +5,9 @@ import {
writeTextAsFile, writeTextAsFile,
} from "./toolchain-helpers.js"; } from "./toolchain-helpers.js";
import { toHtml } from "./markdown-parser.js"; import { toHtml } from "./markdown-parser.js";
import { MenuManifest } from "./zblog-toolchain.js"; import { IPluginBuilder, MenuManifest, Plugin } from "./typings.js";
type MarkdownLoaderOptions = { type MarkdownLoaderManifestOptions = {
notesRootPath: string; notesRootPath: string;
noteFileName: string; noteFileName: string;
assetsDirectoryName: string; assetsDirectoryName: string;
@@ -26,7 +26,7 @@ const createMarkdownLoaderManifest = ({
notesRootPath, notesRootPath,
assetsDirectoryName, assetsDirectoryName,
noteFileName, noteFileName,
}: MarkdownLoaderOptions): MarkdownLoaderManifest => { }: MarkdownLoaderManifestOptions): MarkdownLoaderManifest => {
const directoryNames = fs.readdirSync(notesRootPath); const directoryNames = fs.readdirSync(notesRootPath);
return directoryNames.map((noteDirectory) => { return directoryNames.map((noteDirectory) => {
const assetDirectoryPath = `${notesRootPath}/${noteDirectory}/${assetsDirectoryName}`; const assetDirectoryPath = `${notesRootPath}/${noteDirectory}/${assetsDirectoryName}`;
@@ -79,34 +79,44 @@ const writeMarkdownAsHtmlToOutputDirectory = (
}); });
}; };
export const loadMarkdown = ( type MarkdownLoaderOptions = {
options: { rootPath: string;
rootPath: string; assetsDirectoryName: string;
assetsDirectoryName: string; noteFileName: string;
noteFileName: string; noteHtmlTemplatePath: string;
noteHtmlTemplatePath: string; markdownHtmlReplacementTag: 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);
}; };
export class MarkdownLoader implements IPluginBuilder {
options: MarkdownLoaderOptions;
constructor(options: MarkdownLoaderOptions) {
this.options = options;
}
build(): Plugin {
return (builderContext) => {
const markdownManifest = createMarkdownLoaderManifest({
notesRootPath: this.options.rootPath,
assetsDirectoryName: this.options.assetsDirectoryName,
noteFileName: this.options.noteFileName,
});
builderContext.menuManifest.push(
...markdownManifest.map((m) => ({
name: m.name,
link: `/${m.directoryName}.html`,
}))
);
const noteHtmlTemplate = readFileAsText(
this.options.noteHtmlTemplatePath
);
writeMarkdownAsHtmlToOutputDirectory(
markdownManifest,
noteHtmlTemplate,
this.options.markdownHtmlReplacementTag,
builderContext.outputDirectory
);
copyAssetsToOutputDirectory(
markdownManifest,
builderContext.outputDirectory
);
};
}
}

View File

@@ -0,0 +1,32 @@
import { IPluginBuilder, Plugin } from "./typings.js";
import { readFileAsText, writeTextAsFile } from "./toolchain-helpers.js";
type StartPagePluginOptions = {
indexTemplatePath: string;
contentTemplateTag: string;
};
export class StartPagePlugin implements IPluginBuilder {
options: StartPagePluginOptions;
constructor(options: StartPagePluginOptions) {
this.options = options;
}
build(): Plugin {
return (builderContext) => {
let htmlTemplate = readFileAsText(this.options.indexTemplatePath);
const links = builderContext.menuManifest
.map((m) => `<a href='${m.link}'>${m.name}</a>`)
.reverse();
const unorderedListItems = links.map((l) => `<li>${l}</li>`).join("\r\n");
const html = `
<ul>
${unorderedListItems}
<ul>
`;
htmlTemplate = htmlTemplate.replace("{{content}}", html);
writeTextAsFile(
`${builderContext.outputDirectory}/index.html`,
htmlTemplate
);
};
}
}

View File

@@ -0,0 +1,9 @@
export type MenuManifest = { name: string; link: string }[];
export interface IPluginBuilder {
build(): Plugin;
}
export type Plugin = (builderContext: {
outputDirectory: string;
menuManifest: MenuManifest;
}) => void | Promise<void>;

View File

@@ -1,96 +1,32 @@
import * as fs from "fs"; import * as fs from "fs";
import { destructivelyRecreateDirectory } from "./toolchain-helpers.js"; import { destructivelyRecreateDirectory } from "./toolchain-helpers.js";
import { loadMarkdown } from "./markdown-loader.js"; import { MenuManifest, Plugin, IPluginBuilder } from "./typings.js";
export type Paths = { export type Paths = {
outputDirectory: string; outputDirectory: string;
notes: {
rootPath: string;
assetsDirectoryName: string;
noteFileName: string;
markdownHtmlVariableReplacementTag: string;
};
templates: {
rootPath: string;
noteTemplateName: string;
indexTemplateName: string;
notFoundName: string;
};
}; };
export type MenuManifest = { name: string; link: string }[];
export type PluginBuilder = () => Plugin;
export type Plugin = (builderContext: {
menuManifest: MenuManifest;
}) => void | Promise<void>;
export class ToolchainBuilder { export class ToolchainBuilder {
private paths: Paths; private paths: Paths;
private plugins: Plugin[] = [];
private menuManifest: MenuManifest = []; private menuManifest: MenuManifest = [];
constructor(paths: Paths) { constructor(paths: Paths) {
this.paths = paths; this.paths = paths;
} }
addPlugins(plugins: IPluginBuilder[]): ToolchainBuilder {
this.plugins = plugins.map((p) => p.build());
return this;
}
async build() { async build() {
destructivelyRecreateDirectory(this.paths.outputDirectory); destructivelyRecreateDirectory(this.paths.outputDirectory);
loadMarkdown( for (const plugin of this.plugins) {
{ await plugin({
rootPath: this.paths.notes.rootPath, menuManifest: this.menuManifest,
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, outputDirectory: this.paths.outputDirectory,
}, });
this.menuManifest
);
const plugins: Plugin[] = [
() => {},
async () => {
return new Promise<void>((resolve) => {
resolve();
});
},
];
for (const plugin of plugins) {
await plugin({ menuManifest: this.menuManifest });
} }
// static html loader
[this.paths.templates.notFoundName].forEach((filename) => {
fs.copyFileSync(
`${this.paths.templates.rootPath}/${filename}`,
`${this.paths.outputDirectory}/${filename}`
);
});
console.log("Building startpage..");
let htmlTemplate = fs.readFileSync(
`${this.paths.templates.rootPath}/${this.paths.templates.indexTemplateName}`,
{
encoding: "utf-8",
}
);
const links = this.menuManifest
.map((m) => `<a href='${m.link}'>${m.name}</a>`)
.reverse();
const unorderedListItems = links.map((l) => `<li>${l}</li>`).join("\r\n");
const html = `
<ul>
${unorderedListItems}
<ul>
`;
htmlTemplate = htmlTemplate.replace("{{content}}", html);
fs.writeFileSync(`${this.paths.outputDirectory}/index.html`, htmlTemplate, {
encoding: "utf-8",
flag: "ax",
});
console.log("Done");
} }
} }

View File

@@ -1,5 +1,5 @@
{ {
"include": ["src/zblog-toolchain.ts"], "include": ["src/index.ts"],
"extends": "../../../tsconfig.json", "extends": "../../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "bin", "outDir": "bin",

View File

@@ -1,19 +1,36 @@
import { Paths, ToolchainBuilder } from "@zblog/toolchain"; import {
ToolchainBuilder,
Paths,
FileLoader,
MarkdownLoader,
StartPagePlugin,
} from "@zblog/toolchain";
const paths: Paths = { const paths: Paths = {
outputDirectory: "dist", outputDirectory: "dist",
notes: {
rootPath: "src/notes",
assetsDirectoryName: "assets",
noteFileName: "note.md",
markdownHtmlVariableReplacementTag: "{{markdown}}",
},
templates: {
rootPath: "src/templates",
noteTemplateName: "note.html",
indexTemplateName: "index.html",
notFoundName: "404.html",
},
}; };
new ToolchainBuilder(paths).build(); new ToolchainBuilder(paths)
.addPlugins([
new MarkdownLoader({
rootPath: "src/notes",
assetsDirectoryName: "assets",
markdownHtmlReplacementTag: "{{markdown}}",
noteFileName: "note.md",
noteHtmlTemplatePath: `src/templates/note.html`,
}),
new FileLoader({
files: [
{
path: "src/templates/404.html",
nameOverride: "Not Found Page",
menuEntry: true,
},
],
}),
new StartPagePlugin({
indexTemplatePath: "src/templates/index.html",
contentTemplateTag: "{{content}}",
}),
])
.build();