diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fc31bd114..790220bbba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,11 +55,11 @@ catalogs: specifier: ^7.0.2 version: 7.0.2 vite: - specifier: npm:@voidzero-dev/vite-plus-core@1.3.0 - version: 1.3.0 + specifier: npm:@voidzero-dev/vite-plus-core@1.4.0 + version: 1.4.0 vite-plus: - specifier: 1.3.0 - version: 1.3.0 + specifier: 1.4.0 + version: 1.4.0 vue: specifier: ^3.5.44 version: 3.5.44 @@ -71,7 +71,7 @@ catalogs: version: 4.6.4 overrides: - vite@*: npm:@voidzero-dev/vite-plus-core@1.3.0 + vite@*: npm:@voidzero-dev/vite-plus-core@1.4.0 vitest@*: 5.0.5 importers: @@ -89,7 +89,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) dogfood/ai/workbench: dependencies: @@ -120,22 +120,22 @@ importers: devDependencies: '@tailwindcss/vite': specifier: 'catalog:' - version: 4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 4.3.3(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) '@vitejs/plugin-react': specifier: ^6.0.3 - version: 6.1.2(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 6.1.2(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) laravel-vite-plugin: specifier: 'catalog:' - version: 3.2.0(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 3.2.0(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) tailwindcss: specifier: 'catalog:' version: 4.3.3 vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) src/foundation/resources/exceptions/renderer: dependencies: @@ -163,13 +163,13 @@ importers: devDependencies: '@tailwindcss/vite': specifier: 'catalog:' - version: 4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 4.3.3(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) src/horizon: devDependencies: @@ -178,7 +178,7 @@ importers: version: 2.11.8 '@vitejs/plugin-vue': specifier: 'catalog:' - version: 6.0.10(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2)) + version: 6.0.10(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2)) '@vue/compiler-dom': specifier: 'catalog:' version: 3.5.44 @@ -214,10 +214,10 @@ importers: version: 1.106.0 vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) vue: specifier: 'catalog:' version: 3.5.44(typescript@7.0.2) @@ -235,7 +235,7 @@ importers: version: 2.11.8 '@vitejs/plugin-vue': specifier: 'catalog:' - version: 6.0.10(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2)) + version: 6.0.10(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2)) '@vue/compiler-dom': specifier: 'catalog:' version: 3.5.44 @@ -274,10 +274,10 @@ importers: version: 15.9.0 vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) vue: specifier: 'catalog:' version: 3.5.44(typescript@7.0.2) @@ -301,7 +301,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) src/workbench: devDependencies: @@ -316,7 +316,7 @@ importers: version: 10.6.1(postcss@8.5.31) laravel-vite-plugin: specifier: 'catalog:' - version: 3.2.0(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 3.2.0(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) postcss: specifier: ^8.5.28 version: 8.5.31 @@ -325,10 +325,10 @@ importers: version: 3.4.19 vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) packages: @@ -470,131 +470,131 @@ packages: '@one-ini/wasm@0.2.1': resolution: {integrity: sha512-TUqERXGNTifZ9y2g3wPxQrw3HpHv/02DsW3D90T9x0hhonrL1ZqpSmNrU2XkoIq0fP1N6gZfVQzy2Fw1ZvGBNg==} - '@oxc-project/runtime@0.154.0': - resolution: {integrity: sha512-cBQNGoh659BNgvA8OC/e4BNDtFkv03dJaYdkp2adgOQNtISNAPQ+nB4S8XvWtNk7Xl+0g+7aa+iRGR25Xycd1Q==} + '@oxc-project/runtime@0.155.0': + resolution: {integrity: sha512-VbW9raczGS5ZZ1FMunUBYHe4uV/R684qlQCoMdE3n5JT1PcqQsvmFpMYkF8AD+W04fBLGAq6Tr7YsTk0jjvDBA==} engines: {node: ^20.19.0 || >=22.12.0} - '@oxc-project/types@0.154.0': - resolution: {integrity: sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==} + '@oxc-project/types@0.155.0': + resolution: {integrity: sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==} - '@oxfmt/binding-android-arm-eabi@0.73.0': - resolution: {integrity: sha512-dhunAdMbi/4o0Y66/+Wl6rpsNqpvE4i26Qw6bB77A97L2lJoktWg6m+X9QOon8iOEEqzD9rEZqFi5uZ4oIzhCA==} + '@oxfmt/binding-android-arm-eabi@0.74.0': + resolution: {integrity: sha512-xxQwrf5EPI5zxvuCTW8r6tzCTjPQ+h6+HqJ0iRKRy2zS1yBllt/xLVjoIyUc+FnR7g0a5g1SZv2yyDYhTItLbA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.73.0': - resolution: {integrity: sha512-dBoypjC2/xeyKyIhN2waX0/XeoccH9k0lVVe+sGRc4X8HSJVRtqyZDoXdr7bGY9A75c51dX0z0dLDpGQKwCdCg==} + '@oxfmt/binding-android-arm64@0.74.0': + resolution: {integrity: sha512-FJW3sATVvrWbV64al8xofCPZj4500Z/1W0ad7Tg1rcCUyh0EMbuSrsf3jsZS7CEbwQ5fQ0mRhmkc129GFgofSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.73.0': - resolution: {integrity: sha512-Tn7Y+qWIpCQK+qdYwBcn6YvqeBuPiuG3tcNlYg16a5QL2o3rSmrgBB1nCyYqo0uvc1LNrfQW9/CK2tzFuID/Fg==} + '@oxfmt/binding-darwin-arm64@0.74.0': + resolution: {integrity: sha512-TfmNKBD30dGyMu9GCLiaw2rt/z7fms9LS+5cKcNOyWst0QgAs8oZs6wPxMbVaaDVE0/PCdCosgWM1vbu6f5NUg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.73.0': - resolution: {integrity: sha512-yVb0E0tg/+9Xc9YJCBq66qF1DC/aN5mhb5nnHz0k4ikwkMANtSul2VPDSwjYdXR6/DFimc+pPUgLBqkIa5Ctuw==} + '@oxfmt/binding-darwin-x64@0.74.0': + resolution: {integrity: sha512-5Usf8+DfC4Q3o/MD8FkN47GrI6y0KbhQ4KhZ2HGzMZcXjcW1AeldwbUn51aKih+1TVuJFF9qkNm6X1nzm6NYng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.73.0': - resolution: {integrity: sha512-IKR4lFnBkYNL9Uqdxa5GMTQLvnLj+3sDwTvh2atHCzah0xeo+1xR5bigRU8uyXlOsA4VpXUn0UAimq4E4RBGmQ==} + '@oxfmt/binding-freebsd-x64@0.74.0': + resolution: {integrity: sha512-eL+1O3gykw1XihbZh2MRAciQvU2988LByrnjhyo/dmNG74X4oZ7XzigRoWPRFLfJCp8hzRkp/LbEhwe7uuT2eA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.73.0': - resolution: {integrity: sha512-7qnZ4g0ZCO3aZevOOQpZIABYwwXJW1lLDo2elviGdPQ0i11255xKEMFsi97fC1/nauQFseCJfELUy/k2JoOP+g==} + '@oxfmt/binding-linux-arm-gnueabihf@0.74.0': + resolution: {integrity: sha512-Xt0QtO2hJ4stnul8IL4LJGVLyp0xnc6ccN9fZ30yuubv8FWSRYxWwd1Za62KsEqAfmPSazoS/cLSUUJpja6npA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.73.0': - resolution: {integrity: sha512-36eBico2j/WPB84gb4FYd/jjqGCKOwU4M+TWLBUIqlnzTozQueLy0joA5BwsSp2OZFVkOlpCwvA3ndPamdiyLw==} + '@oxfmt/binding-linux-arm-musleabihf@0.74.0': + resolution: {integrity: sha512-/4wYQ0gZl4vUR0QoBTGaay73FmQ1GAjUa9z9101TR2xbGNrg4a+grazlR0yzEOe3/E+PJr5BGfj66MMzm/54vg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.73.0': - resolution: {integrity: sha512-VZ3cSgCAT18knKo52DcACmAuIoqG6qVEyeZRFbJd0xmYQ8GyYjuBc6IAh6MZHijT6q7p4/HPI+6ZWsCuhdV6iw==} + '@oxfmt/binding-linux-arm64-gnu@0.74.0': + resolution: {integrity: sha512-846bKzF+HJd+JkOFh3rxzmYvSq4Cm+GMT+p+mW7S13ZYo6GAAGv8bL8cAOi/XcdeMqxEJDCIMTVQLVBXHMCdig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.73.0': - resolution: {integrity: sha512-8YVzH59XSHcqwXNgc5qK851zCqwpmBgOifARXEPPe5/xTsIbHceYoPZAyqfpYV0CfKmzSaEDBgYHFza4rhx+Zw==} + '@oxfmt/binding-linux-arm64-musl@0.74.0': + resolution: {integrity: sha512-nI5FJwZ4Flo6CBdscpwxymZXxO3m3IErQJX55ZS6h+ObfGXu8FostL85L/RWPFaCLnIVE4/WfLw+2q8XvVEeUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.73.0': - resolution: {integrity: sha512-MqRJnmCys0rwOgpWyEidSA7v/bvOqfNDJhPkQoR9mPVYrvSmMaW2Xhdpl28cbTA+brOM1is62GjIvJplVlUVpw==} + '@oxfmt/binding-linux-ppc64-gnu@0.74.0': + resolution: {integrity: sha512-BoQYKuqUdsKAFAkK6DLBvkdv4+t0hppSm79sB0wIcE2o/eOwNj+6Z3OlKY4q3Eta/tlssP65FaS9vHn8TeF7vQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.73.0': - resolution: {integrity: sha512-4t2F3SWJSK/O/kRRFnL0elJzn75fJGGu71gs1HtiWcS80SmJOpkHiwwLnTp9suGdns8VHEPxYS0GTcyVwHKWBA==} + '@oxfmt/binding-linux-riscv64-gnu@0.74.0': + resolution: {integrity: sha512-FSroRrM12+jCdSQklF9aOxsKpI6wFs4ZCo/1cSYG80RWZS6z4LV+l9tXRMT8lDGu+yohIZBwi9M76FzGPFDE2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.73.0': - resolution: {integrity: sha512-D8b4I8I1kLdJQFwdFXNV+cwh3xWSiNKofjdGUk0nDpc0AHOds8T+poGbP9RWQMvgsvMb9OMgp/aV7ahBR37TbQ==} + '@oxfmt/binding-linux-riscv64-musl@0.74.0': + resolution: {integrity: sha512-0VLQD0dhGBLY9Ik/w+otMvD81D0YXi4VDwql65macV+O9UzmSWmUr6CKf+dMtysDeC0PnG3y6kBkRO08BJx52g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.73.0': - resolution: {integrity: sha512-QrC1S5VL1+N5rvorB15jH7CPvjVHA7GyUI5oVEd8Bd9m5605RDBQYTxDQ920DuylRoZOh2V7/xXpfCtFUrojUQ==} + '@oxfmt/binding-linux-s390x-gnu@0.74.0': + resolution: {integrity: sha512-6X7BbZNz34+OHM6Oqjf6TRcUyRn6aRcB4VtIDZAF/uQRyeVhg+G2hv6MlMvmnRekVSyTJ/8rG/u8W2dRFW6glg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.73.0': - resolution: {integrity: sha512-dFcUKQPltCEPa+YlkF3zqufn6nJPZdG73LpnjBvZFFsU3laCMssDXSYB99mVaj43FqdEgjpz5TO6gbYXcczd9g==} + '@oxfmt/binding-linux-x64-gnu@0.74.0': + resolution: {integrity: sha512-4kYIQK53yJXrxwxSzx8H2bojlf6/KezLGYLRXzV9QCCkoiCsvBTCid9Bg83dUPTLPa3g9K0730f6MKDbck1+OA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.73.0': - resolution: {integrity: sha512-LmQe+nV8e+eUwfPNCxDF+owbsg88toBiR/8rZRgVOeKQP3MaGefXZRvBoc1UG37iWEQ/9JlCXG9Mc4nf4N3agw==} + '@oxfmt/binding-linux-x64-musl@0.74.0': + resolution: {integrity: sha512-MfWz4wDREjp/JQDmuxtKieix4FHGa1z5rBvB3betdcPwZvf5fqeQrlCm1mmoyuUVQOh9Pt3M61HwN94kJCxw7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.73.0': - resolution: {integrity: sha512-8weOr1OKknE0G2DbR6ejJUT9AOsoBU6cXlsetmbihHv1K+bK7qP61T6lJxY0k/aAO7GyOJiRGNlL+04r4eH8Xg==} + '@oxfmt/binding-openharmony-arm64@0.74.0': + resolution: {integrity: sha512-6rmAApkq5s1kbDjvNI6Nl5A+JqQkLORb2sR941pf5HWsrd01WGKyagK/B4vX9+kh1FfvRkNgEu59FHvVk4H8cQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.73.0': - resolution: {integrity: sha512-mZV+QxXYgb8E18gzAFItb937VIsE4dqWdStrD3tRRC52UubOYEuU+e7NaIqUsGdB5YsJqNeuck3Ue/af2r9wyA==} + '@oxfmt/binding-win32-arm64-msvc@0.74.0': + resolution: {integrity: sha512-+5SpRUnZNGeXTyumHnpHa791tQtt04qZs076WT8eTcGF2OJ631VLnf7S0kQSPuDG+PtJM0phadEB2WAr5n1w9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.73.0': - resolution: {integrity: sha512-QJPUsbgUb97ZPPMkFUq6VQq0fLhC4Np19KL9aB/qKVwXnpKE6MIiILXqzhKnQ/UbnEDjKJ7Xv1d6zH4bYPCUdQ==} + '@oxfmt/binding-win32-ia32-msvc@0.74.0': + resolution: {integrity: sha512-1Fg+wYBO3Dsti1QX+/6EYHjeQzLC84tLOAqPu/r7ah3UJlUs9C/DB703Ay9Vcg+8VMzqZA2P5VGhzJraVUD4OQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.73.0': - resolution: {integrity: sha512-cGcze2fu+ykIaFtkEvZlkZ3HvnCLk+WISthtq99zWTWn0x13t5L9TxvGdLZMUMvtfYR+ICkiCwKnguVubdcw0w==} + '@oxfmt/binding-win32-x64-msvc@0.74.0': + resolution: {integrity: sha512-y9jXUgPBm2TZIWbQhMYRRYAQM57+r4fbykgc+j24ICIIZl+pcuoNhnq1CRGm3oFiHnEUnZO00qOtSPGQdJ2ziQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -629,130 +629,130 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.88.0': - resolution: {integrity: sha512-k6Fgkf86ubfeH4GvqYsRolfuVAp0pfYVZvifSlXNHPdMaWUOfxUrmLEeDD7hF6U2oJuhsVxhSWq86/xCJ+9MmA==} + '@oxlint/binding-android-arm-eabi@1.89.0': + resolution: {integrity: sha512-Pflpba5F3gRAQ12sWglRxvWTHEdAusNDvmi4Y5N1T5SY1AGkM7AixOnET8qkrnLJ/F32qAlNokO6eCknT8tmnw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.88.0': - resolution: {integrity: sha512-AsIyhl9o1ejwtac7NMbhKpMdSml6NWR0ljKjZERxC8OHYDAW8o/Om9n1QHVLi4VCEUo42Zte2/Op9kyoGpJZMg==} + '@oxlint/binding-android-arm64@1.89.0': + resolution: {integrity: sha512-dYmyD1x7LaPlb79vZWUcpTShk8dI9z1I+IubtqrBxWNJaDhQdPHwcsvg7q5StTU4xWyd9AwPfLaTYFZY8NZgtQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.88.0': - resolution: {integrity: sha512-qgBjB7eEKcCB4RqjXGuoSZq74g0wD09fWFJ+HWcCJcKaga8TeUHtb+BY0SsxdleSLBgx4RZ7qmgo0EzqEq7JoQ==} + '@oxlint/binding-darwin-arm64@1.89.0': + resolution: {integrity: sha512-Z/oxOPiIqiS7fItRdxaaOEC+6GD3OpGi0IxoR0dMefzQHolsKLzScw2DNcu/5gUPok3tv+G9mslvYB5bo3i0qg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.88.0': - resolution: {integrity: sha512-sCz3X5Mw/xdzEzn4fsCt1Iq8NKJnGNfhR+b9B5k+4DysYAHsmIhYoocnBPiQ9kvY3+pUun2A0Z8Vtun/Nv6YoQ==} + '@oxlint/binding-darwin-x64@1.89.0': + resolution: {integrity: sha512-eAjTkHSjAxrdCPJxO6y5sPQL5pYLKb2kvxZz9UU7tXnOZk9I/HLtkvx9S7GHY883k+AmL/eC7mqFt+w7WCwk0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.88.0': - resolution: {integrity: sha512-xTOdYOIqCufgIj0knvxu86FBfovP6QBiD7ngNva01gb8xPQCD5gXkE5APGczH35zStim1O3zqvFbLQmY85xXRg==} + '@oxlint/binding-freebsd-x64@1.89.0': + resolution: {integrity: sha512-0lNCA9Tl1n1FKpqy2/9ckp0kIuSrtJ/l9bMRgaEhOtM7TsOHshFuwOcvW8oA9teXLMOJT61LjEKWiaHDFGxF/w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.88.0': - resolution: {integrity: sha512-BLadtw88NdYJBrAtC1dYzrPfSP2d5zgx6iuZycdD0YmZBJ2Nqbit9Z4PSaIb6gxK25dweArGMPbOmq/0ugwUPg==} + '@oxlint/binding-linux-arm-gnueabihf@1.89.0': + resolution: {integrity: sha512-OTFEvi9W+JczaZD1Caj17SGCoW8H7TE+fvx0D7ATEWa1Lohn91UMxFQ6r145SiGy/yCxeIwptMys/RAM7gSVSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.88.0': - resolution: {integrity: sha512-wILSczl/DaZsCcf7j7tvNbssp8hxEHIJxrv4j7+JjI3TAL1U/JTWbeUtoJJC/vo8+2hkTmHLYoKJjmiNbijdXw==} + '@oxlint/binding-linux-arm-musleabihf@1.89.0': + resolution: {integrity: sha512-fRxgNHvBLswj6MrhW2uBq5UDhPRQlCbHeq/Nf8yuG9zOxhDsXqX/mi2PX1axgsQxMm88el7HLiJAzglAnflYxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.88.0': - resolution: {integrity: sha512-jutT2Hy8JCuZuMwIFHo2HtyxfOfBJHyQxlBHBK1SL9W2f8n/WhjAP0FnDMz9d/mjhv9+QA0mx5AdQ9fDjsrFFg==} + '@oxlint/binding-linux-arm64-gnu@1.89.0': + resolution: {integrity: sha512-v9ENzHvwrQoYSWTtviH1pOYc7R9UovDxVofnck50cNmPihRRTcrg5X0UlRbLOZ7IVtxPOlh2hb7kDO+FpE8SfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.88.0': - resolution: {integrity: sha512-8N8iGV9hh4Lkeuq7QsEkjoXNRRflqR1G2qyoGYXcA0k5qllrdqmfylKEXyHIVERxqNDyMO4StqDQ0uLxXcBQAg==} + '@oxlint/binding-linux-arm64-musl@1.89.0': + resolution: {integrity: sha512-vfffe8mQ484AzVadlUOFpgL+JDwhvs9hrzxTgQP1FVfi4ZEUUYeffXlzC9dFV7Dgnu5s/KrNKvfmG6HxBd/ISQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.88.0': - resolution: {integrity: sha512-5ydcQt3znNQ/rGEWYRvpS2cK9+T18P2VYgnyJ+eloEAwi9YUmsXy5VGrRqNWIK6qzWwHGnEE3ojVqqQNo+f0hA==} + '@oxlint/binding-linux-ppc64-gnu@1.89.0': + resolution: {integrity: sha512-Buonlp4hVS7RGNP5M7bPpGNQrenyzA2C64JcO/oz9C1c6dGOXJ7VEAnaiAMY7voou0JYyCJdvDVlFQc5DZX3DQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.88.0': - resolution: {integrity: sha512-iGx0uvFT54b/5DPJdEBCxYIrVB3v0uTy+JhRh3CyEp8/22BXDkG8rffkFwREdLsdj9mOrPbJYVOZw8ipcBuzjw==} + '@oxlint/binding-linux-riscv64-gnu@1.89.0': + resolution: {integrity: sha512-1eVwJJrJ3lYIPT+cm7BcBBZZXY6IQZP/oyonaruTor/MrVyWOpvnwt1oD5CLdsHg5aVBIxXodQSvt+1gK6ck5w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.88.0': - resolution: {integrity: sha512-Wt47mKMWelVjRjuR7BCFxXkB0m79K1sTEbqMHRWciKUgo5iv7sWsF4RXSOyFYHD9zerNrkRZblnN273rhwOA1Q==} + '@oxlint/binding-linux-riscv64-musl@1.89.0': + resolution: {integrity: sha512-fRQQsKUGShdEL5Fl9o9uhj3grkZbeXKLv0icQ5tspUCNYSDy8wzzEcP1WC2bKdTQ0rSszbKlO3iiHORxV22DLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.88.0': - resolution: {integrity: sha512-g3L12KCS4+1gtUt3RbJ4F+Yxx4LNmmZLEPfhd8rAHEJjJk2lAgNfnjGoP/phJyhG5WS6VbNYbJtadJelNfvTMA==} + '@oxlint/binding-linux-s390x-gnu@1.89.0': + resolution: {integrity: sha512-dEtYjIy6+fHcjKklsmtmMkZRhwHr2UbzjmpvuPtQYSeN9awQ2hx5JLms5Wvri+8P/NZtD8v5hfgHALUql+/Ycg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.88.0': - resolution: {integrity: sha512-og/XHVDOD9ZJgRiZkT9DgzQd4sr+JZW7e4l17Q/jVMZiIbeEd4uIu9iAoUbTF1gIvVZoRc4QDOxyVUvQGp4WHg==} + '@oxlint/binding-linux-x64-gnu@1.89.0': + resolution: {integrity: sha512-hqjwIj5KOYM8Wjef/p7IruNi1iByLhSzbMBk62y22P/E8q7Jj0Pp+DeUTKTRYyZYCDtU1wEGlnWOnttHbyNIww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.88.0': - resolution: {integrity: sha512-EmhBsp+xuKb6YbZNEMLBo80Xw71AZOIm18do8ni/GLX/Lw1XzNhVYpbxO1KasYtoAvrGtCOdCPULRlIE0bPbTQ==} + '@oxlint/binding-linux-x64-musl@1.89.0': + resolution: {integrity: sha512-iAQN3e6o9UgUCcdGwqRx/jYcKbG3HK7ur3rH6lTqVcQeNGZ85AeWIg6cgn/edwFQ0ofEZZItMwkzyCVTahnvEA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.88.0': - resolution: {integrity: sha512-DBUcx/KwYdg1DAz+G01P/GFv512J04EsZadbHptx2FTpWT/MWRg21ZToNHt9pnsfQRBQ3et/g+vqzbvrdAVbuA==} + '@oxlint/binding-openharmony-arm64@1.89.0': + resolution: {integrity: sha512-MNxjEByz3N/7Q4qFQuPWvc14TpN9LoD3pECEDsJTZbP/wyB7U+hduUXEl7QAbbgVezFwsb9sWtNUO6XVBndOhg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.88.0': - resolution: {integrity: sha512-xeVd0n4XG5HvynxfYvXi6uB+JvZbVZataowPS8cYpMbYg9mwE4sSyElFQd4LVJFifq8bRLDSR18XgEildmcXAg==} + '@oxlint/binding-win32-arm64-msvc@1.89.0': + resolution: {integrity: sha512-xxe+i9wfuFso/VnoL4mfj0UlzE+cfCFiUB06vkXCMyQdgVVmItF5FfAF4tATAktSU6uHruWR+FjqvAT9cHZZzg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.88.0': - resolution: {integrity: sha512-MA6iOw//amleaz2mVMsZrA7NHlEexjTrhIxSMFCXQi6IUuQO+3Od00GZhr2iCYw6RFzMW36jkKK/WHBXMsoQAw==} + '@oxlint/binding-win32-ia32-msvc@1.89.0': + resolution: {integrity: sha512-saFy07cO94E664glwgLmKLI40HgTL1PY86y4T6+ud4niX4H0Kdqjcmqmjps7mQNdBytV5guGbXDdyQtRfeLbLA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.88.0': - resolution: {integrity: sha512-3Sz6rVWe8Dy+ufsXurFN1GX2dX43F/lQMSLelK9gFjiba7nHvLlqlhR5cHlWVnCpSqUY1hN9pyCX9XToe6ve6g==} + '@oxlint/binding-win32-x64-msvc@1.89.0': + resolution: {integrity: sha512-hLFJ/o0f/dTJh/EW0vmcbREYSQO9tYgfiAAlsOkiLFiJuRRWvYtFELUXN5o2ijpSo/tlO1n+YlWN58tZwPG18A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/plugins@1.88.0': - resolution: {integrity: sha512-G2xbfWTrbqYRhR/vfeKNMQf/sS3/ti0iKJAAkRxHuXWZH/hne/dyIb9QKk5LuR6cJLARxsL6FMoAYmfspd74MQ==} + '@oxlint/plugins@1.89.0': + resolution: {integrity: sha512-TJBBNl4KIxbxrSk7gsqlz6iM1kxidfyTizEzfWqXvijF2Ou8/Da58GvjemBVmCmaUamwRij+JwsVc0kpBFuqfg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} '@parcel/watcher-android-arm64@2.6.0': @@ -1867,12 +1867,9 @@ packages: resolution: {integrity: sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==} peerDependencies: msw: ^2.4.9 || ^3.0.0 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true - vite: - optional: true '@vitest/pretty-format@5.0.5': resolution: {integrity: sha512-24sSdIlMA9DVPXnYiB9siwU0bTd887Y9jX3+vabztCSsvHwo7ipvJ/a2L3Rljw8+2rK1f5fCj3h5U9I/Jsqgbw==} @@ -1891,14 +1888,14 @@ packages: '@vitest/utils@5.0.5': resolution: {integrity: sha512-/rBu7gzLDQI/SC79Tn98b5DwOgaHU/Gt4I9OVeP3YofzNaYtbImRCOJQmT1S6vOJg9gEIlWC+r3QxKkjuSa1nQ==} - '@voidzero-dev/vite-plus-core@1.3.0': - resolution: {integrity: sha512-Bk/LBwNrxoB8XbGUtGJE3r+U9P4puvdZ6UH36ekygE1J+on/ytwM/yBgQwBnvmX6/MVax+2xxj8wk0N/KkyNeA==} + '@voidzero-dev/vite-plus-core@1.4.0': + resolution: {integrity: sha512-LTGXCdZ58qJKfuCOmf0hnZOVA64eOgzDQOqYpI+71tPxk/0cGqPQhYDGQQZoEvNLAn1G2Lo4gKadsQdWxb99WQ==} engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} peerDependencies: '@arethetypeswrong/core': ^0.18.1 '@types/node': ^20.19.0 || >=22.12.0 '@typescript/native-preview': '*' - '@vitejs/devtools': ^0.7.1 + '@vitejs/devtools': ^0.7.1 || ^0.8.0 '@volar/typescript': ~2.4.0 '@vue/language-core': ~3.2.0 || ~3.3.0 esbuild: ^0.27.0 || ^0.28.0 @@ -1960,54 +1957,54 @@ packages: yaml: optional: true - '@voidzero-dev/vite-plus-darwin-arm64@1.3.0': - resolution: {integrity: sha512-BF4W+UwGxUkHPrH2pZLhafJfkJT20WZIbMzZgZLGQ44LPpZPX8Sb+qtblUnXUkkCzTqad4XtSPgiLKglvZkNQQ==} + '@voidzero-dev/vite-plus-darwin-arm64@1.4.0': + resolution: {integrity: sha512-5Ie3vnrFrF5GFU+0VhbLuyoSmnzd8m42BvbLjncMOmAb6D1sEq4g37dlxPiVcJEqITM0t4zFnLkNSF4YAlk0FQ==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [darwin] - '@voidzero-dev/vite-plus-darwin-x64@1.3.0': - resolution: {integrity: sha512-vLZTk0r+s3e7mycj4SGBbcpK+xyM8a86hXQZtQbJyW7xrGJzsavISy7/m4gGZpWN32Hl4Tmx9V/ffeaoXLkRdg==} + '@voidzero-dev/vite-plus-darwin-x64@1.4.0': + resolution: {integrity: sha512-vaR7F+ix/91EycJYXOQ24Nzw470U12x3L+YLbbtuRwHDMW67fCbhXzcW+otZS8q4eRdIzEcA5svv9P8kcsaRkA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [darwin] - '@voidzero-dev/vite-plus-linux-arm64-gnu@1.3.0': - resolution: {integrity: sha512-H43Dj9RkEyjOFXxTk3DLTa6hqmHhxUN9vbbqNdwQoc3/TPNhC1zsLZy3qgUFbYGLdS7M9jICCqFzj5GDSYrhIw==} + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.4.0': + resolution: {integrity: sha512-58/PZrp+f3Hy6kFVKqbrZVenjo5t012rTOgRSgMoJyOhJjXBHqvORh9lB9GIip9r9maoWN31R1a44obPX9MD7g==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-arm64-musl@1.3.0': - resolution: {integrity: sha512-Se2GlvzUZOvFy+5hoQHoG7LFeiplKs+EJwaskayCSLwZAP+e6/LTK7EK+8OcdMpmdsLPtZoTJHkIjSzrQowJwQ==} + '@voidzero-dev/vite-plus-linux-arm64-musl@1.4.0': + resolution: {integrity: sha512-Ij08vPrxG1J23wQVm0LtLPrQevv4tos8TirP/jGTpQQmQAgYOpmIfMrx8BF+vOGMncOIo+kKacp4jPVmhctfhg==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-linux-x64-gnu@1.3.0': - resolution: {integrity: sha512-moxojk6zmog2SaLE4/cG75acE6X3cjR0/52Ba8OG2ct0r+dv55VSE0kellAuxS6o9VHy7TBhkIpwpFpQ34rnAA==} + '@voidzero-dev/vite-plus-linux-x64-gnu@1.4.0': + resolution: {integrity: sha512-RbM21Y5cSx5WesaFV/Swo22JRvimjO6NDItoL5wNI6NTa987ck7v19HBmp+V4nkOCFHbMdxwfhyALDw0y06xEA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-x64-musl@1.3.0': - resolution: {integrity: sha512-CykxH008Dy9u20Myki9/UIqq3Dbo1ZMqa098aS6AtWp3QKyEKzSLPB3jKDUklgmu83xiFe5BpPA+33oF0yNY7A==} + '@voidzero-dev/vite-plus-linux-x64-musl@1.4.0': + resolution: {integrity: sha512-Q+p+YJztEmqjJoumTmPaBQ/PEx3eFhiNTQTn5+85gkBUiRexkFQ8A86TMFxbKVgrJbiAOltkrJRmVRhRs5Q0sA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-win32-arm64-msvc@1.3.0': - resolution: {integrity: sha512-l61S9b8aeiDpi8wPRfz4HonqRkLpt/OVYs0sCskvlXYSW/L8qfLOtuWH/xpF8xDjMIfBefKejBVfU1UlRax5Tw==} + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.4.0': + resolution: {integrity: sha512-PLGVov8c4/QfveOM/AYjYDlgMCQ0SxCmJp392qBgxaSXP0nEg6yP4hn5UtnhxvluhHEzVkTf2r1Q3LNlZRI7kw==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [win32] - '@voidzero-dev/vite-plus-win32-x64-msvc@1.3.0': - resolution: {integrity: sha512-b6Ng8YezA6JA3adQBNA+8Mn26+OX4fw/tsPfnk//5Cx+1XFDZoAxWYXtGzc0yUQPEKnoGtfw/0OUvcibg61yYg==} + '@voidzero-dev/vite-plus-win32-x64-msvc@1.4.0': + resolution: {integrity: sha512-DYG7wDsMLLs2/+8qfeyLScdoMh+aPM4IZanoVPSNt8fMeeXR2GQiiZDKc0qZHKN9+YcdCGK1HLAdMAGSywKvsA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [win32] @@ -2904,8 +2901,8 @@ packages: oniguruma-to-es@4.3.6: resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} - oxfmt@0.73.0: - resolution: {integrity: sha512-Ke4xbpuzqIBP4bkd48q815RhqWNJqOBzxVA9199M4qcF0y0LtpUv7UTg1dJ5ea6jw0RBUDZvX3qOUMNmmxfxbw==} + oxfmt@0.74.0: + resolution: {integrity: sha512-G4OP7FgwGYZR5ttRpCOOfJdyRsSyvtn34vSXnjDq6Oqed5Dufg5mB44mank+51nAdHOKuTJ0lj0mksY7L+N61A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2924,8 +2921,8 @@ packages: resolution: {integrity: sha512-vQ9WDF3fiZJvRdSDosNOflerOCOpwol6vWzVDxW76RcU19cjTh7engvkHLvmv+tzedR8fAiJUVUGr6IVZdkzXQ==} hasBin: true - oxlint@1.88.0: - resolution: {integrity: sha512-uIcFgw8BmqvFRGzH5+r7d2zJMEexwNSrEmM6WQNnGh3VkO0XYTEFoyxyNE6tjKMUib9Cue4XJA2DoWruO5D6aw==} + oxlint@1.89.0: + resolution: {integrity: sha512-a25hkfo61okGPvJz432fRIPoxlKtNnOVy9w4rzrmGgNm4iVTPwpSQlmdqNWnp6p9QyyPVfAZRgnf5XvSBZFk/A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3352,8 +3349,8 @@ packages: vite-plugin-full-reload@1.2.0: resolution: {integrity: sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==} - vite-plus@1.3.0: - resolution: {integrity: sha512-wZGsDeYfPcrCegqgY68pJJHGTpEZdsZvIMH2/mWXkGkBD72UY3M7z24Sv3ZeOL/FrqbbtyqBOfkKO6BvoQ8wMw==} + vite-plus@1.4.0: + resolution: {integrity: sha512-bLyUKNFl0XjbHe20FpDM6OuknOb2E4ueoWKCW4n+wMO2+lQtA9tmAjW+Xs0Xe9rg2+tCgY3W13GOgDh2wjwjAQ==} engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} hasBin: true peerDependencies: @@ -3613,65 +3610,65 @@ snapshots: '@one-ini/wasm@0.2.1': {} - '@oxc-project/runtime@0.154.0': {} + '@oxc-project/runtime@0.155.0': {} - '@oxc-project/types@0.154.0': {} + '@oxc-project/types@0.155.0': {} - '@oxfmt/binding-android-arm-eabi@0.73.0': + '@oxfmt/binding-android-arm-eabi@0.74.0': optional: true - '@oxfmt/binding-android-arm64@0.73.0': + '@oxfmt/binding-android-arm64@0.74.0': optional: true - '@oxfmt/binding-darwin-arm64@0.73.0': + '@oxfmt/binding-darwin-arm64@0.74.0': optional: true - '@oxfmt/binding-darwin-x64@0.73.0': + '@oxfmt/binding-darwin-x64@0.74.0': optional: true - '@oxfmt/binding-freebsd-x64@0.73.0': + '@oxfmt/binding-freebsd-x64@0.74.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.73.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.74.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.73.0': + '@oxfmt/binding-linux-arm-musleabihf@0.74.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.73.0': + '@oxfmt/binding-linux-arm64-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.73.0': + '@oxfmt/binding-linux-arm64-musl@0.74.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.73.0': + '@oxfmt/binding-linux-ppc64-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.73.0': + '@oxfmt/binding-linux-riscv64-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.73.0': + '@oxfmt/binding-linux-riscv64-musl@0.74.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.73.0': + '@oxfmt/binding-linux-s390x-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.73.0': + '@oxfmt/binding-linux-x64-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.73.0': + '@oxfmt/binding-linux-x64-musl@0.74.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.73.0': + '@oxfmt/binding-openharmony-arm64@0.74.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.73.0': + '@oxfmt/binding-win32-arm64-msvc@0.74.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.73.0': + '@oxfmt/binding-win32-ia32-msvc@0.74.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.73.0': + '@oxfmt/binding-win32-x64-msvc@0.74.0': optional: true '@oxlint-tsgolint/darwin-arm64@7.0.2004': @@ -3692,64 +3689,64 @@ snapshots: '@oxlint-tsgolint/win32-x64@7.0.2004': optional: true - '@oxlint/binding-android-arm-eabi@1.88.0': + '@oxlint/binding-android-arm-eabi@1.89.0': optional: true - '@oxlint/binding-android-arm64@1.88.0': + '@oxlint/binding-android-arm64@1.89.0': optional: true - '@oxlint/binding-darwin-arm64@1.88.0': + '@oxlint/binding-darwin-arm64@1.89.0': optional: true - '@oxlint/binding-darwin-x64@1.88.0': + '@oxlint/binding-darwin-x64@1.89.0': optional: true - '@oxlint/binding-freebsd-x64@1.88.0': + '@oxlint/binding-freebsd-x64@1.89.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.88.0': + '@oxlint/binding-linux-arm-gnueabihf@1.89.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.88.0': + '@oxlint/binding-linux-arm-musleabihf@1.89.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.88.0': + '@oxlint/binding-linux-arm64-gnu@1.89.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.88.0': + '@oxlint/binding-linux-arm64-musl@1.89.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.88.0': + '@oxlint/binding-linux-ppc64-gnu@1.89.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.88.0': + '@oxlint/binding-linux-riscv64-gnu@1.89.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.88.0': + '@oxlint/binding-linux-riscv64-musl@1.89.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.88.0': + '@oxlint/binding-linux-s390x-gnu@1.89.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.88.0': + '@oxlint/binding-linux-x64-gnu@1.89.0': optional: true - '@oxlint/binding-linux-x64-musl@1.88.0': + '@oxlint/binding-linux-x64-musl@1.89.0': optional: true - '@oxlint/binding-openharmony-arm64@1.88.0': + '@oxlint/binding-openharmony-arm64@1.89.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.88.0': + '@oxlint/binding-win32-arm64-msvc@1.89.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.88.0': + '@oxlint/binding-win32-ia32-msvc@1.89.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.88.0': + '@oxlint/binding-win32-x64-msvc@1.89.0': optional: true - '@oxlint/plugins@1.88.0': {} + '@oxlint/plugins@1.89.0': {} '@parcel/watcher-android-arm64@2.6.0': optional: true @@ -4513,12 +4510,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': + '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' '@testing-library/dom@10.4.2': dependencies: @@ -4628,94 +4625,216 @@ snapshots: '@ungap/structured-clone@1.4.0': {} - '@vitejs/plugin-react@6.1.2(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': + '@vitejs/plugin-react@6.1.2(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' - '@vitejs/plugin-vue@6.0.10(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2))': + '@vitejs/plugin-vue@6.0.10(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vue: 3.5.44(typescript@7.0.2) - '@vitest/browser-preview@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5)': + '@vitest/browser-preview@5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5)': dependencies: '@testing-library/dom': 10.4.2 '@testing-library/user-event': 14.6.8(@testing-library/dom@10.4.2) - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + '@vitest/browser': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/browser-preview@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5)': + '@vitest/browser-preview@5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5)': dependencies: '@testing-library/dom': 10.4.2 '@testing-library/user-event': 14.6.8(@testing-library/dom@10.4.2) - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + '@vitest/browser': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/browser@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5)': + '@vitest/browser@5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/ui': 5.0.5(vitest@5.0.5) '@vitest/utils': 5.0.5 magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) ws: 8.22.0 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/browser@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5)': + '@vitest/browser@5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/ui': 5.0.5(vitest@5.0.5) '@vitest/utils': 5.0.5 magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) ws: 8.22.0 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/mocker@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': + '@vitest/mocker@5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.5 estree-walker: 3.0.3 magic-string: 1.4.3 - optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml - '@vitest/mocker@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': + '@vitest/mocker@5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.5 estree-walker: 3.0.3 magic-string: 1.4.3 - optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml '@vitest/pretty-format@5.0.5': dependencies: @@ -4738,7 +4857,7 @@ snapshots: pathe: 2.0.3 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/utils@5.0.5': dependencies: @@ -4746,74 +4865,74 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.2.0 - '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': + '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': dependencies: - '@oxc-project/runtime': 0.154.0 - '@oxc-project/types': 0.154.0 + '@oxc-project/runtime': 0.155.0 + '@oxc-project/types': 0.155.0 lightningcss: 1.33.0 postcss: 8.5.31 yuku-parser: 0.19.0 optionalDependencies: '@types/node': 22.20.5 - '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.4.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.4.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.4.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.4.0 fsevents: 2.3.3 jiti: 1.21.7 sass: 1.106.0 terser: 5.51.2 typescript: 7.0.2 - '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': + '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': dependencies: - '@oxc-project/runtime': 0.154.0 - '@oxc-project/types': 0.154.0 + '@oxc-project/runtime': 0.155.0 + '@oxc-project/types': 0.155.0 lightningcss: 1.33.0 postcss: 8.5.31 yuku-parser: 0.19.0 optionalDependencies: '@types/node': 22.20.5 - '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.4.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.4.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.4.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.4.0 fsevents: 2.3.3 jiti: 2.7.0 sass: 1.106.0 terser: 5.51.2 typescript: 7.0.2 - '@voidzero-dev/vite-plus-darwin-arm64@1.3.0': + '@voidzero-dev/vite-plus-darwin-arm64@1.4.0': optional: true - '@voidzero-dev/vite-plus-darwin-x64@1.3.0': + '@voidzero-dev/vite-plus-darwin-x64@1.4.0': optional: true - '@voidzero-dev/vite-plus-linux-arm64-gnu@1.3.0': + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.4.0': optional: true - '@voidzero-dev/vite-plus-linux-arm64-musl@1.3.0': + '@voidzero-dev/vite-plus-linux-arm64-musl@1.4.0': optional: true - '@voidzero-dev/vite-plus-linux-x64-gnu@1.3.0': + '@voidzero-dev/vite-plus-linux-x64-gnu@1.4.0': optional: true - '@voidzero-dev/vite-plus-linux-x64-musl@1.3.0': + '@voidzero-dev/vite-plus-linux-x64-musl@1.4.0': optional: true - '@voidzero-dev/vite-plus-win32-arm64-msvc@1.3.0': + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.4.0': optional: true - '@voidzero-dev/vite-plus-win32-x64-msvc@1.3.0': + '@voidzero-dev/vite-plus-win32-x64-msvc@1.4.0': optional: true '@vue/compiler-core@3.5.44': @@ -5394,18 +5513,18 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - laravel-vite-plugin@3.2.0(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + laravel-vite-plugin@3.2.0(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): dependencies: picocolors: 1.1.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plugin-full-reload: 1.2.0 - laravel-vite-plugin@3.2.0(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + laravel-vite-plugin@3.2.0(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): dependencies: picocolors: 1.1.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plugin-full-reload: 1.2.0 lightningcss-android-arm64@1.32.0: @@ -5634,55 +5753,55 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 - oxfmt@0.73.0(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + oxfmt@0.74.0(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): dependencies: tinypool: 2.2.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.73.0 - '@oxfmt/binding-android-arm64': 0.73.0 - '@oxfmt/binding-darwin-arm64': 0.73.0 - '@oxfmt/binding-darwin-x64': 0.73.0 - '@oxfmt/binding-freebsd-x64': 0.73.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.73.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.73.0 - '@oxfmt/binding-linux-arm64-gnu': 0.73.0 - '@oxfmt/binding-linux-arm64-musl': 0.73.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.73.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.73.0 - '@oxfmt/binding-linux-riscv64-musl': 0.73.0 - '@oxfmt/binding-linux-s390x-gnu': 0.73.0 - '@oxfmt/binding-linux-x64-gnu': 0.73.0 - '@oxfmt/binding-linux-x64-musl': 0.73.0 - '@oxfmt/binding-openharmony-arm64': 0.73.0 - '@oxfmt/binding-win32-arm64-msvc': 0.73.0 - '@oxfmt/binding-win32-ia32-msvc': 0.73.0 - '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) - - oxfmt@0.73.0(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + '@oxfmt/binding-android-arm-eabi': 0.74.0 + '@oxfmt/binding-android-arm64': 0.74.0 + '@oxfmt/binding-darwin-arm64': 0.74.0 + '@oxfmt/binding-darwin-x64': 0.74.0 + '@oxfmt/binding-freebsd-x64': 0.74.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.74.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.74.0 + '@oxfmt/binding-linux-arm64-gnu': 0.74.0 + '@oxfmt/binding-linux-arm64-musl': 0.74.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.74.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.74.0 + '@oxfmt/binding-linux-riscv64-musl': 0.74.0 + '@oxfmt/binding-linux-s390x-gnu': 0.74.0 + '@oxfmt/binding-linux-x64-gnu': 0.74.0 + '@oxfmt/binding-linux-x64-musl': 0.74.0 + '@oxfmt/binding-openharmony-arm64': 0.74.0 + '@oxfmt/binding-win32-arm64-msvc': 0.74.0 + '@oxfmt/binding-win32-ia32-msvc': 0.74.0 + '@oxfmt/binding-win32-x64-msvc': 0.74.0 + vite-plus: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + + oxfmt@0.74.0(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): dependencies: tinypool: 2.2.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.73.0 - '@oxfmt/binding-android-arm64': 0.73.0 - '@oxfmt/binding-darwin-arm64': 0.73.0 - '@oxfmt/binding-darwin-x64': 0.73.0 - '@oxfmt/binding-freebsd-x64': 0.73.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.73.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.73.0 - '@oxfmt/binding-linux-arm64-gnu': 0.73.0 - '@oxfmt/binding-linux-arm64-musl': 0.73.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.73.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.73.0 - '@oxfmt/binding-linux-riscv64-musl': 0.73.0 - '@oxfmt/binding-linux-s390x-gnu': 0.73.0 - '@oxfmt/binding-linux-x64-gnu': 0.73.0 - '@oxfmt/binding-linux-x64-musl': 0.73.0 - '@oxfmt/binding-openharmony-arm64': 0.73.0 - '@oxfmt/binding-win32-arm64-msvc': 0.73.0 - '@oxfmt/binding-win32-ia32-msvc': 0.73.0 - '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + '@oxfmt/binding-android-arm-eabi': 0.74.0 + '@oxfmt/binding-android-arm64': 0.74.0 + '@oxfmt/binding-darwin-arm64': 0.74.0 + '@oxfmt/binding-darwin-x64': 0.74.0 + '@oxfmt/binding-freebsd-x64': 0.74.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.74.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.74.0 + '@oxfmt/binding-linux-arm64-gnu': 0.74.0 + '@oxfmt/binding-linux-arm64-musl': 0.74.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.74.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.74.0 + '@oxfmt/binding-linux-riscv64-musl': 0.74.0 + '@oxfmt/binding-linux-s390x-gnu': 0.74.0 + '@oxfmt/binding-linux-x64-gnu': 0.74.0 + '@oxfmt/binding-linux-x64-musl': 0.74.0 + '@oxfmt/binding-openharmony-arm64': 0.74.0 + '@oxfmt/binding-win32-arm64-msvc': 0.74.0 + '@oxfmt/binding-win32-ia32-msvc': 0.74.0 + '@oxfmt/binding-win32-x64-msvc': 0.74.0 + vite-plus: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) oxlint-tsgolint@7.0.2004: optionalDependencies: @@ -5693,53 +5812,53 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 7.0.2004 '@oxlint-tsgolint/win32-x64': 7.0.2004 - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + oxlint@1.89.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.88.0 - '@oxlint/binding-android-arm64': 1.88.0 - '@oxlint/binding-darwin-arm64': 1.88.0 - '@oxlint/binding-darwin-x64': 1.88.0 - '@oxlint/binding-freebsd-x64': 1.88.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.88.0 - '@oxlint/binding-linux-arm-musleabihf': 1.88.0 - '@oxlint/binding-linux-arm64-gnu': 1.88.0 - '@oxlint/binding-linux-arm64-musl': 1.88.0 - '@oxlint/binding-linux-ppc64-gnu': 1.88.0 - '@oxlint/binding-linux-riscv64-gnu': 1.88.0 - '@oxlint/binding-linux-riscv64-musl': 1.88.0 - '@oxlint/binding-linux-s390x-gnu': 1.88.0 - '@oxlint/binding-linux-x64-gnu': 1.88.0 - '@oxlint/binding-linux-x64-musl': 1.88.0 - '@oxlint/binding-openharmony-arm64': 1.88.0 - '@oxlint/binding-win32-arm64-msvc': 1.88.0 - '@oxlint/binding-win32-ia32-msvc': 1.88.0 - '@oxlint/binding-win32-x64-msvc': 1.88.0 + '@oxlint/binding-android-arm-eabi': 1.89.0 + '@oxlint/binding-android-arm64': 1.89.0 + '@oxlint/binding-darwin-arm64': 1.89.0 + '@oxlint/binding-darwin-x64': 1.89.0 + '@oxlint/binding-freebsd-x64': 1.89.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.89.0 + '@oxlint/binding-linux-arm-musleabihf': 1.89.0 + '@oxlint/binding-linux-arm64-gnu': 1.89.0 + '@oxlint/binding-linux-arm64-musl': 1.89.0 + '@oxlint/binding-linux-ppc64-gnu': 1.89.0 + '@oxlint/binding-linux-riscv64-gnu': 1.89.0 + '@oxlint/binding-linux-riscv64-musl': 1.89.0 + '@oxlint/binding-linux-s390x-gnu': 1.89.0 + '@oxlint/binding-linux-x64-gnu': 1.89.0 + '@oxlint/binding-linux-x64-musl': 1.89.0 + '@oxlint/binding-openharmony-arm64': 1.89.0 + '@oxlint/binding-win32-arm64-msvc': 1.89.0 + '@oxlint/binding-win32-ia32-msvc': 1.89.0 + '@oxlint/binding-win32-x64-msvc': 1.89.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + vite-plus: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + oxlint@1.89.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.88.0 - '@oxlint/binding-android-arm64': 1.88.0 - '@oxlint/binding-darwin-arm64': 1.88.0 - '@oxlint/binding-darwin-x64': 1.88.0 - '@oxlint/binding-freebsd-x64': 1.88.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.88.0 - '@oxlint/binding-linux-arm-musleabihf': 1.88.0 - '@oxlint/binding-linux-arm64-gnu': 1.88.0 - '@oxlint/binding-linux-arm64-musl': 1.88.0 - '@oxlint/binding-linux-ppc64-gnu': 1.88.0 - '@oxlint/binding-linux-riscv64-gnu': 1.88.0 - '@oxlint/binding-linux-riscv64-musl': 1.88.0 - '@oxlint/binding-linux-s390x-gnu': 1.88.0 - '@oxlint/binding-linux-x64-gnu': 1.88.0 - '@oxlint/binding-linux-x64-musl': 1.88.0 - '@oxlint/binding-openharmony-arm64': 1.88.0 - '@oxlint/binding-win32-arm64-msvc': 1.88.0 - '@oxlint/binding-win32-ia32-msvc': 1.88.0 - '@oxlint/binding-win32-x64-msvc': 1.88.0 + '@oxlint/binding-android-arm-eabi': 1.89.0 + '@oxlint/binding-android-arm64': 1.89.0 + '@oxlint/binding-darwin-arm64': 1.89.0 + '@oxlint/binding-darwin-x64': 1.89.0 + '@oxlint/binding-freebsd-x64': 1.89.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.89.0 + '@oxlint/binding-linux-arm-musleabihf': 1.89.0 + '@oxlint/binding-linux-arm64-gnu': 1.89.0 + '@oxlint/binding-linux-arm64-musl': 1.89.0 + '@oxlint/binding-linux-ppc64-gnu': 1.89.0 + '@oxlint/binding-linux-riscv64-gnu': 1.89.0 + '@oxlint/binding-linux-riscv64-musl': 1.89.0 + '@oxlint/binding-linux-s390x-gnu': 1.89.0 + '@oxlint/binding-linux-x64-gnu': 1.89.0 + '@oxlint/binding-linux-x64-musl': 1.89.0 + '@oxlint/binding-openharmony-arm64': 1.89.0 + '@oxlint/binding-win32-arm64-msvc': 1.89.0 + '@oxlint/binding-win32-ia32-msvc': 1.89.0 + '@oxlint/binding-win32-x64-msvc': 1.89.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + vite-plus: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) parse5@8.0.1: dependencies: @@ -6199,31 +6318,32 @@ snapshots: picocolors: 1.1.1 picomatch: 2.3.2 - vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): + vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): dependencies: - '@oxc-project/types': 0.154.0 - '@oxlint/plugins': 1.88.0 - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@oxc-project/runtime': 0.155.0 + '@oxc-project/types': 0.155.0 + '@oxlint/plugins': 1.89.0 + '@vitest/browser': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/pretty-format': 5.0.5 '@vitest/snapshot': 5.0.5 '@vitest/spy': 5.0.5 '@vitest/utils': 5.0.5 - oxfmt: 0.73.0(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + oxfmt: 0.74.0(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + oxlint: 1.89.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) oxlint-tsgolint: 7.0.2004 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.4.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.4.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.4.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.4.0 transitivePeerDependencies: - '@arethetypeswrong/core' - '@astrojs/compiler-rs' @@ -6259,31 +6379,32 @@ snapshots: - vue-tsc - yaml - vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): + vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): dependencies: - '@oxc-project/types': 0.154.0 - '@oxlint/plugins': 1.88.0 - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@oxc-project/runtime': 0.155.0 + '@oxc-project/types': 0.155.0 + '@oxlint/plugins': 1.89.0 + '@vitest/browser': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/pretty-format': 5.0.5 '@vitest/snapshot': 5.0.5 '@vitest/spy': 5.0.5 '@vitest/utils': 5.0.5 - oxfmt: 0.73.0(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + oxfmt: 0.74.0(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + oxlint: 1.89.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) oxlint-tsgolint: 7.0.2004 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.4.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.4.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.4.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.4.0 transitivePeerDependencies: - '@arethetypeswrong/core' - '@astrojs/compiler-rs' @@ -6319,10 +6440,10 @@ snapshots: - vue-tsc - yaml - vitest@5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3): + vitest@5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -6333,21 +6454,41 @@ snapshots: tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 22.20.5 - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) '@vitest/ui': 5.0.5(vitest@5.0.5) happy-dom: 21.0.4 jsdom: 30.1.3 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml - vitest@5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3): + vitest@5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -6358,16 +6499,36 @@ snapshots: tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 22.20.5 - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) '@vitest/ui': 5.0.5(vitest@5.0.5) happy-dom: 21.0.4 jsdom: 30.1.3 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml vue-component-type-helpers@3.3.13: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9ad013edd8..ad8e8b9649 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,7 +14,7 @@ allowBuilds: # report a peer version mismatch despite using the bundled Vite runtime. # Update both core aliases with the vite-plus catalog pin; match Vitest to `vp toolchain vitest`. overrides: - 'vite@*': npm:@voidzero-dev/vite-plus-core@1.3.0 + 'vite@*': npm:@voidzero-dev/vite-plus-core@1.4.0 'vitest@*': 5.0.5 # Supply-chain defense: require packages to be at least 7 days old before @@ -39,8 +39,8 @@ catalog: sass: ^1.106.0 tailwindcss: ^4.3.3 typescript: ^7.0.2 - vite: npm:@voidzero-dev/vite-plus-core@1.3.0 - vite-plus: 1.3.0 + vite: npm:@voidzero-dev/vite-plus-core@1.4.0 + vite-plus: 1.4.0 vue: ^3.5.44 vue-json-pretty: ^2.6.0 vue-router: ^4.6.4 diff --git a/src/ai/src/Gateway/AzureOpenAi/AzureOpenAiGateway.php b/src/ai/src/Gateway/AzureOpenAi/AzureOpenAiGateway.php index 5045ff5d50..57c025d9eb 100644 --- a/src/ai/src/Gateway/AzureOpenAi/AzureOpenAiGateway.php +++ b/src/ai/src/Gateway/AzureOpenAi/AzureOpenAiGateway.php @@ -14,6 +14,7 @@ use Hypervel\Ai\Files\Image; use Hypervel\Ai\Gateway\AzureOpenAi\Concerns\CreatesAzureOpenAiClient; use Hypervel\Ai\Gateway\Concerns\HandlesFailoverErrors; +use Hypervel\Ai\Gateway\Concerns\ParsesIndexedEmbeddings; use Hypervel\Ai\Gateway\Concerns\ParsesServerSentEvents; use Hypervel\Ai\Gateway\OpenAi\Concerns\BuildsTextRequests; use Hypervel\Ai\Gateway\OpenAi\Concerns\HandlesTextGeneration; @@ -46,6 +47,7 @@ class AzureOpenAiGateway implements EmbeddingGateway, ImageGateway, StepTextGate use MapsAttachments; use MapsMessages; use MapsTools; + use ParsesIndexedEmbeddings; use ParsesServerSentEvents; use ParsesTextResponses; @@ -80,7 +82,7 @@ public function generateEmbeddings( $data = $response->json(); return new EmbeddingsResponse( - collect($data['data'] ?? [])->pluck('embedding')->all(), + $this->parseIndexedEmbeddings($data['data'] ?? [], count($inputs), $provider->name()), new Usage($data['usage']['prompt_tokens'] ?? 0), new Meta($provider->name(), $model), ); diff --git a/src/ai/src/Gateway/Concerns/ParsesIndexedEmbeddings.php b/src/ai/src/Gateway/Concerns/ParsesIndexedEmbeddings.php new file mode 100644 index 0000000000..b4ffb9f2e4 --- /dev/null +++ b/src/ai/src/Gateway/Concerns/ParsesIndexedEmbeddings.php @@ -0,0 +1,40 @@ + $records + * @return list + */ + protected function parseIndexedEmbeddings(array $records, int $expectedCount, string $providerName): array + { + if (count($records) !== $expectedCount) { + throw new AiException("{$providerName} Error: [invalid_response] The response did not contain the expected number of embeddings."); + } + + $ordered = array_fill(0, $expectedCount, null); + + foreach ($records as $position => $record) { + // Some compatible servers omit indices; retain their response order. + $index = is_array($record) ? ($record['index'] ?? $position) : null; + + if (! is_int($index) || $index < 0 || $index >= $expectedCount || $ordered[$index] !== null) { + throw new AiException("{$providerName} Error: [invalid_response] The response contained invalid embedding indices."); + } + + $ordered[$index] = $record; + } + + // Matching counts and unique in-range indices ensure every slot contains a record. + /** @var list> $ordered */ + return array_map(static fn (array $record): mixed => $record['embedding'] ?? null, $ordered); + } +} diff --git a/src/ai/src/Gateway/OpenAi/OpenAiGateway.php b/src/ai/src/Gateway/OpenAi/OpenAiGateway.php index 0dc1005763..ffb64b4e56 100644 --- a/src/ai/src/Gateway/OpenAi/OpenAiGateway.php +++ b/src/ai/src/Gateway/OpenAi/OpenAiGateway.php @@ -14,6 +14,7 @@ use Hypervel\Ai\Contracts\Providers\TranscriptionProvider; use Hypervel\Ai\Files\Image; use Hypervel\Ai\Gateway\Concerns\HandlesFailoverErrors; +use Hypervel\Ai\Gateway\Concerns\ParsesIndexedEmbeddings; use Hypervel\Ai\Gateway\Concerns\ParsesServerSentEvents; use Hypervel\Ai\Gateway\Concerns\ResolvesAudioFilenames; use Hypervel\Ai\Gateway\OpenAi\Concerns\BuildsTextRequests; @@ -52,6 +53,7 @@ class OpenAiGateway implements Gateway, StepTextGateway use MapsTools; use ParsesTextResponses; use HandlesFailoverErrors; + use ParsesIndexedEmbeddings; use ParsesServerSentEvents; use ResolvesAudioFilenames; @@ -287,7 +289,7 @@ public function generateEmbeddings( $data = $response->json(); return new EmbeddingsResponse( - collect($data['data'] ?? [])->pluck('embedding')->all(), + $this->parseIndexedEmbeddings($data['data'] ?? [], count($inputs), $provider->name()), new Usage($data['usage']['prompt_tokens'] ?? 0), new Meta($provider->name(), $model), ); diff --git a/src/ai/src/Gateway/OpenAiCompatible/OpenAiCompatibleGateway.php b/src/ai/src/Gateway/OpenAiCompatible/OpenAiCompatibleGateway.php index 1b0a1df06e..1802f28f1a 100644 --- a/src/ai/src/Gateway/OpenAiCompatible/OpenAiCompatibleGateway.php +++ b/src/ai/src/Gateway/OpenAiCompatible/OpenAiCompatibleGateway.php @@ -12,6 +12,7 @@ use Hypervel\Ai\Contracts\Providers\TranscriptionProvider; use Hypervel\Ai\Exceptions\AiException; use Hypervel\Ai\Gateway\Concerns\HandlesFailoverErrors; +use Hypervel\Ai\Gateway\Concerns\ParsesIndexedEmbeddings; use Hypervel\Ai\Gateway\Concerns\ParsesServerSentEvents; use Hypervel\Ai\Gateway\Concerns\ResolvesAudioFilenames; use Hypervel\Ai\Gateway\OpenAiCompatible\Concerns\BuildsTextRequests; @@ -31,7 +32,6 @@ use Hypervel\Ai\Responses\TranscriptionResponse; use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Support\Arr; -use Hypervel\Support\Collection; use InvalidArgumentException; class OpenAiCompatibleGateway implements EmbeddingGateway, StepTextGateway, TranscriptionGateway @@ -45,6 +45,7 @@ class OpenAiCompatibleGateway implements EmbeddingGateway, StepTextGateway, Tran use ParsesTextResponses; use PerformsChatCompletionSteps; use HandlesFailoverErrors; + use ParsesIndexedEmbeddings; use ParsesServerSentEvents; use ResolvesAudioFilenames; @@ -100,19 +101,13 @@ public function generateEmbeddings( */ protected function parseEmbeddings(array $data, int $expectedCount): array { - $embeddings = (new Collection($data['data'] ?? []))->pluck('embedding'); - - if ($embeddings->count() !== $expectedCount) { - throw new AiException('OpenAI-compatible Error: [invalid_response] The response did not contain the expected number of embeddings.'); - } - - return $embeddings->map(function (mixed $embedding): array { + return array_map(static function (mixed $embedding): array { if (! is_array($embedding) || $embedding === [] || array_filter($embedding, 'is_numeric') !== $embedding) { throw new AiException('OpenAI-compatible Error: [invalid_response] The response contained an invalid embedding vector.'); } return array_map(floatval(...), $embedding); - })->all(); + }, $this->parseIndexedEmbeddings($data['data'] ?? [], $expectedCount, 'OpenAI-compatible')); } /** diff --git a/src/ai/src/Gateway/OpenRouter/OpenRouterGateway.php b/src/ai/src/Gateway/OpenRouter/OpenRouterGateway.php index b9089d1e32..59037ec89e 100644 --- a/src/ai/src/Gateway/OpenRouter/OpenRouterGateway.php +++ b/src/ai/src/Gateway/OpenRouter/OpenRouterGateway.php @@ -16,6 +16,7 @@ use Hypervel\Ai\Contracts\Providers\SupportsWebSearch; use Hypervel\Ai\Contracts\Providers\TranscriptionProvider; use Hypervel\Ai\Gateway\Concerns\HandlesFailoverErrors; +use Hypervel\Ai\Gateway\Concerns\ParsesIndexedEmbeddings; use Hypervel\Ai\Gateway\Concerns\ParsesServerSentEvents; use Hypervel\Ai\Gateway\Concerns\WrapsPcmAudio; use Hypervel\Ai\Gateway\OpenAiCompatible\Concerns\MapsChatCompletionTools; @@ -59,6 +60,7 @@ class OpenRouterGateway implements Gateway, RerankingGateway, StepTextGateway use ParsesTextResponses; use HandlesFailoverErrors; use MapsChatCompletionTools; + use ParsesIndexedEmbeddings; use ParsesServerSentEvents; use PerformsChatCompletionSteps; use WrapsPcmAudio; @@ -349,7 +351,7 @@ public function generateEmbeddings( $this->validateTextResponse($data); return new EmbeddingsResponse( - (new Collection($data['data'] ?? []))->pluck('embedding')->all(), + $this->parseIndexedEmbeddings($data['data'] ?? [], count($inputs), $provider->name()), new Usage($data['usage']['prompt_tokens'] ?? 0), new Meta($provider->name(), $model), ); diff --git a/src/cache/src/DatabaseLock.php b/src/cache/src/DatabaseLock.php index c090c35b34..0cf55f7f4b 100644 --- a/src/cache/src/DatabaseLock.php +++ b/src/cache/src/DatabaseLock.php @@ -83,10 +83,11 @@ protected function connection(): ConnectionInterface public function acquire(): bool { $connection = $this->connection(); + $driver = $connection->getDriverName(); - // Every SQLite write takes the database-wide write lock, even an insert that fails on the existing key or an + // Every SQLite write takes the database-wide write lock, even an insert that ignores the existing key or an // update that matches nothing, so a live lock held by another owner is refused with a read instead. - if ($connection->getDriverName() === 'sqlite') { + if ($driver === 'sqlite') { $owner = $connection->table($this->table) ->useWritePdo() ->where('key', $this->name) @@ -98,16 +99,23 @@ public function acquire(): bool } } - try { - $connection->table($this->table)->insert([ - 'key' => $this->name, - 'owner' => $this->owner, - 'expiration' => $this->expiresAt(), - ]); + $record = [ + 'key' => $this->name, + 'owner' => $this->owner, + 'expiration' => $this->expiresAt(), + ]; - $acquired = true; + try { + // MySQL's INSERT IGNORE truncates overlong names, leaving locks that cannot be released by their full name. + $acquired = in_array($driver, ['mysql', 'mariadb'], true) + ? $connection->table($this->table)->insert($record) + : $connection->table($this->table)->insertOrIgnore($record) > 0; } catch (UniqueConstraintViolationException) { // Only an existing row means the lock may still be taken over; other failures, such as a busy database, propagate. + $acquired = false; + } + + if (! $acquired) { $updated = $connection->table($this->table) ->where('key', $this->name) ->where(function ($query) { diff --git a/src/database/src/Eloquent/Relations/BelongsToMany.php b/src/database/src/Eloquent/Relations/BelongsToMany.php index 6f83721f4b..92f4268f75 100644 --- a/src/database/src/Eloquent/Relations/BelongsToMany.php +++ b/src/database/src/Eloquent/Relations/BelongsToMany.php @@ -375,6 +375,8 @@ public function wherePivot(Closure|Expression|string $column, mixed $operator = $column($pivotQuery); + $this->qualifyPivotWheres($pivotQuery->getQuery()); + return $this->addCompiledPivotConstraint( $this->newPivotStatement()->addNestedWhereQuery($pivotQuery->getQuery(), $boolean) ); @@ -659,7 +661,7 @@ public function firstOrCreate(array $attributes = [], Closure|array $values = [] $this->getQuery()->ensureCanCreateOrFirst(); if (is_null($instance = (clone $this)->where($attributes)->first())) { - if (is_null($instance = $this->related->where($attributes)->first())) { + if (is_null($instance = $this->related->newQuery()->withoutGlobalScopes($this->query->removedScopes())->where($attributes)->first())) { $instance = $this->createOrFirst($attributes, $values, $joining, $touch); } else { try { @@ -693,7 +695,13 @@ public function createOrFirst(array $attributes = [], Closure|array $values = [] // ... } - $instance = $this->related->where($attributes)->useWritePdo()->first() ?? throw $exception; + $instance = $this->related->newQuery()->withoutGlobalScopes($this->query->removedScopes()) + ->where($attributes)->useWritePdo()->first() ?? throw $exception; + + // The recovered model may already be linked even without a unique constraint on the pivot table. + if ($this->hasAttachedPivot($instance)) { + return $instance; + } try { $this->getQuery()->withSavepointIfNeeded(fn () => $this->attach($instance, $joining, $touch)); @@ -716,6 +724,38 @@ protected function hasAttachedPivot(Model $instance): bool ->exists(); } + /** + * Get the first record matching the attributes, including trashed records, and restore it. If the record is not found, create it. + * + * @param array|(Closure(): array) $values + * @return TRelatedModel + */ + public function restoreOrCreate(array $attributes = [], Closure|array $values = [], array $joining = [], bool $touch = true): Model + { + $this->withTrashed(); + + return tap($this->firstOrCreate($attributes, $values, $joining, $touch), function ($instance) { + $instance->restore(); + }); + } + + /** + * Attempt to create the record. If a unique constraint violation occurs, find the matching record, including trashed records, and restore it. + * + * @param array|(Closure(): array) $values + * @return TRelatedModel + * + * @throws UniqueConstraintViolationException + */ + public function createOrRestore(array $attributes = [], Closure|array $values = [], array $joining = [], bool $touch = true): Model + { + $this->withTrashed(); + + return tap($this->createOrFirst($attributes, $values, $joining, $touch), function ($instance) { + $instance->restore(); + }); + } + /** * Create or update a related record matching the attributes, and fill it with values. * @@ -1036,11 +1076,11 @@ protected function aliasedPivotColumns(): array * * @return LengthAwarePaginator */ - public function paginate(?int $perPage = null, array $columns = ['*'], string $pageName = 'page', ?int $page = null): mixed + public function paginate(?int $perPage = null, array $columns = ['*'], string $pageName = 'page', ?int $page = null, Closure|int|null $total = null): mixed { $this->query->addSelect($this->shouldSelect($columns)); - return tap($this->query->paginate($perPage, $columns, $pageName, $page), function ($paginator) { + return tap($this->query->paginate($perPage, $columns, $pageName, $page, $total), function ($paginator) { $this->hydratePivotRelation($paginator->items()); }); } @@ -1494,6 +1534,34 @@ public function createManyQuietly(iterable $records, array $joinings = []): arra return Model::withoutEvents(fn (): array => $this->createMany($records, $joinings)); } + /** + * Create a new instance of the related model and attach it to the parent model. Allow mass-assignment. + * + * @return TRelatedModel + */ + public function forceCreate(array $attributes = [], array $joining = [], bool $touch = true): Model + { + $attributes = array_merge($this->getQuery()->pendingAttributes, $attributes); + + $instance = $this->related->newInstance()->forceFill($attributes); + + $instance->save(['touch' => false]); + + $this->attach($instance, $joining, $touch); + + return $instance; + } + + /** + * Create a new instance of the related model with mass assignment without raising any events and attach it to the parent model. + * + * @return TRelatedModel + */ + public function forceCreateQuietly(array $attributes = [], array $joining = [], bool $touch = true): Model + { + return Model::withoutEvents(fn (): Model => $this->forceCreate($attributes, $joining, $touch)); + } + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, mixed $columns = ['*']): Builder { if ($parentQuery->getQuery()->from == $query->getQuery()->from) { @@ -1703,6 +1771,35 @@ public function getPivotColumns(): array return $this->pivotColumns; } + /** + * Qualify the column names in the given query's where clauses by the pivot table. + */ + protected function qualifyPivotWheres(QueryBuilder $query): void + { + foreach ($query->wheres as &$where) { + if ($where['type'] === 'Nested') { + $this->qualifyPivotWheres($where['query']); + + continue; + } + + foreach (['column', 'first', 'second'] as $key) { + if (isset($where[$key]) && is_string($where[$key])) { + $where[$key] = $this->qualifyPivotColumn($where[$key]); + } + } + + // Only betweenColumns stores column names in "values"; other forms store comparison values. + $key = $where['type'] === 'betweenColumns' ? 'values' : 'columns'; + + foreach ($where[$key] ?? [] as $index => $column) { + if (is_string($column)) { + $where[$key][$index] = $this->qualifyPivotColumn($column); + } + } + } + } + /** * Qualify the given column name by the pivot table. * diff --git a/src/database/src/Eloquent/Relations/HasOneOrMany.php b/src/database/src/Eloquent/Relations/HasOneOrMany.php index e8b1de81e9..ae5b7d925a 100755 --- a/src/database/src/Eloquent/Relations/HasOneOrMany.php +++ b/src/database/src/Eloquent/Relations/HasOneOrMany.php @@ -271,6 +271,38 @@ public function createOrFirst(array $attributes = [], Closure|array $values = [] } } + /** + * Get the first record matching the attributes, including trashed records, and restore it. If the record is not found, create it. + * + * @param array|(Closure(): array) $values + * @return TRelatedModel + */ + public function restoreOrCreate(array $attributes = [], Closure|array $values = []): Model + { + $this->withTrashed(); + + return tap($this->firstOrCreate($attributes, $values), function ($instance) { + $instance->restore(); + }); + } + + /** + * Attempt to create the record. If a unique constraint violation occurs, find the matching record, including trashed records, and restore it. + * + * @param array|(Closure(): array) $values + * @return TRelatedModel + * + * @throws UniqueConstraintViolationException + */ + public function createOrRestore(array $attributes = [], Closure|array $values = []): Model + { + $this->withTrashed(); + + return tap($this->createOrFirst($attributes, $values), function ($instance) { + $instance->restore(); + }); + } + /** * Create or update a related record matching the attributes, and fill it with values. * diff --git a/src/database/src/Eloquent/Relations/HasOneOrManyThrough.php b/src/database/src/Eloquent/Relations/HasOneOrManyThrough.php index 195e74dc5f..c3395b9c94 100644 --- a/src/database/src/Eloquent/Relations/HasOneOrManyThrough.php +++ b/src/database/src/Eloquent/Relations/HasOneOrManyThrough.php @@ -534,11 +534,11 @@ public function get(array $columns = ['*']): BaseCollection * * @return LengthAwarePaginator */ - public function paginate(?int $perPage = null, array $columns = ['*'], string $pageName = 'page', ?int $page = null): mixed + public function paginate(?int $perPage = null, array $columns = ['*'], string $pageName = 'page', ?int $page = null, Closure|int|null $total = null): mixed { $this->query->addSelect($this->shouldSelect($columns)); - return $this->query->paginate($perPage, $columns, $pageName, $page); + return $this->query->paginate($perPage, $columns, $pageName, $page, $total); } /** diff --git a/src/docs/eloquent-relationships.md b/src/docs/eloquent-relationships.md index 5ab429c521..4bcf055b5c 100644 --- a/src/docs/eloquent-relationships.md +++ b/src/docs/eloquent-relationships.md @@ -955,7 +955,7 @@ return $this->belongsToMany(Role::class) }); ``` -Qualify column names inside the closure if the related table has columns with the same names. Pivot filters constrain which existing rows `detach`, `sync`, and `updateExistingPivot` affect; they do not restrict newly attached rows. +Unqualified column names inside the closure are automatically prefixed with the intermediate table's name. Raw expressions are not changed, so qualify any column names they contain. Pivot filters constrain which existing rows `detach`, `sync`, and `updateExistingPivot` affect; they do not restrict newly attached rows. The `wherePivot` adds a where clause constraint to the query, but does not add the specified value when creating new models via the defined relationship. If you need to both query and create relationships with a particular pivot value, you may use the `withPivotValue` method: @@ -2547,6 +2547,16 @@ $user->posts()->createManyQuietly([ You may also use the `findOrNew`, `firstOrNew`, `firstOrCreate`, and `updateOrCreate` methods to [create and update models on relationships](/docs/{{version}}/eloquent#upserts). +For related models that use [soft deletes](/docs/{{version}}/eloquent#soft-deleting), `restoreOrCreate` finds a matching record, including deleted records, and restores it, or creates one if none exists. On many-to-many relationships, it also attaches the record to the parent: + +```php +$post = $user->posts()->restoreOrCreate(['title' => 'Draft']); + +$role = $user->roles()->restoreOrCreate(['name' => 'editor']); +``` + +The `createOrRestore` method attempts creation first, then finds and restores a matching record if a unique constraint prevents the insert. + The `incrementOrCreate` method creates a related record with the given default value, or increments the column if a matching record already exists. Any extra attributes are applied when creating or incrementing the record: ```php @@ -2607,6 +2617,12 @@ When attaching a relationship to a model, you may also pass an array of addition $user->roles()->attach($roleId, ['expires' => $expires]); ``` +To create and attach a related model while bypassing mass assignment protection, use `forceCreate`. Its second argument accepts intermediate table attributes. Use `forceCreateQuietly` to also suppress model events: + +```php +$role = $user->roles()->forceCreate(['name' => 'Editor'], ['expires' => $expires]); +``` + Sometimes it may be necessary to remove a role from a user. To remove a many-to-many relationship record, use the `detach` method. The `detach` method will delete the appropriate record out of the intermediate table; however, both models will remain in the database: ```php diff --git a/src/docs/inertia.md b/src/docs/inertia.md index 246d372f58..33a61bcdff 100644 --- a/src/docs/inertia.md +++ b/src/docs/inertia.md @@ -14408,6 +14408,12 @@ The `--runtime` flag on the Artisan command overrides the configured value for a php artisan inertia:start-ssr --runtime=bun ``` +By default, the command stops the server at the configured SSR URL before starting. If that URL points to a shared server or load balancer, use `--no-stop-before-start` to start a new instance without sending a shutdown request. + +```bash +php artisan inertia:start-ssr --no-stop-before-start +``` + You may also enable the `ensure_runtime_exists` option to verify the runtime binary exists before attempting to start the SSR server. The command will exit with an error if the binary cannot be found. ```php diff --git a/src/docs/mcp.md b/src/docs/mcp.md index a777474e9a..be1466cf24 100644 --- a/src/docs/mcp.md +++ b/src/docs/mcp.md @@ -1864,6 +1864,8 @@ If your application is not already using Passport, follow Passport's [installati Dynamic client registration applies `passport.middleware` and Passport's named `passport` rate limiter. You may customize that limiter using Passport's [rate limiting configuration](/docs/{{version}}/passport#rate-limiting). +Clients may register up to 10 redirect URIs, each at most 1,024 characters long. + Dynamically registered clients are marked as third-party clients, so Passport's `firstParty()` method returns `false` even though they have no owner. This keeps them subject to consent when your application skips consent for its own clients. MCP registration creates public clients and grants them `mcp:use` plus the anonymous scopes allowed by `passport.registration.scopes`. Custom client scope defaults are preserved, with wildcard and provisioning scopes excluded. Use Hypervel's Passport migrations and schema when importing client data, including its nullable JSONB `scopes` column and boolean `third_party` column. The registration response reports the saved client name and scope allowance. @@ -2060,6 +2062,21 @@ $client = Client::web('https://api.githubcopilot.com/mcp/')->withToken( If you already obtained a suitable token through [Socialite](/docs/{{version}}/socialite) or another account-linking flow, pass that token to `withToken`; you do not need to register MCP OAuth routes. + +#### Custom Headers + +If a server expects an API key or another custom header, use the `withHeaders` method. Named clients may define the same values using the `headers` configuration option: + +```php +use Hypervel\Mcp\Client; + +$client = Client::web('https://projects.example.com/mcp')->withHeaders([ + 'X-Api-Key' => config()->string('services.projects.api_key'), +]); +``` + +Custom headers stay on the configured URL's origin: its scheme, host and port. Redirects to another origin are followed without those headers, so connect to the destination server's URL directly if it needs them. + #### Linking User Accounts With OAuth @@ -2192,10 +2209,12 @@ try { Your application stores the token and scopes and decides when to retry the operation. Limit repeated authorization attempts when a server continues to reject the requested permissions. +Before redirecting for authorization, the client verifies that the resource advertises any supplied custom metadata URL. The standard well-known metadata URL needs no probe. Direct client-credentials and refresh grants do not perform this check. + #### OAuth Destinations -OAuth discovery, registration and token requests validate resolved addresses before sending, then pin those addresses for the connection. This prevents remote metadata from directing the server to private network services. These checks run during OAuth requests, not on every MCP tool call. Automatic redirects are disabled, and DNS resolution shares the request's timeout budget. +OAuth discovery, registration and token requests validate resolved addresses before sending, then pin those addresses for the connection. This prevents remote metadata from directing the server to private network services. These checks run during OAuth requests, not on every MCP tool call. Automatic redirects are disabled for these requests; resource advertisement probes follow up to five redirects, validating and pinning each hop. DNS resolution shares the request's timeout budget, and response bodies are limited to 1 MiB by default. Hostname pinning requires cURL with SSL support and libcurl 7.75 or later. See the HTTP client's [destination policy limitations](/docs/{{version}}/http-client#destination-policy-limitations). diff --git a/src/docs/pagination.md b/src/docs/pagination.md index b6dcb48f61..036adbb6c8 100644 --- a/src/docs/pagination.md +++ b/src/docs/pagination.md @@ -93,6 +93,12 @@ Of course, you may call the `paginate` method after setting other constraints on $users = User::where('votes', '>', 100)->paginate(15); ``` +If you already know the total number of matching records, pass it as the `total` argument to avoid a separate count query. This also works on relationship queries, including many-to-many and has-many-through relationships. You may pass an integer or a closure that returns the total: + +```php +$roles = $user->roles()->paginate(15, total: $totalRoles); +``` + You may also use the `simplePaginate` method when paginating Eloquent models: ```php diff --git a/src/docs/porting-from-laravel.md b/src/docs/porting-from-laravel.md index 46f3991ac4..adf060e297 100644 --- a/src/docs/porting-from-laravel.md +++ b/src/docs/porting-from-laravel.md @@ -576,6 +576,8 @@ If an OAuth client relies on Laravel's implicit `mcp:use` scope and the remote s Use `redirect()->signedRoute()` when forwarding `resource_metadata` or `scope` to an MCP client's connect route. Unsigned hints are rejected to prevent credential disclosure through substituted discovery metadata; see [authorization challenges](/docs/{{version}}/mcp#client-oauth-scopes). +If an MCP client's URL redirects to another origin that needs its custom headers, configure the final URL directly. Hypervel follows the redirect without forwarding those headers; see [custom client headers](/docs/{{version}}/mcp#client-custom-headers). + Modern HTTP streams cancel on client disconnect. Review handlers that perform side effects while streaming and ensure their cleanup runs on cancellation; legacy streams still finish. See [streaming responses](/docs/{{version}}/mcp#streaming-responses). MCP servers do not negotiate protocol revision `2025-03-26`. Clients limited to that revision cannot connect; use clients that support `2025-06-18` or newer. See [protocol versions](/docs/{{version}}/mcp#protocol-versions). diff --git a/src/docs/queues.md b/src/docs/queues.md index fa07e2a9fe..3d59e697df 100644 --- a/src/docs/queues.md +++ b/src/docs/queues.md @@ -3843,6 +3843,8 @@ Bus::fake(); Bus::assertNothingDispatched(); ``` +When faking only selected jobs with `Bus::fake([...])`, you may call `Bus::withoutDispatchingAfterResponses()` to run unfaked after-response jobs synchronously during the test. Jobs configured to wait for a database transaction still run only after it commits. Call `Bus::withDispatchingAfterResponses()` to restore deferred dispatching. + ### Faking a Subset of Jobs diff --git a/src/docs/session.md b/src/docs/session.md index c50325975e..9106ce903a 100644 --- a/src/docs/session.md +++ b/src/docs/session.md @@ -3,6 +3,7 @@ - [Introduction](#introduction) - [Configuration](#configuration) - [Driver Prerequisites](#driver-prerequisites) + - [Garbage Collection](#garbage-collection) - [Interacting With the Session](#interacting-with-the-session) - [Retrieving Data](#retrieving-data) - [Storing Data](#storing-data) @@ -88,6 +89,25 @@ Redis stores session payloads directly rather than routing them through a cache To list and invalidate a user's Redis sessions, enable `SESSION_TRACK_USER_SESSIONS`. This feature requires phpredis 6.3.0 or later and Redis 8.0 or Valkey 9.0 or later. You may leave this option disabled if your application does not provide session-management controls. + +### Garbage Collection + +The file and database session drivers remove expired sessions using a request-based lottery. You may run cleanup explicitly using the `session:garbage-collect` Artisan command: + +```shell +php artisan session:garbage-collect +``` + +To move cleanup out of requests, [schedule the command](/docs/{{version}}/scheduling) and set the `lottery` option in `config/session.php` to `[0, 100]`: + +```php +use Hypervel\Support\Facades\Schedule; + +Schedule::command('session:garbage-collect')->hourly(); +``` + +The command uses your configured session driver and lifetime. Redis sessions expire automatically and do not need scheduled garbage collection. + ## Interacting With the Session diff --git a/src/docs/validation.md b/src/docs/validation.md index acfa656ad5..69b4e48ee3 100644 --- a/src/docs/validation.md +++ b/src/docs/validation.md @@ -1678,6 +1678,8 @@ use Hypervel\Validation\Rule; ], ``` +Rulesets with non-sequential keys are treated as maps of fields to rules, so pass filtered rulesets through `array_values`. + #### alpha diff --git a/src/filesystem/src/Filesystem.php b/src/filesystem/src/Filesystem.php index 80460a422d..3137f7a3e4 100644 --- a/src/filesystem/src/Filesystem.php +++ b/src/filesystem/src/Filesystem.php @@ -11,6 +11,7 @@ use Hypervel\Support\LazyCollection; use Hypervel\Support\Traits\Conditionable; use Hypervel\Support\Traits\Macroable; +use Phar; use RuntimeException; use SensitiveParameter; use SplFileObject; @@ -33,6 +34,10 @@ class Filesystem */ public function exists(string $path): bool { + if ($this->hasDangerousStreamWrapper($path)) { + return false; + } + return file_exists($path); } @@ -520,9 +525,30 @@ public function hasSameHash(string $firstFile, string $secondFile): bool */ public function isFile(string $file): bool { + if ($this->hasDangerousStreamWrapper($file)) { + return false; + } + return is_file($file); } + /** + * Determine if the given path reads from a phar archive other than the running one. + * + * The require helpers use isFile(), so this also prevents them from executing scripts from uploaded archives. + */ + protected function hasDangerousStreamWrapper(string $path): bool + { + // Avoid lowercasing entire paths on these frequently called checks. + if (strncasecmp($path, 'phar://', 7) !== 0) { + return false; + } + + $running = class_exists(Phar::class, false) ? Phar::running() : ''; + + return $running === '' || ! str_starts_with($path, $running . '/'); + } + /** * Find path names matching a given pattern. */ diff --git a/src/fortify/src/Http/Controllers/ConfirmablePasswordController.php b/src/fortify/src/Http/Controllers/ConfirmablePasswordController.php index 7901d40e29..954775542a 100644 --- a/src/fortify/src/Http/Controllers/ConfirmablePasswordController.php +++ b/src/fortify/src/Http/Controllers/ConfirmablePasswordController.php @@ -39,6 +39,10 @@ public function show(Request $request): ConfirmPasswordViewResponse */ public function store(Request $request): Responsable { + $request->validate([ + 'password' => 'nullable|string', + ]); + $guardName = Fortify::guardName(); /** @var Authenticatable&Model $user */ diff --git a/src/foundation/resources/exceptions/renderer/dist/scripts.js b/src/foundation/resources/exceptions/renderer/dist/scripts.js index 961d7803f8..b8ca1d2f81 100644 --- a/src/foundation/resources/exceptions/renderer/dist/scripts.js +++ b/src/foundation/resources/exceptions/renderer/dist/scripts.js @@ -2,7 +2,7 @@ var e=Object.defineProperty,t=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],en ${n?`Expression: "`+n+`" -`:``}`,t),setTimeout(()=>{throw e},0)}var Re=!0;function ze(e){let t=Re;Re=!1;let n=e();return Re=t,n}function Be(e,t,n={}){let r;return F(e,t)(e=>r=e,n),r}function F(...e){return Ve(...e)}var Ve=()=>{};function He(e){Ve=e}var Ue;function We(e){Ue=e}function Ge(e,t){let n={};je(n,e);let r=[n,...be(e)],i=typeof t==`function`?Ke(r,t):Ye(r,t,e);return Ne.bind(null,e,t,i)}function Ke(e,t){return(n=()=>{},{scope:r={},params:i=[],context:a}={})=>{Re?Xe(n,t.apply(xe([r,...e]),i)):Xe(n,t,xe([r,...e]),i)}}var qe={};function Je(e,t){if(qe[e])return qe[e];let n=Object.getPrototypeOf(async function(){}).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,i=(()=>{try{let t=new n([`__self`,`scope`],`with (scope) { __self.result = ${r} }; __self.finished = true; return __self.result;`);return Object.defineProperty(t,"name",{value:`[Alpine] ${e}`}),t}catch(n){return Pe(n,t,e),Promise.resolve()}})();return qe[e]=i,i}function Ye(e,t,n){let r=Je(t,n);return(i=()=>{},{scope:a={},params:o=[],context:s}={})=>{r.result=void 0,r.finished=!1;let c=xe([a,...e]);if(typeof r==`function`){let e=r.call(s,r,c).catch(e=>Pe(e,n,t));r.finished?(Xe(i,r.result,c,o,n),r.result=void 0):e.then(e=>{Xe(i,e,c,o,n)}).catch(e=>Pe(e,n,t)).finally(()=>r.result=void 0)}}}function Xe(e,t,n,r,i){if(Re&&typeof t==`function`){let a=t.apply(n,r);a instanceof Promise?a.then(t=>Xe(e,t,n,r)).catch(e=>Pe(e,i,t)):e(a)}else typeof t==`object`&&t instanceof Promise?t.then(t=>e(t)):e(t)}function Ze(...e){return Ue(...e)}function Qe(e,t,n={}){let r={};je(r,e);let i=[r,...be(e)],a=xe([n.scope??{},...i]),o=n.params??[];if(t.includes(`await`)){let e=Object.getPrototypeOf(async function(){}).constructor;return new e([`scope`],`with (scope) { let __result = ${/^[\n\s]*if.*\(.*\)/.test(t.trim())||/^(let|const)\s/.test(t.trim())?`(async()=>{ ${t} })()`:t}; return __result }`).call(n.context,a)}{let e=/^[\n\s]*if.*\(.*\)/.test(t.trim())||/^(let|const)\s/.test(t.trim())?`(()=>{ ${t} })()`:t,r=Function([`scope`],`with (scope) { let __result = ${e}; return __result }`).call(n.context,a);return typeof r==`function`&&Re?r.apply(a,o):r}}var $e=`x-`;function et(e=``){return $e+e}function tt(e){$e=e}var nt={};function I(e,t){return nt[e]=t,{before(t){if(!nt[t]){console.warn(String.raw`Cannot find directive \`${t}\`. \`${e}\` will use the default order of execution`);return}let n=xt.indexOf(t);xt.splice(n>=0?n:xt.indexOf(`DEFAULT`),0,e)}}}function rt(e){return Object.keys(nt).includes(e)}function it(e,t,n){if(t=Array.from(t),e._x_virtualDirectives){let n=Object.entries(e._x_virtualDirectives).map(([e,t])=>({name:e,value:t})),r=at(n);n=n.map(e=>r.find(t=>t.name===e.name)?{name:`x-bind:${e.name}`,value:`"${e.value}"`}:e),t=t.concat(n)}let r={};return t.map(mt((e,t)=>r[e]=t)).filter(_t).map(yt(r,n)).sort(St).map(t=>dt(e,t))}function at(e){return Array.from(e).map(mt()).filter(e=>!_t(e))}var ot=!1,st=new Map,ct=Symbol();function lt(e){ot=!0;let t=Symbol();ct=t,st.set(t,[]);let n=()=>{for(;st.get(t).length;)st.get(t).shift()();st.delete(t)};e(n),ot=!1,n()}function ut(e){let t=[],n=e=>t.push(e),[r,i]=O(e);return t.push(i),[{Alpine:ur,effect:r,cleanup:n,evaluateLater:F.bind(F,e),evaluate:Be.bind(Be,e)},()=>t.forEach(e=>e())]}function dt(e,t){let n=nt[t.type]||(()=>{}),[r,i]=ut(e);ie(e,t.original,i);let a=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,r),n=n.bind(n,e,t,r),ot?st.get(ct).push(n):n())};return a.runCleanups=i,a}var ft=(e,t)=>({name:n,value:r})=>(n.startsWith(e)&&(n=n.replace(e,t)),{name:n,value:r}),pt=e=>e;function mt(e=()=>{}){return({name:t,value:n})=>{let{name:r,value:i}=ht.reduce((e,t)=>t(e),{name:t,value:n});return r!==t&&e(r,t),{name:r,value:i}}}var ht=[];function gt(e){ht.push(e)}function _t({name:e}){return vt().test(e)}var vt=()=>RegExp(`^${$e}([^:^.]+)\\b`);function yt(e,t){return({name:n,value:r})=>{n===r&&(r=``);let i=n.match(vt()),a=n.match(/:([a-zA-Z0-9\-_:]+)/),o=n.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],s=t||e[n]||n;return{type:i?i[1]:null,value:a?a[1]:null,modifiers:o.map(e=>e.replace(`.`,``)),expression:r,original:s}}}var bt=`DEFAULT`,xt=[`ignore`,`ref`,`id`,`data`,`anchor`,`bind`,`init`,`for`,`model`,`modelable`,`transition`,`show`,`if`,bt,`teleport`];function St(e,t){let n=xt.indexOf(e.type)===-1?bt:e.type,r=xt.indexOf(t.type)===-1?bt:t.type;return xt.indexOf(n)-xt.indexOf(r)}function Ct(e,t){if(typeof ShadowRoot==`function`&&e instanceof ShadowRoot){Array.from(e.children).forEach(e=>Ct(e,t));return}let n=!1;if(t(e,()=>n=!0),n)return;let r=e.firstElementChild;for(;r;)Ct(r,t,!1),r=r.nextElementSibling}var wt=!1;function Tt(e,t=()=>{}){return(...n)=>wt?t(...n):e(...n)}function Et(e){return(...t)=>wt&&e(...t)}var Dt=[];function Ot(e){Dt.push(e)}function kt(e,t){Dt.forEach(n=>n(e,t)),wt=!0,Nt(()=>{nn(t,(e,t)=>{t(e,()=>{})})}),wt=!1}var At=!1;function jt(e,t){t._x_dataStack||=e._x_dataStack,wt=!0,At=!0,Nt(()=>{Mt(t)}),wt=!1,At=!1}function Mt(e){let t=!1;nn(e,(e,n)=>{Ct(e,(e,r)=>{if(t&&Qt(e))return r();t=!0,n(e,r)})})}function Nt(e){let t=b;D((e,n)=>{let r=t(e);return x(r),()=>{}}),e(),D(t)}var Pt=0;function Ft(e,t){let n=e._x_deferInit;n||(n=e._x_deferInit={pending:0,ownsIgnore:!e._x_ignore,queuedAttributes:new Map},n.ownsIgnore&&(e._x_ignore=!0),Pt++),n.pending++,Promise.resolve(t).catch(t=>{try{Pe(t,e)}catch(e){setTimeout(()=>{throw e},0)}}).then(()=>It(e,n))}function It(e,t){t.pending--,!(t.pending>0)&&(pe(),!(t.pending>0)&&e._x_deferInit===t&&(delete e._x_deferInit,t.ownsIgnore&&delete e._x_ignore,Pt--,e.isConnected&&(zt(t),nn(e))))}function Lt(e,t){if(Pt===0)return!1;let n=Zt(e,e=>e._x_deferInit);return n?(Rt(n._x_deferInit,e,t.map(({name:e})=>e)),!0):!1}function Rt(e,t,n){let r=e.queuedAttributes.get(t);(!r||r.marker!==t._x_marker)&&(r={marker:t._x_marker,names:new Set},e.queuedAttributes.set(t,r)),n.forEach(e=>r.names.add(e))}function zt(e){e.queuedAttributes.forEach((e,t)=>{if(!t.isConnected||!t._x_marker||t._x_marker!==e.marker)return;let n=Zt(t,e=>e._x_deferInit);if(n){Rt(n._x_deferInit,t,Array.from(e.names));return}let r=Array.from(e.names).filter(e=>t.hasAttribute(e)).map(e=>({name:e,value:t.getAttribute(e)}));r.length!==0&&it(t,r).forEach(e=>e())})}Ot((e,t)=>{Pt!==0&&e&&e.nodeType===1&&t&&t.nodeType===1&&Zt(e,e=>e._x_deferInit)&&(t._x_ignore=!0)});function Bt(e,t,n={},r={}){return e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0,cancelable:!0,...r}))}function Vt(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var Ht=!1;function Ut(){Ht&&Vt(`Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems.`),Ht=!0,document.body||Vt("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `'])); + + $this->assertStringNotContainsString('', $html); + } +} diff --git a/tests/Mcp/Unit/Server/McpServiceProviderTest.php b/tests/Mcp/Unit/Server/McpServiceProviderTest.php index 7963b2b668..6c6cf215ed 100644 --- a/tests/Mcp/Unit/Server/McpServiceProviderTest.php +++ b/tests/Mcp/Unit/Server/McpServiceProviderTest.php @@ -4,7 +4,9 @@ namespace Hypervel\Tests\Mcp\Unit\Server; +use Hypervel\Contracts\Http\Kernel; use Hypervel\Mcp\Server\McpServiceProvider; +use Hypervel\Mcp\Server\Middleware\AddAuthorizationFramingHeaders; use Hypervel\Passport\Passport; use Hypervel\Tests\Mcp\TestCase; @@ -23,4 +25,9 @@ public function testRegistersMcpScopeDuringBoot(): void $this->assertArrayHasKey('mcp:use', Passport::$scopes); $this->assertSame('Custom scope', Passport::$scopes['custom']); } + + public function testRegistersTheAuthorizationFramingMiddlewareGlobally(): void + { + $this->assertTrue(app(Kernel::class)->hasMiddleware(AddAuthorizationFramingHeaders::class)); + } } diff --git a/tests/Mcp/Unit/Server/Middleware/AddAuthorizationFramingHeadersTest.php b/tests/Mcp/Unit/Server/Middleware/AddAuthorizationFramingHeadersTest.php new file mode 100644 index 0000000000..235b15eea7 --- /dev/null +++ b/tests/Mcp/Unit/Server/Middleware/AddAuthorizationFramingHeadersTest.php @@ -0,0 +1,68 @@ +setRouteResolver(fn (): Route => (new Route('GET', '/oauth/authorize', []))->name($name)); + + $response = (new AddAuthorizationFramingHeaders)->handle($request, fn (): Response => response('consent')); + + $this->assertSame('DENY', $response->headers->get('X-Frame-Options')); + $this->assertSame("frame-ancestors 'none'", $response->headers->get('Content-Security-Policy')); + } + + public function testReplacesAWeakerFrameOptionsHeader(): void + { + $request = Request::create('/oauth/authorize'); + $request->setRouteResolver(fn (): Route => (new Route('GET', '/oauth/authorize', []))->name('passport.authorizations.authorize')); + + $response = (new AddAuthorizationFramingHeaders)->handle($request, fn (): Response => response('consent', 200, [ + 'X-Frame-Options' => 'SAMEORIGIN', + ])); + + $this->assertSame(['DENY'], $response->headers->all('x-frame-options')); + } + + public function testKeepsTheContentSecurityPolicyTheApplicationAlreadySet(): void + { + $request = Request::create('/oauth/authorize'); + $request->setRouteResolver(fn (): Route => (new Route('GET', '/oauth/authorize', []))->name('passport.authorizations.authorize')); + + $response = (new AddAuthorizationFramingHeaders)->handle($request, fn (): Response => response('consent', 200, [ + 'Content-Security-Policy' => "default-src 'self'", + ])); + + $this->assertSame(["default-src 'self'", "frame-ancestors 'none'"], $response->headers->all('content-security-policy')); + } + + public function testLeavesOtherResponsesUntouched(): void + { + $request = Request::create('/mcp'); + $request->setRouteResolver(fn (): Route => (new Route('POST', '/mcp', []))->name('mcp')); + + $response = (new AddAuthorizationFramingHeaders)->handle($request, fn (): Response => response('ok')); + + $this->assertFalse($response->headers->has('X-Frame-Options')); + $this->assertFalse($response->headers->has('Content-Security-Policy')); + } +} diff --git a/tests/Mcp/Unit/Server/RegistrarTest.php b/tests/Mcp/Unit/Server/RegistrarTest.php index 1c9a0f13f1..649b4397d0 100644 --- a/tests/Mcp/Unit/Server/RegistrarTest.php +++ b/tests/Mcp/Unit/Server/RegistrarTest.php @@ -544,6 +544,38 @@ public function testRejectsNonStringRedirectUrisBeforeCreatingAClient(array $red $this->assertDatabaseCount('oauth_clients', 0); } + #[DataProvider('oversizedRedirectUris')] + public function testRejectsTooManyOrTooLongRedirectUrisBeforeCreatingAClient(array $redirectUris): void + { + $this->prepareOauthRegistration(); + + $this->postJson('/oauth/register', [ + 'redirect_uris' => $redirectUris, + ])->assertBadRequest()->assertJson(['error' => 'invalid_redirect_uri']); + + $this->assertDatabaseCount('oauth_clients', 0); + } + + /** + * Provide redirect lists that exceed registration limits. + */ + public static function oversizedRedirectUris(): array + { + return [ + 'too many' => [array_map(fn (int $index): string => "https://example.com/callback/{$index}", range(1, 11))], + 'too long' => [['https://example.com/' . str_repeat('a', 1005)]], + ]; + } + + public function testAcceptsUpToTenRedirectUris(): void + { + $this->prepareOauthRegistration(); + + $this->postJson('/oauth/register', [ + 'redirect_uris' => array_map(fn (int $index): string => "https://example.com/callback/{$index}", range(1, 10)), + ])->assertCreated(); + } + public function testKeepsRedirectErrorsAheadOfMetadataErrors(): void { $this->prepareOauthRegistration(); diff --git a/tests/Queue/QueueBeanstalkdQueueTest.php b/tests/Queue/QueueBeanstalkdQueueTest.php index 4fce4c86bd..bbcc36f233 100644 --- a/tests/Queue/QueueBeanstalkdQueueTest.php +++ b/tests/Queue/QueueBeanstalkdQueueTest.php @@ -199,7 +199,7 @@ public function testPushProperlyPushesJobOntoBeanstalkd(): void $pheanstalk = $this->queue->getPheanstalk(); $pheanstalk->expects('useTube')->with(m::type(TubeName::class)); $pheanstalk->expects('useTube')->with(m::type(TubeName::class)); - $pheanstalk->expects('put')->times(2)->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), 1024, 0, 60); + $pheanstalk->expects('put')->times(2)->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), 1024, 0, 60); $this->queue->push('foo', ['data'], 'stack'); $this->queue->push('foo', ['data']); @@ -245,7 +245,7 @@ public function testDelayedPushProperlyPushesJobOntoBeanstalkd(): void $pheanstalk = $this->queue->getPheanstalk(); $pheanstalk->expects('useTube')->with(m::type(TubeName::class)); $pheanstalk->expects('useTube')->with(m::type(TubeName::class)); - $pheanstalk->expects('put')->times(2)->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => 5]), Pheanstalk::DEFAULT_PRIORITY, 5, Pheanstalk::DEFAULT_TTR); + $pheanstalk->expects('put')->times(2)->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => 5]), Pheanstalk::DEFAULT_PRIORITY, 5, Pheanstalk::DEFAULT_TTR); $this->queue->later(5, 'foo', ['data'], 'stack'); $this->queue->later(5, 'foo', ['data']); diff --git a/tests/Queue/QueueDatabaseQueueUnitTest.php b/tests/Queue/QueueDatabaseQueueUnitTest.php index 1d565ef4b9..dba089bbe6 100644 --- a/tests/Queue/QueueDatabaseQueueUnitTest.php +++ b/tests/Queue/QueueDatabaseQueueUnitTest.php @@ -265,7 +265,7 @@ public function testDelayedPushProperlyPushesJobOntoDatabase(DateInterval|DateTi $query->expects('insertGetId')->andReturnUsing(function (array $array) use ($uuid, $now): int { $this->assertSame('default', $array['queue']); - $this->assertSame(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => 1]), $array['payload']); + $this->assertSame(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => 1]), $array['payload']); $this->assertEquals(0, $array['attempts']); $this->assertNull($array['reserved_at']); $this->assertSame(1002, $array['available_at']); @@ -433,14 +433,14 @@ public function testBulkBatchPushesOntoDatabase(): void $query->expects('insert')->andReturnUsing(function (array $records) use ($uuid, $now): bool { $this->assertEquals([[ 'queue' => 'queue', - 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), + 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), 'attempts' => 0, 'reserved_at' => null, 'available_at' => 1732502704, 'created_at' => 1732502704, ], [ 'queue' => 'queue', - 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'bar', 'job' => 'bar', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), + 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'bar', 'job' => 'bar', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), 'attempts' => 0, 'reserved_at' => null, 'available_at' => 1732502704, diff --git a/tests/Queue/QueueJobTest.php b/tests/Queue/QueueJobTest.php index 68697e1e64..fb4c564778 100644 --- a/tests/Queue/QueueJobTest.php +++ b/tests/Queue/QueueJobTest.php @@ -17,6 +17,7 @@ use Hypervel\Queue\Events\JobFailed; use Hypervel\Queue\InvalidPayloadException; use Hypervel\Queue\Jobs\Job; +use Hypervel\Queue\SyncQueue; use Hypervel\Queue\TimeoutExceededException; use Hypervel\Tests\TestCase; use Mockery as m; @@ -176,7 +177,10 @@ public function testBatchRollbackCancellationEscapesBeforeFurtherFailureCleanup( $job = new QueueJobPayloadStub(json_encode([ 'uuid' => 'job-uuid', 'job' => QueueJobTimeoutBatchStub::class, - 'data' => ['commandName' => QueueJobTimeoutBatchStub::class], + 'data' => [ + 'commandName' => QueueJobTimeoutBatchStub::class, + 'command' => serialize(new QueueJobTimeoutBatchStub), + ], ], JSON_THROW_ON_ERROR)); $timeout = TimeoutExceededException::forJob($job); $gate = $this->armCurrentCoroutineCancellation(); @@ -258,7 +262,10 @@ public function testBatchRollbackFailurePreservesTimeoutForFailedJobCleanup(?str $job = new QueueJobPayloadStub(json_encode([ 'uuid' => 'job-uuid', 'job' => QueueJobTimeoutBatchStub::class, - 'data' => ['commandName' => QueueJobTimeoutBatchStub::class], + 'data' => [ + 'commandName' => QueueJobTimeoutBatchStub::class, + 'command' => serialize(new QueueJobTimeoutBatchStub), + ], ], JSON_THROW_ON_ERROR)); $timeout = TimeoutExceededException::forJob($job); @@ -311,6 +318,41 @@ public static function failedJobDatabases(): array ]; } + public function testStringJobTimeoutDoesNotTreatCallerDataAsBatchMetadata(): void + { + $queue = new class extends SyncQueue { + /** + * Build a string-job payload through the real producer. + */ + public function payloadFor(string $job, array $data): string + { + return $this->createPayload($job, 'default', $data); + } + }; + $job = new QueueJobPayloadStub($queue->payloadFor( + QueueJobTimeoutBatchStub::class, + ['command' => 'backup:run', 'commandName' => 'backup:run'], + )); + $timeout = TimeoutExceededException::forJob($job); + $events = m::mock(Dispatcher::class); + $events->shouldReceive('hasListeners')->once()->with(JobFailed::class)->andReturnTrue(); + $events->shouldReceive('dispatch')->once() + ->withArgs(fn (JobFailed $event): bool => $event->job === $job && $event->exception === $timeout); + + $container = m::mock(Container::class); + $container->shouldNotReceive('make')->with(BatchRepository::class); + $container->shouldReceive('make')->with('config')->andReturn(new ConfigRepository(['queue' => ['failed' => ['driver' => null]]])); + $container->shouldReceive('make')->once()->with(QueueJobTimeoutBatchStub::class)->andReturn(new QueueJobTimeoutBatchStub); + $container->shouldReceive('make')->once()->with(Dispatcher::class)->andReturn($events); + $job->setContainer($container); + $job->setConnectionName('redis'); + + $job->fail($timeout); + + $this->assertTrue($job->hasFailed()); + $this->assertTrue($job->isDeleted()); + } + /** * Arm exact cancellation of the current coroutine at a controlled channel handoff. */ diff --git a/tests/Queue/QueueListFailedCommandTest.php b/tests/Queue/QueueListFailedCommandTest.php index 0c8ffbff80..bc63556acf 100644 --- a/tests/Queue/QueueListFailedCommandTest.php +++ b/tests/Queue/QueueListFailedCommandTest.php @@ -7,6 +7,7 @@ use Hypervel\Foundation\Application; use Hypervel\Queue\Console\ListFailedCommand; use Hypervel\Queue\Failed\FailedJobProviderInterface; +use Hypervel\Queue\SyncQueue; use Hypervel\Tests\TestCase; use Mockery as m; use Symfony\Component\Console\Input\ArrayInput; @@ -84,6 +85,24 @@ public function testEncryptedJobShowsUnderlyingClass(): void $this->assertStringNotContainsString('CallQueuedHandler', $output); } + public function testStringJobCommandDataDoesNotHideTheHandlerMethod(): void + { + $queue = new class extends SyncQueue { + /** + * Build a string payload through the real queue producer. + */ + public function payloadFor(string $job, array $data): string + { + return $this->createPayload($job, 'default', $data); + } + }; + $output = $this->runListFailedCommandWithRawPayload( + $queue->payloadFor('RunCommand@handle', ['command' => 'backup:run']), + ); + + $this->assertStringContainsString('RunCommand@handle', $output); + } + public function testMalformedPayloadDoesNotThrow(): void { // Malformed JSON in the payload column must not bubble up an exception; diff --git a/tests/Queue/QueueRedisQueueTest.php b/tests/Queue/QueueRedisQueueTest.php index fdeb94e40d..53dff20845 100644 --- a/tests/Queue/QueueRedisQueueTest.php +++ b/tests/Queue/QueueRedisQueueTest.php @@ -381,7 +381,7 @@ public function testPushProperlyPushesJobOntoRedis(): void $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldAllowMockingMethod('evalWithShaCache'); $redisProxy->expects('isCluster')->andReturn(false); - $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); + $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); $redis->expects('connection')->twice()->andReturn($redisProxy); $id = $queue->push('foo', ['data']); @@ -404,7 +404,7 @@ public function testPushProperlyPushesJobOntoRedisWithCustomPayloadHook(): void $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldAllowMockingMethod('evalWithShaCache'); $redisProxy->expects('isCluster')->andReturn(false); - $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'custom' => 'taylor', 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); + $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'custom' => 'taylor', 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); $redis->expects('connection')->twice()->andReturn($redisProxy); Queue::createPayloadUsing(function (string $connection, ?string $queue, array $payload): array { @@ -432,7 +432,7 @@ public function testJobQueueingAndQueuedEventsAreSkippedWhenNoListenersAreRegist $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldAllowMockingMethod('evalWithShaCache'); $redisProxy->expects('isCluster')->andReturn(false); - $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); + $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); $redis->expects('connection')->twice()->andReturn($redisProxy); $events = m::mock(Dispatcher::class); @@ -562,7 +562,7 @@ public function testPushRaisesFailedEventWhenRedisThrows(): void $this->assertSame('foo', $event->job); $this->assertSame('default', $event->connectionName); $this->assertNull($event->queue); - $this->assertSame(['uuid' => (string) $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null], $event->payload()); + $this->assertSame(['uuid' => (string) $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null], $event->payload()); return true; }) @@ -602,7 +602,7 @@ public function testPushProperlyPushesJobOntoRedisWithTwoCustomPayloadHook(): vo $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldAllowMockingMethod('evalWithShaCache'); $redisProxy->expects('isCluster')->andReturn(false); - $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'custom' => 'taylor', 'bar' => 'foo', 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); + $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'custom' => 'taylor', 'bar' => 'foo', 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); $redis->expects('connection')->twice()->andReturn($redisProxy); Queue::createPayloadUsing(function (string $connection, ?string $queue, array $payload): array { @@ -639,7 +639,7 @@ public function testDelayedPushProperlyPushesJobOntoRedis(): void $redisProxy->expects('evalWithShaCache')->with( LuaScripts::later(), ['queues:default:delayed'], - [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] + [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); @@ -668,7 +668,7 @@ public function testDelayedPushWithDateTimeProperlyPushesJobOntoRedis(): void $redisProxy->expects('evalWithShaCache')->with( LuaScripts::later(), ['queues:default:delayed'], - [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] + [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); @@ -694,7 +694,7 @@ public function testDelayedPushWithIntervalNeverRunsBeforeRequestedLifetime(): v $redisProxy->expects('evalWithShaCache')->with( LuaScripts::later(), ['queues:default:delayed'], - [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] + [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); @@ -853,7 +853,7 @@ public function testPushUsesGetRedisKeyForLuaScript(): void $redisProxy->expects('evalWithShaCache')->with( LuaScripts::push(), ['queues:{default}', 'queues:{default}:notify'], - [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])] + [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); @@ -920,7 +920,7 @@ public function testLaterUsesClusterSafeRedisKeyForDelayedSet(): void $redisProxy->expects('evalWithShaCache')->with( LuaScripts::later(), ['queues:{default}:delayed'], - [2, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] + [2, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); diff --git a/tests/Queue/QueueRetryCommandTest.php b/tests/Queue/QueueRetryCommandTest.php index 8a0dfca668..6775e26ba4 100644 --- a/tests/Queue/QueueRetryCommandTest.php +++ b/tests/Queue/QueueRetryCommandTest.php @@ -4,9 +4,12 @@ namespace Hypervel\Tests\Queue; +use Aws\Result; +use Aws\Sqs\SqsClient; use Generator; use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Contracts\Queue\Queue as QueueContract; +use Hypervel\Database\Eloquent\ModelNotFoundException; use Hypervel\Foundation\Application; use Hypervel\Queue\Console\RetryCommand; use Hypervel\Queue\Events\JobRetryRequested; @@ -14,10 +17,12 @@ use Hypervel\Queue\Failed\NullFailedJobProvider; use Hypervel\Queue\QueueManager; use Hypervel\Queue\SqsQueue; +use Hypervel\Queue\SyncQueue; use Hypervel\Support\Collection; use Hypervel\Support\LazyCollection; use Hypervel\Tests\TestCase; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestWith; use RuntimeException; use stdClass; @@ -160,7 +165,9 @@ public function testItResetsAttemptsCountWhenRetryingAJob(): void $this->runRetryCommand(['id' => ['1']], $failer, ['database' => $queue]); } - public function testRefreshesTheRetryUntilTimestampWhenTheJobDefinesRetryUntil(): void + #[TestWith([false], 'unmarked object payload')] + #[TestWith([true], 'custom object handler')] + public function testRefreshesTheRetryUntilTimestampWhenTheJobDefinesRetryUntil(bool $customHandler): void { $failer = m::mock(FailedJobProviderInterface::class); $queue = m::mock(QueueContract::class); @@ -173,6 +180,14 @@ public function testRefreshesTheRetryUntilTimestampWhenTheJobDefinesRetryUntil() job: new QueueRetryCommandTestJobWithRetryUntil(retryUntil: 1234567890) ); + if ($customHandler) { + $producer = new QueueRetryCommandCustomQueue; + $producer->setContainer(new Application); + $payload = json_decode($producer->payloadFor(new QueueRetryCommandTestJobWithRetryUntil(1234567890)), true); + $payload['retryUntil'] = 0; + $job->payload = json_encode($payload); + } + $failer->shouldReceive('find')->once()->with('1')->andReturn($job); $queue->shouldReceive('pushRaw')->once()->with(m::on(function (string $payload): bool { return json_decode($payload, true)['retryUntil'] === 1234567890; @@ -533,6 +548,119 @@ public function testStopsRetryingWhenAJobInACollectionFailsToBePushed(): void $this->assertSame(['job-3'], array_keys($pendingJobs)); } + public function testRetriesRemainingJobsWhenAJobsModelNoLongerExists(): void + { + $failer = m::mock(FailedJobProviderInterface::class); + $queue = m::mock(QueueContract::class); + + $missing = $this->failedJob(id: 'job-1', connection: 'database', queue: 'default', job: new QueueRetryCommandTestJobWithMissingModel); + + $jobs = new Collection([ + 'job-1' => $missing, + 'job-2' => $this->failedJob(id: 'job-2', connection: 'database', queue: 'default'), + ]); + + $failer->shouldReceive('find')->once()->with('batch')->andReturn($jobs); + + $queue->shouldReceive('pushRaw')->once()->with($missing->payload, 'default', []); + $failer->shouldReceive('forget')->once()->with('job-1'); + + $queue->shouldReceive('pushRaw')->once()->with($this->retriedPayload(id: 'job-2'), 'default', []); + $failer->shouldReceive('forget')->once()->with('job-2'); + + $this->runRetryCommand(['id' => ['batch']], $failer, ['database' => $queue]); + } + + public function testRetriesStringJobsWithoutInterpretingTheirCommandData(): void + { + $failer = m::mock(FailedJobProviderInterface::class); + $queue = m::mock(QueueContract::class); + $data = ['command' => 'backup:run', 'commandName' => 'backup:run']; + $job = $this->failedJob(id: '1', connection: 'database', queue: 'default'); + $job->payload = (new QueueRetryCommandCustomQueue)->payloadFor('RunCommand@handle', $data); + + $failer->shouldReceive('find')->once()->with('1')->andReturn($job); + $queue->shouldReceive('pushRaw')->once()->with($job->payload, 'default', []); + $failer->shouldReceive('forget')->once()->with('1'); + + $this->runRetryCommand(['id' => ['1']], $failer, ['database' => $queue]); + } + + public function testRetriesJobWithoutQueueableOptionsWhenItsModelNoLongerExists(): void + { + $failer = m::mock(FailedJobProviderInterface::class); + $sqs = m::mock(SqsClient::class); + $queue = new SqsQueue($sqs, 'default', 'https://sqs.example.test'); + + $job = $this->failedJob(id: '1', connection: 'sqs', queue: 'default', job: new QueueRetryCommandTestJobWithMissingModel); + + $failer->shouldReceive('find')->once()->with('1')->andReturn($job); + $sqs->shouldReceive('sendMessage')->once()->with([ + 'QueueUrl' => 'https://sqs.example.test/default', + 'MessageBody' => $job->payload, + ])->andReturn(new Result(['MessageId' => 'message-1'])); + $failer->shouldReceive('forget')->once()->with('1'); + + $this->runRetryCommand(['id' => ['1']], $failer, ['sqs' => $queue]); + } + + #[DataProvider('jobsWithoutRestorableObjects')] + public function testRetriesSqsJobsWithDefaultOptionsWhenNoObjectCanBeRestored(string $queueName, array $payload): void + { + $failer = m::mock(FailedJobProviderInterface::class); + $sqs = m::mock(SqsClient::class); + $queue = new SqsQueue($sqs, $queueName, 'https://sqs.example.test'); + $job = $this->failedJob( + id: '1', + connection: 'sqs', + queue: $queueName, + payload: $payload, + job: new QueueRetryCommandTestJobWithMissingModel, + ); + + $failer->shouldReceive('find')->once()->with('1')->andReturn($job); + $sqs->shouldReceive('sendMessage')->once()->with(m::on(function (array $message) use ($queueName, $job): bool { + $expected = [ + 'QueueUrl' => 'https://sqs.example.test/' . $queueName, + 'MessageBody' => $job->payload, + ]; + + if (str_ends_with($queueName, '.fifo')) { + $this->assertIsString($message['MessageDeduplicationId']); + unset($message['MessageDeduplicationId']); + $expected['MessageGroupId'] = $queueName; + } + + $this->assertSame($expected, $message); + + return true; + }))->andReturn(new Result(['MessageId' => 'message-1'])); + $failer->shouldReceive('forget')->once()->with('1'); + + $this->runRetryCommand(['id' => ['1']], $failer, ['sqs' => $queue]); + } + + /** + * Provide retry payloads whose queue options need no restored object. + */ + public static function jobsWithoutRestorableObjects(): iterable + { + yield 'missing model on FIFO' => ['default.fifo', []]; + yield 'string job with command data on FIFO' => ['default.fifo', json_decode( + (new QueueRetryCommandCustomQueue)->payloadFor('RunCommand@handle', ['command' => 'backup:run', 'commandName' => 'backup:run']), + true, + )]; + + $incomplete = ['data' => ['commandName' => 'RemovedJob', 'command' => 'O:10:"RemovedJob":0:{}']]; + + yield 'removed class on FIFO' => ['default.fifo', $incomplete]; + yield 'removed class on standard' => ['default', $incomplete]; + yield 'removed class without optional command name' => ['default.fifo', [ + 'job' => 'CustomObjectHandler@call', + 'data' => ['command' => 'O:10:"RemovedJob":0:{}'], + ]]; + } + public function testDispatchesTheEventBeforePushingAndForgetsTheJobAfterwards(): void { $failer = m::mock(FailedJobProviderInterface::class); @@ -711,6 +839,25 @@ private function runRetryCommand(array $input, FailedJobProviderInterface $faile } } +class QueueRetryCommandCustomQueue extends SyncQueue +{ + /** + * Build payloads through the real queue producer. + */ + public function payloadFor(object|string $job, mixed $data = ''): string + { + return $this->createPayload($job, 'default', $data); + } + + /** + * Replace the object handler as a custom queue driver may do. + */ + protected function createObjectPayload(object $job, ?string $queue): array + { + return array_replace(parent::createObjectPayload($job, $queue), ['job' => 'CustomObjectHandler@call']); + } +} + class QueueRetryCommandTestJob { } @@ -732,3 +879,14 @@ public function retryUntil(): int return $this->retryUntil; } } + +class QueueRetryCommandTestJobWithMissingModel +{ + /** + * Restore a job whose model has been deleted. + */ + public function __unserialize(array $data): void + { + throw (new ModelNotFoundException)->setModel('App\Models\User', [1]); + } +} diff --git a/tests/Routing/RoutingRouteTest.php b/tests/Routing/RoutingRouteTest.php index a11ad345f0..2d71af0b80 100644 --- a/tests/Routing/RoutingRouteTest.php +++ b/tests/Routing/RoutingRouteTest.php @@ -45,6 +45,7 @@ use Hypervel\Tests\Routing\Fixtures\CategoryBackedEnum; use Laravel\SerializableClosure\SerializableClosure; use LogicException; +use PHPUnit\Framework\Attributes\TestWith; use stdClass; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -222,6 +223,35 @@ public function testMiddlewareCanBeSkipped(): void $this->assertSame('hello', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent()); } + public function testNamedClosureMiddlewareCanBeSkippedAlongsideClassMiddleware(): void + { + $router = $this->getRouter(); + $router->aliasMiddleware('optional', fn ($request, $next) => $next($request)); + + $route = $router->get('foo', fn (): string => 'hello') + ->middleware(['optional', RoutingTestMiddlewareGroupTwo::class]) + ->withoutMiddleware('optional'); + + $this->assertSame([RoutingTestMiddlewareGroupTwo::class], $router->gatherRouteMiddleware($route)); + } + + public function testMiddlewareWithParametersCanBeSkipped(): void + { + $router = $this->getRouter(); + $router->aliasMiddleware('two', RoutingTestMiddlewareGroupTwo::class); + + $router->get('foo/bar', ['middleware' => RoutingTestMiddlewareGroupTwo::class . ':taylor', function (): string { + return 'hello'; + }])->withoutMiddleware(RoutingTestMiddlewareGroupTwo::class); + + $router->get('foo/baz', ['middleware' => 'two:taylor', function (): string { + return 'hello'; + }])->withoutMiddleware('two'); + + $this->assertSame('hello', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent()); + $this->assertSame('hello', $router->dispatch(Request::create('foo/baz', 'GET'))->getContent()); + } + public function testMiddlewareCanBeSkippedFromResources(): void { $router = $this->getRouter(); @@ -820,6 +850,48 @@ public function testRoutesDontMatchNonMatchingPathsWithLeadingOptionals(): void $this->assertSame('25', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent()); } + #[TestWith(['dispatch'])] + #[TestWith(['dispatchToCallback'])] + public function testCurrentRouteIsResetWhenTheNextRequestDoesNotMatch(string $method): void + { + $router = $this->getRouter(); + $router->get('invoices/{invoice}', fn (string $invoice): string => $invoice)->name('invoices.show'); + + $this->assertSame('123', $router->dispatch(Request::create('invoices/123', 'GET'))->getContent()); + $this->assertSame('invoices.show', $router->currentRouteName()); + + try { + if ($method === 'dispatchToCallback') { + $router->dispatchToCallback(Request::create('missing', 'GET'), static fn (): string => 'unexpected'); + } else { + $router->{$method}(Request::create('missing', 'GET')); + } + + $this->fail('A NotFoundHttpException should have been thrown.'); + } catch (NotFoundHttpException) { + } + + $this->assertNull($router->current()); + $this->assertNull($router->currentRouteName()); + $this->assertFalse($router->is('invoices.show')); + } + + public function testFailedNestedRouteDispatchKeepsTheCurrentRoute(): void + { + $router = $this->getRouter(); + $router->get('invoices/{invoice}', function () use ($router): string { + try { + $router->dispatchToRoute(Request::create('missing', 'GET')); + $this->fail('A NotFoundHttpException should have been thrown.'); + } catch (NotFoundHttpException) { + } + + return $router->input('invoice'); + }); + + $this->assertSame('123', $router->dispatch(Request::create('invoices/123', 'GET'))->getContent()); + } + public function testRoutesDontMatchNonMatchingDomain(): void { $this->expectException(NotFoundHttpException::class); diff --git a/tests/Session/SessionGarbageCollectCommandTest.php b/tests/Session/SessionGarbageCollectCommandTest.php new file mode 100644 index 0000000000..3255675be5 --- /dev/null +++ b/tests/Session/SessionGarbageCollectCommandTest.php @@ -0,0 +1,31 @@ +expects('gc')->with(420)->andReturn($result); + $this->app->make(SessionManager::class)->extend('custom', fn () => $handler); + + $this->artisan('session:garbage-collect') + ->expectsOutputToContain('Session garbage collector ' . $message . '.') + ->assertExitCode($exitCode); + } +} diff --git a/tests/Support/OnceTest.php b/tests/Support/OnceTest.php index 4a5652966a..35bded5b22 100644 --- a/tests/Support/OnceTest.php +++ b/tests/Support/OnceTest.php @@ -126,6 +126,32 @@ public function rand(string $letter): string $this->assertSame($results[0], $results[1]); } + public function testIsNotMemoizedWhenObjectIdIsReused(): void + { + $instance = new class { + /** + * Get the user's name once for each captured object. + */ + public function name(object $user): string + { + return once(fn (): string => $user->name); + } + }; + + $names = array_map(fn (int $index): string => "user {$index}", range(1, 10)); + + $results = []; + + foreach ($names as $name) { + $user = new stdClass; + $user->name = $name; + + $results[] = $instance->name($user); + } + + $this->assertSame($names, $results); + } + public function testUsageOfThis(): void { $instance = new MyClass; @@ -424,26 +450,14 @@ public function testOnceDifferentiatesClosureUses(): void public function testOnceDifferentiatesObjectsFromScalarAndArrayCaptures(): void { $object = new stdClass; + $id = spl_object_id($object); - // The first object token after framework cleanup is 1. $results = array_map( fn (mixed $value): string => once(fn (): string => get_debug_type($value)), - [$object, 1, spl_object_id($object), '1', ['id' => 1]], - ); - - $this->assertSame(['stdClass', 'int', 'int', 'string', 'array'], $results); - } - - public function testOnceDoesNotReuseResultsForTemporaryObjects(): void - { - $format = static fn (object $value): int => once(fn (): int => $value->number); - - $results = array_map( - static fn (int $number): int => $format((object) ['number' => $number]), - [1, 2, 3], + [$object, $id, (string) $id, ['id' => $id]], ); - $this->assertSame([1, 2, 3], $results); + $this->assertSame(['stdClass', 'int', 'string', 'array'], $results); } public function testOnceIsCoroutineScoped(): void diff --git a/tests/Support/SupportTestingBusFakeTest.php b/tests/Support/SupportTestingBusFakeTest.php index 1b08415166..04e6bc48aa 100644 --- a/tests/Support/SupportTestingBusFakeTest.php +++ b/tests/Support/SupportTestingBusFakeTest.php @@ -6,6 +6,7 @@ use Hypervel\Bus\Batch; use Hypervel\Bus\Batchable; +use Hypervel\Bus\Dispatcher as BusDispatcher; use Hypervel\Bus\DispatchLockContext; use Hypervel\Bus\Queueable; use Hypervel\Container\Container; @@ -691,6 +692,18 @@ public function testAssertNothingChainedFails(): void } } + public function testDispatchingAfterResponsesCanBeToggledOnTheFake(): void + { + $dispatcher = m::mock(BusDispatcher::class); + $dispatcher->expects('withoutDispatchingAfterResponses')->andReturnSelf(); + $dispatcher->expects('withDispatchingAfterResponses')->andReturnSelf(); + + $fake = new BusFake($dispatcher); + + $this->assertSame($fake, $fake->withoutDispatchingAfterResponses()); + $this->assertSame($fake, $fake->withDispatchingAfterResponses()); + } + public function testAssertDispatchedWithIgnoreClass(): void { $dispatcher = m::mock(QueueingDispatcher::class); diff --git a/tests/Support/SupportTestingMailFakeTest.php b/tests/Support/SupportTestingMailFakeTest.php index a9b750dc74..00827118d6 100644 --- a/tests/Support/SupportTestingMailFakeTest.php +++ b/tests/Support/SupportTestingMailFakeTest.php @@ -386,6 +386,37 @@ public function testAssertNothingQueued(): void } } + public function testRawHtmlAndPlainMessagesAreNotSent(): void + { + $this->mailManager->shouldNotReceive('raw'); + $this->mailManager->shouldNotReceive('html'); + $this->mailManager->shouldNotReceive('plain'); + + $callback = fn ($message) => $message->to('taylor@hypervel.com'); + + $this->fake->raw('Hello', $callback); + $this->fake->html('

Hello

', $callback); + $this->fake->plain('emails.welcome', [], $callback); + + $this->fake->assertNothingOutgoing(); + + $this->fake->mailer('ses')->raw('raw', $callback); + $rawNext = new MailableStub; + $this->fake->send($rawNext); + + $this->fake->mailer('ses')->html('

html

', $callback); + $htmlNext = new MailableStub; + $this->fake->send($htmlNext); + + $this->fake->mailer('ses')->plain('mail.view', [], $callback); + $plainNext = new MailableStub; + $this->fake->send($plainNext); + + $this->assertTrue($rawNext->usesMailer('smtp')); + $this->assertTrue($htmlNext->usesMailer('smtp')); + $this->assertTrue($plainNext->usesMailer('smtp')); + } + public function testAssertOutgoingCount(): void { $this->fake->assertNothingOutgoing(); @@ -637,32 +668,6 @@ public function testFailedQueueSelectionConsumesExplicitMailer(): void $this->assertTrue($mailable->usesMailer('smtp')); } - public function testRawHtmlAndPlainConsumeMailerWithoutCallingTheRealManager(): void - { - $this->mailManager->shouldNotReceive('raw'); - $this->mailManager->shouldNotReceive('html'); - $this->mailManager->shouldNotReceive('plain'); - - $callback = static function (): void { - }; - - $this->fake->mailer('ses')->raw('raw', $callback); - $rawNext = new MailableStub; - $this->fake->send($rawNext); - - $this->fake->mailer('ses')->html('

html

', $callback); - $htmlNext = new MailableStub; - $this->fake->send($htmlNext); - - $this->fake->mailer('ses')->plain('mail.view', [], $callback); - $plainNext = new MailableStub; - $this->fake->send($plainNext); - - $this->assertTrue($rawNext->usesMailer('smtp')); - $this->assertTrue($htmlNext->usesMailer('smtp')); - $this->assertTrue($plainNext->usesMailer('smtp')); - } - public function testQueueHelpersAcceptEnumsWithoutCallingTheRealManager(): void { $this->mailManager->shouldNotReceive('onQueue'); diff --git a/tests/Telescope/Watchers/JobWatcherTest.php b/tests/Telescope/Watchers/JobWatcherTest.php index d6295f5e40..fd367a7b07 100644 --- a/tests/Telescope/Watchers/JobWatcherTest.php +++ b/tests/Telescope/Watchers/JobWatcherTest.php @@ -28,7 +28,9 @@ use Hypervel\Testbench\Attributes\WithMigration; use Hypervel\Testbench\Factories\UserFactory; use Hypervel\Tests\Telescope\FeatureTestCase; +use JsonSerializable; use Mockery as m; +use PHPUnit\Framework\Attributes\TestWith; use RuntimeException; use Throwable; @@ -175,6 +177,32 @@ public function testItHandlesPushedJobs(): void $this->assertSame(['framework' => 'Laravel'], $entry->content['data']); } + #[TestWith([ + ['command' => 'backup:run', 'commandName' => 'backup:run', 'batchId' => ['external' => 42]], + ['command' => 'backup:run', 'commandName' => 'backup:run', 'batchId' => ['external' => 42]], + ], 'caller metadata names')] + #[TestWith(['', ['']], 'default string data')] + #[TestWith([new PushedJobData, ['command' => 'backup:run']], 'JSON serializable data')] + public function testStringJobsPreserveCallerDataWithoutInterpretingItAsMetadata(mixed $data, array $expectedData): void + { + $this->app->make(QueueManager::class) + ->connection('database') + ->push(MyPushedJobClass::class . '@handle', $data); + $this->artisan('queue:work', [ + 'connection' => 'database', + '--once' => true, + ]); + + $entry = $this->loadTelescopeEntries()->first(); + $this->assertSame(EntryType::JOB, $entry->type); + $this->assertSame('processed', $entry->content['status']); + $this->assertSame('database', $entry->content['connection']); + $this->assertSame(MyPushedJobClass::class, $entry->content['name']); + $this->assertSame('default', $entry->content['queue']); + $this->assertSame($expectedData, $entry->content['data']); + $this->assertNull($entry->family_hash); + } + public function testJobCanHandleDeletedSerializedModel(): void { $user = UserFactory::new()->create(); @@ -431,6 +459,24 @@ public function fire(Job $job, array $data) { throw new PushedJobFailedException; } + + /** + * Handle a string job successfully. + */ + public function handle(Job $job, mixed $data): void + { + } +} + +class PushedJobData implements JsonSerializable +{ + /** + * Publish caller data independently of the object's properties. + */ + public function jsonSerialize(): array + { + return ['command' => 'backup:run']; + } } class PushedJobFailedException extends Exception diff --git a/tests/Validation/ValidationAnyOfRuleTest.php b/tests/Validation/ValidationAnyOfRuleTest.php index c280259676..781ebe9e65 100644 --- a/tests/Validation/ValidationAnyOfRuleTest.php +++ b/tests/Validation/ValidationAnyOfRuleTest.php @@ -10,6 +10,7 @@ use Hypervel\Translation\Translator; use Hypervel\Validation\Rule; use Hypervel\Validation\Validator; +use PHPUnit\Framework\Attributes\DataProvider; enum TaggedUnionDiscriminatorType: string { @@ -133,6 +134,62 @@ public function testBasicStringValidation(): void $this->assertFalse($validator->passes()); } + public function testAssociativeArrayValueAgainstPlainRules(): void + { + $rule = Rule::anyOf([ + ['string', 'email'], + ['string', 'alpha_dash', 'min:6'], + ]); + + $validator = new Validator($this->app->make('translator'), [ + 'username' => ['x' => 1], + ], ['username' => ['required', $rule]]); + $this->assertFalse($validator->passes()); + + $validator = new Validator($this->app->make('translator'), [ + 'username' => 'taylor@hypervel.com', + ], ['username' => ['required', $rule]]); + $this->assertTrue($validator->passes()); + + $validator = new Validator($this->app->make('translator'), [ + 'data' => ['x' => 1], + ], ['data' => Rule::anyOf(['string', 'array'])]); + $this->assertTrue($validator->passes()); + } + + #[DataProvider('alternativeRuleShapes')] + public function testAlternativeRulesPreserveTheirDataShape(array $rules, mixed $value, bool $passes): void + { + $validator = new Validator($this->app->make('translator'), ['data' => $value], [ + 'data' => Rule::anyOf([$rules]), + ]); + + $this->assertSame($passes, $validator->passes()); + } + + /** + * Provide keyed alternatives and values with different shapes. + */ + public static function alternativeRuleShapes(): iterable + { + yield 'numeric field rejects a scalar' => [[1 => 'array'], [1 => 'invalid'], false]; + yield 'numeric field accepts an array' => [[1 => 'array'], [1 => ['valid']], true]; + yield 'wildcard rejects invalid list item' => [['*.email' => 'email'], [['email' => 'invalid']], false]; + yield 'wildcard accepts valid list item' => [['*.email' => 'email'], [['email' => 'test@example.com']], true]; + yield 'wildcard rejects a present null' => [['*' => 'required'], null, false]; + yield 'wildcard has no items in an empty array' => [['*' => 'required'], [], true]; + yield 'keyed rules reject a scalar missing required fields' => [['email' => 'required|email'], 'invalid', false]; + } + + public function testNestedArraysCanUseAnArrayAlternativeAfterExists(): void + { + $validator = new Validator($this->app->make('translator'), ['data' => ['a' => [1], 'b' => [2]]], [ + 'data' => Rule::anyOf(['exists:users,id', 'array']), + ]); + + $this->assertTrue($validator->passes()); + } + public function testTaggedUnionObjects(): void { $validator = new Validator($this->app->make('translator'), [ diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 47a7a77abd..9e00666d0d 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -4672,6 +4672,12 @@ public function testValidateIn(): void $v = new Validator($trans, ['name' => ['foo', []]], ['name' => 'Array|In:foo,bar']); $this->assertFalse($v->passes()); + + $v = new Validator($trans, ['name' => [new stdClass]], ['name' => 'Array|In:foo,bar']); + $this->assertFalse($v->passes()); + + $v = new Validator($trans, ['name' => [new Stringable('foo')]], ['name' => 'Array|In:foo,bar']); + $this->assertTrue($v->passes()); } public function testValidateNotIn(): void @@ -5025,6 +5031,12 @@ public function testValidationExists(): void $mock->expects('getMultiCount')->with('users', 'email_addr', ['foo', 'foo'], [])->andReturn(1); $v->setPresenceVerifier($mock); $this->assertTrue($v->passes()); + + $v = new Validator($trans, ['email' => ['a' => ['foo']]], ['email' => 'exists:users,email_addr']); + $this->assertFalse($v->passes()); + + $v = new Validator($trans, ['email' => [new stdClass, new stdClass]], ['email' => 'exists:users,email_addr']); + $this->assertFalse($v->passes()); } public function testValidationExistsIsNotCalledUnnecessarily(): void diff --git a/types/Database/Eloquent/Relations.php b/types/Database/Eloquent/Relations.php index f4dad7dc81..d72dfd617c 100644 --- a/types/Database/Eloquent/Relations.php +++ b/types/Database/Eloquent/Relations.php @@ -87,6 +87,8 @@ function test(User $user, Post $post, Comment $comment, ChildUser $child): void assertType('Hypervel\Types\Relations\Role', $user->roles()->firstOrCreate()); assertType('Hypervel\Types\Relations\Role', $user->roles()->create()); assertType('Hypervel\Types\Relations\Role', $user->roles()->createQuietly()); + assertType('Hypervel\Types\Relations\Role', $user->roles()->forceCreate()); + assertType('Hypervel\Types\Relations\Role', $user->roles()->forceCreateQuietly()); assertType('Hypervel\Types\Relations\Role', $user->roles()->createOrFirst()); assertType('Hypervel\Types\Relations\Role', $user->roles()->updateOrCreate([])); assertType('Hypervel\Types\Relations\Role', $user->roles()->save(new Role));