@imontene/mdrender 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +243 -0
  4. package/dist/assets/theme-dark.css +40 -0
  5. package/dist/assets/theme.css +545 -0
  6. package/dist/assets.d.ts +45 -0
  7. package/dist/assets.js +109 -0
  8. package/dist/bin.d.ts +2 -0
  9. package/dist/bin.js +16 -0
  10. package/dist/browser/cache.d.ts +15 -0
  11. package/dist/browser/cache.js +30 -0
  12. package/dist/browser/detect.d.ts +17 -0
  13. package/dist/browser/detect.js +90 -0
  14. package/dist/browser/setup.d.ts +13 -0
  15. package/dist/browser/setup.js +69 -0
  16. package/dist/cli.d.ts +8 -0
  17. package/dist/cli.js +194 -0
  18. package/dist/config.d.ts +6 -0
  19. package/dist/config.js +32 -0
  20. package/dist/convert.d.ts +64 -0
  21. package/dist/convert.js +167 -0
  22. package/dist/diagnostics.d.ts +8 -0
  23. package/dist/diagnostics.js +6 -0
  24. package/dist/errors.d.ts +12 -0
  25. package/dist/errors.js +21 -0
  26. package/dist/exit-codes.d.ts +8 -0
  27. package/dist/exit-codes.js +8 -0
  28. package/dist/frontmatter.d.ts +10 -0
  29. package/dist/frontmatter.js +26 -0
  30. package/dist/html.d.ts +2 -0
  31. package/dist/html.js +12 -0
  32. package/dist/i18n.d.ts +9 -0
  33. package/dist/i18n.js +59 -0
  34. package/dist/index.d.ts +17 -0
  35. package/dist/index.js +18 -0
  36. package/dist/inputs.d.ts +26 -0
  37. package/dist/inputs.js +118 -0
  38. package/dist/markdown/alerts.d.ts +6 -0
  39. package/dist/markdown/alerts.js +35 -0
  40. package/dist/markdown/env.d.ts +29 -0
  41. package/dist/markdown/env.js +16 -0
  42. package/dist/markdown/highlight.d.ts +6 -0
  43. package/dist/markdown/highlight.js +17 -0
  44. package/dist/markdown/images.d.ts +9 -0
  45. package/dist/markdown/images.js +93 -0
  46. package/dist/markdown/index.d.ts +27 -0
  47. package/dist/markdown/index.js +69 -0
  48. package/dist/markdown/math.d.ts +9 -0
  49. package/dist/markdown/math.js +147 -0
  50. package/dist/markdown/mermaid.d.ts +8 -0
  51. package/dist/markdown/mermaid.js +22 -0
  52. package/dist/markdown/task-lists.d.ts +6 -0
  53. package/dist/markdown/task-lists.js +40 -0
  54. package/dist/markdown/toc.d.ts +22 -0
  55. package/dist/markdown/toc.js +101 -0
  56. package/dist/options.d.ts +55 -0
  57. package/dist/options.js +131 -0
  58. package/dist/paths.d.ts +2 -0
  59. package/dist/paths.js +4 -0
  60. package/dist/preview.d.ts +22 -0
  61. package/dist/preview.js +126 -0
  62. package/dist/render/browser.d.ts +48 -0
  63. package/dist/render/browser.js +134 -0
  64. package/dist/render/fonts.d.ts +10 -0
  65. package/dist/render/fonts.js +47 -0
  66. package/dist/render/header-footer.d.ts +22 -0
  67. package/dist/render/header-footer.js +39 -0
  68. package/dist/render/katex.d.ts +2 -0
  69. package/dist/render/katex.js +19 -0
  70. package/dist/render/mermaid.d.ts +25 -0
  71. package/dist/render/mermaid.js +91 -0
  72. package/dist/render/page.d.ts +7 -0
  73. package/dist/render/page.js +23 -0
  74. package/dist/render/template.d.ts +22 -0
  75. package/dist/render/template.js +54 -0
  76. package/dist/run.d.ts +26 -0
  77. package/dist/run.js +71 -0
  78. package/dist/version.d.ts +1 -0
  79. package/dist/version.js +4 -0
  80. package/dist/watch.d.ts +25 -0
  81. package/dist/watch.js +128 -0
  82. package/package.json +97 -0
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Per-user folder where `mdrender setup` keeps its downloaded browser:
3
+ * %LOCALAPPDATA%\mdrender on Windows, ~/Library/Caches/mdrender on macOS and
4
+ * $XDG_CACHE_HOME/mdrender (or ~/.cache/mdrender) elsewhere. MDRENDER_CACHE_DIR overrides it.
5
+ */
6
+ export declare function cacheDir(env?: Record<string, string | undefined>, platform?: NodeJS.Platform, home?: string): string;
7
+ /** Small record written by `mdrender setup` pointing at the browser it installed. */
8
+ export interface InstalledBrowserRecord {
9
+ executablePath: string;
10
+ buildId: string;
11
+ installedAt: string;
12
+ }
13
+ export declare const RECORD_FILE = "browser.json";
14
+ /** The browser installed by `mdrender setup`, if it is still there. */
15
+ export declare function installedBrowser(dir?: string): string | undefined;
@@ -0,0 +1,30 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ /**
5
+ * Per-user folder where `mdrender setup` keeps its downloaded browser:
6
+ * %LOCALAPPDATA%\mdrender on Windows, ~/Library/Caches/mdrender on macOS and
7
+ * $XDG_CACHE_HOME/mdrender (or ~/.cache/mdrender) elsewhere. MDRENDER_CACHE_DIR overrides it.
8
+ */
9
+ export function cacheDir(env = process.env, platform = process.platform, home = os.homedir()) {
10
+ if (env.MDRENDER_CACHE_DIR)
11
+ return path.resolve(env.MDRENDER_CACHE_DIR);
12
+ if (platform === "win32") {
13
+ return path.win32.join(env.LOCALAPPDATA ?? path.win32.join(home, "AppData", "Local"), "mdrender");
14
+ }
15
+ if (platform === "darwin")
16
+ return path.posix.join(home, "Library", "Caches", "mdrender");
17
+ return path.posix.join(env.XDG_CACHE_HOME ?? path.posix.join(home, ".cache"), "mdrender");
18
+ }
19
+ export const RECORD_FILE = "browser.json";
20
+ /** The browser installed by `mdrender setup`, if it is still there. */
21
+ export function installedBrowser(dir = cacheDir()) {
22
+ try {
23
+ const record = JSON.parse(readFileSync(path.join(dir, RECORD_FILE), "utf8"));
24
+ return existsSync(record.executablePath) ? record.executablePath : undefined;
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1,17 @@
1
+ export interface DetectOptions {
2
+ platform?: NodeJS.Platform;
3
+ env?: Record<string, string | undefined>;
4
+ exists?: (file: string) => boolean;
5
+ /** Browser downloaded by `mdrender setup`; looked up in the cache folder by default. */
6
+ downloaded?: () => string | undefined;
7
+ }
8
+ /** Well-known Chromium-based browser locations for a platform, in order of preference. */
9
+ export declare function browserCandidates(platform?: NodeJS.Platform, env?: Record<string, string | undefined>): string[];
10
+ /**
11
+ * Find a Chromium-based browser executable.
12
+ *
13
+ * Order: the explicit path (`--browser`), then `MDRENDER_BROWSER`, then well-known locations,
14
+ * then the browser downloaded by `mdrender setup`.
15
+ * An explicit path or env var that does not exist is an error rather than silently ignored.
16
+ */
17
+ export declare function findBrowser(explicit?: string, options?: DetectOptions): string;
@@ -0,0 +1,90 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { BrowserNotFoundError } from "../errors.js";
4
+ import { installedBrowser } from "./cache.js";
5
+ /** Look up an environment variable case-insensitively (Windows env names are not case-sensitive). */
6
+ function getEnv(env, name) {
7
+ if (env[name] !== undefined)
8
+ return env[name];
9
+ const key = Object.keys(env).find((k) => k.toLowerCase() === name.toLowerCase());
10
+ return key ? env[key] : undefined;
11
+ }
12
+ const WINDOWS_APPS = [
13
+ "Google\\Chrome\\Application\\chrome.exe",
14
+ "Chromium\\Application\\chrome.exe",
15
+ "Microsoft\\Edge\\Application\\msedge.exe",
16
+ "BraveSoftware\\Brave-Browser\\Application\\brave.exe",
17
+ ];
18
+ const LINUX_COMMANDS = [
19
+ "google-chrome",
20
+ "google-chrome-stable",
21
+ "chromium",
22
+ "chromium-browser",
23
+ "microsoft-edge",
24
+ "microsoft-edge-stable",
25
+ "brave-browser",
26
+ ];
27
+ const LINUX_FIXED = [
28
+ "/opt/google/chrome/chrome",
29
+ "/opt/microsoft/msedge/msedge",
30
+ "/snap/bin/chromium",
31
+ "/usr/bin/chromium",
32
+ "/usr/bin/chromium-browser",
33
+ ];
34
+ const MAC_APPS = [
35
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
36
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
37
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
38
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
39
+ ];
40
+ /** Well-known Chromium-based browser locations for a platform, in order of preference. */
41
+ export function browserCandidates(platform = process.platform, env = process.env) {
42
+ if (platform === "win32") {
43
+ const roots = ["ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"]
44
+ .map((name) => getEnv(env, name))
45
+ .filter((root) => Boolean(root));
46
+ return WINDOWS_APPS.flatMap((app) => roots.map((root) => path.win32.join(root, app)));
47
+ }
48
+ if (platform === "darwin") {
49
+ return MAC_APPS;
50
+ }
51
+ const dirs = (getEnv(env, "PATH") ?? "").split(":").filter(Boolean);
52
+ const fromPath = LINUX_COMMANDS.flatMap((cmd) => dirs.map((dir) => path.posix.join(dir, cmd)));
53
+ return [...new Set([...fromPath, ...LINUX_FIXED])];
54
+ }
55
+ function isFile(file) {
56
+ try {
57
+ return existsSync(file) && statSync(file).isFile();
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ /**
64
+ * Find a Chromium-based browser executable.
65
+ *
66
+ * Order: the explicit path (`--browser`), then `MDRENDER_BROWSER`, then well-known locations,
67
+ * then the browser downloaded by `mdrender setup`.
68
+ * An explicit path or env var that does not exist is an error rather than silently ignored.
69
+ */
70
+ export function findBrowser(explicit, options = {}) {
71
+ const env = options.env ?? process.env;
72
+ const exists = options.exists ?? isFile;
73
+ const requested = explicit ?? getEnv(env, "MDRENDER_BROWSER");
74
+ if (requested) {
75
+ if (exists(requested))
76
+ return requested;
77
+ const source = explicit ? "--browser" : "MDRENDER_BROWSER";
78
+ throw new BrowserNotFoundError(`browser not found at ${requested} (from ${source})`);
79
+ }
80
+ const found = browserCandidates(options.platform, env).find(exists);
81
+ if (found)
82
+ return found;
83
+ const downloaded = (options.downloaded ?? installedBrowser)();
84
+ if (downloaded)
85
+ return downloaded;
86
+ throw new BrowserNotFoundError("no Chromium-based browser found (Google Chrome, Chromium, Microsoft Edge or Brave).\n" +
87
+ "Run `mdrender setup` to download one, install one, or point to it with --browser <path>\n" +
88
+ "or the MDRENDER_BROWSER variable.");
89
+ }
90
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1,13 @@
1
+ import { ExitCode } from "../exit-codes.js";
2
+ import type { Io } from "../run.js";
3
+ export interface SetupOptions {
4
+ io: Io;
5
+ /** Download even when a system browser is available. */
6
+ force?: boolean;
7
+ dir?: string;
8
+ }
9
+ /**
10
+ * Download Chrome Headless Shell (the official headless build of Chrome, ~100 MB) into the
11
+ * user's cache folder, for machines without Chrome, Edge or Chromium.
12
+ */
13
+ export declare function setup({ io, force, dir, }: SetupOptions): Promise<ExitCode>;
@@ -0,0 +1,69 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { Browser, BrowserPlatform, detectBrowserPlatform, install, resolveBuildId, } from "@puppeteer/browsers";
4
+ import { ExitCode } from "../exit-codes.js";
5
+ import { cacheDir, RECORD_FILE } from "./cache.js";
6
+ import { findBrowser } from "./detect.js";
7
+ /**
8
+ * Download Chrome Headless Shell (the official headless build of Chrome, ~100 MB) into the
9
+ * user's cache folder, for machines without Chrome, Edge or Chromium.
10
+ */
11
+ export async function setup({ io, force = false, dir = cacheDir(), }) {
12
+ let system;
13
+ try {
14
+ system = findBrowser();
15
+ }
16
+ catch {
17
+ // none installed: download one
18
+ }
19
+ if (system && !force) {
20
+ io.err(`A browser is already installed: ${system}\nNo download needed (use --force to download anyway).\n`);
21
+ io.out(`${system}\n`);
22
+ return ExitCode.Ok;
23
+ }
24
+ const platform = detectBrowserPlatform();
25
+ if (!platform || platform === BrowserPlatform.LINUX_ARM) {
26
+ io.err("error: Chrome Headless Shell is not published for this platform.\n" +
27
+ "Install Chromium with your package manager (e.g. `sudo apt install chromium`).\n");
28
+ return ExitCode.BrowserNotFound;
29
+ }
30
+ const browser = Browser.CHROMEHEADLESSSHELL;
31
+ let buildId;
32
+ let executablePath;
33
+ try {
34
+ io.err("Looking up the latest stable Chrome Headless Shell…\n");
35
+ buildId = await resolveBuildId(browser, platform, "stable");
36
+ io.err(`Downloading Chrome Headless Shell ${buildId} into ${dir}\n`);
37
+ let lastPercent = -1;
38
+ ({ executablePath } = await install({
39
+ browser,
40
+ buildId,
41
+ platform,
42
+ cacheDir: dir,
43
+ downloadProgressCallback: (done, total) => {
44
+ const percent = total ? Math.floor((done / total) * 100) : 0;
45
+ if (percent >= lastPercent + 10 || percent === 100) {
46
+ lastPercent = percent;
47
+ io.err(` ${percent}%\n`);
48
+ }
49
+ },
50
+ }));
51
+ }
52
+ catch (error) {
53
+ io.err(`error: could not download the browser: ${error.message}\n` +
54
+ "Check your internet connection or proxy (HTTPS_PROXY), or install Chrome, Edge or\n" +
55
+ "Chromium yourself.\n");
56
+ return ExitCode.BrowserNotFound;
57
+ }
58
+ mkdirSync(dir, { recursive: true });
59
+ const record = {
60
+ executablePath,
61
+ buildId,
62
+ installedAt: new Date().toISOString(),
63
+ };
64
+ writeFileSync(path.join(dir, RECORD_FILE), JSON.stringify(record, null, 2));
65
+ io.err(`Installed: ${executablePath}\n`);
66
+ io.out(`${executablePath}\n`);
67
+ return ExitCode.Ok;
68
+ }
69
+ //# sourceMappingURL=setup.js.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { type Io } from "./run.js";
2
+ export declare const defaultIo: Io;
3
+ export interface MainOptions {
4
+ /** Stops --watch and preview (the CLI aborts it on Ctrl+C). */
5
+ signal?: AbortSignal;
6
+ }
7
+ /** Run the CLI and return the exit code instead of exiting, so it can be tested in-process. */
8
+ export declare function main(argv: string[], io?: Io, { signal }?: MainOptions): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,194 @@
1
+ import { Command, CommanderError, Option } from "commander";
2
+ import { setup } from "./browser/setup.js";
3
+ import { MdRenderError } from "./errors.js";
4
+ import { ExitCode } from "./exit-codes.js";
5
+ import { expandInputs, planOutputs } from "./inputs.js";
6
+ import { MERMAID_THEMES, normalizeOptions, PAGE_SIZES } from "./options.js";
7
+ import { preview } from "./preview.js";
8
+ import { runJobs } from "./run.js";
9
+ import { version } from "./version.js";
10
+ import { watchAndConvert } from "./watch.js";
11
+ /** Command-line flags that map to document options (camelCase = DocumentOptions key). */
12
+ const DOCUMENT_FLAGS = [
13
+ "pageSize",
14
+ "margin",
15
+ "landscape",
16
+ "toc",
17
+ "tocDepth",
18
+ "header",
19
+ "footer",
20
+ "pageNumbers",
21
+ "css",
22
+ "theme",
23
+ "mermaidTheme",
24
+ "lang",
25
+ "title",
26
+ ];
27
+ const toFlag = (key) => "--" + key.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
28
+ export const defaultIo = {
29
+ out: (text) => process.stdout.write(text),
30
+ err: (text) => process.stderr.write(text),
31
+ };
32
+ const collect = (value, previous = []) => [...previous, value];
33
+ /** Options shared by `mdrender` (render) and `mdrender preview`. */
34
+ function addDocumentOptions(command) {
35
+ return command
36
+ .option("--page-size <size>", `paper size: ${PAGE_SIZES.join(", ")} (default: A4)`)
37
+ .option("--margin <margin>", 'page margins, e.g. "20mm" or "15mm 20mm" (default: 20mm)')
38
+ .option("--landscape", "landscape orientation")
39
+ .option("--toc", "insert a table of contents (or place it with [[toc]])")
40
+ .option("--toc-depth <n>", "deepest heading level in the table of contents (default: 3)")
41
+ .option("--header <text>", 'page header, e.g. "{title} | | {date}"')
42
+ .option("--footer <text>", 'page footer (default: "{page} / {pages}")')
43
+ .option("--no-page-numbers", "no default page-number footer")
44
+ .option("--theme <theme>", "light, dark, or a .css file replacing the theme (default: light)")
45
+ .option("--css <file>", "extra stylesheet (repeatable)", collect)
46
+ .option("--mermaid-theme <theme>", `Mermaid theme: ${MERMAID_THEMES.join(", ")} (default: neutral; dark with --theme dark)`)
47
+ .option("--lang <tag>", "document language, e.g. es or en (default: es)")
48
+ .option("--title <text>", "document title (shown as a title block)")
49
+ .option("--config <file>", "config file (default: nearest mdrender.config.json)")
50
+ .option("--no-config", "ignore mdrender.config.json files")
51
+ .option("--browser <path>", "Chrome, Edge or Chromium executable (default: auto-detect)")
52
+ .option("--allow-remote", "let the PDF load remote (http/https) images and stylesheets");
53
+ }
54
+ function configure(command, io) {
55
+ return command.exitOverride().configureOutput({ writeOut: io.out, writeErr: io.err });
56
+ }
57
+ function buildRenderProgram(io) {
58
+ const program = new Command()
59
+ .name("mdrender")
60
+ .description("Render Markdown files to PDF (or self-contained HTML) using the Inter typeface.")
61
+ .version(version, "-V, --version")
62
+ .argument("<inputs...>", "Markdown files, folders or glob patterns (e.g. docs/**/*.md)")
63
+ .option("-o, --output <path>", "output file, or folder for several inputs")
64
+ .addOption(new Option("-f, --format <format>", "output format").choices(["pdf", "html"]).default("pdf"))
65
+ .option("-w, --watch", "convert again whenever the inputs change")
66
+ .option("-q, --quiet", "only print warnings and errors")
67
+ .option("-v, --verbose", "also print config file, browser and timings");
68
+ addDocumentOptions(program).addHelpText("after", `
69
+ Commands:
70
+ mdrender preview <file> live HTML preview in the browser (see: mdrender preview --help)
71
+ mdrender setup download a headless Chrome if no browser is installed
72
+
73
+ Header/footer placeholders: {page} {pages} {title} {author} {date}; "|" separates
74
+ left | center | right columns.
75
+
76
+ Options can also be set in YAML front matter or in mdrender.config.json
77
+ (command line > front matter > config file).`);
78
+ return configure(program, io);
79
+ }
80
+ function buildPreviewProgram(io) {
81
+ const program = new Command()
82
+ .name("mdrender preview")
83
+ .description("Live HTML preview of a Markdown file, reloaded on every change.")
84
+ .argument("<input>", "Markdown file")
85
+ .option("--port <port>", "port on 127.0.0.1 (default: a free port)")
86
+ .option("--no-open", "do not open the browser, only print the URL");
87
+ addDocumentOptions(program);
88
+ return configure(program, io);
89
+ }
90
+ /** Document options given explicitly on the command line (not commander defaults). */
91
+ function cliOverrides(program) {
92
+ const raw = {};
93
+ for (const key of DOCUMENT_FLAGS) {
94
+ if (program.getOptionValueSource(key) === "cli")
95
+ raw[key] = program.getOptionValue(key);
96
+ }
97
+ return normalizeOptions(raw, "command line", process.cwd(), (key) => key === "pageNumbers" ? "--no-page-numbers" : toFlag(key));
98
+ }
99
+ function settings(program, options) {
100
+ return {
101
+ browser: options.browser,
102
+ config: options.config,
103
+ overrides: cliOverrides(program),
104
+ allowRemote: options.allowRemote ?? false,
105
+ };
106
+ }
107
+ async function parse(program, argv) {
108
+ try {
109
+ await program.parseAsync(argv, { from: "user" });
110
+ return undefined;
111
+ }
112
+ catch (error) {
113
+ if (error instanceof CommanderError) {
114
+ // --help and --version are reported by commander as "errors" with exit code 0.
115
+ return error.exitCode === 0 ? ExitCode.Ok : ExitCode.Usage;
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+ function buildSetupProgram(io) {
121
+ const program = new Command()
122
+ .name("mdrender setup")
123
+ .description("Download Chrome Headless Shell (~100 MB) into the user cache folder, for machines\n" +
124
+ "without Chrome, Edge or Chromium. Does nothing when a browser is already installed.")
125
+ .option("--force", "download even if a browser is already installed");
126
+ return configure(program, io);
127
+ }
128
+ async function runSetup(argv, io) {
129
+ const program = buildSetupProgram(io);
130
+ const parsed = await parse(program, argv);
131
+ if (parsed !== undefined)
132
+ return parsed;
133
+ return setup({ io, force: Boolean(program.opts().force) });
134
+ }
135
+ async function runPreview(argv, io, signal) {
136
+ const program = buildPreviewProgram(io);
137
+ const parsed = await parse(program, argv);
138
+ if (parsed !== undefined)
139
+ return parsed;
140
+ const options = program.opts();
141
+ const port = options.port === undefined ? 0 : Number(options.port);
142
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
143
+ io.err(`error: invalid port: ${options.port}\n`);
144
+ return ExitCode.Usage;
145
+ }
146
+ const [input] = expandInputs(program.args);
147
+ return preview({
148
+ input: input.file,
149
+ settings: settings(program, options),
150
+ io,
151
+ signal,
152
+ port,
153
+ open: options.open,
154
+ });
155
+ }
156
+ async function runRender(argv, io, signal) {
157
+ const program = buildRenderProgram(io);
158
+ const parsed = await parse(program, argv);
159
+ if (parsed !== undefined)
160
+ return parsed;
161
+ const options = program.opts();
162
+ const run = settings(program, options);
163
+ io = { ...io, quiet: options.quiet, verbose: options.verbose && !options.quiet };
164
+ if (options.watch) {
165
+ return watchAndConvert({
166
+ inputs: program.args,
167
+ format: options.format,
168
+ output: options.output,
169
+ settings: run,
170
+ io,
171
+ signal,
172
+ });
173
+ }
174
+ const jobs = planOutputs(expandInputs(program.args), options.format, options.output);
175
+ return runJobs(jobs, options.format, run, io);
176
+ }
177
+ /** Run the CLI and return the exit code instead of exiting, so it can be tested in-process. */
178
+ export async function main(argv, io = defaultIo, { signal = new AbortController().signal } = {}) {
179
+ try {
180
+ if (argv[0] === "preview")
181
+ return await runPreview(argv.slice(1), io, signal);
182
+ if (argv[0] === "setup")
183
+ return await runSetup(argv.slice(1), io);
184
+ return await runRender(argv, io, signal);
185
+ }
186
+ catch (error) {
187
+ if (error instanceof MdRenderError) {
188
+ io.err(`error: ${error.message}\n`);
189
+ return error.exitCode;
190
+ }
191
+ throw error;
192
+ }
193
+ }
194
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,6 @@
1
+ import { type DocumentOptions } from "./options.js";
2
+ export declare const CONFIG_FILE = "mdrender.config.json";
3
+ /** Find `mdrender.config.json` in `startDir` or the nearest parent directory. */
4
+ export declare function findConfigFile(startDir: string): string | undefined;
5
+ /** Read and validate a config file. Relative paths inside it resolve against its folder. */
6
+ export declare function loadConfig(file: string): Partial<DocumentOptions>;
package/dist/config.js ADDED
@@ -0,0 +1,32 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { normalizeOptions, OptionsError } from "./options.js";
4
+ export const CONFIG_FILE = "mdrender.config.json";
5
+ /** Find `mdrender.config.json` in `startDir` or the nearest parent directory. */
6
+ export function findConfigFile(startDir) {
7
+ let dir = path.resolve(startDir);
8
+ for (;;) {
9
+ const candidate = path.join(dir, CONFIG_FILE);
10
+ if (existsSync(candidate))
11
+ return candidate;
12
+ const parent = path.dirname(dir);
13
+ if (parent === dir)
14
+ return undefined;
15
+ dir = parent;
16
+ }
17
+ }
18
+ /** Read and validate a config file. Relative paths inside it resolve against its folder. */
19
+ export function loadConfig(file) {
20
+ let raw;
21
+ try {
22
+ raw = JSON.parse(readFileSync(file, "utf8"));
23
+ }
24
+ catch (error) {
25
+ throw new OptionsError(`${file}: ${error.message}`);
26
+ }
27
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
28
+ throw new OptionsError(`${file}: the configuration must be a JSON object`);
29
+ }
30
+ return normalizeOptions(raw, file, path.dirname(file));
31
+ }
32
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,64 @@
1
+ import type { Diagnostic } from "./diagnostics.js";
2
+ import { type DocumentOptions, type MermaidTheme } from "./options.js";
3
+ import type { OutputFormat } from "./paths.js";
4
+ import { BrowserSession } from "./render/browser.js";
5
+ /** Settings shared by every file of a run (batch, watch or preview). */
6
+ export interface RenderSettings {
7
+ /** Explicit browser executable. */
8
+ browser?: string;
9
+ /**
10
+ * Config file to use. Undefined: look for mdrender.config.json next to the input or in a
11
+ * parent folder. `false`: use no config file.
12
+ */
13
+ config?: string | false;
14
+ /** Options from the command line; they win over front matter and the config file. */
15
+ overrides?: Partial<DocumentOptions>;
16
+ /**
17
+ * Let the browser download http(s) resources (remote images, stylesheets) while printing.
18
+ * Command line only: a document cannot grant itself network access.
19
+ */
20
+ allowRemote?: boolean;
21
+ }
22
+ export interface ConvertOptions extends RenderSettings {
23
+ input: string;
24
+ output: string;
25
+ format: OutputFormat;
26
+ /** Reuse a browser across conversions; without it each conversion starts its own. */
27
+ browsers?: BrowserPool;
28
+ }
29
+ export interface ConvertResult {
30
+ output: string;
31
+ /** Problems found while rendering; the output is still written, with errors shown inline. */
32
+ diagnostics: Diagnostic[];
33
+ /** The effective options after merging config file, front matter and command line. */
34
+ options: DocumentOptions;
35
+ /** Config file that was applied, if any. */
36
+ configFile?: string;
37
+ /** Files the output depends on besides the Markdown (config file, stylesheets). */
38
+ dependencies: string[];
39
+ }
40
+ export interface RenderedDocument extends Omit<ConvertResult, "output"> {
41
+ html: string;
42
+ title: string;
43
+ }
44
+ /** Starts one browser on first use and keeps it for later conversions. */
45
+ export declare class BrowserPool {
46
+ private readonly executable?;
47
+ /** Receives notes about the browser (which one started, sandbox fallback) for --verbose. */
48
+ private readonly log?;
49
+ private session?;
50
+ constructor(executable?: string | undefined,
51
+ /** Receives notes about the browser (which one started, sandbox fallback) for --verbose. */
52
+ log?: ((message: string) => void) | undefined);
53
+ get(): Promise<BrowserSession>;
54
+ close(): Promise<void>;
55
+ }
56
+ /** Mermaid theme: explicit choice, else dark diagrams for the dark page theme. */
57
+ export declare function effectiveMermaidTheme(options: DocumentOptions): MermaidTheme;
58
+ /**
59
+ * Render a Markdown file to a complete HTML document. A browser is only started (through
60
+ * `browsers`) when the document has Mermaid diagrams.
61
+ */
62
+ export declare function renderDocument(inputPath: string, settings: RenderSettings, browsers: BrowserPool): Promise<RenderedDocument>;
63
+ /** Read a Markdown file and write it as a self-contained HTML document or a PDF. */
64
+ export declare function convertFile(convert: ConvertOptions): Promise<ConvertResult>;