@matfsw/docusaurus-plantuml-plugin 1.7.1 → 1.8.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 +68 -1
- package/README.md +99 -66
- package/dist/assets.js +38 -1
- package/dist/constants.d.ts +8 -13
- package/dist/constants.js +9 -14
- package/dist/index.cjs +68 -92
- package/dist/index.d.cts +14 -0
- package/dist/index.js +2 -13
- package/dist/options.d.ts +14 -0
- package/dist/options.js +21 -0
- package/dist/runtime/cache.d.ts +9 -1
- package/dist/runtime/cache.js +3 -2
- package/dist/runtime/renderer.d.ts +2 -0
- package/dist/runtime/renderer.js +9 -4
- package/dist/runtime/types.d.ts +12 -3
- package/dist/theme/PlantUmlDiagram/index.js +7 -1
- package/dist/theme/PlantUmlDiagram/routeMatching.d.ts +42 -0
- package/dist/theme/PlantUmlDiagram/routeMatching.js +38 -0
- package/dist/theme/PlantUmlDiagram/useDiagramLinks.d.ts +5 -2
- package/dist/theme/PlantUmlDiagram/useDiagramLinks.js +6 -0
- package/package.json +4 -4
- package/dist/enginePatch.d.ts +0 -41
- package/dist/enginePatch.js +0 -120
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
|
|
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
|
|
|
@@ -59,10 +59,9 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
59
59
|
var PLUGIN_ID = "plantuml-client";
|
|
60
60
|
var DEFAULT_PLUGIN_ID = "default";
|
|
61
61
|
var PLUGIN_NAME = `docusaurus-plugin-${PLUGIN_ID}`;
|
|
62
|
-
var
|
|
63
|
-
var MAX_DIAGRAM_SIZE = 32768;
|
|
62
|
+
var MINIMUM_CORE_VERSION = "1.2026.8";
|
|
64
63
|
function assetsDirForVersion(coreVersion) {
|
|
65
|
-
return `assets/${PLUGIN_ID}-${coreVersion}
|
|
64
|
+
return `assets/${PLUGIN_ID}-${coreVersion}`;
|
|
66
65
|
}
|
|
67
66
|
var VIZ_SCRIPT_FILENAME = "viz-global.js";
|
|
68
67
|
var PLANTUML_MODULE_FILENAME = "plantuml.js";
|
|
@@ -82,6 +81,7 @@ function locatePlantUmlCore() {
|
|
|
82
81
|
const packageDir = import_node_path.default.dirname(packageJsonPath);
|
|
83
82
|
const packageJson = JSON.parse(import_node_fs.default.readFileSync(packageJsonPath, "utf8"));
|
|
84
83
|
const version = typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
84
|
+
assertSupportedVersion(version);
|
|
85
85
|
const files = [
|
|
86
86
|
import_node_path.default.join(packageDir, VIZ_SCRIPT_FILENAME),
|
|
87
87
|
import_node_path.default.join(packageDir, PLANTUML_MODULE_FILENAME)
|
|
@@ -95,52 +95,22 @@ function assertRuntimeFile(file, version) {
|
|
|
95
95
|
`[${PLUGIN_NAME}] '@plantuml/core@${version}' does not contain '${import_node_path.default.basename(file)}', which this plugin serves as a runtime asset. This means the installed version of @plantuml/core is not one this plugin supports. Pin a supported version, or open an issue if the package has genuinely changed its layout.`
|
|
96
96
|
);
|
|
97
97
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
function countOccurrences(haystack, needle) {
|
|
111
|
-
let count = 0;
|
|
112
|
-
let index = haystack.indexOf(needle);
|
|
113
|
-
while (index !== -1) {
|
|
114
|
-
count += 1;
|
|
115
|
-
index = haystack.indexOf(needle, index + needle.length);
|
|
116
|
-
}
|
|
117
|
-
return count;
|
|
118
|
-
}
|
|
119
|
-
function assertOccurrences(source, needle, expected, coreVersion) {
|
|
120
|
-
const found = countOccurrences(source, needle);
|
|
121
|
-
if (found === expected) return;
|
|
98
|
+
function compareVersions(left, right) {
|
|
99
|
+
const parse = (value) => (value.split("-")[0] ?? "").split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
100
|
+
const a = parse(left);
|
|
101
|
+
const b = parse(right);
|
|
102
|
+
for (let index = 0; index < 3; index += 1) {
|
|
103
|
+
const difference = (a[index] ?? 0) - (b[index] ?? 0);
|
|
104
|
+
if (difference !== 0) return difference;
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
function assertSupportedVersion(version) {
|
|
109
|
+
if (compareVersions(version, MINIMUM_CORE_VERSION) >= 0) return;
|
|
122
110
|
throw new Error(
|
|
123
|
-
`[${PLUGIN_NAME}]
|
|
111
|
+
`[${PLUGIN_NAME}] '@plantuml/core@${version}' is older than the minimum this plugin supports, ${MINIMUM_CORE_VERSION}. That release added the 'maxSvgSize' render option this plugin's \`maxSvgSize\` setting relies on; on an older engine the setting would be silently ignored and diagrams larger than 4096 points would be refused. Update @plantuml/core, or remove the override or stale lockfile entry pinning it.`
|
|
124
112
|
);
|
|
125
113
|
}
|
|
126
|
-
function patchEngineSource(source, coreVersion) {
|
|
127
|
-
assertOccurrences(source, COMPARISON, COMPARISON_OCCURRENCES, coreVersion);
|
|
128
|
-
assertOccurrences(source, MESSAGE_SUFFIX, MESSAGE_SUFFIX_OCCURRENCES, coreVersion);
|
|
129
|
-
return source.split(COMPARISON).join(PATCHED_COMPARISON).split(MESSAGE_SUFFIX).join(PATCHED_MESSAGE_SUFFIX);
|
|
130
|
-
}
|
|
131
|
-
function resolvePatchedEngine(options) {
|
|
132
|
-
const { vendoredPath, coreVersion, siteDir, cacheDir } = options;
|
|
133
|
-
const generatedDir = cacheDir ?? import_node_path2.default.join(siteDir, ".docusaurus");
|
|
134
|
-
const target = import_node_path2.default.join(generatedDir, CACHE_SUBDIRECTORY, coreVersion, PLANTUML_MODULE_FILENAME);
|
|
135
|
-
const expectedSize = import_node_fs2.default.statSync(vendoredPath).size + PATCH_SIZE_DELTA;
|
|
136
|
-
if (import_node_fs2.default.existsSync(target) && import_node_fs2.default.statSync(target).size === expectedSize) return target;
|
|
137
|
-
const patched = patchEngineSource(import_node_fs2.default.readFileSync(vendoredPath, "utf8"), coreVersion);
|
|
138
|
-
import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(target), { recursive: true });
|
|
139
|
-
const temporary = `${target}.${process.pid}.tmp`;
|
|
140
|
-
import_node_fs2.default.writeFileSync(temporary, patched, "utf8");
|
|
141
|
-
import_node_fs2.default.renameSync(temporary, target);
|
|
142
|
-
return target;
|
|
143
|
-
}
|
|
144
114
|
|
|
145
115
|
// src/options.ts
|
|
146
116
|
var GRAPHVIZ_ENGINES = [
|
|
@@ -179,6 +149,7 @@ var DEFAULT_OPTIONS = {
|
|
|
179
149
|
showSourceOnError: true,
|
|
180
150
|
renderTimeoutMs: 2e4,
|
|
181
151
|
cacheMaxEntries: 50,
|
|
152
|
+
maxSvgSize: 32768,
|
|
182
153
|
zoom: true,
|
|
183
154
|
showSource: true,
|
|
184
155
|
graphviz: DEFAULT_GRAPHVIZ_OPTIONS,
|
|
@@ -373,6 +344,15 @@ function validateCacheMaxEntries(value) {
|
|
|
373
344
|
}
|
|
374
345
|
return value;
|
|
375
346
|
}
|
|
347
|
+
function validateMaxSvgSize(value) {
|
|
348
|
+
if (value === void 0) return DEFAULT_OPTIONS.maxSvgSize;
|
|
349
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
350
|
+
fail(
|
|
351
|
+
`options.maxSvgSize must be a non-negative integer (0 disables the ceiling), received ${JSON.stringify(value)}.`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
return value;
|
|
355
|
+
}
|
|
376
356
|
function resolveOptions(rawOptions) {
|
|
377
357
|
if (rawOptions === void 0 || rawOptions === null) {
|
|
378
358
|
return {
|
|
@@ -394,6 +374,7 @@ function resolveOptions(rawOptions) {
|
|
|
394
374
|
"showSourceOnError",
|
|
395
375
|
"renderTimeoutMs",
|
|
396
376
|
"cacheMaxEntries",
|
|
377
|
+
"maxSvgSize",
|
|
397
378
|
"zoom",
|
|
398
379
|
"showSource",
|
|
399
380
|
"graphviz",
|
|
@@ -446,6 +427,7 @@ function resolveOptions(rawOptions) {
|
|
|
446
427
|
),
|
|
447
428
|
renderTimeoutMs: validateRenderTimeout(rawOptions.renderTimeoutMs),
|
|
448
429
|
cacheMaxEntries: validateCacheMaxEntries(rawOptions.cacheMaxEntries),
|
|
430
|
+
maxSvgSize: validateMaxSvgSize(rawOptions.maxSvgSize),
|
|
449
431
|
zoom: validateBoolean(rawOptions.zoom, "zoom", DEFAULT_OPTIONS.zoom),
|
|
450
432
|
showSource: validateBoolean(rawOptions.showSource, "showSource", DEFAULT_OPTIONS.showSource)
|
|
451
433
|
};
|
|
@@ -453,13 +435,13 @@ function resolveOptions(rawOptions) {
|
|
|
453
435
|
|
|
454
436
|
// src/stdlib.ts
|
|
455
437
|
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
456
|
-
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
457
|
-
var import_node_path4 = __toESM(require("path"), 1);
|
|
458
|
-
|
|
459
|
-
// src/stdlibBundle.ts
|
|
460
438
|
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
461
439
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
462
440
|
|
|
441
|
+
// src/stdlibBundle.ts
|
|
442
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
443
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
444
|
+
|
|
463
445
|
// src/stdlibShared.ts
|
|
464
446
|
var STDLIB_EXAMPLES_DIRECTORY = "_examples_";
|
|
465
447
|
var STDLIB_INCLUDE_PATTERN = /^[^\S\r\n]*!include(?:sub|url|_many)?[^\S\r\n]+<([^>\r\n]+)>/gim;
|
|
@@ -492,17 +474,17 @@ function isSkippedDirectory(name) {
|
|
|
492
474
|
return name.startsWith("_") && name.endsWith("_");
|
|
493
475
|
}
|
|
494
476
|
function collectFiles(directory, namespaceRoot) {
|
|
495
|
-
const entries =
|
|
477
|
+
const entries = import_node_fs2.default.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
496
478
|
const files = [];
|
|
497
479
|
for (const entry of entries) {
|
|
498
|
-
const absolutePath =
|
|
480
|
+
const absolutePath = import_node_path2.default.join(directory, entry.name);
|
|
499
481
|
if (entry.isDirectory()) {
|
|
500
482
|
if (!isSkippedDirectory(entry.name)) files.push(...collectFiles(absolutePath, namespaceRoot));
|
|
501
483
|
continue;
|
|
502
484
|
}
|
|
503
|
-
const extension =
|
|
485
|
+
const extension = import_node_path2.default.extname(entry.name).toLowerCase();
|
|
504
486
|
if (extension !== ".puml" && extension !== ".json") continue;
|
|
505
|
-
const key =
|
|
487
|
+
const key = import_node_path2.default.relative(namespaceRoot, absolutePath).split(import_node_path2.default.sep).join("/").toLowerCase();
|
|
506
488
|
files.push({
|
|
507
489
|
key,
|
|
508
490
|
absolutePath,
|
|
@@ -513,9 +495,9 @@ function collectFiles(directory, namespaceRoot) {
|
|
|
513
495
|
return files;
|
|
514
496
|
}
|
|
515
497
|
function readNamespaceInfo(namespaceRoot) {
|
|
516
|
-
const readme =
|
|
517
|
-
if (!
|
|
518
|
-
const lines =
|
|
498
|
+
const readme = import_node_path2.default.join(namespaceRoot, "README.md");
|
|
499
|
+
if (!import_node_fs2.default.existsSync(readme)) return {};
|
|
500
|
+
const lines = import_node_fs2.default.readFileSync(readme, "utf8").split(/\r\n|\r|\n/);
|
|
519
501
|
if (lines[0]?.trim() !== "---") return {};
|
|
520
502
|
const info = {};
|
|
521
503
|
for (const line of lines.slice(1)) {
|
|
@@ -554,8 +536,8 @@ function stdlibBundleFileName(namespace) {
|
|
|
554
536
|
return `${namespace.toLowerCase()}.min.js`;
|
|
555
537
|
}
|
|
556
538
|
function buildStdlibNamespace(stdlibRoot, directoryName) {
|
|
557
|
-
const namespaceRoot =
|
|
558
|
-
if (!
|
|
539
|
+
const namespaceRoot = import_node_path2.default.join(stdlibRoot, directoryName);
|
|
540
|
+
if (!import_node_fs2.default.existsSync(namespaceRoot) || !import_node_fs2.default.statSync(namespaceRoot).isDirectory()) {
|
|
559
541
|
throw new Error(`'${namespaceRoot}' is not a plantuml-stdlib namespace directory.`);
|
|
560
542
|
}
|
|
561
543
|
const namespace = directoryName.toLowerCase();
|
|
@@ -576,7 +558,7 @@ function buildStdlibNamespace(stdlibRoot, directoryName) {
|
|
|
576
558
|
` var J = (w.PLANTUML_STDLIB_JSON[${quoted}] = w.PLANTUML_STDLIB_JSON[${quoted}] || {});`
|
|
577
559
|
];
|
|
578
560
|
for (const file of files) {
|
|
579
|
-
const text =
|
|
561
|
+
const text = import_node_fs2.default.readFileSync(file.absolutePath, "utf8");
|
|
580
562
|
const bare = withoutExtension(file.key);
|
|
581
563
|
const target = file.extension === ".puml" ? "P" : "J";
|
|
582
564
|
const value = file.extension === ".puml" ? toJsLiteral(unwrapDocument(text.split(/\r\n|\r|\n/))) : toJsonLiteral(text, file.absolutePath);
|
|
@@ -604,16 +586,16 @@ function buildStdlibNamespace(stdlibRoot, directoryName) {
|
|
|
604
586
|
|
|
605
587
|
// src/stdlib.ts
|
|
606
588
|
function vendoredDirectory(currentDir2) {
|
|
607
|
-
return
|
|
589
|
+
return import_node_path3.default.join(currentDir2, "..", "assets", "stdlib");
|
|
608
590
|
}
|
|
609
591
|
function readVendoredManifest(directory) {
|
|
610
|
-
const manifestPath =
|
|
611
|
-
if (!
|
|
592
|
+
const manifestPath = import_node_path3.default.join(directory, "manifest.json");
|
|
593
|
+
if (!import_node_fs3.default.existsSync(manifestPath)) {
|
|
612
594
|
throw new Error(
|
|
613
595
|
`[${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.`
|
|
614
596
|
);
|
|
615
597
|
}
|
|
616
|
-
return JSON.parse(
|
|
598
|
+
return JSON.parse(import_node_fs3.default.readFileSync(manifestPath, "utf8"));
|
|
617
599
|
}
|
|
618
600
|
function digest(...parts) {
|
|
619
601
|
const hash = import_node_crypto.default.createHash("sha256");
|
|
@@ -623,13 +605,13 @@ function digest(...parts) {
|
|
|
623
605
|
function directoryStamp(directory) {
|
|
624
606
|
const parts = [];
|
|
625
607
|
const walk = (current) => {
|
|
626
|
-
for (const entry of
|
|
627
|
-
const absolute =
|
|
608
|
+
for (const entry of import_node_fs3.default.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
609
|
+
const absolute = import_node_path3.default.join(current, entry.name);
|
|
628
610
|
if (entry.isDirectory()) {
|
|
629
611
|
walk(absolute);
|
|
630
612
|
continue;
|
|
631
613
|
}
|
|
632
|
-
const stats =
|
|
614
|
+
const stats = import_node_fs3.default.statSync(absolute);
|
|
633
615
|
parts.push(`${absolute}:${stats.size}:${stats.mtimeMs}`);
|
|
634
616
|
}
|
|
635
617
|
};
|
|
@@ -638,10 +620,10 @@ function directoryStamp(directory) {
|
|
|
638
620
|
}
|
|
639
621
|
function findNamespaceDirectory(sourceRoots, namespace) {
|
|
640
622
|
for (const root of sourceRoots) {
|
|
641
|
-
if (!
|
|
642
|
-
for (const entry of
|
|
623
|
+
if (!import_node_fs3.default.existsSync(root)) continue;
|
|
624
|
+
for (const entry of import_node_fs3.default.readdirSync(root, { withFileTypes: true })) {
|
|
643
625
|
if (entry.isDirectory() && entry.name.toLowerCase() === namespace) {
|
|
644
|
-
return
|
|
626
|
+
return import_node_path3.default.join(root, entry.name);
|
|
645
627
|
}
|
|
646
628
|
}
|
|
647
629
|
}
|
|
@@ -663,8 +645,8 @@ function resolveStdlibAssets({
|
|
|
663
645
|
if (namespace in namespaces) return;
|
|
664
646
|
const entry = vendoredNamespaces[namespace];
|
|
665
647
|
if (!entry) return;
|
|
666
|
-
const bundlePath =
|
|
667
|
-
if (!
|
|
648
|
+
const bundlePath = import_node_path3.default.join(directory, entry.file);
|
|
649
|
+
if (!import_node_fs3.default.existsSync(bundlePath)) {
|
|
668
650
|
throw new Error(
|
|
669
651
|
`[${PLUGIN_NAME}] The vendored standard library manifest lists '${entry.file}', but '${bundlePath}' does not exist. Reinstall the plugin.`
|
|
670
652
|
);
|
|
@@ -688,8 +670,8 @@ function resolveStdlibAssets({
|
|
|
688
670
|
}
|
|
689
671
|
}
|
|
690
672
|
for (const namespace of selected) addVendored(namespace);
|
|
691
|
-
const sourceRoots = options.source.map((entry) =>
|
|
692
|
-
const generatedDir =
|
|
673
|
+
const sourceRoots = options.source.map((entry) => import_node_path3.default.resolve(siteDir, entry));
|
|
674
|
+
const generatedDir = import_node_path3.default.join(cacheDir ?? import_node_path3.default.join(siteDir, ".docusaurus"), "plantuml-stdlib");
|
|
693
675
|
const stamps = [];
|
|
694
676
|
for (const namespace of options.include) {
|
|
695
677
|
const namespaceDir = findNamespaceDirectory(sourceRoots, namespace);
|
|
@@ -700,12 +682,12 @@ function resolveStdlibAssets({
|
|
|
700
682
|
}
|
|
701
683
|
const stamp = directoryStamp(namespaceDir);
|
|
702
684
|
stamps.push(`${namespace}:${stamp}`);
|
|
703
|
-
const cached =
|
|
704
|
-
if (!
|
|
705
|
-
const bundle = buildStdlibNamespace(
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
685
|
+
const cached = import_node_path3.default.join(generatedDir, stamp, stdlibBundleFileName(namespace));
|
|
686
|
+
if (!import_node_fs3.default.existsSync(cached)) {
|
|
687
|
+
const bundle = buildStdlibNamespace(import_node_path3.default.dirname(namespaceDir), import_node_path3.default.basename(namespaceDir));
|
|
688
|
+
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(cached), { recursive: true });
|
|
689
|
+
import_node_fs3.default.writeFileSync(cached, bundle.script, "utf8");
|
|
690
|
+
import_node_fs3.default.writeFileSync(
|
|
709
691
|
`${cached}.json`,
|
|
710
692
|
JSON.stringify({
|
|
711
693
|
dependencies: bundle.dependencies,
|
|
@@ -714,9 +696,9 @@ function resolveStdlibAssets({
|
|
|
714
696
|
"utf8"
|
|
715
697
|
);
|
|
716
698
|
}
|
|
717
|
-
const meta = JSON.parse(
|
|
699
|
+
const meta = JSON.parse(import_node_fs3.default.readFileSync(`${cached}.json`, "utf8"));
|
|
718
700
|
const previous = files.findIndex(
|
|
719
|
-
(file) =>
|
|
701
|
+
(file) => import_node_path3.default.basename(file) === stdlibBundleFileName(namespace)
|
|
720
702
|
);
|
|
721
703
|
if (previous !== -1) files.splice(previous, 1);
|
|
722
704
|
files.push(cached);
|
|
@@ -735,7 +717,7 @@ function resolveStdlibAssets({
|
|
|
735
717
|
}
|
|
736
718
|
|
|
737
719
|
// src/index.ts
|
|
738
|
-
var currentDir =
|
|
720
|
+
var currentDir = import_node_path4.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
739
721
|
function createCopyPlugin(currentBundler, patterns) {
|
|
740
722
|
if (currentBundler?.name === "rspack") {
|
|
741
723
|
const { CopyRspackPlugin } = currentBundler.instance;
|
|
@@ -762,7 +744,7 @@ function plantumlPlugin(context, options = {}) {
|
|
|
762
744
|
return {
|
|
763
745
|
name: PLUGIN_NAME,
|
|
764
746
|
getThemePath() {
|
|
765
|
-
return
|
|
747
|
+
return import_node_path4.default.join(currentDir, "theme");
|
|
766
748
|
},
|
|
767
749
|
contentLoaded({ actions }) {
|
|
768
750
|
const globalData = {
|
|
@@ -777,14 +759,8 @@ function plantumlPlugin(context, options = {}) {
|
|
|
777
759
|
if (isServer) {
|
|
778
760
|
return {};
|
|
779
761
|
}
|
|
780
|
-
const engine = resolvePatchedEngine({
|
|
781
|
-
vendoredPath: import_node_path5.default.join(core.packageDir, PLANTUML_MODULE_FILENAME),
|
|
782
|
-
coreVersion: core.version,
|
|
783
|
-
siteDir: context.siteDir,
|
|
784
|
-
cacheDir: context.generatedFilesDir
|
|
785
|
-
});
|
|
786
762
|
const patterns = core.files.map((absolutePath) => ({
|
|
787
|
-
from:
|
|
763
|
+
from: absolutePath,
|
|
788
764
|
to: `${assetsDir}/[name][ext]`,
|
|
789
765
|
// These files are already minified upstream; re-processing 8 MB is wasted work.
|
|
790
766
|
info: { minimized: true }
|
package/dist/index.d.cts
CHANGED
|
@@ -108,6 +108,20 @@ interface PlantUmlPluginOptions {
|
|
|
108
108
|
renderTimeoutMs?: number;
|
|
109
109
|
/** Maximum number of cached SVG entries kept per browsing session. */
|
|
110
110
|
cacheMaxEntries?: number;
|
|
111
|
+
/**
|
|
112
|
+
* Refuse to render a PlantUML diagram wider or taller than this many points.
|
|
113
|
+
*
|
|
114
|
+
* The engine measures the laid-out diagram before serializing it and reports anything
|
|
115
|
+
* larger through its error callback. Its own default is 8192; this plugin raises it to
|
|
116
|
+
* 32768, which is high enough that a real architecture diagram or release runbook does not
|
|
117
|
+
* hit it. `0` removes the check entirely.
|
|
118
|
+
*
|
|
119
|
+
* Raising it is not free: the resulting SVG is parsed synchronously twice on its way into
|
|
120
|
+
* the page, so a diagram of many thousands of points can stall the tab and may need
|
|
121
|
+
* `renderTimeoutMs` raised with it. Graphviz fences are bounded by
|
|
122
|
+
* `graphviz.maxSourceBytes` instead — this option does not apply to them.
|
|
123
|
+
*/
|
|
124
|
+
maxSvgSize?: number;
|
|
111
125
|
/**
|
|
112
126
|
* Let readers zoom and pan rendered diagrams, with a small control toolbar.
|
|
113
127
|
* Override for a single fence with `zoom` or `zoom=false` in its metastring.
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
|
4
4
|
import { locatePlantUmlCore } from './assets.js';
|
|
5
|
-
import { assetsDirForVersion, DEFAULT_PLUGIN_ID,
|
|
6
|
-
import { resolvePatchedEngine } from './enginePatch.js';
|
|
5
|
+
import { assetsDirForVersion, DEFAULT_PLUGIN_ID, PLUGIN_NAME } from './constants.js';
|
|
7
6
|
import { resolveOptions, } from './options.js';
|
|
8
7
|
import { resolveStdlibAssets } from './stdlib.js';
|
|
9
8
|
export { DEFAULT_OPTIONS, PlantUmlOptionsError, resolveOptions } from './options.js';
|
|
@@ -63,18 +62,8 @@ export default function plantumlPlugin(context, options = {}) {
|
|
|
63
62
|
if (isServer) {
|
|
64
63
|
return {};
|
|
65
64
|
}
|
|
66
|
-
// The engine is patched to raise its 4096-point diagram ceiling, so what is emitted is
|
|
67
|
-
// a generated file rather than the vendored one. It happens here rather than in the
|
|
68
|
-
// plugin factory so that `swizzle`, `write-translations` and the server compilation do
|
|
69
|
-
// not each pay to read and rewrite 7 MB for output they never emit.
|
|
70
|
-
const engine = resolvePatchedEngine({
|
|
71
|
-
vendoredPath: path.join(core.packageDir, PLANTUML_MODULE_FILENAME),
|
|
72
|
-
coreVersion: core.version,
|
|
73
|
-
siteDir: context.siteDir,
|
|
74
|
-
cacheDir: context.generatedFilesDir,
|
|
75
|
-
});
|
|
76
65
|
const patterns = core.files.map((absolutePath) => ({
|
|
77
|
-
from:
|
|
66
|
+
from: absolutePath,
|
|
78
67
|
to: `${assetsDir}/[name][ext]`,
|
|
79
68
|
// These files are already minified upstream; re-processing 8 MB is wasted work.
|
|
80
69
|
info: { minimized: true },
|
package/dist/options.d.ts
CHANGED
|
@@ -105,6 +105,20 @@ export interface PlantUmlPluginOptions {
|
|
|
105
105
|
renderTimeoutMs?: number;
|
|
106
106
|
/** Maximum number of cached SVG entries kept per browsing session. */
|
|
107
107
|
cacheMaxEntries?: number;
|
|
108
|
+
/**
|
|
109
|
+
* Refuse to render a PlantUML diagram wider or taller than this many points.
|
|
110
|
+
*
|
|
111
|
+
* The engine measures the laid-out diagram before serializing it and reports anything
|
|
112
|
+
* larger through its error callback. Its own default is 8192; this plugin raises it to
|
|
113
|
+
* 32768, which is high enough that a real architecture diagram or release runbook does not
|
|
114
|
+
* hit it. `0` removes the check entirely.
|
|
115
|
+
*
|
|
116
|
+
* Raising it is not free: the resulting SVG is parsed synchronously twice on its way into
|
|
117
|
+
* the page, so a diagram of many thousands of points can stall the tab and may need
|
|
118
|
+
* `renderTimeoutMs` raised with it. Graphviz fences are bounded by
|
|
119
|
+
* `graphviz.maxSourceBytes` instead — this option does not apply to them.
|
|
120
|
+
*/
|
|
121
|
+
maxSvgSize?: number;
|
|
108
122
|
/**
|
|
109
123
|
* Let readers zoom and pan rendered diagrams, with a small control toolbar.
|
|
110
124
|
* Override for a single fence with `zoom` or `zoom=false` in its metastring.
|
package/dist/options.js
CHANGED
|
@@ -42,6 +42,7 @@ export const DEFAULT_OPTIONS = {
|
|
|
42
42
|
showSourceOnError: true,
|
|
43
43
|
renderTimeoutMs: 20_000,
|
|
44
44
|
cacheMaxEntries: 50,
|
|
45
|
+
maxSvgSize: 32_768,
|
|
45
46
|
zoom: true,
|
|
46
47
|
showSource: true,
|
|
47
48
|
graphviz: DEFAULT_GRAPHVIZ_OPTIONS,
|
|
@@ -240,6 +241,24 @@ function validateCacheMaxEntries(value) {
|
|
|
240
241
|
}
|
|
241
242
|
return value;
|
|
242
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Validates `maxSvgSize`, which is a size in PlantUML points and not a byte count.
|
|
246
|
+
*
|
|
247
|
+
* `0` is accepted and means "no ceiling" — that is the engine's own convention for the
|
|
248
|
+
* option, and it is the only value that is not simply a size. Negative numbers are rejected
|
|
249
|
+
* rather than passed through: the engine treats a negative as "unspecified" and falls back to
|
|
250
|
+
* its 8192 default, so `-1` would silently mean something quite different from what it reads
|
|
251
|
+
* like.
|
|
252
|
+
*/
|
|
253
|
+
function validateMaxSvgSize(value) {
|
|
254
|
+
if (value === undefined)
|
|
255
|
+
return DEFAULT_OPTIONS.maxSvgSize;
|
|
256
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
|
|
257
|
+
fail('options.maxSvgSize must be a non-negative integer (0 disables the ceiling), ' +
|
|
258
|
+
`received ${JSON.stringify(value)}.`);
|
|
259
|
+
}
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
243
262
|
/**
|
|
244
263
|
* Validates raw plugin options and applies defaults.
|
|
245
264
|
*
|
|
@@ -267,6 +286,7 @@ export function resolveOptions(rawOptions) {
|
|
|
267
286
|
'showSourceOnError',
|
|
268
287
|
'renderTimeoutMs',
|
|
269
288
|
'cacheMaxEntries',
|
|
289
|
+
'maxSvgSize',
|
|
270
290
|
'zoom',
|
|
271
291
|
'showSource',
|
|
272
292
|
'graphviz',
|
|
@@ -309,6 +329,7 @@ export function resolveOptions(rawOptions) {
|
|
|
309
329
|
showSourceOnError: validateBoolean(rawOptions.showSourceOnError, 'showSourceOnError', DEFAULT_OPTIONS.showSourceOnError),
|
|
310
330
|
renderTimeoutMs: validateRenderTimeout(rawOptions.renderTimeoutMs),
|
|
311
331
|
cacheMaxEntries: validateCacheMaxEntries(rawOptions.cacheMaxEntries),
|
|
332
|
+
maxSvgSize: validateMaxSvgSize(rawOptions.maxSvgSize),
|
|
312
333
|
zoom: validateBoolean(rawOptions.zoom, 'zoom', DEFAULT_OPTIONS.zoom),
|
|
313
334
|
showSource: validateBoolean(rawOptions.showSource, 'showSource', DEFAULT_OPTIONS.showSource),
|
|
314
335
|
};
|
package/dist/runtime/cache.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ export interface CacheKeyInput {
|
|
|
9
9
|
* invalidates the entries whose pictures it shaped. `null` when it is switched off.
|
|
10
10
|
*/
|
|
11
11
|
stdlibRevision: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* The size ceiling the entry was rendered under.
|
|
14
|
+
*
|
|
15
|
+
* Part of the key because the option can be *lowered*, and `session` mode outlives a
|
|
16
|
+
* rebuild: without it, a site that tightened `maxSvgSize` would keep serving readers the
|
|
17
|
+
* oversized diagram their tab had already cached.
|
|
18
|
+
*/
|
|
19
|
+
maxSvgSize: number;
|
|
12
20
|
}
|
|
13
21
|
export interface DiagramCache {
|
|
14
22
|
readonly mode: CacheMode;
|
|
@@ -18,7 +26,7 @@ export interface DiagramCache {
|
|
|
18
26
|
/** Number of entries currently held. Exposed for tests and the documented size limit. */
|
|
19
27
|
size(): number;
|
|
20
28
|
}
|
|
21
|
-
export declare function computeCacheKey({ source, dark, sanitized, coreVersion, stdlibRevision, }: CacheKeyInput): string;
|
|
29
|
+
export declare function computeCacheKey({ source, dark, sanitized, coreVersion, stdlibRevision, maxSvgSize, }: CacheKeyInput): string;
|
|
22
30
|
export interface GraphvizCacheKeyInput {
|
|
23
31
|
source: string;
|
|
24
32
|
/** Graphviz layout engine; `dot` and `neato` draw different pictures from one source. */
|
package/dist/runtime/cache.js
CHANGED
|
@@ -15,12 +15,13 @@ function hash(input) {
|
|
|
15
15
|
}
|
|
16
16
|
return value.toString(36).padStart(7, '0');
|
|
17
17
|
}
|
|
18
|
-
export function computeCacheKey({ source, dark, sanitized, coreVersion, stdlibRevision, }) {
|
|
18
|
+
export function computeCacheKey({ source, dark, sanitized, coreVersion, stdlibRevision, maxSvgSize, }) {
|
|
19
19
|
const mode = dark ? 'dark' : 'light';
|
|
20
20
|
const clean = sanitized ? 'san' : 'raw';
|
|
21
21
|
// The length guards against the (astronomically unlikely) 32-bit hash collision between
|
|
22
22
|
// two different sources that happen to share every other key component.
|
|
23
|
-
return `${coreVersion}|${stdlibRevision ?? 'nostd'}|${mode}|${clean}
|
|
23
|
+
return (`${coreVersion}|${stdlibRevision ?? 'nostd'}|${mode}|${clean}|max${maxSvgSize}|` +
|
|
24
|
+
`${source.length}|${hash(source)}`);
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Cache key for a Graphviz diagram.
|
|
@@ -6,6 +6,8 @@ export interface RenderDiagramRequest {
|
|
|
6
6
|
dark: boolean;
|
|
7
7
|
sanitize: boolean;
|
|
8
8
|
timeoutMs: number;
|
|
9
|
+
/** Size ceiling in PlantUML points, passed straight to the engine. `0` disables it. */
|
|
10
|
+
maxSvgSize: number;
|
|
9
11
|
assetsBaseUrl: string;
|
|
10
12
|
coreVersion: string;
|
|
11
13
|
/** Standard library assets for this site, or `null` when the feature is switched off. */
|
package/dist/runtime/renderer.js
CHANGED
|
@@ -9,8 +9,12 @@ import { loadStdlibForSource } from './stdlibLoader.js';
|
|
|
9
9
|
* This was verified against the installed `@plantuml/core` rather than assumed: the
|
|
10
10
|
* function's arity is 4 and `{dark: true}` produces different fill colours from `{dark:
|
|
11
11
|
* false}`, which itself is byte-identical to omitting the argument.
|
|
12
|
+
*
|
|
13
|
+
* `maxSvgSize` is always passed rather than omitted when it matches a default. The engine's
|
|
14
|
+
* own default (8192) is lower than this plugin's (32768), so omitting it would quietly
|
|
15
|
+
* tighten the ceiling.
|
|
12
16
|
*/
|
|
13
|
-
function renderWithEngine(engine, source, dark) {
|
|
17
|
+
function renderWithEngine(engine, source, dark, maxSvgSize) {
|
|
14
18
|
return new Promise((resolve, reject) => {
|
|
15
19
|
let settled = false;
|
|
16
20
|
const lines = source.split(/\r\n|\r|\n/);
|
|
@@ -25,7 +29,7 @@ function renderWithEngine(engine, source, dark) {
|
|
|
25
29
|
return;
|
|
26
30
|
settled = true;
|
|
27
31
|
reject(new PlantUmlError('engine', describeEngineError(raw)));
|
|
28
|
-
}, { dark });
|
|
32
|
+
}, { dark, maxSvgSize });
|
|
29
33
|
}
|
|
30
34
|
catch (error) {
|
|
31
35
|
if (settled)
|
|
@@ -42,13 +46,14 @@ function renderWithEngine(engine, source, dark) {
|
|
|
42
46
|
* {@link enqueueRender} because the engine cannot render two diagrams at once.
|
|
43
47
|
*/
|
|
44
48
|
export async function renderDiagram(request) {
|
|
45
|
-
const { source, dark, sanitize, timeoutMs, cache, coreVersion, stdlib, signal, onPhase } = request;
|
|
49
|
+
const { source, dark, sanitize, timeoutMs, maxSvgSize, cache, coreVersion, stdlib, signal, onPhase, } = request;
|
|
46
50
|
const cacheKey = computeCacheKey({
|
|
47
51
|
source,
|
|
48
52
|
dark,
|
|
49
53
|
sanitized: sanitize,
|
|
50
54
|
coreVersion,
|
|
51
55
|
stdlibRevision: stdlib?.manifest.revision ?? null,
|
|
56
|
+
maxSvgSize,
|
|
52
57
|
});
|
|
53
58
|
const cached = cache.get(cacheKey);
|
|
54
59
|
if (cached !== undefined)
|
|
@@ -70,7 +75,7 @@ export async function renderDiagram(request) {
|
|
|
70
75
|
]);
|
|
71
76
|
onPhase?.('rendering');
|
|
72
77
|
const svg = await enqueueRender(async () => {
|
|
73
|
-
const raw = await renderWithEngine(engine, source, dark);
|
|
78
|
+
const raw = await renderWithEngine(engine, source, dark, maxSvgSize);
|
|
74
79
|
// Invalid PlantUML is reported as a successfully rendered "error picture", not through
|
|
75
80
|
// the error callback, so a rendered SVG still has to be inspected before it is trusted.
|
|
76
81
|
const diagramError = detectDiagramError(raw);
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The public surface of `@plantuml/core@1.2026.x`, verified against the installed package.
|
|
3
3
|
*
|
|
4
|
-
* `plantuml.js` exports exactly two functions. Both take an optional trailing options
|
|
5
|
-
*
|
|
6
|
-
* was established and `tests/e2e/engine-contract.spec.ts` for the regression guard.
|
|
4
|
+
* `plantuml.js` exports exactly two functions. Both take an optional trailing options object
|
|
5
|
+
* whose recognized members are `dark` and `maxSvgSize`; see `docs/architecture.md` for how
|
|
6
|
+
* this was established and `tests/e2e/engine-contract.spec.ts` for the regression guard.
|
|
7
7
|
*/
|
|
8
8
|
export interface PlantUmlCoreModule {
|
|
9
9
|
render(lines: string[], targetId: string, options?: PlantUmlRenderOptions): void;
|
|
@@ -11,6 +11,15 @@ export interface PlantUmlCoreModule {
|
|
|
11
11
|
}
|
|
12
12
|
export interface PlantUmlRenderOptions {
|
|
13
13
|
dark?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Ceiling, in PlantUML points, on the laid-out diagram's width and height.
|
|
16
|
+
*
|
|
17
|
+
* Added in `@plantuml/core@1.2026.8`; `src/assets.ts` refuses to build against anything
|
|
18
|
+
* older, because an engine that does not know the option ignores it silently. `0` disables
|
|
19
|
+
* the check; a *negative* value is treated by the engine as unspecified and falls back to
|
|
20
|
+
* its own 8192 default, which is why `options.maxSvgSize` rejects negatives outright.
|
|
21
|
+
*/
|
|
22
|
+
maxSvgSize?: number;
|
|
14
23
|
}
|
|
15
24
|
export type DiagramStatus = 'idle' | 'loading' | 'rendering' | 'ready' | 'error';
|
|
16
25
|
/** Which engine renders a given fence. */
|
|
@@ -233,7 +233,12 @@ export default function PlantUmlDiagram({ source, title, language, engine = 'pla
|
|
|
233
233
|
transparentBackground: options.graphviz.transparentBackground,
|
|
234
234
|
maxSourceBytes: options.graphviz.maxSourceBytes,
|
|
235
235
|
})
|
|
236
|
-
: renderDiagram({
|
|
236
|
+
: renderDiagram({
|
|
237
|
+
...shared,
|
|
238
|
+
dark: renderDark,
|
|
239
|
+
stdlib: config.stdlib,
|
|
240
|
+
maxSvgSize: options.maxSvgSize,
|
|
241
|
+
});
|
|
237
242
|
void pending
|
|
238
243
|
.then((svg) => commit({ status: 'ready', svg, error: null }))
|
|
239
244
|
.catch((error) => {
|
|
@@ -258,6 +263,7 @@ export default function PlantUmlDiagram({ source, title, language, engine = 'pla
|
|
|
258
263
|
config?.options.renderTimeoutMs,
|
|
259
264
|
config?.options.cache,
|
|
260
265
|
config?.options.cacheMaxEntries,
|
|
266
|
+
config?.options.maxSvgSize,
|
|
261
267
|
config?.options.graphviz.transparentBackground,
|
|
262
268
|
config?.options.graphviz.maxSourceBytes,
|
|
263
269
|
]);
|