From 60f5f10ba5f207fad831370fbaa94804392e0647 Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 6 Jul 2026 04:27:15 +0000 Subject: [PATCH] =?UTF-8?q?feat(code):=20=E6=96=B0=E5=A2=9E=E9=80=9A?= =?UTF-8?q?=E7=94=A8=20code=20=E9=9B=B6=E4=BB=B6=EF=BC=88sandbox=20inline?= =?UTF-8?q?=20JS=EF=BC=89=E2=80=94=E2=80=94=20Arcrun#10=20=E8=A8=AD?= =?UTF-8?q?=E8=A8=88+PoC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit n8n Code node 式逃生口:config 帶 inline JS、stdin 帶 input JSON、 stdout 回 {success,data}|{success:false,error,error_type}。 沙箱=QuickJS-wasm:user JS 跑在 QuickJS context,global 只有純 ECMAScript 內建 + 唯一 curated builtin sha256(純函式);碰不到網路/檔案/env/secret/ Worker 物件圖。資源上限:timeout(interrupt)/memory/stack/output/code size。 本輪=設計+PoC,未部署 leo21c。sandbox.mjs + test/ 為 Node/vitest 可跑實作 (12 測試全綠,含 card→envelope 與原模組 planCard 逐欄全等)。index.ts 為 Worker host 骨架、DESIGN.md 記錄機制/安全性質/生產路徑/設計岔路(A/B)。 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01HJiLCRUU2o3aSpPEzVCt2o --- registry/components/code/DESIGN.md | 81 + .../components/code/component.contract.yaml | 78 + registry/components/code/index.ts | 47 + registry/components/code/package-lock.json | 1655 +++++++++++++++++ registry/components/code/package.json | 16 + registry/components/code/sandbox.mjs | 119 ++ .../test/fixtures/card-to-envelope.oracle.mjs | Bin 0 -> 14058 bytes .../fixtures/card-to-envelope.usercode.js | Bin 0 -> 14074 bytes .../code/test/fixtures/fixture-card.md | 25 + .../components/code/test/sandbox.test.mjs | 128 ++ registry/components/code/vitest.config.mjs | 5 + 11 files changed, 2154 insertions(+) create mode 100644 registry/components/code/DESIGN.md create mode 100644 registry/components/code/component.contract.yaml create mode 100644 registry/components/code/index.ts create mode 100644 registry/components/code/package-lock.json create mode 100644 registry/components/code/package.json create mode 100644 registry/components/code/sandbox.mjs create mode 100644 registry/components/code/test/fixtures/card-to-envelope.oracle.mjs create mode 100644 registry/components/code/test/fixtures/card-to-envelope.usercode.js create mode 100644 registry/components/code/test/fixtures/fixture-card.md create mode 100644 registry/components/code/test/sandbox.test.mjs create mode 100644 registry/components/code/vitest.config.mjs diff --git a/registry/components/code/DESIGN.md b/registry/components/code/DESIGN.md new file mode 100644 index 0000000..af884fb --- /dev/null +++ b/registry/components/code/DESIGN.md @@ -0,0 +1,81 @@ +# `code` 零件 —— 沙箱設計小結(Arcrun#10) + +> 狀態:設計 + PoC(Node/vitest 綠燈),**未部署 leo21c**。live 前需總管與 leo 過寫入/部署閘。 + +## 0. arcrun 零件 runtime 真相(先摸清再設計) + +- 每個 logic 零件 = **一顆 Worker**(`{name}.arcrun.dev`),POST JSON → 內部跑 wasm → JSON 回傳。 +- 零件實作 = **Go(`//go:build tinygo`)→ TinyGo 編成 WASI-preview1 wasm**,build 時靜態 bundle 進 Worker(`wrangler.toml [[wasm_modules]]`)。 +- host(`component-worker-template/src/index.ts`)**用 TS 自己實作一份 WASI-preview1 shim**:`fd_read` 餵 stdin(= POST body 的 JSON)、`fd_write` 收 stdout(= 回傳 JSON)。 +- `no_network` / `no_filesystem` **不是靠 wasm 自律,是 host 根本不提供那些 import**:`sock_*` / `path_*` 全回 `ENOSYS(76)`、`u6u.http_request` no-op。→ 零件對外「無 ambient 能力」是**由 host 建構保證**的。 +- runtime 是 **workerd(=cf-workers)**;contract 的 `wazero` 只是相容標記(本機/CLI 測試路徑),生產不經 wazero。 + +**關鍵結論**:host 只認 WASI-preview1 + stdin/stdout JSON,**與 guest 語言無關**。所以載入「QuickJS 編成的 wasm」與載入 TinyGo wasm 在 runtime 層是同一件事 —— **QuickJS-wasm 可行,且完全合現有零件模型**。差別只在 guest 從「TinyGo」換成「QuickJS」,且 user 的 JS 是「跑時餵進去的資料」而非「build 時編進去的程式」。 + +## 1. 沙箱機制 + +user JS 跑在 **QuickJS context** 裡,該 context 三層封裝、逐層無逃逸: + +``` +Cloudflare Worker isolate(V8) + └─ QuickJS wasm module(線性記憶體沙箱;無 WASI 網路/檔案 import) + └─ QuickJS JS context(global 只有純 ECMAScript 內建) + └─ user code:讀 input、return 值 +``` + +- **無 ambient 能力(by construction)**:QuickJS context 起始 global 只有 `Object/Array/JSON/Math/Date/String/RegExp…`,**沒有** `fetch/process/require/WebAssembly/XMLHttpRequest/globalThis.env`。要給的能力必須 host 明確、逐一注入。 +- **唯一注入的 curated builtin**:`sha256(str)`(純、決定性、零能力 —— 不能碰網路/檔案/secret)。card→envelope 的 content_hash 需要它。任何新 builtin 都必須維持「純函式、無 ambient 能力」這條線。 +- **input 穿越邊界**:以 JSON 字串 marshal,沙箱內 `JSON.parse` —— host 與 guest **不共享物件圖**,杜絕 prototype/引用逃逸。 +- **user code 形狀**:當「函式體」跑(可含 `const`/`function` 宣告、以 `return` 回值),綁定唯一入參 `input`。回值 `JSON.stringify` 後交回 host。 + +## 2. 生產路徑(PoC → live 的差異) + +- **PoC**(本目錄 `sandbox.mjs` + `test/`):用 `quickjs-emscripten`(預編 QuickJS wasm)在 **Node/vitest** 跑,證明沙箱**語義**。`sha256` 以 Node `node:crypto` 實作。**12 測試全綠**,含 card→envelope 全等。 +- **live(Worker)**:`quickjs-emscripten` 有 Cloudflare Workers 相容 variant,可直接在零件 Worker 內用。**唯一要改**:`sha256` 不用 host function(Worker 的 Web Crypto `crypto.subtle.digest` 是 async,跟 QuickJS 同步 host-call 不合),改成**把純 JS SHA-256 當 prelude 字串注入沙箱**(無 host call、Node/Worker 皆決定性)。這也讓「curated builtin = 純演算法字串」成為往後加 builtin 的標準做法。 + +## 3. 資源限制(防跑飛) + +| 限制 | 機制 | 預設 | +|---|---|---| +| 執行 timeout | QuickJS runtime `setInterruptHandler`(逐指令檢查 wall-clock deadline) | 1000 ms | +| 記憶體 | `setMemoryLimit`(QuickJS runtime 硬上限) | 16 MiB | +| 堆疊 | `setMaxStackSize`(防深遞迴) | 512 KiB | +| 輸出大小 | host 量測 stdout JSON bytes,超限即 `ResourceError` | 1 MiB | +| code 大小 | host 量測 user code bytes,超限即 `ResourceError` | 256 KiB | + +節點 config 可覆蓋(但不得放寬過契約 `sandbox_limits` 硬上限 —— 由零件在讀 config 時 clamp)。 + +## 4. 錯誤處理(Worker 絕不掛) + +一律回結構化 envelope,`error_type` 分類: +- `UserCodeError`:user code 拋錯 / 語法錯誤。 +- `TimeoutError`:超時被 interrupt。 +- `ResourceError`:記憶體 / 輸出 / code 超限。 +- `ContractError`:輸入形狀不合(如 code 非字串)。 +- `SandboxError`:其餘沙箱層例外。 + +## 5. 安全性質(總結) + +1. user code **無網路**:沒有 fetch/XHR,QuickJS wasm 也沒有 WASI socket import。 +2. user code **無檔案**:沒有 fs,WASI path_* 一律 ENOSYS。 +3. user code **無 env/secret**:沒有 process,Worker 的 `env`(bindings/secret)不進 QuickJS context。 +4. user code **碰不到 Worker 物件圖**:獨立 wasm 線性記憶體 + 獨立 QuickJS heap + JSON 邊界。 +5. **可終止**:timeout/記憶體/輸出上限,跑飛也不拖垮 Worker。 +6. **決定性**(除 `Date`/`Math.random`):curated builtin 全是純函式。 + +## 6. 設計岔路(給總管/leo 裁) + +沙箱主機制 = **QuickJS-wasm**(本 PoC 已證可行且合模型)。封裝方式有兩條,取捨如下: + +- **A. QuickJS-emscripten 直接在零件 Worker 用(PoC 走這條,推薦先行)** + 優點:本環境即可跑、npm 現成、限制 API 齊(timeout/mem/stack)、已在 Workers 驗證過可用。 + 缺點:不經現有 TinyGo-wasm 的 `[[wasm_modules]]` + 自建 WASI shim 路徑,是零件家族裡的「特例封裝」。 +- **B. 自建 QuickJS+C-harness → wasi-sdk 編成 preview1 wasm,跑在現有 host shim(最「同構」)** + 優點:與其他零件同一條 runtime(同 WASI shim),`wasi_target: preview1` 名副其實,「無 ambient 能力」最純。 + 缺點:需要 wasi-sdk 工具鏈(**本環境無 sysroot,無法即刻 build**)、要維護一段 C harness。 + +**建議**:先以 A live(快、已驗證),把 B 列為後續「收斂到同構 runtime」的技術債。若總管要求所有零件單一 runtime,則走 B,但需先補 wasi-sdk build 基礎設施。**此為安全敏感原語,機制選定請總管拍板再 live。** + +## 7. 首個消費者(Arcrun#8) + +`km_wiki_ingest_drain` 的 card→envelope 解析,把 `card-to-envelope.mjs` 的 `parseCard`/`planEnvelopes`/`planCard` 當作 `code` 節點的 inline JS(去掉 `import 'node:crypto'` 與 `export`,改用注入的 `sha256`)。PoC 測試 ⑤ 已證:**沙箱輸出與原始模組 `planCard` 逐欄全等**(含 content_hash)。→ 可丟掉 domain 零件 `km_wiki_card_parse`。 diff --git a/registry/components/code/component.contract.yaml b/registry/components/code/component.contract.yaml new file mode 100644 index 0000000..a46561e --- /dev/null +++ b/registry/components/code/component.contract.yaml @@ -0,0 +1,78 @@ +canonical_id: "code" +display_name: "程式碼(沙箱 inline JS)" +category: "logic" +version: "v1" +wasi_target: "preview1" +stability: "experimental" +runtime_compat: + - "cf-workers" + - "workerd" +constraints: + max_size_kb: 2048 + max_cold_start_ms: 80 + no_network_syscall: true + no_filesystem_syscall: true + io_model: "stdin_stdout_json" +# --- 沙箱資源上限(可被節點 config 的 limits 覆蓋,但不得放寬過硬上限)--- +sandbox_limits: + timeout_ms: 1000 + memory_bytes: 16777216 # 16 MiB + max_stack_bytes: 524288 # 512 KiB + max_output_bytes: 1048576 # 1 MiB + max_code_bytes: 262144 # 256 KiB +input_schema: + type: object + required: [code] + properties: + code: + type: string + description: "一段 inline JS(函式體)。可讀綁定的 `input`,以 `return` 回傳一個 JSON-able 值。碰不到網路/檔案/env/secret。" + input: + description: "上游資料(任意 JSON),在沙箱內綁為全域 `input`。" + limits: + type: object + description: "選填,覆蓋預設資源上限(不得超過契約 sandbox_limits 硬上限)。" + properties: + timeout_ms: { type: number } + memory_bytes: { type: number } + max_output_bytes: { type: number } +output_schema: + type: object + properties: + success: + type: boolean + data: + description: "user code 的回傳值(success=true 時)。" + error: + type: string + description: "success=false 時的錯誤訊息。" + error_type: + type: string + enum: [UserCodeError, TimeoutError, ResourceError, ContractError, SandboxError] +gherkin_tests: + - scenario: "基本 sum" + given: '{"code":"return {sum: input.a + input.b};","input":{"a":2,"b":40}}' + then_contains: '"sum":42' + - scenario: "沙箱隔離:碰不到 fetch" + given: '{"code":"return typeof fetch;","input":{}}' + then_contains: '"data":"undefined"' + - scenario: "user code 拋錯 → 結構化 error" + given: '{"code":"throw new Error(\"boom\");","input":{}}' + then_contains: '"success":false' +tags: [builtin, logic, code, sandbox, javascript, escape-hatch] +description: > + 通用「程式碼逃生口」。跑一段 sandbox inline JS(n8n Code node 式): + config 帶 `code`(inline JS 函式體),stdin 帶 `input`(上游 JSON), + stdout 回 `{success, data}` 或 `{success:false, error, error_type}`。 + user code 在 QuickJS-wasm 沙箱內執行,只有純 ECMAScript 內建 + host 明確注入的 + curated builtin(目前:純函式 sha256),碰不到網路/檔案/env/secret/Worker 物件圖。 + 用於一次性/小段程式邏輯(如卡片→envelope 文字處理),避免各自鑄 domain 零件。 +config_example: | + my_code: # 節點名稱(可自訂) + code: | # inline JS 函式體(必填);讀 input、return 一個 JSON-able 值 + const doubled = input.items.map(x => x * 2); + return { doubled, count: doubled.length }; + input: # 上游資料(選填;workflow 可用引用注入) + items: [1, 2, 3] + limits: # 資源上限覆蓋(選填) + timeout_ms: 2000 diff --git a/registry/components/code/index.ts b/registry/components/code/index.ts new file mode 100644 index 0000000..cdf3b6a --- /dev/null +++ b/registry/components/code/index.ts @@ -0,0 +1,47 @@ +/** + * arcrun `code` 零件 —— Worker host(骨架,尚未部署驗證) + * + * POST / → { code, input, limits? } → QuickJS-wasm 沙箱 → { success, data } | { success:false, error, error_type } + * + * 與其他 logic 零件的差異: + * - 其他零件 = 一顆「arcrun 自建 TinyGo→wasm」,靜態 bundle 進 Worker([[wasm_modules]]), + * 跑在 component-worker-template 的 WASI-preview1 shim 上。 + * - `code` 零件 = 載入「QuickJS(JS 直譯器)編成的 wasm」,把 user 的 inline JS 當「資料」 + * 餵進去跑。QuickJS 的 wasm 由 quickjs-emscripten 提供(有 Cloudflare Workers 相容 variant)。 + * + * ⚠️ 此檔為設計骨架。PoC 的可執行 + 受測實作在 ./sandbox.mjs(Node/vitest 綠燈)。 + * live 化差異見 DESIGN.md「② 生產路徑」與「curated builtins(sha256)」。 + */ + +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { getQuickJS } from 'quickjs-emscripten'; + +const app = new Hono(); +app.use('*', cors()); +app.get('/', (c) => c.json({ ok: true, component: 'code' })); + +app.post('/', async (c) => { + let body: { code?: unknown; input?: unknown; limits?: Record }; + try { + body = await c.req.json(); + } catch { + return c.json({ success: false, error: 'request body must be JSON', error_type: 'ContractError' }, 400); + } + // runCode 語義與簽章同 ./sandbox.mjs(PoC 已受測)。生產版把 sha256 curated builtin + // 換成「純 JS SHA-256 prelude」(無 host call、Node/Worker 皆決定性),見 DESIGN.md。 + const result = await runCodeInWorker(String(body.code ?? ''), body.input, body.limits); + return c.json(result); +}); + +export default app; + +// 生產實作(待補:把 sandbox.mjs 的 runCode 移植成 Worker 版)。 +declare function runCodeInWorker( + code: string, + input: unknown, + limits?: Record, +): Promise; + +// 保留 import 以標示相依(bundler 不 tree-shake 掉): +void getQuickJS; diff --git a/registry/components/code/package-lock.json b/registry/components/code/package-lock.json new file mode 100644 index 0000000..da29088 --- /dev/null +++ b/registry/components/code/package-lock.json @@ -0,0 +1,1655 @@ +{ + "name": "arcrun-component-code", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "arcrun-component-code", + "version": "0.1.0", + "dependencies": { + "quickjs-emscripten": "^0.31.0" + }, + "devDependencies": { + "vitest": "^3.1.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/@jitl/quickjs-ffi-types": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-ffi-types/-/quickjs-ffi-types-0.31.0.tgz", + "integrity": "sha512-1yrgvXlmXH2oNj3eFTrkwacGJbmM0crwipA3ohCrjv52gBeDaD7PsTvFYinlAnqU8iPME3LGP437yk05a2oejw==", + "license": "MIT" + }, + "node_modules/@jitl/quickjs-wasmfile-debug-asyncify": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.31.0.tgz", + "integrity": "sha512-YkdzQdr1uaftFhgEnTRjTTZHk2SFZdpWO7XhOmRVbi6CEVsH9g5oNF8Ta1q3OuSJHRwwT8YsuR1YzEiEIJEk6w==", + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.31.0" + } + }, + "node_modules/@jitl/quickjs-wasmfile-debug-sync": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-sync/-/quickjs-wasmfile-debug-sync-0.31.0.tgz", + "integrity": "sha512-8XvloaaWBONqcHXYs5tWOjdhQVxzULilIfB2hvZfS6S+fI4m2+lFiwQy7xeP8ExHmiZ7D8gZGChNkdLgjGfknw==", + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.31.0" + } + }, + "node_modules/@jitl/quickjs-wasmfile-release-asyncify": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-asyncify/-/quickjs-wasmfile-release-asyncify-0.31.0.tgz", + "integrity": "sha512-uz0BbQYTxNsFkvkurd7vk2dOg57ElTBLCuvNtRl4rgrtbC++NIndD5qv2+AXb6yXDD3Uy1O2PCwmoaH0eXgEOg==", + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.31.0" + } + }, + "node_modules/@jitl/quickjs-wasmfile-release-sync": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-sync/-/quickjs-wasmfile-release-sync-0.31.0.tgz", + "integrity": "sha512-hYduecOByj9AsAfsJhZh5nA6exokmuFC8cls39+lYmTCGY51bgjJJJwReEu7Ff7vBWaQCL6TeDdVlnp2WYz0jw==", + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.31.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "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/@vitest/expect": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.6", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "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/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/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/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/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/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/quickjs-emscripten": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/quickjs-emscripten/-/quickjs-emscripten-0.31.0.tgz", + "integrity": "sha512-K7Yt78aRPLjPcqv3fIuLW1jW3pvwO21B9pmFOolsjM/57ZhdVXBr51GqJpalgBlkPu9foAvhEAuuQPnvIGvLvQ==", + "license": "MIT", + "dependencies": { + "@jitl/quickjs-wasmfile-debug-asyncify": "0.31.0", + "@jitl/quickjs-wasmfile-debug-sync": "0.31.0", + "@jitl/quickjs-wasmfile-release-asyncify": "0.31.0", + "@jitl/quickjs-wasmfile-release-sync": "0.31.0", + "quickjs-emscripten-core": "0.31.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quickjs-emscripten-core": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/quickjs-emscripten-core/-/quickjs-emscripten-core-0.31.0.tgz", + "integrity": "sha512-oQz8p0SiKDBc1TC7ZBK2fr0GoSHZKA0jZIeXxsnCyCs4y32FStzCW4d1h6E1sE0uHDMbGITbk2zhNaytaoJwXQ==", + "license": "MIT", + "dependencies": { + "@jitl/quickjs-ffi-types": "0.31.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "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": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/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/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/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/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz", + "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/registry/components/code/package.json b/registry/components/code/package.json new file mode 100644 index 0000000..fa9bee5 --- /dev/null +++ b/registry/components/code/package.json @@ -0,0 +1,16 @@ +{ + "name": "arcrun-component-code", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "arcrun code 零件 —— sandbox inline JS(QuickJS-wasm)", + "scripts": { + "test": "vitest run --config vitest.config.mjs" + }, + "dependencies": { + "quickjs-emscripten": "^0.31.0" + }, + "devDependencies": { + "vitest": "^3.1.0" + } +} diff --git a/registry/components/code/sandbox.mjs b/registry/components/code/sandbox.mjs new file mode 100644 index 0000000..cb7289a --- /dev/null +++ b/registry/components/code/sandbox.mjs @@ -0,0 +1,119 @@ +// arcrun `code` 零件 —— 沙箱核心(PoC 參考實作) +// --------------------------------------------------------------------------- +// 語義:n8n Code node 式。config 帶一段 inline user JS,stdin 帶 input JSON。 +// user code 只能:讀 `input`(已解析的 stdin JSON)、回傳一個 JSON-able 值。 +// user code 碰不到:網路 / 檔案 / env / secret / Worker 物件圖。 +// +// 隔離機制:user JS 跑在 QuickJS(JS 直譯器)編成的 wasm sandbox 內。QuickJS +// context 的 global 只有純 ECMAScript 內建(Object/Array/JSON/Math/Date/String…), +// 沒有 fetch / process / require / globalThis.env / WebAssembly / 任何 host binding +// —— 「無 ambient 能力(no ambient capability by construction)」。要給的能力, +// 只能由 host 明確、逐一注入為 curated builtin(本檔 = 一個純函式 sha256)。 +// +// 對齊 arcrun 契約:io_model=stdin_stdout_json、no_network_syscall、 +// no_filesystem_syscall。與其他零件唯一差別=直譯器是 QuickJS-wasm 而非 TinyGo-wasm。 + +import { getQuickJS } from 'quickjs-emscripten'; +import { createHash } from 'node:crypto'; + +export const DEFAULT_LIMITS = { + timeout_ms: 1000, // 執行牆鐘上限(interrupt handler 逐指令檢查 deadline) + memory_bytes: 16 * 1024 * 1024, // QuickJS runtime 記憶體硬上限 + max_stack_bytes: 512 * 1024, // 遞迴/深堆疊上限 + max_output_bytes: 1024 * 1024, // stdout JSON 大小上限(防跑飛) + max_code_bytes: 256 * 1024, // user code 本身大小上限 +}; + +// curated 安全 builtin:純、決定性、零 ambient 能力(不能碰網路/檔案/secret)。 +// PoC 用 Node crypto 實作 sha256;live(Worker)要換成 Web Crypto 的 +// crypto.subtle.digest('SHA-256', …)(見 DESIGN.md「curated builtins」)。 +function hostSha256(s) { + return createHash('sha256').update(s, 'utf8').digest('hex'); +} + +/** + * 在沙箱內跑一段 user code。 + * @param {string} code user 的 inline JS(函式體:可含宣告、以 `return` 回值) + * @param {*} input 已解析的 stdin JSON(會以 `input` 綁進沙箱) + * @param {object} [opts] { limits, builtins } + * @returns {Promise<{success:true,data:*}|{success:false,error:string,error_type?:string}>} + */ +export async function runCode(code, input, opts = {}) { + const limits = { ...DEFAULT_LIMITS, ...(opts.limits || {}) }; + + if (typeof code !== 'string') { + return err('code must be a string', 'ContractError'); + } + if (Buffer.byteLength(code, 'utf8') > limits.max_code_bytes) { + return err(`code exceeds max_code_bytes (${limits.max_code_bytes})`, 'ResourceError'); + } + + const QuickJS = await getQuickJS(); + const runtime = QuickJS.newRuntime(); + runtime.setMemoryLimit(limits.memory_bytes); + runtime.setMaxStackSize(limits.max_stack_bytes); + + const deadline = Date.now() + limits.timeout_ms; + let interrupted = false; + runtime.setInterruptHandler(() => { + if (Date.now() > deadline) { interrupted = true; return true; } + return false; + }); + + const ctx = runtime.newContext(); + try { + // 注入 curated builtin:sha256(單一純函式;其餘一律不給) + const shaFn = ctx.newFunction('sha256', (argHandle) => { + const s = ctx.getString(argHandle); + return ctx.newString(hostSha256(s)); + }); + ctx.setProp(ctx.global, 'sha256', shaFn); + shaFn.dispose(); + + // 綁 input:以 JSON 字串安全穿越邊界,沙箱內 JSON.parse(不共享物件圖) + const inputJson = JSON.stringify(input === undefined ? null : input); + + // 包裝:user code 當成函式體跑,回值 JSON.stringify 後交回 host。 + // `input` 為唯一綁定;`sha256` 為唯一注入 builtin。 + const wrapped = `(() => { + "use strict"; + const input = JSON.parse(${JSON.stringify(inputJson)}); + const __run = (input) => { ${code} + }; + const __out = __run(input); + return JSON.stringify(__out === undefined ? null : __out); + })()`; + + const evalResult = ctx.evalCode(wrapped, 'user-code.js'); + + if (evalResult.error) { + const detail = ctx.dump(evalResult.error); + evalResult.error.dispose(); + if (interrupted) return err(`execution timed out after ${limits.timeout_ms}ms`, 'TimeoutError'); + const msg = typeof detail === 'object' && detail + ? `${detail.name || 'Error'}: ${detail.message || ''}`.trim() + : String(detail); + return err(msg, 'UserCodeError'); + } + + const outJson = ctx.getString(evalResult.value); + evalResult.value.dispose(); + + if (Buffer.byteLength(outJson, 'utf8') > limits.max_output_bytes) { + return err(`output exceeds max_output_bytes (${limits.max_output_bytes})`, 'ResourceError'); + } + return { success: true, data: JSON.parse(outJson) }; + } catch (e) { + if (interrupted) return err(`execution timed out after ${limits.timeout_ms}ms`, 'TimeoutError'); + const m = e instanceof Error ? e.message : String(e); + if (/out of memory|memory/i.test(m)) return err('out of memory', 'ResourceError'); + return err(m, 'SandboxError'); + } finally { + ctx.dispose(); + runtime.dispose(); + } +} + +function err(message, error_type) { + return { success: false, error: message, error_type }; +} diff --git a/registry/components/code/test/fixtures/card-to-envelope.oracle.mjs b/registry/components/code/test/fixtures/card-to-envelope.oracle.mjs new file mode 100644 index 0000000000000000000000000000000000000000..46ed7ed70015d4d8fd8aa8b55592adc3288df0cc GIT binary patch literal 14058 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 XQ%y)OV};fx&g&dDPFX8aTqF2DT1I-g literal 0 HcmV?d00001 diff --git a/registry/components/code/test/fixtures/card-to-envelope.usercode.js b/registry/components/code/test/fixtures/card-to-envelope.usercode.js new file mode 100644 index 0000000000000000000000000000000000000000..fb7a6dac5c2a57265e6329ac6f0b7250c07f1893 GIT binary patch literal 14074 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 GE&m77fQv=| literal 0 HcmV?d00001 diff --git a/registry/components/code/test/fixtures/fixture-card.md b/registry/components/code/test/fixtures/fixture-card.md new file mode 100644 index 0000000..ce91f40 --- /dev/null +++ b/registry/components/code/test/fixtures/fixture-card.md @@ -0,0 +1,25 @@ +--- +tags: [arcrun, 測試] +gloss: 一張測沙箱用的示範卡片。 +pipeline_candidate: true +--- +# 沙箱示範卡 + +← [[notes/00-INDEX]] + +這張卡引用了 [[notes/arcrun-runtime]] 與 [[notes/quickjs-sandbox]]。 + +## 實體 + +- **QuickJS**(quickjs/qjs)— 小型 JS 直譯器,可編成 wasm。 +- **wazero** — Go 寫的 WASI runtime。 +- **workerd** — Cloudflare Worker 的開源 runtime。 + +## 關聯 + +### 內文知識關係 +- QuickJS >> 編譯成 >> wasm +- workerd >> 執行 >> wasm + +### 卡片關係 +- [[沙箱示範卡]] >> 依賴 >> [[notes/arcrun-runtime]] diff --git a/registry/components/code/test/sandbox.test.mjs b/registry/components/code/test/sandbox.test.mjs new file mode 100644 index 0000000..73eb8cb --- /dev/null +++ b/registry/components/code/test/sandbox.test.mjs @@ -0,0 +1,128 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { runCode } from '../sandbox.mjs'; +import { planCard } from './fixtures/card-to-envelope.oracle.mjs'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const FIX = join(HERE, 'fixtures'); + +describe('① 基本 snippet 跑通', () => { + it('sum: {return {sum: input.a + input.b}}', async () => { + const r = await runCode('return {sum: input.a + input.b};', { a: 2, b: 40 }); + expect(r).toEqual({ success: true, data: { sum: 42 } }); + }); + + it('可用純 ECMAScript 內建(Array/JSON/Math/Date)', async () => { + const r = await runCode( + 'return {mapped: input.xs.map(x=>x*x), max: Math.max(...input.xs), isNum: typeof Date.now()};', + { xs: [1, 2, 3] }, + ); + expect(r.success).toBe(true); + expect(r.data.mapped).toEqual([1, 4, 9]); + expect(r.data.max).toBe(3); + expect(r.data.isNum).toBe('number'); + }); +}); + +describe('② 沙箱隔離:無 ambient 能力', () => { + it('fetch / process / require / WebAssembly / globalThis.env 皆 undefined', async () => { + const r = await runCode(`return { + fetch: typeof fetch, + process: typeof process, + require: typeof require, + wasm: typeof WebAssembly, + globalThisEnv: typeof (globalThis.env), + xhr: typeof XMLHttpRequest, + };`, {}); + expect(r.success).toBe(true); + expect(r.data).toEqual({ + fetch: 'undefined', process: 'undefined', require: 'undefined', + wasm: 'undefined', globalThisEnv: 'undefined', xhr: 'undefined', + }); + }); + + it('嘗試打網路(fetch)→ 被擋、回結構化 error(Worker 不掛)', async () => { + const r = await runCode(`return fetch('https://evil.example/steal');`, {}); + expect(r.success).toBe(false); + expect(r.error).toMatch(/fetch.*is not defined/i); + expect(r.error_type).toBe('UserCodeError'); + }); + + it('嘗試讀 env/secret → 讀不到(process undefined)', async () => { + const r = await runCode(`return process.env.GITEA_TOKEN;`, {}); + expect(r.success).toBe(false); + expect(r.error).toMatch(/process.*is not defined/i); + }); + + it('host 端變數不外洩:沙箱是獨立 heap', async () => { + const r = await runCode(`return typeof GITEA_TOKEN + '|' + typeof globalThis.GITEA_TOKEN;`, {}); + expect(r.success).toBe(true); + expect(r.data).toBe('undefined|undefined'); + }); +}); + +describe('③ 錯誤處理 → 結構化 {error}', () => { + it('user code 拋錯 → success:false + error 訊息', async () => { + const r = await runCode(`throw new Error('boom');`, {}); + expect(r.success).toBe(false); + expect(r.error).toMatch(/boom/); + expect(r.error_type).toBe('UserCodeError'); + }); + + it('語法錯誤 → 結構化 error(不炸 host)', async () => { + const r = await runCode(`return {;`, {}); + expect(r.success).toBe(false); + expect(r.error_type).toBe('UserCodeError'); + }); +}); + +describe('④ 資源限制', () => { + it('timeout:無窮迴圈 → TimeoutError(不掛死 host)', async () => { + const r = await runCode(`while(true){}`, {}, { limits: { timeout_ms: 200 } }); + expect(r.success).toBe(false); + expect(r.error_type).toBe('TimeoutError'); + }, 10000); + + it('輸出過大 → ResourceError', async () => { + const r = await runCode(`return 'x'.repeat(input.n);`, { n: 5000 }, { limits: { max_output_bytes: 1000 } }); + expect(r.success).toBe(false); + expect(r.error_type).toBe('ResourceError'); + }); + + it('code 過大 → ResourceError', async () => { + const big = '/*' + 'a'.repeat(3000) + '*/ return 1;'; + const r = await runCode(big, {}, { limits: { max_code_bytes: 1000 } }); + expect(r.success).toBe(false); + expect(r.error_type).toBe('ResourceError'); + }); +}); + +describe('⑤ 首個真實案例:card-to-envelope 在 code 零件內跑,產出與原模組一致', () => { + const md = readFileSync(join(FIX, 'fixture-card.md'), 'utf8'); + const usercode = readFileSync(join(FIX, 'card-to-envelope.usercode.js'), 'utf8'); + const relPath = 'system-dev/wiki/cards/notes/沙箱示範卡.md'; + const repo = 'Leo/notes'; + + // 移除時間相依欄位(Date.now())以做穩定 deep-equal + const strip = (plan) => { + const p = structuredClone(plan); + for (const e of p.envelopes) delete e.extractor.extracted_at; + return p; + }; + + it('沙箱輸出 === 原始模組 planCard 輸出(entry/nodes/triplets/envelopes 全等)', async () => { + const oracle = planCard(md, relPath, repo, {}); + const r = await runCode(usercode, { md, relPath, repo, opts: {} }, { + limits: { timeout_ms: 3000, max_output_bytes: 4 * 1024 * 1024 }, + }); + expect(r.success).toBe(true); + expect(strip(r.data)).toEqual(strip(oracle)); + expect(r.data.entry.page_name).toBe('wikicard:Leo/notes/沙箱示範卡'); + expect(r.data.entry.metadata.embed).toBe(true); + // 注入的 sha256 builtin 與 node crypto 一致 → content_hash 逐字相同 + expect(r.data.entry.metadata.content_hash).toBe(oracle.entry.metadata.content_hash); + expect(r.data.envelopes.length).toBeGreaterThanOrEqual(1); + }, 15000); +}); diff --git a/registry/components/code/vitest.config.mjs b/registry/components/code/vitest.config.mjs new file mode 100644 index 0000000..6125210 --- /dev/null +++ b/registry/components/code/vitest.config.mjs @@ -0,0 +1,5 @@ +import { defineConfig } from 'vitest/config'; +export default defineConfig({ + root: import.meta.dirname, + test: { environment: 'node', include: ['test/**/*.test.mjs'] }, +});