puml-for-markdown 1.2.1 → 2.0.1

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,29 @@
1
+ # Changelog
2
+
3
+ ## 2.0.1
4
+
5
+ - Correct outdated README statements about Windows support, automated testing,
6
+ the pre-commit hook, and diagram privacy. No runtime behavior changes.
7
+
8
+ ## 2.0.0
9
+
10
+ ### Migration from version 1
11
+
12
+ - Requires Node.js 22 or newer. Upgrade Node.js before installing version 2.
13
+ - CLI failures now exit with a nonzero status so scripts and CI can detect errors.
14
+ - The CLI creates `.puml-cache.json` by default. Add it to `.gitignore`, or use
15
+ `--no-cache` to disable caching. Refresh with `--no-cache` when remote includes
16
+ or the rendering server change.
17
+
18
+ ### Features and fixes
19
+
20
+ - Add local image links, offline check and dry-run modes, persistent caching,
21
+ and private Polr URL shortening.
22
+ - Support `.plantuml` files, Windows paths, nested ignore rules, and recursive
23
+ includes with cycle detection.
24
+ - Preserve Markdown formatting and code examples while replacing diagram links.
25
+ - Validate references before editing Markdown and preserve unrelated exported
26
+ files. Export images atomically with readable permissions.
27
+ - Handle HTTP errors and timeouts, and serialize watch updates.
28
+ - Update dependencies and examples, with automated checks on Linux, macOS,
29
+ 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,45 +76,79 @@ 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
82
89
  Here are some other features supported
83
90
  - You can specify the CLI to output the diagram images as png and/or svg see `--output-images` and `--image-formats`.
84
- For an example of outputted images, checkout [dist_puml/puml](./dist_puml/puml). These images were created by the
85
- [pre-commit hook](./.husky/pre-commit).
91
+ For historical examples of exported images, see [dist_puml/puml](./dist_puml/puml).
92
+ The current pre-commit hook runs the automated tests.
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
@@ -139,34 +173,157 @@ In order to support `!include` we parse puml files and replace any `!include` wi
139
173
  #### Parsing Markdown
140
174
  The markdown files are then parsed for markdown comments. If the comments reference a PlantUML file, a link to the web
141
175
  service url will be added next to the comment. Because these links contain the full PlantUML diagram encoding in it,
142
- there are no issues using them in private repositories.
176
+ the source can be recovered from the URL. A private repository does not make the
177
+ renderer or shortener private; see [diagram privacy](#reliability-and-operational-notes).
143
178
 
144
179
  ### Testing
145
- Currently there is no TDD, for now I've just been using the example diagrams to test. The examples are pretty extensive
146
- though.
180
+ Run `npm test` to execute the automated CLI regression tests. CI runs on Linux,
181
+ macOS, and Windows with Node.js 22 and 24. The suite covers Markdown updates,
182
+ path handling, ignore rules, includes, image exports, caching, and shortening.
183
+ See [CONTRIBUTING.md](https://github.com/danielyaa5/puml-for-markdown/blob/main/CONTRIBUTING.md)
184
+ for development instructions.
147
185
 
148
186
  ### Other Helpful Links
149
187
  * [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
188
 
189
+ ### Windows support
190
+ Version 2 fixes the reported Windows path lookup issue
191
+ ([#6](https://github.com/danielyaa5/puml-for-markdown/issues/6)). Windows path and
192
+ ignore-rule regressions are covered by automated tests. The version 2 changes
193
+ were also tested on Windows 11 ARM64 with Node.js 24.
194
+
195
+ For remaining limitations, see [reliability and operational notes](#reliability-and-operational-notes).
196
+
151
197
  ### More Examples
152
- [![ERD](https://tinyurl.com/ya6qvr7r)](https://tinyurl.com/ya6qvr7r)<!--![ERD](./puml/level_4_erd.puml)-->
198
+ [![ERD](https://tinyurl.com/2amb263t)](https://tinyurl.com/2amb263t)<!--![ERD](./puml/level_4_erd.puml)-->
199
+
200
+
201
+
202
+ [![Container View](https://tinyurl.com/284kg5h5)](https://tinyurl.com/284kg5h5)<!--![Container View](./puml/level_2_container_view.puml)-->
203
+
204
+
205
+
206
+ [![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)-->
153
207
 
154
208
 
155
209
 
156
- [![Container View](https://tinyurl.com/yblre3m4)](https://tinyurl.com/yblre3m4)<!--![Container View](./puml/level_2_container_view.puml)-->
210
+ [![Component View - Pipeline Component](https://tinyurl.com/2xlmr44t)](https://tinyurl.com/2xlmr44t)<!--![Component View - Pipeline Component](./puml/level_3_component_view_pipeline.puml)-->
157
211
 
158
212
 
159
213
 
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)-->
214
+ [![Activity Diagram - Sampler A](https://tinyurl.com/26j5s2fw)](https://tinyurl.com/26j5s2fw)<!--![Activity Diagram - Sampler A](./puml/level_4_activity_diagram_sampler_a.puml)-->
161
215
 
162
216
 
163
217
 
164
- [![Component View - Pipeline Component](https://tinyurl.com/y9j7twkz)](https://tinyurl.com/y9j7twkz)<!--![Component View - Pipeline Component](./puml/level_3_component_view_pipeline.puml)-->
218
+ [![Activity Diagram - Sampler B](https://tinyurl.com/2cqvdekn)](https://tinyurl.com/2cqvdekn)<!--![Activity Diagram - Sampler B](./puml/level_4_activity_diagram_sampler_b.puml)-->
165
219
 
166
220
 
221
+ ### Reliability and operational notes
167
222
 
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)-->
223
+ - Fatal errors return a nonzero exit status, suitable for CI. Watch mode logs errors
224
+ and retries on the next interval; it runs immediately on startup.
225
+ - Local `!include` files are expanded recursively relative to the file containing
226
+ the directive. Remote, standard-library, and macro-based includes remain for the
227
+ renderer to evaluate. This is not a complete PlantUML preprocessor: conditional
228
+ includes and `!include_once` / `!include_many` are not expanded locally.
229
+ - Unrelated Markdown links, fenced/indented code, inline code, and line endings
230
+ are preserved. Unchanged Markdown files are not rewritten.
231
+ - All Markdown references are checked before documents are written. Image exports
232
+ complete before Markdown is updated, but an export failure can leave earlier
233
+ successfully exported images updated; the operation is not a multi-file transaction.
234
+ - Export overwrites the matching generated image files but never clears the output
235
+ directory. Stale images are retained. Ambiguous output names and symlinks inside
236
+ the output path are rejected.
237
+ - Renderer and shortener requests time out after 30 seconds. If TinyURL fails,
238
+ a warning is printed and the direct PlantUML URL is used. Large URLs can still
239
+ exceed renderer/proxy limits; shortening does not remove the renderer's limit.
240
+ - **Diagram privacy:** encoded diagram URLs contain recoverable source text.
241
+ The default shortener receives that URL, and the renderer receives the diagram
242
+ when someone opens it or when images are exported. For private diagrams, use
243
+ your own renderer with `--puml-server-url` and disable shortening with `-t`.
169
244
 
170
245
 
246
+ ### Local images and offline viewing
247
+
248
+ ```sh
249
+ puml-for-markdown --local-images --image-formats svg
250
+ ```
251
+
252
+ This implies `--output-images` and produces relative Markdown links to files in
253
+ `dist_puml`. For example, `<!--![Diagram](puml/a.puml)-->` becomes
254
+ `![Diagram](dist_puml/puml/a.svg)<!--![Diagram](puml/a.puml)-->`.
255
+ Paths are relative to each Markdown file and URL-encoded, including spaces.
256
+ Both `.puml` and `.plantuml` sources work.
257
+
258
+ SVG diagrams link to other exported SVG diagrams using relative paths. Open the
259
+ SVG directly in a browser or serve the export folder to navigate between them.
260
+ Markdown hosts may disable links inside embedded SVG images. PNG previews cannot
261
+ contain clickable elements. With `--image-formats both`, Markdown uses SVG.
262
+
263
+ Add `--interactive-links` to wrap a local preview in a link to the online renderer.
264
+ That renderer link uses separate source with online diagram hyperlinks, while
265
+ exported SVGs retain their local hyperlinks. Local-only mode skips URL shortening.
266
+ Rendering still contacts the configured PlantUML server; use `--puml-server-url`
267
+ with your own renderer when the source must remain private.
268
+
269
+ ### Check or preview changes without modifying files
270
+
271
+ ```sh
272
+ puml-for-markdown --dry-run -t
273
+ puml-for-markdown --check -t
274
+ puml-for-markdown --local-images --image-formats svg --check
275
+ ```
276
+
277
+ Both commands are offline and never write Markdown, images, or cache files.
278
+ `--dry-run` lists planned changes and exits successfully; `--check` exits with 1
279
+ if documentation is stale, otherwise 0. Invalid inputs still exit with 1.
280
+ Neither can be combined with `--hot-reload`.
281
+
282
+ A check that uses shortened URLs or exported images needs the cache from a prior
283
+ successful generation with the same settings. If it is missing, the check reports
284
+ that generation is needed rather than contacting a shortener or claiming the files
285
+ are current. Direct-URL Markdown checks (`--check -t` without image export) do not
286
+ require a cache. For CI, use that deterministic mode or restore the generation
287
+ cache with the exported images.
288
+
289
+ ### Persistent cache and incremental watch mode
290
+
291
+ Successful runs save `.puml-cache.json` at the project root. Add it to your
292
+ `.gitignore`: encoded URLs contain recoverable source text, even for private diagrams.
293
+ The cache contains no Polr API key. Renderer/provider settings and a hash of the key
294
+ separate cache entries when configuration changes.
295
+
296
+ The CLI reuses encodings, shortened URLs, and verified image files. Watch mode
297
+ checks inputs each interval and only rebuilds affected content. Local include
298
+ changes invalidate their parent diagrams; remote diagram links invalidate parents
299
+ when their target URL changes. Local links stay stable when only a child changes.
300
+ Missing or externally modified images are rendered again.
301
+
302
+ ```sh
303
+ puml-for-markdown --hot-reload
304
+ puml-for-markdown --no-cache
305
+ ```
306
+
307
+ `--no-cache` forces regeneration without reading or writing the cache. Use it to
308
+ refresh remote includes, renderer upgrades at the same URL, or expired short links:
309
+ those external changes cannot be detected from local file content. Failed shortening
310
+ attempts are not cached, allowing the next run to retry. Stale generated images are
311
+ retained; the CLI never clears the destination directory.
312
+
313
+ ### Private Polr URL shortening
314
+
315
+ ```sh
316
+ # Set POLR_APIKEY in your shell or CI secret environment first.
317
+ puml-for-markdown --puml-server-url https://uml.example.com/plantuml \
318
+ --polr-url https://short.example.com
319
+ ```
320
+
321
+ This adapts the provider idea from [PR #8](https://github.com/danielyaa5/puml-for-markdown/pull/8)
322
+ by @mzagar. The implementation uses the [Polr v2 API](https://docs.polrproject.org/en/latest/developer-guide/api/):
323
+ POST `/api/v2/action/shorten`, with URL-encoded `url`, `key`, and `response_type`
324
+ fields. JSON and plain-text responses are accepted and validated. Credentials are
325
+ never written into generated links or the cache. A failed Polr request falls back
326
+ to the direct renderer URL, never TinyURL. `--polr-url` cannot be combined with `-t`.
171
327
 
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)-->
328
+ To avoid a shortener entirely, use your own renderer with `-t`. This is also the
329
+ 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)