diff --git a/.gitignore b/.gitignore index 5ea96f6b..b822d314 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ *.lib *.exp *.class -/swoole_compiler +*.wasm /tpc tests/**/*.diff tests/**/*.exp @@ -29,3 +29,4 @@ tests/**/*.log tests/**/*.out tests/**/*.php tests/**/*.sh +/*.browser/ diff --git a/docs/README.md b/docs/README.md index 6092099d..77f91603 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,7 @@ - [后端中立 IR](BACKEND_NEUTRAL_IR.md) - [TypePHP WASM 技术方案与实施计划](TYPEPHP_WASM_IMPLEMENTATION_PLAN.md) +- [构建 TypePHP WASI 程序](WASI_BUILD.md) - [核心重构计划](REFACTORING_PLAN.md) - [构建速度研究](AOT_BUILD_SPEED_RESEARCH.md) - [优化优先级](aot-optimization-priority.md) diff --git a/docs/TYPEPHP_WASM_IMPLEMENTATION_PLAN.md b/docs/TYPEPHP_WASM_IMPLEMENTATION_PLAN.md index 0bf1a65d..634aa11e 100644 --- a/docs/TYPEPHP_WASM_IMPLEMENTATION_PLAN.md +++ b/docs/TYPEPHP_WASM_IMPLEMENTATION_PLAN.md @@ -1,14 +1,14 @@ # TypePHP WASM 技术方案与实施计划 -> 状态:技术预研结论,供后续实现使用 +> 状态:WASI 0.2 Component 与 Chrome Worker 原型已实现 > 调研日期:2026-08-07 -> 首期目标:浏览器中的 Emscripten/WASM,NTS,单线程 +> 当前目标:WASI 0.2(Preview 2),NTS,单线程;不支持 WASI 0.1 ## 1. 文档目的 本文记录 TypePHP 支持 WebAssembly 的技术决策、功能边界、运行时架构、主要风险、验证方法和分阶段实施计划。 -本文不是当前功能说明。TypePHP 尚未承诺已经支持 WASM,后续实现应以本文作为设计起点,并根据原型验证结果更新决策。 +2026-08-07 的实现验证已经证明:精简 PHP 8.5、PHPX 核心、TypePHP 生成代码、GMP、MPFR 和 mpdecimal 可以通过 WASI SDK 静态链接为单个模块,并在 Wasmtime 中运行。可复现构建方法见 [构建 TypePHP WASI 程序](WASI_BUILD.md)。本文余下内容同时保留浏览器阶段的设计目标。 ## 2. 核心结论 @@ -18,28 +18,28 @@ PHP 源码 -> TypePHP 编译器 -> TypePHP 生成的 C++ - -> Emscripten 编译和静态链接 + -> WASI SDK 编译和静态链接 + PHP NTS + PHPX + TypePHP runtime + GMP / MPFR / mpdecimal - + 最小 TypePHP WASM SAPI - -> typephp.wasm + 薄 JavaScript loader + + PHP embed/WASI 运行时 + -> typephp.wasm(WASI 0.2 command component) ``` 具体决策如下: 1. 第一版复用当前 C++/Zend 后端,不直接生成 WAT/WASM,也不重新实现 PHP 运行时。 -2. 使用 Emscripten,而不是把 WASI 作为首期目标。 +2. 使用 WASI SDK 的 `wasm32-wasip2` sysroot 直接生成 Component;Chrome 由 Jco 转译为 ESM,不维护第二套 Emscripten ABI。 3. PHP、PHPX、TypePHP 生成代码和高精度库全部静态链接到一个 `.wasm` 模块。 -4. 浏览器侧保留一个很薄的 JavaScript loader,用于实例化模块和提供必要的宿主能力。 +4. Wasmtime 和 Chrome 共同提供 CLI、stdio、exit、clocks、random 和受控文件系统;Chrome host 固定运行在 Worker 中。 5. 仅支持 PHP NTS,不支持线程。 -6. 禁用 Fiber 和 TypePHP Generator。 +6. 禁用 Fiber 和 Generator。 7. 必须支持 C++ 异常以及 Zend bailout 所需的 `setjmp/longjmp`。 8. 保留 PHP stream 框架和本地 stream,禁用网络 transport 和依赖操作系统进程能力的功能。 9. WordPress Playground 和其他 PHP-WASM 项目只作为补丁与移植经验来源,不作为 TypePHP 的依赖或代码基础。 -本文描述的是最短可落地路径。长期的后端中立方案参见 [BACKEND_NEUTRAL_IR.md](BACKEND_NEUTRAL_IR.md)。在首期原型成功前,不应为了 WASM 重写 TypePHP 前端和语义层。 +本文描述的是最短可落地路径。长期的后端中立方案参见 [BACKEND_NEUTRAL_IR.md](BACKEND_NEUTRAL_IR.md)。WASI 原型证明无需为了 WASM 重写 TypePHP 前端和语义层。 ## 3. 为什么不采用 WordPress Playground @@ -67,22 +67,22 @@ TypePHP 无法直接复用 Playground 发布的 PHP-WASM 二进制,因为 Type ## 4. 目标与非目标 -### 4.1 首期目标 +### 4.1 当前 WASI 目标 -- 在主流浏览器中加载 TypePHP 编译产物。 +- 在 Wasmtime 等 WASI runtime 中加载 TypePHP 编译产物。 - 执行静态编译的 TypePHP 应用入口。 - 保持 TypePHP 当前基于 Zend 和 PHPX 的主要语言语义。 - 正确处理 PHP request 生命周期、C++ 异常和 Zend bailout。 - 支持 GMP、MPFR 和 mpdecimal 高精度类型。 -- 支持内存文件系统和必要的本地 PHP stream。 +- 支持 WASI 文件系统和必要的本地 PHP stream。 - 对不支持的功能给出确定、可测试的错误,而不是链接失败或运行时崩溃。 -- 构建过程可复现,php-src 和 Emscripten 版本固定。 +- 构建过程可复现,php-src、WASI SDK 和数值库版本固定。 ### 4.2 首期非目标 -- WASI 独立运行时。 +- 无宿主适配的浏览器直接运行。 - pthread、Web Worker 并行 PHP 或共享内存。 -- Fiber 和 TypePHP Generator。 +- Fiber 和 Generator。 - 动态扩展加载。 - PHP 源码的运行时编译或通用 `eval()`。 - TCP、UDP、Unix socket 和监听端口。 @@ -95,31 +95,21 @@ TypePHP 无法直接复用 Playground 发布的 PHP-WASM 二进制,因为 Type ## 5. 目标平台选择 -### 5.1 首期使用 Emscripten +### 5.1 当前使用 WASI SDK -首期目标是浏览器,且 PHP 依赖 `setjmp/longjmp`,PHPX 和 TypePHP 依赖 C++ 异常。Emscripten 已经提供: +当前先建立命令行可验证基线。WASI SDK 已经验证可以同时提供: - C/C++ 到 WebAssembly 的完整工具链; -- C++ exception handling; -- `setjmp/longjmp` 支持; -- 浏览器文件系统; -- JavaScript import/export 和模块加载; -- libc、时间、随机数和常见 POSIX 接口的兼容层。 +- 标准 Wasm C++ exception handling; +- Zend bailout 所需的 SJLJ; +- capability-based 文件系统; +- libc、时间和随机数接口。 -这些能力可以显著减少 PHP 移植工作。 +PHP、PHPX 和所有 TypePHP C++ 翻译单元必须使用一致的 Wasm EH/SJLJ 参数。链接器必须将函数签名不一致视为致命错误。 -### 5.2 暂不以 WASI 为目标 +### 5.2 Chrome Component host -WASI 更适合非浏览器运行时和能力安全模型,但首期采用 WASI 会同时引入以下变量: - -- PHP 和 Zend bailout 的兼容性; -- C++ 异常实现; -- 浏览器侧 WASI shim; -- 文件系统和异步 I/O; -- GMP、MPFR、mpdecimal 的 WASI 构建; -- 不同 WASI runtime 的实现差异。 - -在 Emscripten 原型稳定前,不同时维护第二套平台。后续如需支持 Wasmtime、Wasmer 或边缘运行时,应建立独立的 WASI 可行性项目,而不是把兼容层混入首期实现。 +Chrome 当前不能原生实例化 Component。构建器使用 Jco 将同一份 WASI 0.2 Component 转译为 core Wasm 与 ESM,并由 `examples/wasm-hello/typephp-worker.mjs` 演示宿主入口。浏览器适配不包含 PHP、PHPX 或高精度类型语义。 ## 6. 产物和运行模型 @@ -133,12 +123,12 @@ dist/ └── typephp-wasm.mjs ``` -所有 C/C++ 代码进入 `typephp.wasm`。`typephp-wasm.mjs` 只负责: +所有 C/C++ 代码进入 `typephp.wasm`。浏览器自身不会自动提供 WASI imports;`typephp-wasm.mjs` 作为 WASI host/adapter 的装载入口,只负责: - 获取和实例化 `.wasm`; - 提供 stdout/stderr; - 初始化内存文件系统; -- 注入时间、随机数等宿主能力; +- 实现或接入 WASI clocks、随机数等宿主能力; - 调用导出的 TypePHP 生命周期接口; - 把状态码和错误信息转换为 JavaScript 结果。 @@ -232,14 +222,14 @@ PHP 标准库大量依赖 stream。完全关闭 stream 会破坏文件读写、` | 文件系统 | MEMFS;可选只读预加载文件 | | 当前目录和路径 | 虚拟根目录,禁止泄漏宿主路径 | | 环境变量 | loader 注入白名单 | -| 时间 | Emscripten/JavaScript host clock | -| 随机数 | 浏览器安全随机源,不使用弱伪随机替代 | +| 时间 | WASI clocks;浏览器宿主使用浏览器时钟实现该接口 | +| 随机数 | WASI random;浏览器宿主使用安全随机源实现,不使用弱伪随机替代 | | DNS、socket | 不支持 | | 进程、shell | 不支持 | | 信号 | 不支持 | | 用户、组、权限 | 固定值或明确报错 | | 文件锁 | 首期不支持跨实例锁;单实例内按需降级 | -| 持久化 | 首期不提供;后续可选 IDBFS/OPFS | +| 持久化 | 默认关闭;Chrome 可显式启用 OPFS 文件系统快照 | 编译器应逐步增加 WASM target capability 检查:静态可识别的不支持函数在编译期报错;动态调用无法静态判断时,由运行时返回确定错误。禁止让这些调用表现为链接期缺失符号、空函数或未定义行为。 diff --git a/docs/WASI_BUILD.md b/docs/WASI_BUILD.md new file mode 100644 index 00000000..a2ebb48f --- /dev/null +++ b/docs/WASI_BUILD.md @@ -0,0 +1,226 @@ +# 构建 TypePHP WASI 程序 + +TypePHP 使用稳定的 WASI 0.2(Preview 2)和 Component Model。TypePHP 生成的 C++、PHPX 核心、精简的 PHP 8.5 NTS、GMP、MPFR 和 mpdecimal 会静态链接为单个 `.wasm` command 或 library component。WASI 0.1(Preview 1)不受支持。 + +## 环境要求 + +- WASI SDK 33 或更高版本(LLVM/Clang/LLD 22 或更高) +- PHP 8.4 或更高版本,用于运行 TypePHP 编译器 +- Wasmtime 47 或更高版本,用于运行和测试产物 +- Jco 1 或更高版本,用于 browser profile;component profile 不需要 Jco +- 与当前 TypePHP 版本绑定的 `wasm32-wasip2` 集成 SDK + +WASI SDK 的 `bin` 目录和 Wasmtime 必须加入系统 `PATH`。编译器不会探测或使用 `/opt` 等约定安装目录,也不接受专用的工具目录配置。WASI 静态库和头文件统一安装到 PHPX 的 `wasm/wasm32-wasip2/`: + +```bash +export PATH="::$PATH" +``` + +TypePHP 使用现有的 PHPX 定位规则:优先读取 `PHPX_HOME`,其次读取 Composer 的 `swoole/phpx` 安装位置,最后使用 `vendor/swoole/phpx`。不新增 WASI 专用环境变量。 + +WASI 构建会检查 `wasm32-wasip2-clang`、`wasm32-wasip2-clang++`、`llvm-ar`、`llvm-ranlib`、`llvm-nm`、`wasm-component-ld` 和 `wasmtime`,并确认目标是 `wasm32-unknown-wasip2`。browser profile 另外检查 `jco`。所有工具只从 `PATH` 查找;npm script 会自动将项目本地的 `node_modules/.bin` 加入 `PATH`。 + +## 一条命令构建 + +command 模式的源文件必须提供 `main(): void`: + +```php +` 覆盖。 + +项目可以直接使用 `project.yml`: + +```yaml +name: wasm-hello +mode: bin +wasm: component +build-dir: build +output: component/wasm-hello.wasm +sources: + - src +``` + +`wasm` 只接受 `component` 或 `browser`,不接受布尔值。配置后直接执行 `php bin/tpc.php project.yml` 即可进入 WASI 构建,无需重复传入 `--wasm`。WASM 项目未配置 `target-platform` 时默认使用 `wasm32-wasip2`;`build-dir`、`output` 和 `wasm-browser-dir` 都相对于项目文件解析。完整浏览器应用见 `examples/wasm-hello/`,它显式使用 `wasm: browser`。 + +需要生成浏览器模块时,配置 `wasm: browser` 和 `wasm-browser-dir`,并确保 Jco 位于 `PATH`。 + +命令行也可以显式选择产物: + +- `--wasm` 或 `--wasm=component`:仅生成可由 Wasmtime 运行的 Component,不检测 Jco。 +- `--wasm=browser`:生成 Component 和 Jco 浏览器模块,需要 `jco` 位于 `PATH`。 + +路径、sources 等详细配置继续放在 `project.yml`,不通过 `--wasm=` 传递。 + +PHP、PHPX、TypePHP runtime、GMP、MPFR 和 mpdecimal 由 SDK 发布阶段预编译为 WASI 静态库。应用构建只编译 TypePHP 为当前程序生成的 C++,然后链接这些 `.a`。`tpc --wasm` 不会下载源码,也不会调用 PHP、PHPX 或高精度库的构建脚本。library 模式会调用 PHPX 包内固定版本的 `wit-bindgen` 生成当前应用的 Canonical ABI 绑定;普通用户不需要从 `PATH` 安装它。 + +PHP/WASI 当前静态内建 `date`、`pcre`、`hash`、`json`、`lexbor`、`random`、`Reflection`、`SPL`、`standard`、`uri`、`ctype`、`calendar`、`bcmath`、`filter`、`tokenizer`、`mbstring`、`zlib`、`fileinfo`、`sodium`、`openssl`、`libxml`、`dom`、`SimpleXML`、`xml`、`xmlreader`、`xmlwriter`、`PDO`、`pdo_sqlite`、`zip`、`bz2` 和 `exif` 扩展。OpenSSL 采用 crypto-only 构建,不包含 TLS stream transport;HTTP/HTTPS 仍由 WASI HTTP Component 提供。 + +每个 C/C++ 翻译单元统一使用标准 Wasm C++ exceptions 和 WASI SJLJ;链接阶段将 ABI 警告视为错误,旧的 32 位 `zend_long` 缓存也会自动失效。 + +运行: + +```bash +wasmtime hello.wasm +``` + +Chrome Demo: + +```bash +cd examples/wasm-hello +npm ci +npm run wasm +npm run dev +``` + +浏览器端始终在专用 Worker 中执行 Component。默认使用内存文件系统;发送给 Worker 的启动消息设置 `persistent: true` 后,会在启动和退出时通过 OPFS 恢复、保存文件系统快照。程序执行期间仍使用同步内存文件系统,避免每次 PHP 文件访问跨越异步 JS 边界。 + +## Command 与 Library 的 ZendVM 生命周期 + +### Command 模式 + +command 模式具有生成的 C++ `main()` 入口。入口依次调用: + +```text +typephp_runtime_init(argc, argv) + → php_embed_init() + → PHP/SAPI module startup 与 MINIT + → PHP request startup 与 RINIT + → 注册并启动当前 TypePHP 应用模块 + → 当前应用的 MINIT 与 RINIT + +执行 TypePHP main() + +typephp_runtime_shutdown() + → 当前应用的 RSHUTDOWN 与模块清理 + → php_embed_shutdown() + → PHP request/module/SAPI shutdown +``` + +调用者不需要感知这些步骤,因为生成的原生 `main()` 会自动包围整个程序生命周期。 + +### Library 模式必须先创建 runtime resource + +library component 没有可自动执行的 `main()`,单纯实例化 `.wasm` 只完成 Component 和 C/C++ Runtime 的实例化,不代表 ZendVM request 已经可用。Host 必须先调用生成的 WIT 函数: + +```wit +create-runtime: func() -> result; +``` + +浏览器中对应的调用为: + +```js +const component = await instantiate(null, wasi.getImportObject()); +const runtime = await component.api.createRuntime(); + +try { + const result = await runtime.someExportedFunction(); +} finally { + runtime[Symbol.dispose](); +} +``` + +`createRuntime()` 内部调用 `typephp_runtime_init(1, argv)`。Host 只需要调用这一层稳定接口,不应直接调用 `php_embed_init()`、MINIT、RINIT 或任何 Zend C API。 + +当前初始化顺序如下: + +1. `php_embed_init()` 初始化 Embed SAPI、PHP 核心和静态扩展,并启动 PHP request;PHP 核心与已经注册的静态扩展在这里完成 MINIT/RINIT。 +2. 设置 PHPX 的异常桥接,使 PHP 异常可以安全返回到生成的 WIT `result`。 +3. 取得当前 TypePHP 应用的 `zend_module_entry`,调用 `zend_register_module_ex()` 和 `zend_startup_module_ex()`,完成应用模块注册与 MINIT。 +4. 注册标准流并设置请求路径等 SAPI 请求信息。 +5. 因为 Embed request 和请求内存池此时已经启动,生成代码会显式调用当前应用模块的 `request_startup_func`,补做该模块的 RINIT;RINIT 再初始化 TypePHP 请求级全局变量和类静态数据,完成后才返回 `runtime` resource。 + +这里“手动”调用的是 Host 可见的 `create-runtime()`,而不是让用户手动拼装 ZendVM 生命周期。MINIT/RINIT 的具体调用及其先后顺序全部封装在 PHPX 和生成的 Component adapter 中。 + +### 导出调用共享同一个 request + +同一 `runtime` resource 上的所有 `#[WasmExport]` 调用共享一次 RINIT 建立的 Zend request: + +- 不会在每次函数调用前后重复执行 RINIT/RSHUTDOWN。 +- PHP request 内存池、请求级全局变量和静态状态会持续到 resource 被释放。 +- 当前仅支持 NTS;同一个 runtime 上的调用必须串行,生成的 adapter 会拒绝并发或重入调用。 +- 普通 PHP 异常会被转换为 WIT `result` 错误,runtime 仍然可以继续使用。 +- Zend bailout 表示请求状态可能已经损坏,adapter 会将 runtime 标记为 failed,后续调用会被拒绝,直到 resource 被释放。 + +### 释放 resource 才会执行 RSHUTDOWN + +释放 WIT `runtime` resource 会调用 `typephp_runtime_shutdown()`: + +1. 调用当前 TypePHP 应用模块的 RSHUTDOWN,清理 TypePHP 请求级对象和全局数据。 +2. 注销并关闭当前应用模块,执行相应模块清理。 +3. 调用 `php_embed_shutdown()`,完成其余扩展的 request shutdown、module shutdown 和 SAPI shutdown。 +4. 最后释放 request 内存池,避免 PHP/CPP 包装对象在内存池消失后继续析构。 + +不要只依赖 JavaScript GC 触发 resource finalizer。浏览器和 Node Host 应在 `finally` 中显式调用 `runtime[Symbol.dispose]()`;Wasmtime 或其他 Host binding 也应显式 drop resource。直接终止 Worker 或进程会回收整个 Wasm 实例,但不保证 PHP 的 RSHUTDOWN/MSHUTDOWN 回调得到执行,因此不能把必须持久化的数据只放在关闭回调中。 + +一个 Component 实例当前只允许同时存在一个活动的 runtime resource。释放完成后可以重新创建;初始化失败或发生 Zend bailout 时,应先释放旧 resource,而不是继续调用导出函数。 + +## 高精度类型 + +WASI 产物包含 TypePHP 的三种语言级高精度类型: + +- `BigInt`:GMP 6.3.0 +- `BigFloat`:MPFR 4.2.2 +- `Decimal`:mpdecimal 4.0.1 + +完整示例位于 [high-precision.php](../wasm/examples/high-precision.php)。构建并运行: + +```bash +php bin/tpc.php --wasm wasm/examples/high-precision.php +wasmtime -S http high-precision.wasm +``` + +预期输出: + +```text +1111111101111111110111111111010 +1000000000000000000000000000001 +12348.14159265358979324 +``` + +wasm32 使用 32 位指针,但 PHP 的 `zend_long` 保持 64 位,以维持 TypePHP 与 64 位 PHP 的整数语义。GMP 和 mpdecimal 使用 32 位 limb;这不改变任意精度语义,但大数吞吐量低于具有汇编优化的原生 64 位构建。 + +## 当前平台边界 + +- 仅支持 NTS、单线程。 +- Fiber 和 Generator 被禁用;编译器在发现 `yield` 时直接报致命错误。 +- PHPX Facade API 在 `__wasi__` 下整体禁用。PHPX 核心类型和 `phpx_std` 仍可使用。 +- 不支持动态扩展、网络 socket、进程、shell 和信号。静态可识别的调用会在编译期报致命错误。 +- 保留 PHP stream 框架、本地文件能力以及由 WASI host 提供的时间和随机数能力。 +- command component 可由 Wasmtime 直接运行;library component 需要 Host 按 WIT 接口调用 `create-runtime()` 和导出函数。Chrome 使用 Jco 生成的 ESM 和 `examples/wasm-hello/typephp-worker.mjs` 中的 Worker host。 + +PHPX Facade 只是为 PHP 可选扩展生成的便捷包装,并非 TypePHP ABI 的组成部分。WASI 下整体关闭它,可以避免把 curl、socket、Swoole 等不可用 API 暴露为“可编译但链接失败”的接口;PHP/WASI 静态内建扩展本身不受 Facade 开关影响。 + +## WASI SDK 目录 + +集成 SDK 使用唯一、完整的前缀,位于 PHPX 根目录的 `wasm/wasm32-wasip2/`: + +```text +phpx/wasm/wasm32-wasip2/ +├── include/php/ # PHP 安装头文件 +├── include/phpx/ # PHPX 和 TypePHP runtime 头文件 +├── include/gmp.h ... +├── lib/libphp.a +├── lib/libphpx.a +├── lib/libgmp.a +├── lib/libgmpxx.a +├── lib/libmpfr.a +├── lib/libmpdec.a +├── lib/libmpdec++.a +└── .typephp-wasi-sdk-abi +``` + +普通用户通过 TypePHP/PHPX 集成安装包获得该目录。TypePHP 开发者需要自行 clone 与当前版本绑定的 `php-8.5.9-wasm` 和 PHPX 源码,并通过 `wasm/build-sdk.sh` 组装完整 SDK。PHP/WASI 只负责 PHP;PHPX 负责 GMP、MPFR、其专属的 mpdecimal 以及 PHPX runtime。所有产物安装到同一个 PHPX checkout。若 PHPX 不在 `vendor/swoole/phpx`,继续使用已有的 `PHPX_HOME` 指向该 checkout。 + +不提供单独覆盖 `libphp.a`、`libphpx.a` 或数值库的路径;所有库、头文件和 `.typephp-wasi-sdk-abi` 必须来自同一次兼容构建,避免混用不同的 `zend_long`、C++ exceptions、SJLJ 或 Component Model ABI。 diff --git a/docs/wasm.md b/docs/wasm.md new file mode 100644 index 00000000..534598be --- /dev/null +++ b/docs/wasm.md @@ -0,0 +1,22 @@ +## 编译 + +```shell +./tpc --wasm test.php +``` + +编译成功后默认只生成可由 Wasmtime 执行的 WASI 0.2 Component `test.wasm`。WASI 0.1 不受支持。 +生成的 C++ 源码默认写入 `build/`,也可以通过 `--build-dir ` 指定。 + +## 执行 + +```shell +wasmtime test.wasm +``` + +## Chrome + +```shell +./tpc --wasm=browser test.php +``` + +浏览器模式额外生成 `test.browser/` Jco 模块并要求 `jco` 位于 `PATH`。完整浏览器 Demo 位于仓库 `examples/wasm-hello/`,并使用 `wasm: browser` 的 `project.yml` 构建。TypePHP 在专用 Worker 中执行;默认文件系统驻留内存,可显式启用 OPFS 快照持久化。网络 socket、进程、shell 和信号在 WASI 目标下明确不支持。 diff --git a/examples/hello.php b/examples/hello.php index 1abfd2fa..e9504351 100644 --- a/examples/hello.php +++ b/examples/hello.php @@ -7,4 +7,7 @@ function main(): void var_dump(php_uname()); global $argv; var_dump($argv); + + $date = date('Y-m-d H:i:s', time()); + var_dump($date); } diff --git a/examples/https.php b/examples/https.php new file mode 100644 index 00000000..b6b7de83 --- /dev/null +++ b/examples/https.php @@ -0,0 +1,6 @@ + + + + + + + TypePHP · WASI 0.2 Browser Lab + + + +
+
+
+
+
+

WASI 0.2 · Preview 2

+

TypePHP
Browser Lab

+

同一个由 PHP、PHPX 与 TypePHP 静态链接的 Component,在浏览器 Worker 中直接执行。

+
+ +
+ +
+
+
+ +

配置本次运行

+
+
Ready
+
+
+ + + +
+
+ +
+ + +
+
+
+ +
+
+

Runtime

等待运行

+
+
+

Clock

WASI wall clock +
+
+

Secure random

WASI random +
+
+

Filesystem

Memory FS + OPFS snapshot +
+
+

HTTP fetch

file_get_contents() + WASI HTTP +
+
+ +
+
+ +

参数、环境与标准流

+
+
argv
+
env
+
stdin
+
+
+
+ +

高精度计算

+
+
BigInt
+
Decimal
+
BigFloat
+
+
+
+ +
+
+ +

静态编译的 PHP 扩展

+
+ 等待运行 +
+ 由 get_loaded_extensions() 动态读取 +
+
+
+ 选择一个扩展 + 点击上方扩展名称,由 JavaScript 调用 Wasm 导出函数读取详情。 +
+
+
+ +
+ Raw component outputstdout / stderr +
尚未运行。
+
+ +
+ TypePHP → C++17 → WASI 0.2 Component + 运行于独立 ES module Worker · HTTP 通过 JSPI 挂起等待 +
+
+ + + diff --git a/examples/wasm-hello/main.js b/examples/wasm-hello/main.js new file mode 100644 index 00000000..4d1252d2 --- /dev/null +++ b/examples/wasm-hello/main.js @@ -0,0 +1,231 @@ +const storageName = 'typephp-wasi-demo-filesystem.json'; +const elements = Object.fromEntries([ + 'args', 'env', 'stdin', 'persistent', 'run', 'reset', 'status', 'output', + 'runtime-value', 'platform-value', 'clock-value', 'random-value', 'token-value', + 'filesystem-value', 'files-value', 'argv-value', 'env-value', 'stdin-value', + 'http-value', 'http-detail', 'bigint-value', 'decimal-value', 'bigfloat-value', + 'extension-count', 'extension-list', +].map((id) => [id, document.getElementById(id)])); + +let worker = null; +let selectedExtension = ''; + +function parseArguments(source) { + const args = []; + const pattern = /"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\s]+)/g; + for (const match of source.matchAll(pattern)) { + args.push((match[1] ?? match[2] ?? match[3]).replace(/\\([\\"'])/g, '$1')); + } + return args; +} + +function parseEnvironment(source) { + return Object.fromEntries(source.split(/\r?\n/).flatMap((line) => { + const separator = line.indexOf('='); + return separator > 0 ? [[line.slice(0, separator).trim(), line.slice(separator + 1)]] : []; + })); +} + +function setStatus(kind, label) { + elements.status.className = `status ${kind}`; + elements.status.lastChild.textContent = label; +} + +function value(id, content) { + elements[id].textContent = content === '' ? '(空)' : String(content ?? '—'); +} + +function renderReport(report) { + value('runtime-value', `PHP ${report.runtime.php}`); + value('platform-value', report.runtime.platform); + value('extension-count', `${report.runtime.extensions.length} 个内置扩展`); + elements['extension-list'].replaceChildren(...report.runtime.extensions.map((extension) => { + const button = document.createElement('button'); + button.type = 'button'; + button.className = 'extension-button'; + button.dataset.extension = extension; + button.textContent = extension; + button.title = `查看 ${extension} 扩展信息`; + return button; + })); + value('clock-value', report.clock.iso8601); + value('random-value', report.random.integer); + value('token-value', report.random.token); + value('filesystem-value', `第 ${report.filesystem.run} 次运行`); + value('files-value', report.filesystem.files.join(' · ')); + value('http-value', report.http.ok ? `${report.http.bytes} bytes` : '请求失败'); + value('http-detail', report.http.preview); + value('argv-value', report.input.argv.join(' ')); + value('env-value', report.input.greeting); + value('stdin-value', report.input.stdin); + value('bigint-value', report.precision.bigint); + value('decimal-value', report.precision.decimal); + value('bigfloat-value', report.precision.bigfloat); +} + +function appendCodeList(container, values, emptyLabel = '无') { + if (!Array.isArray(values) || values.length === 0) { + const empty = document.createElement('span'); + empty.textContent = emptyLabel; + container.append(empty); + return; + } + for (const item of values) { + const code = document.createElement('code'); + code.textContent = String(item); + code.title = String(item); + container.append(code); + } +} + +function extensionGroup(title, content) { + const group = document.createElement('section'); + group.className = 'extension-group'; + const heading = document.createElement('h4'); + heading.textContent = title; + const items = document.createElement('div'); + items.className = 'extension-items'; + content(items); + group.append(heading, items); + return group; +} + +function keyValueList(container, values) { + const entries = values && typeof values === 'object' ? Object.entries(values) : []; + if (entries.length === 0) { + appendCodeList(container, []); + return; + } + const list = document.createElement('dl'); + for (const [key, item] of entries) { + const row = document.createElement('div'); + const term = document.createElement('dt'); + const value = document.createElement('dd'); + term.textContent = key; + value.textContent = typeof item === 'string' ? item : JSON.stringify(item); + row.append(term, value); + list.append(row); + } + container.append(list); +} + +function renderExtensionInfo(info) { + const heading = document.createElement('div'); + heading.className = 'extension-detail-header'; + const identity = document.createElement('div'); + const name = document.createElement('h3'); + name.textContent = info.name; + const version = document.createElement('p'); + version.textContent = `version ${info.version}`; + identity.append(name, version); + const flags = document.createElement('div'); + flags.className = 'extension-flags'; + for (const label of [info.persistent ? 'persistent' : 'non-persistent', info.temporary ? 'temporary' : 'built-in']) { + const flag = document.createElement('span'); + flag.textContent = label; + flags.append(flag); + } + heading.append(identity, flags); + + const groups = document.createElement('div'); + groups.className = 'extension-groups'; + groups.append( + extensionGroup(`Functions · ${info.functions.length}`, (node) => appendCodeList(node, info.functions)), + extensionGroup(`Classes · ${info.classes.length}`, (node) => appendCodeList(node, info.classes)), + extensionGroup(`Constants · ${info.constants.length}`, (node) => appendCodeList(node, info.constants)), + extensionGroup('INI entries', (node) => keyValueList(node, info.iniEntries)), + extensionGroup('Dependencies', (node) => keyValueList(node, info.dependencies)), + ); + document.getElementById('extension-detail').replaceChildren(heading, groups); +} + +function loadExtensionInfo(extension) { + if (!worker) return; + selectedExtension = extension; + for (const button of elements['extension-list'].querySelectorAll('.extension-button')) { + button.classList.toggle('active', button.dataset.extension === extension); + } + document.getElementById('extension-detail').innerHTML = '正在调用 Wasm 导出函数…'; + worker.postMessage({ type: 'extension-info', extension }); +} + +function run() { + worker?.terminate(); + worker = new Worker(new URL('./typephp-worker.mjs', import.meta.url), { type: 'module' }); + let stdout = ''; + let stderr = ''; + selectedExtension = ''; + + elements.run.disabled = true; + elements.output.textContent = '正在实例化 WASI 0.2 Component…'; + setStatus('running', 'Running'); + + worker.onmessage = ({ data }) => { + if (data.type === 'stdout') { + stdout += data.data; + } else if (data.type === 'stderr') { + stderr += data.data; + } else if (data.type === 'error') { + stderr += `${data.error}\n`; + elements.run.disabled = false; + setStatus('error', 'Wasm error'); + elements.output.textContent = [stdout, stderr].filter(Boolean).join('\n--- stderr ---\n'); + } else if (data.type === 'report') { + elements.run.disabled = false; + elements.output.textContent = [data.json, stdout, stderr].filter(Boolean).join('\n--- component output ---\n'); + try { + renderReport(JSON.parse(data.json)); + setStatus('success', 'Ready for JS calls'); + } catch (error) { + setStatus('error', 'Invalid export result'); + elements.output.textContent += `\n\nUI parse error: ${error.message}`; + } + } else if (data.type === 'extension-info') { + if (data.extension === selectedExtension) { + try { + renderExtensionInfo(JSON.parse(data.json)); + } catch (error) { + document.getElementById('extension-detail').textContent = `无法解析扩展信息:${error.message}`; + } + } + } else if (data.type === 'extension-error' && data.extension === selectedExtension) { + document.getElementById('extension-detail').textContent = data.error; + } + }; + + worker.onerror = (event) => { + elements.run.disabled = false; + setStatus('error', 'Worker error'); + elements.output.textContent = event.message; + }; + + worker.postMessage({ + type: 'run', + args: parseArguments(elements.args.value), + env: parseEnvironment(elements.env.value), + stdin: elements.stdin.value, + persistent: elements.persistent.checked, + storageName, + }); +} + +async function resetStorage() { + if (!navigator.storage?.getDirectory) { + setStatus('error', 'OPFS unavailable'); + return; + } + const root = await navigator.storage.getDirectory(); + await root.removeEntry(storageName).catch((error) => { + if (error.name !== 'NotFoundError') throw error; + }); + setStatus('idle', 'Storage cleared'); + value('filesystem-value', '等待重新运行'); +} + +elements.run.addEventListener('click', run); +elements.reset.addEventListener('click', () => resetStorage().catch((error) => setStatus('error', error.message))); +elements['extension-list'].addEventListener('click', (event) => { + const button = event.target.closest('.extension-button'); + if (button) loadExtensionInfo(button.dataset.extension); +}); +run(); diff --git a/examples/wasm-hello/package-lock.json b/examples/wasm-hello/package-lock.json new file mode 100644 index 00000000..7def2885 --- /dev/null +++ b/examples/wasm-hello/package-lock.json @@ -0,0 +1,4497 @@ +{ + "name": "typephp-wasm-hello", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "typephp-wasm-hello", + "version": "0.1.0", + "dependencies": { + "@bytecodealliance/preview2-shim": "0.18.0" + }, + "devDependencies": { + "@bytecodealliance/jco": "^1.27.0", + "vite": "^7.1.0" + } + }, + "node_modules/@andreiltd/componentize-qjs-binding-darwin-arm64": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@andreiltd/componentize-qjs-binding-darwin-arm64/-/componentize-qjs-binding-darwin-arm64-0.4.3.tgz", + "integrity": "sha512-bCqUlC2gkOSHEn/qaFMzwRegwv7zTgSxH1kbFJ1riHArhXEQjOdMPb3sAGd4euH81CnnDbug6Bqh4SBNBXXrXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@andreiltd/componentize-qjs-binding-darwin-x64": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@andreiltd/componentize-qjs-binding-darwin-x64/-/componentize-qjs-binding-darwin-x64-0.4.3.tgz", + "integrity": "sha512-fP4Ekzn7pR8KqRIoH41zbOTl1i3nEYCog8r/gy2nDzg86EYl51kdrCP+jN4B9tPmYOH8erae7i88Mb50EshyLA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@andreiltd/componentize-qjs-binding-linux-arm64-gnu": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@andreiltd/componentize-qjs-binding-linux-arm64-gnu/-/componentize-qjs-binding-linux-arm64-gnu-0.4.3.tgz", + "integrity": "sha512-3+Oz1cQRq2KklYdGjhNiI8kNXjCO3ZxBVxf/ZjeGg8MC6Z9A/fO2Hx3T65xSnA5voc2RlWAs0eSL9veoH3CqWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@andreiltd/componentize-qjs-binding-linux-arm64-musl": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@andreiltd/componentize-qjs-binding-linux-arm64-musl/-/componentize-qjs-binding-linux-arm64-musl-0.4.3.tgz", + "integrity": "sha512-quZj5Z81UC5ufUXDAS3knfpO3x4KQCADqjH42FgeliBEZWN6imgjDF8DZKTwtzMH8vH6I2pxB9vu/D3ufVA+xA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@andreiltd/componentize-qjs-binding-linux-x64-gnu": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@andreiltd/componentize-qjs-binding-linux-x64-gnu/-/componentize-qjs-binding-linux-x64-gnu-0.4.3.tgz", + "integrity": "sha512-JqknHtOQF5ah6eN7t82f9fPDkbwVhHC5JMS+7UE0iPaJcDss6BqFYMiFBQ8v/iFxZdtrum3PF2xViTBAEdS2EQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@andreiltd/componentize-qjs-binding-linux-x64-musl": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@andreiltd/componentize-qjs-binding-linux-x64-musl/-/componentize-qjs-binding-linux-x64-musl-0.4.3.tgz", + "integrity": "sha512-5J/HSTeWKz49PTkRPS+856L6un8ONGPhs1JvNTAyDXW4zgcCOGQ5x7CBmWStLdIthiIUPKgW53wIAVp2aU5HAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@andreiltd/componentize-qjs-binding-win32-arm64-msvc": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@andreiltd/componentize-qjs-binding-win32-arm64-msvc/-/componentize-qjs-binding-win32-arm64-msvc-0.4.3.tgz", + "integrity": "sha512-lZ7XQzbXsIRpCKqMnof0YSPiV01JFpcw3LOpR4q6kbR8+J8qqCwxD5hrJM0kCeml1YkxP9VxDpLSBRN40c+GVQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@andreiltd/componentize-qjs-binding-win32-x64-msvc": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@andreiltd/componentize-qjs-binding-win32-x64-msvc/-/componentize-qjs-binding-win32-x64-msvc-0.4.3.tgz", + "integrity": "sha512-0kaaM7C9MALnOGKrsJOL2uUJJV6njrfgqJNz7e5aT4LDQbBxjPK0XUVimr3QSyfNBwIySgcZIzufgc+5BVcBMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@bytecodealliance/componentize-js": { + "version": "0.22.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/componentize-js/-/componentize-js-0.22.0.tgz", + "integrity": "sha512-sscwKDazGgMWZkS6azvY66tY3X09nzl58jn9j6X0E9/V4B5hCXTI+v7PFUyLowKYgu8MG4FJsSvCpq64lSXsIA==", + "dev": true, + "workspaces": [ + "." + ], + "dependencies": { + "@bytecodealliance/jco": "^1.15.1", + "@bytecodealliance/weval": "^0.4.1", + "@bytecodealliance/wizer": "^10.0.0", + "es-module-lexer": "^1.6.0", + "oxc-parser": "^0.76.0" + }, + "bin": { + "componentize-js": "src/cli.js" + } + }, + "node_modules/@bytecodealliance/componentize-js-0-19-3": { + "name": "@bytecodealliance/componentize-js", + "version": "0.19.3", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/componentize-js/-/componentize-js-0.19.3.tgz", + "integrity": "sha512-ju7Y4WeF0B9uMkSPHJgmT6ouEfSwbe9M1uR/YOnYZjBpxJjH9qzxIkJg/kf8NycVDyFJ2/lscmJ1E1uPiDQVRQ==", + "dev": true, + "workspaces": [ + "." + ], + "dependencies": { + "@bytecodealliance/jco": "^1.15.1", + "@bytecodealliance/wizer": "^10.0.0", + "es-module-lexer": "^1.6.0", + "oxc-parser": "^0.76.0" + }, + "bin": { + "componentize-js": "src/cli.js" + } + }, + "node_modules/@bytecodealliance/jco": { + "version": "1.27.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/jco/-/jco-1.27.0.tgz", + "integrity": "sha512-MG3875pStjkVZt6hxm/2rszf9EgOR2VGIostrpliTyYLyx+3u6W6U3fWpGVmRwXcS8VW1pUolQl9kHO5RKeIbA==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)", + "dependencies": { + "@bytecodealliance/componentize-js": "^0.22.0", + "@bytecodealliance/componentize-js-0-19-3": "npm:@bytecodealliance/componentize-js@^0.19.3", + "@bytecodealliance/jco-transpile": "^0.6.1", + "@bytecodealliance/preview2-shim": "^0.20.1", + "@bytecodealliance/preview3-shim": "^0.2.2", + "binaryen": "^130.0.0", + "commander": "^14", + "componentize-qjs": "^0.4.2", + "mkdirp": "^3", + "ora": "^8", + "rolldown": "^1.1.0" + }, + "bin": { + "jco": "dist/jco.js" + } + }, + "node_modules/@bytecodealliance/jco-transpile": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/jco-transpile/-/jco-transpile-0.6.3.tgz", + "integrity": "sha512-97f+ObKbAiIIuBTMyewPHrTBBh3qqiUqCP/FZwC1QXgAwbrzsHaE0XVC75z9lr9OWV1iA+lX6dixic07yH2dxg==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)", + "dependencies": { + "@bytecodealliance/preview2-shim": "^0.20.1", + "@bytecodealliance/preview3-shim": "^0.2.2", + "binaryen": "^130.0.0", + "oxc-minify": "^0.136.0" + } + }, + "node_modules/@bytecodealliance/jco-transpile/node_modules/@bytecodealliance/preview2-shim": { + "version": "0.20.1", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/preview2-shim/-/preview2-shim-0.20.1.tgz", + "integrity": "sha512-gvQP6DWfeQIYKHBb+nPykku1DRtfXQjM685+e75YYr8ZF2ZxisE4+nWrpwf84+NaWJRfuL5HvoLKQFbup1iwkQ==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@bytecodealliance/jco/node_modules/@bytecodealliance/preview2-shim": { + "version": "0.20.1", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/preview2-shim/-/preview2-shim-0.20.1.tgz", + "integrity": "sha512-gvQP6DWfeQIYKHBb+nPykku1DRtfXQjM685+e75YYr8ZF2ZxisE4+nWrpwf84+NaWJRfuL5HvoLKQFbup1iwkQ==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.18.0.tgz", + "integrity": "sha512-f7+HY98GBrfdKjXb2QT6ycbz+NR+Ocn3Aq87IZ8aVf0lThJocQ4E6dyCC9Y/5l0oOgS9nJ0Z1edeYfmTU87EdA==", + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@bytecodealliance/preview3-shim": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/preview3-shim/-/preview3-shim-0.2.2.tgz", + "integrity": "sha512-9z4tjc+qixUhyeYo3ycoT4cZ9yIlUfkowJxGoKeMfbRd8zT3uvIr1YNfXyU0I93GPu+gdaTVsr8iMr55utEJSA==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)", + "dependencies": { + "@bytecodealliance/preview2-shim": "^0.19.0" + } + }, + "node_modules/@bytecodealliance/preview3-shim/node_modules/@bytecodealliance/preview2-shim": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/preview2-shim/-/preview2-shim-0.19.0.tgz", + "integrity": "sha512-OSUlcM62fXLuzYP0DjBn/SwjKMZ/y/2PMXZ8v5cRiiFcroauntlPRl/oCnx0xYjYiqdYvE0PY50VCNutll0xOw==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@bytecodealliance/weval": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/weval/-/weval-0.4.1.tgz", + "integrity": "sha512-vJegSAkNjENhJcMUod76KUGAgQLdACDDCwB3JwyR14zDhyHVPAvArvtDDYEEi+c+ELzls62H6wxTvzRmaYTaqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@napi-rs/lzma": "^1.1.2", + "decompress": "^4.2.1", + "decompress-tar": "^4.1.1", + "decompress-unzip": "^4.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@bytecodealliance/wizer": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/wizer/-/wizer-10.0.0.tgz", + "integrity": "sha512-ziWmovyu1jQl9TsKlfC2bwuUZwxVPFHlX4fOqTzxhgS76jITIo45nzODEwPgU+jjmOr8F3YX2V2wAChC5NKujg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "wizer": "wizer.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@bytecodealliance/wizer-darwin-arm64": "10.0.0", + "@bytecodealliance/wizer-darwin-x64": "10.0.0", + "@bytecodealliance/wizer-linux-arm64": "10.0.0", + "@bytecodealliance/wizer-linux-s390x": "10.0.0", + "@bytecodealliance/wizer-linux-x64": "10.0.0", + "@bytecodealliance/wizer-win32-x64": "10.0.0" + } + }, + "node_modules/@bytecodealliance/wizer-darwin-arm64": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-10.0.0.tgz", + "integrity": "sha512-dhZTWel+xccGTKSJtI9A7oM4yyP20FWflsT+AoqkOqkCY7kCNrj4tmMtZ6GXZFRDkrPY5+EnOh62sfShEibAMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "wizer-darwin-arm64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-darwin-x64": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-10.0.0.tgz", + "integrity": "sha512-r/LUIZw6Q3Hf4htd46mD+EBxfwjBkxVIrTM1r+B2pTCddoBYQnKVdVsI4UFyy7NoBxzEg8F8BwmTNoSLmFRjpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "wizer-darwin-x64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-arm64": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-10.0.0.tgz", + "integrity": "sha512-pGSfFWXzeTqHm6z1PtVaEn+7Fm3QGC8YnHrzBV4sQDVS3N1NwmuHZAc8kslmlFPNdu61ycEvdOsSgCny8JPQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-arm64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-s390x": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-10.0.0.tgz", + "integrity": "sha512-O8vHxRTAdb1lUnVXMIMTcp/9q4pq1D4iIKigJCipg2JN15taV9uFAWh0fO88wylXwuSlO7dOE1AwQl54fMKXQg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-s390x": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-x64": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-10.0.0.tgz", + "integrity": "sha512-fJtM1sy43FBMnp+xpapFX6U1YdTBKA/1T4CYfG/qeE8jn0SXk2EuiYoY/EnC2uyNy9hjTrvfdYO5n4MXW0EIdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-x64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-win32-x64": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-10.0.0.tgz", + "integrity": "sha512-55BPLfGT7iT7gH5M69NpTM16QknJZ7OxJ0z73VOEoeGA9CT8QPKMRzFKsPIvLs+W8G28fdudFA94nElrdkp3Kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "wizer-win32-x64": "wizer" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@napi-rs/lzma": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma/-/lzma-1.4.5.tgz", + "integrity": "sha512-zS5LuN1OBPAyZpda2ZZgYOEDC+xecUdAGnrvbYzjnLXkrq/OBC3B9qcRvlxbDR3k5H/gVfvef1/jyUqPknqjbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/lzma-android-arm-eabi": "1.4.5", + "@napi-rs/lzma-android-arm64": "1.4.5", + "@napi-rs/lzma-darwin-arm64": "1.4.5", + "@napi-rs/lzma-darwin-x64": "1.4.5", + "@napi-rs/lzma-freebsd-x64": "1.4.5", + "@napi-rs/lzma-linux-arm-gnueabihf": "1.4.5", + "@napi-rs/lzma-linux-arm64-gnu": "1.4.5", + "@napi-rs/lzma-linux-arm64-musl": "1.4.5", + "@napi-rs/lzma-linux-ppc64-gnu": "1.4.5", + "@napi-rs/lzma-linux-riscv64-gnu": "1.4.5", + "@napi-rs/lzma-linux-s390x-gnu": "1.4.5", + "@napi-rs/lzma-linux-x64-gnu": "1.4.5", + "@napi-rs/lzma-linux-x64-musl": "1.4.5", + "@napi-rs/lzma-wasm32-wasi": "1.4.5", + "@napi-rs/lzma-win32-arm64-msvc": "1.4.5", + "@napi-rs/lzma-win32-ia32-msvc": "1.4.5", + "@napi-rs/lzma-win32-x64-msvc": "1.4.5" + } + }, + "node_modules/@napi-rs/lzma-android-arm-eabi": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.4.5.tgz", + "integrity": "sha512-Up4gpyw2SacmyKWWEib06GhiDdF+H+CCU0LAV8pnM4aJIDqKKd5LHSlBht83Jut6frkB0vwEPmAkv4NjQ5u//Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-android-arm64": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.4.5.tgz", + "integrity": "sha512-uwa8sLlWEzkAM0MWyoZJg0JTD3BkPknvejAFG2acUA1raXM8jLrqujWCdOStisXhqQjZ2nDMp3FV6cs//zjfuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-darwin-arm64": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.4.5.tgz", + "integrity": "sha512-0Y0TQLQ2xAjVabrMDem1NhIssOZzF/y/dqetc6OT8mD3xMTDtF8u5BqZoX3MyPc9FzpsZw4ksol+w7DsxHrpMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-darwin-x64": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.4.5.tgz", + "integrity": "sha512-vR2IUyJY3En+V1wJkwmbGWcYiT8pHloTAWdW4pG24+51GIq+intst6Uf6D/r46citObGZrlX0QvMarOkQeHWpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-freebsd-x64": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.4.5.tgz", + "integrity": "sha512-XpnYQC5SVovO35tF0xGkbHYjsS6kqyNCjuaLQ2dbEblFRr5cAZVvsJ/9h7zj/5FluJPJRDojVNxGyRhTp4z2lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm-gnueabihf": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.4.5.tgz", + "integrity": "sha512-ic1ZZMoRfRMwtSwxkyw4zIlbDZGC6davC9r+2oX6x9QiF247BRqqT94qGeL5ZP4Vtz0Hyy7TEViWhx5j6Bpzvw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.4.5.tgz", + "integrity": "sha512-asEp7FPd7C1Yi6DQb45a3KPHKOFBSfGuJWXcAd4/bL2Fjetb2n/KK2z14yfW8YC/Fv6x3rBM0VAZKmJuz4tysg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-musl": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.4.5.tgz", + "integrity": "sha512-yWjcPDgJ2nIL3KNvi4536dlT/CcCWO0DUyEOlBs/SacG7BeD6IjGh6yYzd3/X1Y3JItCbZoDoLUH8iB1lTXo3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-ppc64-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-ppc64-gnu/-/lzma-linux-ppc64-gnu-1.4.5.tgz", + "integrity": "sha512-0XRhKuIU/9ZjT4WDIG/qnX7Xz7mSQHYZo9Gb3MP2gcvBgr6BA4zywQ9k3gmQaPn9ECE+CZg2V7DV7kT+x2pUMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-riscv64-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-riscv64-gnu/-/lzma-linux-riscv64-gnu-1.4.5.tgz", + "integrity": "sha512-QrqDIPEUUB23GCpyQj/QFyMlr8SGxxyExeZz9OWFnHfb70kXdTLWrHS/hEI1Ru+lSbQ/6xRqeoGyQ4Aqdg+/RA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-s390x-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-s390x-gnu/-/lzma-linux-s390x-gnu-1.4.5.tgz", + "integrity": "sha512-k8RVM5aMhW86E9H0QXdquwojew4H3SwPxbRVbl49/COJQWCUjGi79X6mYruMnMPEznZinUiT1jgKbFo2A00NdA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-musl": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.4.5.tgz", + "integrity": "sha512-eiadGBKi7Vd0bCArBUOO/qqRYPHt/VQVvGyYvDFt6C2ZSIjlD+HuOl+2oS1sjf4CFjK4eDIog6EdXnL0NE6iyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-wasm32-wasi": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-wasm32-wasi/-/lzma-wasm32-wasi-1.4.5.tgz", + "integrity": "sha512-+VyHHlr68dvey6fXc2hehw9gHVFIW3TtGF1XkcbAu65qVXsA9D/T+uuoRVqhE+JCyFHFrO0ixRbZDRK1XJt1sA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/lzma-win32-arm64-msvc": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.4.5.tgz", + "integrity": "sha512-eewnqvIyyhHi3KaZtBOJXohLvwwN27gfS2G/YDWdfHlbz1jrmfeHAmzMsP5qv8vGB+T80TMHNkro4kYjeh6Deg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-win32-ia32-msvc": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.4.5.tgz", + "integrity": "sha512-OeacFVRCJOKNU/a0ephUfYZ2Yt+NvaHze/4TgOwJ0J0P4P7X1mHzN+ig9Iyd74aQDXYqc7kaCXA2dpAOcH87Cg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-win32-x64-msvc": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.4.5.tgz", + "integrity": "sha512-T4I1SamdSmtyZgDXGAGP+y5LEK5vxHUFwe8mz6D4R7Sa5/WCxTcCIgPJ9BD7RkpO17lzhlaM2vmVvMy96Lvk9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma/node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.4.5.tgz", + "integrity": "sha512-6rMtBgnIq2Wcl1rQdZsnM+rtCcVCbws1nF8S2NzaUsVaZv8bjrPiAa0lwg4Eqnn1d9lgwqT+cZgm5m+//K08Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-minify/binding-android-arm-eabi": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-android-arm-eabi/-/binding-android-arm-eabi-0.136.0.tgz", + "integrity": "sha512-gk1jzky1vLeOcK7w9Ib2RT/UooZ7O21s8YSeXCsy4tzsmguKb3Cbsv/Ra2fKhMp4mKeKCzvC1Xa41OfSBWfWdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-android-arm64": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-android-arm64/-/binding-android-arm64-0.136.0.tgz", + "integrity": "sha512-5GIYanqud/ohXEcg2dsdn0+eSKXqclw5qlRUkvSM1sKEa4ZQ7hqOk8XPJBNPh3LXXw+Qw85B6jkmOFLKsCHp5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-darwin-arm64": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-darwin-arm64/-/binding-darwin-arm64-0.136.0.tgz", + "integrity": "sha512-QAMpHKXWOoMCTenSZjQjVT5YmMooujlCKAf1T3PHFl2LuaA6ukQFXba0bgSLgAxRrAeX/itkwi4lzVQXtYmnRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-darwin-x64": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-darwin-x64/-/binding-darwin-x64-0.136.0.tgz", + "integrity": "sha512-rDnoPj8hztiU8C1VNKuzujh7SgTJlgiUvUCiWQjTnVSLsABsLW9ZWd1l1Ji0VN3UW39wGpptgvahIWTrHnGH/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-freebsd-x64": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-freebsd-x64/-/binding-freebsd-x64-0.136.0.tgz", + "integrity": "sha512-9fGSgP7uyPLCTjMx9z3AUV4UjnfOVoGTH5JzamLegPwGLJ97Gr1jht1437CRk8ZNabGmxQR6qNn3e+Tox2lswg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm-gnueabihf": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.136.0.tgz", + "integrity": "sha512-k0QvZS+x6ROI4O5Ikqn9dDU2dmkEJ46Yh6baZJI/eUk/a1k1GOs9/vyXItLZrKlwYrSgdEx2BFXbKgyd1NyRPw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm-musleabihf": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.136.0.tgz", + "integrity": "sha512-iRFxUc8ubotVfuyq3e4LPGbHrjvWd1/B3499a55GueMLc1EDJ0fHzNXJiCx2eMQGfzHtHodjmvrJ2BSvSXBNhQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm64-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.136.0.tgz", + "integrity": "sha512-hbKxNCXJsULrb6EyG5SumzJKuPfP8Y0UQ/TElRgXVUhyyPf6cBPlF8HMq+MAylF98ACBzV8jBp2Mm62lJyTo+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm64-musl": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.136.0.tgz", + "integrity": "sha512-T+u/CGKW0UXemJeVux4ix6rK+ug3bQCUEbu5hFlX9K61KQNd44rOcrM15mDK99L7USaXwmiwHeIkEDvmmyDMSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-ppc64-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.136.0.tgz", + "integrity": "sha512-WNPTN2F448tb49BoqjV9IMRzZWa0WB8GHzrB+bnxKQPOyzz07T6rmDKrr4Hjl81ADGjDYSsr1fgXcjyLQFdMdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-riscv64-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.136.0.tgz", + "integrity": "sha512-MvcCfu8OeabrmNN9wqhh8Gqizg68RQ3iOvXBJmlLMy5p1PC6az9bAwqOk98lsCmKI4I5LpULekOlfxRYh3o5Kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-riscv64-musl": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.136.0.tgz", + "integrity": "sha512-rwJXFUFFA0egAULtOOgfW8YhhLhtWrr2qFxftvZ7/U6Xtxiev8i+eCwSNW1RpHiX74QMsJVpRE3ykoefqb1MtQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-s390x-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.136.0.tgz", + "integrity": "sha512-qizAxHmj7VHm5/D5qiIlvzxAS2tlmadDOJ5XFI7zx/wfnk/+fdk5iVaHOv+C4tDE/4wLQyMoBYVIs6R1LeN91w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-x64-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.136.0.tgz", + "integrity": "sha512-gcyLAad0UblkEhGjVfx5Mwq63OSx8r04IbcfwzpZYpHisO/vioMsrs4GkDsQZ2Ejc8cdUk6YMIrf9FwNBzh+Dw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-x64-musl": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-linux-x64-musl/-/binding-linux-x64-musl-0.136.0.tgz", + "integrity": "sha512-QAK6kwBjljX6luYzeppVdRP3gTaFNFfp9iS/V0cCvU43wUZay5xFXBgmzBHYrzZV2MhzURNnXfEZRo9poAxyvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-openharmony-arm64": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-openharmony-arm64/-/binding-openharmony-arm64-0.136.0.tgz", + "integrity": "sha512-nIGxkTOnN9GwslmooxbZ3Ti2RgbX9QLHy0X9uO0pslOT+4dsNVUawBU/6r4M5cWM+ZdRD2tOF4V+n0l8tS59nQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-wasm32-wasi": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-wasm32-wasi/-/binding-wasm32-wasi-0.136.0.tgz", + "integrity": "sha512-YB3G8AHT6PpDS8/DfjlJrQjywj+/6gCkNBcAJdYzcYbQRIZE6SaTNowiGuZPR1m1jOl1WjcjefniKSmVxzhvRA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-minify/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-minify/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-minify/binding-win32-arm64-msvc": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.136.0.tgz", + "integrity": "sha512-UVRH3BlNc2rmla+sbSd5xTF/u+esCqZS6VSWPolZybcBh3TlVe1Sf5MOuHQWUaFoyv5uFOvee4hOfS/14nPvZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-win32-ia32-msvc": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.136.0.tgz", + "integrity": "sha512-zFGkUd82jci0E+isc2O36lVmmXVUDXyEMN2RYIyez+MBNqhdjPLIGx62FCsrJtLKeuiPf/8XkFv977aUtndUng==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-win32-x64-msvc": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/@oxc-minify/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.136.0.tgz", + "integrity": "sha512-VHgPwd5xibC9UYxBeq7nURTjY+wxhdumllij6JaCIUVfD9IRcrA66SVlJ5fPNoUXcRp8ZXhJfoIZwVz9wAEGJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.76.0.tgz", + "integrity": "sha512-1XJW/16CDmF5bHE7LAyPPmEEVnxSadDgdJz+xiLqBrmC4lfAeuAfRw3HlOygcPGr+AJsbD4Z5sFJMkwjbSZlQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.76.0.tgz", + "integrity": "sha512-yoQwSom8xsB+JdGsPUU0xxmxLKiF2kdlrK7I56WtGKZilixuBf/TmOwNYJYLRWkBoW5l2/pDZOhBm2luwmLiLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.76.0.tgz", + "integrity": "sha512-uRIopPLvr3pf2Xj7f5LKyCuqzIU6zOS+zEIR8UDYhcgJyZHnvBkfrYnfcztyIcrGdQehrFUi3uplmI09E7RdiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.76.0.tgz", + "integrity": "sha512-a0EOFvnOd2FqmDSvH6uWLROSlU6KV/JDKbsYDA/zRLyKcG6HCsmFnPsp8iV7/xr9WMbNgyJi6R5IMpePQlUq7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.76.0.tgz", + "integrity": "sha512-ikRYDHL3fOdZwfJKmcdqjlLgkeNZ3Ez0qM8wAev5zlHZ+lY/Ig7qG5SCqPlvuTu+nNQ6zrFFaKvvt69EBKXU/g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.76.0.tgz", + "integrity": "sha512-dtRv5J5MRCLR7x39K8ufIIW4svIc7gYFUaI0YFXmmeOBhK/K2t/CkguPnDroKtsmXIPHDRtmJ1JJYzNcgJl6Wg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.76.0.tgz", + "integrity": "sha512-IE4iiiggFH2snagQxHrY5bv6dDpRMMat+vdlMN/ibonA65eOmRLp8VLTXnDiNrcla/itJ1L9qGABHNKU+SnE8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.76.0.tgz", + "integrity": "sha512-wi9zQPMDHrBuRuT7Iurfidc9qlZh7cKa5vfYzOWNBCaqJdgxmNOFzvYen02wVUxSWGKhpiPHxrPX0jdRyJ8Npg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.76.0.tgz", + "integrity": "sha512-0tqqu1pqPee2lLGY8vtYlX1L415fFn89e0a3yp4q5N9f03j1rRs0R31qesTm3bt/UK8HYjECZ+56FCVPs2MEMQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.76.0.tgz", + "integrity": "sha512-y36Hh1a5TA+oIGtlc8lT7N9vdHXBlhBetQJW0p457KbiVQ7jF7AZkaPWhESkjHWAsTVKD2OjCa9ZqfaqhSI0FQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.76.0.tgz", + "integrity": "sha512-7/acaG9htovp3gp/J0kHgbItQTuHctl+rbqPPqZ9DRBYTz8iV8kv3QN8t8Or8i/hOmOjfZp9McDoSU1duoR4/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.76.0.tgz", + "integrity": "sha512-AxFt0reY6Q2rfudABmMTFGR8tFFr58NlH2rRBQgcj+F+iEwgJ+jMwAPhXd2y1I2zaI8GspuahedUYQinqxWqjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.76.0.tgz", + "integrity": "sha512-wHdkHdhf6AWBoO8vs5cpoR6zEFY1rB+fXWtq6j/xb9j/lu1evlujRVMkh8IM/M/pOUIrNkna3nzST/mRImiveQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.76.0.tgz", + "integrity": "sha512-G7ZlEWcb2hNwCK3qalzqJoyB6HaTigQ/GEa7CU8sAJ/WwMdG/NnPqiC9IqpEAEy1ARSo4XMALfKbKNuqbSs5mg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.76.0.tgz", + "integrity": "sha512-0jLzzmnu8/mqNhKBnNS2lFUbPEzRdj5ReiZwHGHpjma0+ullmmwP2AqSEqx3ssHDK9CpcEMdKOK2LsbCfhHKIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.76.0.tgz", + "integrity": "sha512-CH3THIrSViKal8yV/Wh3FK0pFhp40nzW1MUDCik9fNuid2D/7JJXKJnfFOAvMxInGXDlvmgT6ACAzrl47TqzkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", + "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", + "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", + "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", + "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", + "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", + "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", + "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", + "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", + "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", + "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", + "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", + "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/binaryen": { + "version": "130.0.0", + "resolved": "https://registry.npmmirror.com/binaryen/-/binaryen-130.0.0.tgz", + "integrity": "sha512-XDrb+zql0RbFPKgj7MuH9zOc78R3Fa/P/VSGnnpdwYsvNZPWjcMYMdAkKCOQEL2A7yqgjSMTDRFp6gfSDW+/QQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "wasm-as": "bin/wasm-as", + "wasm-ctor-eval": "bin/wasm-ctor-eval", + "wasm-dis": "bin/wasm-dis", + "wasm-merge": "bin/wasm-merge", + "wasm-metadce": "bin/wasm-metadce", + "wasm-opt": "bin/wasm-opt", + "wasm-reduce": "bin/wasm-reduce", + "wasm-shell": "bin/wasm-shell", + "wasm2js": "bin/wasm2js" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/componentize-qjs": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/componentize-qjs/-/componentize-qjs-0.4.3.tgz", + "integrity": "sha512-fiOm2Yu33XIDxY9zkzpTfR+gx3kXsmH9UPWPxuR484bVZGMfq12/Yh5HDHkEvpqZwZt2FDgo/Cj7VUIDX0LJEw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "componentize-qjs": "bin/componentize-qjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "@andreiltd/componentize-qjs-binding-darwin-arm64": "0.4.3", + "@andreiltd/componentize-qjs-binding-darwin-x64": "0.4.3", + "@andreiltd/componentize-qjs-binding-linux-arm64-gnu": "0.4.3", + "@andreiltd/componentize-qjs-binding-linux-arm64-musl": "0.4.3", + "@andreiltd/componentize-qjs-binding-linux-x64-gnu": "0.4.3", + "@andreiltd/componentize-qjs-binding-linux-x64-musl": "0.4.3", + "@andreiltd/componentize-qjs-binding-win32-arm64-msvc": "0.4.3", + "@andreiltd/componentize-qjs-binding-win32-x64-msvc": "0.4.3" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "optional": true, + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nanoid": { + "version": "3.3.17", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oxc-minify": { + "version": "0.136.0", + "resolved": "https://registry.npmmirror.com/oxc-minify/-/oxc-minify-0.136.0.tgz", + "integrity": "sha512-u9UvPFTYVwQ/i1kpp/c20S/SbvLaLV0Ngzyhgi0YV4B1SlxM7RgnnePl4vY9N3Z9sTRcozL1MRStI3PlUN9RFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-minify/binding-android-arm-eabi": "0.136.0", + "@oxc-minify/binding-android-arm64": "0.136.0", + "@oxc-minify/binding-darwin-arm64": "0.136.0", + "@oxc-minify/binding-darwin-x64": "0.136.0", + "@oxc-minify/binding-freebsd-x64": "0.136.0", + "@oxc-minify/binding-linux-arm-gnueabihf": "0.136.0", + "@oxc-minify/binding-linux-arm-musleabihf": "0.136.0", + "@oxc-minify/binding-linux-arm64-gnu": "0.136.0", + "@oxc-minify/binding-linux-arm64-musl": "0.136.0", + "@oxc-minify/binding-linux-ppc64-gnu": "0.136.0", + "@oxc-minify/binding-linux-riscv64-gnu": "0.136.0", + "@oxc-minify/binding-linux-riscv64-musl": "0.136.0", + "@oxc-minify/binding-linux-s390x-gnu": "0.136.0", + "@oxc-minify/binding-linux-x64-gnu": "0.136.0", + "@oxc-minify/binding-linux-x64-musl": "0.136.0", + "@oxc-minify/binding-openharmony-arm64": "0.136.0", + "@oxc-minify/binding-wasm32-wasi": "0.136.0", + "@oxc-minify/binding-win32-arm64-msvc": "0.136.0", + "@oxc-minify/binding-win32-ia32-msvc": "0.136.0", + "@oxc-minify/binding-win32-x64-msvc": "0.136.0" + } + }, + "node_modules/oxc-parser": { + "version": "0.76.0", + "resolved": "https://registry.npmmirror.com/oxc-parser/-/oxc-parser-0.76.0.tgz", + "integrity": "sha512-l98B2e9evuhES7zN99rb1QGhbzx25829TJFaKi2j0ib3/K/G5z1FdGYz6HZkrU3U8jdH7v2FC8mX1j2l9JrOUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.76.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm64": "0.76.0", + "@oxc-parser/binding-darwin-arm64": "0.76.0", + "@oxc-parser/binding-darwin-x64": "0.76.0", + "@oxc-parser/binding-freebsd-x64": "0.76.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.76.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.76.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.76.0", + "@oxc-parser/binding-linux-arm64-musl": "0.76.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.76.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.76.0", + "@oxc-parser/binding-linux-x64-gnu": "0.76.0", + "@oxc-parser/binding-linux-x64-musl": "0.76.0", + "@oxc-parser/binding-wasm32-wasi": "0.76.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.76.0", + "@oxc-parser/binding-win32-x64-msvc": "0.76.0" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rolldown": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.2.3.tgz", + "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.143.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.3", + "@rolldown/binding-darwin-arm64": "1.2.3", + "@rolldown/binding-darwin-x64": "1.2.3", + "@rolldown/binding-freebsd-x64": "1.2.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", + "@rolldown/binding-linux-arm64-gnu": "1.2.3", + "@rolldown/binding-linux-arm64-musl": "1.2.3", + "@rolldown/binding-linux-ppc64-gnu": "1.2.3", + "@rolldown/binding-linux-s390x-gnu": "1.2.3", + "@rolldown/binding-linux-x64-gnu": "1.2.3", + "@rolldown/binding-linux-x64-musl": "1.2.3", + "@rolldown/binding-openharmony-arm64": "1.2.3", + "@rolldown/binding-win32-arm64-msvc": "1.2.3", + "@rolldown/binding-win32-x64-msvc": "1.2.3" + } + }, + "node_modules/rolldown/node_modules/@oxc-project/types": { + "version": "0.143.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/examples/wasm-hello/package.json b/examples/wasm-hello/package.json new file mode 100644 index 00000000..74c865a9 --- /dev/null +++ b/examples/wasm-hello/package.json @@ -0,0 +1,18 @@ +{ + "name": "typephp-wasm-hello", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "wasm": "php ../../bin/tpc.php project.yml", + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "@bytecodealliance/preview2-shim": "0.18.0" + }, + "devDependencies": { + "@bytecodealliance/jco": "^1.27.0", + "vite": "^7.1.0" + } +} diff --git a/examples/wasm-hello/project.yml b/examples/wasm-hello/project.yml new file mode 100644 index 00000000..863c1d40 --- /dev/null +++ b/examples/wasm-hello/project.yml @@ -0,0 +1,10 @@ +name: wasm-hello +mode: library +build-dir: build +output: component/wasm-hello.wasm +sources: + - src +wasm: browser +wasm-browser-dir: generated +wasm-package: typephp:wasm-hello@1.0.0 +wasm-world: wasm-hello diff --git a/examples/wasm-hello/public/fetch-demo.json b/examples/wasm-hello/public/fetch-demo.json new file mode 100644 index 00000000..5c97e4fb --- /dev/null +++ b/examples/wasm-hello/public/fetch-demo.json @@ -0,0 +1,4 @@ +{ + "message": "Hello from browser fetch via PHP file_get_contents()", + "runtime": "WASI HTTP 0.2 + JSPI" +} diff --git a/examples/wasm-hello/src/WasiDemo.php b/examples/wasm-hello/src/WasiDemo.php new file mode 100644 index 00000000..02e49324 --- /dev/null +++ b/examples/wasm-hello/src/WasiDemo.php @@ -0,0 +1,145 @@ + [ + 'php' => phpversion(), + 'platform' => php_uname(), + 'integerBits' => PHP_INT_SIZE * 8, + 'extensions' => get_loaded_extensions(), + ], + 'clock' => [ + 'timestamp' => time(), + 'iso8601' => date('Y-m-d H:i:s T'), + 'microtime' => microtime(true), + ], + 'random' => [ + 'integer' => random_int(100000, 999999), + 'token' => bin2hex(random_bytes(8)), + ], + 'input' => [ + 'argc' => count($argv), + 'argv' => $argv, + 'greeting' => $greeting, + 'stdin' => trim($stdin), + ], + 'filesystem' => self::filesystemReport(), + 'http' => self::httpReport(), + 'precision' => self::precisionReport(), + 'capabilities' => [ + 'supported' => ['arguments', 'environment', 'stdin/stdout/stderr', 'clock', 'random', 'filesystem', 'HTTP GET'], + 'disabled' => ['raw sockets', 'process', 'signals', 'shell', 'Fiber', 'Generator'], + ], + ]; + } + + public static function extensionInfo(string $name): array + { + if (!extension_loaded($name)) { + throw new InvalidArgumentException("PHP extension '{$name}' is not loaded"); + } + + $extension = new ReflectionExtension($name); + $functions = get_extension_funcs($name); + if ($functions === false) { + $functions = []; + } + + return [ + 'name' => $extension->getName(), + 'version' => $extension->getVersion() ?: 'built-in', + 'persistent' => $extension->isPersistent(), + 'temporary' => $extension->isTemporary(), + 'dependencies' => $extension->getDependencies(), + 'iniEntries' => $extension->getINIEntries(), + 'constants' => array_keys($extension->getConstants()), + 'functions' => array_values($functions), + 'classes' => $extension->getClassNames(), + ]; + } + + private static function httpReport(): array + { + $url = getenv('TYPEPHP_FETCH_URL'); + if ($url === false || $url === '') { + return ['ok' => false, 'url' => '', 'bytes' => 0, 'preview' => 'No URL configured']; + } + + $body = file_get_contents($url); + if ($body === false) { + return ['ok' => false, 'url' => $url, 'bytes' => 0, 'preview' => 'Request failed']; + } + + return [ + 'ok' => true, + 'url' => $url, + 'bytes' => strlen($body), + 'preview' => trim(substr($body, 0, 80)), + ]; + } + + private static function filesystemReport(): array + { + $directory = '/workspace'; + if (!is_dir($directory)) { + mkdir($directory); + } + + $counterFile = $directory . '/run-count.txt'; + $counter = 0; + if (file_exists($counterFile)) { + $counter = (int) trim((string) file_get_contents($counterFile)); + } + $counter++; + file_put_contents($counterFile, (string) $counter); + + $messageFile = $directory . '/hello.txt'; + $message = 'TypePHP wrote this file during browser run #' . $counter; + file_put_contents($messageFile, $message); + + $files = scandir($directory); + if ($files === false) { + $files = []; + } + $visibleFiles = array_values(array_diff($files, ['.', '..'])); + + return [ + 'run' => $counter, + 'readback' => (string) file_get_contents($messageFile), + 'files' => $visibleFiles, + ]; + } + + private static function precisionReport(): array + { + $big = std::bigInt('123456789012345678901234567890'); + $bigResult = ($big * std::bigInt(1000000) + std::bigInt(42))->toString(); + + $price = std::decimal('199.95'); + $taxRate = std::decimal('0.0825'); + $decimalResult = ($price * (std::decimal(1) + $taxRate))->toString(); + + $pi = std::bigFloat('3.141592653589793238462643383279502884197'); + $radius = std::bigFloat(12); + $bigFloatResult = ($pi * $radius * $radius)->toString(); + + return [ + 'bigint' => $bigResult, + 'decimal' => $decimalResult, + 'bigfloat' => $bigFloatResult, + ]; + } +} diff --git a/examples/wasm-hello/src/main.php b/examples/wasm-hello/src/main.php new file mode 100644 index 00000000..041729b0 --- /dev/null +++ b/examples/wasm-hello/src/main.php @@ -0,0 +1,23 @@ + span:first-child { display: block; margin: 0 0 8px; color: #c7d5e7; font-size: .82rem; font-weight: 680; } +label.wide { grid-column: 1 / -1; } +input, textarea { width: 100%; border: 1px solid #ffffff13; border-radius: 12px; outline: 0; background: #030b14a8; color: #eaf2ff; padding: 13px 15px; transition: border .2s, box-shadow .2s; } +input:focus, textarea:focus { border-color: #66ddff70; box-shadow: 0 0 0 3px #56dfff0d; } +textarea { resize: vertical; } +label small { display: block; margin-top: 7px; color: #62758e; font-size: .7rem; } +.actions { padding-top: 23px; border-top: 1px solid #ffffff0c; } +.switch-row { display: flex; align-items: center; gap: 12px; cursor: pointer; } +.switch-row input { position: absolute; opacity: 0; pointer-events: none; } +.switch { position: relative; width: 44px; height: 24px; border-radius: 20px; background: #28384b; transition: .2s; } +.switch i { position: absolute; width: 18px; height: 18px; left: 3px; top: 3px; border-radius: 50%; background: #8292a7; transition: .2s; } +.switch-row input:checked + .switch { background: #27bb88; }.switch-row input:checked + .switch i { transform: translateX(20px); background: #fff; } +.switch-row > span:last-child strong, .switch-row > span:last-child small { display: block; }.switch-row strong { font-size: .8rem; }.switch-row small { margin-top: 3px; color: #687b94; font-size: .68rem; } +.button-row { display: flex; gap: 10px; }.button { border: 0; border-radius: 12px; padding: 12px 17px; color: #dce9f8; cursor: pointer; font-size: .8rem; font-weight: 750; transition: transform .18s, opacity .18s; }.button:hover { transform: translateY(-1px); }.button:disabled { opacity: .45; cursor: wait; transform: none; }.button.secondary { background: #ffffff0a; border: 1px solid #ffffff12; }.button.primary { min-width: 150px; display: flex; justify-content: space-between; background: linear-gradient(100deg, #25b9dc, #725ee8); box-shadow: 0 9px 30px #4584df2e; color: #fff; } +.results { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin: 16px 0; } +.feature-card { position: relative; min-height: 178px; padding: 21px; overflow: hidden; border: 1px solid #ffffff10; border-radius: 20px; background: #0a1725d9; } +.feature-card::after { content: ""; position: absolute; width: 110px; height: 110px; right: -42px; bottom: -52px; border-radius: 50%; background: var(--accent); filter: blur(45px); opacity: .18; } +.accent-cyan { --accent: #45defd; }.accent-violet { --accent: #9a78ff; }.accent-amber { --accent: #ffb860; }.accent-green { --accent: #4ee1a3; } +.icon { display: grid; place-items: center; width: 35px; height: 35px; border-radius: 10px; background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); font-size: 1.15rem; } +.feature-card p { margin: 22px 0 7px; color: #70839c; font-size: .7rem; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; }.feature-card h3 { margin: 0; overflow: hidden; color: #e8f1fe; font: 650 1.02rem ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }.feature-card small { display: block; margin-top: 8px; overflow: hidden; color: #667991; font-size: .68rem; text-overflow: ellipsis; white-space: nowrap; } +.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }.data-panel, .precision-panel { padding: 27px; } +dl { margin: 22px 0 0; } dl div { display: grid; grid-template-columns: 92px 1fr; gap: 14px; padding: 13px 0; border-top: 1px solid #ffffff0b; } dt { color: #71849d; font: 700 .71rem ui-monospace, monospace; text-transform: uppercase; } dd { margin: 0; overflow-wrap: anywhere; color: #bfd0e5; font: .78rem/1.55 ui-monospace, monospace; } +.extensions-panel { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 22px; margin-top: 16px; padding: 27px; } +.extensions-panel > strong { color: #79e8ff; font: 700 .75rem ui-monospace, monospace; } +.extension-list { display: flex; grid-column: 1 / -1; flex-wrap: wrap; gap: 8px; padding-top: 20px; border-top: 1px solid #ffffff0b; } +.extension-list > span, .extension-button { padding: 7px 11px; border: 1px solid #62daf329; border-radius: 99px; background: #38c5e70c; color: #a9c9dc; font: 650 .7rem ui-monospace, monospace; } +.extension-button { cursor: pointer; transition: border-color .18s, background .18s, color .18s, transform .18s; } +.extension-button:hover, .extension-button:focus-visible { border-color: #79e8ff88; background: #38c5e71c; color: #e7faff; outline: 0; transform: translateY(-1px); } +.extension-button.active { border-color: #a789ff99; background: #8e6fff20; color: #f0eaff; box-shadow: 0 0 0 3px #8e6fff0c; } +.extension-detail { grid-column: 1 / -1; min-height: 112px; padding: 22px; border: 1px solid #ffffff0c; border-radius: 18px; background: #030b1470; } +.extension-empty { display: flex; flex-direction: column; gap: 7px; color: #6f829a; font-size: .76rem; } +.extension-empty strong { color: #a9bad0; font-size: .88rem; } +.extension-detail-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; margin-bottom: 20px; } +.extension-detail-header h3 { margin: 0 0 6px; color: #eef6ff; font: 720 1.2rem ui-monospace, monospace; } +.extension-detail-header p { margin: 0; color: #71849d; font: .72rem ui-monospace, monospace; } +.extension-flags { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 7px; } +.extension-flags span { padding: 5px 8px; border-radius: 7px; background: #ffffff09; color: #91a6c0; font: 650 .64rem ui-monospace, monospace; } +.extension-groups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } +.extension-group { min-width: 0; padding: 15px; border: 1px solid #ffffff0a; border-radius: 13px; background: #0915228a; } +.extension-group h4 { margin: 0 0 11px; color: #7890ab; font-size: .68rem; letter-spacing: .09em; text-transform: uppercase; } +.extension-items { display: flex; flex-wrap: wrap; gap: 6px; max-height: 180px; overflow: auto; } +.extension-items code { max-width: 100%; padding: 4px 7px; overflow: hidden; border-radius: 6px; background: #ffffff07; color: #b9cadf; font: .66rem ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; } +.extension-items dl { width: 100%; margin: 0; } +.extension-items dl div { grid-template-columns: minmax(90px, .7fr) 1fr; padding: 7px 0; } +.extension-items dl div:first-child { border-top: 0; } +.extension-loading { color: #79e8ff; font: .76rem ui-monospace, monospace; } +.console { margin-top: 16px; padding: 0; overflow: hidden; } .console summary { display: flex; justify-content: space-between; padding: 19px 24px; cursor: pointer; color: #a9bad0; font-size: .8rem; } kbd { border: 1px solid #ffffff12; border-radius: 6px; padding: 3px 7px; background: #ffffff08; color: #63778f; font: .65rem ui-monospace, monospace; }.console pre { max-height: 420px; margin: 0; padding: 23px; overflow: auto; border-top: 1px solid #ffffff0b; background: #02081099; color: #8fe9c0; font: .73rem/1.6 ui-monospace, monospace; } +footer { display: flex; justify-content: space-between; gap: 20px; padding: 28px 4px 0; color: #50647d; font-size: .68rem; } +@media (max-width: 820px) { .hero { grid-template-columns: 1fr; padding-bottom: 40px; }.runtime-orbit { display: none; }.results { grid-template-columns: 1fr 1fr; }.detail-grid, .extension-groups { grid-template-columns: 1fr; } } +@media (max-width: 560px) { .shell { width: min(100% - 24px, 1180px); padding-top: 38px; }h1 { font-size: 3.6rem; }.control-panel { padding: 21px; }.form-grid, .results, .extensions-panel { grid-template-columns: 1fr; }.wide { grid-column: auto !important; }.actions, .panel-heading, footer { align-items: stretch; flex-direction: column; }.extension-list { grid-column: auto; }.button-row { width: 100%; }.button { flex: 1; } } diff --git a/examples/wasm-hello/typephp-worker.mjs b/examples/wasm-hello/typephp-worker.mjs new file mode 100644 index 00000000..9f03e122 --- /dev/null +++ b/examples/wasm-hello/typephp-worker.mjs @@ -0,0 +1,154 @@ +import { + _setStderr, + _setStdin, + _setStdout, +} from '@bytecodealliance/preview2-shim/cli'; +import { _setFileData } from '@bytecodealliance/preview2-shim/filesystem'; +import { WASIShim } from '@bytecodealliance/preview2-shim/instantiation'; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); +let runtime = null; +let fileData = null; +let persistent = false; +let storageName = 'typephp-wasi-filesystem.json'; +let extensionQueue = Promise.resolve(); + +function outputHandler(stream) { + return { + write(bytes) { + self.postMessage({ type: stream, data: decoder.decode(bytes, { stream: true }) }); + return BigInt(bytes.byteLength); + }, + blockingFlush() {}, + }; +} + +function inputHandler(text) { + const bytes = encoder.encode(text); + let offset = 0; + return { + blockingRead(length) { + if (offset >= bytes.byteLength) { + throw { tag: 'closed' }; + } + const end = Math.min(offset + Number(length), bytes.byteLength); + const chunk = bytes.slice(offset, end); + offset = end; + return chunk; + }, + }; +} + +function encodeFileData(value) { + return JSON.stringify(value, (_key, item) => item instanceof Uint8Array + ? { typephpBytes: Array.from(item) } + : item); +} + +function decodeFileData(value) { + return JSON.parse(value, (_key, item) => item && Array.isArray(item.typephpBytes) + ? new Uint8Array(item.typephpBytes) + : item); +} + +async function openPersistentFile(name) { + if (!navigator.storage?.getDirectory) { + throw new Error('OPFS is not available in this browser'); + } + const root = await navigator.storage.getDirectory(); + const handle = await root.getFileHandle(name, { create: true }); + if (typeof handle.createSyncAccessHandle !== 'function') { + throw new Error('OPFS synchronous access requires a dedicated Worker'); + } + return handle.createSyncAccessHandle(); +} + +async function loadFileData(storageName) { + const access = await openPersistentFile(storageName); + try { + const size = access.getSize(); + if (size === 0) { + return { dir: {} }; + } + const bytes = new Uint8Array(size); + access.read(bytes, { at: 0 }); + return decodeFileData(decoder.decode(bytes)); + } finally { + access.close(); + } +} + +async function saveFileData(storageName, fileData) { + const access = await openPersistentFile(storageName); + try { + const bytes = encoder.encode(encodeFileData(fileData)); + access.truncate(0); + access.write(bytes, { at: 0 }); + access.flush(); + } finally { + access.close(); + } +} + +async function start(data) { + try { + if (typeof WebAssembly.Suspending !== 'function' + || typeof WebAssembly.promising !== 'function') { + throw new Error('This browser does not support WebAssembly JSPI, which is required for blocking WASI I/O'); + } + persistent = data.persistent === true; + storageName = String(data.storageName || 'typephp-wasi-filesystem.json'); + fileData = persistent ? await loadFileData(storageName) : { dir: {} }; + _setFileData(fileData); + _setStdin(inputHandler(String(data.stdin || ''))); + _setStdout(outputHandler('stdout')); + _setStderr(outputHandler('stderr')); + + const args = ['typephp.wasm', ...(Array.isArray(data.args) ? data.args.map(String) : [])]; + const env = data.env && typeof data.env === 'object' ? { ...data.env } : {}; + env.TYPEPHP_FETCH_URL ??= new URL('/fetch-demo.json', self.location.href).href; + const wasi = new WASIShim({ + sandbox: { + args, + env, + enableNetwork: true, + }, + }); + const { instantiate } = await import('./generated/program.js'); + const component = await instantiate(null, wasi.getImportObject()); + runtime = await component.api.createRuntime(); + const json = await runtime.getDemoReport( + JSON.stringify(Array.isArray(data.args) ? data.args.map(String) : []), + String(env.DEMO_GREETING || ''), + String(data.stdin || ''), + ); + if (persistent) { + await saveFileData(storageName, fileData); + } + self.postMessage({ type: 'report', json }); + } catch (error) { + self.postMessage({ type: 'error', error: error?.stack || String(error) }); + } +} + +async function getExtensionInfo(extension) { + if (!runtime) { + throw new Error('TypePHP runtime is not ready'); + } + const json = await runtime.getExtensionInfo(extension); + self.postMessage({ type: 'extension-info', extension, json }); +} + +self.onmessage = ({ data }) => { + if (data?.type === 'run') { + start(data); + } else if (data?.type === 'extension-info') { + const extension = String(data.extension || ''); + extensionQueue = extensionQueue + .then(() => getExtensionInfo(extension)) + .catch((error) => { + self.postMessage({ type: 'extension-error', extension, error: error?.stack || String(error) }); + }); + } +}; diff --git a/examples/wasm-hello/vite.config.js b/examples/wasm-hello/vite.config.js new file mode 100644 index 00000000..fd2b8d70 --- /dev/null +++ b/examples/wasm-hello/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + host: '127.0.0.1', + }, + worker: { + format: 'es', + }, +}); diff --git a/package.php b/package.php index 40aaa165..3d95d446 100755 --- a/package.php +++ b/package.php @@ -595,13 +595,34 @@ function packageUnixLike(): void $binary = 'tpc'; $versionFile = 'version.txt'; + $phpxSourceDir = getenv('PHPX_HOME'); + if (!is_string($phpxSourceDir) || $phpxSourceDir === '' || !is_dir($phpxSourceDir)) { + $phpxSourceDir = 'vendor/swoole/phpx'; + } + $phpxSourceDir = realpath($phpxSourceDir) ?: rtrim($phpxSourceDir, '/\\'); + $wasiSdkSourceRoot = $phpxSourceDir . '/wasm/wasm32-wasip2'; + $wasiSdkPackageRoot = 'vendor/swoole/phpx/wasm/wasm32-wasip2'; $requiredFiles = [ $binary, 'composer.json', 'README.md', 'LICENSE.md', 'examples/hello.php', + 'wasm/build-typephp-program.sh', + "{$wasiSdkSourceRoot}/.typephp-wasi-sdk-abi", + "{$wasiSdkSourceRoot}/include/php/main/php.h", + "{$wasiSdkSourceRoot}/include/php/main/php_config.h", + "{$wasiSdkSourceRoot}/include/php/Zend/zend_config.h", + "{$wasiSdkSourceRoot}/include/php/ext/date/lib/timelib_config.h", + "{$wasiSdkSourceRoot}/include/phpx/phpx.h", + "{$wasiSdkSourceRoot}/include/phpx/typephp_helper.h", + "{$wasiSdkSourceRoot}/include/gmp.h", + "{$wasiSdkSourceRoot}/include/mpfr.h", + "{$wasiSdkSourceRoot}/include/decimal.hh", ]; + foreach (['libphp.a', 'libphpx.a', 'libgmp.a', 'libgmpxx.a', 'libmpfr.a', 'libmpdec.a', 'libmpdec++.a'] as $library) { + $requiredFiles[] = "{$wasiSdkSourceRoot}/lib/{$library}"; + } foreach ($requiredFiles as $file) { if (!is_file($file)) { throw new RuntimeException("Required package file not found: {$file}"); @@ -682,6 +703,8 @@ function packageUnixLike(): void mustCopy('LICENSE.md', "{$topLevelDir}/LICENSE.md"); mustCreateDirectory("{$topLevelDir}/examples"); mustCopy('examples/hello.php', "{$topLevelDir}/examples/hello.php"); + copyDirectory('wasm', "{$topLevelDir}/wasm"); + copyDirectory("{$phpxSourceDir}/wasm", "{$topLevelDir}/vendor/swoole/phpx/wasm"); if (is_file($outputFile) && !unlink($outputFile)) { throw new RuntimeException("Unable to remove existing archive: {$outputFile}"); @@ -717,7 +740,14 @@ function packageUnixLike(): void "{$topLevelDir}/README.md", "{$topLevelDir}/LICENSE.md", "{$topLevelDir}/examples/hello.php", + "{$topLevelDir}/wasm/build-typephp-program.sh", + "{$topLevelDir}/{$wasiSdkPackageRoot}/.typephp-wasi-sdk-abi", + "{$topLevelDir}/{$wasiSdkPackageRoot}/include/php/main/php.h", + "{$topLevelDir}/{$wasiSdkPackageRoot}/include/phpx/phpx.h", ]; + foreach (['libphp.a', 'libphpx.a', 'libgmp.a', 'libgmpxx.a', 'libmpfr.a', 'libmpdec.a', 'libmpdec++.a'] as $library) { + $requiredEntries[] = "{$topLevelDir}/{$wasiSdkPackageRoot}/lib/{$library}"; + } $verificationZip = new ZipArchive(); if ($verificationZip->open($outputFile) !== true) { throw new RuntimeException("Unable to verify archive: {$outputFile}"); diff --git a/phpunit/code/wasi-backtick.php b/phpunit/code/wasi-backtick.php new file mode 100644 index 00000000..3158b215 --- /dev/null +++ b/phpunit/code/wasi-backtick.php @@ -0,0 +1,7 @@ + yield 1; + var_dump($generator); +} diff --git a/phpunit/code/wasi-generator-closure.php b/phpunit/code/wasi-generator-closure.php new file mode 100644 index 00000000..cbab8509 --- /dev/null +++ b/phpunit/code/wasi-generator-closure.php @@ -0,0 +1,9 @@ +originalPhpxHome = getenv('PHPX_HOME'); + $this->phpxHome = sys_get_temp_dir() . '/typephp-phpx-locator-' . bin2hex(random_bytes(6)); + mkdir($this->phpxHome, 0777, true); + } + + protected function tearDown(): void + { + if ($this->originalPhpxHome === false) { + putenv('PHPX_HOME'); + } else { + putenv('PHPX_HOME=' . $this->originalPhpxHome); + } + rmdir($this->phpxHome); + } + + public function testPhpxHomeHasPriorityAndReturnsAnAbsolutePath(): void + { + putenv('PHPX_HOME=' . $this->phpxHome); + + self::assertSame(realpath($this->phpxHome), PhpxLocator::resolve('/not-used')); + } + + public function testInvalidPhpxHomeFallsBackToComposerInstallation(): void + { + putenv('PHPX_HOME=' . $this->phpxHome . '/missing'); + $projectRoot = dirname(__DIR__, 3); + + self::assertSame( + realpath($projectRoot . '/vendor/swoole/phpx'), + PhpxLocator::resolve($projectRoot), + ); + } +} diff --git a/phpunit/src/Build/WasiProjectConfigTest.php b/phpunit/src/Build/WasiProjectConfigTest.php new file mode 100644 index 00000000..dcabc236 --- /dev/null +++ b/phpunit/src/Build/WasiProjectConfigTest.php @@ -0,0 +1,177 @@ +directory = sys_get_temp_dir() . '/typephp-wasi-project-' . bin2hex(random_bytes(6)); + mkdir($this->directory . '/src', 0777, true); + file_put_contents($this->directory . '/src/main.php', 'directory . '/src/main.php'); + @unlink($this->directory . '/project.yml'); + @rmdir($this->directory . '/src'); + @rmdir($this->directory); + } + + public function testProjectPathsAreResolvedRelativeToYaml(): void + { + file_put_contents($this->directory . '/project.yml', <<<'YAML' +name: demo +mode: bin +target-platform: wasm32-wasip2 +build-dir: cache/build +output: dist/demo.wasm +sources: + - src +wasm: browser +wasm-browser-dir: web/generated +YAML); + + $config = WasiProjectConfig::load( + 'project.yml', + null, + $this->directory, + '/default-build', + ); + + self::assertSame(realpath($this->directory . '/project.yml'), $config->input); + self::assertSame($this->directory . '/cache/build', $config->buildDir); + self::assertSame($this->directory . '/dist/demo.wasm', $config->output); + self::assertSame($this->directory . '/web/generated', $config->browserDir); + self::assertSame('browser', $config->profile); + self::assertSame('command', $config->mode); + self::assertSame('typephp:demo@1.0.0', $config->package); + self::assertSame('demo', $config->world); + self::assertTrue(WasiProjectConfig::isWasmEnabled($this->directory . '/project.yml')); + } + + public function testSingleFileKeepsBuilderOutputDefaults(): void + { + $config = WasiProjectConfig::load( + 'src/main.php', + 'custom-build', + $this->directory, + '/default-build', + ); + + self::assertSame($this->directory . '/custom-build', $config->buildDir); + self::assertNull($config->output); + self::assertNull($config->browserDir); + self::assertSame('component', $config->profile); + self::assertSame('command', $config->mode); + } + + public function testLibraryModeAndWitIdentityAreAccepted(): void + { + file_put_contents($this->directory . '/project.yml', <<<'YAML' +name: calculator +mode: library +wasm: component +wasm-package: acme:calculator@2.1.0 +wasm-world: calculator-api +sources: + - src +YAML); + + $config = WasiProjectConfig::load('project.yml', null, $this->directory, '/default-build'); + + self::assertSame('library', $config->mode); + self::assertSame('acme:calculator@2.1.0', $config->package); + self::assertSame('calculator-api', $config->world); + } + + public function testComponentDoesNotRequireTargetPlatform(): void + { + file_put_contents($this->directory . '/project.yml', <<<'YAML' +name: demo +wasm: component +wasm-browser-dir: generated +sources: + - src +YAML); + + $config = WasiProjectConfig::load( + 'project.yml', + null, + $this->directory, + '/default-build', + ); + + self::assertSame('component', $config->profile); + self::assertNull($config->browserDir); + } + + public function testCliCanSelectBrowserOutput(): void + { + file_put_contents($this->directory . '/project.yml', <<<'YAML' +name: demo +wasm: component +wasm-browser-dir: generated +sources: + - src +YAML); + + $config = WasiProjectConfig::load( + 'project.yml', + null, + $this->directory, + '/default-build', + 'browser', + ); + + self::assertSame('browser', $config->profile); + self::assertSame($this->directory . '/generated', $config->browserDir); + } + + public function testPreviewOneProjectIsRejected(): void + { + file_put_contents($this->directory . '/project.yml', <<<'YAML' +target-platform: wasm32-wasi +sources: + - src +YAML); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('must target wasm32-wasip2'); + WasiProjectConfig::load('project.yml', null, $this->directory, '/default-build'); + } + + public function testBooleanWasmProfileIsRejected(): void + { + file_put_contents($this->directory . '/project.yml', <<<'YAML' +wasm: true +sources: + - src +YAML); + + self::assertTrue(WasiProjectConfig::isWasmEnabled($this->directory . '/project.yml')); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('must be `component` or `browser`'); + WasiProjectConfig::load('project.yml', null, $this->directory, '/default-build'); + } + + public function testUnsupportedWasmProfileAliasIsRejected(): void + { + file_put_contents($this->directory . '/project.yml', <<<'YAML' +wasm: web +sources: + - src +YAML); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('expected browser or component'); + WasiProjectConfig::load('project.yml', null, $this->directory, '/default-build'); + } +} diff --git a/phpunit/src/Build/WasiToolchainTest.php b/phpunit/src/Build/WasiToolchainTest.php new file mode 100644 index 00000000..d620c9ef --- /dev/null +++ b/phpunit/src/Build/WasiToolchainTest.php @@ -0,0 +1,108 @@ +directory = sys_get_temp_dir() . '/typephp-wasi-tools-' . bin2hex(random_bytes(6)); + mkdir($this->directory, 0777, true); + $this->originalPath = getenv('PATH'); + } + + protected function tearDown(): void + { + putenv('PATH=' . ($this->originalPath !== false ? $this->originalPath : '')); + foreach (glob($this->directory . '/*') ?: [] as $file) { + unlink($file); + } + rmdir($this->directory); + } + + public function testDetectsSupportedToolsOnlyFromPath(): void + { + $this->installFakeTools(22, 47, 1, 'wasm32-unknown-wasip2'); + putenv('PATH=' . $this->directory); + + $tools = (new WasiToolchain())->detect(); + + $this->assertSame($this->directory . '/wasm32-wasip2-clang++', $tools['clang++']); + $this->assertSame($this->directory . '/wasmtime', $tools['wasmtime']); + $this->assertSame($this->directory . '/jco', $tools['jco']); + $this->assertSame('wasm32-unknown-wasip2', $tools['target']); + $this->assertSame('22.0.0', $tools['clang-version']); + $this->assertSame('47.0.0', $tools['wasmtime-version']); + $this->assertSame('1.0.0', $tools['jco-version']); + } + + public function testRejectsMissingTool(): void + { + putenv('PATH=' . $this->directory); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('`wasm32-wasip2-clang` was not found in PATH'); + (new WasiToolchain())->detect(); + } + + public function testComponentOnlyProfileDoesNotRequireJco(): void + { + $this->installFakeTools(22, 47, 1, 'wasm32-unknown-wasip2'); + unlink($this->directory . '/jco'); + putenv('PATH=' . $this->directory); + + $tools = (new WasiToolchain())->detect(false); + + $this->assertArrayNotHasKey('jco', $tools); + $this->assertArrayNotHasKey('jco-version', $tools); + $this->assertSame('wasm32-unknown-wasip2', $tools['target']); + } + + public function testRejectsOldLlvm(): void + { + $this->installFakeTools(21, 47, 1, 'wasm32-unknown-wasip2'); + putenv('PATH=' . $this->directory); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('`wasm32-wasip2-clang` 21 is too old'); + (new WasiToolchain())->detect(); + } + + public function testRejectsNonWasiClangTarget(): void + { + $this->installFakeTools(22, 47, 1, 'x86_64-unknown-linux-gnu'); + putenv('PATH=' . $this->directory); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('not configured for wasm32-unknown-wasip2'); + (new WasiToolchain())->detect(); + } + + private function installFakeTools(int $llvmMajor, int $wasmtimeMajor, int $jcoMajor, string $target): void + { + foreach (['wasm32-wasip2-clang', 'llvm-ar', 'llvm-ranlib', 'llvm-nm'] as $tool) { + $this->writeExecutable($tool, "#!/bin/sh\necho 'LLVM version {$llvmMajor}.0.0'\n"); + } + $this->writeExecutable('wasm-component-ld', "#!/bin/sh\necho 'wasm-component-ld version 0.5.22'\n"); + $this->writeExecutable( + 'wasm32-wasip2-clang++', + "#!/bin/sh\nif [ \"\$1\" = '--print-target-triple' ]; then echo '{$target}'; else echo 'clang version {$llvmMajor}.0.0'; fi\n", + ); + $this->writeExecutable('wasmtime', "#!/bin/sh\necho 'wasmtime {$wasmtimeMajor}.0.0'\n"); + $this->writeExecutable('jco', "#!/bin/sh\necho 'jco {$jcoMajor}.0.0'\n"); + } + + private function writeExecutable(string $name, string $contents): void + { + $path = $this->directory . '/' . $name; + file_put_contents($path, $contents); + chmod($path, 0755); + } +} diff --git a/phpunit/src/Build/WasmInterfaceGeneratorTest.php b/phpunit/src/Build/WasmInterfaceGeneratorTest.php new file mode 100644 index 00000000..ae9b6085 --- /dev/null +++ b/phpunit/src/Build/WasmInterfaceGeneratorTest.php @@ -0,0 +1,89 @@ +wasmExport = true; + $function->displayName = 'App\\greetUser'; + $function->returnTypeStr = 'string'; + $argument = new ArgInfo(); + $argument->name = 'name'; + $argument->phpName = 'name'; + $argument->type = Type::STR; + $function->argInfoList = [$argument]; + + $generator = new WasmInterfaceGenerator(); + $manifest = $generator->buildManifest( + [$function], + 'acme:demo@1.0.0', + 'demo', + static fn (): string => 'php_app__greetuser', + ); + + self::assertSame('greet-user', $manifest['functions'][0]['name']); + self::assertSame('php_app__greetuser', $manifest['functions'][0]['cpp-symbol']); + self::assertSame('string', $manifest['functions'][0]['parameters'][0]['wit-type']); + self::assertStringContainsString( + 'greet-user: func(name: string) -> result;', + $generator->renderWit($manifest), + ); + $adapter = $generator->renderCppAdapter($manifest); + self::assertStringContainsString( + 'extern "C" bool exports_acme_demo_api_method_runtime_greet_user(', + $adapter, + ); + self::assertStringContainsString('auto result = php_app__greetuser(', $adapter); + self::assertStringContainsString('catch (zend_object *exception)', $adapter); + self::assertSame( + "acme:demo/api@1.0.0#create-runtime\n" + . "acme:demo/api@1.0.0#[method]runtime.greet-user\n", + $generator->renderJcoAsyncExports($manifest), + ); + } + + public function testRejectsExportNameCollisions(): void + { + $first = new FunctionDef('first', Type::VOID, ''); + $first->wasmExport = true; + $first->wasmExportName = 'same-name'; + $second = new FunctionDef('second', Type::VOID, ''); + $second->wasmExport = true; + $second->wasmExportName = 'same-name'; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('WasmExport name collision'); + (new WasmInterfaceGenerator())->buildManifest( + [$first, $second], + 'acme:demo@1.0.0', + 'demo', + static fn (FunctionDef $function): string => $function->name, + ); + } + + public function testRejectsUntypedAbi(): void + { + $function = new FunctionDef('dynamicValue', Type::VAR, ''); + $function->wasmExport = true; + $function->returnTypeUndeclared = true; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('must declare a return type'); + (new WasmInterfaceGenerator())->buildManifest( + [$function], + 'acme:demo@1.0.0', + 'demo', + static fn (): string => 'php_dynamicvalue', + ); + } +} diff --git a/phpunit/src/CompileTimeAttributeRegistryTest.php b/phpunit/src/CompileTimeAttributeRegistryTest.php index 09395985..a5a31a2e 100644 --- a/phpunit/src/CompileTimeAttributeRegistryTest.php +++ b/phpunit/src/CompileTimeAttributeRegistryTest.php @@ -8,7 +8,7 @@ final class CompileTimeAttributeRegistryTest extends TestCase public function testEveryBuiltInCompileTimeAttributeHasCompleteMetadata(): void { $expected = [ - 'MethodsFor', 'NoExport', 'Getter', 'Setter', 'With', 'Printer', 'Arrayable', + 'MethodsFor', 'NoExport', 'WasmExport', 'Getter', 'Setter', 'With', 'Printer', 'Arrayable', 'NotNull', 'NotEmpty', 'Validate', 'Override', 'MustUse', 'Hot', 'Cold', 'Constructor', ]; $this->assertSame($expected, CompileTimeAttributeRegistry::names()); @@ -21,6 +21,7 @@ final class CompileTimeAttributeRegistryTest extends TestCase $this->assertFalse($definition['repeatable']); } $this->assertNotContains('NoExport', CompileTimeAttributeRegistry::names(true)); + $this->assertNotContains('WasmExport', CompileTimeAttributeRegistry::names(true)); $this->assertContains('Getter', CompileTimeAttributeRegistry::names(true)); $this->assertContains('Override', CompileTimeAttributeRegistry::names(true)); $this->assertSame( diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index b94c9781..4fe7b455 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -99,6 +99,19 @@ class CompilerBaseApiTest extends TestCase return $projectFile; } + public function testWasiTargetDetection(): void + { + foreach (['wasm32-wasip2', 'wasm32-unknown-wasip2'] as $target) { + $this->setPropertyValue('targetPlatform', $target); + $this->assertTrue($this->compiler->isWasiTarget(), $target); + } + + foreach (['', 'wasm32-wasi', 'wasm32-wasip1', 'wasm32-wasip1-threads', 'wasm32-unknown-unknown', 'aarch64-linux-gnu'] as $target) { + $this->setPropertyValue('targetPlatform', $target); + $this->assertFalse($this->compiler->isWasiTarget(), $target); + } + } + private function createFakeClangFormat(string $binDir, string $logFile): void { mkdir($binDir, 0777, true); diff --git a/phpunit/src/Generator/FiberGeneratorTest.php b/phpunit/src/Generator/FiberGeneratorTest.php index c06a4f52..e446c596 100644 --- a/phpunit/src/Generator/FiberGeneratorTest.php +++ b/phpunit/src/Generator/FiberGeneratorTest.php @@ -8,6 +8,19 @@ use TypePhp\Exception\TestError; class FiberGeneratorTest extends TestCase { + public function testWasiTargetRejectsGeneratorDuringPreparation(): void + { + $compiler = CompilerTest::create(ROOT_PATH); + $reflection = new \ReflectionClass($compiler); + $reflection->getProperty('targetPlatform')->setValue($compiler, 'wasm32-wasip2'); + $file = __DIR__ . '/../../code/generator-conversion-error.php'; + $compiler->addFiles([$file]); + + $this->expectException(TestError::class); + $this->expectExceptionMessage('Fiber and Generator are not supported by the WASI target'); + $compiler->prepareFile($file); + } + public function testCompilerStateIsRestoredAfterGeneratorConversionError(): void { $compiler = CompilerTest::create(ROOT_PATH); diff --git a/phpunit/src/Platform/PlatformTest.php b/phpunit/src/Platform/PlatformTest.php index ab1f2d84..3833cbfd 100644 --- a/phpunit/src/Platform/PlatformTest.php +++ b/phpunit/src/Platform/PlatformTest.php @@ -6,9 +6,22 @@ use PHPUnit\Framework\TestCase; use TypePhp\Platform\Windows; use TypePhp\Platform\Linux; use TypePhp\Platform\Macos; +use TypePhp\Platform\Wasi; class PlatformTest extends TestCase { + public function testWasiTargetProperties(): void + { + $platform = new Wasi('wasm32-unknown-wasip2'); + + $this->assertSame('WASI SDK (wasm32-unknown-wasip2)', $platform->getName()); + $this->assertSame('.o', $platform->getObjectExtension()); + $this->assertSame('.wasm', $platform->getExecutableExtension()); + $this->assertSame('.a', $platform->getSharedLibraryExtension()); + $this->assertSame('LL', $platform->getIntegerLiteralSuffix()); + $this->assertSame([], $platform->getBuildLibraryWarnings('', '', 'bin')); + } + /** * 测试 Windows 平台基本功能 */ diff --git a/phpunit/src/WasiUnsupportedSyntaxTest.php b/phpunit/src/WasiUnsupportedSyntaxTest.php new file mode 100644 index 00000000..cea5e6be --- /dev/null +++ b/phpunit/src/WasiUnsupportedSyntaxTest.php @@ -0,0 +1,54 @@ +getProperty('targetPlatform')->setValue($compiler, 'wasm32-wasip2'); + $source = __DIR__ . '/../code/' . $file; + $compiler->addFiles([$source]); + $compiler->prepareFile($source); + + $this->expectException(TestError::class); + $this->expectExceptionMessage($message); + $compiler->convertFile($source); + } + + public static function unsupportedConversionSyntaxProvider(): array + { + return [ + 'backtick shell execution' => [ + 'wasi-backtick.php', + 'Backtick shell execution is not supported by the WASI target', + ], + 'generator closure' => [ + 'wasi-generator-closure.php', + 'Fiber and Generator are not supported by the WASI target', + ], + 'generator arrow function' => [ + 'wasi-generator-arrow.php', + 'Fiber and Generator are not supported by the WASI target', + ], + 'process function' => [ + 'wasi-process.php', + 'Function `proc_open` is not supported by the WASI target', + ], + 'socket function' => [ + 'wasi-socket.php', + 'Function `stream_socket_server` is not supported by the WASI target', + ], + 'signal function' => [ + 'wasi-signal.php', + 'Function `pcntl_signal` is not supported by the WASI target', + ], + ]; + } +} diff --git a/phpunit/src/WasmExportAttributeTest.php b/phpunit/src/WasmExportAttributeTest.php new file mode 100644 index 00000000..f4f337d8 --- /dev/null +++ b/phpunit/src/WasmExportAttributeTest.php @@ -0,0 +1,21 @@ +compile('wasm-export-valid.php'); + } + + public function testRejectsMethods(): void + { + $this->expectException(\TypePhp\Exception\SyntaxError::class); + $this->expectExceptionMessage('WasmExport can only be applied to named functions'); + $this->compile('wasm-export-invalid-method.php'); + } + + public function testRejectsNonStringName(): void + { + $this->exec('WasmExport name must be a constant string', 'wasm-export-invalid-name.php'); + } +} diff --git a/src/Build/PhpxLocator.php b/src/Build/PhpxLocator.php new file mode 100644 index 00000000..2c9c3d86 --- /dev/null +++ b/src/Build/PhpxLocator.php @@ -0,0 +1,48 @@ +getPlatform() instanceof Wasi)) { $this->validatePhpxLibrary(); } @@ -227,7 +228,9 @@ trait SourcePipelineTrait $this->stop('No valid source file found'); } - if ($this->isBuildModeLib()) { + // A WASI library publishes WIT/Component exports rather than a native + // TypePHP shared-library ABI, so a PHP import stub would be misleading. + if ($this->isBuildModeLib() && !$this->isWasiTarget()) { $this->genLibraryImportStub($files); } diff --git a/src/Build/WasiProjectConfig.php b/src/Build/WasiProjectConfig.php new file mode 100644 index 00000000..1935ba56 --- /dev/null +++ b/src/Build/WasiProjectConfig.php @@ -0,0 +1,158 @@ + 'command', + 'lib', 'library', 'reactor' => 'library', + default => $mode, + }; + if (!in_array($mode, ['command', 'library'], true)) { + throw new RuntimeException('A WASI project mode must be `command` or `library`'); + } + + $name = trim((string) ($config['name'] ?? 'app')); + if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) { + throw new RuntimeException('A WASI project name must be a non-empty file name'); + } + if (!empty($config['output'])) { + $output = self::absolutePath((string) $config['output'], $projectDir); + $extension = pathinfo($output, PATHINFO_EXTENSION); + if ($extension === '') { + $output .= '.wasm'; + } elseif (strcasecmp($extension, 'wasm') !== 0) { + throw new RuntimeException('A WASI project output must use the .wasm extension'); + } + } else { + $output = $projectDir . DIRECTORY_SEPARATOR . $name . '.wasm'; + } + + $configProfile = 'component'; + if (array_key_exists('wasm', $config)) { + if (!is_string($config['wasm'])) { + throw new RuntimeException('The `wasm` project option must be `component` or `browser`'); + } + $configProfile = $config['wasm']; + } + $profile = self::normalizeProfile($cliProfile ?? $configProfile); + + $browserPath = $config['wasm-browser-dir'] ?? null; + $browserDir = $profile === 'browser' && !empty($browserPath) + ? self::absolutePath((string) $browserPath, $projectDir) + : null; + + $package = strtolower(trim((string) ($config['wasm-package'] ?? 'typephp:' . $name . '@1.0.0'))); + if (preg_match('/^[a-z][a-z0-9-]*:[a-z][a-z0-9-]*@[0-9]+\.[0-9]+\.[0-9]+$/', $package) !== 1) { + throw new RuntimeException('`wasm-package` must use the WIT form namespace:name@major.minor.patch'); + } + $world = strtolower(trim((string) ($config['wasm-world'] ?? $name))); + $world = str_replace('_', '-', $world); + if (preg_match('/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/', $world) !== 1) { + throw new RuntimeException('`wasm-world` must be a lowercase WIT identifier'); + } + + return new self($realInput, $buildDir, $output, $browserDir, $profile, $mode, $package, $world); + } + + public static function isWasmEnabled(string $path): bool + { + if (!is_file($path) || preg_match('/\.ya?ml$/i', $path) !== 1) { + return false; + } + try { + $config = Yaml::parseFile($path); + } catch (\Throwable) { + return false; + } + if (!is_array($config) || !array_key_exists('wasm', $config)) { + return false; + } + return true; + } + + private static function normalizeProfile(string $profile): string + { + $profile = strtolower(trim($profile)); + if (!in_array($profile, ['browser', 'component'], true)) { + throw new RuntimeException("Unsupported WASI output profile `{$profile}`; expected browser or component"); + } + return $profile; + } + + private static function absolutePath(string $path, string $baseDirectory): string + { + if ($path === '') { + throw new RuntimeException('WASI project paths must not be empty'); + } + if ($path[0] === '/' || $path[0] === '\\' || preg_match('/^[A-Za-z]:[\\\\\/]/', $path) === 1) { + return $path; + } + return rtrim($baseDirectory, '/\\') . DIRECTORY_SEPARATOR . $path; + } +} diff --git a/src/Build/WasiToolchain.php b/src/Build/WasiToolchain.php new file mode 100644 index 00000000..ac9fb036 --- /dev/null +++ b/src/Build/WasiToolchain.php @@ -0,0 +1,119 @@ + */ + public function detect(bool $requireBrowserTools = true): array + { + $tools = []; + $requiredTools = [ + 'wasm32-wasip2-clang', + 'wasm32-wasip2-clang++', + 'llvm-ar', + 'llvm-ranlib', + 'llvm-nm', + 'wasm-component-ld', + 'wasmtime', + ]; + if ($requireBrowserTools) { + $requiredTools[] = 'jco'; + } + foreach ($requiredTools as $name) { + $tools[$name] = $this->findExecutable($name); + } + + $versions = []; + foreach (['wasm32-wasip2-clang', 'wasm32-wasip2-clang++', 'llvm-ar', 'llvm-ranlib', 'llvm-nm'] as $name) { + $versions[$name] = $this->requireVersion($name, $tools[$name], self::MIN_LLVM_MAJOR); + } + $this->requireVersion('wasm-component-ld', $tools['wasm-component-ld'], 0); + $versions['wasmtime'] = $this->requireVersion('wasmtime', $tools['wasmtime'], self::MIN_WASMTIME_MAJOR); + if ($requireBrowserTools) { + $versions['jco'] = $this->requireVersion('jco', $tools['jco'], self::MIN_JCO_MAJOR); + } + + [$exitCode, $target, $error] = $this->run([$tools['wasm32-wasip2-clang++'], '--print-target-triple']); + $target = trim($target); + if ($exitCode !== 0 || $target !== 'wasm32-unknown-wasip2') { + $detail = trim($error) !== '' ? ': ' . trim($error) : ''; + throw new RuntimeException( + "wasm32-wasip2-clang++ from PATH is not configured for wasm32-unknown-wasip2 (reported target: " + . ($target !== '' ? $target : 'unknown') . "){$detail}", + ); + } + + $tools['clang'] = $tools['wasm32-wasip2-clang']; + $tools['clang++'] = $tools['wasm32-wasip2-clang++']; + $tools['wasm-ld'] = $tools['wasm-component-ld']; + $tools['target'] = $target; + $tools['clang-version'] = $versions['wasm32-wasip2-clang++']; + $tools['wasmtime-version'] = $versions['wasmtime']; + if ($requireBrowserTools) { + $tools['jco-version'] = $versions['jco']; + } + return $tools; + } + + private function findExecutable(string $name): string + { + $path = getenv('PATH'); + foreach (explode(PATH_SEPARATOR, is_string($path) ? $path : '') as $directory) { + if ($directory === '') { + continue; + } + $candidate = rtrim($directory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name; + if (is_file($candidate) && is_executable($candidate)) { + // Preserve the PATH entry instead of resolving symlinks. LLVM + // multicall binaries select their driver mode and adjacent + // .cfg file from argv[0] (notably clang++ and wasm-ld). + return $candidate; + } + } + + throw new RuntimeException("Required WASI tool `{$name}` was not found in PATH"); + } + + private function requireVersion(string $name, string $executable, int $minimumMajor): string + { + [$exitCode, $output, $error] = $this->run([$executable, '--version']); + $versionText = trim($output . "\n" . $error); + if ($exitCode !== 0 || preg_match('/\bv?((\d+)(?:\.\d+)+)\b/i', $versionText, $match) !== 1) { + throw new RuntimeException("Unable to determine the version of WASI tool `{$name}` from PATH"); + } + $major = (int) $match[2]; + if ($major < $minimumMajor) { + throw new RuntimeException( + "WASI tool `{$name}` {$major} is too old; version {$minimumMajor} or newer is required", + ); + } + return $match[1]; + } + + /** @return array{int, string, string} */ + private function run(array $command): array + { + $process = proc_open( + $command, + [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], + $pipes, + ); + if (!is_resource($process)) { + return [127, '', 'failed to start process']; + } + + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[1]); + fclose($pipes[2]); + + return [proc_close($process), $stdout, $stderr]; + } +} diff --git a/src/Build/WasmInterfaceGenerator.php b/src/Build/WasmInterfaceGenerator.php new file mode 100644 index 00000000..bb4388e7 --- /dev/null +++ b/src/Build/WasmInterfaceGenerator.php @@ -0,0 +1,470 @@ + $functions + * @return array{package:string, world:string, interface:string, functions:list>} + */ + public function buildManifest( + iterable $functions, + string $package, + string $world, + callable $nativeName, + ): array { + $exports = []; + $names = []; + foreach ($functions as $function) { + if (!$function->wasmExport) { + continue; + } + $displayName = $function->displayName ?: $function->getNamespacedName(); + if ($function->method || $function->stub) { + throw new RuntimeException("WasmExport function {$displayName}() must have a TypePHP function body"); + } + if ($function->generator || $function->returnsByRef || $function->hasVariadicArg()) { + throw new RuntimeException("WasmExport function {$displayName}() cannot be a generator, return by reference, or be variadic"); + } + if ($function->returnTypeUndeclared) { + throw new RuntimeException("WasmExport function {$displayName}() must declare a return type"); + } + + $exportName = $function->wasmExportName !== '' + ? $function->wasmExportName + : $this->toWitName($function->name); + $this->assertWitIdentifier($exportName, "WasmExport name for {$displayName}()"); + $key = strtolower($exportName); + if (isset($names[$key])) { + throw new RuntimeException( + "WasmExport name collision: {$names[$key]}() and {$displayName}() both export `{$exportName}`" + ); + } + $names[$key] = $displayName; + + $parameters = []; + foreach ($function->argInfoList as $argument) { + if ($argument->byRef || $argument->variadic || $argument->default !== '') { + throw new RuntimeException( + "WasmExport parameter \${$argument->phpName} of {$displayName}() cannot be by-reference, variadic, or optional" + ); + } + $parameters[] = [ + 'name' => $this->toWitName($argument->phpName), + 'php-name' => $argument->phpName, + 'cpp-name' => $argument->name, + 'cpp-type' => $argument->type, + 'wit-type' => $this->argumentType($argument, $displayName), + ]; + } + + $exports[] = [ + 'name' => $exportName, + 'php-name' => $function->getNamespacedName(), + 'cpp-symbol' => $nativeName($function), + 'parameters' => $parameters, + 'result' => $this->resultType($function, $displayName), + 'result-cpp-type' => $function->returnType, + ]; + } + if ($exports === []) { + throw new RuntimeException('WASI library mode requires at least one #[WasmExport] function'); + } + + return [ + 'schema' => 1, + 'package' => $package, + 'world' => $world, + 'interface' => 'api', + 'runtime' => [ + 'threading' => 'nts', + 'lifecycle' => 'wit-resource', + 'init-symbol' => 'typephp_runtime_init', + 'shutdown-symbol' => 'typephp_runtime_shutdown', + 'error-model' => 'result', + ], + 'functions' => $exports, + ]; + } + + /** @param array{package:string, world:string, interface:string, functions:list>} $manifest */ + public function renderWit(array $manifest): string + { + $lines = [ + 'package ' . $manifest['package'] . ';', + '', + 'interface ' . $manifest['interface'] . ' {', + ' record typephp-error {', + ' class: string,', + ' message: string,', + ' code: s64,', + ' }', + '', + ' resource runtime {', + ]; + foreach ($manifest['functions'] as $function) { + $parameters = []; + foreach ($function['parameters'] as $parameter) { + $parameters[] = $parameter['name'] . ': ' . $parameter['wit-type']; + } + $success = $function['result'] === null ? '_' : $function['result']; + $lines[] = ' ' . $function['name'] . ': func(' . implode(', ', $parameters) + . ') -> result<' . $success . ', typephp-error>;'; + } + $lines[] = ' }'; + $lines[] = ''; + $lines[] = ' create-runtime: func() -> result;'; + $lines[] = '}'; + $lines[] = ''; + $lines[] = 'world ' . $manifest['world'] . ' {'; + $lines[] = ' export ' . $manifest['interface'] . ';'; + $lines[] = '}'; + return implode(PHP_EOL, $lines) . PHP_EOL; + } + + /** + * Every TypePHP browser export may transitively call an asynchronous WASI + * import. Jco must wrap these entry points with WebAssembly.promising or a + * synchronous-looking PHP call such as RINIT, file I/O, or HTTP cannot + * suspend through JSPI. + * + * @param array $manifest + */ + public function renderJcoAsyncExports(array $manifest): string + { + [$package, $version] = explode('@', $manifest['package'], 2); + $interface = $package . '/' . $manifest['interface'] . '@' . $version; + $exports = [$interface . '#create-runtime']; + foreach ($manifest['functions'] as $function) { + $exports[] = $interface . '#[method]runtime.' . $function['name']; + } + return implode(PHP_EOL, $exports) . PHP_EOL; + } + + /** + * Render the small TypePHP-specific half of the C binding. The generic + * Canonical ABI half and component type object are emitted by the pinned + * wit-bindgen binary shipped with PHPX. + * + * @param array $manifest + */ + public function renderCppAdapter(array $manifest): string + { + [$packageNamespace, $packageTail] = explode(':', explode('@', $manifest['package'], 2)[0], 2); + $packageName = $packageTail; + $world = $this->cName($manifest['world']); + $prefix = 'exports_' . $this->cName($packageNamespace) . '_' . $this->cName($packageName) + . '_' . $this->cName($manifest['interface']); + $errorType = $prefix . '_typephp_error_t'; + $lines = [ + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include "' . $this->cName($manifest['world']) . '.h"', + '', + 'extern "C" int typephp_runtime_init(int argc, char **argv);', + 'extern "C" void typephp_runtime_shutdown();', + '', + 'struct ' . $prefix . '_runtime_t {', + ' bool call_active = false;', + '};', + '', + 'namespace {', + 'bool runtime_started = false;', + 'bool runtime_failed = false;', + '', + 'void set_error(' . $errorType . ' *error, const char *class_name, size_t class_len,', + ' const char *message, size_t message_len, int64_t code = 0) {', + ' ' . $world . '_string_dup_n(&error->class_, class_name, class_len);', + ' ' . $world . '_string_dup_n(&error->message, message, message_len);', + ' error->code = code;', + '}', + '', + 'void set_error(' . $errorType . ' *error, const char *message) {', + ' set_error(error, "TypePHP\\\\WasmError", sizeof("TypePHP\\\\WasmError") - 1,', + ' message, std::strlen(message));', + '}', + '', + 'struct CallGuard {', + ' bool &active;', + ' explicit CallGuard(bool &value) : active(value) { active = true; }', + ' ~CallGuard() { active = false; }', + '};', + '', + 'void set_exception(' . $errorType . ' *error, zend_object *exception) {', + ' zend_class_entry *base = instanceof_function(exception->ce, zend_ce_exception)', + ' ? zend_ce_exception : zend_ce_error;', + ' zval message_rv;', + ' zval code_rv;', + ' zval *message = zend_read_property_ex(base, exception, ZSTR_KNOWN(ZEND_STR_MESSAGE), true, &message_rv);', + ' zval *code = zend_read_property_ex(base, exception, ZSTR_KNOWN(ZEND_STR_CODE), true, &code_rv);', + ' zend_string *class_name = exception->ce->name;', + ' const char *message_data = Z_TYPE_P(message) == IS_STRING ? Z_STRVAL_P(message) : "PHP exception";', + ' size_t message_len = Z_TYPE_P(message) == IS_STRING ? Z_STRLEN_P(message) : sizeof("PHP exception") - 1;', + ' int64_t exception_code = Z_TYPE_P(code) == IS_LONG ? Z_LVAL_P(code) : 0;', + ' set_error(error, ZSTR_VAL(class_name), ZSTR_LEN(class_name), message_data, message_len, exception_code);', + ' zend_clear_exception();', + '}', + '} // namespace', + '', + 'extern "C" bool ' . $prefix . '_create_runtime(', + ' ' . $prefix . '_own_runtime_t *ret, ' . $errorType . ' *error) {', + ' if (runtime_started) {', + ' set_error(error, "Only one TypePHP runtime may be active in an NTS component instance");', + ' return false;', + ' }', + ' if (runtime_failed) {', + ' set_error(error, "The TypePHP runtime is unavailable after an earlier fatal error");', + ' return false;', + ' }', + ' char program[] = "typephp-component";', + ' char *argv[] = {program, nullptr};', + ' if (typephp_runtime_init(1, argv) != 0) {', + ' runtime_failed = true;', + ' set_error(error, "Unable to initialize the TypePHP runtime");', + ' return false;', + ' }', + ' auto *runtime = new (std::nothrow) ' . $prefix . '_runtime_t();', + ' if (runtime == nullptr) {', + ' typephp_runtime_shutdown();', + ' set_error(error, "Unable to allocate the TypePHP runtime resource");', + ' return false;', + ' }', + ' runtime_started = true;', + ' *ret = ' . $prefix . '_runtime_new(runtime);', + ' return true;', + '}', + '', + 'extern "C" void ' . $prefix . '_runtime_destructor(' . $prefix . '_runtime_t *runtime) {', + ' delete runtime;', + ' if (runtime_started) {', + ' typephp_runtime_shutdown();', + ' }', + ' runtime_started = false;', + ' runtime_failed = false;', + '}', + '', + ]; + + foreach ($manifest['functions'] as $function) { + $returnType = $function['result']; + $declaration = [$prefix . '_borrow_runtime_t self']; + $callArguments = []; + foreach ($function['parameters'] as $parameter) { + [$base, $nullable] = $this->splitWitType($parameter['wit-type']); + $cType = $this->cAbiType($base, $world); + $cName = $this->cName($parameter['name']); + if ($base === 'string' || $nullable) { + $declaration[] = $cType . ' *' . ($nullable ? 'maybe_' : '') . $cName; + } else { + $declaration[] = $cType . ' ' . $cName; + } + $callArguments[] = $this->cppArgument($parameter, $cName, $base, $nullable); + } + if ($returnType !== null) { + [$returnBase, $returnNullable] = $this->splitWitType($returnType); + $declaration[] = ($returnNullable + ? $world . '_option_' . $this->cName($returnBase) . '_t' + : $this->cAbiType($returnBase, $world)) . ' *ret'; + } else { + $returnBase = ''; + $returnNullable = false; + } + $declaration[] = $errorType . ' *error'; + + $lines[] = 'extern ' . $function['result-cpp-type'] . ' ' . $function['cpp-symbol'] + . '(' . implode(', ', array_map( + static fn (array $parameter): string => $parameter['cpp-type'] . ' ' . $parameter['cpp-name'], + $function['parameters'], + )) . ');'; + $lines[] = ''; + $lines[] = 'extern "C" bool ' . $prefix . '_method_runtime_' . $this->cName($function['name']) + . '(' . implode(', ', $declaration) . ') {'; + $lines[] = ' if (self == nullptr || !runtime_started || runtime_failed) {'; + $lines[] = ' set_error(error, "The TypePHP runtime resource is closed or unavailable");'; + $lines[] = ' return false;'; + $lines[] = ' }'; + $lines[] = ' if (self->call_active) {'; + $lines[] = ' set_error(error, "Concurrent or reentrant calls on one NTS TypePHP component are not supported");'; + $lines[] = ' return false;'; + $lines[] = ' }'; + $lines[] = ' CallGuard call_guard(self->call_active);'; + $lines[] = ' bool success = false;'; + $lines[] = ' zend_try {'; + $lines[] = ' try {'; + $call = $function['cpp-symbol'] . '(' . implode(', ', $callArguments) . ')'; + if ($returnType === null) { + $lines[] = ' ' . $call . ';'; + } else { + $lines[] = ' auto result = ' . $call . ';'; + foreach ($this->cppResult($returnBase, $returnNullable, $world) as $resultLine) { + $lines[] = ' ' . $resultLine; + } + } + $lines[] = ' success = true;'; + $lines[] = ' } catch (zend_object *exception) {'; + $lines[] = ' set_exception(error, exception);'; + $lines[] = ' } catch (const std::exception &exception) {'; + $lines[] = ' set_error(error, exception.what());'; + $lines[] = ' } catch (...) {'; + $lines[] = ' set_error(error, "Unknown C++ exception");'; + $lines[] = ' }'; + $lines[] = ' } zend_catch {'; + $lines[] = ' runtime_failed = true;'; + $lines[] = ' set_error(error, "Zend bailout while executing the exported function");'; + $lines[] = ' } zend_end_try();'; + $lines[] = ' return success;'; + $lines[] = '}'; + $lines[] = ''; + } + + return implode(PHP_EOL, $lines) . PHP_EOL; + } + + /** @return array{string, bool} */ + private function splitWitType(string $type): array + { + if (str_starts_with($type, 'option<') && str_ends_with($type, '>')) { + return [substr($type, 7, -1), true]; + } + return [$type, false]; + } + + private function cAbiType(string $type, string $world): string + { + return match ($type) { + 'bool' => 'bool', + 's64' => 'int64_t', + 'f64' => 'double', + 'string' => $world . '_string_t', + default => throw new RuntimeException("Unsupported WIT C ABI type `{$type}`"), + }; + } + + /** @param array $parameter */ + private function cppArgument(array $parameter, string $name, string $base, bool $nullable): string + { + if (!$nullable) { + return match ($base) { + 'string' => 'php::Str(reinterpret_cast(' . $name . '->ptr), ' . $name . '->len)', + default => $name, + }; + } + $pointer = 'maybe_' . $name; + $value = match ($base) { + 'string' => 'php::Str(reinterpret_cast(' . $pointer . '->ptr), ' . $pointer . '->len)', + default => '*' . $pointer, + }; + return '(' . $pointer . ' == nullptr ? php::Var(php::null) : php::Var(' . $value . '))'; + } + + /** @return list */ + private function cppResult(string $base, bool $nullable, string $world): array + { + if ($nullable) { + $lines = [ + 'ret->is_some = !result.isNull();', + 'if (ret->is_some) {', + ]; + if ($base === 'string') { + $lines[] = ' php::Str string_result = php::toString(result);'; + $lines[] = ' ' . $world . '_string_dup_n(&ret->val, string_result.data(), string_result.length());'; + $lines[] = '}'; + return $lines; + } + $assignment = match ($base) { + 'bool' => 'ret->val = php::toBool(result);', + 's64' => 'ret->val = php::toInt(result);', + 'f64' => 'ret->val = php::toFloat(result);', + default => throw new RuntimeException("Unsupported nullable WIT result `{$base}`"), + }; + $lines[] = ' ' . $assignment; + $lines[] = '}'; + return $lines; + } + return [match ($base) { + 'bool' => '*ret = php::toBool(result);', + 's64' => '*ret = php::toInt(result);', + 'f64' => '*ret = php::toFloat(result);', + 'string' => $world . '_string_dup_n(ret, result.data(), result.length());', + default => throw new RuntimeException("Unsupported WIT result `{$base}`"), + }]; + } + + private function cName(string $name): string + { + return strtolower(str_replace('-', '_', $name)); + } + + private function argumentType(ArgInfo $argument, string $function): string + { + $type = $this->scalarType( + $argument->type, + "parameter \${$argument->phpName} of {$function}()", + $argument->typeStr, + ); + return $argument->nullable ? "option<{$type}>" : $type; + } + + private function resultType(FunctionDef $function, string $displayName): ?string + { + if ($function->returnType === Type::VOID) { + return null; + } + $nullable = str_starts_with($function->returnTypeStr, '?') + || str_contains(strtolower($function->returnTypeStr), 'null'); + $type = $this->scalarType($function->returnType, "return type of {$displayName}()", $function->returnTypeStr); + return $nullable ? "option<{$type}>" : $type; + } + + private function scalarType(string $type, string $location, string $declaredType = ''): string + { + $mapped = match ($type) { + Type::BOOL => 'bool', + Type::INT => 's64', + Type::FLOAT => 'f64', + Type::STR => 'string', + default => null, + }; + if ($mapped !== null) { + return $mapped; + } + $normalized = strtolower(str_replace(['?', '|null', 'null|'], '', $declaredType)); + $mapped = match ($normalized) { + 'bool' => 'bool', + 'int' => 's64', + 'float' => 'f64', + 'string' => 'string', + default => null, + }; + if ($mapped !== null) { + return $mapped; + } + throw new RuntimeException( + "Unsupported WasmExport {$location}; the first release supports bool, int, float, string, nullable scalars, and void" + ); + } + + private function toWitName(string $name): string + { + $name = preg_replace('/(?<=[a-z0-9])(?=[A-Z])/', '-', $name) ?? $name; + return strtolower(str_replace('_', '-', $name)); + } + + private function assertWitIdentifier(string $name, string $location): void + { + if (preg_match('/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/', $name) !== 1) { + throw new RuntimeException("{$location} must be a lowercase WIT identifier (for example `greet-user`)"); + } + } +} diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 2aa34da0..0a1361cd 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -8,6 +8,8 @@ namespace TypePhp; +use TypePhp\Build\PhpxLocator; + use League\CLImate\CLImate; use TypePhp\Backend\CompilerBackend; use TypePhp\Backend\CompilerFactory; @@ -174,6 +176,41 @@ class CompilerBase implements PropertyAccessContext 'stream_socket_accept', 'popen', ]; + + /** + * APIs which cannot have the same semantics in Wasmtime and a browser. + * Keep this list at the language boundary so a WASI build never degrades + * into a link error or a browser-only implementation. + */ + private const array WASI_UNSUPPORTED_FUNCTIONS = [ + 'exec', + 'passthru', + 'popen', + 'proc_close', + 'proc_get_status', + 'proc_nice', + 'proc_open', + 'proc_terminate', + 'shell_exec', + 'system', + 'fsockopen', + 'pfsockopen', + 'stream_socket_accept', + 'stream_socket_client', + 'stream_socket_enable_crypto', + 'stream_socket_get_name', + 'stream_socket_pair', + 'stream_socket_recvfrom', + 'stream_socket_sendto', + 'stream_socket_server', + 'stream_socket_shutdown', + ]; + + private const array WASI_UNSUPPORTED_FUNCTION_PREFIXES = [ + 'pcntl_', + 'posix_', + 'socket_', + ]; public const int DECL_TYPE_OF_RETURN = 1; public const int DECL_TYPE_OF_PROPERTY = 2; public const int DECL_TYPE_OF_CONST = 3; @@ -492,59 +529,11 @@ class CompilerBase implements PropertyAccessContext protected function getPhpxDir(): string { - // 解析 phpx 目录,优先级(macOS / Windows / Linux 通用): - // 1. PHPX_HOME 环境变量指定的目录 - // 2. vendor 目录(Composer 安装路径,或 TypePHP 源码检出下的 vendor/swoole/phpx) - $phpxDir = getenv('PHPX_HOME'); - if ($phpxDir !== false && $phpxDir !== '') { - $phpxDir = rtrim($phpxDir, '\/'); - if (is_dir($phpxDir)) { - return $this->normalizePhpxDir($phpxDir); - } - } - - // Composer-installed TypePHP and PHPX are sibling packages. Ask - // Composer for the actual package path instead of assuming a nested - // vendor directory inside the TypePHP package. - if (class_exists(\Composer\InstalledVersions::class) - && \Composer\InstalledVersions::isInstalled('swoole/phpx')) { - $composerInstallPath = \Composer\InstalledVersions::getInstallPath('swoole/phpx'); - if (is_string($composerInstallPath)) { - $composerInstallPath = rtrim($composerInstallPath, '\/'); - if (is_dir($composerInstallPath)) { - return $this->normalizePhpxDir($composerInstallPath); - } - } - } - - // TypePHP source checkout: phpx is installed below this repository. - $composerPhpxDir = $this->rootPath . self::PHPX_VENDOR_DIR; - if (is_dir($composerPhpxDir)) { - return $this->normalizePhpxDir($composerPhpxDir); - } - - if (defined('ROOT_PATH')) { - $rootPhpxDir = ROOT_PATH . self::PHPX_VENDOR_DIR; - if (is_dir($rootPhpxDir)) { - return $this->normalizePhpxDir($rootPhpxDir); - } + try { + return PhpxLocator::resolve($this->rootPath); + } catch (\RuntimeException $exception) { + $this->error($exception->getMessage()); } - - // 两个路径都不存在,报错 - $this->error( - 'phpx directory not found. Please either:\n' . - '1. Set PHPX_HOME environment variable to your phpx installation path\n' . - '2. Install phpx via Composer: composer require swoole/phpx' - ); - } - - /** - * 规范化 phpx 目录路径(解析相对路径与 ..,便于错误提示和后续路径拼接) - */ - private function normalizePhpxDir(string $dir): string - { - $real = realpath($dir); - return $real !== false ? $real : $dir; } protected function getPlatform(): PlatformBase @@ -581,6 +570,29 @@ class CompilerBase implements PropertyAccessContext return $this->getPlatform() instanceof Macos; } + public function isWasiTarget(): bool + { + $target = strtolower($this->targetPlatform); + return $target === 'wasm32-unknown-wasip2' || $target === 'wasm32-wasip2'; + } + + protected function assertWasiFunctionSupported(NodeAbstract $expr, string $name): void + { + if (!$this->isWasiTarget()) { + return; + } + + $name = strtolower(ltrim($name, '\\')); + if (in_array($name, self::WASI_UNSUPPORTED_FUNCTIONS, true)) { + $this->fatalError($expr, "Function `{$name}` is not supported by the WASI target"); + } + foreach (self::WASI_UNSUPPORTED_FUNCTION_PREFIXES as $prefix) { + if (str_starts_with($name, $prefix)) { + $this->fatalError($expr, "Function `{$name}` is not supported by the WASI target"); + } + } + } + public function isBuildModeBin(): bool { return $this->buildMode === self::BUILD_MODE_BIN; @@ -3804,6 +3816,9 @@ class CompilerBase implements PropertyAccessContext protected function parseShellExec(Expr\ShellExec $expr): string { + if ($this->isWasiTarget()) { + $this->fatalError($expr, 'Backtick shell execution is not supported by the WASI target'); + } $list = []; foreach ($expr->parts as $part) { $list[] = $this->identifierToStr($part); diff --git a/src/Entity/FunctionDef.php b/src/Entity/FunctionDef.php index 4457cdaa..afb39254 100644 --- a/src/Entity/FunctionDef.php +++ b/src/Entity/FunctionDef.php @@ -46,6 +46,10 @@ class FunctionDef public bool $hot = false; /** Prefer optimizing this function for rarely executed paths. */ public bool $cold = false; + /** Whether this function is part of the public WIT component interface. */ + public bool $wasmExport = false; + /** Explicit WIT export name, or an empty string when it is derived from the PHP name. */ + public string $wasmExportName = ''; /** Number of fixed positional values returned through the internal tuple fast path. */ public int $multiReturnCount = 0; /** Source file containing this function definition. */ diff --git a/src/Generator/ClosureGenerator.php b/src/Generator/ClosureGenerator.php index c2311824..512d4f1b 100644 --- a/src/Generator/ClosureGenerator.php +++ b/src/Generator/ClosureGenerator.php @@ -247,6 +247,9 @@ trait ClosureGenerator protected function validateGeneratorClosure(Expr\ArrowFunction|Expr\Closure $expr, array $params): void { + if ($this->isWasiTarget()) { + $this->fatalError($expr, 'Fiber and Generator are not supported by the WASI target'); + } if ($expr->byRef) { $this->fatalError($expr, 'Generator closures returning by reference are not supported yet'); } diff --git a/src/Generator/FiberGenerator.php b/src/Generator/FiberGenerator.php index 1fbeba39..657e725b 100644 --- a/src/Generator/FiberGenerator.php +++ b/src/Generator/FiberGenerator.php @@ -61,6 +61,9 @@ trait FiberGenerator protected function prepareGeneratorFunction(Function_|ClassMethod $v, FunctionDef $functionDef): void { + if ($this->isWasiTarget()) { + $this->fatalError($v, 'Fiber and Generator are not supported by the WASI target'); + } if ($v->byRef) { $this->fatalError($v, 'Generators returning by reference are not supported yet'); } diff --git a/src/Parser/FunctionCallTrait.php b/src/Parser/FunctionCallTrait.php index cc4706f7..48d6f298 100644 --- a/src/Parser/FunctionCallTrait.php +++ b/src/Parser/FunctionCallTrait.php @@ -78,6 +78,9 @@ trait FunctionCallTrait } elseif ($expr->name->getType() === 'Name' or $expr->name->getType() === 'Name_FullyQualified') { $name = $this->parseIdentifier($expr->name); $globalName = ltrim($name, '\\'); + if ($this->isInternalFunction($globalName)) { + $this->assertWasiFunctionSupported($expr, $globalName); + } if (in_array($name, Constants::UNSUPPORTED_FUNCTIONS)) { $this->fatalError($expr, 'Unsupported function: `' . $name . '`'); } diff --git a/src/Platform/Wasi.php b/src/Platform/Wasi.php new file mode 100644 index 00000000..9765d51b --- /dev/null +++ b/src/Platform/Wasi.php @@ -0,0 +1,50 @@ +target})"; + } + + public function isCurrent(): bool + { + return false; + } + + public function getSharedLibraryExtension(): string + { + return '.a'; + } + + public function getExecutableExtension(): string + { + return '.wasm'; + } + + public function getDefaultCompiler(): string + { + $compiler = getenv('TYPEPHP_WASI_CXX'); + return is_string($compiler) && $compiler !== '' ? $compiler : 'clang++'; + } + + public function getBuildLibraryWarnings( + string $phpDir, + string $phpxDir, + string $buildMode, + bool $checkPhpxRuntime = true, + ): array { + return []; + } + + public function getIntegerLiteralSuffix(): string + { + return 'LL'; + } +} diff --git a/src/Preprocessor.php b/src/Preprocessor.php index 97c20dfb..83820500 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -231,6 +231,65 @@ class Preprocessor extends CompilerBase return false; } + private function parseWasmExportAttribute(Node\Stmt\Function_|Node\Stmt\ClassMethod $node): ?string + { + foreach ($node->attrGroups as $group) { + foreach ($group->attrs as $attribute) { + if (!$this->isRootCompileTimeAttribute($attribute, 'WasmExport')) { + continue; + } + if ($node instanceof Node\Stmt\ClassMethod) { + $this->fatalCompileTimeAttribute( + $node, + 'WasmExport', + 'WasmExport can only be applied to named functions', + $attribute, + ); + } + if (count($attribute->args) > 1) { + $this->fatalCompileTimeAttribute( + $node, + 'WasmExport', + 'WasmExport accepts at most one string name', + $attribute, + ); + } + if ($attribute->args === []) { + return ''; + } + $argument = $attribute->args[0]; + if ($argument->name !== null && strcasecmp($argument->name->toString(), 'name') !== 0) { + $this->fatalCompileTimeAttribute( + $node, + 'WasmExport', + 'WasmExport only accepts the named argument `name`', + $attribute, + ); + } + if (!$argument->value instanceof Node\Scalar\String_) { + $this->fatalCompileTimeAttribute( + $node, + 'WasmExport', + 'WasmExport name must be a constant string', + $attribute, + ); + } + $name = $argument->value->value; + if ($name === '') { + $this->fatalCompileTimeAttribute( + $node, + 'WasmExport', + 'WasmExport name must not be empty', + $attribute, + ); + } + return $name; + } + } + + return null; + } + private function isRootCompileTimeAttribute(Node\Attribute $attribute, string $name): bool { return strcasecmp($this->getResolvedPhpName($attribute->name), $name) === 0; @@ -535,6 +594,9 @@ class Preprocessor extends CompilerBase $functionDef->overrideRequired = (bool) $v->getAttribute(FunctionAttributeLowering::OVERRIDE_ATTRIBUTE, false); $functionDef->hot = (bool) $v->getAttribute(FunctionAttributeLowering::HOT_ATTRIBUTE, false); $functionDef->cold = (bool) $v->getAttribute(FunctionAttributeLowering::COLD_ATTRIBUTE, false); + $wasmExportName = $this->parseWasmExportAttribute($v); + $functionDef->wasmExport = $wasmExportName !== null; + $functionDef->wasmExportName = $wasmExportName ?? ''; if ($functionDef->mustUse && $returnType === Type::VOID) { $this->fatalCompileTimeAttribute( $v, diff --git a/src/Transform/CompileTimeAttributeRegistry.php b/src/Transform/CompileTimeAttributeRegistry.php index 1c2db082..f85bdc86 100644 --- a/src/Transform/CompileTimeAttributeRegistry.php +++ b/src/Transform/CompileTimeAttributeRegistry.php @@ -23,6 +23,7 @@ final class CompileTimeAttributeRegistry public const ARGUMENTS_METHODS_FOR = 'methods_for'; public const ARGUMENTS_FIELDS = 'fields'; public const ARGUMENTS_VALIDATE = 'validate'; + public const ARGUMENTS_WASM_EXPORT = 'wasm_export'; public const PHASE_PREPROCESS = 'preprocess'; public const PHASE_ENTER = 'enter'; @@ -73,6 +74,7 @@ final class CompileTimeAttributeRegistry $add('MethodsFor', [self::TARGET_NAMED_CLASS], 'MethodsFor can only be applied to classes', self::ARGUMENTS_METHODS_FOR, self::PHASE_PREPROCESS); $add('NoExport', [self::TARGET_CLASS_LIKE, self::TARGET_FUNCTION, self::TARGET_METHOD], 'NoExport can only be applied to classes, functions, or methods', self::ARGUMENTS_NONE, self::PHASE_PREPROCESS, false); + $add('WasmExport', [self::TARGET_FUNCTION], 'WasmExport can only be applied to named functions', self::ARGUMENTS_WASM_EXPORT, self::PHASE_PREPROCESS, false); foreach (['Getter', 'Setter', 'With'] as $name) { $add($name, [self::TARGET_PROPERTY], $name . ' can only be applied to instance properties', self::ARGUMENTS_NONE, self::PHASE_CLASS_LEAVE); } diff --git a/src/Translator.php b/src/Translator.php index f922e759..d3426cc3 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -18,6 +18,7 @@ use TypePhp\Build\NativeCommandOptionsTrait; use TypePhp\Build\NativeBuilder; use TypePhp\Build\PrecompiledHeaderManager; use TypePhp\Build\SourcePipelineTrait; +use TypePhp\Build\WasmInterfaceGenerator; use TypePhp\Config\ProjectYamlLoader; use TypePhp\Diagnostics\CompileTimeAttributeDiagnostic; use TypePhp\Build\ResourceCompilationTrait; @@ -37,6 +38,7 @@ use TypePhp\Generator\LibraryImportStubGenerator; use TypePhp\Generator\Symbol; use TypePhp\Metadata\Constants; use TypePhp\Platform\PlatformFactory; +use TypePhp\Platform\Wasi; use TypePhp\Platform\Windows; use TypePhp\Resolver\Reflection; use TypePhp\Resolver\ClassConstantValueTrait; @@ -164,8 +166,23 @@ class Translator extends Preprocessor protected function detectPlatform(): void { try { - $this->platform = PlatformFactory::create(); - $this->cppCompiler = CompilerFactory::detectCompilerName($this->platform); + $targetPlatform = $this->climate->arguments->defined('target-platform') + ? (string) $this->climate->arguments->get('target-platform') + : ''; + if ($targetPlatform === 'wasm32-wasip1' || $targetPlatform === 'wasm32-wasi') { + throw new \RuntimeException('WASI Preview 1 is not supported; use wasm32-wasip2'); + } + if ($targetPlatform === 'wasm32-wasip2' || $targetPlatform === 'wasm32-unknown-wasip2') { + $detectedTarget = getenv('TYPEPHP_WASI_TARGET'); + $this->platform = new Wasi( + is_string($detectedTarget) && $detectedTarget !== '' ? $detectedTarget : $targetPlatform, + ); + } else { + $this->platform = PlatformFactory::create(); + } + $this->cppCompiler = $this->platform instanceof Wasi + ? $this->platform->getDefaultCompiler() + : CompilerFactory::detectCompilerName($this->platform); if ($this->platform instanceof Windows) { $libInfo = $this->platform->detectPhpLibs($this->getPhpDir()); @@ -181,8 +198,15 @@ class Translator extends Preprocessor } $this->compilerBackend = CompilerFactory::createByName($this->cppCompiler, $this->platform); + $backendName = $this->compilerBackend->getName(); + if ($this->platform instanceof Wasi) { + $clangVersion = getenv('TYPEPHP_WASI_CLANG_VERSION'); + $backendName = 'LLVM Clang' + . (is_string($clangVersion) && $clangVersion !== '' ? " {$clangVersion}" : ''); + } + $label = $this->platform instanceof Wasi ? 'Initialized target/toolchain' : 'Initialized platform/backend'; $this->climate->info( - "Initialized platform/backend: {$this->platform->getName()} + {$this->compilerBackend->getName()} ({$this->compilerBackend->getCompilerCommand()})" + "{$label}: {$this->platform->getName()} + {$backendName} ({$this->compilerBackend->getCompilerCommand()})" ); } catch (\Throwable $e) { $this->error($e->getMessage()); @@ -244,6 +268,7 @@ class Translator extends Preprocessor $climate->tab()->out('--cxx-std C++ standard version (c++17, c++20, etc., default: c++17)'); $climate->tab()->out('--march Target CPU instruction set (e.g. native, x86-64-v3, armv8-a)'); $climate->tab()->out('--target-platform Cross-compilation target triple (e.g. aarch64-linux-gnu)'); + $climate->tab()->out('--wasm[=profile] Build WASI component (default) or browser output'); $climate->tab()->out('--lto Enable Link Time Optimization (-flto)'); $climate->tab()->out('--no-literal-strings Disable literal strings optimization'); $climate->tab()->out('--php-version PHP language version to accept (8.2-8.5, default: 8.5)'); @@ -754,7 +779,7 @@ class Translator extends Preprocessor $code .= 'extern "C" void save_ps_args(int, char **) {}' . PHP_EOL; } - if ($this->isBuildModeBin()) { + if ($this->isBuildModeBin() && !$this->isWasiTarget()) { $cliHeaders = [ '#include "php_cli_process_title.h"', '#include "php_cli_process_title_arginfo.h"', @@ -856,7 +881,7 @@ CODE; $code .= "// clang-format off\n"; $code .= "static const zend_function_entry ext_functions[] = {\n"; - if ($this->isBuildModeBin()) { + if ($this->isBuildModeBin() && !$this->isWasiTarget()) { $code .= $this->getIndent() . "PHP_FE(cli_set_process_title, arginfo_cli_set_process_title)\n"; $code .= $this->getIndent() . "PHP_FE(cli_get_process_title, arginfo_cli_get_process_title)\n"; } @@ -885,7 +910,9 @@ CODE; $code .= 'PHP_MINIT_FUNCTION(' . $this->getModuleName() . ') {' . PHP_EOL; $code .= 'zend_try {' . PHP_EOL; $code .= '// class/interface class entries' . PHP_EOL; - $code .= 'typephp_register_fiber_generator_class();' . PHP_EOL; + if (!$this->isWasiTarget()) { + $code .= 'typephp_register_fiber_generator_class();' . PHP_EOL; + } $code .= 'if (typephp_install_reflection_attribute_handlers() != SUCCESS) {' . PHP_EOL; $code .= $this->getIndent() . 'return FAILURE;' . PHP_EOL; $code .= '}' . PHP_EOL; @@ -1289,7 +1316,9 @@ CODE; { $job = $this->maxJob; - $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_fiber_generator.cc'; + if (!$this->isWasiTarget()) { + $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_fiber_generator.cc'; + } $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_helper.cc'; // embed 需要 main 函数,以及 cli 的内置函数定义 @@ -1297,7 +1326,7 @@ CODE; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_main.cc'; } - if ($this->isBuildModeBin()) { + if ($this->isBuildModeBin() && !$this->isWasiTarget()) { $sourceFiles[] = $this->getPhpxDir() . '/src/misc/php_cli_process_title.c'; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/ps_title.c'; } @@ -1728,6 +1757,50 @@ CODE; return $this->buildMode; } + /** Write the public WIT contract and PHPX host-bindgen manifest. */ + public function writeWasmInterface( + string $manifestFile, + string $witFile, + string $adapterFile, + string $asyncExportsFile, + string $package, + string $world, + ): void + { + if (!$this->isWasiTarget() || !$this->isBuildModeLib()) { + $this->error('WIT interfaces can only be generated for a WASI library build'); + } + try { + $generator = new WasmInterfaceGenerator(); + $manifest = $generator->buildManifest( + $this->symbols->functions(), + $package, + $world, + fn (FunctionDef $function): string => self::PREFIX + . $this->getNativeName($function->name, $function->namespace), + ); + foreach (array_unique([ + dirname($manifestFile), + dirname($witFile), + dirname($adapterFile), + dirname($asyncExportsFile), + ]) as $directory) { + if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new \RuntimeException("Unable to create WASM interface directory: {$directory}"); + } + } + $json = json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); + if (file_put_contents($manifestFile, $json . PHP_EOL) === false + || file_put_contents($witFile, $generator->renderWit($manifest)) === false + || file_put_contents($adapterFile, $generator->renderCppAdapter($manifest)) === false + || file_put_contents($asyncExportsFile, $generator->renderJcoAsyncExports($manifest)) === false) { + throw new \RuntimeException('Unable to write the generated WASM interface'); + } + } catch (\Throwable $exception) { + $this->error($exception->getMessage()); + } + } + public function getArgInfoStubFilename(string $stubFile): string { $rs = str_replace(['.stub.php', '.php'], '', $stubFile); diff --git a/src/compiler.php b/src/compiler.php index 255c5bd1..e51a43a2 100644 --- a/src/compiler.php +++ b/src/compiler.php @@ -1,5 +1,8 @@ = 2 && str_ends_with($argv[1], '.prof')) { profileAnalyze($argc, $argv); @@ -27,10 +35,45 @@ function main(int $argc, array $argv): void // 生成 C++ 文件 $sourceFiles = $translator->convert($files); + $wasmManifest = getenv('TYPEPHP_WASM_INTERFACE_MANIFEST'); + if (is_string($wasmManifest) && $wasmManifest !== '') { + $wasmWit = getenv('TYPEPHP_WASM_INTERFACE_WIT'); + $wasmAdapter = getenv('TYPEPHP_WASM_INTERFACE_ADAPTER'); + $wasmAsyncExports = getenv('TYPEPHP_WASM_INTERFACE_ASYNC_EXPORTS'); + $wasmPackage = getenv('TYPEPHP_WASM_PACKAGE'); + $wasmWorld = getenv('TYPEPHP_WASM_WORLD'); + if (!is_string($wasmWit) || $wasmWit === '' + || !is_string($wasmAdapter) || $wasmAdapter === '' + || !is_string($wasmAsyncExports) || $wasmAsyncExports === '' + || !is_string($wasmPackage) || $wasmPackage === '' + || !is_string($wasmWorld) || $wasmWorld === '') { + throw new RuntimeException('Incomplete internal WASM interface configuration'); + } + $translator->writeWasmInterface( + $wasmManifest, + $wasmWit, + $wasmAdapter, + $wasmAsyncExports, + $wasmPackage, + $wasmWorld, + ); + $sourceFiles[] = $wasmAdapter; + } + // --dry 模式:仅生成 C++ 代码,不执行编译 if ($translator->isDryRun()) { $buildDir = $translator->getBuildDir(); $count = count($sourceFiles); + $sourceListFile = getenv('TYPEPHP_GENERATED_SOURCE_LIST'); + if (is_string($sourceListFile) && $sourceListFile !== '') { + $sourceListDir = dirname($sourceListFile); + if (!is_dir($sourceListDir) && !mkdir($sourceListDir, 0777, true) && !is_dir($sourceListDir)) { + throw new RuntimeException("Unable to create generated source manifest directory: {$sourceListDir}"); + } + if (file_put_contents($sourceListFile, implode(PHP_EOL, $sourceFiles) . PHP_EOL) === false) { + throw new RuntimeException("Unable to write generated source manifest: {$sourceListFile}"); + } + } $translator->output("Dry run completed: {$count} C++ source file(s) generated in {$buildDir}", 'lightBlue'); return; } @@ -45,6 +88,205 @@ function main(int $argc, array $argv): void } } +/** + * Build a self-contained WASI 0.2 command component through the public CLI. + * The lower-level build scripts are implementation details and are not part of + * the user-facing workflow. + */ +function compileWasmProgram(array $argv): void +{ + $input = null; + $buildDir = null; + $profile = null; + $arguments = array_slice($argv, 1); + for ($i = 0, $count = count($arguments); $i < $count; $i++) { + $argument = $arguments[$i]; + if ($argument === '--wasm') { + continue; + } + if (str_starts_with($argument, '--wasm=')) { + $value = substr($argument, strlen('--wasm=')); + if ($value === '') { + fwrite(STDERR, "Option --wasm requires browser or component after `=`\n"); + exit(1); + } + if ($profile !== null && $profile !== $value) { + fwrite(STDERR, "Option --wasm was specified with conflicting profiles\n"); + exit(1); + } + $profile = $value; + continue; + } + if ($argument === '--build-dir') { + if (!isset($arguments[$i + 1]) || $arguments[$i + 1] === '') { + fwrite(STDERR, "Option --build-dir requires a directory\n"); + exit(1); + } + $buildDir = $arguments[++$i]; + continue; + } + if (str_starts_with($argument, '--build-dir=')) { + $buildDir = substr($argument, strlen('--build-dir=')); + if ($buildDir === '') { + fwrite(STDERR, "Option --build-dir requires a directory\n"); + exit(1); + } + continue; + } + if (str_starts_with($argument, '-')) { + fwrite(STDERR, "Unsupported option in --wasm mode: {$argument}\n"); + exit(1); + } + if ($input !== null) { + fwrite(STDERR, "The --wasm mode accepts exactly one PHP file or project.yml\n"); + exit(1); + } + $input = $argument; + } + + if ($input === null) { + fwrite(STDERR, "Usage: php bin/tpc.php [--wasm[=browser|component]] [--build-dir ]\n"); + exit(1); + } + + $workingDirectory = getcwd(); + try { + $project = WasiProjectConfig::load( + $input, + $buildDir, + $workingDirectory, + ROOT_PATH . DIRECTORY_SEPARATOR . 'build', + $profile, + ); + } catch (RuntimeException $exception) { + fwrite(STDERR, "Invalid WASI project: {$exception->getMessage()}\n"); + exit(1); + } + + $builder = dirname(__DIR__) . '/wasm/build-typephp-program.sh'; + if (!is_executable($builder)) { + fwrite(STDERR, "TypePHP WASI builder is not executable: {$builder}\n"); + exit(1); + } + + try { + $tools = (new WasiToolchain())->detect($project->profile === 'browser'); + } catch (RuntimeException $exception) { + fwrite(STDERR, "WASI toolchain check failed: {$exception->getMessage()}\n"); + fwrite(STDERR, "Add WASI SDK and Wasmtime bin directories to PATH"); + if ($project->profile === 'browser') { + fwrite(STDERR, ", and install Jco (`npm install -g @bytecodealliance/jco`)" + . " or use --wasm=component"); + } + fwrite(STDERR, ", then try again.\n"); + exit(1); + } + + $environment = getenv(); + if (!is_array($environment)) { + $environment = []; + } + $environment['TYPEPHP_WASI_CC'] = $tools['clang']; + $environment['TYPEPHP_WASI_CXX'] = $tools['clang++']; + $environment['TYPEPHP_WASI_AR'] = $tools['llvm-ar']; + $environment['TYPEPHP_WASI_RANLIB'] = $tools['llvm-ranlib']; + $environment['TYPEPHP_WASI_NM'] = $tools['llvm-nm']; + $environment['TYPEPHP_WASI_LD'] = $tools['wasm-ld']; + $environment['TYPEPHP_WASMTIME'] = $tools['wasmtime']; + $environment['TYPEPHP_WASM_BROWSER'] = $project->profile === 'browser' ? '1' : '0'; + if ($project->profile === 'browser') { + $environment['TYPEPHP_JCO'] = $tools['jco']; + $environment['TYPEPHP_JCO_VERSION'] = $tools['jco-version']; + } + $environment['TYPEPHP_WASI_TARGET'] = $tools['target']; + $environment['TYPEPHP_WASI_CLANG_VERSION'] = $tools['clang-version']; + $environment['TYPEPHP_WASMTIME_VERSION'] = $tools['wasmtime-version']; + $environment['TYPEPHP_WASM_PROGRAM_BUILD_DIR'] = $project->buildDir; + $environment['TYPEPHP_WASM_MODE'] = $project->mode; + $environment['TYPEPHP_WASM_PACKAGE'] = $project->package; + $environment['TYPEPHP_WASM_WORLD'] = $project->world; + $compilerExecutable = realpath($argv[0]); + if ($compilerExecutable === false || !is_executable($compilerExecutable)) { + fwrite(STDERR, "Unable to resolve the current TypePHP compiler executable: {$argv[0]}\n"); + exit(1); + } + if ($project->browserDir !== null) { + $environment['TYPEPHP_WASM_BROWSER_DIR'] = $project->browserDir; + } + + try { + $phpxDir = PhpxLocator::resolve(ROOT_PATH); + } catch (RuntimeException $exception) { + fwrite(STDERR, "Unable to locate PHPX: {$exception->getMessage()}\n"); + exit(1); + } + if ($project->mode === 'library') { + $hostOs = match (PHP_OS_FAMILY) { + 'Linux' => 'linux', + 'Darwin' => 'macos', + 'Windows' => 'windows', + default => strtolower(PHP_OS_FAMILY), + }; + $hostArch = strtolower(php_uname('m')); + $hostArch = match ($hostArch) { + 'amd64', 'x64' => 'x86_64', + 'arm64' => $hostOs === 'linux' ? 'aarch64' : 'arm64', + default => $hostArch, + }; + $bindgen = $phpxDir . DIRECTORY_SEPARATOR . 'wasm' . DIRECTORY_SEPARATOR . 'bin' + . DIRECTORY_SEPARATOR . $hostOs . '-' . $hostArch . DIRECTORY_SEPARATOR . 'wit-bindgen' + . ($hostOs === 'windows' ? '.exe' : ''); + if (!is_file($bindgen)) { + fwrite(STDERR, "PHPX bundled WIT binding generator is missing: {$bindgen}\n"); + fwrite(STDERR, "Install the matching PHPX package; installing wit-bindgen separately is not required.\n"); + exit(1); + } + $environment['TYPEPHP_WIT_BINDGEN'] = $bindgen; + } + $command = [$builder, $project->input, $project->output ?? '-', $phpxDir, $compilerExecutable]; + + $process = proc_open( + $command, + [STDIN, STDOUT, STDERR], + $pipes, + getcwd(), + $environment, + ); + if (!is_resource($process)) { + fwrite(STDERR, "Failed to start the TypePHP WASI builder\n"); + exit(1); + } + + $exitCode = proc_close($process); + if ($exitCode !== 0) { + exit($exitCode); + } +} + +function shouldCompileWasm(array $argv): bool +{ + foreach (array_slice($argv, 1) as $argument) { + if ($argument === '--wasm' || str_starts_with($argument, '--wasm=')) { + return true; + } + } + + $workingDirectory = getcwd(); + foreach (array_slice($argv, 1) as $argument) { + if ($argument === '' || $argument[0] === '-') { + continue; + } + $path = $argument; + if ($path[0] !== '/' && preg_match('/^[A-Za-z]:[\\\\\/]/', $path) !== 1) { + $path = $workingDirectory . DIRECTORY_SEPARATOR . $path; + } + if (WasiProjectConfig::isWasmEnabled($path)) { + return true; + } + } + return false; +} + function profileAnalyze(int $argc, array $argv): void { $profFile = $argv[1]; diff --git a/src/polyfills.php b/src/polyfills.php index 2919f6b5..fcca006f 100644 --- a/src/polyfills.php +++ b/src/polyfills.php @@ -19,6 +19,20 @@ final readonly class NoExport { } +/** + * Expose a statically typed function through a WASI 0.2 component interface. + * + * This is a compile-time attribute. It is never instantiated by the PHP + * runtime and therefore adds no reflection or dispatch overhead. + */ +#[Attribute(Attribute::TARGET_FUNCTION)] +final readonly class WasmExport +{ + public function __construct(public ?string $name = null) + { + } +} + #[Attribute(Attribute::TARGET_PROPERTY)] final readonly class Getter { diff --git a/wasm/README.md b/wasm/README.md new file mode 100644 index 00000000..95f5b087 --- /dev/null +++ b/wasm/README.md @@ -0,0 +1,139 @@ +# TypePHP WASI SDK layout + +TypePHP application builds never compile PHP, PHPX, GMP, MPFR, or mpdecimal. +The integrated PHPX installer places their prebuilt `wasm32-wasip2` SDK at: + +```text +/wasm/wasm32-wasip2/ +├── include/ +│ ├── php/ PHP installed and generated headers +│ ├── phpx/ PHPX public and TypePHP runtime headers +│ ├── zlib.h zlib API used by PHP's static zlib extension +│ ├── zconf.h +│ ├── sodium.h +│ ├── openssl/ OpenSSL crypto-only public headers +│ ├── libxml2/ libxml2 public headers +│ ├── sqlite3.h +│ ├── zip.h +│ ├── bzlib.h +│ ├── gmp.h +│ ├── gmpxx.h +│ ├── mpfr.h +│ ├── mpdecimal.h +│ └── decimal.hh +├── lib/ +│ ├── libphp.a +│ ├── libphpx.a +│ ├── libgmp.a +│ ├── libgmpxx.a +│ ├── libmpfr.a +│ ├── libmpdec.a +│ └── libmpdec++.a +└── .typephp-wasi-sdk-abi +``` + +The zlib, bzip2, libsodium, libcrypto, libxml2, SQLite, and libzip objects are +embedded in `libphp.a`; the SDK intentionally does not ship or link their +dependency archives separately. + +The ABI file must contain exactly: + +```text +typephp-wasip2-sdk-abi-v4 +``` + +TypePHP locates PHPX through the existing `PHPX_HOME` setting, Composer's +`swoole/phpx` installation metadata, or `vendor/swoole/phpx`. TypePHP developers +who independently clone and build the matching `php-8.5.9-wasm` and PHPX +repositories install the complete SDK below that PHPX checkout. There is no +additional WASI SDK environment variable and no set of per-library search +paths: all headers, archives, and the ABI marker must be installed together so +an application cannot accidentally mix incompatible builds. + +TypePHP owns complete SDK orchestration. From the compiler repository, build +and install the matching PHP and PHPX portions with: + +```shell +./wasm/build-sdk.sh \ + --prefix "${PHPX_HOME}/wasm/wasm32-wasip2" \ + --jobs 16 +``` + +The PHP build produces only `libphp.a` and PHP headers. The PHPX build owns +GMP, MPFR, the vendored mpdecimal, `libphpx.a`, and their headers. The +orchestrator validates the combined installation before writing +`.typephp-wasi-sdk-abi`. + +Autoconf, Bison, re2c, Rust, upstream `wit-bindgen`, and the PHP/PHPX source +trees are SDK producer dependencies only. They are never searched for by an +application build. PHPX release packages include the pinned host-side +`phpx-wit-bindgen` needed for application-specific exports below +`/wasm/bin/-/`. + +SDK producer and TypePHP integration checks are kept with the TypePHP WASM +backend rather than php-src: + +```text +wasm/link-numeric-smoke-test.sh GMP, MPFR, and mpdecimal link check +wasm/test-typephp-program.sh TypePHP high-precision integration check +wasm/numeric-smoke-test.cc Native numeric test program +wasm/examples/high-precision.php TypePHP integration example +``` + +## Language-level component exports + +A WASI command remains the default and defines `main()`. A callable component +uses library mode and exports explicitly annotated, statically typed functions: + +```php +#[WasmExport] +function add(int $left, int $right): int +{ + return $left + $right; +} + +#[WasmExport(name: 'greet-user')] +function greetUser(string $name): string +{ + return "Hello, $name"; +} +``` + +```yaml +name: calculator +mode: library +wasm: browser +wasm-package: app:calculator@1.0.0 +wasm-world: calculator +sources: + - src +``` + +The first ABI version supports `bool`, `int`, `float`, `string`, nullable +versions of those types, and `void`. PHP `int` maps to WIT `s64` and therefore +to JavaScript `bigint`. Untyped values, arrays, objects, references, variadic +or optional parameters, generators, and exported methods are compile-time +errors. Every exported call uses a WIT `result` so PHP exceptions and Zend +bailouts are converted before crossing the Canonical ABI boundary. + +`tpc` writes the generated `.wit` next to its intermediate interface manifest, +invokes PHPX's bundled generator, and links a reactor component. Browser mode +then uses Jco exactly as command components do. The generated +`create-runtime()` function returns a WIT `runtime` resource. Creating it +starts one NTS PHP request; dropping it runs `RSHUTDOWN` before the request +memory pool is released. Methods on the same resource must remain serialized, +and only one runtime resource may be active in a component instance. + +Jco exposes the resource as a JavaScript class. Browser code creates one +runtime and reuses it for hot calls: + +```js +const runtime = await api.createRuntime(); +console.log(await runtime.add(20n, 22n)); +console.log(await runtime.greetUser('TypePHP')); + +// Release deterministically instead of waiting for JavaScript GC. +runtime[Symbol.dispose](); +``` + +Top-level WIT `result` errors are surfaced by Jco as JavaScript exceptions. diff --git a/wasm/build-sdk.sh b/wasm/build-sdk.sh new file mode 100755 index 00000000..dd275a08 --- /dev/null +++ b/wasm/build-sdk.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +compiler_dir=$(cd "${script_dir}/.." && pwd) + +usage() +{ + cat <<'EOF' +Usage: ./wasm/build-sdk.sh --prefix [options] + +Options: + --prefix Required complete SDK installation prefix + --php-source PHP source tree (default: projects/php-8.5.9) + --phpx-source PHPX source tree (default: PHPX_HOME or vendor package) + --build-dir Build root (default: /tmp/typephp-wasip2-sdk-build) + --jobs Parallel build jobs (default: 8) + -h, --help Show this help +EOF +} + +prefix= +php_source=${compiler_dir}/projects/php-8.5.9 +phpx_source=${PHPX_HOME:-${compiler_dir}/vendor/swoole/phpx} +build_root=${TYPEPHP_WASM_SDK_BUILD_DIR:-/tmp/typephp-wasip2-sdk-build} +jobs=${TYPEPHP_WASM_JOBS:-8} + +while [[ $# -gt 0 ]]; do + case "$1" in + --prefix) + [[ $# -ge 2 ]] || { echo "--prefix requires a directory" >&2; exit 2; } + prefix=$2 + shift 2 + ;; + --prefix=*) prefix=${1#*=}; shift ;; + --php-source) + [[ $# -ge 2 ]] || { echo "--php-source requires a directory" >&2; exit 2; } + php_source=$2 + shift 2 + ;; + --php-source=*) php_source=${1#*=}; shift ;; + --phpx-source) + [[ $# -ge 2 ]] || { echo "--phpx-source requires a directory" >&2; exit 2; } + phpx_source=$2 + shift 2 + ;; + --phpx-source=*) phpx_source=${1#*=}; shift ;; + --build-dir) + [[ $# -ge 2 ]] || { echo "--build-dir requires a directory" >&2; exit 2; } + build_root=$2 + shift 2 + ;; + --build-dir=*) build_root=${1#*=}; shift ;; + --jobs|-j) + [[ $# -ge 2 ]] || { echo "$1 requires a number" >&2; exit 2; } + jobs=$2 + shift 2 + ;; + --jobs=*|-j*) jobs=${1#*=}; jobs=${jobs#-j}; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; + esac +done + +if [[ -z "${prefix}" ]]; then + echo "--prefix is required" >&2 + usage >&2 + exit 2 +fi +if [[ ! "${jobs}" =~ ^[1-9][0-9]*$ ]]; then + echo "Invalid --jobs value: ${jobs}" >&2 + exit 2 +fi +if [[ ! -x "${php_source}/wasm/build.sh" ]]; then + echo "PHP/WASI build entry was not found: ${php_source}/wasm/build.sh" >&2 + exit 1 +fi +if [[ ! -x "${phpx_source}/wasm/build.sh" ]]; then + echo "PHPX/WASI build entry was not found: ${phpx_source}/wasm/build.sh" >&2 + exit 1 +fi + +mkdir -p "${prefix}" "${build_root}" +prefix=$(cd "${prefix}" && pwd) +php_source=$(cd "${php_source}" && pwd) +phpx_source=$(cd "${phpx_source}" && pwd) +build_root=$(cd "${build_root}" && pwd) + +"${php_source}/wasm/build.sh" \ + --prefix "${prefix}" \ + --build-dir "${build_root}/php" \ + --jobs "${jobs}" + +"${phpx_source}/wasm/build.sh" \ + --prefix "${prefix}" \ + --build-dir "${build_root}/phpx" \ + --jobs "${jobs}" + +required_files=( + .typephp-wasi-php-abi + .typephp-wasi-numeric-abi + .typephp-wasi-runtime-abi + include/php/main/php.h + include/php/main/php_config.h + include/phpx/phpx.h + include/phpx/typephp_helper.h + include/zlib.h + include/zconf.h + include/sodium.h + include/openssl/evp.h + include/libxml2/libxml/parser.h + include/sqlite3.h + include/zip.h + include/bzlib.h + include/gmp.h + include/mpfr.h + include/mpdecimal.h + include/decimal.hh + lib/libphp.a + lib/libphpx.a + lib/libgmp.a + lib/libgmpxx.a + lib/libmpfr.a + lib/libmpdec.a + lib/libmpdec++.a +) +for file in "${required_files[@]}"; do + if [[ ! -f "${prefix}/${file}" ]]; then + echo "TypePHP WASI SDK is incomplete: ${prefix}/${file}" >&2 + exit 1 + fi +done + +printf '%s\n' 'typephp-wasip2-sdk-abi-v4' > "${prefix}/.typephp-wasi-sdk-abi" +echo "Installed complete TypePHP WASI 0.2 SDK: ${prefix}" diff --git a/wasm/build-typephp-program.sh b/wasm/build-typephp-program.sh new file mode 100755 index 00000000..7143f20e --- /dev/null +++ b/wasm/build-typephp-program.sh @@ -0,0 +1,278 @@ +#!/usr/bin/env bash + +set -euo pipefail + +fatal_error() { + local red='' + local reset='' + if [[ -t 2 && -z "${NO_COLOR:-}" && "${TERM:-}" != dumb ]]; then + red=$'\033[1;31m' + reset=$'\033[0m' + fi + printf '%sFatal error: %s%s\n' "${red}" "$1" "${reset}" >&2 + shift + for line in "$@"; do + printf '%s %s%s\n' "${red}" "${line}" "${reset}" >&2 + done + exit 1 +} + +if [[ $# -ne 4 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +caller_dir=${PWD} +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +compiler_dir=$(cd "${script_dir}/.." && pwd) +phpx_dir=$3 +typephp_compiler=$4 +wasi_sdk_dir=${phpx_dir}/wasm/wasm32-wasip2 +wasi_include_dir=${wasi_sdk_dir}/include +wasi_php_include_dir=${wasi_include_dir}/php +wasi_phpx_include_dir=${wasi_include_dir}/phpx +wasi_library_dir=${wasi_sdk_dir}/lib +wasi_cxx=${TYPEPHP_WASI_CXX:?TYPEPHP_WASI_CXX is required} + +input=$1 +if [[ "${input}" != /* ]]; then + input=${caller_dir}/${input} +fi +input=$(realpath "${input}") + +stem=$(basename "${input}" .php) +stem=${stem//[^a-zA-Z0-9_-]/_} +build_root=${TYPEPHP_WASM_PROGRAM_BUILD_DIR:-${caller_dir}/build} +mkdir -p "${build_root}" +build_root=$(cd "${build_root}" && pwd) +generated_dir=${build_root} +generated_source_list=${build_root}/.typephp-wasm-sources +wasm_mode=${TYPEPHP_WASM_MODE:-command} +interface_dir=${build_root}/wasm-interface +interface_manifest=${interface_dir}/typephp-wasm-interface.json +interface_wit=${interface_dir}/world.wit +interface_adapter=${interface_dir}/typephp_wasm_adapter.cc +interface_async_exports=${interface_dir}/jco-async-exports +cleanup_generated_source_list() { + rm -f -- "${generated_source_list}" +} +trap cleanup_generated_source_list EXIT + +if [[ $2 != - ]]; then + output=$2 + if [[ "${output}" != /* ]]; then + output=${caller_dir}/${output} + fi +else + output=${caller_dir}/${stem}.wasm +fi + +mkdir -p "${generated_dir}" "$(dirname "${output}")" + +# Convert first so target-specific source errors are reported before validating +# and linking the separately installed WASI SDK. +internal_compile_env=( + TYPEPHP_WASM_INTERNAL_COMPILE=1 + TYPEPHP_GENERATED_SOURCE_LIST="${generated_source_list}" +) +internal_compile_args=() +if [[ "${wasm_mode}" == library ]]; then + mkdir -p "${interface_dir}" + internal_compile_env+=( + TYPEPHP_WASM_INTERFACE_MANIFEST="${interface_manifest}" + TYPEPHP_WASM_INTERFACE_WIT="${interface_wit}" + TYPEPHP_WASM_INTERFACE_ADAPTER="${interface_adapter}" + TYPEPHP_WASM_INTERFACE_ASYNC_EXPORTS="${interface_async_exports}" + TYPEPHP_WASM_PACKAGE="${TYPEPHP_WASM_PACKAGE:?TYPEPHP_WASM_PACKAGE is required in library mode}" + TYPEPHP_WASM_WORLD="${TYPEPHP_WASM_WORLD:?TYPEPHP_WASM_WORLD is required in library mode}" + ) + internal_compile_args+=(-m lib) +fi +env "${internal_compile_env[@]}" "${typephp_compiler}" "${input}" \ + --dry \ + --target-platform wasm32-wasip2 \ + --build-dir "${generated_dir}" \ + "${internal_compile_args[@]}" \ + --no-progress \ + --no-color + +if [[ ! -s "${generated_source_list}" ]]; then + echo "TypePHP did not write the generated C++ source manifest: ${generated_source_list}" >&2 + exit 1 +fi +mapfile -t generated_sources < "${generated_source_list}" +if [[ ${#generated_sources[@]} -eq 0 ]]; then + echo "TypePHP did not generate any C++ source files" >&2 + exit 1 +fi + +wasi_sdk_stamp=${wasi_sdk_dir}/.typephp-wasi-sdk-abi +if [[ ! -f "${wasi_sdk_stamp}" ]] \ + || ! grep -qx 'typephp-wasip2-sdk-abi-v4' "${wasi_sdk_stamp}"; then + fatal_error \ + "TypePHP WASI SDK is missing or ABI-incompatible: ${wasi_sdk_dir}" \ + "Install the matching PHPX package or set PHPX_HOME to its installation directory." +fi + +required_libraries=(libphp.a libphpx.a libgmp.a libgmpxx.a libmpfr.a libmpdec.a libmpdec++.a) +for library in "${required_libraries[@]}"; do + if [[ ! -f "${wasi_library_dir}/${library}" ]]; then + fatal_error "TypePHP WASI SDK library is missing: ${wasi_library_dir}/${library}" + fi +done +required_headers=( + php/main/php.h + php/main/php_config.h + php/Zend/zend_config.h + php/ext/date/lib/timelib_config.h + phpx/phpx.h + phpx/typephp_helper.h + zlib.h + zconf.h + gmp.h + mpfr.h + decimal.hh +) +for header in "${required_headers[@]}"; do + if [[ ! -f "${wasi_include_dir}/${header}" ]]; then + fatal_error "TypePHP WASI SDK header is missing: ${wasi_include_dir}/${header}" + fi +done + +compile_flags=( + -std=c++17 + -O2 + -fwasm-exceptions + -mllvm -wasm-enable-sjlj + -mllvm -wasm-use-legacy-eh=false + -Wno-deprecated-literal-operator +) +include_flags=( + -I"${wasi_php_include_dir}" + -I"${wasi_php_include_dir}/main" + -I"${wasi_php_include_dir}/Zend" + -I"${wasi_php_include_dir}/TSRM" + -I"${wasi_php_include_dir}/ext/date/lib" + -I"${wasi_phpx_include_dir}" + -I"${wasi_include_dir}" + -I"${generated_dir}/include" +) + +# WIT is application-specific, but its generator is a host build tool. PHPX +# packages a pinned wit-bindgen binary per host so users never install it or a +# Rust toolchain. It is not linked into PHPX or the resulting component. +binding_objects=() +if [[ "${wasm_mode}" == library ]]; then + bindgen=${TYPEPHP_WIT_BINDGEN:?TYPEPHP_WIT_BINDGEN is required in library mode} + if [[ ! -x "${bindgen}" ]]; then + fatal_error \ + "PHPX bundled WIT binding generator is missing: ${bindgen}" \ + "Install the matching PHPX package; installing wit-bindgen separately is not required." + fi + bindgen_version=$("${bindgen}" --version 2>/dev/null || true) + if [[ "${bindgen_version}" != 'wit-bindgen-cli 0.60.0' ]]; then + fatal_error \ + "PHPX bundled WIT binding generator has an incompatible version: ${bindgen_version:-unknown}" \ + "Expected wit-bindgen-cli 0.60.0 from the matching PHPX package." + fi + binding_world=${TYPEPHP_WASM_WORLD//-/_} + "${bindgen}" c \ + --world "${TYPEPHP_WASM_WORLD}" \ + --rename-world "${binding_world}" \ + --out-dir "${interface_dir}" \ + "${interface_wit}" + generated_sources+=("${interface_dir}/${binding_world}.c") + binding_objects+=("${interface_dir}/${binding_world}_component_type.o") +fi + +generated_objects=() +for source in "${generated_sources[@]}"; do + if [[ ! -f "${source}" ]]; then + echo "Generated C++ source file not found: ${source}" >&2 + exit 1 + fi + object=${source%.cc}.o + if [[ "${source}" == *.c ]]; then + "${TYPEPHP_WASI_CC:?TYPEPHP_WASI_CC is required}" -O2 -c "${source}" -o "${object}" + else + "${wasi_cxx}" "${compile_flags[@]}" "${include_flags[@]}" -I"${interface_dir}" -c "${source}" -o "${object}" + fi + generated_objects+=("${object}") +done + +link_mode_flags=() +if [[ "${wasm_mode}" == library ]]; then + link_mode_flags+=(-mexec-model=reactor) +fi + +# Every generated object and runtime archive is already built with -O2. Keep +# the final driver invocation optimized as well, but do not let Clang discover +# an arbitrary system wasm-opt: older Binaryen releases cannot parse the Wasm +# exception-reference instructions emitted by the current WASI SDK. Stripping +# linker metadata has a much larger browser startup benefit than another slow +# whole-module optimization pass and does not change runtime semantics. +"${wasi_cxx}" \ + -O2 \ + --no-wasm-opt \ + -std=c++17 \ + -fwasm-exceptions \ + "${generated_objects[@]}" \ + "${binding_objects[@]}" \ + -Wl,--whole-archive \ + "${wasi_library_dir}/libphpx.a" \ + -Wl,--no-whole-archive \ + "${wasi_library_dir}/libphp.a" \ + "${wasi_library_dir}/libmpdec++.a" \ + "${wasi_library_dir}/libmpdec.a" \ + "${wasi_library_dir}/libmpfr.a" \ + "${wasi_library_dir}/libgmpxx.a" \ + "${wasi_library_dir}/libgmp.a" \ + -lwasi-emulated-signal -lsetjmp -lunwind -ldl -lm \ + "${link_mode_flags[@]}" \ + -Wl,--strip-all \ + -Wl,--fatal-warnings \ + -o "${output}" + +echo "Built TypePHP/WASI program: ${output}" + +if [[ "${TYPEPHP_WASM_BROWSER:-0}" == 1 ]]; then + # Chrome does not yet load components natively, so Jco lowers the same + # WASI 0.2 component to core Wasm + ESM. + jco_bin=${TYPEPHP_JCO:-jco} + browser_dir=${TYPEPHP_WASM_BROWSER_DIR:-${output%.wasm}.browser} + mkdir -p "${browser_dir}" + jco_flags=() + if "${jco_bin}" transpile --help 2>&1 | grep -q -- '--bindgen-enable-wasm-exnref'; then + jco_flags+=(--bindgen-enable-wasm-exnref) + fi + if ! "${jco_bin}" transpile --help 2>&1 | grep -q -- '--async-wasi-imports'; then + echo "Jco does not support JSPI-backed asynchronous WASI imports; upgrade Jco" >&2 + exit 1 + fi + jco_flags+=(--async-mode jspi --async-wasi-imports --async-wasi-exports) + if [[ "${wasm_mode}" == library ]]; then + if ! "${jco_bin}" transpile --help 2>&1 | grep -q -- '--async-exports'; then + fatal_error "Jco does not support JSPI-backed TypePHP exports; upgrade Jco" + fi + if [[ ! -s "${interface_async_exports}" ]]; then + fatal_error "TypePHP did not generate the Jco async export list: ${interface_async_exports}" + fi + mapfile -t jco_async_exports < "${interface_async_exports}" + jco_flags+=(--async-exports "${jco_async_exports[@]}") + fi + "${jco_bin}" transpile "${output}" \ + -o "${browser_dir}" \ + --name program \ + --no-nodejs-compat \ + --no-namespaced-exports \ + --instantiation async \ + --base64-cutoff=0 \ + "${jco_flags[@]}" + echo "Built TypePHP/WASI browser module: ${browser_dir}" +fi + +if [[ "${TYPEPHP_WASM_RUN:-0}" == 1 ]]; then + wasmtime_bin=${TYPEPHP_WASMTIME:-wasmtime} + XDG_CACHE_HOME=${XDG_CACHE_HOME:-/tmp/typephp-wasmtime-cache} \ + "${wasmtime_bin}" "${output}" +fi diff --git a/wasm/examples/high-precision.php b/wasm/examples/high-precision.php new file mode 100644 index 00000000..e7d6668d --- /dev/null +++ b/wasm/examples/high-precision.php @@ -0,0 +1,15 @@ +toString(), "\n"; + + $float = std::bigFloat("1000000000000000000000000000000"); + echo ($float + std::bigFloat("1"))->toString(), "\n"; + + $decimal = std::decimal("12345.00000000000000001"); + echo ($decimal + std::decimal("3.14159265358979323"))->toString(), "\n"; +} diff --git a/wasm/link-numeric-smoke-test.sh b/wasm/link-numeric-smoke-test.sh new file mode 100755 index 00000000..381293cb --- /dev/null +++ b/wasm/link-numeric-smoke-test.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +compiler_dir=$(cd "${script_dir}/.." && pwd) +phpx_home=${PHPX_HOME:-${compiler_dir}/vendor/swoole/phpx} +prefix=${TYPEPHP_WASI_SDK_DIR:-${phpx_home}/wasm/wasm32-wasip2} +output=${TYPEPHP_WASM_NUMERIC_OUTPUT:-/tmp/typephp-wasm-numeric.wasm} +wasi_cxx=${TYPEPHP_WASI_CXX:-$(command -v wasm32-wasip2-clang++ || true)} + +if [[ -z "${wasi_cxx}" ]]; then + echo "Required WASI tool 'wasm32-wasip2-clang++' was not found in PATH" >&2 + exit 1 +fi + +for library in libgmp.a libgmpxx.a libmpfr.a libmpdec.a libmpdec++.a; do + if [[ ! -f "${prefix}/lib/${library}" ]]; then + echo "WASI numeric library not found: ${prefix}/lib/${library}" >&2 + exit 1 + fi +done + +"${wasi_cxx}" \ + -O0 \ + -std=c++17 \ + -fwasm-exceptions \ + -mllvm -wasm-enable-sjlj \ + -mllvm -wasm-use-legacy-eh=false \ + -I"${prefix}/include" \ + "${script_dir}/numeric-smoke-test.cc" \ + -L"${prefix}/lib" \ + -lmpdec++ -lmpdec -lmpfr -lgmpxx -lgmp \ + -lwasi-emulated-signal \ + -lsetjmp -lunwind -lm \ + -o "${output}" + +echo "Linked numeric WASI smoke test: ${output}" diff --git a/wasm/numeric-smoke-test.cc b/wasm/numeric-smoke-test.cc new file mode 100644 index 00000000..9cf5b3a5 --- /dev/null +++ b/wasm/numeric-smoke-test.cc @@ -0,0 +1,52 @@ +#include +#include +#include + +#include +#include + +int main() +{ + mpz_class integer("18446744073709551616"); + integer = integer * integer + 7; + if (integer.get_str() != "340282366920938463463374607431768211463") { + return 1; + } + + mpfr_t value; + mpfr_init2(value, 256); + if (mpfr_set_str(value, "2", 10, MPFR_RNDN) != 0) { + mpfr_clear(value); + return 2; + } + mpfr_sqrt(value, value, MPFR_RNDN); + char float_buffer[96]; + mpfr_snprintf(float_buffer, sizeof(float_buffer), "%.40RNf", value); + mpfr_clear(value); + if (std::string(float_buffer) != "1.4142135623730950488016887242096980785697") { + return 3; + } + + decimal::Decimal small_decimal("1.25"); + if (small_decimal.to_sci() != "1.25") { + std::fprintf(stderr, "unexpected parsed Decimal: %s\n", small_decimal.to_sci().c_str()); + return 4; + } + small_decimal *= decimal::Decimal("8"); + if (small_decimal.to_sci() != "10.00") { + std::fprintf(stderr, "unexpected small Decimal result: %s\n", small_decimal.to_sci().c_str()); + return 5; + } + + decimal::Context decimal_context(32); + decimal::Decimal decimal_value("12345678901234567890.125"); + decimal_value = decimal_value.mul(decimal::Decimal("8"), decimal_context); + const std::string decimal_string = decimal_value.to_sci(); + if (decimal_string != "98765431209876543121.000") { + std::fprintf(stderr, "unexpected Decimal result: %s\n", decimal_string.c_str()); + return 6; + } + + std::puts("TYPEPHP_WASM_NUMERIC_OK"); + return 0; +} diff --git a/wasm/test-typephp-program.sh b/wasm/test-typephp-program.sh new file mode 100755 index 00000000..d80a8928 --- /dev/null +++ b/wasm/test-typephp-program.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +compiler_dir=$(cd "${script_dir}/.." && pwd) +output=${TYPEPHP_WASM_TEST_OUTPUT:-/tmp/typephp-wasm-high-precision.wasm} +wasmtime_bin=${TYPEPHP_WASMTIME:-$(command -v wasmtime || true)} +if [[ -z "${wasmtime_bin}" ]]; then + echo "Required WASI tool 'wasmtime' was not found in PATH" >&2 + exit 1 +fi + +output_dir=$(dirname "${output}") +output_name=$(basename "${output}") +( + cd "${output_dir}" + php "${compiler_dir}/bin/tpc.php" --wasm=component "${script_dir}/examples/high-precision.php" + if [[ high-precision.wasm != "${output_name}" ]]; then + mv high-precision.wasm "${output_name}" + fi +) + +actual=$(XDG_CACHE_HOME=${XDG_CACHE_HOME:-/tmp/typephp-wasmtime-cache} \ + "${wasmtime_bin}" -S http "${output}") +expected=$'1111111101111111110111111111010\n1000000000000000000000000000001\n12348.14159265358979324' + +if [[ "${actual}" != "${expected}" ]]; then + echo "Unexpected TypePHP/WASI output:" >&2 + printf '%s\n' "${actual}" >&2 + exit 1 +fi + +printf '%s\n' "${actual}" +echo "TypePHP/WASI integration test passed"