puml-for-markdown 1.2.1 → 2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0
4
+
5
+ ### Migration from version 1
6
+
7
+ - Requires Node.js 22 or newer. Upgrade Node.js before installing version 2.
8
+ - CLI failures now exit with a nonzero status so scripts and CI can detect errors.
9
+ - The CLI creates `.puml-cache.json` by default. Add it to `.gitignore`, or use
10
+ `--no-cache` to disable caching. Refresh with `--no-cache` when remote includes
11
+ or the rendering server change.
12
+
13
+ ### Features and fixes
14
+
15
+ - Add local image links, offline check and dry-run modes, persistent caching,
16
+ and private Polr URL shortening.
17
+ - Support `.plantuml` files, Windows paths, nested ignore rules, and recursive
18
+ includes with cycle detection.
19
+ - Preserve Markdown formatting and code examples while replacing diagram links.
20
+ - Validate references before editing Markdown and preserve unrelated exported
21
+ files. Export images atomically with readable permissions.
22
+ - Handle HTTP errors and timeouts, and serialize watch updates.
23
+ - Update dependencies and examples, with automated checks on Linux, macOS,
24
+ and Windows using Node.js 22 and 24.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Just run `puml-for-markdown` in any directory where you have markdown files and
5
5
  in markdown comments. The best part about this is that it requires no build files, you don't even need to store
6
6
  puml images in your repo. Also the embedded diagrams can link to other diagrams. [PlantUML](https://plantuml.com) files
7
7
  allow you to create diagrams that are defined using a simple and intuitive language. By default the entire directory
8
- is recursively searched, automatically ignoring anything in the gitignore file.
8
+ is recursively searched, respecting root and nested `.gitignore` rules, including negation. `.git`, `node_modules`, the output directory, and symbolic links are always skipped. Both `.puml` and `.plantuml` diagram files are supported.
9
9
 
10
10
  ## Examples
11
11
  The following examples are of a project I worked on, **yellow components will link you to another diagram**. Obviously the
@@ -20,7 +20,7 @@ You can easily add an embedded diagram to your markdown. Simply add a link to th
20
20
 
21
21
  Here's the result. **Try clicking on the yellow blocks.**
22
22
 
23
- [![Diagram Image Link](https://tinyurl.com/y847en68)](https://tinyurl.com/y847en68)<!--![Diagram Image Link](./puml/level_1_system_view.puml)-->
23
+ [![Diagram Image Link](https://tinyurl.com/2d47useb)](https://tinyurl.com/2d47useb)<!--![Diagram Image Link](./puml/level_1_system_view.puml)-->
24
24
 
25
25
  ### Link Only
26
26
  To render a link to a puml diagram, do the same thing but exclude the `!`
@@ -30,7 +30,7 @@ To render a link to a puml diagram, do the same thing but exclude the `!`
30
30
 
31
31
  which renders into
32
32
 
33
- [Click to Open Interactive Diagram](https://tinyurl.com/y847en68)<!--[Click to Open Interactive Diagram](./puml/level_1_system_view.puml)-->
33
+ [Click to Open Interactive Diagram](https://tinyurl.com/2d47useb)<!--[Click to Open Interactive Diagram](./puml/level_1_system_view.puml)-->
34
34
 
35
35
  ## Goal
36
36
  The goal is to make PlantUML diagrams easily accessible from markdown, specifically GitHub flavored.
@@ -76,6 +76,13 @@ If you want to update the link text or switch it between image and link, just up
76
76
  don't need to delete the rendered image or link.
77
77
 
78
78
  ### Installation
79
+ Requires **Node.js 22 or newer** (Node.js 24 recommended for development).
80
+
81
+ Version 2 raises the minimum Node.js version to 22. Upgrade Node.js before upgrading
82
+ from version 1. Existing dependencies pinned to `1.x` or `^1.2.1` stay on version 1;
83
+ installing `latest` will select version 2 once it is published. See the
84
+ [release notes](./CHANGELOG.md) for the changes.
85
+
79
86
  `npm i -g puml-for-markdown`
80
87
 
81
88
  ### Advanced Usage
@@ -86,35 +93,62 @@ For an example of outputted images, checkout [dist_puml/puml](./dist_puml/puml).
86
93
  - You can specify the PUML server used to render the diagrams see `--puml-server-url`
87
94
 
88
95
  ### All CLI Options
89
- ```
96
+ ```text
90
97
  Usage: puml-for-markdown [options]
91
98
 
92
99
  An application to add interactive PUML diagrams to your github markdown files. If running with default arguments, run in project root directory.
93
100
 
94
101
  Options:
95
- -s, --puml-server-url <url> This is the base URL used to render diagrams. Defaults to the public plantuml server. (default: "https://www.plantuml.com/plantuml")
102
+ -s, --puml-server-url <url> This is the base URL used to render
103
+ diagrams. Defaults to the public plantuml
104
+ server. (default:
105
+ "https://www.plantuml.com/plantuml")
96
106
  -x, --root-directory <path> The path to your project (default: CWD)
97
- -r, --hot-reload Rerun markdown generator every `interval` seconds, determined by interval option
98
- -v, --interval-seconds <number> If --hot-reload is set, how often should it reload (default: 2)
99
- -p, --puml-directory <path> Path to directory containing puml files which are referenced in markdown files (default: rootDirectory)
100
- -m, --markdown-directory <path> Path to directory containing markdown files referencing puml files (default: rootDirectory)
101
- -g, --ignore-gitignore Don't use .gitignore to skip PUML and MD
102
- -i, --gitignore-path <path> Use this as path to .gitignore file. (default: rootDirectory/.gitignore)
103
- -d, --output-images If set, will output images of diagrams to the dist directory
104
- -b, --dist-directory <path> If --output-images is set, path to output diagram images (default: rootDirectory/dist_puml)
105
- -f, --image-formats <format> If --output-images is set, sets the output image format (choices: "png", "svg", "both", default: "png")
106
- -t, --turn-off-link-shortening Use the full puml server link instead of the tiny url, if your diagrams are too big this won't work
107
+ -r, --hot-reload Rerun markdown generator every `interval`
108
+ seconds, determined by interval option
109
+ -v, --interval-seconds <number> If --hot-reload is set, how often should it
110
+ reload (default: 2)
111
+ -p, --puml-directory <path> Path to directory containing puml files
112
+ which are referenced in markdown files
113
+ (default: rootDirectory)
114
+ -m, --markdown-directory <path> Path to directory containing markdown files
115
+ referencing puml files (default:
116
+ rootDirectory)
117
+ -g, --ignore-gitignore Don't use .gitignore rules to skip PUML and
118
+ MD
119
+ -i, --gitignore-path <path> Use this as path to .gitignore file.
120
+ (default: rootDirectory/.gitignore)
121
+ -d, --output-images If set, will output images of diagrams to
122
+ the dist directory
123
+ -b, --dist-directory <path> If --output-images is set, path to output
124
+ diagram images (default:
125
+ rootDirectory/dist_puml)
126
+ -f, --image-formats <format> If --output-images is set, sets the output
127
+ image format (choices: "png", "svg", "both",
128
+ default: "png")
129
+ -t, --turn-off-link-shortening Use the full puml server link instead of the
130
+ tiny url, if your diagrams are too big this
131
+ won't work
132
+ --local-images Link Markdown to exported local images
133
+ (implies --output-images)
134
+ --interactive-links Wrap local image previews in an interactive
135
+ renderer link
136
+ --check Check for stale Markdown and images without
137
+ writing or using the network
138
+ --dry-run List planned changes without writing or
139
+ using the network
140
+ --no-cache Disable the persistent diagram and image
141
+ cache
142
+ -l, --polr-url <url> Use a private Polr shortener; reads
143
+ POLR_APIKEY from the environment
107
144
  -h, --help display help for command
108
145
  ```
109
146
 
110
147
  ### Notes
111
148
  - Comments within inline or multiline code styling will be ignored
112
- - Currently doesn't support cyclic graph references, i.e. a diagram can't reference any diagrams which reference back
113
- to it
149
+ - Cyclic diagram links and recursive local includes fail with an error showing the reference chain.
114
150
  - See the [pre-commit hook](./.husky/pre-commit) to see how to add a git hook
115
- - If you are saving diagram images and have puml files which only define constants or settings
116
- (i.e. [example](./puml/constants.puml)) you'll see a warning in the console saying it failed to save the image to file
117
- because these aren't renderable on their own (since there is nothing to render)
151
+ - Image export skips files without an `@start...` directive and files used only as local includes. A Markdown reference or diagram hyperlink makes an included file exportable. Other files must render successfully.
118
152
  - Anything inside `inline code` or `code blocks` will be excluded from rendering
119
153
 
120
154
  ### How It Works
@@ -148,25 +182,140 @@ though.
148
182
  ### Other Helpful Links
149
183
  * [C4-Puml](https://github.com/plantuml-stdlib/C4-PlantUML): A collection of PlantUML diagrams for use in C4, you'll see me using this in my examples
150
184
 
185
+ ### Known Issues
186
+ * Seems to be breaking on Windows, see [here](https://github.com/danielyaa5/puml-for-markdown/issues/6). I don't have a
187
+ Windows machine to test on so help here would be appreciated.
188
+
151
189
  ### More Examples
152
- [![ERD](https://tinyurl.com/ya6qvr7r)](https://tinyurl.com/ya6qvr7r)<!--![ERD](./puml/level_4_erd.puml)-->
190
+ [![ERD](https://tinyurl.com/2amb263t)](https://tinyurl.com/2amb263t)<!--![ERD](./puml/level_4_erd.puml)-->
191
+
192
+
193
+
194
+ [![Container View](https://tinyurl.com/284kg5h5)](https://tinyurl.com/284kg5h5)<!--![Container View](./puml/level_2_container_view.puml)-->
195
+
196
+
197
+
198
+ [![Component View - Label Retrieval Job](https://tinyurl.com/262ys7bk)](https://tinyurl.com/262ys7bk)<!--![Component View - Label Retrieval Job](./puml/level_3_component_view_label_retrieval_job.puml)-->
199
+
153
200
 
154
201
 
202
+ [![Component View - Pipeline Component](https://tinyurl.com/2xlmr44t)](https://tinyurl.com/2xlmr44t)<!--![Component View - Pipeline Component](./puml/level_3_component_view_pipeline.puml)-->
155
203
 
156
- [![Container View](https://tinyurl.com/yblre3m4)](https://tinyurl.com/yblre3m4)<!--![Container View](./puml/level_2_container_view.puml)-->
157
204
 
158
205
 
206
+ [![Activity Diagram - Sampler A](https://tinyurl.com/26j5s2fw)](https://tinyurl.com/26j5s2fw)<!--![Activity Diagram - Sampler A](./puml/level_4_activity_diagram_sampler_a.puml)-->
159
207
 
160
- [![Component View - Label Retrieval Job](https://tinyurl.com/y8egw3wt)](https://tinyurl.com/y8egw3wt)<!--![Component View - Label Retrieval Job](./puml/level_3_component_view_label_retrieval_job.puml)-->
161
208
 
162
209
 
210
+ [![Activity Diagram - Sampler B](https://tinyurl.com/2cqvdekn)](https://tinyurl.com/2cqvdekn)<!--![Activity Diagram - Sampler B](./puml/level_4_activity_diagram_sampler_b.puml)-->
163
211
 
164
- [![Component View - Pipeline Component](https://tinyurl.com/y9j7twkz)](https://tinyurl.com/y9j7twkz)<!--![Component View - Pipeline Component](./puml/level_3_component_view_pipeline.puml)-->
165
212
 
213
+ ### Reliability and operational notes
166
214
 
215
+ - Fatal errors return a nonzero exit status, suitable for CI. Watch mode logs errors
216
+ and retries on the next interval; it runs immediately on startup.
217
+ - Local `!include` files are expanded recursively relative to the file containing
218
+ the directive. Remote, standard-library, and macro-based includes remain for the
219
+ renderer to evaluate. This is not a complete PlantUML preprocessor: conditional
220
+ includes and `!include_once` / `!include_many` are not expanded locally.
221
+ - Unrelated Markdown links, fenced/indented code, inline code, and line endings
222
+ are preserved. Unchanged Markdown files are not rewritten.
223
+ - All Markdown references are checked before documents are written. Image exports
224
+ complete before Markdown is updated, but an export failure can leave earlier
225
+ successfully exported images updated; the operation is not a multi-file transaction.
226
+ - Export overwrites the matching generated image files but never clears the output
227
+ directory. Stale images are retained. Ambiguous output names and symlinks inside
228
+ the output path are rejected.
229
+ - Renderer and shortener requests time out after 30 seconds. If TinyURL fails,
230
+ a warning is printed and the direct PlantUML URL is used. Large URLs can still
231
+ exceed renderer/proxy limits; shortening does not remove the renderer's limit.
232
+ - **Diagram privacy:** encoded diagram URLs contain recoverable source text.
233
+ The default shortener receives that URL, and the renderer receives the diagram
234
+ when someone opens it or when images are exported. For private diagrams, use
235
+ your own renderer with `--puml-server-url` and disable shortening with `-t`.
167
236
 
168
- [![Activity Diagram - Sampler A](https://tinyurl.com/ybp8ju9x)](https://tinyurl.com/ybp8ju9x)<!--![Activity Diagram - Sampler A](./puml/level_4_activity_diagram_sampler_a.puml)-->
169
237
 
238
+ ### Local images and offline viewing
239
+
240
+ ```sh
241
+ puml-for-markdown --local-images --image-formats svg
242
+ ```
243
+
244
+ This implies `--output-images` and produces relative Markdown links to files in
245
+ `dist_puml`. For example, `<!--![Diagram](puml/a.puml)-->` becomes
246
+ `![Diagram](dist_puml/puml/a.svg)<!--![Diagram](puml/a.puml)-->`.
247
+ Paths are relative to each Markdown file and URL-encoded, including spaces.
248
+ Both `.puml` and `.plantuml` sources work.
249
+
250
+ SVG diagrams link to other exported SVG diagrams using relative paths. Open the
251
+ SVG directly in a browser or serve the export folder to navigate between them.
252
+ Markdown hosts may disable links inside embedded SVG images. PNG previews cannot
253
+ contain clickable elements. With `--image-formats both`, Markdown uses SVG.
254
+
255
+ Add `--interactive-links` to wrap a local preview in a link to the online renderer.
256
+ That renderer link uses separate source with online diagram hyperlinks, while
257
+ exported SVGs retain their local hyperlinks. Local-only mode skips URL shortening.
258
+ Rendering still contacts the configured PlantUML server; use `--puml-server-url`
259
+ with your own renderer when the source must remain private.
260
+
261
+ ### Check or preview changes without modifying files
262
+
263
+ ```sh
264
+ puml-for-markdown --dry-run -t
265
+ puml-for-markdown --check -t
266
+ puml-for-markdown --local-images --image-formats svg --check
267
+ ```
268
+
269
+ Both commands are offline and never write Markdown, images, or cache files.
270
+ `--dry-run` lists planned changes and exits successfully; `--check` exits with 1
271
+ if documentation is stale, otherwise 0. Invalid inputs still exit with 1.
272
+ Neither can be combined with `--hot-reload`.
273
+
274
+ A check that uses shortened URLs or exported images needs the cache from a prior
275
+ successful generation with the same settings. If it is missing, the check reports
276
+ that generation is needed rather than contacting a shortener or claiming the files
277
+ are current. Direct-URL Markdown checks (`--check -t` without image export) do not
278
+ require a cache. For CI, use that deterministic mode or restore the generation
279
+ cache with the exported images.
280
+
281
+ ### Persistent cache and incremental watch mode
282
+
283
+ Successful runs save `.puml-cache.json` at the project root. Add it to your
284
+ `.gitignore`: encoded URLs contain recoverable source text, even for private diagrams.
285
+ The cache contains no Polr API key. Renderer/provider settings and a hash of the key
286
+ separate cache entries when configuration changes.
287
+
288
+ The CLI reuses encodings, shortened URLs, and verified image files. Watch mode
289
+ checks inputs each interval and only rebuilds affected content. Local include
290
+ changes invalidate their parent diagrams; remote diagram links invalidate parents
291
+ when their target URL changes. Local links stay stable when only a child changes.
292
+ Missing or externally modified images are rendered again.
293
+
294
+ ```sh
295
+ puml-for-markdown --hot-reload
296
+ puml-for-markdown --no-cache
297
+ ```
298
+
299
+ `--no-cache` forces regeneration without reading or writing the cache. Use it to
300
+ refresh remote includes, renderer upgrades at the same URL, or expired short links:
301
+ those external changes cannot be detected from local file content. Failed shortening
302
+ attempts are not cached, allowing the next run to retry. Stale generated images are
303
+ retained; the CLI never clears the destination directory.
304
+
305
+ ### Private Polr URL shortening
306
+
307
+ ```sh
308
+ # Set POLR_APIKEY in your shell or CI secret environment first.
309
+ puml-for-markdown --puml-server-url https://uml.example.com/plantuml \
310
+ --polr-url https://short.example.com
311
+ ```
170
312
 
313
+ This adapts the provider idea from [PR #8](https://github.com/danielyaa5/puml-for-markdown/pull/8)
314
+ by @mzagar. The implementation uses the [Polr v2 API](https://docs.polrproject.org/en/latest/developer-guide/api/):
315
+ POST `/api/v2/action/shorten`, with URL-encoded `url`, `key`, and `response_type`
316
+ fields. JSON and plain-text responses are accepted and validated. Credentials are
317
+ never written into generated links or the cache. A failed Polr request falls back
318
+ to the direct renderer URL, never TinyURL. `--polr-url` cannot be combined with `-t`.
171
319
 
172
- [![Activity Diagram - Sampler B](https://tinyurl.com/ya3cqxkv)](https://tinyurl.com/ya3cqxkv)<!--![Activity Diagram - Sampler B](./puml/level_4_activity_diagram_sampler_b.puml)-->
320
+ To avoid a shortener entirely, use your own renderer with `-t`. This is also the
321
+ simplest setup for private diagrams when URLs are short enough for your renderer.
package/bin/index.js CHANGED
@@ -13,6 +13,7 @@ const run = require(path.resolve(__dirname, '../index.js'))
13
13
  // =============
14
14
 
15
15
  new Command()
16
+ .name('puml-for-markdown')
16
17
  .description('An application to add interactive PUML diagrams to your github markdown files. If running with default arguments, run in project root directory.')
17
18
  .option(
18
19
  '-s, --puml-server-url <url>',
@@ -45,7 +46,7 @@ new Command()
45
46
  )
46
47
  .option(
47
48
  '-g, --ignore-gitignore',
48
- "Don't use .gitignore to skip PUML and MD. Will automatically be true if no gitignore is found",
49
+ "Don't use .gitignore rules to skip PUML and MD",
49
50
  )
50
51
  .addOption(new Option(
51
52
  '-i, --gitignore-path <path>',
@@ -70,6 +71,12 @@ new Command()
70
71
  '-t, --turn-off-link-shortening',
71
72
  "Use the full puml server link instead of the tiny url, if your diagrams are too big this won't work",
72
73
  )
74
+ .option('--local-images', 'Link Markdown to exported local images (implies --output-images)')
75
+ .option('--interactive-links', 'Wrap local image previews in an interactive renderer link')
76
+ .option('--check', 'Check for stale Markdown and images without writing or using the network')
77
+ .option('--dry-run', 'List planned changes without writing or using the network')
78
+ .option('--no-cache', 'Disable the persistent diagram and image cache')
79
+ .option('-l, --polr-url <url>', 'Use a private Polr shortener; reads POLR_APIKEY from the environment')
73
80
  .action(opts => {
74
81
  const useDefaultGitignorePath = !opts.gitignorePath
75
82
 
@@ -81,14 +88,16 @@ new Command()
81
88
  opts.respectGitignore = !opts.ignoreGitignore
82
89
  opts.imageFormats = opts.imageFormats === 'both' ? ['png', 'svg'] : [opts.imageFormats]
83
90
 
84
- // If a gitignore path wasn't specified, don't try and parse it
91
+ // A missing root ignore file must not disable nested .gitignore files.
85
92
  if (useDefaultGitignorePath && !fs.existsSync(opts.gitignorePath)) {
86
- opts.respectGitignore = false
93
+ opts.gitignorePath = undefined
87
94
  }
88
95
 
89
- return run(opts).catch((e) => {
90
- console.error('FATAL EXCEPTION')
91
- console.error(e)
96
+ return run(opts).then(result => {
97
+ if (opts.check && result.stale) process.exitCode = 1
98
+ }).catch((e) => {
99
+ console.error(`ERROR: ${e.message}`)
100
+ process.exitCode = 1
92
101
  })
93
102
  })
94
103
  .parse(process.argv)