node-plantuml-2 1.0.2 → 1.0.3
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 +166 -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,11 @@ A powerful Node.js module and CLI for running [PlantUML](http://plantuml.sourcef
|
|
|
20
20
|
|
|
21
21
|
## ✨ Key Features
|
|
22
22
|
|
|
23
|
-
- 🚀 **
|
|
24
|
-
- 📦 **
|
|
23
|
+
- 🚀 **Optimized Java Execution** - Uses Nailgun for faster Java startup, keeping JVM resident in memory
|
|
24
|
+
- 📦 **Easy Setup** - Just `npm install` and ensure Java is installed
|
|
25
25
|
- 🎨 **Multiple Output Formats** - Support for PNG, SVG, EPS, ASCII, and Unicode text
|
|
26
26
|
- 🌏 **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
|
|
27
|
+
- ⚡ **Fast Performance** - Nailgun optimization reduces Java startup overhead
|
|
29
28
|
- 📝 **CLI & API** - Both command-line interface and programmatic API
|
|
30
29
|
- 🎯 **Based on PlantUML** - Full compatibility with PlantUML syntax
|
|
31
30
|
|
|
@@ -33,11 +32,20 @@ A powerful Node.js module and CLI for running [PlantUML](http://plantuml.sourcef
|
|
|
33
32
|
|
|
34
33
|
## 📦 Installation
|
|
35
34
|
|
|
35
|
+
### Quick Install (Recommended)
|
|
36
|
+
|
|
36
37
|
```bash
|
|
37
38
|
npm install node-plantuml-2
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
**That's it!**
|
|
41
|
+
**That's it!** The package automatically installs a bundled, minimal JRE for your platform. **No Java installation required!** 🎉
|
|
42
|
+
|
|
43
|
+
The bundled JRE is automatically installed via platform-specific optional dependencies:
|
|
44
|
+
- **Windows x64**: `@node-plantuml-2/jre-win32-x64`
|
|
45
|
+
- **macOS ARM64**: `@node-plantuml-2/jre-darwin-arm64`
|
|
46
|
+
- **Linux x64**: `@node-plantuml-2/jre-linux-x64`
|
|
47
|
+
|
|
48
|
+
Only the JRE matching your platform will be installed, keeping the installation lightweight.
|
|
41
49
|
|
|
42
50
|
For global CLI installation:
|
|
43
51
|
|
|
@@ -45,6 +53,33 @@ For global CLI installation:
|
|
|
45
53
|
npm install node-plantuml-2 -g
|
|
46
54
|
```
|
|
47
55
|
|
|
56
|
+
### Java Requirements
|
|
57
|
+
|
|
58
|
+
This library uses Java to run PlantUML. **Java is automatically provided** via bundled JRE packages - no manual installation needed!
|
|
59
|
+
|
|
60
|
+
**How it works:**
|
|
61
|
+
|
|
62
|
+
1. **Bundled JRE** (Primary) - Automatically installed for your platform via `optionalDependencies`
|
|
63
|
+
- Lightweight minimal JRE built with `jlink`
|
|
64
|
+
- Only ~40-60MB per platform
|
|
65
|
+
- Works out of the box, no configuration needed
|
|
66
|
+
|
|
67
|
+
2. **System Java** (Fallback) - If bundled JRE is unavailable, uses system Java if present
|
|
68
|
+
- Checks `JAVA_HOME` environment variable
|
|
69
|
+
- Checks system PATH for `java` command
|
|
70
|
+
|
|
71
|
+
3. **Custom Java** (Optional) - Specify custom Java path via `options.javaPath`
|
|
72
|
+
```javascript
|
|
73
|
+
plantuml.generate(code, { javaPath: '/custom/path/to/java' })
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**No manual Java installation required!** The bundled JRE works out of the box on supported platforms:
|
|
77
|
+
- ✅ Windows x64
|
|
78
|
+
- ✅ macOS ARM64 (Apple Silicon)
|
|
79
|
+
- ✅ Linux x64
|
|
80
|
+
|
|
81
|
+
If you prefer to use system Java instead, ensure **Java Runtime Environment (JRE) 8+** is installed, and the bundled JRE will be automatically skipped.
|
|
82
|
+
|
|
48
83
|
---
|
|
49
84
|
|
|
50
85
|
## 🚀 Quick Start
|
|
@@ -427,29 +462,27 @@ app.get('/svg/:uml', (req, res) => {
|
|
|
427
462
|
app.listen(8080)
|
|
428
463
|
```
|
|
429
464
|
|
|
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
465
|
---
|
|
439
466
|
|
|
440
467
|
## 🏗️ Architecture
|
|
441
468
|
|
|
442
|
-
This project uses
|
|
469
|
+
This project uses **Java execution** with automatic JRE bundling and optimization:
|
|
470
|
+
|
|
471
|
+
1. **Bundled JRE** (Automatic)
|
|
472
|
+
- Lightweight minimal JRE (~40-60MB) installed automatically via `optionalDependencies`
|
|
473
|
+
- Built with `jlink` for optimal size
|
|
474
|
+
- Platform-specific packages ensure only relevant JRE is installed
|
|
475
|
+
- **No manual Java installation required!**
|
|
443
476
|
|
|
444
|
-
|
|
445
|
-
-
|
|
446
|
-
-
|
|
447
|
-
-
|
|
477
|
+
2. **Java Executor** (Primary)
|
|
478
|
+
- Uses bundled JRE or system Java to execute `java -jar plantuml.jar`
|
|
479
|
+
- Full compatibility with PlantUML features
|
|
480
|
+
- Automatic Java path resolution with fallback strategy
|
|
448
481
|
|
|
449
|
-
|
|
450
|
-
-
|
|
451
|
-
-
|
|
452
|
-
-
|
|
482
|
+
3. **Nailgun Optimization** (Optional, for performance)
|
|
483
|
+
- Keeps JVM resident in memory for faster startup
|
|
484
|
+
- Use `plantumlExecutor.useNailgun()` to enable
|
|
485
|
+
- Reduces startup overhead significantly
|
|
453
486
|
|
|
454
487
|
### Execution Flow
|
|
455
488
|
|
|
@@ -458,9 +491,19 @@ User Code
|
|
|
458
491
|
↓
|
|
459
492
|
plantuml.generate()
|
|
460
493
|
↓
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
494
|
+
plantumlExecutor.exec()
|
|
495
|
+
↓
|
|
496
|
+
Java Path Resolution (Priority Order)
|
|
497
|
+
├─ options.javaPath (User specified)
|
|
498
|
+
├─ Bundled JRE (Auto-installed)
|
|
499
|
+
├─ JAVA_HOME (System env var)
|
|
500
|
+
└─ System PATH java
|
|
501
|
+
↓
|
|
502
|
+
Check if Nailgun is running
|
|
503
|
+
├─ Running → Use Nailgun (faster)
|
|
504
|
+
└─ Not running → Use spawn('java', ...)
|
|
505
|
+
↓
|
|
506
|
+
Execute PlantUML JAR
|
|
464
507
|
↓
|
|
465
508
|
Generate Diagram
|
|
466
509
|
↓
|
|
@@ -471,10 +514,17 @@ Return Stream
|
|
|
471
514
|
|
|
472
515
|
## 📋 System Requirements
|
|
473
516
|
|
|
474
|
-
- **Node.js 12+**
|
|
475
|
-
- **
|
|
517
|
+
- **Node.js 12+**
|
|
518
|
+
- **Java Runtime Environment (JRE) 8+** - **Automatically provided via bundled JRE packages** (no installation needed!)
|
|
476
519
|
- **Graphviz** (optional, for advanced diagram types)
|
|
477
520
|
|
|
521
|
+
**Supported Platforms:**
|
|
522
|
+
- ✅ Windows x64
|
|
523
|
+
- ✅ macOS ARM64 (Apple Silicon)
|
|
524
|
+
- ✅ Linux x64
|
|
525
|
+
|
|
526
|
+
**Note**: Java is automatically bundled via platform-specific npm packages - **no manual Java installation required!** The bundled minimal JRE is lightweight (~40-60MB) and works out of the box.
|
|
527
|
+
|
|
478
528
|
---
|
|
479
529
|
|
|
480
530
|
## 🧪 Testing
|
|
@@ -495,13 +545,13 @@ npm run test:batch:png
|
|
|
495
545
|
|
|
496
546
|
## 📝 Changelog
|
|
497
547
|
|
|
498
|
-
###
|
|
548
|
+
### v1.0.2
|
|
499
549
|
|
|
500
|
-
- ✨ **Pure Node.js Support** - WebAssembly-based execution, no Java required
|
|
501
550
|
- 🌏 **Multi-language Support** - Perfect rendering for Chinese, Japanese, Korean with automatic font detection
|
|
502
551
|
- 📦 **Auto-update** - Automatic PlantUML JAR updates from GitHub Releases
|
|
503
552
|
- 🎨 **Multiple Formats** - PNG, SVG, EPS, ASCII, Unicode support
|
|
504
|
-
-
|
|
553
|
+
- ⚡ **Performance Optimization** - Nailgun support for faster Java startup
|
|
554
|
+
- 🧹 **Code Cleanup** - Removed non-functional Wasm implementation (see docs/WASM_BUILD_LIMITATIONS.md)
|
|
505
555
|
|
|
506
556
|
---
|
|
507
557
|
|
|
@@ -523,7 +573,6 @@ This project is based on:
|
|
|
523
573
|
|
|
524
574
|
- **[PlantUML](http://plantuml.sourceforge.net/)** - The powerful diagramming tool
|
|
525
575
|
- **[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
576
|
|
|
528
577
|
Special thanks to the PlantUML community and all contributors!
|
|
529
578
|
|
|
@@ -545,34 +594,43 @@ Special thanks to the PlantUML community and all contributors!
|
|
|
545
594
|
|
|
546
595
|
# node-plantuml-2
|
|
547
596
|
|
|
548
|
-
>
|
|
597
|
+
> **Node.js PlantUML 渲染器 - 基于 Java 后端**
|
|
549
598
|
|
|
550
599
|
[](https://www.npmjs.com/package/node-plantuml-2)
|
|
551
600
|
[](LICENSE)
|
|
552
601
|
[](https://nodejs.org/)
|
|
553
602
|
|
|
554
|
-
一个强大的 Node.js 模块和 CLI,用于运行 [PlantUML](http://plantuml.sourceforge.net/)
|
|
603
|
+
一个强大的 Node.js 模块和 CLI,用于运行 [PlantUML](http://plantuml.sourceforge.net/)。本项目基于 [node-plantuml](https://github.com/markushedvall/node-plantuml) Fork 并增强,通过 Nailgun 优化提供更快的 Java 启动性能。
|
|
555
604
|
|
|
556
605
|
## ✨ 核心特性
|
|
557
606
|
|
|
558
|
-
-
|
|
559
|
-
-
|
|
607
|
+
- 📦 **无需安装 Java** - 通过特定平台包自动安装捆绑的轻量级 JRE
|
|
608
|
+
- 🚀 **优化的 Java 执行** - 使用 Nailgun 加速 Java 启动,保持 JVM 常驻内存
|
|
609
|
+
- 🎯 **易于安装** - 只需 `npm install` - 无需手动配置 Java!
|
|
560
610
|
- 🎨 **多种输出格式** - 支持 PNG、SVG、EPS、ASCII 和 Unicode 文本
|
|
561
611
|
- 🌏 **多语言支持** - 完美支持中文、日文、韩文等多种 CJK 字符渲染,自动字体检测和配置
|
|
562
|
-
- ⚡
|
|
563
|
-
- 🔄 **自动降级** - Wasm 不可用时自动降级到 Java 执行器
|
|
612
|
+
- ⚡ **高性能** - Nailgun 优化减少 Java 启动开销
|
|
564
613
|
- 📝 **CLI 和 API** - 同时提供命令行界面和编程 API
|
|
565
|
-
-
|
|
614
|
+
- 🏗️ **基于 PlantUML** - 完全兼容 PlantUML 语法
|
|
566
615
|
|
|
567
616
|
---
|
|
568
617
|
|
|
569
618
|
## 📦 安装
|
|
570
619
|
|
|
620
|
+
### 快速安装(推荐)
|
|
621
|
+
|
|
571
622
|
```bash
|
|
572
623
|
npm install node-plantuml-2
|
|
573
624
|
```
|
|
574
625
|
|
|
575
|
-
**就这么简单!**
|
|
626
|
+
**就这么简单!** 该包会自动为您的平台安装捆绑的轻量级 JRE。**无需安装 Java!** 🎉
|
|
627
|
+
|
|
628
|
+
捆绑的 JRE 通过特定平台的可选依赖自动安装:
|
|
629
|
+
- **Windows x64**: `@node-plantuml-2/jre-win32-x64`
|
|
630
|
+
- **macOS ARM64**: `@node-plantuml-2/jre-darwin-arm64`
|
|
631
|
+
- **Linux x64**: `@node-plantuml-2/jre-linux-x64`
|
|
632
|
+
|
|
633
|
+
只会安装与您平台匹配的 JRE,保持安装轻量。
|
|
576
634
|
|
|
577
635
|
全局安装 CLI:
|
|
578
636
|
|
|
@@ -580,6 +638,33 @@ npm install node-plantuml-2
|
|
|
580
638
|
npm install node-plantuml-2 -g
|
|
581
639
|
```
|
|
582
640
|
|
|
641
|
+
### Java 要求
|
|
642
|
+
|
|
643
|
+
本库使用 Java 来运行 PlantUML。**Java 会自动提供**,通过捆绑的 JRE 包 - 无需手动安装!
|
|
644
|
+
|
|
645
|
+
**工作原理:**
|
|
646
|
+
|
|
647
|
+
1. **捆绑的 JRE**(主要方式)- 通过 `optionalDependencies` 自动为您的平台安装
|
|
648
|
+
- 使用 `jlink` 构建的轻量级最小 JRE
|
|
649
|
+
- 每个平台仅约 40-60MB
|
|
650
|
+
- 开箱即用,无需配置
|
|
651
|
+
|
|
652
|
+
2. **系统 Java**(后备方案)- 如果捆绑的 JRE 不可用,会使用系统 Java(如果存在)
|
|
653
|
+
- 检查 `JAVA_HOME` 环境变量
|
|
654
|
+
- 检查系统 PATH 中的 `java` 命令
|
|
655
|
+
|
|
656
|
+
3. **自定义 Java**(可选)- 通过 `options.javaPath` 指定自定义 Java 路径
|
|
657
|
+
```javascript
|
|
658
|
+
plantuml.generate(code, { javaPath: '/custom/path/to/java' })
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**无需手动安装 Java!** 捆绑的 JRE 在支持的平台上开箱即用:
|
|
662
|
+
- ✅ Windows x64
|
|
663
|
+
- ✅ macOS ARM64 (Apple Silicon)
|
|
664
|
+
- ✅ Linux x64
|
|
665
|
+
|
|
666
|
+
如果您更喜欢使用系统 Java,只需确保已安装 **Java Runtime Environment (JRE) 8+**,捆绑的 JRE 将自动跳过。
|
|
667
|
+
|
|
583
668
|
---
|
|
584
669
|
|
|
585
670
|
## 🚀 快速开始
|
|
@@ -960,17 +1045,23 @@ PLANTUML_USE_JAVA=true node your-script.js
|
|
|
960
1045
|
|
|
961
1046
|
## 🏗️ 架构
|
|
962
1047
|
|
|
963
|
-
|
|
1048
|
+
本项目使用**Java 执行**,并自动捆绑 JRE 和进行优化:
|
|
1049
|
+
|
|
1050
|
+
1. **捆绑的 JRE**(自动)
|
|
1051
|
+
- 轻量级最小 JRE(约 40-60MB)通过 `optionalDependencies` 自动安装
|
|
1052
|
+
- 使用 `jlink` 构建以获得最佳体积
|
|
1053
|
+
- 特定平台的包确保只安装相关的 JRE
|
|
1054
|
+
- **无需手动安装 Java!**
|
|
964
1055
|
|
|
965
|
-
|
|
966
|
-
-
|
|
967
|
-
-
|
|
968
|
-
-
|
|
1056
|
+
2. **Java 执行器**(主要)
|
|
1057
|
+
- 使用捆绑的 JRE 或系统 Java 执行 `java -jar plantuml.jar`
|
|
1058
|
+
- 完全支持 PlantUML 的所有功能
|
|
1059
|
+
- 自动 Java 路径解析,带后备策略
|
|
969
1060
|
|
|
970
|
-
|
|
971
|
-
-
|
|
972
|
-
-
|
|
973
|
-
-
|
|
1061
|
+
3. **Nailgun 优化**(可选,用于性能提升)
|
|
1062
|
+
- 保持 JVM 常驻内存以加速启动
|
|
1063
|
+
- 使用 `plantumlExecutor.useNailgun()` 启用
|
|
1064
|
+
- 显著减少启动开销
|
|
974
1065
|
|
|
975
1066
|
### 执行流程
|
|
976
1067
|
|
|
@@ -979,9 +1070,19 @@ PLANTUML_USE_JAVA=true node your-script.js
|
|
|
979
1070
|
↓
|
|
980
1071
|
plantuml.generate()
|
|
981
1072
|
↓
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1073
|
+
plantumlExecutor.exec()
|
|
1074
|
+
↓
|
|
1075
|
+
Java 路径解析(优先级顺序)
|
|
1076
|
+
├─ options.javaPath(用户指定)
|
|
1077
|
+
├─ 捆绑的 JRE(自动安装)
|
|
1078
|
+
├─ JAVA_HOME(系统环境变量)
|
|
1079
|
+
└─ 系统 PATH 中的 java
|
|
1080
|
+
↓
|
|
1081
|
+
检查 Nailgun 是否运行
|
|
1082
|
+
├─ 运行中 → 使用 Nailgun(更快)
|
|
1083
|
+
└─ 未运行 → 使用 spawn('java', ...)
|
|
1084
|
+
↓
|
|
1085
|
+
执行 PlantUML JAR
|
|
985
1086
|
↓
|
|
986
1087
|
生成图表
|
|
987
1088
|
↓
|
|
@@ -992,10 +1093,17 @@ plantuml.generate()
|
|
|
992
1093
|
|
|
993
1094
|
## 📋 系统要求
|
|
994
1095
|
|
|
995
|
-
- **Node.js 12
|
|
996
|
-
-
|
|
1096
|
+
- **Node.js 12+**
|
|
1097
|
+
- **Java Runtime Environment (JRE) 8+** - **通过捆绑的 JRE 包自动提供**(无需安装!)
|
|
997
1098
|
- **Graphviz**(可选,用于高级图表类型)
|
|
998
1099
|
|
|
1100
|
+
**支持的平台:**
|
|
1101
|
+
- ✅ Windows x64
|
|
1102
|
+
- ✅ macOS ARM64 (Apple Silicon)
|
|
1103
|
+
- ✅ Linux x64
|
|
1104
|
+
|
|
1105
|
+
**注意**:Java 通过特定平台的 npm 包自动捆绑 - **无需手动安装 Java!** 捆绑的轻量级 JRE 体积小(约 40-60MB),开箱即用。
|
|
1106
|
+
|
|
999
1107
|
---
|
|
1000
1108
|
|
|
1001
1109
|
## 🧪 测试
|
|
@@ -1016,13 +1124,13 @@ npm run test:batch:png
|
|
|
1016
1124
|
|
|
1017
1125
|
## 📝 更新日志
|
|
1018
1126
|
|
|
1019
|
-
###
|
|
1127
|
+
### v1.0.2
|
|
1020
1128
|
|
|
1021
|
-
- ✨ **纯 Node.js 支持** - 基于 WebAssembly 的执行,无需 Java
|
|
1022
1129
|
- 🌏 **多语言支持** - 完美支持中文、日文、韩文等多种语言,自动字体检测
|
|
1023
1130
|
- 📦 **自动更新** - 从 GitHub Releases 自动更新 PlantUML JAR
|
|
1024
1131
|
- 🎨 **多种格式** - PNG、SVG、EPS、ASCII、Unicode 支持
|
|
1025
|
-
-
|
|
1132
|
+
- ⚡ **性能优化** - Nailgun 支持以加速 Java 启动
|
|
1133
|
+
- 🧹 **代码清理** - 移除了不可用的 Wasm 实现(参见 docs/WASM_BUILD_LIMITATIONS.md)
|
|
1026
1134
|
|
|
1027
1135
|
---
|
|
1028
1136
|
|
|
@@ -1044,7 +1152,6 @@ MIT License
|
|
|
1044
1152
|
|
|
1045
1153
|
- **[PlantUML](http://plantuml.sourceforge.net/)** - 强大的图表工具
|
|
1046
1154
|
- **[node-plantuml](https://github.com/markushedvall/node-plantuml)** - Markus Hedvall 的原始 Node.js 包装器
|
|
1047
|
-
- **[Bytecoder](https://github.com/mirkosertic/Bytecoder)** - Java 到 WebAssembly 编译器
|
|
1048
1155
|
|
|
1049
1156
|
特别感谢 PlantUML 社区和所有贡献者!
|
|
1050
1157
|
|
|
@@ -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.3",
|
|
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.3",
|
|
30
|
+
"@node-plantuml-2/jre-darwin-arm64": "^1.0.3",
|
|
31
|
+
"@node-plantuml-2/jre-linux-x64": "^1.0.3"
|
|
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
|
-
}
|