Continuation on breaking out functionality
This commit is contained in:
112
packages/@zblog/toolchain/src/markdown-loader.ts
Normal file
112
packages/@zblog/toolchain/src/markdown-loader.ts
Normal file
@@ -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);
|
||||
};
|
||||
24
packages/@zblog/toolchain/src/toolchain-helpers.ts
Normal file
24
packages/@zblog/toolchain/src/toolchain-helpers.ts
Normal file
@@ -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);
|
||||
};
|
||||
@@ -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<void>;
|
||||
|
||||
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");
|
||||
}
|
||||
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,
|
||||
async build() {
|
||||
destructivelyRecreateDirectory(this.paths.outputDirectory);
|
||||
|
||||
loadMarkdown(
|
||||
{
|
||||
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}`
|
||||
)
|
||||
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<void>((resolve) => {
|
||||
resolve();
|
||||
});
|
||||
console.log("Moving static templates..");
|
||||
},
|
||||
];
|
||||
|
||||
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}`,
|
||||
@@ -83,8 +77,8 @@ export class ToolchainBuilder {
|
||||
encoding: "utf-8",
|
||||
}
|
||||
);
|
||||
const links = manifest
|
||||
.map((m) => `<a href='/${m.directoryName}.html'>${m.name}</a>`)
|
||||
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 = `
|
||||
|
||||
@@ -6,6 +6,7 @@ const paths: Paths = {
|
||||
rootPath: "src/notes",
|
||||
assetsDirectoryName: "assets",
|
||||
noteFileName: "note.md",
|
||||
markdownHtmlVariableReplacementTag: "{{markdown}}",
|
||||
},
|
||||
templates: {
|
||||
rootPath: "src/templates",
|
||||
|
||||
Reference in New Issue
Block a user