File size: 1,685 Bytes
43370b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { spawn } from 'node:child_process'
import { promises as fs } from 'node:fs'
import os from 'node:os'
import path from 'node:path'

export type ExportFormat = 'pdf' | 'pptx' | 'html'

export const MIME: Record<ExportFormat, string> = {
  pdf: 'application/pdf',
  pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  html: 'text/html',
}

/**
 * Render markdown to a file with marp-cli. PDF/PPTX need headless Chrome (provided in
 * the Docker image via CHROME_PATH + CHROME_NO_SANDBOX); HTML needs nothing.
 */
export async function exportDeck(markdown: string, format: ExportFormat): Promise<Buffer> {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'marp-'))
  const input = path.join(dir, 'deck.md')
  const output = path.join(dir, `deck.${format}`)
  await fs.writeFile(input, markdown, 'utf8')
  try {
    const args = [input, '-o', output, '--allow-local-files']
    if (format === 'pdf') args.push('--pdf')
    else if (format === 'pptx') args.push('--pptx')
    // html is inferred from the .html output extension
    await runMarp(args)
    return await fs.readFile(output)
  } finally {
    await fs.rm(dir, { recursive: true, force: true })
  }
}

function runMarp(args: string[]): Promise<void> {
  return new Promise((resolve, reject) => {
    const bin = path.resolve('node_modules/.bin/marp')
    const child = spawn(bin, args, { stdio: ['ignore', 'ignore', 'pipe'] })
    let err = ''
    child.stderr.on('data', (d) => {
      err += d.toString()
    })
    child.on('error', reject)
    child.on('close', (code) =>
      code === 0 ? resolve() : reject(new Error(`marp exited ${code}: ${err.slice(-600)}`)),
    )
  })
}