@matfsw/docusaurus-plantuml-plugin 1.1.2 → 1.3.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 (43) hide show
  1. package/CHANGELOG.md +103 -1
  2. package/README.md +211 -32
  3. package/assets/stdlib/LICENSES.md +21 -0
  4. package/assets/stdlib/archimate.min.js +22 -0
  5. package/assets/stdlib/azure.min.js +305 -0
  6. package/assets/stdlib/c4.min.js +47 -0
  7. package/assets/stdlib/cloudinsight.min.js +93 -0
  8. package/assets/stdlib/eip.min.js +14 -0
  9. package/assets/stdlib/k8s.min.js +52 -0
  10. package/assets/stdlib/kubernetes.min.js +17 -0
  11. package/assets/stdlib/manifest.json +112 -0
  12. package/assets/stdlib/office.min.js +557 -0
  13. package/dist/constants.d.ts +2 -0
  14. package/dist/constants.js +2 -0
  15. package/dist/index.cjs +384 -8
  16. package/dist/index.d.cts +91 -3
  17. package/dist/index.d.ts +12 -2
  18. package/dist/index.js +20 -1
  19. package/dist/options.d.ts +57 -1
  20. package/dist/options.js +84 -0
  21. package/dist/runtime/assetLoader.d.ts +13 -1
  22. package/dist/runtime/assetLoader.js +36 -13
  23. package/dist/runtime/cache.d.ts +6 -1
  24. package/dist/runtime/cache.js +2 -2
  25. package/dist/runtime/errors.d.ts +3 -1
  26. package/dist/runtime/errors.js +27 -1
  27. package/dist/runtime/renderer.d.ts +3 -0
  28. package/dist/runtime/renderer.js +21 -7
  29. package/dist/runtime/stdlibLoader.d.ts +31 -0
  30. package/dist/runtime/stdlibLoader.js +99 -0
  31. package/dist/stdlib.d.ts +35 -0
  32. package/dist/stdlib.js +163 -0
  33. package/dist/stdlibBundle.d.ts +60 -0
  34. package/dist/stdlibBundle.js +218 -0
  35. package/dist/stdlibShared.d.ts +51 -0
  36. package/dist/stdlibShared.js +54 -0
  37. package/dist/theme/MDXComponents/Code/index.js +1 -1
  38. package/dist/theme/PlantUmlDiagram/index.d.ts +6 -1
  39. package/dist/theme/PlantUmlDiagram/index.js +53 -5
  40. package/dist/theme/PlantUmlDiagram/styles.module.css +117 -0
  41. package/dist/theme/usePlantUmlConfig.d.ts +8 -0
  42. package/dist/theme/usePlantUmlConfig.js +14 -8
  43. package/package.json +3 -1
@@ -39,4 +39,6 @@ export declare const DATA_ATTR: {
39
39
  readonly zoom: "data-plantuml-zoom";
40
40
  /** `"true"` on the figure while the diagram fills the browser viewport. */
41
41
  readonly maximized: "data-plantuml-maximized";
42
+ /** `"true"` on the figure while the source panel is open. */
43
+ readonly sourceOpen: "data-plantuml-source-open";
42
44
  };
package/dist/constants.js CHANGED
@@ -41,4 +41,6 @@ export const DATA_ATTR = {
41
41
  zoom: 'data-plantuml-zoom',
42
42
  /** `"true"` on the figure while the diagram fills the browser viewport. */
43
43
  maximized: 'data-plantuml-maximized',
44
+ /** `"true"` on the figure while the source panel is open. */
45
+ sourceOpen: 'data-plantuml-source-open',
44
46
  };
package/dist/index.cjs CHANGED
@@ -46,7 +46,7 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
46
46
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
47
47
 
48
48
  // src/index.ts
49
- var import_node_path2 = __toESM(require("path"), 1);
49
+ var import_node_path4 = __toESM(require("path"), 1);
50
50
  var import_node_url = require("url");
51
51
  var import_copy_webpack_plugin = __toESM(require("copy-webpack-plugin"), 1);
52
52
 
@@ -116,6 +116,12 @@ var DEFAULT_GRAPHVIZ_OPTIONS = {
116
116
  maxSourceBytes: 1e5,
117
117
  transparentBackground: true
118
118
  };
119
+ var DEFAULT_STDLIB_OPTIONS = {
120
+ enabled: true,
121
+ include: [],
122
+ source: [],
123
+ namespaces: null
124
+ };
119
125
  var DEFAULT_OPTIONS = {
120
126
  languages: ["plantuml", "puml"],
121
127
  theme: "auto",
@@ -126,7 +132,9 @@ var DEFAULT_OPTIONS = {
126
132
  renderTimeoutMs: 2e4,
127
133
  cacheMaxEntries: 50,
128
134
  zoom: true,
129
- graphviz: DEFAULT_GRAPHVIZ_OPTIONS
135
+ showSource: true,
136
+ graphviz: DEFAULT_GRAPHVIZ_OPTIONS,
137
+ stdlib: DEFAULT_STDLIB_OPTIONS
130
138
  };
131
139
  var CACHE_MODES = ["none", "memory", "session"];
132
140
  var THEMES = ["auto", "light", "dark"];
@@ -233,6 +241,68 @@ function validateGraphviz(value) {
233
241
  )
234
242
  };
235
243
  }
244
+ function validateNamespaceList(value, key) {
245
+ if (!Array.isArray(value)) {
246
+ fail(`options.${key} must be an array of strings, received ${JSON.stringify(value)}.`);
247
+ }
248
+ const normalized = value.map((entry, index) => {
249
+ if (typeof entry !== "string" || entry.trim() === "") {
250
+ fail(
251
+ `options.${key}[${index}] must be a non-empty string, received ${JSON.stringify(entry)}.`
252
+ );
253
+ }
254
+ return entry.trim().toLowerCase();
255
+ });
256
+ const duplicates = normalized.filter((entry, index) => normalized.indexOf(entry) !== index);
257
+ if (duplicates.length > 0) {
258
+ fail(`options.${key} contains duplicate entries: ${quoteList([...new Set(duplicates)])}.`);
259
+ }
260
+ return normalized;
261
+ }
262
+ function validateStdlibSource(value) {
263
+ if (value === void 0) return [];
264
+ const entries = Array.isArray(value) ? value : [value];
265
+ return entries.map((entry, index) => {
266
+ if (typeof entry !== "string" || entry.trim() === "") {
267
+ const where = Array.isArray(value) ? `stdlib.source[${index}]` : "stdlib.source";
268
+ fail(`options.${where} must be a non-empty string, received ${JSON.stringify(entry)}.`);
269
+ }
270
+ return entry.trim();
271
+ });
272
+ }
273
+ function validateStdlib(value) {
274
+ if (value === void 0) return { ...DEFAULT_STDLIB_OPTIONS, include: [], source: [] };
275
+ if (value === false) return { ...DEFAULT_STDLIB_OPTIONS, enabled: false, include: [], source: [] };
276
+ if (!isPlainObject(value)) {
277
+ fail(`options.stdlib must be an object or false, received ${JSON.stringify(value)}.`);
278
+ }
279
+ const allowedKeys = ["enabled", "include", "source", "namespaces"];
280
+ const unknownKeys = Object.keys(value).filter((key) => !allowedKeys.includes(key));
281
+ if (unknownKeys.length > 0) {
282
+ fail(
283
+ `Unknown option${unknownKeys.length > 1 ? "s" : ""} ${quoteList(unknownKeys.map((key) => `stdlib.${key}`))}. Supported options: ${quoteList(allowedKeys.map((key) => `stdlib.${key}`))}.`
284
+ );
285
+ }
286
+ const include = value.include === void 0 ? [] : validateNamespaceList(value.include, "stdlib.include");
287
+ const source = validateStdlibSource(value.source);
288
+ const namespaces = value.namespaces === void 0 ? null : validateNamespaceList(value.namespaces, "stdlib.namespaces");
289
+ if (namespaces !== null && namespaces.length === 0) {
290
+ fail(
291
+ "options.stdlib.namespaces must name at least one namespace. To turn the standard library off entirely, use `stdlib: false`."
292
+ );
293
+ }
294
+ if (include.length > 0 && source.length === 0) {
295
+ fail(
296
+ `options.stdlib.include names ${quoteList(include)}, but options.stdlib.source is not set, so there is nowhere to read them from. Point it at the \`stdlib\` directory of a plantuml-stdlib checkout.`
297
+ );
298
+ }
299
+ return {
300
+ enabled: validateBoolean(value.enabled, "stdlib.enabled", DEFAULT_STDLIB_OPTIONS.enabled),
301
+ include,
302
+ source,
303
+ namespaces
304
+ };
305
+ }
236
306
  function validateRenderTimeout(value) {
237
307
  if (value === void 0) return DEFAULT_OPTIONS.renderTimeoutMs;
238
308
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -260,7 +330,8 @@ function resolveOptions(rawOptions) {
260
330
  return {
261
331
  ...DEFAULT_OPTIONS,
262
332
  languages: [...DEFAULT_OPTIONS.languages],
263
- graphviz: { ...DEFAULT_GRAPHVIZ_OPTIONS, languages: [...DEFAULT_GRAPHVIZ_OPTIONS.languages] }
333
+ graphviz: { ...DEFAULT_GRAPHVIZ_OPTIONS, languages: [...DEFAULT_GRAPHVIZ_OPTIONS.languages] },
334
+ stdlib: { ...DEFAULT_STDLIB_OPTIONS, include: [], source: [] }
264
335
  };
265
336
  }
266
337
  if (!isPlainObject(rawOptions)) {
@@ -276,7 +347,9 @@ function resolveOptions(rawOptions) {
276
347
  "renderTimeoutMs",
277
348
  "cacheMaxEntries",
278
349
  "zoom",
350
+ "showSource",
279
351
  "graphviz",
352
+ "stdlib",
280
353
  // Docusaurus injects `id` for multi-instance plugins.
281
354
  "id"
282
355
  ]);
@@ -297,6 +370,7 @@ function resolveOptions(rawOptions) {
297
370
  }
298
371
  const languages = validateLanguages(rawOptions.languages, "languages", DEFAULT_OPTIONS.languages);
299
372
  const graphviz = validateGraphviz(rawOptions.graphviz);
373
+ const stdlib = validateStdlib(rawOptions.stdlib);
300
374
  if (graphviz.enabled) {
301
375
  const claimedTwice = graphviz.languages.filter((entry) => languages.includes(entry));
302
376
  if (claimedTwice.length > 0) {
@@ -308,6 +382,7 @@ function resolveOptions(rawOptions) {
308
382
  return {
309
383
  languages,
310
384
  graphviz,
385
+ stdlib,
311
386
  theme: theme ?? DEFAULT_OPTIONS.theme,
312
387
  lazy: validateBoolean(rawOptions.lazy, "lazy", DEFAULT_OPTIONS.lazy),
313
388
  cache: cache ?? DEFAULT_OPTIONS.cache,
@@ -323,12 +398,296 @@ function resolveOptions(rawOptions) {
323
398
  ),
324
399
  renderTimeoutMs: validateRenderTimeout(rawOptions.renderTimeoutMs),
325
400
  cacheMaxEntries: validateCacheMaxEntries(rawOptions.cacheMaxEntries),
326
- zoom: validateBoolean(rawOptions.zoom, "zoom", DEFAULT_OPTIONS.zoom)
401
+ zoom: validateBoolean(rawOptions.zoom, "zoom", DEFAULT_OPTIONS.zoom),
402
+ showSource: validateBoolean(rawOptions.showSource, "showSource", DEFAULT_OPTIONS.showSource)
403
+ };
404
+ }
405
+
406
+ // src/stdlib.ts
407
+ var import_node_crypto = __toESM(require("crypto"), 1);
408
+ var import_node_fs3 = __toESM(require("fs"), 1);
409
+ var import_node_path3 = __toESM(require("path"), 1);
410
+
411
+ // src/stdlibBundle.ts
412
+ var import_node_fs2 = __toESM(require("fs"), 1);
413
+ var import_node_path2 = __toESM(require("path"), 1);
414
+
415
+ // src/stdlibShared.ts
416
+ var STDLIB_EXAMPLES_DIRECTORY = "_examples_";
417
+ var STDLIB_INCLUDE_PATTERN = /^[^\S\r\n]*!include(?:sub|url|_many)?[^\S\r\n]+<([^>\r\n]+)>/gim;
418
+ function findStdlibIncludes(source) {
419
+ const found = [];
420
+ const seen = /* @__PURE__ */ new Set();
421
+ for (const match of source.matchAll(STDLIB_INCLUDE_PATTERN)) {
422
+ const raw = match[1]?.trim() ?? "";
423
+ if (raw.includes("$") || raw.includes("%")) continue;
424
+ const slash = raw.indexOf("/");
425
+ if (slash <= 0) continue;
426
+ const namespace = raw.slice(0, slash).toLowerCase();
427
+ const target = raw.slice(slash + 1).toLowerCase();
428
+ if (target === "") continue;
429
+ const key = `${namespace}/${target}`;
430
+ if (seen.has(key)) continue;
431
+ seen.add(key);
432
+ found.push({
433
+ namespace,
434
+ target,
435
+ fromExamples: target.startsWith(`${STDLIB_EXAMPLES_DIRECTORY}/`)
436
+ });
437
+ }
438
+ return found;
439
+ }
440
+
441
+ // src/stdlibBundle.ts
442
+ function isSkippedDirectory(name) {
443
+ if (name === STDLIB_EXAMPLES_DIRECTORY) return false;
444
+ return name.startsWith("_") && name.endsWith("_");
445
+ }
446
+ function collectFiles(directory, namespaceRoot) {
447
+ const entries = import_node_fs2.default.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
448
+ const files = [];
449
+ for (const entry of entries) {
450
+ const absolutePath = import_node_path2.default.join(directory, entry.name);
451
+ if (entry.isDirectory()) {
452
+ if (!isSkippedDirectory(entry.name)) files.push(...collectFiles(absolutePath, namespaceRoot));
453
+ continue;
454
+ }
455
+ const extension = import_node_path2.default.extname(entry.name).toLowerCase();
456
+ if (extension !== ".puml" && extension !== ".json") continue;
457
+ const key = import_node_path2.default.relative(namespaceRoot, absolutePath).split(import_node_path2.default.sep).join("/").toLowerCase();
458
+ files.push({
459
+ key,
460
+ absolutePath,
461
+ extension,
462
+ fromExamples: key.startsWith(`${STDLIB_EXAMPLES_DIRECTORY}/`)
463
+ });
464
+ }
465
+ return files;
466
+ }
467
+ function readNamespaceInfo(namespaceRoot) {
468
+ const readme = import_node_path2.default.join(namespaceRoot, "README.md");
469
+ if (!import_node_fs2.default.existsSync(readme)) return {};
470
+ const lines = import_node_fs2.default.readFileSync(readme, "utf8").split(/\r\n|\r|\n/);
471
+ if (lines[0]?.trim() !== "---") return {};
472
+ const info = {};
473
+ for (const line of lines.slice(1)) {
474
+ const trimmed = line.trim();
475
+ if (trimmed === "---") break;
476
+ const colon = trimmed.indexOf(":");
477
+ if (colon === -1) continue;
478
+ info[trimmed.slice(0, colon).trim()] = trimmed.slice(colon + 1).trim();
479
+ }
480
+ return info;
481
+ }
482
+ function toJsLiteral(value) {
483
+ return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
484
+ }
485
+ function toJsonLiteral(text, file) {
486
+ try {
487
+ return toJsLiteral(JSON.parse(text));
488
+ } catch (cause) {
489
+ throw new Error(`'${file ?? "a standard library JSON file"}' is not valid JSON.`, { cause });
490
+ }
491
+ }
492
+ function unwrapDocument(lines) {
493
+ if (lines.filter((line) => /^@startuml\b/.test(line.trim())).length !== 1) return lines;
494
+ let first = 0;
495
+ while (first < lines.length && lines[first]?.trim() === "") first += 1;
496
+ if (!/^@startuml\b/.test(lines[first]?.trim() ?? "")) return lines;
497
+ let last = lines.length - 1;
498
+ while (last > first && lines[last]?.trim() === "") last -= 1;
499
+ if (lines[last]?.trim() !== "@enduml") return lines;
500
+ return lines.slice(first + 1, last);
501
+ }
502
+ function withoutExtension(key) {
503
+ return key.replace(/\.(puml|json)$/, "");
504
+ }
505
+ function stdlibBundleFileName(namespace) {
506
+ return `${namespace.toLowerCase()}.min.js`;
507
+ }
508
+ function buildStdlibNamespace(stdlibRoot, directoryName) {
509
+ const namespaceRoot = import_node_path2.default.join(stdlibRoot, directoryName);
510
+ if (!import_node_fs2.default.existsSync(namespaceRoot) || !import_node_fs2.default.statSync(namespaceRoot).isDirectory()) {
511
+ throw new Error(`'${namespaceRoot}' is not a plantuml-stdlib namespace directory.`);
512
+ }
513
+ const namespace = directoryName.toLowerCase();
514
+ const info = readNamespaceInfo(namespaceRoot);
515
+ const files = collectFiles(namespaceRoot, namespaceRoot);
516
+ const dependencies = /* @__PURE__ */ new Set();
517
+ const exampleDependencies = /* @__PURE__ */ new Set();
518
+ const quoted = toJsLiteral(namespace);
519
+ const lines = [
520
+ `// ${namespace}.min.js - generated from plantuml-stdlib by @matfsw/docusaurus-plantuml-plugin.`,
521
+ "(function () {",
522
+ " var w = window;",
523
+ " w.PLANTUML_STDLIB = w.PLANTUML_STDLIB || {};",
524
+ " w.PLANTUML_STDLIB_JSON = w.PLANTUML_STDLIB_JSON || {};",
525
+ " w.PLANTUML_STDLIB_INFO = w.PLANTUML_STDLIB_INFO || {};",
526
+ ` w.PLANTUML_STDLIB_INFO[${quoted}] = ${toJsLiteral(info)};`,
527
+ ` var P = (w.PLANTUML_STDLIB[${quoted}] = w.PLANTUML_STDLIB[${quoted}] || {});`,
528
+ ` var J = (w.PLANTUML_STDLIB_JSON[${quoted}] = w.PLANTUML_STDLIB_JSON[${quoted}] || {});`
529
+ ];
530
+ for (const file of files) {
531
+ const text = import_node_fs2.default.readFileSync(file.absolutePath, "utf8");
532
+ const bare = withoutExtension(file.key);
533
+ const target = file.extension === ".puml" ? "P" : "J";
534
+ const value = file.extension === ".puml" ? toJsLiteral(unwrapDocument(text.split(/\r\n|\r|\n/))) : toJsonLiteral(text, file.absolutePath);
535
+ lines.push(
536
+ ` ${target}[${toJsLiteral(bare)}] = ${target}[${toJsLiteral(file.key)}] = ${value};`
537
+ );
538
+ if (file.extension !== ".puml") continue;
539
+ for (const reference of findStdlibIncludes(text)) {
540
+ if (reference.namespace === namespace) continue;
541
+ (file.fromExamples ? exampleDependencies : dependencies).add(reference.namespace);
542
+ }
543
+ }
544
+ lines.push("})();", "");
545
+ return {
546
+ namespace,
547
+ directoryName,
548
+ script: lines.join("\n"),
549
+ info,
550
+ dependencies: [...dependencies].sort(),
551
+ // An example-only dependency that the library already needs is not worth recording twice.
552
+ exampleDependencies: [...exampleDependencies].filter((name) => !dependencies.has(name)).sort(),
553
+ fileCount: files.length
554
+ };
555
+ }
556
+
557
+ // src/stdlib.ts
558
+ function vendoredDirectory(currentDir2) {
559
+ return import_node_path3.default.join(currentDir2, "..", "assets", "stdlib");
560
+ }
561
+ function readVendoredManifest(directory) {
562
+ const manifestPath = import_node_path3.default.join(directory, "manifest.json");
563
+ if (!import_node_fs3.default.existsSync(manifestPath)) {
564
+ throw new Error(
565
+ `[${PLUGIN_NAME}] The vendored standard library manifest is missing from '${manifestPath}'. This is a packaging fault: reinstall the plugin, or run 'npm run stdlib:update' if you are working in a checkout of it.`
566
+ );
567
+ }
568
+ return JSON.parse(import_node_fs3.default.readFileSync(manifestPath, "utf8"));
569
+ }
570
+ function digest(...parts) {
571
+ const hash = import_node_crypto.default.createHash("sha256");
572
+ for (const part of parts) hash.update(part).update("\0");
573
+ return hash.digest("hex").slice(0, 12);
574
+ }
575
+ function directoryStamp(directory) {
576
+ const parts = [];
577
+ const walk = (current) => {
578
+ for (const entry of import_node_fs3.default.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
579
+ const absolute = import_node_path3.default.join(current, entry.name);
580
+ if (entry.isDirectory()) {
581
+ walk(absolute);
582
+ continue;
583
+ }
584
+ const stats = import_node_fs3.default.statSync(absolute);
585
+ parts.push(`${absolute}:${stats.size}:${stats.mtimeMs}`);
586
+ }
587
+ };
588
+ walk(directory);
589
+ return digest(...parts);
590
+ }
591
+ function findNamespaceDirectory(sourceRoots, namespace) {
592
+ for (const root of sourceRoots) {
593
+ if (!import_node_fs3.default.existsSync(root)) continue;
594
+ for (const entry of import_node_fs3.default.readdirSync(root, { withFileTypes: true })) {
595
+ if (entry.isDirectory() && entry.name.toLowerCase() === namespace) {
596
+ return import_node_path3.default.join(root, entry.name);
597
+ }
598
+ }
599
+ }
600
+ return null;
601
+ }
602
+ function resolveStdlibAssets({
603
+ options,
604
+ currentDir: currentDir2,
605
+ siteDir,
606
+ cacheDir
607
+ }) {
608
+ if (!options.enabled) return null;
609
+ const directory = vendoredDirectory(currentDir2);
610
+ const vendored = readVendoredManifest(directory);
611
+ const vendoredNamespaces = vendored.namespaces ?? {};
612
+ const files = [];
613
+ const namespaces = {};
614
+ const addVendored = (namespace) => {
615
+ if (namespace in namespaces) return;
616
+ const entry = vendoredNamespaces[namespace];
617
+ if (!entry) return;
618
+ const bundlePath = import_node_path3.default.join(directory, entry.file);
619
+ if (!import_node_fs3.default.existsSync(bundlePath)) {
620
+ throw new Error(
621
+ `[${PLUGIN_NAME}] The vendored standard library manifest lists '${entry.file}', but '${bundlePath}' does not exist. Reinstall the plugin.`
622
+ );
623
+ }
624
+ files.push(bundlePath);
625
+ namespaces[namespace] = {
626
+ dependencies: entry.dependencies ?? [],
627
+ exampleDependencies: entry.exampleDependencies ?? []
628
+ };
629
+ for (const dependency of entry.dependencies ?? []) addVendored(dependency);
630
+ };
631
+ const selected = options.namespaces ?? Object.keys(vendoredNamespaces);
632
+ if (options.namespaces) {
633
+ const unknown = options.namespaces.filter(
634
+ (namespace) => !(namespace in vendoredNamespaces) && !options.include.includes(namespace)
635
+ );
636
+ if (unknown.length > 0) {
637
+ throw new Error(
638
+ `[${PLUGIN_NAME}] options.stdlib.namespaces names ${unknown.map((name) => `'${name}'`).join(", ")}, which ${unknown.length > 1 ? "are" : "is"} not vendored with this plugin. Vendored namespaces: ${Object.keys(vendoredNamespaces).join(", ")}. To use another one, add it to options.stdlib.include and point options.stdlib.source at a plantuml-stdlib checkout.`
639
+ );
640
+ }
641
+ }
642
+ for (const namespace of selected) addVendored(namespace);
643
+ const sourceRoots = options.source.map((entry) => import_node_path3.default.resolve(siteDir, entry));
644
+ const generatedDir = import_node_path3.default.join(cacheDir ?? import_node_path3.default.join(siteDir, ".docusaurus"), "plantuml-stdlib");
645
+ const stamps = [];
646
+ for (const namespace of options.include) {
647
+ const namespaceDir = findNamespaceDirectory(sourceRoots, namespace);
648
+ if (!namespaceDir) {
649
+ throw new Error(
650
+ `[${PLUGIN_NAME}] options.stdlib.include names '${namespace}', which was not found in ${sourceRoots.map((root) => `'${root}'`).join(", ")}. Clone the standard library (git clone --depth 1 https://github.com/plantuml/plantuml-stdlib) and point options.stdlib.source at its 'stdlib' directory.`
651
+ );
652
+ }
653
+ const stamp = directoryStamp(namespaceDir);
654
+ stamps.push(`${namespace}:${stamp}`);
655
+ const cached = import_node_path3.default.join(generatedDir, stamp, stdlibBundleFileName(namespace));
656
+ if (!import_node_fs3.default.existsSync(cached)) {
657
+ const bundle = buildStdlibNamespace(import_node_path3.default.dirname(namespaceDir), import_node_path3.default.basename(namespaceDir));
658
+ import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(cached), { recursive: true });
659
+ import_node_fs3.default.writeFileSync(cached, bundle.script, "utf8");
660
+ import_node_fs3.default.writeFileSync(
661
+ `${cached}.json`,
662
+ JSON.stringify({
663
+ dependencies: bundle.dependencies,
664
+ exampleDependencies: bundle.exampleDependencies
665
+ }),
666
+ "utf8"
667
+ );
668
+ }
669
+ const meta = JSON.parse(import_node_fs3.default.readFileSync(`${cached}.json`, "utf8"));
670
+ const previous = files.findIndex(
671
+ (file) => import_node_path3.default.basename(file) === stdlibBundleFileName(namespace)
672
+ );
673
+ if (previous !== -1) files.splice(previous, 1);
674
+ files.push(cached);
675
+ namespaces[namespace] = {
676
+ dependencies: meta.dependencies,
677
+ exampleDependencies: meta.exampleDependencies
678
+ };
679
+ }
680
+ return {
681
+ files,
682
+ manifest: {
683
+ revision: digest(vendored.upstream?.commit ?? "unknown", ...stamps.sort()),
684
+ namespaces
685
+ }
327
686
  };
328
687
  }
329
688
 
330
689
  // src/index.ts
331
- var currentDir = import_node_path2.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
690
+ var currentDir = import_node_path4.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
332
691
  function createCopyPlugin(currentBundler, patterns) {
333
692
  if (currentBundler?.name === "rspack") {
334
693
  const { CopyRspackPlugin } = currentBundler.instance;
@@ -341,20 +700,28 @@ function createCopyPlugin(currentBundler, patterns) {
341
700
  }
342
701
  return new import_copy_webpack_plugin.default({ patterns });
343
702
  }
344
- function plantumlPlugin(_context, options = {}) {
703
+ function plantumlPlugin(context, options = {}) {
345
704
  const resolved = resolveOptions(options);
346
705
  const core = locatePlantUmlCore();
347
706
  const assetsDir = assetsDirForVersion(core.version);
707
+ const stdlib = resolveStdlibAssets({
708
+ options: resolved.stdlib,
709
+ currentDir,
710
+ siteDir: context.siteDir,
711
+ cacheDir: context.generatedFilesDir
712
+ });
713
+ const stdlibDir = stdlib ? `${assetsDir}/stdlib-${stdlib.manifest.revision}` : null;
348
714
  return {
349
715
  name: PLUGIN_NAME,
350
716
  getThemePath() {
351
- return import_node_path2.default.join(currentDir, "theme");
717
+ return import_node_path4.default.join(currentDir, "theme");
352
718
  },
353
719
  contentLoaded({ actions }) {
354
720
  const globalData = {
355
721
  options: resolved,
356
722
  assetsDir,
357
- coreVersion: core.version
723
+ coreVersion: core.version,
724
+ stdlib: stdlib && stdlibDir ? { dir: stdlibDir, manifest: stdlib.manifest } : null
358
725
  };
359
726
  actions.setGlobalData(globalData);
360
727
  },
@@ -368,6 +735,15 @@ function plantumlPlugin(_context, options = {}) {
368
735
  // These files are already minified upstream; re-processing 8 MB is wasted work.
369
736
  info: { minimized: true }
370
737
  }));
738
+ if (stdlib && stdlibDir) {
739
+ patterns.push(
740
+ ...stdlib.files.map((absolutePath) => ({
741
+ from: absolutePath,
742
+ to: `${stdlibDir}/[name][ext]`,
743
+ info: { minimized: true }
744
+ }))
745
+ );
746
+ }
371
747
  return { plugins: [createCopyPlugin(utils?.currentBundler, patterns)] };
372
748
  }
373
749
  };
package/dist/index.d.cts CHANGED
@@ -42,6 +42,50 @@ type ResolvedGraphvizOptions = Required<Omit<GraphvizOptions, 'languages'>> & {
42
42
  /** Normalized to lower case; matching is case-insensitive. */
43
43
  languages: string[];
44
44
  };
45
+ /**
46
+ * PlantUML standard library support: `!include <C4/C4_Container>` and friends.
47
+ *
48
+ * On by default and normally not worth configuring. The two lists exist for the namespaces
49
+ * this package does not vendor — the standard library is 265 MB in full, and several of its
50
+ * namespaces carry no upstream licence — which a site can still opt into from its own
51
+ * checkout. See `assets/stdlib/LICENSES.md` for what is vendored and under what terms.
52
+ */
53
+ interface StdlibOptions {
54
+ /**
55
+ * Resolve `!include <namespace/…>` at all. `false` (or `stdlib: false`) leaves such
56
+ * diagrams to fail in the engine, exactly as they did before this feature existed.
57
+ */
58
+ enabled?: boolean;
59
+ /**
60
+ * Extra namespaces to make available, beyond the vendored ones — `['aws', 'tupadr3']`.
61
+ * Each must be found in one of {@link StdlibOptions.source}, and the build fails naming
62
+ * the namespace if it is not.
63
+ */
64
+ include?: string[];
65
+ /**
66
+ * Directories to resolve {@link StdlibOptions.include} from: the `stdlib/` directory of a
67
+ * `plantuml-stdlib` checkout, relative to the site directory or absolute. Searched in
68
+ * order, before the vendored bundles, so a namespace can also be replaced with a newer one
69
+ * by naming it in `include`.
70
+ */
71
+ source?: string | string[];
72
+ /**
73
+ * Narrow the vendored namespaces emitted into the build, for sites that care more about
74
+ * build output size than about which diagrams keep working. Dependencies of the named
75
+ * namespaces are kept regardless. Defaults to all of them; a reader only downloads the
76
+ * ones a page uses either way.
77
+ */
78
+ namespaces?: string[];
79
+ }
80
+ /** Standard library options after validation, with every default applied. */
81
+ interface ResolvedStdlibOptions {
82
+ enabled: boolean;
83
+ /** Normalized to lower case. */
84
+ include: string[];
85
+ source: string[];
86
+ /** Normalized to lower case. `null` means "every vendored namespace". */
87
+ namespaces: string[] | null;
88
+ }
45
89
  /**
46
90
  * Options accepted by the plugin in `docusaurus.config.*`.
47
91
  *
@@ -69,16 +113,27 @@ interface PlantUmlPluginOptions {
69
113
  * Override for a single fence with `zoom` or `zoom=false` in its metastring.
70
114
  */
71
115
  zoom?: boolean;
116
+ /**
117
+ * Offer a control that reveals the diagram's source and copies it to the clipboard.
118
+ * Override for a single fence with `showSource` or `showSource=false` in its metastring.
119
+ */
120
+ showSource?: boolean;
72
121
  /** Graphviz/DOT diagram support. See {@link GraphvizOptions}. */
73
122
  graphviz?: GraphvizOptions;
123
+ /**
124
+ * PlantUML standard library support. See {@link StdlibOptions}. `false` is shorthand for
125
+ * `{enabled: false}`.
126
+ */
127
+ stdlib?: StdlibOptions | false;
74
128
  /** Docusaurus plugin instance id, used when the plugin is registered more than once. */
75
129
  id?: string;
76
130
  }
77
131
  /** Plugin options after validation, with every default applied. */
78
- type ResolvedPlantUmlOptions = Required<Omit<PlantUmlPluginOptions, 'id' | 'languages' | 'graphviz'>> & {
132
+ type ResolvedPlantUmlOptions = Required<Omit<PlantUmlPluginOptions, 'id' | 'languages' | 'graphviz' | 'stdlib'>> & {
79
133
  /** Normalized to lower case; matching is case-insensitive. */
80
134
  languages: string[];
81
135
  graphviz: ResolvedGraphvizOptions;
136
+ stdlib: ResolvedStdlibOptions;
82
137
  };
83
138
  declare const DEFAULT_OPTIONS: ResolvedPlantUmlOptions;
84
139
  /** Thrown for invalid plugin options so Docusaurus fails the build with a useful message. */
@@ -93,6 +148,31 @@ declare class PlantUmlOptionsError extends Error {
93
148
  */
94
149
  declare function resolveOptions(rawOptions: unknown): ResolvedPlantUmlOptions;
95
150
 
151
+ /**
152
+ * What the Node side and the browser side of standard library support both need: the shape
153
+ * of the manifest, and the one definition of what a `<namespace/…>` include looks like.
154
+ *
155
+ * Deliberately free of `node:` imports. `stdlibBundle.ts` reads the file system and
156
+ * `stdlib.ts` resolves paths; neither may be pulled into a theme component's bundle, and
157
+ * keeping the shared half here is what stops that from happening by accident.
158
+ */
159
+ /** One namespace as the browser needs to know about it. Kept small: this ships in global data. */
160
+ interface StdlibNamespaceEntry {
161
+ /** Namespaces the engine will need as soon as this one is included. */
162
+ dependencies: string[];
163
+ /** Namespaces needed only by this one's `_examples_/` files. */
164
+ exampleDependencies: string[];
165
+ }
166
+ interface StdlibRuntimeManifest {
167
+ /**
168
+ * Changes whenever the emitted bundles change. Part of the asset directory name, so a
169
+ * refreshed standard library can never be served from a stale HTTP cache, and part of the
170
+ * render cache key, so it can never be served from a stale SVG either.
171
+ */
172
+ revision: string;
173
+ namespaces: Record<string, StdlibNamespaceEntry>;
174
+ }
175
+
96
176
  /**
97
177
  * Identifiers shared by the Node-side plugin and the browser-side theme components.
98
178
  *
@@ -111,6 +191,14 @@ interface PlantUmlGlobalData {
111
191
  assetsDir: string;
112
192
  /** Installed `@plantuml/core` version; part of the cache key. */
113
193
  coreVersion: string;
194
+ /**
195
+ * `baseUrl`-relative directory holding the standard library bundles, and what is in it.
196
+ * `null` when the standard library is switched off.
197
+ */
198
+ stdlib: {
199
+ dir: string;
200
+ manifest: StdlibRuntimeManifest;
201
+ } | null;
114
202
  }
115
203
  /** One copy pattern per runtime file, shared by the webpack and Rspack code paths. */
116
204
  interface CopyPattern {
@@ -127,7 +215,7 @@ interface CopyPattern {
127
215
  * it is `undefined` and webpack is the only bundler, so an absent value is not an error.
128
216
  */
129
217
  declare function createCopyPlugin(currentBundler: ConfigureWebpackUtils['currentBundler'] | undefined, patterns: CopyPattern[]): WebpackPluginInstance;
130
- declare function plantumlPlugin(_context: LoadContext, options?: PlantUmlPluginOptions): Plugin<void>;
218
+ declare function plantumlPlugin(context: LoadContext, options?: PlantUmlPluginOptions): Plugin<void>;
131
219
  /**
132
220
  * Docusaurus option validation hook. Runs before the plugin function so configuration
133
221
  * mistakes fail the build immediately with an actionable message.
@@ -139,4 +227,4 @@ declare function validateOptions({ options, }: {
139
227
  options: PlantUmlPluginOptions & Partial<PluginOptions>;
140
228
  }): PlantUmlPluginOptions & PluginOptions;
141
229
 
142
- export { type CacheMode, DEFAULT_OPTIONS, type DiagramTheme, PLUGIN_ID, PLUGIN_NAME, type PlantUmlGlobalData, PlantUmlOptionsError, type PlantUmlPluginOptions, type ResolvedPlantUmlOptions, createCopyPlugin, plantumlPlugin as default, resolveOptions, validateOptions };
230
+ export { type CacheMode, DEFAULT_OPTIONS, type DiagramTheme, PLUGIN_ID, PLUGIN_NAME, type PlantUmlGlobalData, PlantUmlOptionsError, type PlantUmlPluginOptions, type ResolvedPlantUmlOptions, type ResolvedStdlibOptions, type StdlibOptions, type StdlibRuntimeManifest, createCopyPlugin, plantumlPlugin as default, resolveOptions, validateOptions };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import type { ConfigureWebpackUtils, LoadContext, Plugin, PluginOptions as DocusaurusPluginOptions } from '@docusaurus/types';
2
2
  import type { WebpackPluginInstance } from 'webpack';
3
3
  import { type PlantUmlPluginOptions, type ResolvedPlantUmlOptions } from './options.js';
4
- export type { CacheMode, DiagramTheme, PlantUmlPluginOptions, ResolvedPlantUmlOptions, } from './options.js';
4
+ import { type StdlibRuntimeManifest } from './stdlib.js';
5
+ export type { CacheMode, DiagramTheme, PlantUmlPluginOptions, ResolvedPlantUmlOptions, StdlibOptions, ResolvedStdlibOptions, } from './options.js';
5
6
  export { DEFAULT_OPTIONS, PlantUmlOptionsError, resolveOptions } from './options.js';
6
7
  export { PLUGIN_ID, PLUGIN_NAME } from './constants.js';
8
+ export type { StdlibRuntimeManifest } from './stdlib.js';
7
9
  /** Shape of the data this plugin publishes to the browser through Docusaurus global data. */
8
10
  export interface PlantUmlGlobalData {
9
11
  options: ResolvedPlantUmlOptions;
@@ -11,6 +13,14 @@ export interface PlantUmlGlobalData {
11
13
  assetsDir: string;
12
14
  /** Installed `@plantuml/core` version; part of the cache key. */
13
15
  coreVersion: string;
16
+ /**
17
+ * `baseUrl`-relative directory holding the standard library bundles, and what is in it.
18
+ * `null` when the standard library is switched off.
19
+ */
20
+ stdlib: {
21
+ dir: string;
22
+ manifest: StdlibRuntimeManifest;
23
+ } | null;
14
24
  }
15
25
  /** One copy pattern per runtime file, shared by the webpack and Rspack code paths. */
16
26
  interface CopyPattern {
@@ -27,7 +37,7 @@ interface CopyPattern {
27
37
  * it is `undefined` and webpack is the only bundler, so an absent value is not an error.
28
38
  */
29
39
  export declare function createCopyPlugin(currentBundler: ConfigureWebpackUtils['currentBundler'] | undefined, patterns: CopyPattern[]): WebpackPluginInstance;
30
- export default function plantumlPlugin(_context: LoadContext, options?: PlantUmlPluginOptions): Plugin<void>;
40
+ export default function plantumlPlugin(context: LoadContext, options?: PlantUmlPluginOptions): Plugin<void>;
31
41
  /**
32
42
  * Docusaurus option validation hook. Runs before the plugin function so configuration
33
43
  * mistakes fail the build immediately with an actionable message.