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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # node-plantuml-2
2
2
 
3
- > **Pure Node.js PlantUML Renderer - No Java Required!**
3
+ > **Node.js PlantUML Renderer with Java Backend**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/node-plantuml-2)](https://www.npmjs.com/package/node-plantuml-2)
6
6
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
7
  [![Node.js](https://img.shields.io/badge/node-%3E%3D12-green.svg)](https://nodejs.org/)
8
8
 
9
- A powerful Node.js module and CLI for running [PlantUML](http://plantuml.sourceforge.net/) with **pure Node.js support**. This project is a fork and enhancement of [node-plantuml](https://github.com/markushedvall/node-plantuml), featuring WebAssembly-based execution that eliminates the need for Java runtime.
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
- - 🚀 **Pure Node.js Environment** - No Java installation required! Uses pre-compiled WebAssembly module
24
- - 📦 **Zero Configuration** - Just `npm install` and start using
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 Startup** - WebAssembly execution is faster than JVM
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!** No Java, no configuration, no build steps required.
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 a **hybrid execution model**:
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
- 1. **Primary: WebAssembly Executor** (Pure Node.js)
445
- - Pre-compiled Wasm module included in npm package
446
- - Fast startup, low memory footprint
447
- - No Java required
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
- 2. **Fallback: Java Executor** (Optional)
450
- - Automatic fallback if Wasm unavailable
451
- - Requires Java runtime
452
- - Full compatibility with original node-plantuml
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
- Check Wasm Availability
462
- ├─ Available → Use Wasm Executor ✅ (Pure Node)
463
- └─ Unavailable Use Java Executor (Fallback)
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+** (recommended 20+ for stable WASI support)
475
- - **No Java required** (Wasm executor works out of the box)
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
- ### v0.9.0
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
- - 🔄 **Smart Fallback** - Automatic fallback to Java if Wasm unavailable
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
- > **纯 Node.js PlantUML 渲染器 - 无需 Java!**
597
+ > **Node.js PlantUML 渲染器 - 基于 Java 后端**
549
598
 
550
599
  [![npm version](https://img.shields.io/npm/v/node-plantuml-2)](https://www.npmjs.com/package/node-plantuml-2)
551
600
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
552
601
  [![Node.js](https://img.shields.io/badge/node-%3E%3D12-green.svg)](https://nodejs.org/)
553
602
 
554
- 一个强大的 Node.js 模块和 CLI,用于运行 [PlantUML](http://plantuml.sourceforge.net/),支持**纯 Node.js 环境**。本项目基于 [node-plantuml](https://github.com/markushedvall/node-plantuml) Fork 并增强,采用 WebAssembly 执行,无需 Java 运行时。
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
- - 🚀 **纯 Node.js 环境** - 无需安装 Java!使用预编译的 WebAssembly 模块
559
- - 📦 **零配置** - 只需 `npm install` 即可使用
607
+ - 📦 **无需安装 Java** - 通过特定平台包自动安装捆绑的轻量级 JRE
608
+ - 🚀 **优化的 Java 执行** - 使用 Nailgun 加速 Java 启动,保持 JVM 常驻内存
609
+ - 🎯 **易于安装** - 只需 `npm install` - 无需手动配置 Java!
560
610
  - 🎨 **多种输出格式** - 支持 PNG、SVG、EPS、ASCII 和 Unicode 文本
561
611
  - 🌏 **多语言支持** - 完美支持中文、日文、韩文等多种 CJK 字符渲染,自动字体检测和配置
562
- - ⚡ **快速启动** - WebAssembly 执行比 JVM 更快
563
- - 🔄 **自动降级** - Wasm 不可用时自动降级到 Java 执行器
612
+ - ⚡ **高性能** - Nailgun 优化减少 Java 启动开销
564
613
  - 📝 **CLI 和 API** - 同时提供命令行界面和编程 API
565
- - 🎯 **基于 PlantUML** - 完全兼容 PlantUML 语法
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
- **就这么简单!** 无需 Java,无需配置,无需构建步骤。
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
- 1. **主要:WebAssembly 执行器**(纯 Node.js)
966
- - npm 包中包含预编译的 Wasm 模块
967
- - 快速启动,低内存占用
968
- - 无需 Java
1056
+ 2. **Java 执行器**(主要)
1057
+ - 使用捆绑的 JRE 或系统 Java 执行 `java -jar plantuml.jar`
1058
+ - 完全支持 PlantUML 的所有功能
1059
+ - 自动 Java 路径解析,带后备策略
969
1060
 
970
- 2. **降级:Java 执行器**(可选)
971
- - Wasm 不可用时自动降级
972
- - 需要 Java 运行时
973
- - 与原始 node-plantuml 完全兼容
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
- 检查 Wasm 可用性
983
- ├─ 可用 → 使用 Wasm 执行器 ✅ (纯 Node)
984
- └─ 不可用 → 使用 Java 执行器 (降级)
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+**(推荐 20+ 以获得稳定的 WASI 支持)
996
- - **无需 Java** ✅(Wasm 执行器开箱即用)
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
- ### v0.9.0
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
- - 🔄 **智能降级** - Wasm 不可用时自动降级到 Java
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
+
@@ -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
- var child = plantumlExecutor.exec(o, options.include, callback)
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) })
@@ -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
- return childProcess.spawn('java', opts)
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
- callback = argv
96
+ actualCallback = argv
79
97
  argv = undefined
80
98
  cwd = undefined
81
99
  } else if (typeof cwd === 'function') {
82
- callback = cwd
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
- // Priority 1: Try Wasm executor first (pure Node, no Java needed)
87
- var wasmExecutor = require('./plantuml-executor-wasm')
88
- var useJava = process.env.PLANTUML_USE_JAVA === 'true' || process.env.PLANTUML_USE_JAVA === '1'
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.2",
4
- "plantumlVersion": "1.2026.0",
5
- "description": "Pure Node.js PlantUML renderer - No Java required! Multiple output formats (PNG, SVG, EPS, ASCII, Unicode) with full UTF-8 support",
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:wasm": "node scripts/build-plantuml-wasm.js --method cheerpj",
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"
@@ -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. Downloading vizjs instead. See http://plantuml.com/vizjs. This may take a few minutes.')
34
-
35
- // download additional libraries for working without dot installed.
36
- download(VIZJS_URL, VIZJS_JAR, false, function (err) {
37
- if (err) {
38
- console.warn('Warning: Failed to download vizjs.jar:', err)
39
- console.warn('The package can still be used if graphviz is installed on the system, or if vizjs.jar is provided manually.')
40
- console.warn('You can retry the download later or install graphviz to avoid this dependency.')
41
- // Don't exit with error code - this is a non-critical dependency
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
  })
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
- }