Select Git revision
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
index.ts 5.84 KiB
import fs from "node:fs/promises";
import {
generateIndexHtml,
generateResolutionsHtml,
generateTranscriptsRssXml,
renderTranscriptPageHtml,
} from "./lib/rendering";
import bootstrap from "bootstrap/dist/css/bootstrap.min.css";
import { Command } from "commander";
import * as Path from "path";
import { Protocol, parseProtocol, parseProtocolFromFile } from "./lib/parsing/protocol";
async function main() {
const program = new Command();
program
.name("fsinfo-transcript-postprocessor")
.description("CLI to post-process markdown transcripts of FSR meetings")
.version("unknown")
.argument(
"<inputDir>",
"path to directory containing the input markdown files"
)
.argument("<outputDir>", "path to output the rendered html to")
.option("-A, --output-ast", "Also save the AST as a file", false)
.option("-B, --base-url [url]", "Basic URL for the RSS feed")
.action(run);
await program.parseAsync(process.argv);
}
main();
export interface CliOptions {
outputAst: boolean;
baseUrl: string;
}
async function run(inputDir: string, outputDir: string, options: CliOptions) {
// (1) Get a list of files to read
const files = await getListOfFiles(inputDir);
// (2) Parse all of them, report errors
if (files.length > 1000) {
throw "Too many files to be read, something is off: " + files.length;
}
console.info("⚙️ Parsing", files.length, "markdown files...");
const protocols = await async function () {
// parse the protocols
const protocolsWithErrors = await Promise.all(
files.map(file => parseProtocolFromFile(file))
);
// extract errors and exit if any exist
const errors = protocolsWithErrors.filter(p => typeof p === "string");
if (errors.length > 0) {
console.error(errors.length, "transcripts had errors. Aborting.");
process.exit(1);
}
// there are no errors, so return as Protocol[]
return protocolsWithErrors
.map(p => p as Protocol);
}();
// Generate transcript pages
console.info("✏️ Writing individual HTML files...");
await generateAllTranscriptPages(
outputDir,
protocols,
options
);
// Generate index page
console.info("✏️ Writing index file...");
await generateIndexPage(
outputDir,
protocols,
options
);
// Generate resolution list
console.info("✏️ Writing resolutions list file...");
await generateResolutionsPage(
outputDir,
protocols
);
await fs.writeFile(outputDir + "/bootstrap.min.css", bootstrap, "utf8");
// Generate JSON of all data
console.info("✏️ Writing Metadata as JSON file...");
await fs.writeFile(
outputDir + "/index.json",
JSON.stringify(
protocols.map(p => ({
...p.data,
tags: null,
categories: null,
contents: "gone_with_this_generator_sorry",
}))
)
);
// Create RSS file
console.info("✏️ Writing RSS feed index.xml...");
const rssXml = generateTranscriptsRssXml(
protocols,
options.baseUrl ?? process.env.CI_PAGES_URL
);
await fs.writeFile(Path.join(outputDir, "index.xml"), rssXml);
await fs.writeFile(Path.join(outputDir, "protokolle/index.xml"), rssXml);
// Be done
console.info("✅ All done!");
}
async function getListOfFiles(rootPath: string | undefined): Promise<string[]> {
if (typeof rootPath !== "string") {
throw new TypeError("Missing argument <filesDirectory>");
}
return (await fs.readdir(rootPath))
.filter((path) => path.endsWith(".md"))
.map((path) => rootPath + "/" + path)
.reverse();
}
async function generateAllTranscriptPages(
outputDir: string,
protocols: Protocol[],
options: CliOptions
) {
// make sure the dir exists and is empty
await fs.mkdir(outputDir, { recursive: true });
await fs.rm(outputDir, {
force: true,
recursive: true,
});
await fs.mkdir(outputDir, { recursive: true });
await Promise.all(
protocols.map(p => generateTranscriptPage(p, outputDir, options)
)
);
}
async function generateTranscriptPage(
protocol: Protocol,
outputDir: string,
options: CliOptions
) {
const { data, html, ast } = protocol;
if (protocol.session_type === "fsr") {
const transcriptDir = `protokolle/fsr-sitzung-${data.number}-${data.date}`;
const dir = Path.join(outputDir, transcriptDir);
await fs.mkdir(dir, { recursive: true, });
await fs.writeFile(Path.join(dir, "index.html"), html, "utf-8");
const pageHtml = renderTranscriptPageHtml(protocol);
await fs.writeFile(Path.join(dir, "index.html"), pageHtml, "utf-8");
if (options.outputAst) {
await fs.writeFile(
Path.join(dir, "ast.json"),
JSON.stringify(ast, null, 2),
"utf-8"
);
}
} else if (protocol.session_type === "fvv") {
const transcriptDir = `protokolle/fvv-sitzung-${data.number}-${data.date}`;
const dir = Path.join(outputDir, transcriptDir);
await fs.mkdir(dir, { recursive: true, });
const pageHtml = renderTranscriptPageHtml(protocol);
await fs.writeFile(Path.join(dir, "index.html"), pageHtml, "utf-8");
if (options.outputAst) {
await fs.writeFile(
Path.join(dir, "ast.json"),
JSON.stringify(ast, null, 2),
"utf-8"
);
}
} else {
console.log(`unknown session type ${protocol.session_type}`)
console.log("not generating transcript page")
}
}
async function generateIndexPage(
outputDir: string,
protocols: Protocol[],
options: CliOptions
) {
const html = generateIndexHtml(protocols);
const htmlFilePath = outputDir + "/index.html";
await fs.writeFile(htmlFilePath, html, "utf8");
}
async function generateResolutionsPage(
outputDir: string,
protocols: Protocol[]
) {
const html = generateResolutionsHtml(protocols);
await fs.mkdir(outputDir + "/resolutions/");
const htmlFilePath = outputDir + "/resolutions/index.html";
await fs.writeFile(htmlFilePath, html, "utf8");
}