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 * as fs from "fs";
|
||||||
import { toHtml } from "./markdown.js";
|
import { destructivelyRecreateDirectory } from "./toolchain-helpers.js";
|
||||||
|
import { loadMarkdown } from "./markdown-loader.js";
|
||||||
|
|
||||||
export type Paths = {
|
export type Paths = {
|
||||||
outputDirectory: string;
|
outputDirectory: string;
|
||||||
@@ -7,6 +8,7 @@ export type Paths = {
|
|||||||
rootPath: string;
|
rootPath: string;
|
||||||
assetsDirectoryName: string;
|
assetsDirectoryName: string;
|
||||||
noteFileName: string;
|
noteFileName: string;
|
||||||
|
markdownHtmlVariableReplacementTag: string;
|
||||||
};
|
};
|
||||||
templates: {
|
templates: {
|
||||||
rootPath: string;
|
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 {
|
export class ToolchainBuilder {
|
||||||
private paths: Paths;
|
private paths: Paths;
|
||||||
|
private menuManifest: MenuManifest = [];
|
||||||
constructor(paths: Paths) {
|
constructor(paths: Paths) {
|
||||||
this.paths = paths;
|
this.paths = paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
build() {
|
async build() {
|
||||||
if (fs.readdirSync(".").includes(this.paths.outputDirectory)) {
|
destructivelyRecreateDirectory(this.paths.outputDirectory);
|
||||||
fs.rmSync(this.paths.outputDirectory, { recursive: true, force: true });
|
|
||||||
console.log("Starting rebuild");
|
loadMarkdown(
|
||||||
} else {
|
{
|
||||||
console.log("Starting build");
|
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();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
await plugin({ menuManifest: this.menuManifest });
|
||||||
}
|
}
|
||||||
fs.mkdirSync(this.paths.outputDirectory);
|
|
||||||
console.log("Indexing content..");
|
// static html loader
|
||||||
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..");
|
|
||||||
[this.paths.templates.notFoundName].forEach((filename) => {
|
[this.paths.templates.notFoundName].forEach((filename) => {
|
||||||
fs.copyFileSync(
|
fs.copyFileSync(
|
||||||
`${this.paths.templates.rootPath}/${filename}`,
|
`${this.paths.templates.rootPath}/${filename}`,
|
||||||
@@ -83,8 +77,8 @@ export class ToolchainBuilder {
|
|||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const links = manifest
|
const links = this.menuManifest
|
||||||
.map((m) => `<a href='/${m.directoryName}.html'>${m.name}</a>`)
|
.map((m) => `<a href='${m.link}'>${m.name}</a>`)
|
||||||
.reverse();
|
.reverse();
|
||||||
const unorderedListItems = links.map((l) => `<li>${l}</li>`).join("\r\n");
|
const unorderedListItems = links.map((l) => `<li>${l}</li>`).join("\r\n");
|
||||||
const html = `
|
const html = `
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const paths: Paths = {
|
|||||||
rootPath: "src/notes",
|
rootPath: "src/notes",
|
||||||
assetsDirectoryName: "assets",
|
assetsDirectoryName: "assets",
|
||||||
noteFileName: "note.md",
|
noteFileName: "note.md",
|
||||||
|
markdownHtmlVariableReplacementTag: "{{markdown}}",
|
||||||
},
|
},
|
||||||
templates: {
|
templates: {
|
||||||
rootPath: "src/templates",
|
rootPath: "src/templates",
|
||||||
|
|||||||
Reference in New Issue
Block a user