63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
/**
|
|
* Server-side HTML renderer for markwhen projections.
|
|
*
|
|
* Shells out to `@markwhen/mw` CLI (same binary the official tool uses)
|
|
* rather than importing the parser directly. Why: the view templates'
|
|
* inlined Vue app was built against `@markwhen/parser@0.10.x`, whose
|
|
* output shape differs from the npm-current `@markwhen/parser@1.x`. The
|
|
* CLI ships its own matched parser, so delegating to it avoids any
|
|
* version-skew silently breaking the render.
|
|
*
|
|
* The binary writes HTML to a destination file, so we round-trip through
|
|
* a per-request temp file pair. ~5ms overhead; cheap vs. the correctness.
|
|
*/
|
|
|
|
import { writeFileSync, readFileSync, unlinkSync, mkdtempSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { createRequire } from 'node:module';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
const require_ = createRequire(import.meta.url);
|
|
let mwBinPath: string | null = null;
|
|
|
|
function findMwBinary(): string {
|
|
if (mwBinPath) return mwBinPath;
|
|
const pkgJson = require_.resolve('@markwhen/mw/package.json');
|
|
const pkgDir = pkgJson.slice(0, pkgJson.length - '/package.json'.length);
|
|
mwBinPath = join(pkgDir, 'lib', 'index.js');
|
|
return mwBinPath;
|
|
}
|
|
|
|
export function renderMarkwhenHtml(
|
|
mwText: string,
|
|
view: 'timeline' | 'calendar' = 'timeline',
|
|
): string {
|
|
if (!mwText.trim()) {
|
|
return `<!doctype html><meta charset="utf-8"><title>empty</title>
|
|
<body style="display:grid;place-items:center;height:100vh;margin:0;font:13px system-ui;color:#94a3b8;background:#0b1221">
|
|
<div>No dated events to show yet.</div>
|
|
</body>`;
|
|
}
|
|
|
|
const dir = mkdtempSync(join(tmpdir(), 'rpast-'));
|
|
const input = join(dir, 'in.mw');
|
|
const output = join(dir, `out.${view === 'calendar' ? 'calendar' : 'timeline'}.html`);
|
|
writeFileSync(input, mwText, 'utf-8');
|
|
|
|
try {
|
|
const result = spawnSync(process.execPath, [findMwBinary(), input, '-o', view, '-d', output], {
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
timeout: 15_000,
|
|
});
|
|
if (result.status !== 0) {
|
|
const stderr = result.stderr?.toString() ?? '';
|
|
throw new Error(`mw CLI exited ${result.status}: ${stderr.slice(0, 400)}`);
|
|
}
|
|
return readFileSync(output, 'utf-8');
|
|
} finally {
|
|
try { unlinkSync(input); } catch {}
|
|
try { unlinkSync(output); } catch {}
|
|
}
|
|
}
|