legal-markdown-js
Version:
Node.js implementation of LegalMarkdown for processing legal documents with markdown and YAML - Complete feature parity with Ruby version
113 lines • 4 kB
JavaScript
import * as fsp from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import { execFile, spawn } from 'child_process';
function execFileAsync(command, args) {
return new Promise((resolve, reject) => {
execFile(command, args, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
resolve({ stdout, stderr });
});
});
}
export class WeasyprintConnector {
name = 'weasyprint';
executablePath = null;
async detectExecutablePath() {
if (this.executablePath) {
return this.executablePath;
}
try {
const { stdout } = await execFileAsync('which', ['weasyprint']);
const resolved = stdout.trim();
this.executablePath = resolved || null;
return this.executablePath;
}
catch {
this.executablePath = null;
return null;
}
}
async isAvailable() {
return Boolean(await this.detectExecutablePath());
}
buildPageCss(options) {
const size = options.landscape ? `${options.format} landscape` : options.format;
return `@page { size: ${size}; margin: ${options.margin.top} ${options.margin.right} ${options.margin.bottom} ${options.margin.left}; }`;
}
async generatePdf(html, outputPath, options) {
const executablePath = await this.detectExecutablePath();
if (!executablePath) {
throw new Error('weasyprint executable not found.');
}
await fsp.mkdir(path.dirname(outputPath), { recursive: true });
const tempDir = path.join(os.tmpdir(), 'legal-md-weasyprint');
await fsp.mkdir(tempDir, { recursive: true });
const id = `${Date.now()}-${Math.random()}`;
const tempHtmlPath = path.join(tempDir, `input-${id}.html`);
const tempCssPath = path.join(tempDir, `print-${id}.css`);
try {
await fsp.writeFile(tempHtmlPath, html, 'utf8');
await fsp.writeFile(tempCssPath, this.buildPageCss(options), 'utf8');
const args = [
'--media-type',
'print',
'--stylesheet',
tempCssPath,
tempHtmlPath,
path.resolve(outputPath),
];
await new Promise((resolve, reject) => {
const child = spawn(executablePath, args, {
stdio: 'ignore',
});
child.on('error', error => {
reject(error);
});
child.on('exit', code => {
if (code === 0) {
resolve();
return;
}
reject(new Error(`weasyprint exited with code ${code ?? 'unknown'}`));
});
});
}
finally {
await Promise.all([
fsp.unlink(tempHtmlPath).catch(() => undefined),
fsp.unlink(tempCssPath).catch(() => undefined),
]);
}
}
async getInfo() {
const executablePath = await this.detectExecutablePath();
if (!executablePath) {
return {
name: this.name,
version: 'not-installed',
};
}
try {
const { stdout, stderr } = await execFileAsync(executablePath, ['--version']);
const output = (stdout || stderr).trim();
const versionMatch = output.match(/(\d+(?:\.\d+)+)/);
return {
name: this.name,
version: versionMatch?.[1] ?? (output || 'unknown'),
executablePath,
};
}
catch {
return {
name: this.name,
version: 'unknown',
executablePath,
};
}
}
}
//# sourceMappingURL=weasyprint-connector.js.map