diff --git a/.changeset/fix-memory-leaks.md b/.changeset/fix-memory-leaks.md new file mode 100644 index 00000000..ab2540fe --- /dev/null +++ b/.changeset/fix-memory-leaks.md @@ -0,0 +1,5 @@ +--- +'@cloudflare/sandbox': patch +--- + +Fix memory leaks from listener accumulation, unbounded process storage, and stale DO state diff --git a/.vscode/settings.json b/.vscode/settings.json index 9182bec3..9a81f9af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,12 @@ { "[javascript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "editor.codeActionsOnSave": { "source.fixAll.biome": "explicit", diff --git a/package-lock.json b/package-lock.json index a356dcab..e3e5d027 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,22 +46,6 @@ "ws": "^8.18.3" } }, - "examples/basic": { - "name": "@cloudflare/sandbox-example", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@cloudflare/sandbox": "*", - "katex": "^0.16.25", - "react-katex": "^3.1.0", - "react-markdown": "^10.1.0", - "remark-gfm": "^4.0.1" - }, - "devDependencies": { - "@types/react-katex": "^3.0.4" - } - }, "examples/claude-code": { "name": "@cloudflare/sandbox-claude-code-example", "version": "1.0.0", @@ -417,7 +401,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -439,7 +422,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -579,7 +561,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1324,8 +1305,8 @@ }, "node_modules/@cloudflare/kv-asset-handler": { "version": "0.4.0", - "resolved": "https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/kv-asset-handler@a6e0700", - "integrity": "sha512-2TeesduPAAnP+mKeBSjZu2/p5EVSMKwjvFuIcG4BSyKhuLiTQ2SI/OQ3PM27i2QW3kmQFRET4J65JdRQQPs3aA==", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", "license": "MIT OR Apache-2.0", "dependencies": { "mime": "^3.0.0" @@ -1362,40 +1343,40 @@ "resolved": "examples/typescript-validator", "link": true }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.11.tgz", + "integrity": "sha512-se23f1D4PxKrMKOq+Stz+Yn7AJ9ITHcEecXo2Yjb+UgbUDCEBch1FXQC6hx6uT5fNA3kmX3mfzeZiUmpK1W9IQ==", + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": "^1.20251106.1" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, "node_modules/@cloudflare/vite-plugin": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@cloudflare/vite-plugin/-/vite-plugin-1.15.0.tgz", - "integrity": "sha512-ACDLNKr0II1bAoGU/1Rva9HIn3y7clmiMzW84m9xh+twrSzDwNyh5nKAoQm3PhlfJ4LU/AUEeaPFi2jitaE8hQ==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@cloudflare/vite-plugin/-/vite-plugin-1.15.2.tgz", + "integrity": "sha512-SPMxsesbABOjzcAa4IzW+yM+fTIjx3GG1doh229Pg16FjSEZJhknyRpcld4gnaZioK3JKwG9FWdKsUhbplKY8w==", "license": "MIT", "dependencies": { - "@cloudflare/unenv-preset": "2.7.10", + "@cloudflare/unenv-preset": "2.7.11", "@remix-run/node-fetch-server": "^0.8.0", "get-port": "^7.1.0", - "miniflare": "4.20251113.0", + "miniflare": "4.20251118.1", "picocolors": "^1.1.1", "tinyglobby": "^0.2.12", "unenv": "2.0.0-rc.24", - "wrangler": "4.49.0", + "wrangler": "4.50.0", "ws": "8.18.0" }, "peerDependencies": { "vite": "^6.1.0 || ^7.0.0", - "wrangler": "^4.49.0" - } - }, - "node_modules/@cloudflare/vite-plugin/node_modules/@cloudflare/unenv-preset": { - "version": "2.7.10", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.10.tgz", - "integrity": "sha512-mvsNAiJSduC/9yxv1ZpCxwgAXgcuoDvkl8yaHjxoLpFxXy2ugc6TZK20EKgv4yO0vZhAEKwqJm+eGOzf8Oc45w==", - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "unenv": "2.0.0-rc.24", - "workerd": "^1.20251106.1" - }, - "peerDependenciesMeta": { - "workerd": { - "optional": true - } + "wrangler": "^4.50.0" } }, "node_modules/@cloudflare/vite-plugin/node_modules/ws": { @@ -1420,18 +1401,18 @@ } }, "node_modules/@cloudflare/vitest-pool-workers": { - "version": "0.10.8", - "resolved": "https://registry.npmjs.org/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.10.8.tgz", - "integrity": "sha512-nhLqxdK3iugtI6/6/ennG3tfNmry2oj17Dpjzt+aP6gcP8EI6Mi7CPCnJC1/odHOZBRRkhrynvABTP/CX6Ux1A==", + "version": "0.10.10", + "resolved": "https://registry.npmjs.org/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.10.10.tgz", + "integrity": "sha512-hsDhDE6tYjwWiODpDvUQ2MgV0NZ/FAp5/8ScUI9soW7pWqtK78bR6X8s8eW1Y9qssMeHySc7z3E0Pc/2Pm4K5w==", "dev": true, "license": "MIT", "dependencies": { "birpc": "0.2.14", "cjs-module-lexer": "^1.2.3", "devalue": "^5.3.2", - "miniflare": "4.20251113.0", + "miniflare": "4.20251118.1", "semver": "^7.7.1", - "wrangler": "4.49.0", + "wrangler": "4.50.0", "zod": "^3.22.3" }, "peerDependencies": { @@ -1440,13 +1421,92 @@ "vitest": "2.0.x - 3.2.x" } }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20251118.0.tgz", + "integrity": "sha512-UmWmYEYS/LkK/4HFKN6xf3Hk8cw70PviR+ftr3hUvs9HYZS92IseZEp16pkL6ZBETrPRpZC7OrzoYF7ky6kHsg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20251118.0.tgz", + "integrity": "sha512-RockU7Qzf4rxNfY1lx3j4rvwutNLjTIX7rr2hogbQ4mzLo8Ea40/oZTzXVxl+on75joLBrt0YpenGW8o/r44QA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20251118.0.tgz", + "integrity": "sha512-aT97GnOAbJDuuOG0zPVhgRk0xFtB1dzBMrxMZ09eubDLoU4djH4BuORaqvxNRMmHgKfa4T6drthckT0NjUvBdw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20251118.0.tgz", + "integrity": "sha512-bXZPJcwlq00MPOXqP7DMWjr+goYj0+Fqyw6zgEC2M3FR1+SWla4yjghnZ4IdpN+H1t7VbUrsi5np2LzMUFs0NA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20251118.0.tgz", + "integrity": "sha512-2LV99AHSlpr8WcCb/BYbU2QsYkXLUL1izN6YKWkN9Eibv80JKX0RtgmD3dfmajE5sNvClavxZejgzVvHD9N9Ag==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, "node_modules/@cloudflare/workers-types": { - "version": "4.20251119.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251119.0.tgz", - "integrity": "sha512-DfCMGhqhvOow3NDViL3T9SgAtogc+xjtAKCfL+aWpVcAcct31P8FSFFRqlZCjcXJ5Kqw/TOjD/87UEAXAqaQZA==", + "version": "4.20251121.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251121.0.tgz", + "integrity": "sha512-jzFg7hEGKzpEalxTCanN6lM8IdkvO/brsERp/+OyMms4Zi0nhDPUAg9dUcKU8wDuDUnzbjkplY6YRwle7Cq6gA==", "devOptional": true, - "license": "MIT OR Apache-2.0", - "peer": true + "license": "MIT OR Apache-2.0" }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", @@ -1525,9 +1585,9 @@ "license": "MIT" }, "node_modules/@emnapi/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz", - "integrity": "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "dev": true, "license": "MIT", "optional": true, @@ -1558,9 +1618,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", - "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -1574,9 +1634,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", - "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -1590,9 +1650,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", - "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -1606,9 +1666,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", - "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -1622,9 +1682,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", - "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -1638,9 +1698,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", - "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -1654,9 +1714,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", - "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -1670,9 +1730,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", - "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -1686,9 +1746,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", - "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -1702,9 +1762,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", - "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -1718,9 +1778,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", - "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -1734,9 +1794,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", - "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -1750,9 +1810,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", - "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -1766,9 +1826,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", - "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -1782,9 +1842,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", - "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -1798,9 +1858,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", - "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -1814,9 +1874,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", - "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -1830,9 +1890,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", - "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -1846,9 +1906,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", - "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -1862,9 +1922,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", - "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -1878,9 +1938,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", - "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -1894,9 +1954,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", - "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "cpu": [ "arm64" ], @@ -1910,9 +1970,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", - "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -1926,9 +1986,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", - "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -1942,9 +2002,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", - "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -1958,9 +2018,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", - "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -2450,13 +2510,13 @@ } }, "node_modules/@inquirer/external-editor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", - "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "dev": true, "license": "MIT", "dependencies": { - "chardet": "^2.1.0", + "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "engines": { @@ -2760,7 +2820,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -2969,14 +3028,14 @@ } }, "node_modules/@openai/agents": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.3.2.tgz", - "integrity": "sha512-iMy5d6T0K6kxsPuPl3icAGifVb/7QpvxgXP5LrnPa7JTwKDCCe1XMuOJts5Zi1/JNbHpxWk4HCZ908s2auQSoA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.3.3.tgz", + "integrity": "sha512-jLqvY/l7w0QbUkyUUHaKl3VPDwBewjWRpiwV8hnYAFfGSwKR2K0WA9RjcC56qmhuQ7pB5NAkDVM82v3urOnMRg==", "license": "MIT", "dependencies": { - "@openai/agents-core": "0.3.2", - "@openai/agents-openai": "0.3.2", - "@openai/agents-realtime": "0.3.2", + "@openai/agents-core": "0.3.3", + "@openai/agents-openai": "0.3.3", + "@openai/agents-realtime": "0.3.3", "debug": "^4.4.0", "openai": "^6" }, @@ -2985,9 +3044,9 @@ } }, "node_modules/@openai/agents-core": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.3.2.tgz", - "integrity": "sha512-lt8KxmaDFXbGkQKxtuJz79arBvbN/BGvZhjLnVPEGC0p0WoUxGbq2HKG5NX3rajBfZZGA27aTGvYQirSXIUpUg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.3.3.tgz", + "integrity": "sha512-MlF70Vcjmi7rMlU00bBjes7DxM3IzcFlmpN0/f8fDKVuLKnqPy+dZRCpX+eNbPrJ4xg14Hmx3LgulNvzxgZx2g==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -3006,12 +3065,12 @@ } }, "node_modules/@openai/agents-openai": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.3.2.tgz", - "integrity": "sha512-Qg0HixgR9pMzQix0x2gVigfx3S2GAWG/akYjSSMDcan4NTd/luGoiJ/ehDNODMhCAudhwM+ciSfwM9AbhJDKmg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.3.3.tgz", + "integrity": "sha512-uxXXVWTEhQrZOKUUp/gTQ85176/6kR2CnEMDjSnjOacMzwybXo5IkreBrZh2BmhseTpOvQIek57UV+z344mTvw==", "license": "MIT", "dependencies": { - "@openai/agents-core": "0.3.2", + "@openai/agents-core": "0.3.3", "debug": "^4.4.0", "openai": "^6" }, @@ -3020,12 +3079,12 @@ } }, "node_modules/@openai/agents-realtime": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.3.2.tgz", - "integrity": "sha512-T8tOWuMDsk+TsoD0VAzeKDazopEIpGhrpeNunEpIsgIN9wXGI96f2im+BIkPWvPDuaztTmkOul8Zbx1vWhPV5g==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.3.3.tgz", + "integrity": "sha512-Rv7qqBIuerjZtMmI4YdXcz7mQ7bymIcmGjza0WbLLzF/D9ax0b/trvFaKnfM/YE/9nKHd5N6IgwYLlH4xV/UGw==", "license": "MIT", "dependencies": { - "@openai/agents-core": "0.3.2", + "@openai/agents-core": "0.3.3", "@types/ws": "^8.18.1", "debug": "^4.4.0", "ws": "^8.18.1" @@ -3051,9 +3110,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.97.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.97.0.tgz", - "integrity": "sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==", + "version": "0.98.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.98.0.tgz", + "integrity": "sha512-Vzmd6FsqVuz5HQVcRC/hrx7Ujo3WEVeQP7C2UNP5uy1hUY4SQvMB+93jxkI1KRHz9a/6cni3glPOtvteN+zpsw==", "dev": true, "license": "MIT", "funding": { @@ -3077,9 +3136,9 @@ } }, "node_modules/@poppinss/dumper": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.4.tgz", - "integrity": "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", "license": "MIT", "dependencies": { "@poppinss/colors": "^4.1.5", @@ -3129,9 +3188,9 @@ "link": true }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.50.tgz", - "integrity": "sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.51.tgz", + "integrity": "sha512-Ctn8FUXKWWQI9pWC61P1yumS9WjQtelNS9riHwV7oCkknPGaAry4o7eFx2KgoLMnI2BgFJYpW7Im8/zX3BuONg==", "cpu": [ "arm64" ], @@ -3146,9 +3205,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.50.tgz", - "integrity": "sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.51.tgz", + "integrity": "sha512-EL1aRW2Oq15ShUEkBPsDtLMO8GTqfb/ktM/dFaVzXKQiEE96Ss6nexMgfgQrg8dGnNpndFyffVDb5IdSibsu1g==", "cpu": [ "arm64" ], @@ -3163,9 +3222,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.50.tgz", - "integrity": "sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.51.tgz", + "integrity": "sha512-uGtYKlFen9pMIPvkHPWZVDtmYhMQi5g5Ddsndg1gf3atScKYKYgs5aDP4DhHeTwGXQglhfBG7lEaOIZ4UAIWww==", "cpu": [ "x64" ], @@ -3180,9 +3239,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.50.tgz", - "integrity": "sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.51.tgz", + "integrity": "sha512-JRoVTQtHYbZj1P07JLiuTuXjiBtIa7ag7/qgKA6CIIXnAcdl4LrOf7nfDuHPJcuRKaP5dzecMgY99itvWfmUFQ==", "cpu": [ "x64" ], @@ -3197,9 +3256,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.50.tgz", - "integrity": "sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.51.tgz", + "integrity": "sha512-BKATVnpPZ0TYBW9XfDwyd4kPGgvf964HiotIwUgpMrFOFYWqpZ+9ONNzMV4UFAYC7Hb5C2qgYQk/qj2OnAd4RQ==", "cpu": [ "arm" ], @@ -3214,9 +3273,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.50.tgz", - "integrity": "sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.51.tgz", + "integrity": "sha512-xLd7da5jkfbVsBCm1buIRdWtuXY8+hU3+6ESXY/Tk5X5DPHaifrUblhYDgmA34dQt6WyNC2kfXGgrduPEvDI6Q==", "cpu": [ "arm64" ], @@ -3231,9 +3290,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.50.tgz", - "integrity": "sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.51.tgz", + "integrity": "sha512-EQFXTgHxxTzv3t5EmjUP/DfxzFYx9sMndfLsYaAY4DWF6KsK1fXGYsiupif6qPTViPC9eVmRm78q0pZU/kuIPg==", "cpu": [ "arm64" ], @@ -3248,9 +3307,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.50.tgz", - "integrity": "sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.51.tgz", + "integrity": "sha512-p5P6Xpa68w3yFaAdSzIZJbj+AfuDnMDqNSeglBXM7UlJT14Q4zwK+rV+8Mhp9MiUb4XFISZtbI/seBprhkQbiQ==", "cpu": [ "x64" ], @@ -3265,9 +3324,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.50.tgz", - "integrity": "sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.51.tgz", + "integrity": "sha512-sNVVyLa8HB8wkFipdfz1s6i0YWinwpbMWk5hO5S+XAYH2UH67YzUT13gs6wZTKg2x/3gtgXzYnHyF5wMIqoDAw==", "cpu": [ "x64" ], @@ -3282,9 +3341,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.50.tgz", - "integrity": "sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.51.tgz", + "integrity": "sha512-e/JMTz9Q8+T3g/deEi8DK44sFWZWGKr9AOCW5e8C8SCVWzAXqYXAG7FXBWBNzWEZK0Rcwo9TQHTQ9Q0gXgdCaA==", "cpu": [ "arm64" ], @@ -3299,9 +3358,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.50.tgz", - "integrity": "sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.51.tgz", + "integrity": "sha512-We3LWqSu6J9s5Y0MK+N7fUiiu37aBGPG3Pc347EoaROuAwkCS2u9xJ5dpIyLW4B49CIbS3KaPmn4kTgPb3EyPw==", "cpu": [ "wasm32" ], @@ -3316,9 +3375,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.50.tgz", - "integrity": "sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.51.tgz", + "integrity": "sha512-fj56buHRuMM+r/cb6ZYfNjNvO/0xeFybI6cTkTROJatdP4fvmQ1NS8D/Lm10FCSDEOkqIz8hK3TGpbAThbPHsA==", "cpu": [ "arm64" ], @@ -3333,9 +3392,9 @@ } }, "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.50.tgz", - "integrity": "sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.51.tgz", + "integrity": "sha512-fkqEqaeEx8AySXiDm54b/RdINb3C0VovzJA3osMhZsbn6FoD73H0AOIiaVAtGr6x63hefruVKTX8irAm4Jkt2w==", "cpu": [ "ia32" ], @@ -3350,9 +3409,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.50.tgz", - "integrity": "sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.51.tgz", + "integrity": "sha512-CWuLG/HMtrVcjKGa0C4GnuxONrku89g0+CsH8nT0SNhOtREXuzwgjIXNJImpE/A/DMf9JF+1Xkrq/YRr+F/rCg==", "cpu": [ "x64" ], @@ -3414,9 +3473,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", "cpu": [ "arm" ], @@ -3427,9 +3486,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", "cpu": [ "arm64" ], @@ -3440,9 +3499,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "cpu": [ "arm64" ], @@ -3453,9 +3512,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", "cpu": [ "x64" ], @@ -3466,9 +3525,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", "cpu": [ "arm64" ], @@ -3479,9 +3538,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", "cpu": [ "x64" ], @@ -3492,9 +3551,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", "cpu": [ "arm" ], @@ -3505,9 +3564,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", "cpu": [ "arm" ], @@ -3518,9 +3577,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", "cpu": [ "arm64" ], @@ -3531,9 +3590,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", "cpu": [ "arm64" ], @@ -3544,9 +3603,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", "cpu": [ "loong64" ], @@ -3557,9 +3616,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", "cpu": [ "ppc64" ], @@ -3570,9 +3629,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", "cpu": [ "riscv64" ], @@ -3583,9 +3642,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", "cpu": [ "riscv64" ], @@ -3596,9 +3655,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", "cpu": [ "s390x" ], @@ -3609,9 +3668,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", "cpu": [ "x64" ], @@ -3622,9 +3681,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", "cpu": [ "x64" ], @@ -3635,9 +3694,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", "cpu": [ "arm64" ], @@ -3648,9 +3707,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", "cpu": [ "arm64" ], @@ -3661,9 +3720,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", "cpu": [ "ia32" ], @@ -3674,9 +3733,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", "cpu": [ "x64" ], @@ -3687,9 +3746,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", "cpu": [ "x64" ], @@ -3767,9 +3826,9 @@ "license": "MIT" }, "node_modules/@sindresorhus/is": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz", - "integrity": "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.1.tgz", + "integrity": "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==", "license": "MIT", "engines": { "node": ">=18" @@ -3779,9 +3838,9 @@ } }, "node_modules/@speed-highlight/core": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.8.tgz", - "integrity": "sha512-IGytNtnUnPIobIbOq5Y6LIlqiHNX+vnToQIS7lj6L5819C+rA8TXRDkkG8vePsiBOGcoW9R6i+dp2YBUKdB09Q==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.12.tgz", + "integrity": "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==", "license": "CC0-1.0" }, "node_modules/@swc/helpers": { @@ -4230,7 +4289,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4246,7 +4304,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4256,7 +4313,6 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4383,7 +4439,6 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -4399,7 +4454,6 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -4428,7 +4482,6 @@ "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "fflate": "^0.8.2", @@ -4590,7 +4643,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4784,9 +4836,9 @@ } }, "node_modules/astro": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.9.tgz", - "integrity": "sha512-XLDXxu0282cC/oYHswWZm3johGlRvk9rLRS7pWVWSne+HsZe9JgrpHI+vewAJSSNHBGd1aCyaQOElT5RNGe7IQ==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.16.0.tgz", + "integrity": "sha512-GaDRs2Mngpw3dr2vc085GnORh98NiXxwIjg/EoQQQl/icZt3Z7s0BRsYHDZ8swkZbOA6wZsqWJdrNirl+iKcDg==", "license": "MIT", "dependencies": { "@astrojs/compiler": "^2.13.0", @@ -4828,13 +4880,14 @@ "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.5.0", - "picocolors": "^1.1.1", + "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.15.0", "smol-toml": "^1.5.0", + "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", @@ -5262,6 +5315,15 @@ "node": ">=8" } }, + "node_modules/astro/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/astro/node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -5326,7 +5388,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5499,9 +5560,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.21", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz", - "integrity": "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==", + "version": "2.8.30", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", + "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -5577,6 +5638,12 @@ "node": ">=0.10.0" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/boxen": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", @@ -5621,9 +5688,9 @@ } }, "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", "funding": [ { "type": "opencollective", @@ -5639,12 +5706,11 @@ } ], "license": "MIT", - "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { @@ -5738,9 +5804,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001751", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", - "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "version": "1.0.30001756", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", + "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", "funding": [ { "type": "opencollective", @@ -5837,9 +5903,9 @@ } }, "node_modules/chardet": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", - "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "dev": true, "license": "MIT" }, @@ -6061,16 +6127,17 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", "optional": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -6090,12 +6157,13 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", + "optional": true, "engines": { - "node": ">=18" + "node": ">= 0.6" } }, "node_modules/cookie-es": { @@ -6152,6 +6220,22 @@ "uncrypto": "^0.1.3" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/css-tree": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", @@ -6165,6 +6249,18 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -6177,6 +6273,39 @@ "node": ">=4" } }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -6363,17 +6492,84 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, - "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "dev": true, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/dset": { + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dset": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", @@ -6426,9 +6622,9 @@ "optional": true }, "node_modules/electron-to-chromium": { - "version": "1.5.243", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.243.tgz", - "integrity": "sha512-ZCphxFW3Q1TVhcgS9blfut1PX8lusVi2SvXQgmEEnK4TCmE1JhH2JkjJN+DNt0pJJwfBri5AROBnz2b/C+YU9g==", + "version": "1.5.259", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", + "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", "license": "ISC" }, "node_modules/emmet": { @@ -6562,9 +6758,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", - "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -6574,32 +6770,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.11", - "@esbuild/android-arm": "0.25.11", - "@esbuild/android-arm64": "0.25.11", - "@esbuild/android-x64": "0.25.11", - "@esbuild/darwin-arm64": "0.25.11", - "@esbuild/darwin-x64": "0.25.11", - "@esbuild/freebsd-arm64": "0.25.11", - "@esbuild/freebsd-x64": "0.25.11", - "@esbuild/linux-arm": "0.25.11", - "@esbuild/linux-arm64": "0.25.11", - "@esbuild/linux-ia32": "0.25.11", - "@esbuild/linux-loong64": "0.25.11", - "@esbuild/linux-mips64el": "0.25.11", - "@esbuild/linux-ppc64": "0.25.11", - "@esbuild/linux-riscv64": "0.25.11", - "@esbuild/linux-s390x": "0.25.11", - "@esbuild/linux-x64": "0.25.11", - "@esbuild/netbsd-arm64": "0.25.11", - "@esbuild/netbsd-x64": "0.25.11", - "@esbuild/openbsd-arm64": "0.25.11", - "@esbuild/openbsd-x64": "0.25.11", - "@esbuild/openharmony-arm64": "0.25.11", - "@esbuild/sunos-x64": "0.25.11", - "@esbuild/win32-arm64": "0.25.11", - "@esbuild/win32-ia32": "0.25.11", - "@esbuild/win32-x64": "0.25.11" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -6783,16 +6979,6 @@ "express": ">= 4.11" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7211,9 +7397,9 @@ } }, "node_modules/happy-dom/node_modules/@types/node": { - "version": "20.19.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", - "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "version": "20.19.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", + "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7507,30 +7693,24 @@ "license": "BSD-2-Clause" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "optional": true, "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/human-id": { @@ -7588,9 +7768,9 @@ "optional": true }, "node_modules/inline-style-parser": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", - "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, "node_modules/ipaddr.js": { @@ -7792,9 +7972,9 @@ } }, "node_modules/isbinaryfile": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.6.tgz", - "integrity": "sha512-I+NmIfBHUl+r2wcDd6JwE9yWje/PIVY/R5/CmV8dXLZd5K+L9X2klAOwfAHNnondLXkbHyTAleQAWonpTJBTtw==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", "dev": true, "license": "MIT", "engines": { @@ -7817,7 +7997,6 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -8082,7 +8261,6 @@ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", "devOptional": true, "license": "MPL-2.0", - "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -8119,6 +8297,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8139,6 +8318,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8159,6 +8339,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8179,6 +8360,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8199,6 +8381,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8219,6 +8402,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8239,6 +8423,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8259,6 +8444,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8279,6 +8465,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8299,6 +8486,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8319,6 +8507,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8368,6 +8557,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -9353,22 +9543,26 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "optional": true, "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/miniflare": { - "version": "4.20251113.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20251113.0.tgz", - "integrity": "sha512-uaNiBJu8JQY27pcM7Q/E+hLggClmJCQji9uOOBdlZV+aJ7rk7tFnyIvjpLxYJRpryrMjm3aKNeMvE8ham01DtA==", + "version": "4.20251118.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20251118.1.tgz", + "integrity": "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ==", "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -9379,7 +9573,7 @@ "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", - "workerd": "1.20251113.0", + "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" @@ -9391,106 +9585,6 @@ "node": ">=18.0.0" } }, - "node_modules/miniflare/node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20251113.0.tgz", - "integrity": "sha512-GBlun9onB69rDMZIJ9TdvQ9d3KS5L2FXwlzJ9+AjrbHqYnIif8eH5wyLPIR5wfkXNyrEFiWtEYses+aUQZQLWQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/miniflare/node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20251113.0.tgz", - "integrity": "sha512-CFakh0Plmkwe8BHuc3iQ0rSIMttvJ3XpWYPjhWUL6KxA2zL6FvYUiWG0hpaEW60KPqgFMisBoPzH0kzyzSKMhg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/miniflare/node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20251113.0.tgz", - "integrity": "sha512-6JqK+Csie47656Kr+48GHl8v1zK940ruOczY4Da67xvsUAsFkUEOQkes4rW5i3I3XjNbcnyf96l0gnI6lzTKlQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/miniflare/node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20251113.0.tgz", - "integrity": "sha512-Ye9I2AeXZ2IPcWOYD9xIrjcFrnH0UkS/ijvgI81mJCr8w7dMd4ONb+BK5oXUJtBW8IEAV3yRU7Mz7DBLaXe/AQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/miniflare/node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20251113.0.tgz", - "integrity": "sha512-H2sq6H2sMhjt/72S3ZAsZV+GC3chRJkwwGq7AGVjIehR66e/z5KKhxOX8u29iqtShyERvFUVHyfCzRBKfmJwfw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/miniflare/node_modules/workerd": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20251113.0.tgz", - "integrity": "sha512-XLQKbzzn6DndOosyCviYc0ExQVKPem5KAqvsgY+yrsJXvQnKXojIG8rtI0JQgKflHD6sHwjjg17P+q4lPvaLGQ==", - "hasInstallScript": true, - "license": "Apache-2.0", - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20251113.0", - "@cloudflare/workerd-darwin-arm64": "1.20251113.0", - "@cloudflare/workerd-linux-64": "1.20251113.0", - "@cloudflare/workerd-linux-arm64": "1.20251113.0", - "@cloudflare/workerd-windows-64": "1.20251113.0" - } - }, "node_modules/miniflare/node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -9676,6 +9770,18 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9699,23 +9805,15 @@ } }, "node_modules/obug": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/obug/-/obug-1.0.0.tgz", - "integrity": "sha512-WKcS43Yl6YPJekid7KiRdT6CHMSmYWVfJiSFbTaGxWQlC+cEBPxHa9jR1uS2cMiQmXd8Hsa2ipAKErQ/GLhSpg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.0.tgz", + "integrity": "sha512-uu/tgLPoa75CFA7UDkmqspKbefvZh1WMPwkU3bNr0PY746a/+xwXVgbw5co5C3GvJj3h5u8g/pbxXzI0gd1QFg==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" ], - "license": "MIT", - "peerDependencies": { - "ms": "^2.0.0" - }, - "peerDependenciesMeta": { - "ms": { - "optional": true - } - } + "license": "MIT" }, "node_modules/ofetch": { "version": "1.5.1", @@ -9764,9 +9862,9 @@ "license": "MIT" }, "node_modules/oniguruma-to-es": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", - "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", "license": "MIT", "dependencies": { "oniguruma-parser": "^0.12.1", @@ -10000,10 +10098,15 @@ } }, "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "license": "MIT" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -10031,6 +10134,12 @@ "node": ">= 14.16" } }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10134,7 +10243,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10359,12 +10467,38 @@ "node": ">= 0.10" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10374,7 +10508,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10386,7 +10519,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-katex": { "version": "3.1.0", @@ -10755,14 +10889,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.50.tgz", - "integrity": "sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.51.tgz", + "integrity": "sha512-ZRLgPlS91l4JztLYEZnmMcd3Umcla1hkXJgiEiR4HloRJBBoeaX8qogTu5Jfu36rRMVLndzqYv0h+M5gJAkUfg==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.97.0", - "@rolldown/pluginutils": "1.0.0-beta.50" + "@oxc-project/types": "=0.98.0", + "@rolldown/pluginutils": "1.0.0-beta.51" }, "bin": { "rolldown": "bin/cli.mjs" @@ -10771,26 +10905,26 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-beta.50", - "@rolldown/binding-darwin-arm64": "1.0.0-beta.50", - "@rolldown/binding-darwin-x64": "1.0.0-beta.50", - "@rolldown/binding-freebsd-x64": "1.0.0-beta.50", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.50", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.50", - "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.50", - "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.50", - "@rolldown/binding-linux-x64-musl": "1.0.0-beta.50", - "@rolldown/binding-openharmony-arm64": "1.0.0-beta.50", - "@rolldown/binding-wasm32-wasi": "1.0.0-beta.50", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.50", - "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.50", - "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.50" + "@rolldown/binding-android-arm64": "1.0.0-beta.51", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.51", + "@rolldown/binding-darwin-x64": "1.0.0-beta.51", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.51", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.51", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.51", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.51", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.51", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.51", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.51", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.51", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.51", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.51", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.51" } }, "node_modules/rolldown-plugin-dts": { - "version": "0.17.7", - "resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.17.7.tgz", - "integrity": "sha512-ZGgXMhzCItmznNzbJlTcC/KdM6bIwcZoYUykJ2q14HOGvnMhnl2RXU+XrIrdjA2Hyzq3nWqDH7qWaM5a4uCVnw==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.18.0.tgz", + "integrity": "sha512-2CJtKYa9WPClZxkJeCt4bGUegQvQKQ1VJp9jFJzG0h8I/80XI6qDgoWfVJUOEhT2swbsRQh/42N1RIWvbXT4rA==", "dev": true, "license": "MIT", "dependencies": { @@ -10802,10 +10936,10 @@ "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.0", "magic-string": "^0.30.21", - "obug": "^1.0.0" + "obug": "^2.0.0" }, "engines": { - "node": ">=20.18.0" + "node": ">=20.19.0" }, "funding": { "url": "https://github.com/sponsors/sxzz" @@ -10813,7 +10947,7 @@ "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", - "rolldown": "^1.0.0-beta.44", + "rolldown": "^1.0.0-beta.51", "typescript": "^5.0.0", "vue-tsc": "~3.1.0" }, @@ -10843,16 +10977,16 @@ } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz", - "integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.51.tgz", + "integrity": "sha512-51/8cNXMrqWqX3o8DZidhwz1uYq0BhHDDSfVygAND1Skx5s1TDw3APSSxCMcFFedwgqGcx34gRouwY+m404BBQ==", "dev": true, "license": "MIT" }, "node_modules/rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -10865,28 +10999,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, @@ -10907,17 +11041,6 @@ "node": ">= 18" } }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10941,27 +11064,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "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", - "optional": true - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10969,6 +11071,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.0.0" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -11586,21 +11694,21 @@ "license": "MIT" }, "node_modules/style-to-js": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.18.tgz", - "integrity": "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", "license": "MIT", "dependencies": { - "style-to-object": "1.0.11" + "style-to-object": "1.0.14" } }, "node_modules/style-to-object": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.11.tgz", - "integrity": "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", "license": "MIT", "dependencies": { - "inline-style-parser": "0.2.4" + "inline-style-parser": "0.2.7" } }, "node_modules/supports-color": { @@ -11615,6 +11723,40 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/tailwindcss": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", @@ -11709,7 +11851,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11837,9 +11978,9 @@ } }, "node_modules/tsdown": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.16.5.tgz", - "integrity": "sha512-jo/2MmJI1uNJ+QvwEfF/2DcICd2Bc/Gc/XIVJS9Gvfns7ji5TgUeu3kYfG8nA/mGgWXU8REpTNweIcVJQoSLAQ==", + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.16.6.tgz", + "integrity": "sha512-g3xHEnGdfwJTlXhEkqww3Q/KlCfyNFw4rnzuQ9Gqw8T2xjDYrw94qmSw5wYYTAW5zV1sEfWDlfgxZo5mmtu0NQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11849,15 +11990,15 @@ "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", - "obug": "^2.0.0", - "rolldown": "1.0.0-beta.50", - "rolldown-plugin-dts": "^0.17.7", + "obug": "^2.1.0", + "rolldown": "1.0.0-beta.51", + "rolldown-plugin-dts": "^0.18.0", "semver": "^7.7.3", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig-core": "^7.4.1", - "unrun": "^0.2.10" + "unrun": "^0.2.11" }, "bin": { "tsdown": "dist/run.mjs" @@ -11897,25 +12038,6 @@ } } }, - "node_modules/tsdown/node_modules/obug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.0.0.tgz", - "integrity": "sha512-dpSQuPXoKUjulinHmXjZV1YIRhOLEqBl1J6PYi9mRQR2dYcSK+OULRr+GuT1vufk2f40mtIOqmSL/aTikjmq5Q==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT", - "peerDependencies": { - "ms": "^2.0.0" - }, - "peerDependenciesMeta": { - "ms": { - "optional": true - } - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -11928,7 +12050,6 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -12103,7 +12224,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12173,7 +12293,6 @@ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "license": "MIT", - "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -12406,298 +12525,10 @@ } } }, - "node_modules/unrun/node_modules/@oxc-project/types": { - "version": "0.98.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.98.0.tgz", - "integrity": "sha512-Vzmd6FsqVuz5HQVcRC/hrx7Ujo3WEVeQP7C2UNP5uy1hUY4SQvMB+93jxkI1KRHz9a/6cni3glPOtvteN+zpsw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.51.tgz", - "integrity": "sha512-Ctn8FUXKWWQI9pWC61P1yumS9WjQtelNS9riHwV7oCkknPGaAry4o7eFx2KgoLMnI2BgFJYpW7Im8/zX3BuONg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.51.tgz", - "integrity": "sha512-EL1aRW2Oq15ShUEkBPsDtLMO8GTqfb/ktM/dFaVzXKQiEE96Ss6nexMgfgQrg8dGnNpndFyffVDb5IdSibsu1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.51.tgz", - "integrity": "sha512-uGtYKlFen9pMIPvkHPWZVDtmYhMQi5g5Ddsndg1gf3atScKYKYgs5aDP4DhHeTwGXQglhfBG7lEaOIZ4UAIWww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.51.tgz", - "integrity": "sha512-JRoVTQtHYbZj1P07JLiuTuXjiBtIa7ag7/qgKA6CIIXnAcdl4LrOf7nfDuHPJcuRKaP5dzecMgY99itvWfmUFQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.51.tgz", - "integrity": "sha512-BKATVnpPZ0TYBW9XfDwyd4kPGgvf964HiotIwUgpMrFOFYWqpZ+9ONNzMV4UFAYC7Hb5C2qgYQk/qj2OnAd4RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.51.tgz", - "integrity": "sha512-xLd7da5jkfbVsBCm1buIRdWtuXY8+hU3+6ESXY/Tk5X5DPHaifrUblhYDgmA34dQt6WyNC2kfXGgrduPEvDI6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.51.tgz", - "integrity": "sha512-EQFXTgHxxTzv3t5EmjUP/DfxzFYx9sMndfLsYaAY4DWF6KsK1fXGYsiupif6qPTViPC9eVmRm78q0pZU/kuIPg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.51.tgz", - "integrity": "sha512-p5P6Xpa68w3yFaAdSzIZJbj+AfuDnMDqNSeglBXM7UlJT14Q4zwK+rV+8Mhp9MiUb4XFISZtbI/seBprhkQbiQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.51.tgz", - "integrity": "sha512-sNVVyLa8HB8wkFipdfz1s6i0YWinwpbMWk5hO5S+XAYH2UH67YzUT13gs6wZTKg2x/3gtgXzYnHyF5wMIqoDAw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.51.tgz", - "integrity": "sha512-e/JMTz9Q8+T3g/deEi8DK44sFWZWGKr9AOCW5e8C8SCVWzAXqYXAG7FXBWBNzWEZK0Rcwo9TQHTQ9Q0gXgdCaA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.51.tgz", - "integrity": "sha512-We3LWqSu6J9s5Y0MK+N7fUiiu37aBGPG3Pc347EoaROuAwkCS2u9xJ5dpIyLW4B49CIbS3KaPmn4kTgPb3EyPw==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.51.tgz", - "integrity": "sha512-fj56buHRuMM+r/cb6ZYfNjNvO/0xeFybI6cTkTROJatdP4fvmQ1NS8D/Lm10FCSDEOkqIz8hK3TGpbAThbPHsA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.51.tgz", - "integrity": "sha512-fkqEqaeEx8AySXiDm54b/RdINb3C0VovzJA3osMhZsbn6FoD73H0AOIiaVAtGr6x63hefruVKTX8irAm4Jkt2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.51.tgz", - "integrity": "sha512-CWuLG/HMtrVcjKGa0C4GnuxONrku89g0+CsH8nT0SNhOtREXuzwgjIXNJImpE/A/DMf9JF+1Xkrq/YRr+F/rCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unrun/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.51.tgz", - "integrity": "sha512-51/8cNXMrqWqX3o8DZidhwz1uYq0BhHDDSfVygAND1Skx5s1TDw3APSSxCMcFFedwgqGcx34gRouwY+m404BBQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unrun/node_modules/rolldown": { - "version": "1.0.0-beta.51", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.51.tgz", - "integrity": "sha512-ZRLgPlS91l4JztLYEZnmMcd3Umcla1hkXJgiEiR4HloRJBBoeaX8qogTu5Jfu36rRMVLndzqYv0h+M5gJAkUfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.98.0", - "@rolldown/pluginutils": "1.0.0-beta.51" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-beta.51", - "@rolldown/binding-darwin-arm64": "1.0.0-beta.51", - "@rolldown/binding-darwin-x64": "1.0.0-beta.51", - "@rolldown/binding-freebsd-x64": "1.0.0-beta.51", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.51", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.51", - "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.51", - "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.51", - "@rolldown/binding-linux-x64-musl": "1.0.0-beta.51", - "@rolldown/binding-openharmony-arm64": "1.0.0-beta.51", - "@rolldown/binding-wasm32-wasi": "1.0.0-beta.51", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.51", - "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.51", - "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.51" - } - }, "node_modules/unstorage": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.2.tgz", - "integrity": "sha512-cKEsD6iBWJgOMJ6vW1ID/SYuqNf8oN4yqRk8OYqaVQ3nnkJXOT1PSpaMh2QfzLs78UN5kSNRD2c/mgjT8tX7+w==", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", "license": "MIT", "dependencies": { "anymatch": "^3.1.3", @@ -12706,7 +12537,7 @@ "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", - "ofetch": "^1.5.0", + "ofetch": "^1.5.1", "ufo": "^1.6.1" }, "peerDependencies": { @@ -12899,11 +12730,10 @@ } }, "node_modules/vite": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", - "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", + "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -13018,7 +12848,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13051,7 +12880,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -13471,20 +13299,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/workerd": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20251118.0.tgz", + "integrity": "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20251118.0", + "@cloudflare/workerd-darwin-arm64": "1.20251118.0", + "@cloudflare/workerd-linux-64": "1.20251118.0", + "@cloudflare/workerd-linux-arm64": "1.20251118.0", + "@cloudflare/workerd-windows-64": "1.20251118.0" + } + }, "node_modules/wrangler": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.49.0.tgz", - "integrity": "sha512-AptJADXtZwDiYS5b0G3kYNYnW4fsoInMBxw++eCs5wot1h0Q6B7HXNaoizD11rLGkU03Tpsh49hYqYyT5qkIvg==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.50.0.tgz", + "integrity": "sha512-+nuZuHZxDdKmAyXOSrHlciGshCoAPiy5dM+t6mEohWm7HpXvTHmWQGUf/na9jjWlWJHCJYOWzkA1P5HBJqrIEA==", "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.7.10", + "@cloudflare/unenv-preset": "2.7.11", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", - "miniflare": "4.20251113.0", + "miniflare": "4.20251118.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", - "workerd": "1.20251113.0" + "workerd": "1.20251118.0" }, "bin": { "wrangler": "bin/wrangler.js", @@ -13497,7 +13345,7 @@ "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20251113.0" + "@cloudflare/workers-types": "^4.20251118.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -13505,101 +13353,6 @@ } } }, - "node_modules/wrangler/node_modules/@cloudflare/unenv-preset": { - "version": "2.7.10", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.10.tgz", - "integrity": "sha512-mvsNAiJSduC/9yxv1ZpCxwgAXgcuoDvkl8yaHjxoLpFxXy2ugc6TZK20EKgv4yO0vZhAEKwqJm+eGOzf8Oc45w==", - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "unenv": "2.0.0-rc.24", - "workerd": "^1.20251106.1" - }, - "peerDependenciesMeta": { - "workerd": { - "optional": true - } - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20251113.0.tgz", - "integrity": "sha512-GBlun9onB69rDMZIJ9TdvQ9d3KS5L2FXwlzJ9+AjrbHqYnIif8eH5wyLPIR5wfkXNyrEFiWtEYses+aUQZQLWQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20251113.0.tgz", - "integrity": "sha512-CFakh0Plmkwe8BHuc3iQ0rSIMttvJ3XpWYPjhWUL6KxA2zL6FvYUiWG0hpaEW60KPqgFMisBoPzH0kzyzSKMhg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20251113.0.tgz", - "integrity": "sha512-6JqK+Csie47656Kr+48GHl8v1zK940ruOczY4Da67xvsUAsFkUEOQkes4rW5i3I3XjNbcnyf96l0gnI6lzTKlQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20251113.0.tgz", - "integrity": "sha512-Ye9I2AeXZ2IPcWOYD9xIrjcFrnH0UkS/ijvgI81mJCr8w7dMd4ONb+BK5oXUJtBW8IEAV3yRU7Mz7DBLaXe/AQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20251113.0.tgz", - "integrity": "sha512-H2sq6H2sMhjt/72S3ZAsZV+GC3chRJkwwGq7AGVjIehR66e/z5KKhxOX8u29iqtShyERvFUVHyfCzRBKfmJwfw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, "node_modules/wrangler/node_modules/@esbuild/aix-ppc64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", @@ -14040,26 +13793,11 @@ "@esbuild/win32-x64": "0.25.4" } }, - "node_modules/wrangler/node_modules/workerd": { - "version": "1.20251113.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20251113.0.tgz", - "integrity": "sha512-XLQKbzzn6DndOosyCviYc0ExQVKPem5KAqvsgY+yrsJXvQnKXojIG8rtI0JQgKflHD6sHwjjg17P+q4lPvaLGQ==", - "hasInstallScript": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20251113.0", - "@cloudflare/workerd-darwin-arm64": "1.20251113.0", - "@cloudflare/workerd-linux-64": "1.20251113.0", - "@cloudflare/workerd-linux-arm64": "1.20251113.0", - "@cloudflare/workerd-windows-64": "1.20251113.0" - } + "node_modules/wrangler/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "9.0.2", @@ -14316,12 +14054,20 @@ "error-stack-parser-es": "^1.0.5" } }, + "node_modules/youch/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -14340,12 +14086,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25 || ^4" } }, "node_modules/zod-to-ts": { diff --git a/package.json b/package.json index 2425a266..0e552d17 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,10 @@ "version": "0.0.0", "description": "an api for computers", "scripts": { - "typecheck": "turbo run typecheck", + "typecheck": "turbo run typecheck && npm run typecheck:e2e", + "typecheck:e2e": "tsc --noEmit --project tests/e2e/tsconfig.json", "format": "prettier --write .", - "check": "sherif && biome check && turbo run typecheck", + "check": "sherif && biome check && turbo run typecheck && npm run typecheck:e2e", "fix": "biome check --fix && turbo run typecheck", "build": "turbo run build", "build:clean": "turbo run build --force", diff --git a/packages/sandbox-container/package.json b/packages/sandbox-container/package.json index 07f38099..25aaa5d1 100644 --- a/packages/sandbox-container/package.json +++ b/packages/sandbox-container/package.json @@ -5,7 +5,7 @@ "description": "Container runtime for Cloudflare Sandbox SDK", "type": "module", "scripts": { - "build": "tsc --project tsconfig.json", + "build": "tsc --project tsconfig.build.json", "typecheck": "tsc --noEmit", "test": "bun test tests", "test:watch": "bun test --watch tests", diff --git a/packages/sandbox-container/src/core/container.ts b/packages/sandbox-container/src/core/container.ts index a5e71db2..751a9639 100644 --- a/packages/sandbox-container/src/core/container.ts +++ b/packages/sandbox-container/src/core/container.ts @@ -16,10 +16,8 @@ import { FileService } from '../services/file-service'; import { GitService } from '../services/git-service'; import { InterpreterService } from '../services/interpreter-service'; import { InMemoryPortStore, PortService } from '../services/port-service'; -import { - InMemoryProcessStore, - ProcessService -} from '../services/process-service'; +import { ProcessService } from '../services/process-service'; +import { ProcessStore } from '../services/process-store'; import { SessionManager } from '../services/session-manager'; import { RequestValidator } from '../validation/request-validator'; @@ -90,7 +88,7 @@ export class Container { const validator = new RequestValidator(); // Initialize stores - const processStore = new InMemoryProcessStore(); + const processStore = new ProcessStore(logger); const portStore = new InMemoryPortStore(); // Initialize SessionManager diff --git a/packages/sandbox-container/src/core/types.ts b/packages/sandbox-container/src/core/types.ts index 84e6b00d..0a63891d 100644 --- a/packages/sandbox-container/src/core/types.ts +++ b/packages/sandbox-container/src/core/types.ts @@ -183,6 +183,7 @@ export interface PortNotFoundResponse { export interface ProxyErrorResponse { error: string; message: string; + port: number; } export interface Middleware { diff --git a/packages/sandbox-container/src/handlers/interpreter-handler.ts b/packages/sandbox-container/src/handlers/interpreter-handler.ts index 5eee9c39..bc4ff395 100644 --- a/packages/sandbox-container/src/handlers/interpreter-handler.ts +++ b/packages/sandbox-container/src/handlers/interpreter-handler.ts @@ -99,21 +99,15 @@ export class InterpreterHandler extends BaseHandler { } else { // Special handling for interpreter not ready - return 503 with Retry-After header if (result.error.code === 'INTERPRETER_NOT_READY') { - return new Response( - JSON.stringify({ - success: false, - error: result.error, - timestamp: new Date().toISOString() - }), - { - status: 503, - headers: { - 'Content-Type': 'application/json', - 'Retry-After': String(result.error.details?.retryAfter || 5), - ...context.corsHeaders - } + const errorResponse = this.enrichServiceError(result.error); + return new Response(JSON.stringify(errorResponse), { + status: 503, + headers: { + 'Content-Type': 'application/json', + 'Retry-After': String(result.error.details?.retryAfter || 5), + ...context.corsHeaders } - ); + }); } return this.createErrorResponse(result.error, context); diff --git a/packages/sandbox-container/src/handlers/misc-handler.ts b/packages/sandbox-container/src/handlers/misc-handler.ts index 36aaec91..92c46992 100644 --- a/packages/sandbox-container/src/handlers/misc-handler.ts +++ b/packages/sandbox-container/src/handlers/misc-handler.ts @@ -5,6 +5,12 @@ import { ErrorCode } from '@repo/shared/errors'; import type { RequestContext } from '../core/types'; import { BaseHandler } from './base-handler'; +export interface VersionResult { + success: boolean; + version: string; + timestamp: string; +} + export class MiscHandler extends BaseHandler { async handle(request: Request, context: RequestContext): Promise { const url = new URL(request.url); @@ -74,7 +80,7 @@ export class MiscHandler extends BaseHandler { ): Promise { const version = process.env.SANDBOX_VERSION || 'unknown'; - const response = { + const response: VersionResult = { success: true, version, timestamp: new Date().toISOString() diff --git a/packages/sandbox-container/src/services/port-service.ts b/packages/sandbox-container/src/services/port-service.ts index c8dfa356..3063a38b 100644 --- a/packages/sandbox-container/src/services/port-service.ts +++ b/packages/sandbox-container/src/services/port-service.ts @@ -8,7 +8,11 @@ import type { PortNotExposedContext } from '@repo/shared/errors'; import { ErrorCode } from '@repo/shared/errors'; -import type { PortInfo, ServiceResult } from '../core/types'; +import type { + PortInfo, + ProxyErrorResponse, + ServiceResult +} from '../core/types'; import { PortManager } from '../managers/port-manager'; export interface SecurityService { @@ -281,17 +285,15 @@ export class PortService { // Check if port is exposed const portInfo = await this.store.get(port); if (!portInfo) { - return new Response( - JSON.stringify({ - error: 'Port not found', - message: `Port ${port} is not exposed`, - port - }), - { - status: 404, - headers: { 'Content-Type': 'application/json' } - } - ); + const errorResponse: ProxyErrorResponse = { + error: 'Port not found', + message: `Port ${port} is not exposed`, + port + }; + return new Response(JSON.stringify(errorResponse), { + status: 404, + headers: { 'Content-Type': 'application/json' } + }); } // Parse proxy path using manager @@ -323,17 +325,15 @@ export class PortService { { port } ); - return new Response( - JSON.stringify({ - error: 'Proxy error', - message: `Failed to proxy request to port ${port}: ${errorMessage}`, - port - }), - { - status: 502, - headers: { 'Content-Type': 'application/json' } - } - ); + const errorResponse: ProxyErrorResponse = { + error: 'Proxy error', + message: `Failed to proxy request to port ${port}: ${errorMessage}`, + port + }; + return new Response(JSON.stringify(errorResponse), { + status: 502, + headers: { 'Content-Type': 'application/json' } + }); } } diff --git a/packages/sandbox-container/src/services/process-service.ts b/packages/sandbox-container/src/services/process-service.ts index 8f41f955..ab110aa2 100644 --- a/packages/sandbox-container/src/services/process-service.ts +++ b/packages/sandbox-container/src/services/process-service.ts @@ -13,60 +13,17 @@ import type { ServiceResult } from '../core/types'; import { ProcessManager } from '../managers/process-manager'; +import type { ProcessStore } from './process-store'; import type { SessionManager } from './session-manager'; -export interface ProcessStore { - create(process: ProcessRecord): Promise; - get(id: string): Promise; - update(id: string, data: Partial): Promise; - delete(id: string): Promise; - list(filters?: ProcessFilters): Promise; -} +// Re-export types for use by ProcessStore implementations +export type { ProcessRecord, ProcessStatus } from '../core/types'; +export type { ProcessStore } from './process-store'; export interface ProcessFilters { status?: ProcessStatus; } -export class InMemoryProcessStore implements ProcessStore { - private processes = new Map(); - - async create(process: ProcessRecord): Promise { - this.processes.set(process.id, process); - } - - async get(id: string): Promise { - return this.processes.get(id) || null; - } - - async update(id: string, data: Partial): Promise { - const existing = this.processes.get(id); - if (!existing) { - throw new Error(`Process ${id} not found`); - } - - const updated = { ...existing, ...data }; - this.processes.set(id, updated); - } - - async delete(id: string): Promise { - // Note: ProcessService is responsible for killing processes via SessionManager - // This method only removes the record from storage - this.processes.delete(id); - } - - async list(filters?: ProcessFilters): Promise { - let processes = Array.from(this.processes.values()); - - // Processes are sandbox-scoped, not session-scoped - // Filter by status only (like filtering 'ps' output by state) - if (filters?.status) { - processes = processes.filter((p) => p.status === filters.status); - } - - return processes; - } -} - export class ProcessService { private manager: ProcessManager; @@ -190,7 +147,7 @@ export class ProcessService { const streamResult = await this.sessionManager.executeStreamInSession( sessionId, command, - (event) => { + async (event) => { // Route events to process record listeners if (event.type === 'stdout' && event.data) { processRecord.stdout += event.data; @@ -215,17 +172,22 @@ export class ProcessService { listener(status); }); - this.store - .update(processRecord.id, { + // Await store update to ensure consistency before next event + try { + await this.store.update(processRecord.id, { status, endTime, exitCode - }) - .catch((error) => { - this.logger.error('Failed to update process status', error, { - processId: processRecord.id - }); }); + } catch (error) { + this.logger.error( + 'Failed to update process status', + error instanceof Error ? error : undefined, + { + processId: processRecord.id + } + ); + } } else if (event.type === 'error') { processRecord.status = 'error'; processRecord.endTime = new Date(); @@ -478,6 +440,22 @@ export class ProcessService { } // All processes use SessionManager - create stream from listeners and buffered output + let outputListener: + | ((stream: 'stdout' | 'stderr', data: string) => void) + | null = null; + let statusListener: ((status: string) => void) | null = null; + + const cleanup = () => { + if (outputListener) { + process.outputListeners.delete(outputListener); + outputListener = null; + } + if (statusListener) { + process.statusListeners.delete(statusListener); + statusListener = null; + } + }; + const stream = new ReadableStream({ start(controller) { const encoder = new TextEncoder(); @@ -491,15 +469,13 @@ export class ProcessService { } // Set up listener for future output - const outputListener = ( - stream: 'stdout' | 'stderr', - data: string - ) => { + outputListener = (stream: 'stdout' | 'stderr', data: string) => { controller.enqueue(encoder.encode(data)); }; - const statusListener = (status: string) => { + statusListener = (status: string) => { if (['completed', 'failed', 'killed', 'error'].includes(status)) { + cleanup(); controller.close(); } }; @@ -511,8 +487,12 @@ export class ProcessService { if ( ['completed', 'failed', 'killed', 'error'].includes(process.status) ) { + cleanup(); controller.close(); } + }, + cancel() { + cleanup(); } }); diff --git a/packages/sandbox-container/src/services/process-store.ts b/packages/sandbox-container/src/services/process-store.ts new file mode 100644 index 00000000..3a4620a8 --- /dev/null +++ b/packages/sandbox-container/src/services/process-store.ts @@ -0,0 +1,200 @@ +import { mkdir, unlink } from 'node:fs/promises'; +import type { Logger } from '@repo/shared'; +import type { ProcessFilters, ProcessRecord } from './process-service'; + +/** + * File-based process store that persists completed processes to disk. + * Active processes are kept in memory for fast access. + * When a process reaches a terminal state, it is written to disk and removed from memory. + */ +export class ProcessStore { + private processes = new Map(); + private processDir = '/tmp/sandbox-internal/processes'; + private initialized = false; + + constructor(private logger: Logger) {} + + private async ensureInitialized(): Promise { + if (this.initialized) { + return; + } + + try { + await mkdir(this.processDir, { recursive: true }); + this.initialized = true; + } catch (error) { + // Directory might already exist, that's fine + this.initialized = true; + } + } + + async create(process: ProcessRecord): Promise { + await this.ensureInitialized(); + this.processes.set(process.id, process); + } + + async get(id: string): Promise { + await this.ensureInitialized(); + + // Check in-memory first for active processes + const inMemory = this.processes.get(id); + if (inMemory) { + return inMemory; + } + + // Fall back to file system for completed processes + return await this.readProcessFile(id); + } + + async update(id: string, data: Partial): Promise { + await this.ensureInitialized(); + + const existing = this.processes.get(id); + if (!existing) { + // Process might be in file already + const fileProcess = await this.readProcessFile(id); + if (!fileProcess) { + throw new Error(`Process ${id} not found`); + } + const updated = { ...fileProcess, ...data }; + await this.writeProcessFile(id, updated); + return; + } + + const updated = { ...existing, ...data }; + this.processes.set(id, updated); + + // Persist terminal states to disk and free memory + const isTerminal = ['completed', 'failed', 'killed', 'error'].includes( + updated.status + ); + if (isTerminal) { + try { + await this.writeProcessFile(id, updated); + } catch (error) { + // Write failed, still delete to prevent memory leak + // Explicit tradeoff: container stability > process history + this.logger.error( + 'Failed to persist completed process, will be lost on restart', + error instanceof Error ? error : new Error(String(error)), + { processId: id } + ); + } + // Always delete from memory to prevent leak, even if write failed + this.processes.delete(id); + } + } + + async delete(id: string): Promise { + await this.ensureInitialized(); + this.processes.delete(id); + await this.deleteProcessFile(id); + } + + async list(filters?: ProcessFilters): Promise { + await this.ensureInitialized(); + + // Start with active processes in memory + let processes = Array.from(this.processes.values()); + const seenIds = new Set(processes.map((p) => p.id)); + + // Include completed processes from disk + try { + const files = await Array.fromAsync( + new Bun.Glob('*.json').scan({ cwd: this.processDir }) + ); + + for (const file of files) { + const processId = file.replace('.json', ''); + if (!seenIds.has(processId)) { + // Skip if already in memory + const process = await this.readProcessFile(processId); + if (process) { + processes.push(process); + } + } + } + } catch (error) { + // If scanning fails (e.g., directory doesn't exist), just return in-memory processes + this.logger.error( + 'Failed to scan completed processes from disk', + error instanceof Error ? error : new Error(String(error)) + ); + } + + if (filters?.status) { + processes = processes.filter((p) => p.status === filters.status); + } + + return processes; + } + + // File I/O helper methods + + private getProcessFilePath(id: string): string { + return `${this.processDir}/${id}.json`; + } + + private async writeProcessFile( + id: string, + process: ProcessRecord + ): Promise { + // Serialize process record, excluding non-serializable fields + const serializable = { + id: process.id, + pid: process.pid, + command: process.command, + status: process.status, + startTime: process.startTime, + endTime: process.endTime, + exitCode: process.exitCode, + stdout: process.stdout, + stderr: process.stderr, + commandHandle: process.commandHandle + // Exclude: outputListeners, statusListeners (Set objects, not serializable) + }; + + const filePath = this.getProcessFilePath(id); + await Bun.write(filePath, JSON.stringify(serializable, null, 2)); + } + + private async readProcessFile(id: string): Promise { + try { + const filePath = this.getProcessFilePath(id); + const file = Bun.file(filePath); + + if (!(await file.exists())) { + return null; + } + + const text = await file.text(); + const data = JSON.parse(text); + + // Reconstruct ProcessRecord with empty listener Sets + const process: ProcessRecord = { + ...data, + startTime: new Date(data.startTime), + endTime: data.endTime ? new Date(data.endTime) : undefined, + outputListeners: new Set(), + statusListeners: new Set() + }; + + return process; + } catch (error) { + return null; + } + } + + private async deleteProcessFile(id: string): Promise { + try { + const filePath = this.getProcessFilePath(id); + const file = Bun.file(filePath); + + if (await file.exists()) { + await unlink(filePath); + } + } catch (error) { + // Best effort - don't throw if cleanup fails + } + } +} diff --git a/packages/sandbox-container/src/services/session-manager.ts b/packages/sandbox-container/src/services/session-manager.ts index 43c50a17..1dae4bca 100644 --- a/packages/sandbox-container/src/services/session-manager.ts +++ b/packages/sandbox-container/src/services/session-manager.ts @@ -186,7 +186,7 @@ export class SessionManager { async executeStreamInSession( sessionId: string, command: string, - onEvent: (event: ExecEvent) => void, + onEvent: (event: ExecEvent) => Promise, cwd: string | undefined, commandId: string ): Promise }>> { @@ -222,14 +222,14 @@ export class SessionManager { const firstResult = await generator.next(); if (!firstResult.done) { - onEvent(firstResult.value); + await onEvent(firstResult.value); } // Create background task for remaining events const continueStreaming = (async () => { try { for await (const event of generator) { - onEvent(event); + await onEvent(event); } } catch (error) { const errorMessage = diff --git a/packages/sandbox-container/tests/handlers/execute-handler.test.ts b/packages/sandbox-container/tests/handlers/execute-handler.test.ts index ba0b8574..5545e0b0 100644 --- a/packages/sandbox-container/tests/handlers/execute-handler.test.ts +++ b/packages/sandbox-container/tests/handlers/execute-handler.test.ts @@ -1,14 +1,13 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; -import type { ExecResult, ProcessStartResult } from '@repo/shared'; +import type { ExecResult, Logger, ProcessStartResult } from '@repo/shared'; import type { ErrorResponse } from '@repo/shared/errors'; import type { ExecuteRequest, - Logger, RequestContext, ServiceResult -} from '@sandbox-container/core/types.ts'; +} from '@sandbox-container/core/types'; import { ExecuteHandler } from '@sandbox-container/handlers/execute-handler.js'; -import type { ProcessService } from '@sandbox-container/services/process-service.ts'; +import type { ProcessService } from '@sandbox-container/services/process-service'; import { mocked } from '../test-utils'; // Mock the service dependencies @@ -21,12 +20,14 @@ const mockProcessService = { streamProcessLogs: vi.fn() } as unknown as ProcessService; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock request context const mockContext: RequestContext = { diff --git a/packages/sandbox-container/tests/handlers/file-handler.test.ts b/packages/sandbox-container/tests/handlers/file-handler.test.ts index b629bc12..d8e33254 100644 --- a/packages/sandbox-container/tests/handlers/file-handler.test.ts +++ b/packages/sandbox-container/tests/handlers/file-handler.test.ts @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; import type { DeleteFileResult, FileExistsResult, + Logger, MkdirResult, MoveFileResult, ReadFileResult, @@ -9,7 +10,7 @@ import type { WriteFileResult } from '@repo/shared'; import type { ErrorResponse } from '@repo/shared/errors'; -import type { Logger, RequestContext } from '@sandbox-container/core/types'; +import type { RequestContext } from '@sandbox-container/core/types'; import { FileHandler } from '@sandbox-container/handlers/file-handler'; import type { FileService } from '@sandbox-container/services/file-service'; @@ -33,12 +34,14 @@ const mockFileService = { // Remove private properties to avoid type conflicts } as unknown as FileService; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock request context const mockContext: RequestContext = { diff --git a/packages/sandbox-container/tests/handlers/git-handler.test.ts b/packages/sandbox-container/tests/handlers/git-handler.test.ts index e5187d5a..2070a03c 100644 --- a/packages/sandbox-container/tests/handlers/git-handler.test.ts +++ b/packages/sandbox-container/tests/handlers/git-handler.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; -import type { GitCheckoutResult } from '@repo/shared'; +import type { GitCheckoutResult, Logger } from '@repo/shared'; import type { ErrorResponse } from '@repo/shared/errors'; -import type { Logger, RequestContext } from '@sandbox-container/core/types.ts'; +import type { RequestContext } from '@sandbox-container/core/types'; import { GitHandler } from '@sandbox-container/handlers/git-handler'; import type { GitService } from '@sandbox-container/services/git-service'; @@ -13,12 +13,14 @@ const mockGitService = { listBranches: vi.fn() } as unknown as GitService; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock request context const mockContext: RequestContext = { diff --git a/packages/sandbox-container/tests/handlers/interpreter-handler.test.ts b/packages/sandbox-container/tests/handlers/interpreter-handler.test.ts index e50f1d58..52b52a4d 100644 --- a/packages/sandbox-container/tests/handlers/interpreter-handler.test.ts +++ b/packages/sandbox-container/tests/handlers/interpreter-handler.test.ts @@ -3,19 +3,22 @@ import type { ContextCreateResult, ContextDeleteResult, ContextListResult, - InterpreterHealthResult + InterpreterHealthResult, + Logger } from '@repo/shared'; import type { ErrorResponse } from '@repo/shared/errors'; +import { ErrorCode } from '@repo/shared/errors'; import type { - Logger, RequestContext, ServiceResult -} from '@sandbox-container/core/types.ts'; +} from '@sandbox-container/core/types'; import { InterpreterHandler } from '@sandbox-container/handlers/interpreter-handler.js'; import type { + Context, CreateContextRequest, - InterpreterService -} from '@sandbox-container/services/interpreter-service.ts'; + HealthStatus +} from '@sandbox-container/interpreter-service'; +import type { InterpreterService } from '@sandbox-container/services/interpreter-service'; import { mocked } from '../test-utils'; // Mock the service dependencies @@ -27,12 +30,14 @@ const mockInterpreterService = { executeCode: vi.fn() } as unknown as InterpreterService; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock request context const mockContext: RequestContext = { @@ -65,11 +70,11 @@ describe('InterpreterHandler', () => { const mockHealthResult = { success: true, data: { - status: 'healthy', ready: true, - version: '1.0.0' + initializing: false, + progress: 1.0 } - } as ServiceResult<{ status: string; ready: boolean; version: string }>; + } as ServiceResult; mocked(mockInterpreterService.getHealthStatus).mockResolvedValue( mockHealthResult @@ -139,14 +144,10 @@ describe('InterpreterHandler', () => { id: 'ctx-123', language: 'python', cwd: '/workspace', - createdAt: new Date() + createdAt: '2023-01-01T00:00:00Z', + lastUsed: '2023-01-01T00:00:00Z' } - } as ServiceResult<{ - id: string; - language: string; - cwd: string; - createdAt: Date; - }>; + } as ServiceResult; mocked(mockInterpreterService.createContext).mockResolvedValue( mockContextResult @@ -186,7 +187,7 @@ describe('InterpreterHandler', () => { success: false, error: { message: 'Invalid language specified', - code: 'VALIDATION_ERROR', + code: ErrorCode.VALIDATION_FAILED, details: { language: 'invalid-lang' } } } as ServiceResult; @@ -211,7 +212,7 @@ describe('InterpreterHandler', () => { // Verify error response: {code, message, context, httpStatus, timestamp} expect(response.status).toBeGreaterThanOrEqual(400); const responseData = (await response.json()) as ErrorResponse; - expect(responseData.code).toBe('VALIDATION_ERROR'); + expect(responseData.code).toBe(ErrorCode.VALIDATION_FAILED); expect(responseData.message).toBe('Invalid language specified'); expect(responseData.context).toMatchObject({ language: 'invalid-lang' }); expect(responseData.httpStatus).toBeDefined(); @@ -250,9 +251,8 @@ describe('InterpreterHandler', () => { expect(response.status).toBe(503); expect(response.headers.get('Retry-After')).toBe('10'); - const responseData = await response.json(); - expect(responseData.success).toBe(false); - expect(responseData.error.code).toBe('INTERPRETER_NOT_READY'); + const responseData = (await response.json()) as ErrorResponse; + expect(responseData.code).toBe('INTERPRETER_NOT_READY'); expect(responseData.timestamp).toBeDefined(); }); }); @@ -263,10 +263,22 @@ describe('InterpreterHandler', () => { const mockContexts = { success: true, data: [ - { id: 'ctx-1', language: 'python', cwd: '/workspace1' }, - { id: 'ctx-2', language: 'javascript', cwd: '/workspace2' } + { + id: 'ctx-1', + language: 'python', + cwd: '/workspace1', + createdAt: '2023-01-01T00:00:00Z', + lastUsed: '2023-01-01T00:00:00Z' + }, + { + id: 'ctx-2', + language: 'javascript', + cwd: '/workspace2', + createdAt: '2023-01-01T00:00:00Z', + lastUsed: '2023-01-01T00:00:00Z' + } ] - } as ServiceResult>; + } as ServiceResult; mocked(mockInterpreterService.listContexts).mockResolvedValue( mockContexts @@ -363,7 +375,7 @@ describe('InterpreterHandler', () => { success: false, error: { message: 'Context not found', - code: 'RESOURCE_NOT_FOUND', + code: ErrorCode.CONTEXT_NOT_FOUND, details: { contextId: 'ctx-999' } } } as ServiceResult; @@ -384,7 +396,7 @@ describe('InterpreterHandler', () => { // Verify error response: {code, message, context, httpStatus, timestamp} expect(response.status).toBeGreaterThanOrEqual(400); const responseData = (await response.json()) as ErrorResponse; - expect(responseData.code).toBe('RESOURCE_NOT_FOUND'); + expect(responseData.code).toBe(ErrorCode.CONTEXT_NOT_FOUND); expect(responseData.message).toBe('Context not found'); expect(responseData.context).toMatchObject({ contextId: 'ctx-999' }); expect(responseData.httpStatus).toBeDefined(); @@ -454,7 +466,7 @@ describe('InterpreterHandler', () => { // Mock error response from service const mockErrorResponse = new Response( JSON.stringify({ - code: 'RESOURCE_NOT_FOUND', + code: ErrorCode.CONTEXT_NOT_FOUND, message: 'Context not found', context: { contextId: 'ctx-invalid' }, httpStatus: 404, @@ -487,7 +499,7 @@ describe('InterpreterHandler', () => { // Verify error response: {code, message, context, httpStatus, timestamp} expect(response.status).toBe(404); const responseData = (await response.json()) as ErrorResponse; - expect(responseData.code).toBe('RESOURCE_NOT_FOUND'); + expect(responseData.code).toBe(ErrorCode.CONTEXT_NOT_FOUND); expect(responseData.message).toBe('Context not found'); expect(responseData.context).toMatchObject({ contextId: 'ctx-invalid' }); expect(responseData.httpStatus).toBe(404); diff --git a/packages/sandbox-container/tests/handlers/misc-handler.test.ts b/packages/sandbox-container/tests/handlers/misc-handler.test.ts index 00404303..e7d923b0 100644 --- a/packages/sandbox-container/tests/handlers/misc-handler.test.ts +++ b/packages/sandbox-container/tests/handlers/misc-handler.test.ts @@ -1,16 +1,21 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; -import type { HealthCheckResult, ShutdownResult } from '@repo/shared'; +import type { HealthCheckResult, Logger, ShutdownResult } from '@repo/shared'; import type { ErrorResponse } from '@repo/shared/errors'; -import type { Logger, RequestContext } from '@sandbox-container/core/types'; -import { MiscHandler } from '@sandbox-container/handlers/misc-handler'; +import type { RequestContext } from '@sandbox-container/core/types'; +import { + MiscHandler, + type VersionResult +} from '@sandbox-container/handlers/misc-handler'; // Mock the dependencies -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock request context const mockContext: RequestContext = { @@ -176,7 +181,7 @@ describe('MiscHandler', () => { expect(response.status).toBe(200); expect(response.headers.get('Content-Type')).toBe('application/json'); - const responseData = await response.json(); + const responseData = (await response.json()) as VersionResult; expect(responseData.success).toBe(true); expect(responseData.version).toBe('1.2.3'); expect(responseData.timestamp).toBeDefined(); @@ -196,7 +201,7 @@ describe('MiscHandler', () => { const response = await miscHandler.handle(request, mockContext); expect(response.status).toBe(200); - const responseData = await response.json(); + const responseData = (await response.json()) as VersionResult; expect(responseData.version).toBe('unknown'); }); @@ -431,12 +436,14 @@ describe('MiscHandler', () => { describe('no service dependencies', () => { it('should work without any service dependencies', async () => { // MiscHandler only requires logger, no other services - const simpleLogger: Logger = { + const simpleLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() - }; + debug: vi.fn(), + child: vi.fn() + } as Logger; + simpleLogger.child = vi.fn(() => simpleLogger); const independentHandler = new MiscHandler(simpleLogger); diff --git a/packages/sandbox-container/tests/handlers/port-handler.test.ts b/packages/sandbox-container/tests/handlers/port-handler.test.ts index 9614f7e3..11e60feb 100644 --- a/packages/sandbox-container/tests/handlers/port-handler.test.ts +++ b/packages/sandbox-container/tests/handlers/port-handler.test.ts @@ -1,18 +1,27 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; import type { + Logger, PortCloseResult, PortExposeResult, PortListResult } from '@repo/shared'; import type { ErrorResponse } from '@repo/shared/errors'; +import { ErrorCode } from '@repo/shared/errors'; import type { - Logger, PortInfo, + ProxyErrorResponse, RequestContext } from '@sandbox-container/core/types'; import { PortHandler } from '@sandbox-container/handlers/port-handler'; import type { PortService } from '@sandbox-container/services/port-service'; +// Test-specific type for mock proxy response +// The proxy handler passes through responses from the target service unchanged, +// so the shape depends on what the target returns. This type represents our test mock. +interface MockProxySuccessResponse { + success: boolean; +} + // Mock the dependencies - use partial mock to avoid private property issues const mockPortService = { exposePort: vi.fn(), @@ -25,12 +34,14 @@ const mockPortService = { destroy: vi.fn() } as unknown as PortService; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock request context const mockContext: RequestContext = { @@ -341,7 +352,7 @@ describe('PortHandler', () => { success: false, error: { message: 'Database error', - code: 'PORT_LIST_ERROR' + code: ErrorCode.PORT_OPERATION_ERROR } }); @@ -353,7 +364,7 @@ describe('PortHandler', () => { expect(response.status).toBe(500); const responseData = (await response.json()) as ErrorResponse; - expect(responseData.code).toBe('PORT_LIST_ERROR'); + expect(responseData.code).toBe(ErrorCode.PORT_OPERATION_ERROR); expect(responseData.message).toBe('Database error'); expect(responseData.httpStatus).toBe(500); expect(responseData.timestamp).toBeDefined(); @@ -409,7 +420,7 @@ describe('PortHandler', () => { const response = await portHandler.handle(request, mockContext); expect(response.status).toBe(201); - const responseData = await response.json(); + const responseData = (await response.json()) as MockProxySuccessResponse; expect(responseData.success).toBe(true); expect(mockPortService.proxyRequest).toHaveBeenCalledWith(3000, request); @@ -432,7 +443,7 @@ describe('PortHandler', () => { const response = await portHandler.handle(request, mockContext); expect(response.status).toBe(404); - const responseData = await response.json(); + const responseData = (await response.json()) as ProxyErrorResponse; expect(responseData.error).toBe('Port not found'); }); diff --git a/packages/sandbox-container/tests/handlers/process-handler.test.ts b/packages/sandbox-container/tests/handlers/process-handler.test.ts index 92d76d25..fa6c0dd7 100644 --- a/packages/sandbox-container/tests/handlers/process-handler.test.ts +++ b/packages/sandbox-container/tests/handlers/process-handler.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; import type { + Logger, ProcessCleanupResult, ProcessInfoResult, ProcessKillResult, @@ -10,7 +11,6 @@ import type { } from '@repo/shared'; import type { ErrorResponse } from '@repo/shared/errors'; import type { - Logger, ProcessInfo, RequestContext } from '@sandbox-container/core/types'; @@ -28,12 +28,14 @@ const mockProcessService = { executeCommand: vi.fn() } as unknown as ProcessService; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock request context const mockContext: RequestContext = { diff --git a/packages/sandbox-container/tests/handlers/session-handler.test.ts b/packages/sandbox-container/tests/handlers/session-handler.test.ts index 0da0dbab..b4bbf052 100644 --- a/packages/sandbox-container/tests/handlers/session-handler.test.ts +++ b/packages/sandbox-container/tests/handlers/session-handler.test.ts @@ -1,6 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; +import type { Logger, SessionDeleteResult } from '@repo/shared'; import type { ErrorResponse } from '@repo/shared/errors'; -import type { Logger, RequestContext } from '@sandbox-container/core/types'; +import { ErrorCode } from '@repo/shared/errors'; +import type { RequestContext } from '@sandbox-container/core/types'; import { SessionHandler } from '@sandbox-container/handlers/session-handler'; import type { SessionManager } from '@sandbox-container/services/session-manager'; import type { Session } from '@sandbox-container/session'; @@ -28,12 +30,14 @@ const mockSessionManager = { destroy: vi.fn() } as unknown as SessionManager; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock request context const mockContext: RequestContext = { @@ -92,7 +96,7 @@ describe('SessionHandler', () => { success: false, error: { message: 'Failed to create session', - code: 'SESSION_CREATE_ERROR', + code: ErrorCode.UNKNOWN_ERROR, details: { originalError: 'Store connection failed' } } }); @@ -106,7 +110,7 @@ describe('SessionHandler', () => { expect(response.status).toBe(500); const responseData = (await response.json()) as ErrorResponse; - expect(responseData.code).toBe('SESSION_CREATE_ERROR'); + expect(responseData.code).toBe(ErrorCode.UNKNOWN_ERROR); expect(responseData.message).toBe('Failed to create session'); expect(responseData.context).toEqual({ originalError: 'Store connection failed' @@ -165,7 +169,7 @@ describe('SessionHandler', () => { const response = await sessionHandler.handle(request, mockContext); expect(response.status).toBe(200); - const responseBody = await response.json(); + const responseBody = (await response.json()) as SessionDeleteResult; expect(responseBody.success).toBe(true); expect(responseBody.sessionId).toBe('test-session-123'); expect(responseBody.timestamp).toBeDefined(); @@ -356,7 +360,7 @@ describe('SessionHandler', () => { success: false, error: { message: 'Failed to list sessions', - code: 'SESSION_LIST_ERROR', + code: ErrorCode.UNKNOWN_ERROR, details: { originalError: 'Database connection lost' } } }); @@ -369,7 +373,7 @@ describe('SessionHandler', () => { expect(response.status).toBe(500); const responseData = (await response.json()) as ErrorResponse; - expect(responseData.code).toBe('SESSION_LIST_ERROR'); + expect(responseData.code).toBe(ErrorCode.UNKNOWN_ERROR); expect(responseData.message).toBe('Failed to list sessions'); expect(responseData.context).toEqual({ originalError: 'Database connection lost' diff --git a/packages/sandbox-container/tests/managers/file-manager.test.ts b/packages/sandbox-container/tests/managers/file-manager.test.ts index 7e11dcb4..12bc9981 100644 --- a/packages/sandbox-container/tests/managers/file-manager.test.ts +++ b/packages/sandbox-container/tests/managers/file-manager.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from 'bun:test'; -import type { FileStats } from '@sandbox-container/core/types.ts'; -import { FileManager } from '@sandbox-container/managers/file-manager.ts'; +import type { FileStats } from '@sandbox-container/core/types'; +import { FileManager } from '@sandbox-container/managers/file-manager'; describe('FileManager', () => { let manager: FileManager; diff --git a/packages/sandbox-container/tests/managers/git-manager.test.ts b/packages/sandbox-container/tests/managers/git-manager.test.ts index 97adc9c9..b0c3c807 100644 --- a/packages/sandbox-container/tests/managers/git-manager.test.ts +++ b/packages/sandbox-container/tests/managers/git-manager.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from 'bun:test'; -import { GitManager } from '@sandbox-container/managers/git-manager.ts'; +import { GitManager } from '@sandbox-container/managers/git-manager'; describe('GitManager', () => { let manager: GitManager; diff --git a/packages/sandbox-container/tests/managers/port-manager.test.ts b/packages/sandbox-container/tests/managers/port-manager.test.ts index 62f60319..ed687305 100644 --- a/packages/sandbox-container/tests/managers/port-manager.test.ts +++ b/packages/sandbox-container/tests/managers/port-manager.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from 'bun:test'; -import type { PortInfo } from '@sandbox-container/core/types.ts'; -import { PortManager } from '@sandbox-container/managers/port-manager.ts'; +import type { PortInfo } from '@sandbox-container/core/types'; +import { PortManager } from '@sandbox-container/managers/port-manager'; describe('PortManager', () => { let manager: PortManager; diff --git a/packages/sandbox-container/tests/managers/process-manager.test.ts b/packages/sandbox-container/tests/managers/process-manager.test.ts index c9da686c..bf19ec25 100644 --- a/packages/sandbox-container/tests/managers/process-manager.test.ts +++ b/packages/sandbox-container/tests/managers/process-manager.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from 'bun:test'; -import type { ProcessOptions } from '@sandbox-container/core/types.ts'; -import { ProcessManager } from '@sandbox-container/managers/process-manager.ts'; +import type { ProcessOptions } from '@sandbox-container/core/types'; +import { ProcessManager } from '@sandbox-container/managers/process-manager'; describe('ProcessManager', () => { let manager: ProcessManager; diff --git a/packages/sandbox-container/tests/security/security-service.test.ts b/packages/sandbox-container/tests/security/security-service.test.ts index 19d02608..a53f2177 100644 --- a/packages/sandbox-container/tests/security/security-service.test.ts +++ b/packages/sandbox-container/tests/security/security-service.test.ts @@ -8,14 +8,16 @@ */ import { beforeEach, describe, expect, test } from 'bun:test'; -import type { Logger } from '@sandbox-container/core/types'; +import type { Logger } from '@repo/shared'; import { SecurityService } from '@sandbox-container/security/security-service'; // Mock logger const mockLogger: Logger = { info: () => {}, warn: () => {}, - error: () => {} + error: () => {}, + debug: () => {}, + child: () => mockLogger }; describe('SecurityService - Simplified Security Model', () => { @@ -232,7 +234,9 @@ describe('SecurityService - Simplified Security Model', () => { const testLogger: Logger = { info: () => {}, warn: (msg, data) => logs.push({ msg, data }), - error: () => {} + error: () => {}, + debug: () => {}, + child: () => testLogger }; const testService = new SecurityService(testLogger); diff --git a/packages/sandbox-container/tests/services/file-service.test.ts b/packages/sandbox-container/tests/services/file-service.test.ts index 1d6c9cb2..b39f4fe5 100644 --- a/packages/sandbox-container/tests/services/file-service.test.ts +++ b/packages/sandbox-container/tests/services/file-service.test.ts @@ -1,38 +1,41 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; -import type { Logger, ServiceResult } from '@sandbox-container/core/types'; +import type { Logger } from '@repo/shared'; +import type { ServiceResult } from '@sandbox-container/core/types'; import { FileService, type SecurityService } from '@sandbox-container/services/file-service'; -import type { - RawExecResult, - SessionManager -} from '@sandbox-container/services/session-manager'; +import type { SessionManager } from '@sandbox-container/services/session-manager'; +import type { RawExecResult } from '@sandbox-container/session'; import { mocked } from '../test-utils'; // Mock SecurityService with proper typing const mockSecurityService: SecurityService = { - validatePath: vi.fn(), - sanitizePath: vi.fn() + validatePath: vi.fn() }; // Mock Logger with proper typing -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock SessionManager with proper typing -const mockSessionManager: Partial = { +const mockSessionManager = { executeInSession: vi.fn(), executeStreamInSession: vi.fn(), killCommand: vi.fn(), setEnvVars: vi.fn(), getSession: vi.fn(), - createSession: vi.fn() -}; + createSession: vi.fn(), + deleteSession: vi.fn(), + listSessions: vi.fn(), + destroy: vi.fn() +} as unknown as SessionManager; describe('FileService', () => { let fileService: FileService; @@ -51,7 +54,7 @@ describe('FileService', () => { fileService = new FileService( mockSecurityService, mockLogger, - mockSessionManager as SessionManager + mockSessionManager ); }); @@ -445,6 +448,7 @@ describe('FileService', () => { ); expect(aliasResult.success).toBe(true); + if (!aliasResult.success) throw new Error('Expected success'); expect(aliasResult.metadata?.encoding).toBe('utf-8'); }); @@ -583,7 +587,8 @@ describe('FileService', () => { ); expect(result.success).toBe(false); - expect(result.error?.code).toBe('VALIDATION_FAILED'); + if (result.success) throw new Error('Expected failure'); + expect(result.error.code).toBe('VALIDATION_FAILED'); expect(mockSessionManager.executeInSession).not.toHaveBeenCalled(); } }); diff --git a/packages/sandbox-container/tests/services/git-service.test.ts b/packages/sandbox-container/tests/services/git-service.test.ts index 593b6a48..103d98c3 100644 --- a/packages/sandbox-container/tests/services/git-service.test.ts +++ b/packages/sandbox-container/tests/services/git-service.test.ts @@ -1,42 +1,45 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; +import type { Logger } from '@repo/shared'; +import type { ValidationFailedContext } from '@repo/shared/errors'; import type { CloneOptions, - Logger, ServiceResult } from '@sandbox-container/core/types'; import { GitService, type SecurityService } from '@sandbox-container/services/git-service'; -import type { - RawExecResult, - SessionManager -} from '@sandbox-container/services/session-manager'; +import type { SessionManager } from '@sandbox-container/services/session-manager'; +import type { RawExecResult } from '@sandbox-container/session'; import { mocked } from '../test-utils'; // Properly typed mock dependencies const mockSecurityService: SecurityService = { validateGitUrl: vi.fn(), - validatePath: vi.fn(), - sanitizePath: vi.fn() + validatePath: vi.fn() }; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Properly typed mock SessionManager -const mockSessionManager: Partial = { +const mockSessionManager = { executeInSession: vi.fn(), executeStreamInSession: vi.fn(), killCommand: vi.fn(), setEnvVars: vi.fn(), getSession: vi.fn(), - createSession: vi.fn() -}; + createSession: vi.fn(), + deleteSession: vi.fn(), + listSessions: vi.fn(), + destroy: vi.fn() +} as unknown as SessionManager; describe('GitService', () => { let gitService: GitService; @@ -58,7 +61,7 @@ describe('GitService', () => { gitService = new GitService( mockSecurityService, mockLogger, - mockSessionManager as SessionManager + mockSessionManager ); }); @@ -172,14 +175,15 @@ describe('GitService', () => { ); expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.code).toBe('INVALID_GIT_URL'); - expect(result.error.message).toContain('Invalid URL scheme'); - expect(result.error.details?.validationErrors).toBeDefined(); - expect(result.error.details?.validationErrors?.[0]?.message).toContain( - 'Invalid URL scheme' - ); - } + if (result.success) throw new Error('Expected failure'); + expect(result.error.code).toBe('INVALID_GIT_URL'); + expect(result.error.message).toContain('Invalid URL scheme'); + const details = result.error + .details as unknown as ValidationFailedContext; + expect(details.validationErrors).toBeDefined(); + expect(details.validationErrors[0]?.message).toContain( + 'Invalid URL scheme' + ); // Should not attempt git clone expect(mockSessionManager.executeInSession).not.toHaveBeenCalled(); @@ -197,13 +201,14 @@ describe('GitService', () => { ); expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.code).toBe('VALIDATION_FAILED'); - expect(result.error.details?.validationErrors).toBeDefined(); - expect(result.error.details?.validationErrors?.[0]?.message).toContain( - 'Path outside sandbox' - ); - } + if (result.success) throw new Error('Expected failure'); + expect(result.error.code).toBe('VALIDATION_FAILED'); + const details = result.error + .details as unknown as ValidationFailedContext; + expect(details.validationErrors).toBeDefined(); + expect(details.validationErrors[0]?.message).toContain( + 'Path outside sandbox' + ); // Should not attempt git clone expect(mockSessionManager.executeInSession).not.toHaveBeenCalled(); diff --git a/packages/sandbox-container/tests/services/port-service.test.ts b/packages/sandbox-container/tests/services/port-service.test.ts index 7f7e3830..fed4a714 100644 --- a/packages/sandbox-container/tests/services/port-service.test.ts +++ b/packages/sandbox-container/tests/services/port-service.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'bun:test'; +import type { Logger } from '@repo/shared'; import type { - Logger, PortInfo, PortNotFoundResponse, ProxyErrorResponse @@ -25,12 +25,14 @@ const mockSecurityService: SecurityService = { validatePort: vi.fn() }; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock fetch for proxy testing const mockFetch = vi.fn(); diff --git a/packages/sandbox-container/tests/services/process-service.test.ts b/packages/sandbox-container/tests/services/process-service.test.ts index 14295e11..ef419d6e 100644 --- a/packages/sandbox-container/tests/services/process-service.test.ts +++ b/packages/sandbox-container/tests/services/process-service.test.ts @@ -1,46 +1,49 @@ import { beforeEach, describe, expect, it, vi } from 'bun:test'; +import type { Logger } from '@repo/shared'; import type { - Logger, ProcessRecord, ServiceResult -} from '@sandbox-container/core/types.ts'; +} from '@sandbox-container/core/types'; import { type ProcessFilters, ProcessService, type ProcessStore } from '@sandbox-container/services/process-service.js'; -import type { - RawExecResult, - SessionManager -} from '@sandbox-container/services/session-manager'; +import type { SessionManager } from '@sandbox-container/services/session-manager'; +import type { RawExecResult } from '@sandbox-container/session'; import { mocked } from '../test-utils'; // Mock the dependencies with proper typing -const mockProcessStore: ProcessStore = { +const mockProcessStore = { create: vi.fn(), get: vi.fn(), update: vi.fn(), delete: vi.fn(), list: vi.fn(), cleanup: vi.fn() -}; +} as unknown as ProcessStore; -const mockLogger: Logger = { +const mockLogger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), - debug: vi.fn() -}; + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); // Mock SessionManager with proper typing -const mockSessionManager: Partial = { +const mockSessionManager = { executeInSession: vi.fn(), executeStreamInSession: vi.fn(), killCommand: vi.fn(), setEnvVars: vi.fn(), getSession: vi.fn(), - createSession: vi.fn() -}; + createSession: vi.fn(), + deleteSession: vi.fn(), + listSessions: vi.fn(), + destroy: vi.fn() +} as unknown as SessionManager; // Mock factory functions const createMockProcess = ( @@ -72,7 +75,7 @@ describe('ProcessService', () => { processService = new ProcessService( mockProcessStore, mockLogger, - mockSessionManager as SessionManager + mockSessionManager ); }); diff --git a/packages/sandbox-container/tests/services/process-store.test.ts b/packages/sandbox-container/tests/services/process-store.test.ts new file mode 100644 index 00000000..6108f1c9 --- /dev/null +++ b/packages/sandbox-container/tests/services/process-store.test.ts @@ -0,0 +1,382 @@ +import { beforeEach, describe, expect, it, vi } from 'bun:test'; +import { mkdir, rm } from 'node:fs/promises'; +import type { Logger } from '@repo/shared'; +import type { ProcessRecord } from '@sandbox-container/core/types'; +import { ProcessStore } from '@sandbox-container/services/process-store.js'; + +const mockLogger = { + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + child: vi.fn() +} as Logger; +mockLogger.child = vi.fn(() => mockLogger); + +const createMockProcess = ( + overrides: Partial = {} +): ProcessRecord => ({ + id: 'proc-123', + pid: 12345, + command: 'test command', + status: 'running', + startTime: new Date('2024-01-01T00:00:00Z'), + stdout: '', + stderr: '', + outputListeners: new Set(), + statusListeners: new Set(), + commandHandle: { + sessionId: 'default', + commandId: 'proc-123' + }, + ...overrides +}); + +describe('ProcessStore', () => { + let processStore: ProcessStore; + const testProcessDir = '/tmp/sandbox-internal/processes'; + + beforeEach(async () => { + vi.clearAllMocks(); + + // Clean up test directory + try { + await rm(testProcessDir, { recursive: true, force: true }); + } catch { + // Ignore errors if directory doesn't exist + } + + processStore = new ProcessStore(mockLogger); + }); + + describe('initialization', () => { + it('should initialize when first operation is called', async () => { + const process = createMockProcess(); + await processStore.create(process); + + // Verify process is stored + const result = await processStore.get(process.id); + expect(result).not.toBeNull(); + }); + }); + + describe('create', () => { + it('should store process in memory', async () => { + const process = createMockProcess({ id: 'proc-1', command: 'ls' }); + await processStore.create(process); + + const result = await processStore.get('proc-1'); + expect(result).toEqual(process); + }); + }); + + describe('get', () => { + it('should retrieve process from memory', async () => { + const process = createMockProcess({ id: 'proc-1' }); + await processStore.create(process); + + const result = await processStore.get('proc-1'); + expect(result).toEqual(process); + }); + + it('should retrieve completed process from disk', async () => { + const process = createMockProcess({ + id: 'proc-1', + status: 'running' + }); + await processStore.create(process); + + // Update to terminal state (should move to disk) + await processStore.update('proc-1', { + status: 'completed', + exitCode: 0, + endTime: new Date('2024-01-01T00:01:00Z') + }); + + // Should retrieve from disk + const result = await processStore.get('proc-1'); + expect(result).not.toBeNull(); + expect(result?.status).toBe('completed'); + expect(result?.exitCode).toBe(0); + }); + + it('should return null for non-existent process', async () => { + const result = await processStore.get('nonexistent'); + expect(result).toBeNull(); + }); + }); + + describe('update', () => { + it('should update process in memory', async () => { + const process = createMockProcess({ id: 'proc-1', stdout: '' }); + await processStore.create(process); + + await processStore.update('proc-1', { stdout: 'output' }); + + const result = await processStore.get('proc-1'); + expect(result?.stdout).toBe('output'); + }); + + it('should move completed process from memory to disk', async () => { + const process = createMockProcess({ + id: 'proc-1', + status: 'running' + }); + await processStore.create(process); + + await processStore.update('proc-1', { + status: 'completed', + exitCode: 0, + endTime: new Date('2024-01-01T00:01:00Z') + }); + + // Process should be on disk, not in memory + const result = await processStore.get('proc-1'); + expect(result).not.toBeNull(); + expect(result?.status).toBe('completed'); + }); + + it('should delete from memory even if disk write fails', async () => { + const process = createMockProcess({ + id: 'proc-1', + status: 'running' + }); + await processStore.create(process); + + // Remove write permissions on directory to cause write failure + const { chmod } = await import('node:fs/promises'); + await chmod(testProcessDir, 0o444); // Read-only + + try { + await processStore.update('proc-1', { + status: 'completed', + exitCode: 0 + }); + + // Logger should have recorded the error + expect(mockLogger.error).toHaveBeenCalledWith( + 'Failed to persist completed process, will be lost on restart', + expect.any(Error), + { processId: 'proc-1' } + ); + + // Process should be removed from memory despite write failure + const result = await processStore.get('proc-1'); + expect(result).toBeNull(); + } finally { + // Restore write permissions + await chmod(testProcessDir, 0o755); + } + }); + + it('should handle all terminal states', async () => { + const terminalStates: Array<'completed' | 'failed' | 'killed' | 'error'> = + ['completed', 'failed', 'killed', 'error']; + + for (const status of terminalStates) { + const id = `proc-${status}`; + const process = createMockProcess({ id, status: 'running' }); + await processStore.create(process); + + await processStore.update(id, { status, endTime: new Date() }); + + // Should be retrievable from disk + const result = await processStore.get(id); + expect(result).not.toBeNull(); + expect(result?.status).toBe(status); + } + }); + + it('should throw error when updating non-existent process', async () => { + await expect( + processStore.update('nonexistent', { stdout: 'output' }) + ).rejects.toThrow('Process nonexistent not found'); + }); + }); + + describe('delete', () => { + it('should delete process from memory', async () => { + const process = createMockProcess({ id: 'proc-1' }); + await processStore.create(process); + + await processStore.delete('proc-1'); + + const result = await processStore.get('proc-1'); + expect(result).toBeNull(); + }); + + it('should delete process file from disk', async () => { + const process = createMockProcess({ id: 'proc-1', status: 'running' }); + await processStore.create(process); + + // Move to disk + await processStore.update('proc-1', { + status: 'completed', + endTime: new Date() + }); + + // Delete + await processStore.delete('proc-1'); + + const result = await processStore.get('proc-1'); + expect(result).toBeNull(); + }); + }); + + describe('list', () => { + it('should return active processes from memory', async () => { + const process1 = createMockProcess({ id: 'proc-1', status: 'running' }); + const process2 = createMockProcess({ id: 'proc-2', status: 'running' }); + + await processStore.create(process1); + await processStore.create(process2); + + const result = await processStore.list(); + expect(result).toHaveLength(2); + expect(result.map((p) => p.id)).toContain('proc-1'); + expect(result.map((p) => p.id)).toContain('proc-2'); + }); + + it('should include completed processes from disk', async () => { + const process1 = createMockProcess({ id: 'proc-1', status: 'running' }); + const process2 = createMockProcess({ id: 'proc-2', status: 'running' }); + + await processStore.create(process1); + await processStore.create(process2); + + // Complete process1 (moves to disk) + await processStore.update('proc-1', { + status: 'completed', + endTime: new Date() + }); + + // Verify file exists on disk + const filePath = `${testProcessDir}/proc-1.json`; + const file = Bun.file(filePath); + expect(await file.exists()).toBe(true); + + const result = await processStore.list(); + expect(result).toHaveLength(2); + expect(result.map((p) => p.id)).toContain('proc-1'); + expect(result.map((p) => p.id)).toContain('proc-2'); + }); + + it('should filter by status', async () => { + const process1 = createMockProcess({ id: 'proc-1', status: 'running' }); + const process2 = createMockProcess({ id: 'proc-2', status: 'running' }); + + await processStore.create(process1); + await processStore.create(process2); + + // Complete process1 + await processStore.update('proc-1', { + status: 'completed', + endTime: new Date() + }); + + // Filter by running + const runningProcesses = await processStore.list({ status: 'running' }); + expect(runningProcesses).toHaveLength(1); + expect(runningProcesses[0].id).toBe('proc-2'); + + // Filter by completed + const completedProcesses = await processStore.list({ + status: 'completed' + }); + expect(completedProcesses).toHaveLength(1); + expect(completedProcesses[0].id).toBe('proc-1'); + }); + + it('should handle disk scan errors gracefully', async () => { + const process = createMockProcess({ id: 'proc-1', status: 'running' }); + await processStore.create(process); + + // Create completed process on disk + const process2 = createMockProcess({ id: 'proc-2', status: 'running' }); + await processStore.create(process2); + await processStore.update('proc-2', { + status: 'completed', + endTime: new Date() + }); + + // Mock Bun.Glob to throw error + const originalGlob = Bun.Glob; + Bun.Glob = vi.fn().mockImplementation(() => { + throw new Error('Scan failed'); + }) as unknown as typeof Bun.Glob; + + try { + // Should still return in-memory processes + const result = await processStore.list(); + expect(result).toHaveLength(1); + expect(result[0].id).toBe('proc-1'); + + // Should log error + expect(mockLogger.error).toHaveBeenCalledWith( + 'Failed to scan completed processes from disk', + expect.any(Error) + ); + } finally { + Bun.Glob = originalGlob; + } + }); + + it('should return empty array when no processes exist', async () => { + const result = await processStore.list(); + expect(result).toEqual([]); + }); + }); + + describe('disk persistence', () => { + it('should serialize process without non-serializable fields', async () => { + const process = createMockProcess({ + id: 'proc-1', + status: 'running', + stdout: 'output', + stderr: 'error' + }); + await processStore.create(process); + + // Add listeners (non-serializable) + process.outputListeners.add(() => {}); + process.statusListeners.add(() => {}); + + // Move to disk + await processStore.update('proc-1', { + status: 'completed', + endTime: new Date('2024-01-01T00:01:00Z') + }); + + // Read from disk and verify structure + const retrieved = await processStore.get('proc-1'); + expect(retrieved).not.toBeNull(); + expect(retrieved?.id).toBe('proc-1'); + expect(retrieved?.stdout).toBe('output'); + expect(retrieved?.stderr).toBe('error'); + // Listeners should be reconstructed as empty Sets + expect(retrieved?.outputListeners).toEqual(new Set()); + expect(retrieved?.statusListeners).toEqual(new Set()); + }); + + it('should preserve dates when reading from disk', async () => { + const startTime = new Date('2024-01-01T00:00:00Z'); + const endTime = new Date('2024-01-01T00:01:00Z'); + + const process = createMockProcess({ + id: 'proc-1', + status: 'running', + startTime + }); + await processStore.create(process); + + await processStore.update('proc-1', { + status: 'completed', + endTime + }); + + const retrieved = await processStore.get('proc-1'); + expect(retrieved?.startTime).toEqual(startTime); + expect(retrieved?.endTime).toEqual(endTime); + }); + }); +}); diff --git a/packages/sandbox-container/tests/tsconfig.json b/packages/sandbox-container/tests/tsconfig.json deleted file mode 100644 index 8bcce265..00000000 --- a/packages/sandbox-container/tests/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "@repo/typescript-config/bun.json", - "compilerOptions": { - "baseUrl": "..", - "paths": { - "@sandbox-container/*": ["./src/*"] - } - }, - "include": ["./**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/packages/sandbox-container/tests/validation/request-validator.test.ts b/packages/sandbox-container/tests/validation/request-validator.test.ts index d152ec47..32951978 100644 --- a/packages/sandbox-container/tests/validation/request-validator.test.ts +++ b/packages/sandbox-container/tests/validation/request-validator.test.ts @@ -9,6 +9,10 @@ import { describe, expect, test } from 'bun:test'; import { RequestValidator } from '@sandbox-container/validation/request-validator'; +import type { + ReadFileRequest, + WriteFileRequest +} from '@sandbox-container/validation/schemas'; describe('RequestValidator - Structure Validation Only', () => { const validator = new RequestValidator(); @@ -75,7 +79,7 @@ describe('RequestValidator - Structure Validation Only', () => { describe('validateFileRequest', () => { test('should validate read file request', () => { - const result = validator.validateFileRequest( + const result = validator.validateFileRequest( { path: '/workspace/file.txt', sessionId: 'session-123' }, 'read' ); @@ -85,7 +89,7 @@ describe('RequestValidator - Structure Validation Only', () => { }); test('should validate write file request', () => { - const result = validator.validateFileRequest( + const result = validator.validateFileRequest( { path: '/workspace/file.txt', content: 'hello world' }, 'write' ); @@ -105,7 +109,10 @@ describe('RequestValidator - Structure Validation Only', () => { ]; for (const path of paths) { - const result = validator.validateFileRequest({ path }, 'read'); + const result = validator.validateFileRequest( + { path }, + 'read' + ); expect(result.isValid).toBe(true); expect(result.data?.path).toBe(path); } diff --git a/packages/sandbox-container/tsconfig.build.json b/packages/sandbox-container/tsconfig.build.json new file mode 100644 index 00000000..c7352165 --- /dev/null +++ b/packages/sandbox-container/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/sandbox-container/tsconfig.json b/packages/sandbox-container/tsconfig.json index c945bf51..5041ffff 100644 --- a/packages/sandbox-container/tsconfig.json +++ b/packages/sandbox-container/tsconfig.json @@ -1,12 +1,10 @@ { "extends": "@repo/typescript-config/bun.json", "compilerOptions": { - "rootDir": "src", "outDir": "dist", "paths": { "@sandbox-container/*": ["./src/*"] } }, - "include": ["src/**/*.ts"], - "exclude": ["**/*.test.ts", "tests/**"] + "include": ["src/**/*.ts", "tests/**/*.ts"] } diff --git a/packages/sandbox/src/sandbox.ts b/packages/sandbox/src/sandbox.ts index 9b3fb45c..ea28ec07 100644 --- a/packages/sandbox/src/sandbox.ts +++ b/packages/sandbox/src/sandbox.ts @@ -756,8 +756,20 @@ export class Sandbox extends Container implements ISandbox { } } - override onStop() { + override async onStop() { this.logger.debug('Sandbox stopped'); + + // Clear in-memory state that references the old container + // This prevents stale references after container restarts + this.portTokens.clear(); + this.defaultSession = null; + this.activeMounts.clear(); + + // Persist cleanup to storage so state is clean on next container start + await Promise.all([ + this.ctx.storage.delete('portTokens'), + this.ctx.storage.delete('defaultSession') + ]); } override onError(error: unknown) { diff --git a/packages/sandbox/tests/git-client.test.ts b/packages/sandbox/tests/git-client.test.ts index 28d67837..c685c779 100644 --- a/packages/sandbox/tests/git-client.test.ts +++ b/packages/sandbox/tests/git-client.test.ts @@ -1,5 +1,5 @@ +import type { GitCheckoutResult } from '@repo/shared'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { GitCheckoutResponse } from '../src/clients'; import { GitClient } from '../src/clients/git-client'; import { GitAuthenticationError, @@ -35,12 +35,8 @@ describe('GitClient', () => { describe('repository cloning', () => { it('should clone public repositories successfully', async () => { - const mockResponse: GitCheckoutResponse = { + const mockResponse: GitCheckoutResult = { success: true, - stdout: - "Cloning into 'react'...\nReceiving objects: 100% (1284/1284), done.", - stderr: '', - exitCode: 0, repoUrl: 'https://github.com/facebook/react.git', branch: 'main', targetDir: 'react', @@ -59,15 +55,11 @@ describe('GitClient', () => { expect(result.success).toBe(true); expect(result.repoUrl).toBe('https://github.com/facebook/react.git'); expect(result.branch).toBe('main'); - expect(result.exitCode).toBe(0); }); it('should clone repositories to specific branches', async () => { - const mockResponse: GitCheckoutResponse = { + const mockResponse: GitCheckoutResult = { success: true, - stdout: "Cloning into 'project'...\nSwitching to branch 'development'", - stderr: '', - exitCode: 0, repoUrl: 'https://github.com/company/project.git', branch: 'development', targetDir: 'project', @@ -89,11 +81,8 @@ describe('GitClient', () => { }); it('should clone repositories to custom directories', async () => { - const mockResponse: GitCheckoutResponse = { + const mockResponse: GitCheckoutResult = { success: true, - stdout: "Cloning into 'workspace/my-app'...\nDone.", - stderr: '', - exitCode: 0, repoUrl: 'https://github.com/user/my-app.git', branch: 'main', targetDir: 'workspace/my-app', @@ -115,12 +104,8 @@ describe('GitClient', () => { }); it('should handle large repository clones with warnings', async () => { - const mockResponse: GitCheckoutResponse = { + const mockResponse: GitCheckoutResult = { success: true, - stdout: - "Cloning into 'linux'...\nReceiving objects: 100% (8125432/8125432), 2.34 GiB, done.", - stderr: 'warning: filtering not recognized by server', - exitCode: 0, repoUrl: 'https://github.com/torvalds/linux.git', branch: 'master', targetDir: 'linux', @@ -137,15 +122,11 @@ describe('GitClient', () => { ); expect(result.success).toBe(true); - expect(result.stderr).toContain('warning:'); }); it('should handle SSH repository URLs', async () => { - const mockResponse: GitCheckoutResponse = { + const mockResponse: GitCheckoutResult = { success: true, - stdout: "Cloning into 'private-project'...\nDone.", - stderr: '', - exitCode: 0, repoUrl: 'git@github.com:company/private-project.git', branch: 'main', targetDir: 'private-project', @@ -175,8 +156,6 @@ describe('GitClient', () => { JSON.stringify({ success: true, stdout: `Cloning into '${repoName}'...\nDone.`, - stderr: '', - exitCode: 0, repoUrl: body.repoUrl, branch: body.branch || 'main', targetDir: body.targetDir || repoName, @@ -320,11 +299,8 @@ describe('GitClient', () => { }); it('should handle partial clone failures', async () => { - const mockResponse: GitCheckoutResponse = { + const mockResponse: GitCheckoutResult = { success: false, - stdout: "Cloning into 'repo'...\nReceiving objects: 45% (450/1000)", - stderr: 'error: RPC failed\nfatal: early EOF', - exitCode: 128, repoUrl: 'https://github.com/problematic/repo.git', branch: 'main', targetDir: 'repo', @@ -341,8 +317,6 @@ describe('GitClient', () => { ); expect(result.success).toBe(false); - expect(result.exitCode).toBe(128); - expect(result.stderr).toContain('RPC failed'); }); }); @@ -434,9 +408,6 @@ describe('GitClient', () => { new Response( JSON.stringify({ success: true, - stdout: "Cloning into 'private-repo'...\nDone.", - stderr: '', - exitCode: 0, repoUrl: 'https://oauth2:ghp_token123@github.com/user/private-repo.git', branch: 'main', @@ -463,9 +434,6 @@ describe('GitClient', () => { new Response( JSON.stringify({ success: true, - stdout: "Cloning into 'react'...\nDone.", - stderr: '', - exitCode: 0, repoUrl: 'https://github.com/facebook/react.git', branch: 'main', targetDir: '/workspace/react', diff --git a/packages/sandbox/tests/openai-shell-editor.test.ts b/packages/sandbox/tests/openai-shell-editor.test.ts index 7df23f54..81575146 100644 --- a/packages/sandbox/tests/openai-shell-editor.test.ts +++ b/packages/sandbox/tests/openai-shell-editor.test.ts @@ -1,7 +1,7 @@ import type { ApplyPatchOperation } from '@openai/agents'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { Editor, Shell } from '../src/openai/index.ts'; -import type { Sandbox } from '../src/sandbox.ts'; +import { Editor, Shell } from '../src/openai/index'; +import type { Sandbox } from '../src/sandbox'; interface MockSandbox { exec?: ReturnType; diff --git a/packages/sandbox/tests/port-client.test.ts b/packages/sandbox/tests/port-client.test.ts index 1da988e9..0caf4cdd 100644 --- a/packages/sandbox/tests/port-client.test.ts +++ b/packages/sandbox/tests/port-client.test.ts @@ -1,9 +1,9 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { - ExposePortResponse, - GetExposedPortsResponse, - UnexposePortResponse -} from '../src/clients'; + PortCloseResult, + PortExposeResult, + PortListResult +} from '@repo/shared'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { PortClient } from '../src/clients/port-client'; import { InvalidPortError, @@ -37,11 +37,10 @@ describe('PortClient', () => { describe('service exposure', () => { it('should expose web services successfully', async () => { - const mockResponse: ExposePortResponse = { + const mockResponse: PortExposeResult = { success: true, port: 3001, - exposedAt: 'https://preview-abc123.workers.dev', - name: 'web-server', + url: 'https://preview-abc123.workers.dev', timestamp: '2023-01-01T00:00:00Z' }; @@ -53,17 +52,15 @@ describe('PortClient', () => { expect(result.success).toBe(true); expect(result.port).toBe(3001); - expect(result.exposedAt).toBe('https://preview-abc123.workers.dev'); - expect(result.name).toBe('web-server'); - expect(result.exposedAt.startsWith('https://')).toBe(true); + expect(result.url).toBe('https://preview-abc123.workers.dev'); + expect(result.url.startsWith('https://')).toBe(true); }); it('should expose API services on different ports', async () => { - const mockResponse: ExposePortResponse = { + const mockResponse: PortExposeResult = { success: true, port: 8080, - exposedAt: 'https://api-def456.workers.dev', - name: 'api-server', + url: 'https://api-def456.workers.dev', timestamp: '2023-01-01T00:00:00Z' }; @@ -75,15 +72,14 @@ describe('PortClient', () => { expect(result.success).toBe(true); expect(result.port).toBe(8080); - expect(result.name).toBe('api-server'); - expect(result.exposedAt).toContain('api-'); + expect(result.url).toContain('api-'); }); it('should expose services without explicit names', async () => { - const mockResponse: ExposePortResponse = { + const mockResponse: PortExposeResult = { success: true, port: 5000, - exposedAt: 'https://service-ghi789.workers.dev', + url: 'https://service-ghi789.workers.dev', timestamp: '2023-01-01T00:00:00Z' }; @@ -95,33 +91,31 @@ describe('PortClient', () => { expect(result.success).toBe(true); expect(result.port).toBe(5000); - expect(result.name).toBeUndefined(); - expect(result.exposedAt).toBeDefined(); + expect(result.url).toBeDefined(); }); }); describe('service management', () => { it('should list all exposed services', async () => { - const mockResponse: GetExposedPortsResponse = { + const mockResponse: PortListResult = { success: true, ports: [ { port: 3000, - exposedAt: 'https://frontend-abc123.workers.dev', - name: 'frontend' + url: 'https://frontend-abc123.workers.dev', + status: 'active' }, { port: 4000, - exposedAt: 'https://api-def456.workers.dev', - name: 'api' + url: 'https://api-def456.workers.dev', + status: 'active' }, { port: 5432, - exposedAt: 'https://db-ghi789.workers.dev', - name: 'database' + url: 'https://db-ghi789.workers.dev', + status: 'active' } ], - count: 3, timestamp: '2023-01-01T00:10:00Z' }; @@ -132,21 +126,18 @@ describe('PortClient', () => { const result = await client.getExposedPorts('session-list'); expect(result.success).toBe(true); - expect(result.count).toBe(3); expect(result.ports).toHaveLength(3); result.ports.forEach((service) => { - expect(service.exposedAt).toContain('.workers.dev'); + expect(service.url).toContain('.workers.dev'); expect(service.port).toBeGreaterThan(0); - expect(service.name).toBeDefined(); }); }); it('should handle empty exposed ports list', async () => { - const mockResponse: GetExposedPortsResponse = { + const mockResponse: PortListResult = { success: true, ports: [], - count: 0, timestamp: '2023-01-01T00:00:00Z' }; @@ -157,12 +148,11 @@ describe('PortClient', () => { const result = await client.getExposedPorts('session-empty'); expect(result.success).toBe(true); - expect(result.count).toBe(0); expect(result.ports).toHaveLength(0); }); it('should unexpose services cleanly', async () => { - const mockResponse: UnexposePortResponse = { + const mockResponse: PortCloseResult = { success: true, port: 3001, timestamp: '2023-01-01T00:15:00Z' diff --git a/packages/sandbox/tests/process-client.test.ts b/packages/sandbox/tests/process-client.test.ts index 85a5dfac..1cb344da 100644 --- a/packages/sandbox/tests/process-client.test.ts +++ b/packages/sandbox/tests/process-client.test.ts @@ -1,11 +1,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { - GetProcessLogsResponse, - GetProcessResponse, - KillAllProcessesResponse, - KillProcessResponse, - ListProcessesResponse, - StartProcessResponse + ProcessCleanupResult, + ProcessInfoResult, + ProcessKillResult, + ProcessListResult, + ProcessLogsResult, + ProcessStartResult } from '../src/clients'; import { ProcessClient } from '../src/clients/process-client'; import { @@ -37,15 +37,11 @@ describe('ProcessClient', () => { describe('process lifecycle management', () => { it('should start background processes successfully', async () => { - const mockResponse: StartProcessResponse = { + const mockResponse: ProcessStartResult = { success: true, - process: { - id: 'proc-web-server', - command: 'npm run dev', - status: 'running', - pid: 12345, - startTime: '2023-01-01T00:00:00Z' - }, + processId: 'proc-web-server', + command: 'npm run dev', + pid: 12345, timestamp: '2023-01-01T00:00:00Z' }; @@ -56,22 +52,17 @@ describe('ProcessClient', () => { const result = await client.startProcess('npm run dev', 'session-123'); expect(result.success).toBe(true); - expect(result.process.command).toBe('npm run dev'); - expect(result.process.status).toBe('running'); - expect(result.process.pid).toBe(12345); - expect(result.process.id).toBe('proc-web-server'); + expect(result.command).toBe('npm run dev'); + expect(result.pid).toBe(12345); + expect(result.processId).toBe('proc-web-server'); }); it('should start processes with custom process IDs', async () => { - const mockResponse: StartProcessResponse = { + const mockResponse: ProcessStartResult = { success: true, - process: { - id: 'my-api-server', - command: 'python app.py', - status: 'running', - pid: 54321, - startTime: '2023-01-01T00:00:00Z' - }, + processId: 'my-api-server', + command: 'python app.py', + pid: 54321, timestamp: '2023-01-01T00:00:00Z' }; @@ -84,21 +75,16 @@ describe('ProcessClient', () => { }); expect(result.success).toBe(true); - expect(result.process.id).toBe('my-api-server'); - expect(result.process.command).toBe('python app.py'); - expect(result.process.status).toBe('running'); + expect(result.processId).toBe('my-api-server'); + expect(result.command).toBe('python app.py'); }); it('should handle long-running process startup', async () => { - const mockResponse: StartProcessResponse = { + const mockResponse: ProcessStartResult = { success: true, - process: { - id: 'proc-database', - command: 'docker run postgres', - status: 'running', - pid: 99999, - startTime: '2023-01-01T00:00:00Z' - }, + processId: 'proc-database', + command: 'docker run postgres', + pid: 99999, timestamp: '2023-01-01T00:00:05Z' }; @@ -121,8 +107,8 @@ describe('ProcessClient', () => { ); expect(result.success).toBe(true); - expect(result.process.status).toBe('running'); - expect(result.process.command).toBe('docker run postgres'); + expect(result.processId).toBe('proc-database'); + expect(result.command).toBe('docker run postgres'); }); it('should handle command not found errors', async () => { @@ -158,7 +144,7 @@ describe('ProcessClient', () => { describe('process monitoring and inspection', () => { it('should list running processes', async () => { - const mockResponse: ListProcessesResponse = { + const mockResponse: ProcessListResult = { success: true, processes: [ { @@ -185,7 +171,6 @@ describe('ProcessClient', () => { endTime: '2023-01-01T00:05:00Z' } ], - count: 3, timestamp: '2023-01-01T00:05:30Z' }; @@ -193,10 +178,9 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.listProcesses('session-list'); + const result = await client.listProcesses(); expect(result.success).toBe(true); - expect(result.count).toBe(3); expect(result.processes).toHaveLength(3); const runningProcesses = result.processes.filter( @@ -214,7 +198,7 @@ describe('ProcessClient', () => { }); it('should get specific process details', async () => { - const mockResponse: GetProcessResponse = { + const mockResponse: ProcessInfoResult = { success: true, process: { id: 'proc-analytics', @@ -230,7 +214,7 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.getProcess('proc-analytics', 'session-get'); + const result = await client.getProcess('proc-analytics'); expect(result.success).toBe(true); expect(result.process.id).toBe('proc-analytics'); @@ -249,16 +233,15 @@ describe('ProcessClient', () => { new Response(JSON.stringify(errorResponse), { status: 404 }) ); - await expect( - client.getProcess('nonexistent-proc', 'session-err') - ).rejects.toThrow(ProcessNotFoundError); + await expect(client.getProcess('nonexistent-proc')).rejects.toThrow( + ProcessNotFoundError + ); }); it('should handle empty process list', async () => { - const mockResponse: ListProcessesResponse = { + const mockResponse: ProcessListResult = { success: true, processes: [], - count: 0, timestamp: '2023-01-01T00:00:00Z' }; @@ -266,19 +249,18 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.listProcesses('session-list'); + const result = await client.listProcesses(); expect(result.success).toBe(true); - expect(result.count).toBe(0); expect(result.processes).toHaveLength(0); }); }); describe('process termination', () => { it('should kill individual processes', async () => { - const mockResponse: KillProcessResponse = { + const mockResponse: ProcessKillResult = { success: true, - message: 'Process proc-web killed successfully', + processId: 'test-process', timestamp: '2023-01-01T00:10:00Z' }; @@ -286,11 +268,9 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.killProcess('proc-web', 'session-kill'); + const result = await client.killProcess('proc-web'); expect(result.success).toBe(true); - expect(result.message).toContain('killed successfully'); - expect(result.message).toContain('proc-web'); }); it('should handle kill non-existent process', async () => { @@ -303,16 +283,15 @@ describe('ProcessClient', () => { new Response(JSON.stringify(errorResponse), { status: 404 }) ); - await expect( - client.killProcess('already-dead-proc', 'session-err') - ).rejects.toThrow(ProcessNotFoundError); + await expect(client.killProcess('already-dead-proc')).rejects.toThrow( + ProcessNotFoundError + ); }); it('should kill all processes at once', async () => { - const mockResponse: KillAllProcessesResponse = { + const mockResponse: ProcessCleanupResult = { success: true, - killedCount: 5, - message: 'All 5 processes killed successfully', + cleanedCount: 0, timestamp: '2023-01-01T00:15:00Z' }; @@ -320,18 +299,15 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.killAllProcesses('session-killall'); + const result = await client.killAllProcesses(); expect(result.success).toBe(true); - expect(result.killedCount).toBe(5); - expect(result.message).toContain('All 5 processes killed'); }); it('should handle kill all when no processes running', async () => { - const mockResponse: KillAllProcessesResponse = { + const mockResponse: ProcessCleanupResult = { success: true, - killedCount: 0, - message: 'No processes to kill', + cleanedCount: 0, timestamp: '2023-01-01T00:00:00Z' }; @@ -339,11 +315,9 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.killAllProcesses('session-killall'); + const result = await client.killAllProcesses(); expect(result.success).toBe(true); - expect(result.killedCount).toBe(0); - expect(result.message).toContain('No processes to kill'); }); it('should handle kill failures', async () => { @@ -356,15 +330,15 @@ describe('ProcessClient', () => { new Response(JSON.stringify(errorResponse), { status: 500 }) ); - await expect( - client.killProcess('protected-proc', 'session-err') - ).rejects.toThrow(ProcessError); + await expect(client.killProcess('protected-proc')).rejects.toThrow( + ProcessError + ); }); }); describe('process log management', () => { it('should retrieve process logs', async () => { - const mockResponse: GetProcessLogsResponse = { + const mockResponse: ProcessLogsResult = { success: true, processId: 'proc-server', stdout: `Server starting... @@ -382,7 +356,7 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.getProcessLogs('proc-server', 'session-logs'); + const result = await client.getProcessLogs('proc-server'); expect(result.success).toBe(true); expect(result.processId).toBe('proc-server'); @@ -402,16 +376,16 @@ describe('ProcessClient', () => { new Response(JSON.stringify(errorResponse), { status: 404 }) ); - await expect( - client.getProcessLogs('missing-proc', 'session-err') - ).rejects.toThrow(ProcessNotFoundError); + await expect(client.getProcessLogs('missing-proc')).rejects.toThrow( + ProcessNotFoundError + ); }); it('should retrieve logs for processes with large output', async () => { const largeStdout = 'Log entry with details\n'.repeat(10000); const largeStderr = 'Error trace line\n'.repeat(1000); - const mockResponse: GetProcessLogsResponse = { + const mockResponse: ProcessLogsResult = { success: true, processId: 'proc-batch', stdout: largeStdout, @@ -423,7 +397,7 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.getProcessLogs('proc-batch', 'session-logs'); + const result = await client.getProcessLogs('proc-batch'); expect(result.success).toBe(true); expect(result.stdout.length).toBeGreaterThan(200000); @@ -433,7 +407,7 @@ describe('ProcessClient', () => { }); it('should handle empty process logs', async () => { - const mockResponse: GetProcessLogsResponse = { + const mockResponse: ProcessLogsResult = { success: true, processId: 'proc-silent', stdout: '', @@ -445,7 +419,7 @@ describe('ProcessClient', () => { new Response(JSON.stringify(mockResponse), { status: 200 }) ); - const result = await client.getProcessLogs('proc-silent', 'session-logs'); + const result = await client.getProcessLogs('proc-silent'); expect(result.success).toBe(true); expect(result.stdout).toBe(''); @@ -480,10 +454,7 @@ data: {"type":"stdout","data":"Server ready on port 3000\\n","timestamp":"2023-0 }) ); - const stream = await client.streamProcessLogs( - 'proc-realtime', - 'session-stream' - ); + const stream = await client.streamProcessLogs('proc-realtime'); expect(stream).toBeInstanceOf(ReadableStream); @@ -517,9 +488,9 @@ data: {"type":"stdout","data":"Server ready on port 3000\\n","timestamp":"2023-0 new Response(JSON.stringify(errorResponse), { status: 404 }) ); - await expect( - client.streamProcessLogs('stream-missing', 'session-err') - ).rejects.toThrow(ProcessNotFoundError); + await expect(client.streamProcessLogs('stream-missing')).rejects.toThrow( + ProcessNotFoundError + ); }); it('should handle streaming setup failures', async () => { @@ -532,9 +503,9 @@ data: {"type":"stdout","data":"Server ready on port 3000\\n","timestamp":"2023-0 new Response(JSON.stringify(errorResponse), { status: 500 }) ); - await expect( - client.streamProcessLogs('proc-no-logs', 'session-err') - ).rejects.toThrow(ProcessError); + await expect(client.streamProcessLogs('proc-no-logs')).rejects.toThrow( + ProcessError + ); }); it('should handle missing stream body', async () => { @@ -546,22 +517,18 @@ data: {"type":"stdout","data":"Server ready on port 3000\\n","timestamp":"2023-0 ); await expect( - client.streamProcessLogs('proc-empty-stream', 'session-err') + client.streamProcessLogs('proc-empty-stream') ).rejects.toThrow('No response body for streaming'); }); }); describe('session integration', () => { it('should include session in process operations', async () => { - const mockResponse: StartProcessResponse = { + const mockResponse: ProcessStartResult = { success: true, - process: { - id: 'proc-session-test', - command: 'echo session-test', - status: 'running', - pid: 11111, - startTime: '2023-01-01T00:00:00Z' - }, + processId: 'proc-session-test', + command: 'echo session-test', + pid: 11111, timestamp: '2023-01-01T00:00:00Z' }; @@ -608,7 +575,6 @@ data: {"type":"stdout","data":"Server ready on port 3000\\n","timestamp":"2023-0 JSON.stringify({ success: true, processes: [], - count: 0, timestamp: new Date().toISOString() }) ) @@ -632,8 +598,8 @@ data: {"type":"stdout","data":"Server ready on port 3000\\n","timestamp":"2023-0 const operations = await Promise.all([ client.startProcess('npm run dev', 'session-concurrent'), client.startProcess('python api.py', 'session-concurrent'), - client.listProcesses('session-concurrent'), - client.getProcessLogs('existing-proc', 'session-concurrent'), + client.listProcesses(), + client.getProcessLogs('existing-proc'), client.startProcess('node worker.js', 'session-concurrent') ]); @@ -650,7 +616,7 @@ data: {"type":"stdout","data":"Server ready on port 3000\\n","timestamp":"2023-0 it('should handle network failures gracefully', async () => { mockFetch.mockRejectedValue(new Error('Network connection failed')); - await expect(client.listProcesses('session-err')).rejects.toThrow( + await expect(client.listProcesses()).rejects.toThrow( 'Network connection failed' ); }); diff --git a/packages/sandbox/tests/sandbox.test.ts b/packages/sandbox/tests/sandbox.test.ts index e4341a7d..b96602e2 100644 --- a/packages/sandbox/tests/sandbox.test.ts +++ b/packages/sandbox/tests/sandbox.test.ts @@ -76,6 +76,7 @@ describe('Sandbox - Automatic Session Management', () => { .mockImplementation( (callback: () => Promise): Promise => callback() ), + waitUntil: vi.fn(), id: { toString: () => 'test-sandbox-id', equals: vi.fn(), @@ -86,7 +87,7 @@ describe('Sandbox - Automatic Session Management', () => { mockEnv = {}; // Create Sandbox instance - SandboxClient is created internally - const stub = new Sandbox(mockCtx, mockEnv); + const stub = new Sandbox(mockCtx as DurableObjectState<{}>, mockEnv); // Wait for blockConcurrencyWhile to complete await vi.waitFor(() => { @@ -746,9 +747,10 @@ describe('Sandbox - Automatic Session Management', () => { await sandbox.setSandboxName('MyProject-123', false); vi.spyOn(sandbox.client.ports, 'exposePort').mockResolvedValue({ + success: true, port: 8080, - token: 'test-token-1234', - previewUrl: '' + url: '', + timestamp: '2023-01-01T00:00:00Z' }); await expect( @@ -760,9 +762,10 @@ describe('Sandbox - Automatic Session Management', () => { await sandbox.setSandboxName('my-project', false); vi.spyOn(sandbox.client.ports, 'exposePort').mockResolvedValue({ + success: true, port: 8080, - token: 'mock-token', - previewUrl: '' + url: '', + timestamp: '2023-01-01T00:00:00Z' }); const result = await sandbox.exposePort(8080, { @@ -779,9 +782,10 @@ describe('Sandbox - Automatic Session Management', () => { await sandbox.setSandboxName('myproject-123', true); vi.spyOn(sandbox.client.ports, 'exposePort').mockResolvedValue({ + success: true, port: 4000, - token: 'mock-token', - previewUrl: '' + url: '', + timestamp: '2023-01-01T00:00:00Z' }); const result = await sandbox.exposePort(4000, { hostname: 'my-app.dev' }); @@ -796,9 +800,10 @@ describe('Sandbox - Automatic Session Management', () => { await sandbox.setSandboxName('test-sandbox', false); vi.spyOn(sandbox.client.ports, 'exposePort').mockResolvedValue({ + success: true, port: 8080, - token: 'mock-token', - previewUrl: '' + url: '', + timestamp: '2023-01-01T00:00:00Z' }); const result = await sandbox.exposePort(8080, { @@ -814,9 +819,10 @@ describe('Sandbox - Automatic Session Management', () => { await sandbox.setSandboxName('MyProject-ABC', false); vi.spyOn(sandbox.client.ports, 'exposePort').mockResolvedValue({ + success: true, port: 8080, - token: 'test-token-1234', - previewUrl: '' + url: '', + timestamp: '2023-01-01T00:00:00Z' }); await expect( diff --git a/packages/sandbox/tsconfig.json b/packages/sandbox/tsconfig.json index ea90ac8e..0ba9108f 100644 --- a/packages/sandbox/tsconfig.json +++ b/packages/sandbox/tsconfig.json @@ -6,6 +6,6 @@ "lib": ["ES2022"], "resolveJsonModule": true }, - "include": ["src/**/*.ts"], - "exclude": ["tests/**", "node_modules", "dist"] + "include": ["src/**/*.ts", "tests/**/*.ts"], + "exclude": ["node_modules", "dist"] } diff --git a/packages/shared/package.json b/packages/shared/package.json index badf4556..18467d0a 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -15,7 +15,7 @@ } }, "scripts": { - "build": "tsc --project tsconfig.json", + "build": "tsc --project tsconfig.build.json", "typecheck": "tsc --noEmit", "test": "vitest run --config vitest.config.ts", "clean": "rm -rf dist" diff --git a/packages/shared/tests/logger.test.ts b/packages/shared/tests/logger.test.ts index 6a4b47f1..fab3be1e 100644 --- a/packages/shared/tests/logger.test.ts +++ b/packages/shared/tests/logger.test.ts @@ -43,7 +43,7 @@ describe('Logger Module', () => { describe('CloudflareLogger - Basic Logging', () => { it('should log debug messages when level is DEBUG', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_test123' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_test123' } as LogContext, LogLevelEnum.DEBUG, false ); @@ -51,11 +51,11 @@ describe('Logger Module', () => { logger.debug('Debug message', { operation: 'test' }); expect(consoleLogSpy).toHaveBeenCalledOnce(); - const logOutput = consoleLogSpy.mock.calls[0][0]; + const logOutput = consoleLogSpy.mock.calls[0][0] as string; expect(JSON.parse(logOutput)).toMatchObject({ level: 'debug', msg: 'Debug message', - component: 'durable-object', + component: 'sandbox-do', traceId: 'tr_test123', operation: 'test' }); @@ -71,14 +71,14 @@ describe('Logger Module', () => { logger.info('Info message'); expect(consoleLogSpy).toHaveBeenCalledOnce(); - const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(logOutput.level).toBe('info'); expect(logOutput.msg).toBe('Info message'); }); it('should log warn messages', () => { const logger = new CloudflareLogger( - { component: 'worker', traceId: 'tr_xyz' } as LogContext, + { component: 'container', traceId: 'tr_xyz' } as LogContext, LogLevelEnum.INFO, false ); @@ -86,14 +86,14 @@ describe('Logger Module', () => { logger.warn('Warning message'); expect(consoleWarnSpy).toHaveBeenCalledOnce(); - const logOutput = JSON.parse(consoleWarnSpy.mock.calls[0][0]); + const logOutput = JSON.parse(consoleWarnSpy.mock.calls[0][0] as string); expect(logOutput.level).toBe('warn'); expect(logOutput.msg).toBe('Warning message'); }); it('should log error messages with error object', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_err' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_err' } as LogContext, LogLevelEnum.INFO, false ); @@ -103,7 +103,7 @@ describe('Logger Module', () => { logger.error('Error occurred', error); expect(consoleErrorSpy).toHaveBeenCalledOnce(); - const logOutput = JSON.parse(consoleErrorSpy.mock.calls[0][0]); + const logOutput = JSON.parse(consoleErrorSpy.mock.calls[0][0] as string); expect(logOutput.level).toBe('error'); expect(logOutput.msg).toBe('Error occurred'); expect(logOutput.error).toMatchObject({ @@ -121,7 +121,7 @@ describe('Logger Module', () => { logger.info('Test message'); - const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(logOutput.timestamp).toMatch( /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ ); @@ -131,7 +131,7 @@ describe('Logger Module', () => { describe('CloudflareLogger - Log Level Filtering', () => { it('should not log debug when level is INFO', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_filter' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_filter' } as LogContext, LogLevelEnum.INFO, false ); @@ -143,7 +143,7 @@ describe('Logger Module', () => { it('should not log info when level is WARN', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_filter' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_filter' } as LogContext, LogLevelEnum.WARN, false ); @@ -156,7 +156,7 @@ describe('Logger Module', () => { it('should log warn and error when level is WARN', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_filter' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_filter' } as LogContext, LogLevelEnum.WARN, false ); @@ -170,7 +170,7 @@ describe('Logger Module', () => { it('should only log errors when level is ERROR', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_filter' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_filter' } as LogContext, LogLevelEnum.ERROR, false ); @@ -190,7 +190,7 @@ describe('Logger Module', () => { it('should create child logger with merged context', () => { const parentLogger = new CloudflareLogger( { - component: 'durable-object', + component: 'sandbox-do', traceId: 'tr_parent', sandboxId: 'sandbox-1' } as LogContext, @@ -205,9 +205,9 @@ describe('Logger Module', () => { childLogger.info('Child log'); expect(consoleLogSpy).toHaveBeenCalledOnce(); - const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(logOutput).toMatchObject({ - component: 'durable-object', + component: 'sandbox-do', traceId: 'tr_parent', sandboxId: 'sandbox-1', operation: 'exec', @@ -228,7 +228,7 @@ describe('Logger Module', () => { child3.info('Nested child log'); - const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(logOutput).toMatchObject({ component: 'container', traceId: 'tr_nest', @@ -240,7 +240,7 @@ describe('Logger Module', () => { it('should inherit log level from parent', () => { const parentLogger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_level' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_level' } as LogContext, LogLevelEnum.ERROR, false ); @@ -259,7 +259,7 @@ describe('Logger Module', () => { it('should use pretty printing when enabled', () => { const logger = new CloudflareLogger( { - component: 'durable-object', + component: 'sandbox-do', traceId: 'tr_pretty123456789', sandboxId: 'sandbox-1' } as LogContext, @@ -270,7 +270,7 @@ describe('Logger Module', () => { logger.info('Test message', { operation: 'exec' }); expect(consoleLogSpy).toHaveBeenCalledOnce(); - const output = consoleLogSpy.mock.calls[0][0]; + const output = consoleLogSpy.mock.calls[0][0] as string; // Pretty output should NOT be JSON expect(typeof output).toBe('string'); @@ -278,7 +278,7 @@ describe('Logger Module', () => { // Should contain human-readable elements expect(output).toContain('INFO'); - expect(output).toContain('[durable-object]'); + expect(output).toContain('[sandbox-do]'); expect(output).toContain('Test message'); expect(output).toContain('tr_pretty123'); // Truncated trace ID (12 chars) }); @@ -292,7 +292,7 @@ describe('Logger Module', () => { logger.info('JSON message'); - const output = consoleLogSpy.mock.calls[0][0]; + const output = consoleLogSpy.mock.calls[0][0] as string; expect(() => JSON.parse(output)).not.toThrow(); expect(JSON.parse(output)).toMatchObject({ level: 'info', @@ -303,7 +303,7 @@ describe('Logger Module', () => { it('should include ANSI color codes in pretty mode', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_color' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_color' } as LogContext, LogLevelEnum.INFO, true ); @@ -313,9 +313,9 @@ describe('Logger Module', () => { logger.error('Colored error'); // Check for ANSI escape codes - expect(consoleLogSpy.mock.calls[0][0]).toContain('\x1b['); // ANSI code prefix - expect(consoleWarnSpy.mock.calls[0][0]).toContain('\x1b['); - expect(consoleErrorSpy.mock.calls[0][0]).toContain('\x1b['); + expect(consoleLogSpy.mock.calls[0][0] as string).toContain('\x1b['); // ANSI code prefix + expect(consoleWarnSpy.mock.calls[0][0] as string).toContain('\x1b['); + expect(consoleErrorSpy.mock.calls[0][0] as string).toContain('\x1b['); }); it('should display error stack in pretty mode', () => { @@ -392,7 +392,7 @@ describe('Logger Module', () => { it('should create logger with base context', () => { const logger = createLogger({ - component: 'durable-object', + component: 'sandbox-do', traceId: 'tr_factory', sandboxId: 'sandbox-1' }); @@ -400,9 +400,9 @@ describe('Logger Module', () => { logger.info('Factory test'); expect(consoleLogSpy).toHaveBeenCalledOnce(); - const output = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(output).toMatchObject({ - component: 'durable-object', + component: 'sandbox-do', traceId: 'tr_factory', sandboxId: 'sandbox-1' }); @@ -415,7 +415,7 @@ describe('Logger Module', () => { logger.info('Auto trace'); - const output = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(output.traceId).toMatch(/^tr_[0-9a-f]{16}$/); }); }); @@ -435,7 +435,7 @@ describe('Logger Module', () => { it('should handle undefined error in error()', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_noerr' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_noerr' } as LogContext, LogLevelEnum.ERROR, false ); @@ -443,7 +443,7 @@ describe('Logger Module', () => { logger.error('Error without error object', undefined); expect(consoleErrorSpy).toHaveBeenCalledOnce(); - const output = JSON.parse(consoleErrorSpy.mock.calls[0][0]); + const output = JSON.parse(consoleErrorSpy.mock.calls[0][0] as string); expect(output.error).toBeUndefined(); }); @@ -458,13 +458,13 @@ describe('Logger Module', () => { logger.info(longMessage); expect(consoleLogSpy).toHaveBeenCalledOnce(); - const output = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(output.msg).toBe(longMessage); }); it('should handle special characters in messages', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_special' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_special' } as LogContext, LogLevelEnum.INFO, false ); @@ -474,7 +474,7 @@ describe('Logger Module', () => { logger.info(specialMessage); expect(consoleLogSpy).toHaveBeenCalledOnce(); - const output = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(output.msg).toBe(specialMessage); }); @@ -488,13 +488,13 @@ describe('Logger Module', () => { logger.info('Message', { operation: undefined, commandId: 'cmd-123' }); expect(consoleLogSpy).toHaveBeenCalledOnce(); - const output = JSON.parse(consoleLogSpy.mock.calls[0][0]); + const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); expect(output.commandId).toBe('cmd-123'); }); it('should handle errors with circular references in error object', () => { const logger = new CloudflareLogger( - { component: 'durable-object', traceId: 'tr_circ' } as LogContext, + { component: 'sandbox-do', traceId: 'tr_circ' } as LogContext, LogLevelEnum.ERROR, false ); diff --git a/packages/shared/tsconfig.build.json b/packages/shared/tsconfig.build.json new file mode 100644 index 00000000..c7352165 --- /dev/null +++ b/packages/shared/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index f8424895..13b56ad3 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -1,9 +1,7 @@ { "extends": "@repo/typescript-config/declarations.json", "compilerOptions": { - "rootDir": "src", "outDir": "dist" }, - "include": ["src/**/*.ts"], - "exclude": ["**/*.test.ts"] + "include": ["src/**/*.ts", "tests/**/*.ts"] } diff --git a/sites/sandbox/src/App.tsx b/sites/sandbox/src/App.tsx index 6db58a32..9441b864 100644 --- a/sites/sandbox/src/App.tsx +++ b/sites/sandbox/src/App.tsx @@ -68,41 +68,8 @@ function Header() { }, []); const handleCopy = async () => { - const copyCommand = async () => { - if (navigator.clipboard?.writeText) { - await navigator.clipboard.writeText(command); - return true; - } - - const textarea = document.createElement('textarea'); - textarea.value = command; - textarea.setAttribute('readonly', ''); - textarea.style.position = 'absolute'; - textarea.style.opacity = '0'; - document.body.appendChild(textarea); - - const selection = document.getSelection(); - const originalRange = - selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; - - textarea.select(); - const succeeded = document.execCommand('copy'); - document.body.removeChild(textarea); - - if (originalRange && selection) { - selection.removeAllRanges(); - selection.addRange(originalRange); - } - - return succeeded; - }; - try { - const didCopy = await copyCommand(); - - if (!didCopy) { - throw new Error('Copy command failed'); - } + await navigator.clipboard.writeText(command); setCopied(true); diff --git a/sites/sandbox/src/components/Header.tsx b/sites/sandbox/src/components/Header.tsx index b34f59b7..c636483c 100644 --- a/sites/sandbox/src/components/Header.tsx +++ b/sites/sandbox/src/components/Header.tsx @@ -15,41 +15,8 @@ export default function Header() { }, []); const handleCopy = async () => { - const copyCommand = async () => { - if (navigator.clipboard?.writeText) { - await navigator.clipboard.writeText(command); - return true; - } - - const textarea = document.createElement('textarea'); - textarea.value = command; - textarea.setAttribute('readonly', ''); - textarea.style.position = 'absolute'; - textarea.style.opacity = '0'; - document.body.appendChild(textarea); - - const selection = document.getSelection(); - const originalRange = - selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; - - textarea.select(); - const succeeded = document.execCommand('copy'); - document.body.removeChild(textarea); - - if (originalRange && selection) { - selection.removeAllRanges(); - selection.addRange(originalRange); - } - - return succeeded; - }; - try { - const didCopy = await copyCommand(); - - if (!didCopy) { - throw new Error('Copy command failed'); - } + await navigator.clipboard.writeText(command); setCopied(true); diff --git a/tests/e2e/_smoke.test.ts b/tests/e2e/_smoke.test.ts index 4d3ba4df..c5535110 100644 --- a/tests/e2e/_smoke.test.ts +++ b/tests/e2e/_smoke.test.ts @@ -1,6 +1,7 @@ import { describe, test, expect, beforeAll, afterAll, afterEach } from 'vitest'; import { getTestWorkerUrl, WranglerDevRunner } from './helpers/wrangler-runner'; import { createSandboxId, cleanupSandbox } from './helpers/test-fixtures'; +import type { HealthResponse } from './test-worker/types'; /** * Smoke test to verify integration test infrastructure @@ -44,7 +45,7 @@ describe('Integration Infrastructure Smoke Test', () => { const response = await fetch(`${workerUrl}/health`); expect(response.status).toBe(200); - const data = await response.json(); + const data = (await response.json()) as HealthResponse; expect(data.status).toBe('ok'); // In local mode, verify stdout captured wrangler startup diff --git a/tests/e2e/bucket-mounting.test.ts b/tests/e2e/bucket-mounting.test.ts index 635777ea..01b683b0 100644 --- a/tests/e2e/bucket-mounting.test.ts +++ b/tests/e2e/bucket-mounting.test.ts @@ -8,6 +8,8 @@ import { getTestWorkerUrl, type WranglerDevRunner } from './helpers/wrangler-runner'; +import type { ExecResult } from '@repo/shared'; +import type { SuccessResponse, BucketGetResponse } from './test-worker/types'; /** * E2E test for S3-compatible bucket mounting @@ -107,7 +109,7 @@ describe('Bucket Mounting E2E', () => { }) }); expect(mountResponse.ok).toBe(true); - const mountResult = await mountResponse.json(); + const mountResult = (await mountResponse.json()) as SuccessResponse; expect(mountResult.success).toBe(true); // 3. Verify pre-existing R2 file appears in mount (R2 → Mount) @@ -121,7 +123,8 @@ describe('Bucket Mounting E2E', () => { }) } ); - const readPreExistingResult = await readPreExistingResponse.json(); + const readPreExistingResult = + (await readPreExistingResponse.json()) as ExecResult; expect(readPreExistingResult.exitCode).toBe(0); expect(readPreExistingResult.stdout?.trim()).toBe(PRE_EXISTING_CONTENT); @@ -133,7 +136,7 @@ describe('Bucket Mounting E2E', () => { command: `echo "${TEST_CONTENT}" > ${MOUNT_PATH}/${TEST_FILE}` }) }); - const writeResult = await writeResponse.json(); + const writeResult = (await writeResponse.json()) as ExecResult; expect(writeResult.exitCode).toBe(0); // 5. Verify new file appears in R2 via binding (Mount → R2) @@ -145,7 +148,7 @@ describe('Bucket Mounting E2E', () => { } ); expect(getResponse.ok).toBe(true); - const getResult = await getResponse.json(); + const getResult = (await getResponse.json()) as BucketGetResponse; expect(getResult.success).toBe(true); expect(getResult.content.trim()).toBe(TEST_CONTENT); diff --git a/tests/e2e/build-test-workflow.test.ts b/tests/e2e/build-test-workflow.test.ts index eeb6a8c8..57d71668 100644 --- a/tests/e2e/build-test-workflow.test.ts +++ b/tests/e2e/build-test-workflow.test.ts @@ -5,6 +5,8 @@ import { createTestHeaders, cleanupSandbox } from './helpers/test-fixtures'; +import type { ExecResult, WriteFileResult, ReadFileResult } from '@repo/shared'; +import type { ErrorResponse } from './test-worker/types'; /** * Build and Test Workflow Integration Tests @@ -56,7 +58,7 @@ describe('Build and Test Workflow', () => { }); expect(echoResponse.status).toBe(200); - const echoData = await echoResponse.json(); + const echoData = (await echoResponse.json()) as ExecResult; expect(echoData.exitCode).toBe(0); expect(echoData.stdout).toContain('Hello from sandbox'); @@ -71,7 +73,7 @@ describe('Build and Test Workflow', () => { }); expect(writeResponse.status).toBe(200); - const writeData = await writeResponse.json(); + const writeData = (await writeResponse.json()) as WriteFileResult; expect(writeData.success).toBe(true); // Step 3: Read the file back to verify persistence @@ -84,7 +86,7 @@ describe('Build and Test Workflow', () => { }); expect(readResponse.status).toBe(200); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ReadFileResult; expect(readData.content).toBe('Integration test content'); // Step 4: Verify pwd to understand working directory @@ -97,7 +99,7 @@ describe('Build and Test Workflow', () => { }); expect(pwdResponse.status).toBe(200); - const pwdData = await pwdResponse.json(); + const pwdData = (await pwdResponse.json()) as ExecResult; expect(pwdData.stdout).toMatch(/\/workspace/); }); @@ -114,7 +116,7 @@ describe('Build and Test Workflow', () => { // Should return 500 error since shell terminated unexpectedly expect(response.status).toBe(500); - const data = await response.json(); + const data = (await response.json()) as ErrorResponse; // Should have an error object (500 responses may not have success field) expect(data.error).toBeDefined(); diff --git a/tests/e2e/code-interpreter-workflow.test.ts b/tests/e2e/code-interpreter-workflow.test.ts index 525c009f..efdf3851 100644 --- a/tests/e2e/code-interpreter-workflow.test.ts +++ b/tests/e2e/code-interpreter-workflow.test.ts @@ -29,6 +29,8 @@ import { createTestHeaders, cleanupSandbox } from './helpers/test-fixtures'; +import type { CodeContext, ExecutionResult } from '@repo/shared'; +import type { ErrorResponse } from './test-worker/types'; describe('Code Interpreter Workflow (E2E)', () => { let runner: WranglerDevRunner | null; @@ -73,7 +75,7 @@ describe('Code Interpreter Workflow (E2E)', () => { ); expect(pythonCtxResponse.status).toBe(200); - const pythonCtx = await pythonCtxResponse.json(); + const pythonCtx = (await pythonCtxResponse.json()) as CodeContext; expect(pythonCtx.id).toBeTruthy(); expect(pythonCtx.language).toBe('python'); @@ -85,7 +87,7 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(jsCtxResponse.status).toBe(200); - const jsCtx = await jsCtxResponse.json(); + const jsCtx = (await jsCtxResponse.json()) as CodeContext; expect(jsCtx.id).toBeTruthy(); expect(jsCtx.language).toBe('javascript'); expect(jsCtx.id).not.toBe(pythonCtx.id); // Different contexts @@ -97,11 +99,11 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(listResponse.status).toBe(200); - const contexts = await listResponse.json(); + const contexts = (await listResponse.json()) as CodeContext[]; expect(Array.isArray(contexts)).toBe(true); expect(contexts.length).toBeGreaterThanOrEqual(2); - const contextIds = contexts.map((ctx: any) => ctx.id); + const contextIds = contexts.map((ctx) => ctx.id); expect(contextIds).toContain(pythonCtx.id); expect(contextIds).toContain(jsCtx.id); }, 120000); @@ -117,7 +119,7 @@ describe('Code Interpreter Workflow (E2E)', () => { body: JSON.stringify({ language: 'python' }) }); - const context = await createResponse.json(); + const context = (await createResponse.json()) as CodeContext; const contextId = context.id; // Delete context @@ -130,9 +132,8 @@ describe('Code Interpreter Workflow (E2E)', () => { ); expect(deleteResponse.status).toBe(200); - const deleteData = await deleteResponse.json(); + const deleteData = (await deleteResponse.json()) as { success: boolean }; expect(deleteData.success).toBe(true); - expect(deleteData.contextId).toBe(contextId); // Verify context is removed from list const listResponse = await fetch(`${workerUrl}/api/code/context/list`, { @@ -140,8 +141,8 @@ describe('Code Interpreter Workflow (E2E)', () => { headers }); - const contexts = await listResponse.json(); - const contextIds = contexts.map((ctx: any) => ctx.id); + const contexts = (await listResponse.json()) as CodeContext[]; + const contextIds = contexts.map((ctx) => ctx.id); expect(contextIds).not.toContain(contextId); }, 120000); @@ -173,7 +174,7 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(execResponse.status).toBe(200); - const execution = await execResponse.json(); + const execution = (await execResponse.json()) as ExecutionResult; expect(execution.code).toBe('print("Hello from Python!")'); expect(execution.logs.stdout.join('')).toContain('Hello from Python!'); @@ -204,7 +205,7 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(exec1Response.status).toBe(200); - const execution1 = await exec1Response.json(); + const execution1 = (await exec1Response.json()) as ExecutionResult; expect(execution1.error).toBeUndefined(); // Use variable in second execution @@ -218,7 +219,7 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(exec2Response.status).toBe(200); - const execution2 = await exec2Response.json(); + const execution2 = (await exec2Response.json()) as ExecutionResult; expect(execution2.logs.stdout.join('')).toContain('52'); expect(execution2.error).toBeUndefined(); }, 120000); @@ -247,9 +248,11 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(execResponse.status).toBe(200); - const execution = await execResponse.json(); + const execution = (await execResponse.json()) as ExecutionResult; expect(execution.error).toBeDefined(); + if (!execution.error) throw new Error('Expected error to be defined'); + expect(execution.error.name).toContain('Error'); expect(execution.error.message || execution.error.traceback).toContain( 'division' @@ -284,7 +287,7 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(execResponse.status).toBe(200); - const execution = await execResponse.json(); + const execution = (await execResponse.json()) as ExecutionResult; expect(execution.logs.stdout.join('')).toContain('Hello from JavaScript!'); expect(execution.error).toBeUndefined(); @@ -326,7 +329,7 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(exec2Response.status).toBe(200); - const execution2 = await exec2Response.json(); + const execution2 = (await exec2Response.json()) as ExecutionResult; expect(execution2.logs.stdout.join('')).toContain('1'); }, 120000); @@ -354,9 +357,11 @@ describe('Code Interpreter Workflow (E2E)', () => { }); expect(execResponse.status).toBe(200); - const execution = await execResponse.json(); + const execution = (await execResponse.json()) as ExecutionResult; expect(execution.error).toBeDefined(); + if (!execution.error) throw new Error('Expected error to be defined'); + expect(execution.error.name || execution.error.message).toMatch( /Error|undefined/i ); @@ -462,7 +467,7 @@ for i in range(3): } ); - const pythonCtx = await pythonCtxResponse.json(); + const pythonCtx = (await pythonCtxResponse.json()) as CodeContext; // Generate data in Python and save to file const pythonExecResponse = await fetch(`${workerUrl}/api/code/execute`, { @@ -481,7 +486,7 @@ print("Data saved") }); expect(pythonExecResponse.status).toBe(200); - const pythonExec = await pythonExecResponse.json(); + const pythonExec = (await pythonExecResponse.json()) as ExecutionResult; expect(pythonExec.error).toBeUndefined(); expect(pythonExec.logs.stdout.join('')).toContain('Data saved'); @@ -492,7 +497,7 @@ print("Data saved") body: JSON.stringify({ language: 'javascript' }) }); - const jsCtx = await jsCtxResponse.json(); + const jsCtx = (await jsCtxResponse.json()) as CodeContext; // Read and process data in JavaScript const jsExecResponse = await fetch(`${workerUrl}/api/code/execute`, { @@ -510,7 +515,7 @@ console.log('Sum:', sum); }); expect(jsExecResponse.status).toBe(200); - const jsExec = await jsExecResponse.json(); + const jsExec = (await jsExecResponse.json()) as ExecutionResult; expect(jsExec.error).toBeUndefined(); expect(jsExec.logs.stdout.join('')).toContain('Sum: 15'); }, 120000); @@ -563,10 +568,12 @@ console.log('Sum:', sum); }); expect(exec2Response.status).toBe(200); - const execution2 = await exec2Response.json(); + const execution2 = (await exec2Response.json()) as ExecutionResult; // Should have error about undefined variable expect(execution2.error).toBeDefined(); + if (!execution2.error) throw new Error('Expected error to be defined'); + expect(execution2.error.name || execution2.error.message).toMatch( /NameError|not defined/i ); @@ -589,8 +596,8 @@ console.log('Sum:', sum); // Should return error expect(ctxResponse.status).toBeGreaterThanOrEqual(400); - const errorData = await ctxResponse.json(); - expect(errorData.error || errorData.message).toBeTruthy(); + const errorData = (await ctxResponse.json()) as ErrorResponse; + expect(errorData.error).toBeTruthy(); }, 120000); test('should return error for non-existent context', async () => { @@ -611,8 +618,8 @@ console.log('Sum:', sum); // Should return error expect(execResponse.status).toBeGreaterThanOrEqual(400); - const errorData = await execResponse.json(); - expect(errorData.error || errorData.message).toBeTruthy(); + const errorData = (await execResponse.json()) as ErrorResponse; + expect(errorData.error).toBeTruthy(); }, 120000); test('should return error when deleting non-existent context', async () => { @@ -637,7 +644,7 @@ console.log('Sum:', sum); // Should return error expect(deleteResponse.status).toBeGreaterThanOrEqual(400); - const errorData = await deleteResponse.json(); - expect(errorData.error || errorData.message).toBeTruthy(); + const errorData = (await deleteResponse.json()) as ErrorResponse; + expect(errorData.error).toBeTruthy(); }, 120000); }); diff --git a/tests/e2e/environment-workflow.test.ts b/tests/e2e/environment-workflow.test.ts index 6edecff8..d902b2a1 100644 --- a/tests/e2e/environment-workflow.test.ts +++ b/tests/e2e/environment-workflow.test.ts @@ -13,6 +13,13 @@ import { createTestHeaders, cleanupSandbox } from './helpers/test-fixtures'; +import type { + EnvSetResult, + ExecResult, + WriteFileResult, + Process, + ProcessLogsResult +} from '@repo/shared'; describe('Environment Variables Workflow', () => { describe('local', () => { @@ -55,7 +62,7 @@ describe('Environment Variables Workflow', () => { }); expect(setEnvResponse.status).toBe(200); - const setEnvData = await setEnvResponse.json(); + const setEnvData = (await setEnvResponse.json()) as EnvSetResult; expect(setEnvData.success).toBe(true); // Step 2: Verify environment variable with echo command (same sandbox) @@ -68,7 +75,7 @@ describe('Environment Variables Workflow', () => { }); expect(execResponse.status).toBe(200); - const execData = await execResponse.json(); + const execData = (await execResponse.json()) as ExecResult; expect(execData.success).toBe(true); expect(execData.stdout.trim()).toBe('hello_world'); }, 90000); @@ -91,7 +98,7 @@ describe('Environment Variables Workflow', () => { }); expect(setEnvResponse.status).toBe(200); - const setEnvData = await setEnvResponse.json(); + const setEnvData = (await setEnvResponse.json()) as EnvSetResult; expect(setEnvData.success).toBe(true); // Step 2: Verify all environment variables (same sandbox) @@ -104,7 +111,7 @@ describe('Environment Variables Workflow', () => { }); expect(execResponse.status).toBe(200); - const execData = await execResponse.json(); + const execData = (await execResponse.json()) as ExecResult; expect(execData.success).toBe(true); expect(execData.stdout.trim()).toBe('secret123|localhost|3000'); }, 90000); @@ -134,7 +141,7 @@ describe('Environment Variables Workflow', () => { }); expect(exec1Response.status).toBe(200); - const exec1Data = await exec1Response.json(); + const exec1Data = (await exec1Response.json()) as ExecResult; expect(exec1Data.success).toBe(true); expect(exec1Data.stdout.trim()).toBe('still_here'); @@ -148,7 +155,7 @@ describe('Environment Variables Workflow', () => { }); expect(exec2Response.status).toBe(200); - const exec2Data = await exec2Response.json(); + const exec2Data = (await exec2Response.json()) as ExecResult; expect(exec2Data.success).toBe(true); expect(exec2Data.stdout.trim()).toBe('still_here'); @@ -162,7 +169,7 @@ describe('Environment Variables Workflow', () => { }); expect(exec3Response.status).toBe(200); - const exec3Data = await exec3Response.json(); + const exec3Data = (await exec3Response.json()) as ExecResult; expect(exec3Data.success).toBe(true); expect(exec3Data.stdout.trim()).toBe('still_here'); }, 90000); @@ -214,7 +221,7 @@ describe('Environment Variables Workflow', () => { }); expect(startResponse.status).toBe(200); - const startData = await startResponse.json(); + const startData = (await startResponse.json()) as Process; expect(startData.id).toBeTruthy(); const processId = startData.id; @@ -230,7 +237,7 @@ describe('Environment Variables Workflow', () => { ); expect(logsResponse.status).toBe(200); - const logsData = await logsResponse.json(); + const logsData = (await logsResponse.json()) as ProcessLogsResult; expect(logsData.stdout).toContain('ENV_VALUE=from_env'); // Cleanup (same sandbox) @@ -257,7 +264,7 @@ describe('Environment Variables Workflow', () => { }); expect(catResponse.status).toBe(200); - const catData = await catResponse.json(); + const catData = (await catResponse.json()) as ExecResult; // cat with no input should exit with code 0 and produce no output expect(catData.success).toBe(true); expect(catData.stdout).toBe(''); @@ -272,7 +279,7 @@ describe('Environment Variables Workflow', () => { }); expect(readResponse.status).toBe(200); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ExecResult; expect(readData.success).toBe(true); expect(readData.stdout).toContain('read returned'); @@ -286,7 +293,7 @@ describe('Environment Variables Workflow', () => { }); expect(grepResponse.status).toBe(200); - const grepData = await grepResponse.json(); + const grepData = (await grepResponse.json()) as ExecResult; expect(grepData.success).toBe(true); }, 90000); }); diff --git a/tests/e2e/file-operations-workflow.test.ts b/tests/e2e/file-operations-workflow.test.ts index 73b7dc53..2cb8cdff 100644 --- a/tests/e2e/file-operations-workflow.test.ts +++ b/tests/e2e/file-operations-workflow.test.ts @@ -20,7 +20,17 @@ import { test, vi } from 'vitest'; -import type { FileInfo } from '@repo/shared'; +import type { + FileInfo, + WriteFileResult, + ReadFileResult, + DeleteFileResult, + MkdirResult, + ListFilesResult, + ExecResult, + FileExistsResult +} from '@repo/shared'; +import type { ErrorResponse } from './test-worker/types'; import { getTestWorkerUrl, WranglerDevRunner } from './helpers/wrangler-runner'; import { createSandboxId, @@ -69,7 +79,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const mkdirData = await mkdirResponse.json(); + const mkdirData = (await mkdirResponse.json()) as MkdirResult; expect(mkdirData.success).toBe(true); // Verify directory exists by listing it @@ -81,7 +91,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const lsData = await lsResponse.json(); + const lsData = (await lsResponse.json()) as ReadFileResult; expect(lsResponse.status).toBe(200); expect(lsData.success).toBe(true); // Directory should exist (ls succeeds) @@ -123,7 +133,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ReadFileResult; expect(readResponse.status).toBe(200); expect(readData.content).toContain('debug'); expect(readData.content).toContain('3000'); @@ -174,7 +184,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const readNewData = await readNewResponse.json(); + const readNewData = (await readNewResponse.json()) as ReadFileResult; expect(readNewResponse.status).toBe(200); expect(readNewData.content).toContain('Project Documentation'); @@ -246,7 +256,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const readDestData = await readDestResponse.json(); + const readDestData = (await readDestResponse.json()) as ReadFileResult; expect(readDestResponse.status).toBe(200); expect(readDestData.content).toContain('test'); @@ -347,7 +357,7 @@ describe('File Operations Workflow (E2E)', () => { // Should return error expect(deleteResponse.status).toBe(500); - const deleteData = await deleteResponse.json(); + const deleteData = (await deleteResponse.json()) as ErrorResponse; expect(deleteData.error).toContain('Cannot delete directory'); expect(deleteData.error).toContain('deleteFile()'); @@ -360,7 +370,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const lsData = await lsResponse.json(); + const lsData = (await lsResponse.json()) as ReadFileResult; expect(lsResponse.status).toBe(200); expect(lsData.success).toBe(true); // Directory should still exist @@ -417,7 +427,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const deleteData = await deleteResponse.json(); + const deleteData = (await deleteResponse.json()) as DeleteFileResult; expect(deleteResponse.status).toBe(200); expect(deleteData.success).toBe(true); @@ -430,7 +440,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const lsData = await lsResponse.json(); + const lsData = (await lsResponse.json()) as ReadFileResult; // ls should fail or show empty result expect(lsData.success).toBe(false); }, 90000); @@ -542,7 +552,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const packageData = await readPackageResponse.json(); + const packageData = (await readPackageResponse.json()) as ReadFileResult; expect(readPackageResponse.status).toBe(200); expect(packageData.content).toContain('myapp'); @@ -554,7 +564,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const configData = await readConfigResponse.json(); + const configData = (await readConfigResponse.json()) as ReadFileResult; expect(readConfigResponse.status).toBe(200); expect(configData.content).toContain('development'); @@ -566,7 +576,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const indexData = await readIndexResponse.json(); + const indexData = (await readIndexResponse.json()) as ReadFileResult; expect(readIndexResponse.status).toBe(200); expect(indexData.content).toContain('Hello World'); @@ -579,7 +589,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const deleteData = await deleteResponse.json(); + const deleteData = (await deleteResponse.json()) as DeleteFileResult; expect(deleteResponse.status).toBe(200); expect(deleteData.success).toBe(true); @@ -592,7 +602,7 @@ describe('File Operations Workflow (E2E)', () => { }) }); - const lsData = await lsResponse.json(); + const lsData = (await lsResponse.json()) as ReadFileResult; expect(lsData.success).toBe(false); // Directory should not exist }, 90000); @@ -615,7 +625,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(writeResponse.status).toBe(200); - const writeData = await writeResponse.json(); + const writeData = (await writeResponse.json()) as WriteFileResult; expect(writeData.success).toBe(true); // Verify we can read it back @@ -628,7 +638,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(readResponse.status).toBe(200); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ReadFileResult; expect(readData.content).toBe('Users control their sandbox!'); }, 90000); @@ -656,7 +666,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(readResponse.status).toBe(200); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ReadFileResult; expect(readData.success).toBe(true); expect(readData.content).toBe('Hello, World! This is a test.'); @@ -693,7 +703,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(readResponse.status).toBe(200); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ReadFileResult; expect(readData.success).toBe(true); expect(readData.encoding).toBe('base64'); @@ -732,7 +742,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(readResponse.status).toBe(200); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ReadFileResult; expect(readData.success).toBe(true); expect(readData.content).toBe(jsonContent); @@ -837,7 +847,7 @@ describe('File Operations Workflow (E2E)', () => { // Should return error with FILE_NOT_FOUND expect(deleteResponse.status).toBe(500); - const errorData = await deleteResponse.json(); + const errorData = (await deleteResponse.json()) as ErrorResponse; expect(errorData.error).toBeTruthy(); expect(errorData.error).toMatch(/not found|does not exist|no such file/i); }, 90000); @@ -884,7 +894,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(listResponse.status).toBe(200); - const listData = await listResponse.json(); + const listData = (await listResponse.json()) as ListFilesResult; expect(listData.success).toBe(true); expect(listData.path).toBe('/workspace/project'); @@ -896,6 +906,8 @@ describe('File Operations Workflow (E2E)', () => { (f: FileInfo) => f.name === 'data.txt' ); expect(dataFile).toBeDefined(); + if (!dataFile) throw new Error('dataFile not found'); + expect(dataFile.type).toBe('file'); expect(dataFile.absolutePath).toBe('/workspace/project/data.txt'); expect(dataFile.relativePath).toBe('data.txt'); @@ -910,6 +922,8 @@ describe('File Operations Workflow (E2E)', () => { (f: FileInfo) => f.name === 'script.sh' ); expect(scriptFile).toBeDefined(); + if (!scriptFile) throw new Error('scriptFile not found'); + expect(scriptFile.permissions.executable).toBe(true); }, 90000); @@ -955,7 +969,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(listResponse.status).toBe(200); - const listData = await listResponse.json(); + const listData = (await listResponse.json()) as ListFilesResult; expect(listData.success).toBe(true); @@ -985,7 +999,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(notFoundResponse.status).toBe(500); - const notFoundData = await notFoundResponse.json(); + const notFoundData = (await notFoundResponse.json()) as ErrorResponse; expect(notFoundData.error).toBeTruthy(); expect(notFoundData.error).toMatch(/not found|does not exist/i); @@ -1008,7 +1022,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(wrongTypeResponse.status).toBe(500); - const wrongTypeData = await wrongTypeResponse.json(); + const wrongTypeData = (await wrongTypeResponse.json()) as ErrorResponse; expect(wrongTypeData.error).toMatch(/not a directory/i); }, 90000); @@ -1045,7 +1059,8 @@ describe('File Operations Workflow (E2E)', () => { }); expect(fileExistsResponse.status).toBe(200); - const fileExistsData = await fileExistsResponse.json(); + const fileExistsData = + (await fileExistsResponse.json()) as FileExistsResult; expect(fileExistsData.success).toBe(true); expect(fileExistsData.exists).toBe(true); @@ -1059,7 +1074,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(dirExistsResponse.status).toBe(200); - const dirExistsData = await dirExistsResponse.json(); + const dirExistsData = (await dirExistsResponse.json()) as FileExistsResult; expect(dirExistsData.success).toBe(true); expect(dirExistsData.exists).toBe(true); @@ -1073,7 +1088,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(notExistsResponse.status).toBe(200); - const notExistsData = await notExistsResponse.json(); + const notExistsData = (await notExistsResponse.json()) as FileExistsResult; expect(notExistsData.success).toBe(true); expect(notExistsData.exists).toBe(false); }, 90000); @@ -1131,7 +1146,7 @@ describe('File Operations Workflow (E2E)', () => { }); expect(listResponse.status).toBe(200); - const listData = await listResponse.json(); + const listData = (await listResponse.json()) as ListFilesResult; expect(listData.success).toBe(true); expect(listData.files).toBeInstanceOf(Array); @@ -1169,7 +1184,8 @@ describe('File Operations Workflow (E2E)', () => { }); expect(listWithHiddenResponse.status).toBe(200); - const listWithHiddenData = await listWithHiddenResponse.json(); + const listWithHiddenData = + (await listWithHiddenResponse.json()) as ListFilesResult; expect(listWithHiddenData.success).toBe(true); expect(listWithHiddenData.files.length).toBe(4); // visible1.txt, visible2.txt, bar/, .hiddenfile.txt diff --git a/tests/e2e/fixtures/websocket-echo-server.ts b/tests/e2e/fixtures/websocket-echo-server.ts index 818165ab..d48eceb5 100644 --- a/tests/e2e/fixtures/websocket-echo-server.ts +++ b/tests/e2e/fixtures/websocket-echo-server.ts @@ -7,11 +7,13 @@ * Usage: bun run websocket-echo-server.ts */ +import type { Server, ServerWebSocket } from 'bun'; + const port = parseInt(process.argv[2] || '8080', 10); -Bun.serve({ +Bun.serve({ port, - fetch(req, server) { + fetch(req: Request, server: Server) { // Upgrade HTTP request to WebSocket if (server.upgrade(req)) { return; // Successfully upgraded @@ -19,14 +21,14 @@ Bun.serve({ return new Response('Expected WebSocket', { status: 400 }); }, websocket: { - message(ws, message) { + message(ws: ServerWebSocket, message: string | Buffer) { // Echo the message back ws.send(message); }, - open(ws) { + open(ws: ServerWebSocket) { console.log('WebSocket client connected'); }, - close(ws) { + close(ws: ServerWebSocket) { console.log('WebSocket client disconnected'); } } diff --git a/tests/e2e/git-clone-workflow.test.ts b/tests/e2e/git-clone-workflow.test.ts index f703a5e1..9fcafb0c 100644 --- a/tests/e2e/git-clone-workflow.test.ts +++ b/tests/e2e/git-clone-workflow.test.ts @@ -13,6 +13,12 @@ import { createTestHeaders, cleanupSandbox } from './helpers/test-fixtures'; +import type { + GitCheckoutResult, + ReadFileResult, + ExecResult +} from '@repo/shared'; +import type { ErrorResponse } from './test-worker/types'; /** * Git Clone Workflow Integration Tests @@ -75,7 +81,7 @@ describe('Git Clone Workflow', () => { }); expect(cloneResponse.status).toBe(200); - const cloneData = await cloneResponse.json(); + const cloneData = (await cloneResponse.json()) as GitCheckoutResult; expect(cloneData.success).toBe(true); // Verify the repository was cloned by checking for README @@ -88,7 +94,7 @@ describe('Git Clone Workflow', () => { }); expect(fileCheckResponse.status).toBe(200); - const fileData = await fileCheckResponse.json(); + const fileData = (await fileCheckResponse.json()) as ReadFileResult; expect(fileData.content).toBeTruthy(); expect(fileData.content).toContain('Hello'); // Verify README content }); @@ -109,7 +115,7 @@ describe('Git Clone Workflow', () => { }); expect(cloneResponse.status).toBe(200); - const cloneData = await cloneResponse.json(); + const cloneData = (await cloneResponse.json()) as GitCheckoutResult; expect(cloneData.success).toBe(true); // Verify we're on the correct branch by checking git branch @@ -122,7 +128,7 @@ describe('Git Clone Workflow', () => { }); expect(branchCheckResponse.status).toBe(200); - const branchData = await branchCheckResponse.json(); + const branchData = (await branchCheckResponse.json()) as ExecResult; expect(branchData.stdout.trim()).toBe('master'); }); @@ -152,7 +158,7 @@ describe('Git Clone Workflow', () => { }); expect(listResponse.status).toBe(200); - const listData = await listResponse.json(); + const listData = (await listResponse.json()) as ExecResult; expect(listData.exitCode).toBe(0); expect(listData.stdout).toContain('README'); // Verify README exists expect(listData.stdout).toContain('.git'); // Verify .git directory exists @@ -167,7 +173,7 @@ describe('Git Clone Workflow', () => { }); expect(readmeResponse.status).toBe(200); - const readmeData = await readmeResponse.json(); + const readmeData = (await readmeResponse.json()) as ReadFileResult; expect(readmeData.content).toBeTruthy(); // Step 4: Run a git command to verify repo is functional @@ -180,7 +186,7 @@ describe('Git Clone Workflow', () => { }); expect(gitLogResponse.status).toBe(200); - const gitLogData = await gitLogResponse.json(); + const gitLogData = (await gitLogResponse.json()) as ExecResult; expect(gitLogData.exitCode).toBe(0); expect(gitLogData.stdout).toBeTruthy(); // Should have at least one commit }); @@ -199,7 +205,7 @@ describe('Git Clone Workflow', () => { }); expect(cloneResponse.status).toBe(200); - const cloneData = await cloneResponse.json(); + const cloneData = (await cloneResponse.json()) as GitCheckoutResult; expect(cloneData.success).toBe(true); // The SDK should extract 'Hello-World' from the URL and clone there @@ -213,7 +219,7 @@ describe('Git Clone Workflow', () => { }); expect(dirCheckResponse.status).toBe(200); - const dirData = await dirCheckResponse.json(); + const dirData = (await dirCheckResponse.json()) as ExecResult; expect(dirData.stdout).toContain('Hello-World'); }); @@ -233,7 +239,7 @@ describe('Git Clone Workflow', () => { // Git clone should fail with appropriate error expect(cloneResponse.status).toBe(500); - const errorData = await cloneResponse.json(); + const errorData = (await cloneResponse.json()) as ErrorResponse; expect(errorData.error).toBeTruthy(); // Should mention repository not found or doesn't exist expect(errorData.error).toMatch( @@ -258,7 +264,7 @@ describe('Git Clone Workflow', () => { // Should fail with authentication error expect(cloneResponse.status).toBe(500); - const errorData = await cloneResponse.json(); + const errorData = (await cloneResponse.json()) as ErrorResponse; expect(errorData.error).toBeTruthy(); // Should mention authentication, permission, or access denied expect(errorData.error).toMatch( @@ -304,7 +310,7 @@ describe('Git Clone Workflow', () => { }); expect(listResponse.status).toBe(200); - const listData = await listResponse.json(); + const listData = (await listResponse.json()) as ExecResult; expect(listData.stdout).toContain('README'); // From cloned repo expect(listData.stdout).toContain('test-marker.txt'); // Our new file }); diff --git a/tests/e2e/keepalive-workflow.test.ts b/tests/e2e/keepalive-workflow.test.ts index 6234f689..5a3ae80e 100644 --- a/tests/e2e/keepalive-workflow.test.ts +++ b/tests/e2e/keepalive-workflow.test.ts @@ -13,6 +13,7 @@ import { createTestHeaders, cleanupSandbox } from './helpers/test-fixtures'; +import type { Process, ExecResult, ReadFileResult } from '@repo/shared'; /** * KeepAlive Workflow Integration Tests @@ -75,7 +76,7 @@ describe('KeepAlive Workflow', () => { }); expect(initResponse.status).toBe(200); - const initData = await initResponse.json(); + const initData = (await initResponse.json()) as ExecResult; expect(initData.stdout).toContain('Container initialized with keepAlive'); // Step 2: Wait longer than normal activity timeout would allow (15 seconds) @@ -92,7 +93,7 @@ describe('KeepAlive Workflow', () => { }); expect(verifyResponse.status).toBe(200); - const verifyData = await verifyResponse.json(); + const verifyData = (await verifyResponse.json()) as ExecResult; expect(verifyData.stdout).toContain('Still alive after timeout period'); }, 120000); @@ -115,8 +116,8 @@ describe('KeepAlive Workflow', () => { }); expect(startResponse.status).toBe(200); - const startData = await startResponse.json(); - expect(startData.status).toBe('running'); + const startData = (await startResponse.json()) as Process; + expect(startData.id).toBeTruthy(); const processId = startData.id; // Wait 20 seconds (longer than normal activity timeout) @@ -132,7 +133,7 @@ describe('KeepAlive Workflow', () => { ); expect(statusResponse.status).toBe(200); - const statusData = await statusResponse.json(); + const statusData = (await statusResponse.json()) as Process; expect(statusData.status).toBe('running'); // Cleanup - kill the process @@ -220,7 +221,7 @@ describe('KeepAlive Workflow', () => { }); expect(response.status).toBe(200); - const data = await response.json(); + const data = (await response.json()) as ExecResult; expect(data.stdout).toContain(`Command ${i}`); } }, 120000); @@ -269,7 +270,7 @@ describe('KeepAlive Workflow', () => { }); expect(readResponse.status).toBe(200); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ReadFileResult; expect(readData.content).toContain('Updated content after keepAlive'); }, 120000); }); diff --git a/tests/e2e/process-lifecycle-workflow.test.ts b/tests/e2e/process-lifecycle-workflow.test.ts index f0d3e134..866660f0 100644 --- a/tests/e2e/process-lifecycle-workflow.test.ts +++ b/tests/e2e/process-lifecycle-workflow.test.ts @@ -1,18 +1,16 @@ -import { - describe, - test, - expect, - beforeAll, - afterAll, - afterEach, - vi -} from 'vitest'; +import { describe, test, expect, beforeAll, afterAll, afterEach } from 'vitest'; import { getTestWorkerUrl, WranglerDevRunner } from './helpers/wrangler-runner'; import { createSandboxId, createTestHeaders, cleanupSandbox } from './helpers/test-fixtures'; +import type { + Process, + ProcessLogsResult, + ProcessStatus, + PortExposeResult +} from '@repo/shared'; // Port exposure tests require custom domain with wildcard DNS routing // Skip these tests when running against workers.dev deployment (no wildcard support) @@ -80,9 +78,8 @@ describe('Process Lifecycle Workflow', () => { }); expect(startResponse.status).toBe(200); - const startData = await startResponse.json(); + const startData = (await startResponse.json()) as Process; expect(startData.id).toBeTruthy(); - expect(startData.status).toBe('running'); const processId = startData.id; // Wait a bit for the process to start @@ -98,7 +95,7 @@ describe('Process Lifecycle Workflow', () => { ); expect(statusResponse.status).toBe(200); - const statusData = await statusResponse.json(); + const statusData = (await statusResponse.json()) as Process; expect(statusData.id).toBe(processId); expect(statusData.status).toBe('running'); @@ -127,7 +124,7 @@ describe('Process Lifecycle Workflow', () => { }) }); - const process1Data = await process1Response.json(); + const process1Data = (await process1Response.json()) as Process; const process1Id = process1Data.id; const process2Response = await fetch(`${workerUrl}/api/process/start`, { @@ -138,7 +135,7 @@ describe('Process Lifecycle Workflow', () => { }) }); - const process2Data = await process2Response.json(); + const process2Data = (await process2Response.json()) as Process; const process2Id = process2Data.id; // Wait a bit for processes to be registered @@ -151,7 +148,7 @@ describe('Process Lifecycle Workflow', () => { }); expect(listResponse.status).toBe(200); - const listData = await listResponse.json(); + const listData = (await listResponse.json()) as Process[]; // Debug logging console.log('[DEBUG] List response:', JSON.stringify(listData, null, 2)); @@ -190,7 +187,7 @@ describe('Process Lifecycle Workflow', () => { }) }); - const startData = await startResponse.json(); + const startData = (await startResponse.json()) as Process; const processId = startData.id; // Immediately run a foreground command - should complete quickly @@ -241,7 +238,7 @@ describe('Process Lifecycle Workflow', () => { }) }); - const startData = await startResponse.json(); + const startData = (await startResponse.json()) as Process; const processId = startData.id; // Wait for process to complete @@ -257,7 +254,7 @@ describe('Process Lifecycle Workflow', () => { ); expect(logsResponse.status).toBe(200); - const logsData = await logsResponse.json(); + const logsData = (await logsResponse.json()) as ProcessLogsResult; expect(logsData.stdout).toContain('Hello from process'); }, 90000); @@ -292,7 +289,7 @@ console.log("Line 3"); }) }); - const startData = await startResponse.json(); + const startData = (await startResponse.json()) as Process; const processId = startData.id; // Stream logs (SSE) @@ -395,7 +392,7 @@ console.log("Server started on port 8080"); }) }); - const startData = await startResponse.json(); + const startData = (await startResponse.json()) as Process; const processId = startData.id; // Wait for server to start @@ -412,9 +409,9 @@ console.log("Server started on port 8080"); }); expect(exposeResponse.status).toBe(200); - const exposeData = await exposeResponse.json(); + const exposeData = (await exposeResponse.json()) as PortExposeResult; expect(exposeData.url).toBeTruthy(); - const previewUrl = exposeData.url; + const previewUrl = exposeData.url!; // Make HTTP request to preview URL const healthResponse = await fetch(previewUrl); @@ -450,7 +447,7 @@ console.log("Server started on port 8080"); }) }); - const data = await startResponse.json(); + const data = (await startResponse.json()) as Process; processes.push(data.id); } @@ -462,7 +459,7 @@ console.log("Server started on port 8080"); method: 'GET', headers }); - const listData = await listResponse.json(); + const listData = (await listResponse.json()) as Process[]; expect(listData.length).toBeGreaterThanOrEqual(3); // Kill all processes @@ -481,7 +478,7 @@ console.log("Server started on port 8080"); method: 'GET', headers }); - const listAfterData = await listAfterResponse.json(); + const listAfterData = (await listAfterResponse.json()) as Process[]; // Should have fewer running processes now const runningProcesses = listAfterData.filter( @@ -535,7 +532,7 @@ console.log("Server listening on port 8080"); }); expect(startResponse.status).toBe(200); - const startData = await startResponse.json(); + const startData = (await startResponse.json()) as Process; const processId = startData.id; // Step 3: Wait and verify process is running @@ -550,7 +547,7 @@ console.log("Server listening on port 8080"); ); expect(statusResponse.status).toBe(200); - const statusData = await statusResponse.json(); + const statusData = (await statusResponse.json()) as Process; expect(statusData.status).toBe('running'); // Step 4: Expose port @@ -564,7 +561,7 @@ console.log("Server listening on port 8080"); }); expect(exposeResponse.status).toBe(200); - const exposeData = await exposeResponse.json(); + const exposeData = (await exposeResponse.json()) as PortExposeResult; const previewUrl = exposeData.url; // Step 5: Make HTTP request to health endpoint @@ -585,7 +582,7 @@ console.log("Server listening on port 8080"); ); expect(logsResponse.status).toBe(200); - const logsData = await logsResponse.json(); + const logsData = (await logsResponse.json()) as ProcessLogsResult; expect(logsData.stdout).toContain('Server listening on port 8080'); // Step 7: Cleanup - unexpose port and kill the process @@ -688,5 +685,141 @@ console.log("Server listening on port 8080"); }, 90000 ); + + test('should retrieve completed process metadata', async () => { + const sandboxId = createSandboxId(); + const headers = createTestHeaders(sandboxId); + + // Start a short-lived process that completes quickly + const startResponse = await fetch(`${workerUrl}/api/process/start`, { + method: 'POST', + headers, + body: JSON.stringify({ + command: 'echo "test output"' + }) + }); + + expect(startResponse.status).toBe(200); + const startData = (await startResponse.json()) as Process; + const processId = startData.id; + + // Poll until process completes (pattern developers would use) + let processData; + const maxAttempts = 60; // 30 seconds with 500ms intervals + for (let attempt = 0; attempt < maxAttempts; attempt++) { + const getResponse = await fetch( + `${workerUrl}/api/process/${processId}`, + { + method: 'GET', + headers + } + ); + + expect(getResponse.status).toBe(200); + processData = (await getResponse.json()) as Process; + + if (processData.status === 'completed') { + break; + } + + if (attempt < maxAttempts - 1) { + await new Promise((resolve) => setTimeout(resolve, 500)); + } + } + + // Verify completed status + if (!processData) { + throw new Error('Process never completed within timeout'); + } + expect(processData.id).toBe(processId); + expect(processData.status).toBe('completed'); + expect(processData.exitCode).toBe(0); + expect(processData.endTime).toBeTruthy(); + }, 90000); + + test('should include completed processes in list', async () => { + const sandboxId = createSandboxId(); + const headers = createTestHeaders(sandboxId); + + // Start a process that completes quickly + const completedResponse = await fetch(`${workerUrl}/api/process/start`, { + method: 'POST', + headers, + body: JSON.stringify({ + command: 'echo "completed process"' + }) + }); + + const completedData = (await completedResponse.json()) as Process; + const completedId = completedData.id; + + // Poll until first process completes (pattern developers would use) + const maxAttempts = 60; // 30 seconds with 500ms intervals + for (let attempt = 0; attempt < maxAttempts; attempt++) { + const getResponse = await fetch( + `${workerUrl}/api/process/${completedId}`, + { + method: 'GET', + headers + } + ); + + expect(getResponse.status).toBe(200); + const data = (await getResponse.json()) as Process; + + if (data.status === 'completed') { + break; + } + + if (attempt < maxAttempts - 1) { + await new Promise((resolve) => setTimeout(resolve, 500)); + } + } + + // Start a long-running process + const runningResponse = await fetch(`${workerUrl}/api/process/start`, { + method: 'POST', + headers, + body: JSON.stringify({ + command: 'sleep 60' + }) + }); + + const runningData = (await runningResponse.json()) as Process; + const runningId = runningData.id; + + // Wait for running process to start + await new Promise((resolve) => setTimeout(resolve, 500)); + + // List all processes + const listResponse = await fetch(`${workerUrl}/api/process/list`, { + method: 'GET', + headers + }); + + expect(listResponse.status).toBe(200); + const listData = (await listResponse.json()) as Process[]; + + // Should include both completed and running processes + const processIds = listData.map((p: any) => p.id); + expect(processIds).toContain(completedId); + expect(processIds).toContain(runningId); + + // Verify statuses + const completedProcess = listData.find((p) => p.id === completedId); + const runningProcess = listData.find((p) => p.id === runningId); + + if (!completedProcess) throw new Error('Completed process not found'); + if (!runningProcess) throw new Error('Running process not found'); + + expect(completedProcess.status).toBe('completed'); + expect(runningProcess.status).toBe('running'); + + // Cleanup - kill running process + await fetch(`${workerUrl}/api/process/${runningId}`, { + method: 'DELETE', + headers + }); + }, 90000); }); }); diff --git a/tests/e2e/session-state-isolation-workflow.test.ts b/tests/e2e/session-state-isolation-workflow.test.ts index e021f571..f42d80a9 100644 --- a/tests/e2e/session-state-isolation-workflow.test.ts +++ b/tests/e2e/session-state-isolation-workflow.test.ts @@ -13,6 +13,15 @@ import { createTestHeaders, cleanupSandbox } from './helpers/test-fixtures'; +import type { + Process, + SessionCreateResult, + SessionDeleteResult, + ReadFileResult, + WriteFileResult, + ExecResult, + ProcessInfoResult +} from '@repo/shared'; /** * Session State Isolation Workflow Integration Tests @@ -75,7 +84,8 @@ describe('Session State Isolation Workflow', () => { }); expect(session1Response.status).toBe(200); - const session1Data = await session1Response.json(); + const session1Data = + (await session1Response.json()) as SessionCreateResult; expect(session1Data.success).toBe(true); expect(session1Data.sessionId).toBeTruthy(); const session1Id = session1Data.sessionId; @@ -94,7 +104,8 @@ describe('Session State Isolation Workflow', () => { }); expect(session2Response.status).toBe(200); - const session2Data = await session2Response.json(); + const session2Data = + (await session2Response.json()) as SessionCreateResult; expect(session2Data.sessionId).toBeTruthy(); const session2Id = session2Data.sessionId; @@ -108,7 +119,7 @@ describe('Session State Isolation Workflow', () => { }); expect(exec1Response.status).toBe(200); - const exec1Data = await exec1Response.json(); + const exec1Data = (await exec1Response.json()) as ExecResult; expect(exec1Data.success).toBe(true); expect(exec1Data.stdout.trim()).toBe( 'production|prod-key-123|prod.example.com' @@ -124,7 +135,7 @@ describe('Session State Isolation Workflow', () => { }); expect(exec2Response.status).toBe(200); - const exec2Data = await exec2Response.json(); + const exec2Data = (await exec2Response.json()) as ExecResult; expect(exec2Data.success).toBe(true); expect(exec2Data.stdout.trim()).toBe( 'test|test-key-456|test.example.com' @@ -150,7 +161,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const check1Data = await check1Response.json(); + const check1Data = (await check1Response.json()) as ExecResult; expect(check1Data.stdout.trim()).toBe('session1-only'); // Verify NEW_VAR does NOT leak to session2 @@ -162,7 +173,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const check2Data = await check2Response.json(); + const check2Data = (await check2Response.json()) as ExecResult; expect(check2Data.stdout.trim()).toBe('VALUE::END'); // NEW_VAR should be empty }, 90000); @@ -215,7 +226,8 @@ describe('Session State Isolation Workflow', () => { }) }); - const session1Data = await session1Response.json(); + const session1Data = + (await session1Response.json()) as SessionCreateResult; const session1Id = session1Data.sessionId; // Create session2 with cwd: /workspace/test @@ -227,7 +239,8 @@ describe('Session State Isolation Workflow', () => { }) }); - const session2Data = await session2Response.json(); + const session2Data = + (await session2Response.json()) as SessionCreateResult; const session2Id = session2Data.sessionId; // Verify session1 starts in /workspace/app @@ -239,7 +252,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const pwd1Data = await pwd1Response.json(); + const pwd1Data = (await pwd1Response.json()) as ExecResult; expect(pwd1Data.stdout.trim()).toBe('/workspace/app'); // Verify session2 starts in /workspace/test @@ -251,7 +264,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const pwd2Data = await pwd2Response.json(); + const pwd2Data = (await pwd2Response.json()) as ExecResult; expect(pwd2Data.stdout.trim()).toBe('/workspace/test'); // Change directory in session1 @@ -281,7 +294,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const newPwd1Data = await newPwd1Response.json(); + const newPwd1Data = (await newPwd1Response.json()) as ExecResult; expect(newPwd1Data.stdout.trim()).toBe('/workspace/app/src'); // Verify session2 is in /workspace/test/unit @@ -293,7 +306,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const newPwd2Data = await newPwd2Response.json(); + const newPwd2Data = (await newPwd2Response.json()) as ExecResult; expect(newPwd2Data.stdout.trim()).toBe('/workspace/test/unit'); }, 90000); @@ -307,7 +320,8 @@ describe('Session State Isolation Workflow', () => { body: JSON.stringify({}) }); - const session1Data = await session1Response.json(); + const session1Data = + (await session1Response.json()) as SessionCreateResult; const session1Id = session1Data.sessionId; const session2Response = await fetch(`${workerUrl}/api/session/create`, { @@ -316,7 +330,8 @@ describe('Session State Isolation Workflow', () => { body: JSON.stringify({}) }); - const session2Data = await session2Response.json(); + const session2Data = + (await session2Response.json()) as SessionCreateResult; const session2Id = session2Data.sessionId; // Define greet() function in session1 @@ -339,7 +354,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const call1Data = await call1Response.json(); + const call1Data = (await call1Response.json()) as ExecResult; expect(call1Data.success).toBe(true); expect(call1Data.stdout.trim()).toBe('Hello from Production'); @@ -352,7 +367,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const call2Data = await call2Response.json(); + const call2Data = (await call2Response.json()) as ExecResult; expect(call2Data.success).toBe(false); // Function not found expect(call2Data.exitCode).not.toBe(0); @@ -374,7 +389,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const call3Data = await call3Response.json(); + const call3Data = (await call3Response.json()) as ExecResult; expect(call3Data.success).toBe(true); expect(call3Data.stdout.trim()).toBe('Hello from Test'); @@ -387,7 +402,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const call4Data = await call4Response.json(); + const call4Data = (await call4Response.json()) as ExecResult; expect(call4Data.stdout.trim()).toBe('Hello from Production'); }, 90000); @@ -401,7 +416,8 @@ describe('Session State Isolation Workflow', () => { body: JSON.stringify({}) }); - const session1Data = await session1Response.json(); + const session1Data = + (await session1Response.json()) as SessionCreateResult; const session1Id = session1Data.sessionId; const session2Response = await fetch(`${workerUrl}/api/session/create`, { @@ -410,7 +426,8 @@ describe('Session State Isolation Workflow', () => { body: JSON.stringify({}) }); - const session2Data = await session2Response.json(); + const session2Data = + (await session2Response.json()) as SessionCreateResult; const session2Id = session2Data.sessionId; // Start a long-running process in session1 @@ -423,7 +440,7 @@ describe('Session State Isolation Workflow', () => { }); expect(startResponse.status).toBe(200); - const startData = await startResponse.json(); + const startData = (await startResponse.json()) as Process; const processId = startData.id; // Wait for process to be registered @@ -436,12 +453,15 @@ describe('Session State Isolation Workflow', () => { }); expect(listResponse.status).toBe(200); - const processes = await listResponse.json(); + const listData = (await listResponse.json()) as Process[]; + const processes = listData; expect(Array.isArray(processes)).toBe(true); // Find our process in the list - const ourProcess = processes.find((p: any) => p.id === processId); + const ourProcess = processes.find((p) => p.id === processId); expect(ourProcess).toBeTruthy(); + if (!ourProcess) throw new Error('Process not found'); + expect(ourProcess.status).toBe('running'); // Kill the process from session2 - should work (shared process table) @@ -466,7 +486,7 @@ describe('Session State Isolation Workflow', () => { } ); - const verifyData = await verifyResponse.json(); + const verifyData = (await verifyResponse.json()) as Process; expect(verifyData.status).not.toBe('running'); }, 90000); @@ -480,7 +500,8 @@ describe('Session State Isolation Workflow', () => { body: JSON.stringify({}) }); - const session1Data = await session1Response.json(); + const session1Data = + (await session1Response.json()) as SessionCreateResult; const session1Id = session1Data.sessionId; const session2Response = await fetch(`${workerUrl}/api/session/create`, { @@ -489,7 +510,8 @@ describe('Session State Isolation Workflow', () => { body: JSON.stringify({}) }); - const session2Data = await session2Response.json(); + const session2Data = + (await session2Response.json()) as SessionCreateResult; const session2Id = session2Data.sessionId; // Write a file from session1 @@ -514,7 +536,7 @@ describe('Session State Isolation Workflow', () => { }); expect(readResponse.status).toBe(200); - const readData = await readResponse.json(); + const readData = (await readResponse.json()) as ReadFileResult; expect(readData.content).toBe('Written by session1'); // Modify the file from session2 @@ -538,7 +560,7 @@ describe('Session State Isolation Workflow', () => { }) }); - const verifyData = await verifyResponse.json(); + const verifyData = (await verifyResponse.json()) as ReadFileResult; expect(verifyData.content).toBe('Modified by session2'); // Cleanup @@ -563,7 +585,8 @@ describe('Session State Isolation Workflow', () => { }) }); - const session1Data = await session1Response.json(); + const session1Data = + (await session1Response.json()) as SessionCreateResult; const session1Id = session1Data.sessionId; const session2Response = await fetch(`${workerUrl}/api/session/create`, { @@ -574,7 +597,8 @@ describe('Session State Isolation Workflow', () => { }) }); - const session2Data = await session2Response.json(); + const session2Data = + (await session2Response.json()) as SessionCreateResult; const session2Id = session2Data.sessionId; // Execute commands simultaneously @@ -604,8 +628,8 @@ describe('Session State Isolation Workflow', () => { expect(exec1Response.status).toBe(200); expect(exec2Response.status).toBe(200); - const exec1Data = await exec1Response.json(); - const exec2Data = await exec2Response.json(); + const exec1Data = (await exec1Response.json()) as ExecResult; + const exec2Data = (await exec2Response.json()) as ExecResult; // Verify correct output (no mixing) expect(exec1Data.success).toBe(true); @@ -628,7 +652,7 @@ describe('Session State Isolation Workflow', () => { }); expect(sessionResponse.status).toBe(200); - const sessionData = await sessionResponse.json(); + const sessionData = (await sessionResponse.json()) as SessionCreateResult; const sessionId = sessionData.sessionId; // Verify session works before deletion @@ -641,7 +665,7 @@ describe('Session State Isolation Workflow', () => { }); expect(execBeforeResponse.status).toBe(200); - const execBeforeData = await execBeforeResponse.json(); + const execBeforeData = (await execBeforeResponse.json()) as ExecResult; expect(execBeforeData.stdout.trim()).toBe('test-value'); // Delete the session @@ -654,7 +678,7 @@ describe('Session State Isolation Workflow', () => { }); expect(deleteResponse.status).toBe(200); - const deleteData = await deleteResponse.json(); + const deleteData = (await deleteResponse.json()) as SessionDeleteResult; expect(deleteData.success).toBe(true); expect(deleteData.sessionId).toBe(sessionId); expect(deleteData.timestamp).toBeTruthy(); @@ -674,7 +698,8 @@ describe('Session State Isolation Workflow', () => { ); expect(useDeletedSessionResponse.status).toBe(200); - const recreatedSessionData = await useDeletedSessionResponse.json(); + const recreatedSessionData = + (await useDeletedSessionResponse.json()) as ExecResult; expect(recreatedSessionData.success).toBe(true); // Session state should be gone - SESSION_VAR should be empty (fresh session) expect(recreatedSessionData.stdout.trim()).toBe(''); @@ -692,7 +717,8 @@ describe('Session State Isolation Workflow', () => { ); expect(sandboxStillAliveResponse.status).toBe(200); - const sandboxAliveData = await sandboxStillAliveResponse.json(); + const sandboxAliveData = + (await sandboxStillAliveResponse.json()) as ExecResult; expect(sandboxAliveData.success).toBe(true); expect(sandboxAliveData.stdout.trim()).toBe('sandbox-alive'); }, 90000); diff --git a/tests/e2e/streaming-operations-workflow.test.ts b/tests/e2e/streaming-operations-workflow.test.ts index 34bfc978..1d9d0700 100644 --- a/tests/e2e/streaming-operations-workflow.test.ts +++ b/tests/e2e/streaming-operations-workflow.test.ts @@ -14,7 +14,7 @@ import { cleanupSandbox } from './helpers/test-fixtures'; import { parseSSEStream } from '../../packages/sandbox/src/sse-parser'; -import type { ExecEvent } from '@repo/shared'; +import type { ExecEvent, SessionCreateResult } from '@repo/shared'; /** * Streaming Operations Workflow Integration Tests @@ -424,7 +424,7 @@ describe('Streaming Operations Workflow', () => { }) }); - const sessionData = await sessionResponse.json(); + const sessionData = (await sessionResponse.json()) as SessionCreateResult; const sessionId = sessionData.sessionId; if (!sessionId) { throw new Error('Session ID not returned from API'); diff --git a/tests/e2e/test-worker/index.ts b/tests/e2e/test-worker/index.ts index b2e5f207..19f550c4 100644 --- a/tests/e2e/test-worker/index.ts +++ b/tests/e2e/test-worker/index.ts @@ -5,6 +5,19 @@ * Supports both default sessions (implicit) and explicit sessions via X-Session-Id header. */ import { Sandbox, getSandbox, proxyToSandbox } from '@cloudflare/sandbox'; +import type { + HealthResponse, + SessionCreateResponse, + SuccessResponse, + SuccessWithMessageResponse, + BucketPutResponse, + BucketGetResponse, + BucketDeleteResponse, + PortUnexposeResponse, + CodeContextDeleteResponse, + WebSocketInitResponse, + ErrorResponse +} from './types'; export { Sandbox }; interface Env { @@ -202,27 +215,25 @@ console.log('Terminal server on port ' + port); (r) => r.status === 'fulfilled' ).length; - return new Response( - JSON.stringify({ - success: failedCount === 0, - serversStarted: succeededCount, - serversFailed: failedCount, - errors: - failedCount > 0 - ? results - .filter((r) => r.status === 'rejected') - .map( - (r) => - (r as PromiseRejectedResult).reason?.message || - String((r as PromiseRejectedResult).reason) - ) - : undefined - }), - { - headers: { 'Content-Type': 'application/json' }, - status: failedCount > 0 ? 500 : 200 - } - ); + const response: WebSocketInitResponse = { + success: failedCount === 0, + serversStarted: succeededCount, + serversFailed: failedCount, + errors: + failedCount > 0 + ? results + .filter((r) => r.status === 'rejected') + .map( + (r) => + (r as PromiseRejectedResult).reason?.message || + String((r as PromiseRejectedResult).reason) + ) + : undefined + }; + return new Response(JSON.stringify(response), { + headers: { 'Content-Type': 'application/json' }, + status: failedCount > 0 ? 500 : 200 + }); } // WebSocket endpoints @@ -241,7 +252,8 @@ console.log('Terminal server on port ' + port); // Health check if (url.pathname === '/health') { - return new Response(JSON.stringify({ status: 'ok' }), { + const response: HealthResponse = { status: 'ok' }; + return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }); } @@ -250,12 +262,13 @@ console.log('Terminal server on port ' + port); if (url.pathname === '/api/session/create' && request.method === 'POST') { const session = await sandbox.createSession(body); // Note: We don't store the session - it will be retrieved fresh via getSession() on each request - return new Response( - JSON.stringify({ success: true, sessionId: session.id }), - { - headers: { 'Content-Type': 'application/json' } - } - ); + const response: SessionCreateResponse = { + success: true, + sessionId: session.id + }; + return new Response(JSON.stringify(response), { + headers: { 'Content-Type': 'application/json' } + }); } if (url.pathname === '/api/session/delete' && request.method === 'POST') { @@ -303,7 +316,8 @@ console.log('Terminal server on port ' + port); branch: body.branch, targetDir: body.targetDir }); - return new Response(JSON.stringify({ success: true }), { + const response: SuccessResponse = { success: true }; + return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }); } @@ -327,7 +341,8 @@ console.log('Terminal server on port ' + port); } await sandbox.mountBucket(body.bucket, body.mountPath, body.options); - return new Response(JSON.stringify({ success: true }), { + const response: SuccessResponse = { success: true }; + return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }); } @@ -339,7 +354,8 @@ console.log('Terminal server on port ' + port); ? { contentType: body.contentType } : undefined }); - return new Response(JSON.stringify({ success: true, key: body.key }), { + const response: BucketPutResponse = { success: true, key: body.key }; + return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }); } @@ -348,39 +364,39 @@ console.log('Terminal server on port ' + port); if (url.pathname === '/api/bucket/get' && request.method === 'GET') { const key = url.searchParams.get('key'); if (!key) { - return new Response( - JSON.stringify({ error: 'Key parameter required' }), - { - status: 400, - headers: { 'Content-Type': 'application/json' } - } - ); + const errorResponse: ErrorResponse = { + error: 'Key parameter required' + }; + return new Response(JSON.stringify(errorResponse), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); } const object = await env.TEST_BUCKET.get(key); if (!object) { - return new Response(JSON.stringify({ error: 'Object not found' }), { + const errorResponse: ErrorResponse = { error: 'Object not found' }; + return new Response(JSON.stringify(errorResponse), { status: 404, headers: { 'Content-Type': 'application/json' } }); } - return new Response( - JSON.stringify({ - success: true, - key, - content: await object.text(), - contentType: object.httpMetadata?.contentType, - size: object.size - }), - { - headers: { 'Content-Type': 'application/json' } - } - ); + const response: BucketGetResponse = { + success: true, + key, + content: await object.text(), + contentType: object.httpMetadata?.contentType, + size: object.size + }; + return new Response(JSON.stringify(response), { + headers: { 'Content-Type': 'application/json' } + }); } // R2 bucket delete if (url.pathname === '/api/bucket/delete' && request.method === 'POST') { await env.TEST_BUCKET.delete(body.key); - return new Response(JSON.stringify({ success: true, key: body.key }), { + const response: BucketDeleteResponse = { success: true, key: body.key }; + return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }); } @@ -678,12 +694,13 @@ console.log('Terminal server on port ' + port); // This is used by E2E tests to explicitly clean up after each test if (url.pathname === '/cleanup' && request.method === 'POST') { await sandbox.destroy(); - return new Response( - JSON.stringify({ success: true, message: 'Sandbox destroyed' }), - { - headers: { 'Content-Type': 'application/json' } - } - ); + const response: SuccessWithMessageResponse = { + success: true, + message: 'Sandbox destroyed' + }; + return new Response(JSON.stringify(response), { + headers: { 'Content-Type': 'application/json' } + }); } return new Response('Not found', { status: 404 }); diff --git a/tests/e2e/test-worker/types.ts b/tests/e2e/test-worker/types.ts new file mode 100644 index 00000000..f9490a7d --- /dev/null +++ b/tests/e2e/test-worker/types.ts @@ -0,0 +1,75 @@ +/** + * Type definitions for test worker endpoints + * + * These types define responses from test worker endpoints that wrap SDK functionality + * or provide test-specific features (health checks, R2 operations, WebSocket init). + * + * For SDK operations (exec, file ops, process management), the test worker passes through + * SDK types directly (ExecResult, ProcessStartResult, etc.) - those should be imported + * from @repo/shared in test files. + */ + +// Health check endpoint +export interface HealthResponse { + status: string; +} + +// Session management wrapper responses +export interface SessionCreateResponse { + success: boolean; + sessionId: string; +} + +// Simple success responses (used by multiple endpoints) +export interface SuccessResponse { + success: boolean; +} + +export interface SuccessWithMessageResponse { + success: boolean; + message: string; +} + +// R2 bucket operations +export interface BucketPutResponse { + success: boolean; + key: string; +} + +export interface BucketGetResponse { + success: boolean; + key: string; + content: string; + contentType?: string; + size: number; +} + +export interface BucketDeleteResponse { + success: boolean; + key: string; +} + +// Port unexpose response +export interface PortUnexposeResponse { + success: boolean; + port: number; +} + +// Code context delete response +export interface CodeContextDeleteResponse { + success: boolean; + contextId: string; +} + +// WebSocket init response +export interface WebSocketInitResponse { + success: boolean; + serversStarted: number; + serversFailed: number; + errors?: string[]; +} + +// Error responses +export interface ErrorResponse { + error: string; +} diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json new file mode 100644 index 00000000..9bd028f4 --- /dev/null +++ b/tests/e2e/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@repo/typescript-config/base.json", + "compilerOptions": { + "types": ["node", "vitest"], + "lib": ["ES2022"] + }, + "include": ["**/*.ts"], + "exclude": ["test-worker/**", "node_modules"] +} diff --git a/tests/e2e/websocket-workflow.test.ts b/tests/e2e/websocket-workflow.test.ts index 404e2661..811c19ec 100644 --- a/tests/e2e/websocket-workflow.test.ts +++ b/tests/e2e/websocket-workflow.test.ts @@ -16,6 +16,7 @@ import { createTestHeaders, cleanupSandbox } from './helpers/test-fixtures'; +import type { Process, PortExposeResult } from '@repo/shared'; // Port exposure tests require custom domain with wildcard DNS routing // Skip these tests when running against workers.dev deployment (no wildcard support) @@ -99,9 +100,9 @@ describe('WebSocket Workflow', () => { }); expect(startResponse.status).toBe(200); - const processData = await startResponse.json(); + const processData = (await startResponse.json()) as Process; const processId = processData.id; - expect(processData.status).toBe('running'); + expect(processData.id).toBeTruthy(); // Wait for server to be ready (generous timeout for first startup) await new Promise((resolve) => setTimeout(resolve, 2000)); @@ -117,7 +118,7 @@ describe('WebSocket Workflow', () => { }); expect(exposeResponse.status).toBe(200); - const exposeData = await exposeResponse.json(); + const exposeData = (await exposeResponse.json()) as PortExposeResult; expect(exposeData.url).toBeTruthy(); console.log('[DEBUG] Preview URL:', exposeData.url);