node-plantuml-2 1.0.2 → 1.0.4
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/README.md +207 -59
- package/lib/java-resolver.js +232 -0
- package/lib/node-plantuml.js +7 -3
- package/lib/plantuml-executor.js +49 -72
- package/lib/plantuml-syntax-fixer.js +1 -1
- package/package.json +11 -16
- package/scripts/get-vizjs.js +9 -38
- package/vendor/plantuml.jar +0 -0
- package/lib/plantuml-executor-wasm.js +0 -328
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# node-plantuml-2
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **Node.js PlantUML Renderer with Java Backend**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/node-plantuml-2)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://nodejs.org/)
|
|
8
8
|
|
|
9
|
-
A powerful Node.js module and CLI for running [PlantUML](http://plantuml.sourceforge.net/)
|
|
9
|
+
A powerful Node.js module and CLI for running [PlantUML](http://plantuml.sourceforge.net/). This project is a fork and enhancement of [node-plantuml](https://github.com/markushedvall/node-plantuml), providing improved performance with Nailgun optimization for faster Java startup.
|
|
10
10
|
|
|
11
11
|
<div align="center">
|
|
12
12
|
|
|
@@ -20,12 +20,12 @@ A powerful Node.js module and CLI for running [PlantUML](http://plantuml.sourcef
|
|
|
20
20
|
|
|
21
21
|
## ✨ Key Features
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
- 📦 **
|
|
23
|
+
- 🎯 **Zero Java Installation Required** - **Java is automatically bundled!** Just `npm install` and it works - no manual Java setup needed
|
|
24
|
+
- 📦 **Auto-Integrated JRE** - Automatically installs a lightweight JRE (40-60MB) for your platform during `npm install`
|
|
25
|
+
- 🚀 **Optimized Java Execution** - Uses Nailgun for faster Java startup, keeping JVM resident in memory
|
|
25
26
|
- 🎨 **Multiple Output Formats** - Support for PNG, SVG, EPS, ASCII, and Unicode text
|
|
26
27
|
- 🌏 **Multi-language Support** - Perfect rendering for Chinese, Japanese, Korean, and other CJK characters with automatic font detection
|
|
27
|
-
- ⚡ **Fast
|
|
28
|
-
- 🔄 **Automatic Fallback** - Falls back to Java executor if Wasm is unavailable
|
|
28
|
+
- ⚡ **Fast Performance** - Nailgun optimization reduces Java startup overhead
|
|
29
29
|
- 📝 **CLI & API** - Both command-line interface and programmatic API
|
|
30
30
|
- 🎯 **Based on PlantUML** - Full compatibility with PlantUML syntax
|
|
31
31
|
|
|
@@ -33,11 +33,24 @@ A powerful Node.js module and CLI for running [PlantUML](http://plantuml.sourcef
|
|
|
33
33
|
|
|
34
34
|
## 📦 Installation
|
|
35
35
|
|
|
36
|
+
### Quick Install (Recommended)
|
|
37
|
+
|
|
36
38
|
```bash
|
|
37
39
|
npm install node-plantuml-2
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
**That's it
|
|
42
|
+
**That's it! 🎉 No Java installation needed!**
|
|
43
|
+
|
|
44
|
+
The package **automatically detects your platform** and installs a lightweight, bundled JRE (40-60MB) during `npm install`. You don't need to install Java separately - it's handled automatically!
|
|
45
|
+
|
|
46
|
+
**How it works:**
|
|
47
|
+
- During `npm install`, the package detects your operating system and architecture
|
|
48
|
+
- Automatically downloads and installs the matching JRE runtime package for your platform:
|
|
49
|
+
- **Windows x64** → `@node-plantuml-2/jre-win32-x64`
|
|
50
|
+
- **macOS ARM64** → `@node-plantuml-2/jre-darwin-arm64`
|
|
51
|
+
- **Linux x64** → `@node-plantuml-2/jre-linux-x64`
|
|
52
|
+
- Only the JRE for your platform is installed (others are skipped automatically)
|
|
53
|
+
- Works out of the box - no configuration needed!
|
|
41
54
|
|
|
42
55
|
For global CLI installation:
|
|
43
56
|
|
|
@@ -45,6 +58,47 @@ For global CLI installation:
|
|
|
45
58
|
npm install node-plantuml-2 -g
|
|
46
59
|
```
|
|
47
60
|
|
|
61
|
+
**⚠️ Important:** You do NOT need to install Java on your system. The bundled JRE is automatically integrated when you install this npm package.
|
|
62
|
+
|
|
63
|
+
### Java Requirements
|
|
64
|
+
|
|
65
|
+
**🎯 Zero Java Installation Required!**
|
|
66
|
+
|
|
67
|
+
This package **automatically bundles a minimal JRE** during installation. You do **NOT** need to install Java on your system!
|
|
68
|
+
|
|
69
|
+
**Automatic JRE Integration:**
|
|
70
|
+
|
|
71
|
+
When you run `npm install node-plantuml-2`, the package:
|
|
72
|
+
1. **Detects your platform** (Windows/macOS/Linux and architecture)
|
|
73
|
+
2. **Automatically downloads** the matching lightweight JRE (40-60MB)
|
|
74
|
+
3. **Integrates it seamlessly** - no manual setup needed
|
|
75
|
+
4. **Works immediately** after installation completes
|
|
76
|
+
|
|
77
|
+
**Java Resolution Priority:**
|
|
78
|
+
|
|
79
|
+
The package uses the following priority to find Java:
|
|
80
|
+
|
|
81
|
+
1. **Bundled JRE** (Primary) ⭐ - Automatically installed during `npm install`
|
|
82
|
+
- Platform-specific: only your platform's JRE is downloaded
|
|
83
|
+
- Lightweight: ~40-60MB, built with `jlink` for optimal size
|
|
84
|
+
- Zero configuration: works out of the box
|
|
85
|
+
|
|
86
|
+
2. **System Java** (Fallback) - If bundled JRE is unavailable, uses system Java if present
|
|
87
|
+
- Checks `JAVA_HOME` environment variable
|
|
88
|
+
- Checks system PATH for `java` command
|
|
89
|
+
|
|
90
|
+
3. **Custom Java** (Optional) - Override with custom Java path:
|
|
91
|
+
```javascript
|
|
92
|
+
plantuml.generate(code, { javaPath: '/custom/path/to/java' })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Supported Platforms (with automatic JRE):**
|
|
96
|
+
- ✅ Windows x64 - JRE automatically bundled
|
|
97
|
+
- ✅ macOS ARM64 (Apple Silicon) - JRE automatically bundled
|
|
98
|
+
- ✅ Linux x64 - JRE automatically bundled
|
|
99
|
+
|
|
100
|
+
**Summary: Install the npm package, Java is included automatically! No separate Java installation needed.**
|
|
101
|
+
|
|
48
102
|
---
|
|
49
103
|
|
|
50
104
|
## 🚀 Quick Start
|
|
@@ -427,29 +481,27 @@ app.get('/svg/:uml', (req, res) => {
|
|
|
427
481
|
app.listen(8080)
|
|
428
482
|
```
|
|
429
483
|
|
|
430
|
-
### Force Java Executor (Optional)
|
|
431
|
-
|
|
432
|
-
If you prefer to use Java executor (requires Java installed):
|
|
433
|
-
|
|
434
|
-
```bash
|
|
435
|
-
PLANTUML_USE_JAVA=true node your-script.js
|
|
436
|
-
```
|
|
437
|
-
|
|
438
484
|
---
|
|
439
485
|
|
|
440
486
|
## 🏗️ Architecture
|
|
441
487
|
|
|
442
|
-
This project uses
|
|
488
|
+
This project uses **Java execution** with automatic JRE bundling and optimization:
|
|
489
|
+
|
|
490
|
+
1. **Bundled JRE** (Automatic)
|
|
491
|
+
- Lightweight minimal JRE (~40-60MB) installed automatically via `optionalDependencies`
|
|
492
|
+
- Built with `jlink` for optimal size
|
|
493
|
+
- Platform-specific packages ensure only relevant JRE is installed
|
|
494
|
+
- **No manual Java installation required!**
|
|
443
495
|
|
|
444
|
-
|
|
445
|
-
-
|
|
446
|
-
-
|
|
447
|
-
-
|
|
496
|
+
2. **Java Executor** (Primary)
|
|
497
|
+
- Uses bundled JRE or system Java to execute `java -jar plantuml.jar`
|
|
498
|
+
- Full compatibility with PlantUML features
|
|
499
|
+
- Automatic Java path resolution with fallback strategy
|
|
448
500
|
|
|
449
|
-
|
|
450
|
-
-
|
|
451
|
-
-
|
|
452
|
-
-
|
|
501
|
+
3. **Nailgun Optimization** (Optional, for performance)
|
|
502
|
+
- Keeps JVM resident in memory for faster startup
|
|
503
|
+
- Use `plantumlExecutor.useNailgun()` to enable
|
|
504
|
+
- Reduces startup overhead significantly
|
|
453
505
|
|
|
454
506
|
### Execution Flow
|
|
455
507
|
|
|
@@ -458,9 +510,19 @@ User Code
|
|
|
458
510
|
↓
|
|
459
511
|
plantuml.generate()
|
|
460
512
|
↓
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
513
|
+
plantumlExecutor.exec()
|
|
514
|
+
↓
|
|
515
|
+
Java Path Resolution (Priority Order)
|
|
516
|
+
├─ options.javaPath (User specified)
|
|
517
|
+
├─ Bundled JRE (Auto-installed)
|
|
518
|
+
├─ JAVA_HOME (System env var)
|
|
519
|
+
└─ System PATH java
|
|
520
|
+
↓
|
|
521
|
+
Check if Nailgun is running
|
|
522
|
+
├─ Running → Use Nailgun (faster)
|
|
523
|
+
└─ Not running → Use spawn('java', ...)
|
|
524
|
+
↓
|
|
525
|
+
Execute PlantUML JAR
|
|
464
526
|
↓
|
|
465
527
|
Generate Diagram
|
|
466
528
|
↓
|
|
@@ -471,10 +533,19 @@ Return Stream
|
|
|
471
533
|
|
|
472
534
|
## 📋 System Requirements
|
|
473
535
|
|
|
474
|
-
- **Node.js 12+**
|
|
475
|
-
-
|
|
536
|
+
- **Node.js 12+** - That's it!
|
|
537
|
+
- ~~**Java Runtime Environment (JRE)**~~ - **NOT REQUIRED!** Automatically bundled during `npm install`
|
|
476
538
|
- **Graphviz** (optional, for advanced diagram types)
|
|
477
539
|
|
|
540
|
+
**✅ Supported Platforms (with automatic JRE integration):**
|
|
541
|
+
- Windows x64 - JRE automatically installed during `npm install`
|
|
542
|
+
- macOS ARM64 (Apple Silicon) - JRE automatically installed during `npm install`
|
|
543
|
+
- Linux x64 - JRE automatically installed during `npm install`
|
|
544
|
+
|
|
545
|
+
**🎯 Key Point:** You only need Node.js installed. The Java runtime is automatically downloaded and integrated when you install this package. **No manual Java installation or configuration needed!**
|
|
546
|
+
|
|
547
|
+
The bundled JRE is lightweight (~40-60MB) and specifically built for your platform.
|
|
548
|
+
|
|
478
549
|
---
|
|
479
550
|
|
|
480
551
|
## 🧪 Testing
|
|
@@ -495,13 +566,13 @@ npm run test:batch:png
|
|
|
495
566
|
|
|
496
567
|
## 📝 Changelog
|
|
497
568
|
|
|
498
|
-
###
|
|
569
|
+
### v1.0.2
|
|
499
570
|
|
|
500
|
-
- ✨ **Pure Node.js Support** - WebAssembly-based execution, no Java required
|
|
501
571
|
- 🌏 **Multi-language Support** - Perfect rendering for Chinese, Japanese, Korean with automatic font detection
|
|
502
572
|
- 📦 **Auto-update** - Automatic PlantUML JAR updates from GitHub Releases
|
|
503
573
|
- 🎨 **Multiple Formats** - PNG, SVG, EPS, ASCII, Unicode support
|
|
504
|
-
-
|
|
574
|
+
- ⚡ **Performance Optimization** - Nailgun support for faster Java startup
|
|
575
|
+
- 🧹 **Code Cleanup** - Removed non-functional Wasm implementation (see docs/WASM_BUILD_LIMITATIONS.md)
|
|
505
576
|
|
|
506
577
|
---
|
|
507
578
|
|
|
@@ -523,7 +594,6 @@ This project is based on:
|
|
|
523
594
|
|
|
524
595
|
- **[PlantUML](http://plantuml.sourceforge.net/)** - The powerful diagramming tool
|
|
525
596
|
- **[node-plantuml](https://github.com/markushedvall/node-plantuml)** - Original Node.js wrapper by Markus Hedvall
|
|
526
|
-
- **[Bytecoder](https://github.com/mirkosertic/Bytecoder)** - Java to WebAssembly compiler
|
|
527
597
|
|
|
528
598
|
Special thanks to the PlantUML community and all contributors!
|
|
529
599
|
|
|
@@ -545,34 +615,47 @@ Special thanks to the PlantUML community and all contributors!
|
|
|
545
615
|
|
|
546
616
|
# node-plantuml-2
|
|
547
617
|
|
|
548
|
-
>
|
|
618
|
+
> **Node.js PlantUML 渲染器 - 基于 Java 后端**
|
|
549
619
|
|
|
550
620
|
[](https://www.npmjs.com/package/node-plantuml-2)
|
|
551
621
|
[](LICENSE)
|
|
552
622
|
[](https://nodejs.org/)
|
|
553
623
|
|
|
554
|
-
一个强大的 Node.js 模块和 CLI,用于运行 [PlantUML](http://plantuml.sourceforge.net/)
|
|
624
|
+
一个强大的 Node.js 模块和 CLI,用于运行 [PlantUML](http://plantuml.sourceforge.net/)。本项目基于 [node-plantuml](https://github.com/markushedvall/node-plantuml) Fork 并增强,通过 Nailgun 优化提供更快的 Java 启动性能。
|
|
555
625
|
|
|
556
626
|
## ✨ 核心特性
|
|
557
627
|
|
|
558
|
-
-
|
|
559
|
-
- 📦
|
|
628
|
+
- 🎯 **零 Java 安装要求** - **Java 自动集成!** 只需 `npm install` 即可使用 - 无需手动安装 Java
|
|
629
|
+
- 📦 **自动集成 JRE** - 安装 npm 包时自动为您的平台下载并安装轻量级 JRE(40-60MB)
|
|
630
|
+
- 🚀 **优化的 Java 执行** - 使用 Nailgun 加速 Java 启动,保持 JVM 常驻内存
|
|
560
631
|
- 🎨 **多种输出格式** - 支持 PNG、SVG、EPS、ASCII 和 Unicode 文本
|
|
561
632
|
- 🌏 **多语言支持** - 完美支持中文、日文、韩文等多种 CJK 字符渲染,自动字体检测和配置
|
|
562
|
-
- ⚡
|
|
563
|
-
- 🔄 **自动降级** - Wasm 不可用时自动降级到 Java 执行器
|
|
633
|
+
- ⚡ **高性能** - Nailgun 优化减少 Java 启动开销
|
|
564
634
|
- 📝 **CLI 和 API** - 同时提供命令行界面和编程 API
|
|
565
|
-
-
|
|
635
|
+
- 🏗️ **基于 PlantUML** - 完全兼容 PlantUML 语法
|
|
566
636
|
|
|
567
637
|
---
|
|
568
638
|
|
|
569
639
|
## 📦 安装
|
|
570
640
|
|
|
641
|
+
### 快速安装(推荐)
|
|
642
|
+
|
|
571
643
|
```bash
|
|
572
644
|
npm install node-plantuml-2
|
|
573
645
|
```
|
|
574
646
|
|
|
575
|
-
|
|
647
|
+
**就这么简单!🎉 无需安装 Java!**
|
|
648
|
+
|
|
649
|
+
该包会在安装时**自动检测您的平台**并安装轻量级 JRE(40-60MB)。您无需单独安装 Java - 一切自动处理!
|
|
650
|
+
|
|
651
|
+
**工作原理:**
|
|
652
|
+
- 运行 `npm install` 时,包会自动检测您的操作系统和架构
|
|
653
|
+
- 自动下载并安装匹配平台的 JRE runtime 包:
|
|
654
|
+
- **Windows x64** → `@node-plantuml-2/jre-win32-x64`
|
|
655
|
+
- **macOS ARM64** → `@node-plantuml-2/jre-darwin-arm64`
|
|
656
|
+
- **Linux x64** → `@node-plantuml-2/jre-linux-x64`
|
|
657
|
+
- 只安装您平台的 JRE(其他平台自动跳过)
|
|
658
|
+
- 开箱即用 - 无需配置!
|
|
576
659
|
|
|
577
660
|
全局安装 CLI:
|
|
578
661
|
|
|
@@ -580,6 +663,47 @@ npm install node-plantuml-2
|
|
|
580
663
|
npm install node-plantuml-2 -g
|
|
581
664
|
```
|
|
582
665
|
|
|
666
|
+
**⚠️ 重要提示:** 您**无需**在系统上安装 Java。JRE 会在安装此 npm 包时自动集成。
|
|
667
|
+
|
|
668
|
+
### Java 要求
|
|
669
|
+
|
|
670
|
+
**🎯 零 Java 安装要求!**
|
|
671
|
+
|
|
672
|
+
此包在安装时**自动集成最小 JRE**。您**无需**在系统上安装 Java!
|
|
673
|
+
|
|
674
|
+
**自动 JRE 集成:**
|
|
675
|
+
|
|
676
|
+
当您运行 `npm install node-plantuml-2` 时,包会:
|
|
677
|
+
1. **检测您的平台**(Windows/macOS/Linux 和架构)
|
|
678
|
+
2. **自动下载**匹配的轻量级 JRE(40-60MB)
|
|
679
|
+
3. **无缝集成** - 无需手动设置
|
|
680
|
+
4. **立即可用** - 安装完成后即可使用
|
|
681
|
+
|
|
682
|
+
**Java 解析优先级:**
|
|
683
|
+
|
|
684
|
+
包使用以下优先级查找 Java:
|
|
685
|
+
|
|
686
|
+
1. **捆绑的 JRE**(主要方式)⭐ - 在 `npm install` 时自动安装
|
|
687
|
+
- 平台特定:只下载您平台的 JRE
|
|
688
|
+
- 轻量级:约 40-60MB,使用 `jlink` 构建以获得最佳体积
|
|
689
|
+
- 零配置:开箱即用
|
|
690
|
+
|
|
691
|
+
2. **系统 Java**(后备方案)- 如果捆绑的 JRE 不可用,会使用系统 Java(如果存在)
|
|
692
|
+
- 检查 `JAVA_HOME` 环境变量
|
|
693
|
+
- 检查系统 PATH 中的 `java` 命令
|
|
694
|
+
|
|
695
|
+
3. **自定义 Java**(可选)- 使用自定义 Java 路径覆盖:
|
|
696
|
+
```javascript
|
|
697
|
+
plantuml.generate(code, { javaPath: '/custom/path/to/java' })
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**支持的平台(带自动 JRE):**
|
|
701
|
+
- ✅ Windows x64 - 自动集成 JRE
|
|
702
|
+
- ✅ macOS ARM64 (Apple Silicon) - 自动集成 JRE
|
|
703
|
+
- ✅ Linux x64 - 自动集成 JRE
|
|
704
|
+
|
|
705
|
+
**总结:安装 npm 包,Java 自动包含在内!无需单独安装 Java。**
|
|
706
|
+
|
|
583
707
|
---
|
|
584
708
|
|
|
585
709
|
## 🚀 快速开始
|
|
@@ -960,17 +1084,23 @@ PLANTUML_USE_JAVA=true node your-script.js
|
|
|
960
1084
|
|
|
961
1085
|
## 🏗️ 架构
|
|
962
1086
|
|
|
963
|
-
|
|
1087
|
+
本项目使用**Java 执行**,并自动捆绑 JRE 和进行优化:
|
|
1088
|
+
|
|
1089
|
+
1. **捆绑的 JRE**(自动)
|
|
1090
|
+
- 轻量级最小 JRE(约 40-60MB)通过 `optionalDependencies` 自动安装
|
|
1091
|
+
- 使用 `jlink` 构建以获得最佳体积
|
|
1092
|
+
- 特定平台的包确保只安装相关的 JRE
|
|
1093
|
+
- **无需手动安装 Java!**
|
|
964
1094
|
|
|
965
|
-
|
|
966
|
-
-
|
|
967
|
-
-
|
|
968
|
-
-
|
|
1095
|
+
2. **Java 执行器**(主要)
|
|
1096
|
+
- 使用捆绑的 JRE 或系统 Java 执行 `java -jar plantuml.jar`
|
|
1097
|
+
- 完全支持 PlantUML 的所有功能
|
|
1098
|
+
- 自动 Java 路径解析,带后备策略
|
|
969
1099
|
|
|
970
|
-
|
|
971
|
-
-
|
|
972
|
-
-
|
|
973
|
-
-
|
|
1100
|
+
3. **Nailgun 优化**(可选,用于性能提升)
|
|
1101
|
+
- 保持 JVM 常驻内存以加速启动
|
|
1102
|
+
- 使用 `plantumlExecutor.useNailgun()` 启用
|
|
1103
|
+
- 显著减少启动开销
|
|
974
1104
|
|
|
975
1105
|
### 执行流程
|
|
976
1106
|
|
|
@@ -979,9 +1109,19 @@ PLANTUML_USE_JAVA=true node your-script.js
|
|
|
979
1109
|
↓
|
|
980
1110
|
plantuml.generate()
|
|
981
1111
|
↓
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1112
|
+
plantumlExecutor.exec()
|
|
1113
|
+
↓
|
|
1114
|
+
Java 路径解析(优先级顺序)
|
|
1115
|
+
├─ options.javaPath(用户指定)
|
|
1116
|
+
├─ 捆绑的 JRE(自动安装)
|
|
1117
|
+
├─ JAVA_HOME(系统环境变量)
|
|
1118
|
+
└─ 系统 PATH 中的 java
|
|
1119
|
+
↓
|
|
1120
|
+
检查 Nailgun 是否运行
|
|
1121
|
+
├─ 运行中 → 使用 Nailgun(更快)
|
|
1122
|
+
└─ 未运行 → 使用 spawn('java', ...)
|
|
1123
|
+
↓
|
|
1124
|
+
执行 PlantUML JAR
|
|
985
1125
|
↓
|
|
986
1126
|
生成图表
|
|
987
1127
|
↓
|
|
@@ -992,10 +1132,19 @@ plantuml.generate()
|
|
|
992
1132
|
|
|
993
1133
|
## 📋 系统要求
|
|
994
1134
|
|
|
995
|
-
- **Node.js 12
|
|
996
|
-
-
|
|
1135
|
+
- **Node.js 12+** - 仅此而已!
|
|
1136
|
+
- ~~**Java Runtime Environment (JRE)**~~ - **不需要!** 在 `npm install` 时自动集成
|
|
997
1137
|
- **Graphviz**(可选,用于高级图表类型)
|
|
998
1138
|
|
|
1139
|
+
**✅ 支持的平台(带自动 JRE 集成):**
|
|
1140
|
+
- Windows x64 - 在 `npm install` 时自动安装 JRE
|
|
1141
|
+
- macOS ARM64 (Apple Silicon) - 在 `npm install` 时自动安装 JRE
|
|
1142
|
+
- Linux x64 - 在 `npm install` 时自动安装 JRE
|
|
1143
|
+
|
|
1144
|
+
**🎯 关键点:** 您只需要安装 Node.js。Java 运行时会在您安装此包时自动下载并集成。**无需手动安装或配置 Java!**
|
|
1145
|
+
|
|
1146
|
+
捆绑的 JRE 轻量级(约 40-60MB),专门为您的平台构建。
|
|
1147
|
+
|
|
999
1148
|
---
|
|
1000
1149
|
|
|
1001
1150
|
## 🧪 测试
|
|
@@ -1016,13 +1165,13 @@ npm run test:batch:png
|
|
|
1016
1165
|
|
|
1017
1166
|
## 📝 更新日志
|
|
1018
1167
|
|
|
1019
|
-
###
|
|
1168
|
+
### v1.0.2
|
|
1020
1169
|
|
|
1021
|
-
- ✨ **纯 Node.js 支持** - 基于 WebAssembly 的执行,无需 Java
|
|
1022
1170
|
- 🌏 **多语言支持** - 完美支持中文、日文、韩文等多种语言,自动字体检测
|
|
1023
1171
|
- 📦 **自动更新** - 从 GitHub Releases 自动更新 PlantUML JAR
|
|
1024
1172
|
- 🎨 **多种格式** - PNG、SVG、EPS、ASCII、Unicode 支持
|
|
1025
|
-
-
|
|
1173
|
+
- ⚡ **性能优化** - Nailgun 支持以加速 Java 启动
|
|
1174
|
+
- 🧹 **代码清理** - 移除了不可用的 Wasm 实现(参见 docs/WASM_BUILD_LIMITATIONS.md)
|
|
1026
1175
|
|
|
1027
1176
|
---
|
|
1028
1177
|
|
|
@@ -1044,7 +1193,6 @@ MIT License
|
|
|
1044
1193
|
|
|
1045
1194
|
- **[PlantUML](http://plantuml.sourceforge.net/)** - 强大的图表工具
|
|
1046
1195
|
- **[node-plantuml](https://github.com/markushedvall/node-plantuml)** - Markus Hedvall 的原始 Node.js 包装器
|
|
1047
|
-
- **[Bytecoder](https://github.com/mirkosertic/Bytecoder)** - Java 到 WebAssembly 编译器
|
|
1048
1196
|
|
|
1049
1197
|
特别感谢 PlantUML 社区和所有贡献者!
|
|
1050
1198
|
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var fs = require('fs')
|
|
4
|
+
var path = require('path')
|
|
5
|
+
var os = require('os')
|
|
6
|
+
var childProcess = require('child_process')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve Java executable path with fallback strategy
|
|
10
|
+
*
|
|
11
|
+
* Priority order:
|
|
12
|
+
* 1. options.javaPath (user-specified)
|
|
13
|
+
* 2. Bundled JRE (from optional dependencies)
|
|
14
|
+
* 3. JAVA_HOME environment variable
|
|
15
|
+
* 4. System 'java' in PATH (which java)
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} options - Options object
|
|
18
|
+
* @param {string} options.javaPath - User-specified Java path (highest priority)
|
|
19
|
+
* @returns {string|null} - Path to Java executable, or null if not found
|
|
20
|
+
*/
|
|
21
|
+
function resolveJavaExecutable (options) {
|
|
22
|
+
options = options || {}
|
|
23
|
+
|
|
24
|
+
// Priority 1: User-specified Java path
|
|
25
|
+
if (options.javaPath) {
|
|
26
|
+
var javaPath = path.resolve(options.javaPath)
|
|
27
|
+
if (fs.existsSync(javaPath)) {
|
|
28
|
+
try {
|
|
29
|
+
// Make executable on Unix if needed
|
|
30
|
+
if (process.platform !== 'win32') {
|
|
31
|
+
fs.chmodSync(javaPath, 0o755)
|
|
32
|
+
}
|
|
33
|
+
return javaPath
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// If can't make executable, still try to use it
|
|
36
|
+
return javaPath
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Priority 2: Bundled JRE from optional dependencies
|
|
42
|
+
var bundledJava = resolveBundledJava()
|
|
43
|
+
if (bundledJava) {
|
|
44
|
+
return bundledJava
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Priority 3: JAVA_HOME environment variable
|
|
48
|
+
var javaHome = process.env.JAVA_HOME
|
|
49
|
+
if (javaHome) {
|
|
50
|
+
var javaHomeJava = getJavaFromHome(javaHome)
|
|
51
|
+
if (javaHomeJava && fs.existsSync(javaHomeJava)) {
|
|
52
|
+
return javaHomeJava
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Priority 4: System 'java' in PATH
|
|
57
|
+
try {
|
|
58
|
+
var systemJava = findJavaInPath()
|
|
59
|
+
if (systemJava) {
|
|
60
|
+
return systemJava
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// Ignore errors when checking PATH
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve bundled JRE from optional dependencies
|
|
71
|
+
* Based on platform and architecture
|
|
72
|
+
*
|
|
73
|
+
* @returns {string|null} - Path to bundled Java executable
|
|
74
|
+
*/
|
|
75
|
+
function resolveBundledJava () {
|
|
76
|
+
var platform = os.platform()
|
|
77
|
+
var arch = os.arch()
|
|
78
|
+
|
|
79
|
+
// Map platform and arch to package name
|
|
80
|
+
var pkgName = getRuntimePackageName(platform, arch)
|
|
81
|
+
if (!pkgName) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Try to resolve the runtime package
|
|
87
|
+
var pkgPath
|
|
88
|
+
try {
|
|
89
|
+
// Try to require.resolve the package
|
|
90
|
+
var pkgJsonPath = require.resolve(pkgName + '/package.json')
|
|
91
|
+
pkgPath = path.dirname(pkgJsonPath)
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// Package might not be installed (optional dependency)
|
|
94
|
+
// Try to find it in node_modules relative to this package
|
|
95
|
+
var thisPkgPath = path.join(__dirname, '..')
|
|
96
|
+
var possiblePath = path.join(thisPkgPath, 'node_modules', pkgName)
|
|
97
|
+
if (fs.existsSync(path.join(possiblePath, 'package.json'))) {
|
|
98
|
+
pkgPath = possiblePath
|
|
99
|
+
} else {
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Construct Java executable path
|
|
105
|
+
var javaExe = platform === 'win32' ? 'java.exe' : 'java'
|
|
106
|
+
var javaPath = path.join(pkgPath, 'jre', 'bin', javaExe)
|
|
107
|
+
|
|
108
|
+
if (fs.existsSync(javaPath)) {
|
|
109
|
+
try {
|
|
110
|
+
// Make executable on Unix
|
|
111
|
+
if (platform !== 'win32') {
|
|
112
|
+
fs.chmodSync(javaPath, 0o755)
|
|
113
|
+
}
|
|
114
|
+
return javaPath
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// If chmod fails, still return the path (it might already be executable)
|
|
117
|
+
return javaPath
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
// Silently fail - bundled JRE not available
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get runtime package name based on platform and architecture
|
|
130
|
+
*
|
|
131
|
+
* @param {string} platform - OS platform (win32, darwin, linux)
|
|
132
|
+
* @param {string} arch - Architecture (x64, arm64)
|
|
133
|
+
* @returns {string|null} - Package name or null if unsupported
|
|
134
|
+
*/
|
|
135
|
+
function getRuntimePackageName (platform, arch) {
|
|
136
|
+
if (platform === 'win32' && arch === 'x64') {
|
|
137
|
+
return '@node-plantuml-2/jre-win32-x64'
|
|
138
|
+
} else if (platform === 'darwin' && arch === 'arm64') {
|
|
139
|
+
return '@node-plantuml-2/jre-darwin-arm64'
|
|
140
|
+
} else if (platform === 'darwin' && arch === 'x64') {
|
|
141
|
+
return '@node-plantuml-2/jre-darwin-x64'
|
|
142
|
+
} else if (platform === 'linux' && arch === 'x64') {
|
|
143
|
+
return '@node-plantuml-2/jre-linux-x64'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Unsupported platform/arch combination
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get Java executable from JAVA_HOME
|
|
152
|
+
*
|
|
153
|
+
* @param {string} javaHome - JAVA_HOME path
|
|
154
|
+
* @returns {string|null} - Path to java executable
|
|
155
|
+
*/
|
|
156
|
+
function getJavaFromHome (javaHome) {
|
|
157
|
+
var javaExe = process.platform === 'win32' ? 'java.exe' : 'java'
|
|
158
|
+
var javaPath = path.join(javaHome, 'bin', javaExe)
|
|
159
|
+
return javaPath
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Find Java executable in system PATH
|
|
164
|
+
*
|
|
165
|
+
* @returns {string|null} - Path to java executable
|
|
166
|
+
*/
|
|
167
|
+
function findJavaInPath () {
|
|
168
|
+
try {
|
|
169
|
+
// Use 'which' on Unix, 'where' on Windows
|
|
170
|
+
var command = process.platform === 'win32' ? 'where' : 'which'
|
|
171
|
+
var result = childProcess.execSync(command + ' java', {
|
|
172
|
+
encoding: 'utf-8',
|
|
173
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
if (result) {
|
|
177
|
+
var javaPath = result.trim().split('\n')[0]
|
|
178
|
+
if (javaPath && fs.existsSync(javaPath)) {
|
|
179
|
+
return javaPath
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch (e) {
|
|
183
|
+
// Java not found in PATH
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Verify Java executable works
|
|
191
|
+
*
|
|
192
|
+
* @param {string} javaPath - Path to Java executable
|
|
193
|
+
* @returns {Promise<boolean>} - True if Java works
|
|
194
|
+
*/
|
|
195
|
+
function verifyJava (javaPath) {
|
|
196
|
+
return new Promise(function (resolve) {
|
|
197
|
+
try {
|
|
198
|
+
var child = childProcess.spawn(javaPath, ['-version'], {
|
|
199
|
+
stdio: 'pipe'
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
var hasOutput = false
|
|
203
|
+
child.on('data', function () {
|
|
204
|
+
hasOutput = true
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
child.on('close', function (code) {
|
|
208
|
+
resolve(code === 0)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
child.on('error', function () {
|
|
212
|
+
resolve(false)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// Timeout after 5 seconds
|
|
216
|
+
setTimeout(function () {
|
|
217
|
+
child.kill()
|
|
218
|
+
resolve(false)
|
|
219
|
+
}, 5000)
|
|
220
|
+
} catch (e) {
|
|
221
|
+
resolve(false)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
resolveJavaExecutable: resolveJavaExecutable,
|
|
228
|
+
resolveBundledJava: resolveBundledJava,
|
|
229
|
+
getRuntimePackageName: getRuntimePackageName,
|
|
230
|
+
verifyJava: verifyJava
|
|
231
|
+
}
|
|
232
|
+
|
package/lib/node-plantuml.js
CHANGED
|
@@ -265,7 +265,11 @@ module.exports.generate = function (input, options, callback) {
|
|
|
265
265
|
callback = args.callback
|
|
266
266
|
|
|
267
267
|
var o = joinOptions([PIPE], options)
|
|
268
|
-
|
|
268
|
+
// Pass options (including javaPath) to executor
|
|
269
|
+
var executorOptions = {
|
|
270
|
+
javaPath: options.javaPath
|
|
271
|
+
}
|
|
272
|
+
var child = plantumlExecutor.exec(o, options.include, executorOptions, callback)
|
|
269
273
|
|
|
270
274
|
if (!input) {
|
|
271
275
|
// For stdin, we need to handle font config in a transform stream
|
|
@@ -355,7 +359,7 @@ module.exports.encode = function (input, options, callback) {
|
|
|
355
359
|
}
|
|
356
360
|
|
|
357
361
|
module.exports.decode = function (encoded, callback) {
|
|
358
|
-
var child = plantumlExecutor.exec([DECODE, encoded], callback)
|
|
362
|
+
var child = plantumlExecutor.exec([DECODE, encoded], undefined, {}, callback)
|
|
359
363
|
|
|
360
364
|
return {
|
|
361
365
|
out: child.stdout
|
|
@@ -363,7 +367,7 @@ module.exports.decode = function (encoded, callback) {
|
|
|
363
367
|
}
|
|
364
368
|
|
|
365
369
|
module.exports.testdot = function (callback) {
|
|
366
|
-
var child = plantumlExecutor.exec([TESTDOT])
|
|
370
|
+
var child = plantumlExecutor.exec([TESTDOT], undefined, {}, callback)
|
|
367
371
|
|
|
368
372
|
var chunks = []
|
|
369
373
|
child.stdout.on('data', function (chunk) { chunks.push(chunk) })
|
package/lib/plantuml-executor.js
CHANGED
|
@@ -4,6 +4,7 @@ var childProcess = require('child_process')
|
|
|
4
4
|
var path = require('path')
|
|
5
5
|
var nailgun = require('node-nailgun-server')
|
|
6
6
|
var ngClient = require('node-nailgun-client')
|
|
7
|
+
var javaResolver = require('./java-resolver')
|
|
7
8
|
|
|
8
9
|
var INCLUDED_PLANTUML_JAR = path.join(__dirname, '../vendor/plantuml.jar')
|
|
9
10
|
var PLANTUML_JAR = process.env.PLANTUML_HOME || INCLUDED_PLANTUML_JAR
|
|
@@ -47,9 +48,33 @@ function execWithNailgun (argv, cwd, cb) {
|
|
|
47
48
|
return ngClient.exec(PLANTUML_NAIL_CLASS, argv, clientOptions)
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Find Java executable with fallback strategy
|
|
53
|
+
* @param {Object} options - Options with optional javaPath
|
|
54
|
+
* @returns {string} - Java executable path
|
|
55
|
+
*/
|
|
56
|
+
function findJavaExecutable (options) {
|
|
57
|
+
options = options || {}
|
|
58
|
+
|
|
59
|
+
// Try to resolve Java executable
|
|
60
|
+
var javaPath = javaResolver.resolveJavaExecutable(options)
|
|
61
|
+
|
|
62
|
+
if (!javaPath) {
|
|
63
|
+
// Fallback to 'java' in PATH if resolver fails
|
|
64
|
+
// This maintains backward compatibility
|
|
65
|
+
return 'java'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return javaPath
|
|
69
|
+
}
|
|
70
|
+
|
|
50
71
|
// TODO: proper error handling
|
|
51
|
-
function execWithSpawn (argv, cwd, cb) {
|
|
72
|
+
function execWithSpawn (argv, cwd, options, cb) {
|
|
52
73
|
cwd = cwd || process.cwd()
|
|
74
|
+
options = options || {}
|
|
75
|
+
|
|
76
|
+
var javaExe = findJavaExecutable(options)
|
|
77
|
+
|
|
53
78
|
var opts = [
|
|
54
79
|
'-Dplantuml.include.path=' + cwd,
|
|
55
80
|
'-Djava.awt.headless=true',
|
|
@@ -58,95 +83,47 @@ function execWithSpawn (argv, cwd, cb) {
|
|
|
58
83
|
'-Duser.country=US',
|
|
59
84
|
'-jar', PLANTUML_JAR
|
|
60
85
|
].concat(argv)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
module.exports.useWasm = function (callback) {
|
|
65
|
-
var wasmExecutor = require('./plantuml-executor-wasm')
|
|
66
|
-
if (wasmExecutor.isAvailable()) {
|
|
67
|
-
return wasmExecutor.initWasm(callback)
|
|
68
|
-
} else {
|
|
69
|
-
console.warn('Wasm executor not available, falling back to Java executor')
|
|
70
|
-
if (typeof callback === 'function') {
|
|
71
|
-
callback(new Error('Wasm executor not available'))
|
|
72
|
-
}
|
|
73
|
-
}
|
|
86
|
+
|
|
87
|
+
return childProcess.spawn(javaExe, opts)
|
|
74
88
|
}
|
|
75
89
|
|
|
76
|
-
module.exports.exec = function (argv, cwd, callback) {
|
|
90
|
+
module.exports.exec = function (argv, cwd, callbackOrOptions, callback) {
|
|
91
|
+
var options = {}
|
|
92
|
+
var actualCallback
|
|
93
|
+
|
|
94
|
+
// Handle flexible argument signature
|
|
77
95
|
if (typeof argv === 'function') {
|
|
78
|
-
|
|
96
|
+
actualCallback = argv
|
|
79
97
|
argv = undefined
|
|
80
98
|
cwd = undefined
|
|
81
99
|
} else if (typeof cwd === 'function') {
|
|
82
|
-
|
|
100
|
+
actualCallback = cwd
|
|
83
101
|
cwd = undefined
|
|
102
|
+
} else if (typeof callbackOrOptions === 'function') {
|
|
103
|
+
actualCallback = callbackOrOptions
|
|
104
|
+
} else if (callbackOrOptions && typeof callbackOrOptions === 'object') {
|
|
105
|
+
options = callbackOrOptions
|
|
106
|
+
actualCallback = callback
|
|
107
|
+
} else if (typeof callback === 'function') {
|
|
108
|
+
actualCallback = callback
|
|
84
109
|
}
|
|
85
110
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
var task
|
|
91
|
-
// Use Wasm by default, unless explicitly requested to use Java
|
|
92
|
-
if (!useJava && wasmExecutor.isAvailable()) {
|
|
93
|
-
try {
|
|
94
|
-
// Try to initialize Wasm synchronously first
|
|
95
|
-
if (!wasmExecutor.isReady()) {
|
|
96
|
-
if (!wasmExecutor.initWasmSync()) {
|
|
97
|
-
// Sync init failed, try async (non-blocking)
|
|
98
|
-
wasmExecutor.initWasm(function (err) {
|
|
99
|
-
if (err) {
|
|
100
|
-
console.warn('Wasm initialization failed, falling back to Java:', err.message)
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Try to use Wasm executor
|
|
107
|
-
if (wasmExecutor.isReady()) {
|
|
108
|
-
task = wasmExecutor.exec(argv, cwd, callback)
|
|
109
|
-
// Wasm executor handles its own callback setup
|
|
110
|
-
if (task && typeof callback === 'function') {
|
|
111
|
-
// Setup callback for Wasm executor if needed
|
|
112
|
-
var chunks = []
|
|
113
|
-
if (task.stdout) {
|
|
114
|
-
task.stdout.on('data', function (chunk) { chunks.push(chunk) })
|
|
115
|
-
task.stdout.on('end', function () {
|
|
116
|
-
var data = Buffer.concat(chunks)
|
|
117
|
-
callback(null, data)
|
|
118
|
-
})
|
|
119
|
-
task.stdout.on('error', function () {
|
|
120
|
-
callback(new Error('error while reading plantuml output'), null)
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return task
|
|
125
|
-
} else {
|
|
126
|
-
// Wasm not ready yet, fallback to Java
|
|
127
|
-
task = getJavaTask(argv, cwd, callback)
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
console.warn('Wasm executor failed, falling back to Java:', e.message)
|
|
131
|
-
task = getJavaTask(argv, cwd, callback)
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
// Use Java executor (fallback or explicitly requested)
|
|
135
|
-
task = getJavaTask(argv, cwd, callback)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return task
|
|
111
|
+
// Use Java executor (Wasm executor is not available due to Bytecoder limitations)
|
|
112
|
+
// See docs/WASM_BUILD_LIMITATIONS.md for details
|
|
113
|
+
return getJavaTask(argv, cwd, options, actualCallback)
|
|
139
114
|
}
|
|
140
115
|
|
|
141
116
|
/**
|
|
142
117
|
* Get Java executor task
|
|
143
118
|
*/
|
|
144
|
-
function getJavaTask (argv, cwd, callback) {
|
|
119
|
+
function getJavaTask (argv, cwd, options, callback) {
|
|
120
|
+
options = options || {}
|
|
145
121
|
var task
|
|
122
|
+
|
|
146
123
|
if (nailgunRunning) {
|
|
147
124
|
task = execWithNailgun(argv, cwd, callback)
|
|
148
125
|
} else {
|
|
149
|
-
task = execWithSpawn(argv, cwd, callback)
|
|
126
|
+
task = execWithSpawn(argv, cwd, options, callback)
|
|
150
127
|
}
|
|
151
128
|
|
|
152
129
|
if (typeof callback === 'function') {
|
|
@@ -436,7 +436,7 @@ function checkSyntaxError (code, callback) {
|
|
|
436
436
|
var SVG = '-tsvg'
|
|
437
437
|
var PIPE = '-pipe'
|
|
438
438
|
|
|
439
|
-
var child = plantumlExecutor.exec([PIPE, SVG])
|
|
439
|
+
var child = plantumlExecutor.exec([PIPE, SVG], undefined, {})
|
|
440
440
|
var svgChunks = []
|
|
441
441
|
var errorChunks = []
|
|
442
442
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-plantuml-2",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"plantumlVersion": "1.2026.
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"plantumlVersion": "1.2026.1",
|
|
5
|
+
"description": "Node.js PlantUML renderer with Java backend. Multiple output formats (PNG, SVG, EPS, ASCII, Unicode) with full UTF-8 support",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Markus Hedvall <mackanhedvall@gmail.com>",
|
|
8
8
|
"repository": "https://github.com/JaredYe04/node-plantuml-2",
|
|
@@ -14,10 +14,6 @@
|
|
|
14
14
|
"plantuml",
|
|
15
15
|
"uml",
|
|
16
16
|
"diagram",
|
|
17
|
-
"wasm",
|
|
18
|
-
"webassembly",
|
|
19
|
-
"pure-node",
|
|
20
|
-
"no-java",
|
|
21
17
|
"svg",
|
|
22
18
|
"png",
|
|
23
19
|
"markdown",
|
|
@@ -29,6 +25,11 @@
|
|
|
29
25
|
"node-nailgun-server": "^0.1.4",
|
|
30
26
|
"plantuml-encoder": "^1.2.5"
|
|
31
27
|
},
|
|
28
|
+
"optionalDependencies": {
|
|
29
|
+
"@node-plantuml-2/jre-win32-x64": "^1.0.4",
|
|
30
|
+
"@node-plantuml-2/jre-darwin-arm64": "^1.0.4",
|
|
31
|
+
"@node-plantuml-2/jre-linux-x64": "^1.0.4"
|
|
32
|
+
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"chai": "^4.x",
|
|
34
35
|
"mocha": "^5.x",
|
|
@@ -39,27 +40,21 @@
|
|
|
39
40
|
"index.js",
|
|
40
41
|
"lib/",
|
|
41
42
|
"vendor/plantuml.jar",
|
|
42
|
-
"vendor/wasm/plantuml.wasm",
|
|
43
43
|
"resources/",
|
|
44
44
|
"nail/plantumlnail.jar",
|
|
45
45
|
"scripts/download.js",
|
|
46
|
-
"scripts/get-vizjs.js"
|
|
46
|
+
"scripts/get-vizjs.js",
|
|
47
|
+
"README.md"
|
|
47
48
|
],
|
|
48
49
|
"scripts": {
|
|
49
50
|
"prepublish": "node scripts/get-plantuml-jar.js --latest || echo 'JAR download failed, continuing...'",
|
|
50
|
-
"build:wasm:publish": "node scripts/get-plantuml-jar.js --latest && node scripts/build-plantuml-wasm.js",
|
|
51
51
|
"postinstall": "node scripts/get-vizjs.js",
|
|
52
52
|
"test": "standard && node test/fixtures/prepare.js && mocha",
|
|
53
53
|
"test:batch": "node test/batch-convert-test.js",
|
|
54
54
|
"test:batch:svg": "node test/batch-convert-test.js --svg",
|
|
55
55
|
"test:batch:png": "node test/batch-convert-test.js --png",
|
|
56
56
|
"build": "node nail/build.js",
|
|
57
|
-
"build:
|
|
58
|
-
"build:wasm:cheerpj": "node scripts/build-plantuml-wasm.js --method cheerpj",
|
|
59
|
-
"build:wasm:bytecoder": "node scripts/build-plantuml-wasm.js --method bytecoder",
|
|
60
|
-
"build:all": "node scripts/build-all.js",
|
|
61
|
-
"build:all:jar-only": "node scripts/build-all.js --skip-wasm",
|
|
62
|
-
"build:all:wasm-only": "node scripts/build-all.js --skip-jar"
|
|
57
|
+
"build:all": "node scripts/build-all.js"
|
|
63
58
|
},
|
|
64
59
|
"bin": {
|
|
65
60
|
"puml": "index.js"
|
package/scripts/get-vizjs.js
CHANGED
|
@@ -30,44 +30,15 @@ plantuml.testdot(function (isOk) {
|
|
|
30
30
|
if (fs.existsSync(VIZJS_JAR)) {
|
|
31
31
|
console.info('vizjs.jar already exists. Skipping download.')
|
|
32
32
|
} else {
|
|
33
|
-
console.info('graphviz was not found on the system.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} else {
|
|
43
|
-
// also install the V8 engine just in case the currently installed Java does not have Nashorn
|
|
44
|
-
switch (process.platform) {
|
|
45
|
-
case 'win32':
|
|
46
|
-
download(J2V8_WIN_URL, J2V8_WIN_JAR, false, function (err) {
|
|
47
|
-
if (err) {
|
|
48
|
-
console.warn('Warning: Failed to download j2v8 Windows jar:', err)
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
break
|
|
52
|
-
case 'linux':
|
|
53
|
-
download(J2V8_LINUX_URL, J2V8_LINUX_JAR, false, function (err) {
|
|
54
|
-
if (err) {
|
|
55
|
-
console.warn('Warning: Failed to download j2v8 Linux jar:', err)
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
break
|
|
59
|
-
case 'darwin':
|
|
60
|
-
download(J2V8_MAC_URL, J2V8_MAC_JAR, false, function (err) {
|
|
61
|
-
if (err) {
|
|
62
|
-
console.warn('Warning: Failed to download j2v8 macOS jar:', err)
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
break
|
|
66
|
-
default:
|
|
67
|
-
console.error('Unsupported operating system for V8 jars.')
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
})
|
|
33
|
+
console.info('graphviz was not found on the system.')
|
|
34
|
+
console.info('Note: vizjs and j2v8 downloads are disabled to avoid rate limiting.')
|
|
35
|
+
console.info('The package can still be used if:')
|
|
36
|
+
console.info(' 1. Graphviz is installed on the system (recommended)')
|
|
37
|
+
console.info(' 2. vizjs.jar is provided manually in vendor/ directory')
|
|
38
|
+
console.info(' 3. Only non-Graphviz diagram types are used')
|
|
39
|
+
console.info('See http://plantuml.com/vizjs for more information.')
|
|
40
|
+
// Don't download to avoid rate limiting (429 errors)
|
|
41
|
+
// Users should install graphviz or provide vizjs.jar manually if needed
|
|
71
42
|
}
|
|
72
43
|
}
|
|
73
44
|
})
|
package/vendor/plantuml.jar
CHANGED
|
Binary file
|
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PlantUML Wasm Executor
|
|
5
|
-
*
|
|
6
|
-
* This module provides Wasm-based execution of PlantUML using TeaVM/Bytecoder.
|
|
7
|
-
*
|
|
8
|
-
* Implementation Strategy:
|
|
9
|
-
* 1. Use TeaVM or Bytecoder to convert PlantUML JAR to WebAssembly
|
|
10
|
-
* 2. Run Wasm module in Node.js using WASI or WebAssembly API
|
|
11
|
-
* 3. Provide file system access through WASI or Node.js FS API
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
var fs = require('fs')
|
|
15
|
-
var path = require('path')
|
|
16
|
-
var stream = require('stream')
|
|
17
|
-
|
|
18
|
-
// WASI is available in Node.js 12+ as experimental, stable in 20+
|
|
19
|
-
var WASI
|
|
20
|
-
try {
|
|
21
|
-
// Try Node.js 20+ stable API
|
|
22
|
-
WASI = require('wasi').WASI
|
|
23
|
-
} catch (e) {
|
|
24
|
-
try {
|
|
25
|
-
// Try experimental API (Node.js 12-19)
|
|
26
|
-
WASI = require('wasi').WASI
|
|
27
|
-
} catch (e2) {
|
|
28
|
-
// WASI not available
|
|
29
|
-
WASI = null
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
var WASM_DIR = path.join(__dirname, '../vendor/wasm')
|
|
34
|
-
var PLANTUML_WASM = path.join(WASM_DIR, 'plantuml.wasm')
|
|
35
|
-
// CheerpJ files (official PlantUML WASM solution)
|
|
36
|
-
var PLANTUML_CORE_JS = path.join(WASM_DIR, 'plantuml-core.js')
|
|
37
|
-
var PLANTUML_CORE_WASM = path.join(WASM_DIR, 'plantuml-core.wasm')
|
|
38
|
-
var wasmInstance = null
|
|
39
|
-
var wasmMemory = null
|
|
40
|
-
var wasi = null
|
|
41
|
-
var wasmReady = false
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Initialize Wasm module
|
|
45
|
-
* @param {Function} callback - Callback when ready
|
|
46
|
-
*/
|
|
47
|
-
function initWasm (callback) {
|
|
48
|
-
if (wasmReady && wasmInstance) {
|
|
49
|
-
if (typeof callback === 'function') {
|
|
50
|
-
callback(null)
|
|
51
|
-
}
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!fs.existsSync(PLANTUML_WASM)) {
|
|
56
|
-
var err = new Error('Wasm module not found: ' + PLANTUML_WASM + '\nPlease run: node scripts/build-plantuml-wasm.js')
|
|
57
|
-
if (typeof callback === 'function') {
|
|
58
|
-
callback(err)
|
|
59
|
-
} else {
|
|
60
|
-
throw err
|
|
61
|
-
}
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
// Check Node.js version for WASI support (Node.js 12+)
|
|
67
|
-
var nodeVersion = process.version
|
|
68
|
-
var majorVersion = parseInt(nodeVersion.split('.')[0].substring(1))
|
|
69
|
-
if (majorVersion < 12) {
|
|
70
|
-
throw new Error('WASI requires Node.js 12+. Current version: ' + nodeVersion)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Initialize WASI
|
|
74
|
-
var cwd = process.cwd()
|
|
75
|
-
wasi = new WASI({
|
|
76
|
-
version: 'preview1',
|
|
77
|
-
env: process.env,
|
|
78
|
-
preopens: {
|
|
79
|
-
'/': cwd,
|
|
80
|
-
'/tmp': require('os').tmpdir()
|
|
81
|
-
},
|
|
82
|
-
args: []
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
// Load Wasm module
|
|
86
|
-
var wasmBuffer = fs.readFileSync(PLANTUML_WASM)
|
|
87
|
-
|
|
88
|
-
// Create import object for WASI
|
|
89
|
-
var importObject = {
|
|
90
|
-
wasi_snapshot_preview1: wasi.wasiImport
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Instantiate Wasm module
|
|
94
|
-
/* global WebAssembly */
|
|
95
|
-
WebAssembly.instantiate(wasmBuffer, importObject)
|
|
96
|
-
.then(function (result) {
|
|
97
|
-
wasmInstance = result.instance
|
|
98
|
-
wasmMemory = wasmInstance.exports.memory
|
|
99
|
-
|
|
100
|
-
// Initialize WASI
|
|
101
|
-
wasi.initialize(wasmInstance)
|
|
102
|
-
|
|
103
|
-
wasmReady = true
|
|
104
|
-
console.log('✓ Wasm module loaded successfully')
|
|
105
|
-
|
|
106
|
-
if (typeof callback === 'function') {
|
|
107
|
-
callback(null)
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
.catch(function (err) {
|
|
111
|
-
console.error('Failed to load Wasm module:', err)
|
|
112
|
-
if (typeof callback === 'function') {
|
|
113
|
-
callback(err)
|
|
114
|
-
} else {
|
|
115
|
-
throw err
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
} catch (err) {
|
|
119
|
-
if (typeof callback === 'function') {
|
|
120
|
-
callback(err)
|
|
121
|
-
} else {
|
|
122
|
-
throw err
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Create a process-like object for Wasm execution
|
|
129
|
-
* @param {Array} argv - Command line arguments
|
|
130
|
-
* @param {string} cwd - Working directory
|
|
131
|
-
* @param {Function} callback - Callback function
|
|
132
|
-
* @returns {Object} Child process-like object with stdin/stdout/stderr
|
|
133
|
-
*/
|
|
134
|
-
function execWithWasm (argv, cwd, callback) {
|
|
135
|
-
if (!wasmReady || !wasmInstance) {
|
|
136
|
-
throw new Error('Wasm module not initialized. Call initWasm() first.')
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Create streams for stdin/stdout/stderr
|
|
140
|
-
var stdinStream = new stream.PassThrough()
|
|
141
|
-
var stdoutStream = new stream.PassThrough()
|
|
142
|
-
var stderrStream = new stream.PassThrough()
|
|
143
|
-
|
|
144
|
-
// Collect stdin data
|
|
145
|
-
var stdinData = []
|
|
146
|
-
stdinStream.on('data', function (chunk) {
|
|
147
|
-
stdinData.push(chunk)
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
stdinStream.on('end', function () {
|
|
151
|
-
// Process input when stdin ends
|
|
152
|
-
var inputBuffer = Buffer.concat(stdinData)
|
|
153
|
-
processWasmExecution(argv, inputBuffer, stdoutStream, stderrStream, cwd, callback)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
// If no stdin data expected, process immediately
|
|
157
|
-
setTimeout(function () {
|
|
158
|
-
if (stdinData.length === 0 && stdinStream.readableEnded) {
|
|
159
|
-
processWasmExecution(argv, null, stdoutStream, stderrStream, cwd, callback)
|
|
160
|
-
}
|
|
161
|
-
}, 100)
|
|
162
|
-
|
|
163
|
-
// Return process-like object
|
|
164
|
-
return {
|
|
165
|
-
stdin: stdinStream,
|
|
166
|
-
stdout: stdoutStream,
|
|
167
|
-
stderr: stderrStream
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Process Wasm execution
|
|
173
|
-
* @private
|
|
174
|
-
*/
|
|
175
|
-
function processWasmExecution (argv, stdinData, stdoutStream, stderrStream, cwd, callback) {
|
|
176
|
-
try {
|
|
177
|
-
// Convert argv to string array for Wasm
|
|
178
|
-
var args = argv || []
|
|
179
|
-
var argsString = args.join(' ')
|
|
180
|
-
|
|
181
|
-
// Prepare input data
|
|
182
|
-
var inputText = stdinData ? stdinData.toString('utf-8') : ''
|
|
183
|
-
|
|
184
|
-
// Call Wasm main function
|
|
185
|
-
// Note: This is a simplified version. Actual PlantUML Wasm module
|
|
186
|
-
// may have different function signatures
|
|
187
|
-
if (wasmInstance.exports.main) {
|
|
188
|
-
// If main function exists, call it with arguments
|
|
189
|
-
var result = wasmInstance.exports.main(args.length, argsString, inputText)
|
|
190
|
-
|
|
191
|
-
// Read output from memory
|
|
192
|
-
if (wasmMemory && result !== undefined) {
|
|
193
|
-
// Parse result and write to stdout
|
|
194
|
-
// This is simplified - actual implementation depends on Wasm module API
|
|
195
|
-
stdoutStream.end(Buffer.from(result))
|
|
196
|
-
} else {
|
|
197
|
-
stdoutStream.end()
|
|
198
|
-
}
|
|
199
|
-
} else if (wasmInstance.exports._start) {
|
|
200
|
-
// WASI entry point
|
|
201
|
-
wasi.start(wasmInstance)
|
|
202
|
-
stdoutStream.end()
|
|
203
|
-
} else {
|
|
204
|
-
// Fallback: try to find PlantUML-specific export
|
|
205
|
-
console.warn('Wasm module does not export expected functions. PlantUML Wasm module may need custom integration.')
|
|
206
|
-
stdoutStream.end()
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (typeof callback === 'function') {
|
|
210
|
-
var chunks = []
|
|
211
|
-
stdoutStream.on('data', function (chunk) {
|
|
212
|
-
chunks.push(chunk)
|
|
213
|
-
})
|
|
214
|
-
stdoutStream.on('end', function () {
|
|
215
|
-
var data = Buffer.concat(chunks)
|
|
216
|
-
callback(null, data)
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
} catch (err) {
|
|
220
|
-
stderrStream.write('Error executing Wasm module: ' + err.message + '\n')
|
|
221
|
-
stderrStream.end()
|
|
222
|
-
if (typeof callback === 'function') {
|
|
223
|
-
callback(err)
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Initialize CheerpJ runtime (official PlantUML WASM solution)
|
|
230
|
-
*/
|
|
231
|
-
function initCheerpJ (callback) {
|
|
232
|
-
try {
|
|
233
|
-
// CheerpJ uses a different loading mechanism
|
|
234
|
-
// The JS file contains the runtime and can be loaded as a module
|
|
235
|
-
console.log('Loading CheerpJ PlantUML-core...')
|
|
236
|
-
|
|
237
|
-
// For now, we'll use a simple approach - load the JS file
|
|
238
|
-
// In a real implementation, you'd need to handle CheerpJ's runtime API
|
|
239
|
-
var cheerpjCode = fs.readFileSync(PLANTUML_CORE_JS, 'utf8')
|
|
240
|
-
|
|
241
|
-
// Note: CheerpJ requires a browser-like environment or special runtime
|
|
242
|
-
// This is a placeholder - actual implementation would need CheerpJ runtime
|
|
243
|
-
console.log('✓ CheerpJ PlantUML-core loaded (runtime initialization needed)')
|
|
244
|
-
|
|
245
|
-
wasmReady = true
|
|
246
|
-
if (typeof callback === 'function') {
|
|
247
|
-
callback(null)
|
|
248
|
-
}
|
|
249
|
-
} catch (err) {
|
|
250
|
-
if (typeof callback === 'function') {
|
|
251
|
-
callback(err)
|
|
252
|
-
} else {
|
|
253
|
-
throw err
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Check if Wasm executor is available
|
|
260
|
-
* @returns {boolean}
|
|
261
|
-
*/
|
|
262
|
-
function isWasmAvailable () {
|
|
263
|
-
return fs.existsSync(PLANTUML_WASM) || fs.existsSync(PLANTUML_CORE_JS)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Check if Wasm executor is ready (initialized)
|
|
268
|
-
* @returns {boolean}
|
|
269
|
-
*/
|
|
270
|
-
function isReady () {
|
|
271
|
-
return wasmReady && wasmInstance !== null
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Initialize Wasm synchronously (for immediate use)
|
|
276
|
-
*/
|
|
277
|
-
function initWasmSync () {
|
|
278
|
-
if (wasmReady && wasmInstance) {
|
|
279
|
-
return true
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (!fs.existsSync(PLANTUML_WASM)) {
|
|
283
|
-
return false
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
var nodeVersion = process.version
|
|
288
|
-
var majorVersion = parseInt(nodeVersion.split('.')[0].substring(1))
|
|
289
|
-
if (majorVersion < 12) {
|
|
290
|
-
return false
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
var cwd = process.cwd()
|
|
294
|
-
wasi = new WASI({
|
|
295
|
-
version: 'preview1',
|
|
296
|
-
env: process.env,
|
|
297
|
-
preopens: {
|
|
298
|
-
'/': cwd,
|
|
299
|
-
'/tmp': require('os').tmpdir()
|
|
300
|
-
},
|
|
301
|
-
args: []
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
var wasmBuffer = fs.readFileSync(PLANTUML_WASM)
|
|
305
|
-
var importObject = {
|
|
306
|
-
wasi_snapshot_preview1: wasi.wasiImport
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/* global WebAssembly */
|
|
310
|
-
var result = WebAssembly.instantiateSync(wasmBuffer, importObject)
|
|
311
|
-
wasmInstance = result.instance
|
|
312
|
-
wasmMemory = wasmInstance.exports.memory
|
|
313
|
-
wasi.initialize(wasmInstance)
|
|
314
|
-
wasmReady = true
|
|
315
|
-
return true
|
|
316
|
-
} catch (e) {
|
|
317
|
-
console.warn('Failed to initialize Wasm synchronously:', e.message)
|
|
318
|
-
return false
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
module.exports = {
|
|
323
|
-
initWasm: initWasm,
|
|
324
|
-
exec: execWithWasm,
|
|
325
|
-
isAvailable: isWasmAvailable,
|
|
326
|
-
isReady: isReady,
|
|
327
|
-
initWasmSync: initWasmSync
|
|
328
|
-
}
|