@matfsw/docusaurus-plantuml-plugin 1.2.0 → 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.
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",
@@ -127,7 +133,8 @@ var DEFAULT_OPTIONS = {
127
133
  cacheMaxEntries: 50,
128
134
  zoom: true,
129
135
  showSource: true,
130
- graphviz: DEFAULT_GRAPHVIZ_OPTIONS
136
+ graphviz: DEFAULT_GRAPHVIZ_OPTIONS,
137
+ stdlib: DEFAULT_STDLIB_OPTIONS
131
138
  };
132
139
  var CACHE_MODES = ["none", "memory", "session"];
133
140
  var THEMES = ["auto", "light", "dark"];
@@ -234,6 +241,68 @@ function validateGraphviz(value) {
234
241
  )
235
242
  };
236
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
+ }
237
306
  function validateRenderTimeout(value) {
238
307
  if (value === void 0) return DEFAULT_OPTIONS.renderTimeoutMs;
239
308
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -261,7 +330,8 @@ function resolveOptions(rawOptions) {
261
330
  return {
262
331
  ...DEFAULT_OPTIONS,
263
332
  languages: [...DEFAULT_OPTIONS.languages],
264
- 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: [] }
265
335
  };
266
336
  }
267
337
  if (!isPlainObject(rawOptions)) {
@@ -279,6 +349,7 @@ function resolveOptions(rawOptions) {
279
349
  "zoom",
280
350
  "showSource",
281
351
  "graphviz",
352
+ "stdlib",
282
353
  // Docusaurus injects `id` for multi-instance plugins.
283
354
  "id"
284
355
  ]);
@@ -299,6 +370,7 @@ function resolveOptions(rawOptions) {
299
370
  }
300
371
  const languages = validateLanguages(rawOptions.languages, "languages", DEFAULT_OPTIONS.languages);
301
372
  const graphviz = validateGraphviz(rawOptions.graphviz);
373
+ const stdlib = validateStdlib(rawOptions.stdlib);
302
374
  if (graphviz.enabled) {
303
375
  const claimedTwice = graphviz.languages.filter((entry) => languages.includes(entry));
304
376
  if (claimedTwice.length > 0) {
@@ -310,6 +382,7 @@ function resolveOptions(rawOptions) {
310
382
  return {
311
383
  languages,
312
384
  graphviz,
385
+ stdlib,
313
386
  theme: theme ?? DEFAULT_OPTIONS.theme,
314
387
  lazy: validateBoolean(rawOptions.lazy, "lazy", DEFAULT_OPTIONS.lazy),
315
388
  cache: cache ?? DEFAULT_OPTIONS.cache,
@@ -330,8 +403,291 @@ function resolveOptions(rawOptions) {
330
403
  };
331
404
  }
332
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
+ }
686
+ };
687
+ }
688
+
333
689
  // src/index.ts
334
- 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));
335
691
  function createCopyPlugin(currentBundler, patterns) {
336
692
  if (currentBundler?.name === "rspack") {
337
693
  const { CopyRspackPlugin } = currentBundler.instance;
@@ -344,20 +700,28 @@ function createCopyPlugin(currentBundler, patterns) {
344
700
  }
345
701
  return new import_copy_webpack_plugin.default({ patterns });
346
702
  }
347
- function plantumlPlugin(_context, options = {}) {
703
+ function plantumlPlugin(context, options = {}) {
348
704
  const resolved = resolveOptions(options);
349
705
  const core = locatePlantUmlCore();
350
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;
351
714
  return {
352
715
  name: PLUGIN_NAME,
353
716
  getThemePath() {
354
- return import_node_path2.default.join(currentDir, "theme");
717
+ return import_node_path4.default.join(currentDir, "theme");
355
718
  },
356
719
  contentLoaded({ actions }) {
357
720
  const globalData = {
358
721
  options: resolved,
359
722
  assetsDir,
360
- coreVersion: core.version
723
+ coreVersion: core.version,
724
+ stdlib: stdlib && stdlibDir ? { dir: stdlibDir, manifest: stdlib.manifest } : null
361
725
  };
362
726
  actions.setGlobalData(globalData);
363
727
  },
@@ -371,6 +735,15 @@ function plantumlPlugin(_context, options = {}) {
371
735
  // These files are already minified upstream; re-processing 8 MB is wasted work.
372
736
  info: { minimized: true }
373
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
+ }
374
747
  return { plugins: [createCopyPlugin(utils?.currentBundler, patterns)] };
375
748
  }
376
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
  *
@@ -76,14 +120,20 @@ interface PlantUmlPluginOptions {
76
120
  showSource?: boolean;
77
121
  /** Graphviz/DOT diagram support. See {@link GraphvizOptions}. */
78
122
  graphviz?: GraphvizOptions;
123
+ /**
124
+ * PlantUML standard library support. See {@link StdlibOptions}. `false` is shorthand for
125
+ * `{enabled: false}`.
126
+ */
127
+ stdlib?: StdlibOptions | false;
79
128
  /** Docusaurus plugin instance id, used when the plugin is registered more than once. */
80
129
  id?: string;
81
130
  }
82
131
  /** Plugin options after validation, with every default applied. */
83
- type ResolvedPlantUmlOptions = Required<Omit<PlantUmlPluginOptions, 'id' | 'languages' | 'graphviz'>> & {
132
+ type ResolvedPlantUmlOptions = Required<Omit<PlantUmlPluginOptions, 'id' | 'languages' | 'graphviz' | 'stdlib'>> & {
84
133
  /** Normalized to lower case; matching is case-insensitive. */
85
134
  languages: string[];
86
135
  graphviz: ResolvedGraphvizOptions;
136
+ stdlib: ResolvedStdlibOptions;
87
137
  };
88
138
  declare const DEFAULT_OPTIONS: ResolvedPlantUmlOptions;
89
139
  /** Thrown for invalid plugin options so Docusaurus fails the build with a useful message. */
@@ -98,6 +148,31 @@ declare class PlantUmlOptionsError extends Error {
98
148
  */
99
149
  declare function resolveOptions(rawOptions: unknown): ResolvedPlantUmlOptions;
100
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
+
101
176
  /**
102
177
  * Identifiers shared by the Node-side plugin and the browser-side theme components.
103
178
  *
@@ -116,6 +191,14 @@ interface PlantUmlGlobalData {
116
191
  assetsDir: string;
117
192
  /** Installed `@plantuml/core` version; part of the cache key. */
118
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;
119
202
  }
120
203
  /** One copy pattern per runtime file, shared by the webpack and Rspack code paths. */
121
204
  interface CopyPattern {
@@ -132,7 +215,7 @@ interface CopyPattern {
132
215
  * it is `undefined` and webpack is the only bundler, so an absent value is not an error.
133
216
  */
134
217
  declare function createCopyPlugin(currentBundler: ConfigureWebpackUtils['currentBundler'] | undefined, patterns: CopyPattern[]): WebpackPluginInstance;
135
- declare function plantumlPlugin(_context: LoadContext, options?: PlantUmlPluginOptions): Plugin<void>;
218
+ declare function plantumlPlugin(context: LoadContext, options?: PlantUmlPluginOptions): Plugin<void>;
136
219
  /**
137
220
  * Docusaurus option validation hook. Runs before the plugin function so configuration
138
221
  * mistakes fail the build immediately with an actionable message.
@@ -144,4 +227,4 @@ declare function validateOptions({ options, }: {
144
227
  options: PlantUmlPluginOptions & Partial<PluginOptions>;
145
228
  }): PlantUmlPluginOptions & PluginOptions;
146
229
 
147
- 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.