diff --git a/bin/threatdown.ts b/bin/threatdown.ts index 48dbf7a..9078105 100755 --- a/bin/threatdown.ts +++ b/bin/threatdown.ts @@ -6,6 +6,7 @@ import { parseArgs } from "node:util"; import { parse, compileToMermaid, + renderMermaid, } from "../lib"; const { @@ -20,6 +21,7 @@ const { }, outputType: { type: "string", + alias: "type", short: "t", default: "mermaid", }, @@ -34,11 +36,11 @@ function usage () { `); } -function main () { +async function main () { const inputFile = positionals.shift(); // non-null assertion safe because the outputType has a default // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (!inputFile || !["json", "mermaid"].includes(values.outputType!)) { + if (!inputFile || !["json", "mermaid", "svg"].includes(values.outputType!)) { usage(); } else { const fileContent = readFileSync(resolve(process.cwd(), inputFile), { encoding: "utf8" }); @@ -59,8 +61,22 @@ function main () { } else { console.log(mermaidContent); } + return; + } + + const svgContent = await renderMermaid(mermaidContent); + if (values.outputType === "svg") { + if (values.output) { + writeFileSync(resolve(process.cwd(), values.output), svgContent); + } else { + console.log(svgContent); + } } } } -main(); +main() + .catch((err: Error) => { + process.exitCode = 1; + console.error(err.stack); + }); diff --git a/lib/index.ts b/lib/index.ts index 9655f98..cb1608b 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,2 +1,3 @@ export { parse } from "./parser"; export { compileToMermaid } from "./compiler"; +export { renderMermaid } from "./renderer"; diff --git a/lib/renderer.ts b/lib/renderer.ts new file mode 100644 index 0000000..f2a1004 --- /dev/null +++ b/lib/renderer.ts @@ -0,0 +1,31 @@ +import { spawn } from "node:child_process"; +import { readFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +export async function renderMermaid (contents: string): Promise { + const tmpOutput = join(tmpdir(), `.threatdown-${Date.now()}.svg`); + + let resolve: (value?: unknown) => void; + let reject: (err: Error) => void; + const p = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + const mmdc = spawn("mmdc", ["--input", "-", "--output", tmpOutput], { stdio: "pipe" }); + + mmdc.on("error", (err: Error) => { + reject(err); + }); + + mmdc.on("close", () => { + resolve(); + }); + + mmdc.stdin.end(contents); + await p; + + const rendered = await readFile(tmpOutput, { encoding: "utf8" }); + return rendered; +} diff --git a/package.json b/package.json index 0a2bdae..3787bdd 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,8 @@ "lib/**/*.js", "lib/**/*.d.ts", "!lib/types/**" - ] + ], + "dependencies": { + "mermaid-cli": "^0.2.4" + } }