buildmd/index.ts
2025-05-23 16:05:45 -04:00

150 lines
3 KiB
TypeScript

import Path from "node:path";
import { parseArgs } from "util";
import { marked } from "marked";
const { values: flags, positionals } = parseArgs({
args: Bun.argv.slice(2),
options: {
outfile: {
type: 'string',
short: 'o',
default: 'index.html',
},
template: {
type: 'string',
short: 't',
default: 'template.html',
},
stylesheet: {
type: 'string',
short: 's',
default: 'style.css',
},
},
allowPositionals: true,
});
async function readOptionalFileContent(filePath: string, defaultContent: string): Promise<string> {
const file = Bun.file(filePath);
const exists = await file.exists();
if (!exists) {
return defaultContent;
}
return file.text();
}
const inputFileArg: string | null = positionals[0] ?? null;
const inputFileName = Path.basename(inputFileArg ?? 'Document', '.md');
const inputFile = inputFileArg ? Bun.file(inputFileArg) : Bun.stdin;
const inputFileExists = await inputFile.exists();
if (!inputFileExists) {
throw new Error(`Input file ${inputFileArg} does not exist.`);
}
const outputFile = Bun.file(flags.outfile);
const templateFile = await readOptionalFileContent(flags.template, `
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>%title%</title>
<style>
%stylesheet%
</style>
</head>
<body>
<article>
%content%
</article>
</body>
</html>
`.trim());
const stylesheetFile = await readOptionalFileContent(flags.stylesheet, `
:root {
--color-bg: #333;
--color-text: #DDD;
--color-link: #FFF;
--color-strong: #ff8f8f;
--color-emphasis: #FF8F00;
--font-size: 18px;
--font-family: Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif;
--font-weight: 400;
--font-weight-bold: 500;
--line-height: 1.4;
--max-width: 700px;
}
body {
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size);
font-family: var(--font-family);
font-weight: var(--font-weight);
line-height: var(--line-height);
}
article {
max-width: var(--max-width);
margin: 0 auto;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-family);
font-weight: var(--font-weight-bold);
}
em, i {
color: var(--color-emphasis);
}
strong, b {
color: var(--color-strong);
font-weight: var(--font-weight-bold);
}
a {
color: var(--color-link);
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
font-size: 1em;
background-color: var(--color-bg);
color: var(--color-text);
border: 1px solid var(--color-text);
}
th, td {
padding: 0.75em 1em;
text-align: left;
border-bottom: 1px solid var(--color-text);
}
th {
background: var(--color-bg);
font-weight: 500;
}
`.trim());
const markdown = await inputFile.text();
const html = await marked.parse(markdown, {
gfm: true,
});
const output = templateFile
.replace("%stylesheet%", stylesheetFile)
.replace("%content%", html)
.replace("%title%", inputFileName);
await outputFile.write(output);