@dkothule/md2pdf 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.
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 Deepak Kothule
4
+ """
5
+ Pandoc filter for mermaid code blocks. Outputs SVG by default (vector quality in PDF).
6
+ Based on timofurrer/pandoc-mermaid-filter; modified to use SVG for LaTeX/PDF.
7
+
8
+ Requires: mmdc (mermaid-cli) on PATH or MERMAID_BIN.
9
+ For PDF: install librsvg so pandoc can embed SVG (e.g. brew install librsvg).
10
+ Python: pip install pandocfilters (or use PYTHON env to point to a venv that has it).
11
+ Optional: set MERMAID_CONFIG to pass a Mermaid config JSON to mmdc.
12
+ Optional: set MERMAID_IMAGE_PREFIX to control temporary Mermaid asset directory prefix.
13
+ Optional: set MERMAID_LATEX_FORMAT (pdf|svg|png). Default is svg.
14
+ Optional: set MERMAID_PDF_FIT=true|false (default true). If true, Mermaid PDF
15
+ output uses fit-to-content page boxes to avoid page-sized diagram assets.
16
+ Optional: set MERMAID_AUTO_PDF_FALLBACK=true|false (default true). If SVG uses
17
+ foreignObject labels in PDF output pipeline, fallback rendering uses Mermaid PDF.
18
+ """
19
+ import os
20
+ import sys
21
+ import subprocess
22
+
23
+ try:
24
+ from pandocfilters import toJSONFilter, Para, Image
25
+ from pandocfilters import get_filename4code, get_caption, get_extension
26
+ except ImportError:
27
+ sys.stderr.write(
28
+ "Missing Python dependency: pandocfilters.\n"
29
+ "Install with: python3 -m pip install pandocfilters\n"
30
+ )
31
+ raise SystemExit(1)
32
+
33
+ MERMAID_BIN = os.environ.get("MERMAID_BIN", "mmdc")
34
+ MERMAID_CONFIG = os.environ.get("MERMAID_CONFIG", None)
35
+ PUPPETEER_CFG = os.environ.get("PUPPETEER_CFG", None)
36
+ MERMAID_IMAGE_PREFIX = os.environ.get("MERMAID_IMAGE_PREFIX", "mermaid")
37
+ MERMAID_LATEX_FORMAT = os.environ.get("MERMAID_LATEX_FORMAT", "svg")
38
+
39
+
40
+ def env_bool(name, default):
41
+ value = os.environ.get(name)
42
+ if value is None:
43
+ return default
44
+ return value.strip().lower() in {"1", "true", "yes", "on"}
45
+
46
+
47
+ MERMAID_AUTO_PDF_FALLBACK = env_bool("MERMAID_AUTO_PDF_FALLBACK", True)
48
+ MERMAID_PDF_FIT = env_bool("MERMAID_PDF_FIT", True)
49
+
50
+
51
+ def render_mermaid(src, dest):
52
+ cmd = [MERMAID_BIN, "-i", src, "-o", dest, "-q"]
53
+ if dest.lower().endswith(".pdf") and MERMAID_PDF_FIT:
54
+ cmd.append("--pdfFit")
55
+ if MERMAID_CONFIG is not None:
56
+ cmd.extend(["-c", MERMAID_CONFIG])
57
+ if PUPPETEER_CFG is not None:
58
+ cmd.extend(["-p", PUPPETEER_CFG])
59
+ if os.path.isfile(".puppeteer.json"):
60
+ cmd.extend(["-p", ".puppeteer.json"])
61
+ result = subprocess.run(cmd, capture_output=True, text=True)
62
+ if result.returncode != 0:
63
+ sys.stderr.write("Failed to render Mermaid diagram with mermaid-cli.\n")
64
+ sys.stderr.write("Command: " + " ".join(cmd) + "\n")
65
+ if result.stderr:
66
+ sys.stderr.write(result.stderr.strip() + "\n")
67
+ raise SystemExit(result.returncode)
68
+
69
+
70
+ def svg_has_foreign_object(path):
71
+ try:
72
+ with open(path, "rb") as f:
73
+ return b"<foreignObject" in f.read()
74
+ except OSError:
75
+ return False
76
+
77
+
78
+ def mermaid(key, value, format_, _):
79
+ if key == "CodeBlock":
80
+ [[ident, classes, keyvals], code] = value
81
+ if "mermaid" in classes:
82
+ caption, typef, keyvals = get_caption(keyvals)
83
+ filename = get_filename4code(MERMAID_IMAGE_PREFIX, code)
84
+ filetype = get_extension(
85
+ format_,
86
+ MERMAID_LATEX_FORMAT,
87
+ html="svg",
88
+ latex=MERMAID_LATEX_FORMAT,
89
+ pdf=MERMAID_LATEX_FORMAT,
90
+ )
91
+ src = filename + ".mmd"
92
+ dest = filename + "." + filetype
93
+ txt = code.encode(sys.getfilesystemencoding())
94
+ with open(src, "wb") as f:
95
+ f.write(txt)
96
+
97
+ if not os.path.isfile(dest):
98
+ render_mermaid(src, dest)
99
+ sys.stderr.write("Created image " + dest + "\n")
100
+
101
+ # Flowchart/graph SVG labels may be emitted via <foreignObject>, which
102
+ # some SVG->PDF pipelines drop. Keep SVG default, but fallback to Mermaid
103
+ # PDF for only affected diagrams when generating PDF output.
104
+ if (
105
+ MERMAID_AUTO_PDF_FALLBACK
106
+ and filetype == "svg"
107
+ and format_ in {"", "pdf", "latex"}
108
+ and svg_has_foreign_object(dest)
109
+ ):
110
+ fallback_dest = filename + ".pdf"
111
+ if not os.path.isfile(fallback_dest):
112
+ render_mermaid(src, fallback_dest)
113
+ sys.stderr.write(
114
+ "Created PDF fallback image "
115
+ + fallback_dest
116
+ + " (foreignObject detected)\n"
117
+ )
118
+ dest = fallback_dest
119
+
120
+ return Para([Image([ident, [], keyvals], caption, [dest, typef])])
121
+
122
+
123
+ def main():
124
+ toJSONFilter(mermaid)
125
+
126
+
127
+ if __name__ == "__main__":
128
+ main()
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 Deepak Kothule
4
+ # Wrapper so pandoc can run the mermaid filter as a single executable.
5
+ # Uses PYTHON if set (e.g. path to venv python), else python3.
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ exec "${PYTHON:-python3}" "$SCRIPT_DIR/pandoc_mermaid_filter.py"
@@ -0,0 +1,54 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 Deepak Kothule
3
+ #
4
+ # Copy this file to one of:
5
+ # - $HOME/.config/md2pdf/config.env (global defaults)
6
+ # - <folder-containing-markdown>/.md2pdfrc (project/doc defaults)
7
+ # or pass it at runtime:
8
+ # md2pdf input.md --config /path/to/config.env
9
+ #
10
+ # All keys are optional.
11
+
12
+ # PDF engine command used by pandoc.
13
+ PDF_ENGINE=xelatex
14
+
15
+ # Pandoc formatting columns.
16
+ PANDOC_COLUMNS=200
17
+
18
+ # Page margins.
19
+ LR_MARGIN=0.7in
20
+ TB_MARGIN=0.5in
21
+
22
+ # Optional LaTeX header file (set empty to disable custom header).
23
+ # Default uses bundled: assets/table-style.tex
24
+ # TABLE_STYLE=/absolute/path/to/table-style.tex
25
+
26
+ # Mermaid/Python executables (command name or absolute path).
27
+ MERMAID_BIN=mmdc
28
+ # Optional Mermaid CLI config JSON.
29
+ # Default uses bundled: assets/mermaid.config.json
30
+ # MERMAID_CONFIG=/absolute/path/to/mermaid.config.json
31
+
32
+ # Mermaid asset format for LaTeX/PDF output (pdf, svg, png).
33
+ # Default is svg to keep high-resolution vector Mermaid diagrams in PDF output.
34
+ MERMAID_LATEX_FORMAT=svg
35
+
36
+ # When rendering Mermaid to PDF (directly or via fallback), use fit-to-content page bounds.
37
+ # This avoids page-sized Mermaid PDF assets that can force one diagram per page.
38
+ MERMAID_PDF_FIT=true
39
+
40
+ # When true, if Mermaid SVG contains foreignObject labels (common with flowchart/graph),
41
+ # md2pdf auto-renders a Mermaid PDF fallback for that diagram to preserve labels in PDF.
42
+ MERMAID_AUTO_PDF_FALLBACK=true
43
+
44
+ PYTHON=python3
45
+
46
+ # Optional puppeteer config file for mermaid-cli.
47
+ # PUPPETEER_CFG=/absolute/path/to/puppeteer-config.json
48
+
49
+ # Temporary Mermaid asset folder behavior.
50
+ # If true, remove generated "<prefix>-<run-id>-images/" folder after conversion.
51
+ CLEANUP_MERMAID_ASSETS=true
52
+
53
+ # Prefix for generated Mermaid asset folder.
54
+ MERMAID_ASSET_PREFIX=md2pdf-mermaid
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@dkothule/md2pdf",
3
+ "version": "1.0.0",
4
+ "description": "Markdown to PDF converter with high-resolution Mermaid diagram rendering",
5
+ "author": "Deepak Kothule",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "LICENSE",
12
+ "README.md",
13
+ "bin/",
14
+ "lib/pandoc_mermaid_filter.py",
15
+ "lib/run_pandoc_mermaid_filter.sh",
16
+ "assets/table-style.tex",
17
+ "assets/mermaid.config.json",
18
+ "scripts/",
19
+ "docs/ARCHITECTURE.md",
20
+ "md2pdf.config.example",
21
+ "install-system-deps.sh",
22
+ "setup-local-deps.sh",
23
+ "requirements.txt"
24
+ ],
25
+ "bin": {
26
+ "md2pdf": "./bin/md2pdf",
27
+ "md2pdf-install-finder-action": "./scripts/install_md2pdf_quick_action.sh",
28
+ "md2pdf-uninstall-finder-action": "./scripts/uninstall_md2pdf_quick_action.sh"
29
+ },
30
+ "scripts": {
31
+ "postinstall": "echo \"Mermaid CLI installed\""
32
+ },
33
+ "dependencies": {
34
+ "@mermaid-js/mermaid-cli": "10.9.1"
35
+ }
36
+ }
@@ -0,0 +1,3 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 Deepak Kothule
3
+ pandocfilters==1.5.1
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 Deepak Kothule
4
+ # Simple standalone utility for local use from this repo checkout.
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9
+
10
+ exec "$PROJECT_ROOT/bin/md2pdf" "$@"
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 Deepak Kothule
4
+ set -euo pipefail
5
+
6
+ resolve_script_dir() {
7
+ local source="${BASH_SOURCE[0]}"
8
+ while [[ -L "$source" ]]; do
9
+ local dir
10
+ dir="$(cd -P "$(dirname "$source")" && pwd)"
11
+ source="$(readlink "$source")"
12
+ if [[ "$source" != /* ]]; then
13
+ source="$dir/$source"
14
+ fi
15
+ done
16
+ cd -P "$(dirname "$source")" && pwd
17
+ }
18
+
19
+ if [[ "$(uname -s)" != "Darwin" ]]; then
20
+ echo "Error: Finder Quick Action installer is only supported on macOS." >&2
21
+ exit 1
22
+ fi
23
+
24
+ WORKFLOW_NAME="${WORKFLOW_NAME:-Convert Markdown to PDF}"
25
+ WORKFLOW_DIR="$HOME/Library/Services/${WORKFLOW_NAME}.workflow"
26
+ CONTENTS_DIR="$WORKFLOW_DIR/Contents"
27
+ SCRIPT_DIR="$(resolve_script_dir)"
28
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
29
+
30
+ MD2PDF_SCRIPT=""
31
+ if [[ -x "$PROJECT_ROOT/bin/md2pdf" ]]; then
32
+ MD2PDF_SCRIPT="$PROJECT_ROOT/bin/md2pdf"
33
+ elif command -v md2pdf >/dev/null 2>&1; then
34
+ MD2PDF_SCRIPT="$(command -v md2pdf)"
35
+ fi
36
+
37
+ if [[ -z "$MD2PDF_SCRIPT" ]]; then
38
+ cat >&2 <<'EOF'
39
+ Error: could not find md2pdf executable.
40
+ Install from this repo first:
41
+ ./setup-local-deps.sh
42
+ Or install globally:
43
+ npm i -g @dkothule/md2pdf
44
+ EOF
45
+ exit 1
46
+ fi
47
+
48
+ ACTION_UUID="$(uuidgen)"
49
+ INPUT_UUID="$(uuidgen)"
50
+ OUTPUT_UUID="$(uuidgen)"
51
+
52
+ mkdir -p "$CONTENTS_DIR"
53
+
54
+ cat > "$CONTENTS_DIR/Info.plist" <<'PLIST'
55
+ <?xml version="1.0" encoding="UTF-8"?>
56
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
57
+ <plist version="1.0">
58
+ <dict>
59
+ <key>NSServices</key>
60
+ <array>
61
+ <dict>
62
+ <key>NSBackgroundColorName</key>
63
+ <string>background</string>
64
+ <key>NSIconName</key>
65
+ <string>NSActionTemplate</string>
66
+ <key>NSMenuItem</key>
67
+ <dict>
68
+ <key>default</key>
69
+ <string>Convert Markdown to PDF</string>
70
+ </dict>
71
+ <key>NSMessage</key>
72
+ <string>runWorkflowAsService</string>
73
+ <key>NSRequiredContext</key>
74
+ <dict>
75
+ <key>NSApplicationIdentifier</key>
76
+ <string>com.apple.finder</string>
77
+ </dict>
78
+ <key>NSSendFileTypes</key>
79
+ <array>
80
+ <string>net.daringfireball.markdown</string>
81
+ <string>md</string>
82
+ </array>
83
+ </dict>
84
+ </array>
85
+ </dict>
86
+ </plist>
87
+ PLIST
88
+
89
+ cat > "$CONTENTS_DIR/document.wflow" <<PLIST
90
+ <?xml version="1.0" encoding="UTF-8"?>
91
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
92
+ <plist version="1.0">
93
+ <dict>
94
+ <key>AMApplicationBuild</key>
95
+ <string>533</string>
96
+ <key>AMApplicationVersion</key>
97
+ <string>2.10</string>
98
+ <key>AMDocumentVersion</key>
99
+ <string>2</string>
100
+ <key>actions</key>
101
+ <array>
102
+ <dict>
103
+ <key>action</key>
104
+ <dict>
105
+ <key>ActionBundlePath</key>
106
+ <string>/System/Library/Automator/Run Shell Script.action</string>
107
+ <key>ActionName</key>
108
+ <string>Run Shell Script</string>
109
+ <key>ActionParameters</key>
110
+ <dict>
111
+ <key>CheckedForUserDefaultShell</key>
112
+ <true/>
113
+ <key>COMMAND_STRING</key>
114
+ <string>export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/TeX/texbin:$HOME/.npm-global/bin"
115
+ MD2PDF_SCRIPT="$MD2PDF_SCRIPT"
116
+ if [[ ! -x "\$MD2PDF_SCRIPT" ]] &amp;&amp; [[ -z "\$(command -v md2pdf 2&gt;/dev/null)" ]]; then
117
+ print -u2 -- "Error: md2pdf is not in PATH: \$PATH"
118
+ exit 127
119
+ fi
120
+ for f in "\$@"; do
121
+ case "\$f" in
122
+ *.md|*.MD) ;;
123
+ *) continue ;;
124
+ esac
125
+ if [[ -x "\$MD2PDF_SCRIPT" ]]; then
126
+ "\$MD2PDF_SCRIPT" "\$f"
127
+ else
128
+ md2pdf "\$f"
129
+ fi
130
+ done
131
+ </string>
132
+ <key>inputMethod</key>
133
+ <integer>1</integer>
134
+ <key>shell</key>
135
+ <string>/bin/zsh</string>
136
+ <key>source</key>
137
+ <string></string>
138
+ </dict>
139
+ <key>AMAccepts</key>
140
+ <dict>
141
+ <key>Container</key>
142
+ <string>List</string>
143
+ <key>Optional</key>
144
+ <true/>
145
+ <key>Types</key>
146
+ <array>
147
+ <string>com.apple.cocoa.string</string>
148
+ </array>
149
+ </dict>
150
+ <key>AMActionVersion</key>
151
+ <string>2.0.3</string>
152
+ <key>AMApplication</key>
153
+ <array>
154
+ <string>Automator</string>
155
+ </array>
156
+ <key>AMParameterProperties</key>
157
+ <dict>
158
+ <key>CheckedForUserDefaultShell</key>
159
+ <dict/>
160
+ <key>COMMAND_STRING</key>
161
+ <dict/>
162
+ <key>inputMethod</key>
163
+ <dict/>
164
+ <key>shell</key>
165
+ <dict/>
166
+ <key>source</key>
167
+ <dict/>
168
+ </dict>
169
+ <key>AMProvides</key>
170
+ <dict>
171
+ <key>Container</key>
172
+ <string>List</string>
173
+ <key>Types</key>
174
+ <array>
175
+ <string>com.apple.cocoa.string</string>
176
+ </array>
177
+ </dict>
178
+ <key>BundleIdentifier</key>
179
+ <string>com.apple.RunShellScript</string>
180
+ <key>CanShowSelectedItemsWhenRun</key>
181
+ <false/>
182
+ <key>CanShowWhenRun</key>
183
+ <true/>
184
+ <key>Category</key>
185
+ <array>
186
+ <string>AMCategoryUtilities</string>
187
+ </array>
188
+ <key>CFBundleVersion</key>
189
+ <string>2.0.3</string>
190
+ <key>Class Name</key>
191
+ <string>RunShellScriptAction</string>
192
+ <key>InputUUID</key>
193
+ <string>$INPUT_UUID</string>
194
+ <key>OutputUUID</key>
195
+ <string>$OUTPUT_UUID</string>
196
+ <key>UUID</key>
197
+ <string>$ACTION_UUID</string>
198
+ <key>isViewVisible</key>
199
+ <integer>1</integer>
200
+ </dict>
201
+ <key>isViewVisible</key>
202
+ <integer>1</integer>
203
+ </dict>
204
+ </array>
205
+ <key>connectors</key>
206
+ <dict/>
207
+ <key>workflowMetaData</key>
208
+ <dict>
209
+ <key>applicationBundleID</key>
210
+ <string>com.apple.finder</string>
211
+ <key>applicationBundleIDsByPath</key>
212
+ <dict>
213
+ <key>/System/Library/CoreServices/Finder.app</key>
214
+ <string>com.apple.finder</string>
215
+ </dict>
216
+ <key>applicationPath</key>
217
+ <string>/System/Library/CoreServices/Finder.app</string>
218
+ <key>applicationPaths</key>
219
+ <array>
220
+ <string>/System/Library/CoreServices/Finder.app</string>
221
+ </array>
222
+ <key>inputTypeIdentifier</key>
223
+ <string>com.apple.Automator.fileSystemObject</string>
224
+ <key>outputTypeIdentifier</key>
225
+ <string>com.apple.Automator.nothing</string>
226
+ <key>presentationMode</key>
227
+ <integer>15</integer>
228
+ <key>processesInput</key>
229
+ <false/>
230
+ <key>serviceApplicationBundleID</key>
231
+ <string>com.apple.finder</string>
232
+ <key>serviceApplicationPath</key>
233
+ <string>/System/Library/CoreServices/Finder.app</string>
234
+ <key>serviceInputTypeIdentifier</key>
235
+ <string>com.apple.Automator.fileSystemObject</string>
236
+ <key>serviceOutputTypeIdentifier</key>
237
+ <string>com.apple.Automator.nothing</string>
238
+ <key>serviceProcessesInput</key>
239
+ <false/>
240
+ <key>systemImageName</key>
241
+ <string>NSActionTemplate</string>
242
+ <key>useAutomaticInputType</key>
243
+ <false/>
244
+ <key>workflowTypeIdentifier</key>
245
+ <string>com.apple.Automator.servicesMenu</string>
246
+ </dict>
247
+ </dict>
248
+ </plist>
249
+ PLIST
250
+
251
+ plutil -lint "$CONTENTS_DIR/Info.plist" >/dev/null
252
+ plutil -lint "$CONTENTS_DIR/document.wflow" >/dev/null
253
+
254
+ echo "Installed Quick Action: $WORKFLOW_NAME"
255
+ echo "Location: $WORKFLOW_DIR"
256
+ echo "Using md2pdf executable: $MD2PDF_SCRIPT"
257
+ echo
258
+ echo "If it does not appear immediately in Finder context menu, restart Finder:"
259
+ echo " killall Finder"
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 Deepak Kothule
4
+ set -euo pipefail
5
+
6
+ if [[ "$(uname -s)" != "Darwin" ]]; then
7
+ echo "Error: Finder Quick Action uninstaller is only supported on macOS." >&2
8
+ exit 1
9
+ fi
10
+
11
+ WORKFLOW_NAME="${WORKFLOW_NAME:-Convert Markdown to PDF}"
12
+ WORKFLOW_DIR="$HOME/Library/Services/${WORKFLOW_NAME}.workflow"
13
+
14
+ if [[ -d "$WORKFLOW_DIR" ]]; then
15
+ rm -rf "$WORKFLOW_DIR"
16
+ echo "Removed Quick Action: $WORKFLOW_NAME"
17
+ echo "Deleted: $WORKFLOW_DIR"
18
+ else
19
+ echo "Quick Action not found: $WORKFLOW_DIR"
20
+ fi
21
+
22
+ echo
23
+ echo "Restart Finder to refresh the context menu:"
24
+ echo " killall Finder"
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 Deepak Kothule
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ cd "$SCRIPT_DIR"
8
+
9
+ if ! command -v python3 >/dev/null 2>&1; then
10
+ echo "python3 not found. Install Python 3 first."
11
+ exit 1
12
+ fi
13
+
14
+ if ! command -v npm >/dev/null 2>&1; then
15
+ echo "npm not found. Install Node.js/npm first."
16
+ exit 1
17
+ fi
18
+
19
+ if [[ ! -d ".venv" ]]; then
20
+ python3 -m venv .venv
21
+ fi
22
+
23
+ ./.venv/bin/python -m pip install --upgrade pip
24
+ ./.venv/bin/pip install -r requirements.txt
25
+
26
+ npm install
27
+
28
+ echo
29
+ echo "Local dependencies installed."
30
+ echo "Python: $SCRIPT_DIR/.venv/bin/python"
31
+ echo "Mermaid CLI: $SCRIPT_DIR/node_modules/.bin/mmdc"